mirror of
https://github.com/eolinker/apinto
synced 2025-11-03 02:53:33 +08:00
fasthttp替换完成
This commit is contained in:
396
node/http-context/body-request.go
Normal file
396
node/http-context/body-request.go
Normal file
@@ -0,0 +1,396 @@
|
||||
package http_context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
|
||||
goku_plugin "github.com/eolinker/goku-standard-plugin"
|
||||
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
var (
|
||||
errorNotForm = errors.New("contentType is not Form")
|
||||
errorNotMultipart = errors.New("contentType is not Multipart")
|
||||
errorNotAllowRaw = errors.New("contentType is not allow Raw")
|
||||
)
|
||||
|
||||
//BodyRequestHandler body请求处理器
|
||||
type BodyRequestHandler struct {
|
||||
form url.Values
|
||||
rawBody []byte
|
||||
orgContentParam map[string]string
|
||||
contentType string
|
||||
files map[string]*goku_plugin.FileHeader
|
||||
|
||||
isInit bool
|
||||
isWriteRaw bool
|
||||
|
||||
object interface{}
|
||||
}
|
||||
|
||||
//Files 获取文件参数
|
||||
func (b *BodyRequestHandler) Files() (map[string]*goku_plugin.FileHeader, error) {
|
||||
|
||||
err := b.Parse()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.files, nil
|
||||
|
||||
}
|
||||
|
||||
//Parse 解析
|
||||
func (b *BodyRequestHandler) Parse() error {
|
||||
if b.isInit {
|
||||
return nil
|
||||
}
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
switch contentType {
|
||||
case goku_plugin.JSON:
|
||||
{
|
||||
e := json.Unmarshal(b.rawBody, &b.object)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
case goku_plugin.AppLicationXML, goku_plugin.TextXML:
|
||||
{
|
||||
e := xml.Unmarshal(b.rawBody, &b.object)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case goku_plugin.MultipartForm:
|
||||
{
|
||||
r, err := multipartReader(b.contentType, false, b.rawBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form, err := r.ReadForm(defaultMultipartMemory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.form == nil {
|
||||
b.form = make(url.Values)
|
||||
}
|
||||
for k, v := range form.Value {
|
||||
b.form[k] = append(b.form[k], v...)
|
||||
}
|
||||
|
||||
b.files = make(map[string]*goku_plugin.FileHeader)
|
||||
for k, fs := range form.File {
|
||||
|
||||
if len(fs) > 0 {
|
||||
file, err := fs[0].Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileData, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.files[k] = &goku_plugin.FileHeader{
|
||||
FileName: fs[0].Filename,
|
||||
Data: fileData,
|
||||
Header: fs[0].Header,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.object = b.form
|
||||
}
|
||||
case goku_plugin.FormData:
|
||||
{
|
||||
form, err := url.ParseQuery(string(b.rawBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b.form == nil {
|
||||
b.form = form
|
||||
} else {
|
||||
for k, v := range form {
|
||||
b.form[k] = append(b.form[k], v...)
|
||||
}
|
||||
|
||||
}
|
||||
b.object = b.form
|
||||
|
||||
}
|
||||
}
|
||||
b.isInit = true
|
||||
return nil
|
||||
}
|
||||
|
||||
//GetForm 获取表单参数
|
||||
func (b *BodyRequestHandler) GetForm(key string) string {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return ""
|
||||
}
|
||||
b.Parse()
|
||||
|
||||
if !b.isInit || b.form == nil {
|
||||
return ""
|
||||
}
|
||||
return b.form.Get(key)
|
||||
}
|
||||
|
||||
//GetFile 获取文件参数
|
||||
func (b *BodyRequestHandler) GetFile(key string) (file *goku_plugin.FileHeader, has bool) {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !b.isInit || b.files == nil {
|
||||
return nil, false
|
||||
}
|
||||
f, has := b.files[key]
|
||||
return f, has
|
||||
}
|
||||
|
||||
//SetToForm 设置表单参数
|
||||
func (b *BodyRequestHandler) SetToForm(key, value string) error {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return errorNotForm
|
||||
}
|
||||
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.isWriteRaw = false
|
||||
|
||||
if b.form == nil {
|
||||
b.form = make(url.Values)
|
||||
}
|
||||
b.form.Set(key, value)
|
||||
b.isWriteRaw = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//AddForm 新增表单参数
|
||||
func (b *BodyRequestHandler) AddForm(key, value string) error {
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return errorNotForm
|
||||
}
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.isWriteRaw = false
|
||||
|
||||
if b.form == nil {
|
||||
b.form = make(url.Values)
|
||||
}
|
||||
b.form.Add(key, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
//AddFile 新增文件参数
|
||||
func (b *BodyRequestHandler) AddFile(key string, file *goku_plugin.FileHeader) error {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return errorNotMultipart
|
||||
}
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.isWriteRaw = false
|
||||
if file == nil && b.files != nil {
|
||||
delete(b.files, key)
|
||||
return nil
|
||||
}
|
||||
if b.files == nil {
|
||||
b.files = make(map[string]*goku_plugin.FileHeader)
|
||||
}
|
||||
b.files[key] = file
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//Clone 克隆body
|
||||
func (b *BodyRequestHandler) Clone() *BodyRequestHandler {
|
||||
rawbody, _ := b.RawBody()
|
||||
return NewBodyRequestHandler(b.contentType, rawbody)
|
||||
|
||||
}
|
||||
|
||||
//ContentType 获取contentType
|
||||
func (b *BodyRequestHandler) ContentType() string {
|
||||
return b.contentType
|
||||
}
|
||||
|
||||
//BodyForm 获取表单参数
|
||||
func (b *BodyRequestHandler) BodyForm() (url.Values, error) {
|
||||
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.form, nil
|
||||
}
|
||||
|
||||
//BodyInterface 获取请求体对象
|
||||
func (b *BodyRequestHandler) BodyInterface() (interface{}, error) {
|
||||
err := b.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.object, nil
|
||||
}
|
||||
|
||||
//RawBody 获取raw数据
|
||||
func (b *BodyRequestHandler) RawBody() ([]byte, error) {
|
||||
|
||||
err := b.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.rawBody, nil
|
||||
|
||||
}
|
||||
|
||||
//encoder encode
|
||||
func (b *BodyRequestHandler) Encode() error {
|
||||
if b.isWriteRaw {
|
||||
return nil
|
||||
}
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
b.isWriteRaw = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(b.files) > 0 {
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
for name, file := range b.files {
|
||||
part, err := writer.CreateFormFile(name, file.FileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = part.Write(file.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range b.form {
|
||||
temp := make(url.Values)
|
||||
temp[key] = values
|
||||
value := temp.Encode()
|
||||
err := writer.WriteField(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.contentType = writer.FormDataContentType()
|
||||
b.rawBody = body.Bytes()
|
||||
b.isWriteRaw = true
|
||||
} else {
|
||||
if b.form != nil {
|
||||
b.rawBody = []byte(b.form.Encode())
|
||||
} else {
|
||||
b.rawBody = make([]byte, 0, 0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//SetForm 设置表单参数
|
||||
func (b *BodyRequestHandler) SetForm(values url.Values) error {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return errorNotForm
|
||||
}
|
||||
b.Parse()
|
||||
b.form = values
|
||||
b.isWriteRaw = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SetFile 设置文件参数
|
||||
func (b *BodyRequestHandler) SetFile(files map[string]*goku_plugin.FileHeader) error {
|
||||
|
||||
contentType, _, _ := mime.ParseMediaType(b.contentType)
|
||||
if contentType != goku_plugin.FormData && contentType != goku_plugin.MultipartForm {
|
||||
return errorNotForm
|
||||
}
|
||||
b.Parse()
|
||||
b.files = files
|
||||
// b.form = values
|
||||
b.isWriteRaw = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SetRaw 设置raw数据
|
||||
func (b *BodyRequestHandler) SetRaw(contentType string, body []byte) {
|
||||
|
||||
b.rawBody, b.contentType, b.isInit, b.isWriteRaw = body, contentType, false, true
|
||||
_, b.orgContentParam, _ = mime.ParseMediaType(contentType)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
//NewBodyRequestHandler 创建body请求处理器
|
||||
func NewBodyRequestHandler(contentType string, body []byte) *BodyRequestHandler {
|
||||
b := new(BodyRequestHandler)
|
||||
b.SetRaw(contentType, body)
|
||||
return b
|
||||
}
|
||||
|
||||
func multipartReader(contentType string, allowMixed bool, raw []byte) (*multipart.Reader, error) {
|
||||
|
||||
if contentType == "" {
|
||||
return nil, http.ErrNotMultipart
|
||||
}
|
||||
d, params, err := mime.ParseMediaType(contentType)
|
||||
if err != nil || !(d == "multipart/form-data" || allowMixed && d == "multipart/mixed") {
|
||||
return nil, http.ErrNotMultipart
|
||||
}
|
||||
boundary, ok := params["boundary"]
|
||||
if !ok {
|
||||
return nil, http.ErrMissingBoundary
|
||||
}
|
||||
body := ioutil.NopCloser(bytes.NewBuffer(raw))
|
||||
return multipart.NewReader(body, boundary), nil
|
||||
}
|
||||
24
node/http-context/body.go
Normal file
24
node/http-context/body.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package http_context
|
||||
|
||||
//BodyHandler 请求体处理器
|
||||
type BodyHandler struct {
|
||||
body []byte
|
||||
}
|
||||
|
||||
//GetBody 获取body内容
|
||||
func (r *BodyHandler) GetBody() []byte {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r.body
|
||||
}
|
||||
|
||||
//SetBody 设置body内容
|
||||
func (r *BodyHandler) SetBody(body []byte) {
|
||||
r.body = body
|
||||
}
|
||||
|
||||
//NewBodyHandler 创建BodyHandler
|
||||
func NewBodyHandler(body []byte) *BodyHandler {
|
||||
return &BodyHandler{body: body}
|
||||
}
|
||||
221
node/http-context/context.go
Normal file
221
node/http-context/context.go
Normal file
@@ -0,0 +1,221 @@
|
||||
package http_context
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
goku_plugin "github.com/eolinker/goku-standard-plugin"
|
||||
|
||||
access_field "github.com/eolinker/goku-eosc/node/common/access-field"
|
||||
"github.com/eolinker/goku-eosc/utils"
|
||||
)
|
||||
|
||||
var _ goku_plugin.ContextProxy = (*Context)(nil)
|
||||
|
||||
//Context context
|
||||
type Context struct {
|
||||
responseWriter *fasthttp.RequestCtx
|
||||
*CookiesHandler
|
||||
*PriorityHeader
|
||||
*StatusHandler
|
||||
*StoreHandler
|
||||
RequestOrg *RequestReader
|
||||
ProxyRequest *Request
|
||||
ProxyResponseHandler *ResponseReader
|
||||
Body []byte
|
||||
|
||||
requestID string
|
||||
|
||||
RestfulParam map[string]string
|
||||
LogFields *access_field.Fields
|
||||
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
func (ctx *Context) Labels() map[string]string {
|
||||
if ctx.labels == nil {
|
||||
ctx.labels = map[string]string{}
|
||||
}
|
||||
return ctx.labels
|
||||
}
|
||||
|
||||
func (ctx *Context) SetLabels(labels map[string]string) {
|
||||
if ctx.labels == nil {
|
||||
ctx.labels = make(map[string]string)
|
||||
}
|
||||
if labels != nil {
|
||||
for k, v := range labels {
|
||||
ctx.labels[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Finish finish
|
||||
func (ctx *Context) Finish() (statusCode int) {
|
||||
defer ctx.responseWriter.Done()
|
||||
header := ctx.PriorityHeader.header
|
||||
|
||||
statusCode = ctx.StatusHandler.code
|
||||
if statusCode == 0 {
|
||||
statusCode = 504
|
||||
}
|
||||
ctx.LogFields.StatusCode = statusCode
|
||||
bodyAllowed := true
|
||||
switch {
|
||||
case statusCode >= 100 && statusCode <= 199:
|
||||
bodyAllowed = false
|
||||
break
|
||||
case statusCode == 204:
|
||||
bodyAllowed = false
|
||||
break
|
||||
case statusCode == 304:
|
||||
bodyAllowed = false
|
||||
break
|
||||
}
|
||||
|
||||
if ctx.PriorityHeader.appendHeader != nil {
|
||||
for k, vs := range ctx.PriorityHeader.appendHeader.header {
|
||||
for _, v := range vs {
|
||||
header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.PriorityHeader.setHeader != nil {
|
||||
for k, vs := range ctx.PriorityHeader.setHeader.header {
|
||||
header.Del(k)
|
||||
for _, v := range vs {
|
||||
header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, vs := range ctx.PriorityHeader.header {
|
||||
if k == "Content-Length" {
|
||||
continue
|
||||
//vs = []string{strconv.Itoa(len(string(ctx.body)))}
|
||||
}
|
||||
for _, v := range vs {
|
||||
ctx.responseWriter.Response.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
if ctx.ProxyResponseHandler.header.Get("Content-Type") == "" {
|
||||
ctx.responseWriter.Response.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if ctx.ProxyResponseHandler.header.Get("Content-Encoding") == "gzip" {
|
||||
body, err := utils.GzipCompress(ctx.Body)
|
||||
if err == nil {
|
||||
ctx.Body = body
|
||||
}
|
||||
}
|
||||
ctx.responseWriter.SetStatusCode(statusCode)
|
||||
ctx.LogFields.ResponseHeader = utils.HeaderToString(ctx.header)
|
||||
|
||||
if !bodyAllowed {
|
||||
return statusCode
|
||||
}
|
||||
ctx.responseWriter.SetBody(ctx.Body)
|
||||
ctx.LogFields.ResponseMsg = string(ctx.Body)
|
||||
ctx.LogFields.ResponseMsgSize = len(ctx.Body)
|
||||
return statusCode
|
||||
}
|
||||
|
||||
//RequestId 请求ID
|
||||
func (ctx *Context) RequestId() string {
|
||||
return ctx.requestID
|
||||
}
|
||||
|
||||
//NewContext 创建Context
|
||||
func NewContext(requestCtx *fasthttp.RequestCtx) *Context {
|
||||
requestID := utils.GetRandomString(16)
|
||||
requestReader := NewRequestReader(requestCtx.Request)
|
||||
ctx := &Context{
|
||||
responseWriter: requestCtx,
|
||||
CookiesHandler: newCookieHandle(requestReader.header),
|
||||
PriorityHeader: NewPriorityHeader(),
|
||||
StatusHandler: NewStatusHandler(),
|
||||
StoreHandler: NewStoreHandler(),
|
||||
RequestOrg: requestReader,
|
||||
ProxyRequest: NewRequest(requestReader),
|
||||
ProxyResponseHandler: nil,
|
||||
requestID: requestID,
|
||||
LogFields: access_field.NewFields(),
|
||||
}
|
||||
ctx.LogFields.RequestHeader = utils.HeaderToString(requestReader.Headers())
|
||||
ctx.LogFields.RequestMsg = string(ctx.RequestOrg.rawBody)
|
||||
ctx.LogFields.RequestMsgSize = len(ctx.RequestOrg.rawBody)
|
||||
ctx.LogFields.RequestUri = requestReader.req.URL.RequestURI()
|
||||
ctx.LogFields.RequestID = requestID
|
||||
return ctx
|
||||
}
|
||||
|
||||
//SetProxyResponse 设置转发响应
|
||||
func (ctx *Context) SetProxyResponse(response *http.Response) {
|
||||
|
||||
ctx.SetProxyResponseHandler(newResponseReader(response))
|
||||
|
||||
}
|
||||
|
||||
//SetProxyResponseHandler 设置转发响应处理器
|
||||
func (ctx *Context) SetProxyResponseHandler(response *ResponseReader) {
|
||||
ctx.ProxyResponseHandler = response
|
||||
if ctx.ProxyResponseHandler != nil {
|
||||
ctx.Body = ctx.ProxyResponseHandler.body
|
||||
ctx.SetStatus(ctx.ProxyResponseHandler.StatusCode(), ctx.ProxyResponseHandler.Status())
|
||||
ctx.header = ctx.ProxyResponseHandler.header
|
||||
}
|
||||
}
|
||||
|
||||
//func (ctx *Context) Write(w http.ResponseWriter) {
|
||||
// if ctx.StatusCode() == 0 {
|
||||
// ctx.SetStatus(200, "200 ok")
|
||||
// }
|
||||
// if ctx.Body != nil {
|
||||
// w.Write(ctx.Body)
|
||||
// }
|
||||
//
|
||||
// w.WriteHeader(ctx.StatusCode())
|
||||
//
|
||||
//}
|
||||
|
||||
//GetBody 获取请求body
|
||||
func (ctx *Context) GetBody() []byte {
|
||||
return ctx.Body
|
||||
}
|
||||
|
||||
//SetBody 设置body
|
||||
func (ctx *Context) SetBody(data []byte) {
|
||||
ctx.Body = data
|
||||
}
|
||||
|
||||
func (ctx *Context) SetError(err error) {
|
||||
result := map[string]string{
|
||||
"status": "error",
|
||||
"msg": err.Error(),
|
||||
}
|
||||
errByte, _ := json.Marshal(result)
|
||||
ctx.Body = errByte
|
||||
}
|
||||
|
||||
//ProxyResponse 返回响应
|
||||
func (ctx *Context) ProxyResponse() goku_plugin.ResponseReader {
|
||||
return ctx.ProxyResponseHandler
|
||||
}
|
||||
|
||||
//Request 获取原始请求
|
||||
func (ctx *Context) Request() goku_plugin.RequestReader {
|
||||
return ctx.RequestOrg
|
||||
}
|
||||
|
||||
//Proxy 代理
|
||||
func (ctx *Context) Proxy() goku_plugin.Request {
|
||||
return ctx.ProxyRequest
|
||||
}
|
||||
|
||||
func NotFound(ctx *Context) {
|
||||
ctx.responseWriter.SetStatusCode(404)
|
||||
ctx.responseWriter.SetBody([]byte("404 Not Found"))
|
||||
}
|
||||
29
node/http-context/cookies.go
Normal file
29
node/http-context/cookies.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package http_context
|
||||
|
||||
import "net/http"
|
||||
|
||||
//CookiesHandler cookies处理器
|
||||
type CookiesHandler struct {
|
||||
req http.Request
|
||||
}
|
||||
|
||||
//AddCookie 新增cookiess
|
||||
func (cs *CookiesHandler) AddCookie(c *http.Cookie) {
|
||||
cs.req.AddCookie(c)
|
||||
}
|
||||
|
||||
//Cookie 获取cookie
|
||||
func (cs *CookiesHandler) Cookie(name string) (*http.Cookie, error) {
|
||||
return cs.req.Cookie(name)
|
||||
}
|
||||
|
||||
//Cookies 获取cookies
|
||||
func (cs *CookiesHandler) Cookies() []*http.Cookie {
|
||||
return cs.req.Cookies()
|
||||
}
|
||||
|
||||
func newCookieHandle(header http.Header) *CookiesHandler {
|
||||
return &CookiesHandler{
|
||||
req: http.Request{Header: header},
|
||||
}
|
||||
}
|
||||
90
node/http-context/header.go
Normal file
90
node/http-context/header.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package http_context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
goku_plugin "github.com/eolinker/goku-standard-plugin"
|
||||
)
|
||||
|
||||
//Header header
|
||||
type Header struct {
|
||||
header http.Header
|
||||
}
|
||||
|
||||
//Headers 获取头部
|
||||
func (h *Header) Headers() http.Header {
|
||||
|
||||
n := make(http.Header)
|
||||
for k, v := range h.header {
|
||||
n[k] = v
|
||||
}
|
||||
return n
|
||||
}
|
||||
func (h *Header) String() string {
|
||||
|
||||
return url.Values(h.header).Encode()
|
||||
|
||||
}
|
||||
|
||||
//SetHeader 设置请求头部
|
||||
func (h *Header) SetHeader(key, value string) {
|
||||
h.header.Set(key, value)
|
||||
}
|
||||
|
||||
//AddHeader 新增头部
|
||||
func (h *Header) AddHeader(key, value string) {
|
||||
h.header.Add(key, value)
|
||||
}
|
||||
|
||||
//DelHeader 删除头部
|
||||
func (h *Header) DelHeader(key string) {
|
||||
h.header.Del(key)
|
||||
}
|
||||
|
||||
//GetHeader 根据名称获取头部
|
||||
func (h *Header) GetHeader(name string) string {
|
||||
return h.header.Get(name)
|
||||
}
|
||||
|
||||
//NewHeader 创建Header
|
||||
func NewHeader(header http.Header) *Header {
|
||||
if header == nil {
|
||||
header = make(http.Header)
|
||||
}
|
||||
return &Header{
|
||||
header: header,
|
||||
}
|
||||
}
|
||||
|
||||
//PriorityHeader priorityHeader
|
||||
type PriorityHeader struct {
|
||||
*Header
|
||||
setHeader *Header
|
||||
appendHeader *Header
|
||||
}
|
||||
|
||||
//Set set
|
||||
func (h *PriorityHeader) Set() goku_plugin.Header {
|
||||
if h.setHeader == nil {
|
||||
h.setHeader = NewHeader(nil)
|
||||
}
|
||||
return h.setHeader
|
||||
}
|
||||
|
||||
//Add append
|
||||
func (h *PriorityHeader) Append() goku_plugin.Header {
|
||||
if h.appendHeader == nil {
|
||||
h.appendHeader = NewHeader(nil)
|
||||
}
|
||||
return h.setHeader
|
||||
}
|
||||
|
||||
//NewPriorityHeader 创建PriorityHeader
|
||||
func NewPriorityHeader() *PriorityHeader {
|
||||
return &PriorityHeader{
|
||||
Header: NewHeader(nil),
|
||||
setHeader: nil,
|
||||
appendHeader: nil,
|
||||
}
|
||||
}
|
||||
106
node/http-context/request-reader.go
Normal file
106
node/http-context/request-reader.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package http_context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
//RequestReader 请求reader
|
||||
type RequestReader struct {
|
||||
*Header
|
||||
*BodyRequestHandler
|
||||
req *http.Request
|
||||
body []byte
|
||||
}
|
||||
|
||||
//Proto 获取协议
|
||||
func (r *RequestReader) Proto() string {
|
||||
return r.req.Proto
|
||||
}
|
||||
|
||||
//NewRequestReader 创建RequestReader
|
||||
func NewRequestReader(req fasthttp.Request) *RequestReader {
|
||||
r := new(RequestReader)
|
||||
r.ParseRequest(req)
|
||||
return r
|
||||
}
|
||||
|
||||
//ParseRequest 解析请求
|
||||
func (r *RequestReader) ParseRequest(req fasthttp.Request) {
|
||||
newReq, _ := http.NewRequest(string(req.Header.Method()), string(req.URI().FullURI()), nil)
|
||||
hs := strings.Split(string(req.Header.Header()), "\r\n")
|
||||
for i, h := range hs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
values := strings.Split(h, ":")
|
||||
vLen := len(values)
|
||||
if vLen < 2 {
|
||||
if values[0] != "" {
|
||||
newReq.Header.Set(values[0], "")
|
||||
}
|
||||
} else {
|
||||
newReq.Header.Set(values[0], values[1])
|
||||
}
|
||||
}
|
||||
qs := strings.Split(string(req.URI().QueryString()), "&")
|
||||
queries := url.Values{}
|
||||
for _, q := range qs {
|
||||
values := strings.Split(q, "=")
|
||||
vLen := len(values)
|
||||
if vLen < 2 {
|
||||
if values[0] != "" {
|
||||
queries.Set(values[0], "")
|
||||
}
|
||||
} else {
|
||||
queries.Set(values[0], values[1])
|
||||
}
|
||||
}
|
||||
newReq.URL.RawQuery = queries.Encode()
|
||||
r.req = newReq
|
||||
r.Header = NewHeader(r.req.Header)
|
||||
|
||||
r.BodyRequestHandler = NewBodyRequestHandler(r.req.Header.Get("Content-Type"), req.Body())
|
||||
}
|
||||
|
||||
//Cookie 获取cookie
|
||||
func (r *RequestReader) Cookie(name string) (*http.Cookie, error) {
|
||||
return r.req.Cookie(name)
|
||||
}
|
||||
|
||||
//Cookies 获取cookies
|
||||
func (r *RequestReader) Cookies() []*http.Cookie {
|
||||
return r.req.Cookies()
|
||||
}
|
||||
|
||||
//method 获取请求方式
|
||||
func (r *RequestReader) Method() string {
|
||||
return r.req.Method
|
||||
}
|
||||
|
||||
//url url
|
||||
func (r *RequestReader) URL() *url.URL {
|
||||
return r.req.URL
|
||||
}
|
||||
|
||||
//RequestURI 获取请求URI
|
||||
func (r *RequestReader) RequestURI() string {
|
||||
return r.req.RequestURI
|
||||
}
|
||||
|
||||
//Host 获取host
|
||||
func (r *RequestReader) Host() string {
|
||||
return r.req.Host
|
||||
}
|
||||
|
||||
//RemoteAddr 远程地址
|
||||
func (r *RequestReader) RemoteAddr() string {
|
||||
return r.req.RemoteAddr
|
||||
}
|
||||
|
||||
func (r *RequestReader) Request() *http.Request {
|
||||
return r.req
|
||||
}
|
||||
60
node/http-context/request.go
Normal file
60
node/http-context/request.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package http_context
|
||||
|
||||
import "net/url"
|
||||
|
||||
//Request 转发内容
|
||||
type Request struct {
|
||||
*Header
|
||||
*CookiesHandler
|
||||
*BodyRequestHandler
|
||||
queries url.Values
|
||||
targetURL string
|
||||
targetServer string
|
||||
Method string
|
||||
Scheme string
|
||||
}
|
||||
|
||||
func (r *Request) Querys() url.Values {
|
||||
return r.queries
|
||||
}
|
||||
|
||||
//TargetURL 获取转发url
|
||||
func (r *Request) TargetURL() string {
|
||||
return r.targetURL
|
||||
}
|
||||
|
||||
//SetTargetURL 设置转发URL
|
||||
func (r *Request) SetTargetURL(targetURL string) {
|
||||
r.targetURL = targetURL
|
||||
}
|
||||
|
||||
//TargetServer 获取转发服务器地址
|
||||
func (r *Request) TargetServer() string {
|
||||
return r.targetServer
|
||||
}
|
||||
|
||||
//SetTargetServer 设置最终转发地址
|
||||
func (r *Request) SetTargetServer(targetServer string) {
|
||||
r.targetServer = targetServer
|
||||
}
|
||||
|
||||
//Queries 获取query参数
|
||||
func (r *Request) Queries() url.Values {
|
||||
return r.queries
|
||||
}
|
||||
|
||||
//NewRequest 创建请求
|
||||
func NewRequest(r *RequestReader) *Request {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
header := r.Headers()
|
||||
return &Request{
|
||||
Scheme: r.Proto(),
|
||||
Method: r.Method(),
|
||||
Header: NewHeader(header),
|
||||
CookiesHandler: newCookieHandle(header),
|
||||
BodyRequestHandler: r.BodyRequestHandler.Clone(),
|
||||
queries: r.URL().Query(),
|
||||
}
|
||||
}
|
||||
67
node/http-context/response.go
Normal file
67
node/http-context/response.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package http_context
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
//ResponseReader 响应结构体
|
||||
type ResponseReader struct {
|
||||
*CookiesHandler
|
||||
*Header
|
||||
*BodyHandler
|
||||
*StatusHandler
|
||||
}
|
||||
|
||||
func newResponseReader(response *http.Response) *ResponseReader {
|
||||
if response == nil {
|
||||
return nil
|
||||
}
|
||||
r := new(ResponseReader)
|
||||
r.Header = NewHeader(response.Header)
|
||||
r.CookiesHandler = newCookieHandle(response.Header)
|
||||
r.StatusHandler = NewStatusHandler()
|
||||
r.SetStatus(response.StatusCode, response.Status)
|
||||
// if response.ContentLength > 0 {
|
||||
// body, _ := ioutil.ReadAll(response.body)
|
||||
// r.BodyHandler = NewBodyHandler(body)
|
||||
// } else {
|
||||
// r.BodyHandler = NewBodyHandler(nil)
|
||||
// }
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
r.BodyHandler = NewBodyHandler(body)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
//NewResponseReader 新增ResponseReader
|
||||
func NewResponseReader(header *fasthttp.ResponseHeader, statusCode int, body []byte) *ResponseReader {
|
||||
r := new(ResponseReader)
|
||||
tmpHeader := http.Header{}
|
||||
|
||||
hs := strings.Split(string(header.Header()), "\r\n")
|
||||
for i, h := range hs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
values := strings.Split(h, ":")
|
||||
vLen := len(values)
|
||||
if vLen < 2 {
|
||||
if values[0] != "" {
|
||||
tmpHeader.Set(values[0], "")
|
||||
}
|
||||
} else {
|
||||
tmpHeader.Set(values[0], values[1])
|
||||
}
|
||||
}
|
||||
r.Header = &Header{header: tmpHeader}
|
||||
r.StatusHandler = NewStatusHandler()
|
||||
r.SetStatus(statusCode, strconv.Itoa(statusCode))
|
||||
|
||||
r.BodyHandler = NewBodyHandler(body)
|
||||
return r
|
||||
}
|
||||
27
node/http-context/status.go
Normal file
27
node/http-context/status.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package http_context
|
||||
|
||||
//StatusHandler 状态处理器
|
||||
type StatusHandler struct {
|
||||
code int
|
||||
status string
|
||||
}
|
||||
|
||||
//SetStatus 设置状态信息
|
||||
func (s *StatusHandler) SetStatus(code int, status string) {
|
||||
s.code, s.status = code, status
|
||||
}
|
||||
|
||||
//StatusCode 获取状态码
|
||||
func (s *StatusHandler) StatusCode() int {
|
||||
return s.code
|
||||
}
|
||||
|
||||
//Status 获取状态
|
||||
func (s *StatusHandler) Status() string {
|
||||
return s.status
|
||||
}
|
||||
|
||||
//NewStatusHandler 状态处理器
|
||||
func NewStatusHandler() *StatusHandler {
|
||||
return new(StatusHandler)
|
||||
}
|
||||
66
node/http-context/store.go
Normal file
66
node/http-context/store.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package http_context
|
||||
|
||||
import goku_plugin "github.com/eolinker/goku-standard-plugin"
|
||||
|
||||
//Store 存储
|
||||
type Store struct {
|
||||
value interface{}
|
||||
}
|
||||
|
||||
//Set set
|
||||
func (s *Store) Set(value interface{}) {
|
||||
s.value = value
|
||||
}
|
||||
|
||||
//GetEmployee get
|
||||
func (s *Store) Get() (value interface{}) {
|
||||
return s.value
|
||||
}
|
||||
|
||||
//StoreHandler 存储器
|
||||
type StoreHandler struct {
|
||||
Cache map[string]interface{}
|
||||
Stores map[string]goku_plugin.Store
|
||||
CurrentPluginName string
|
||||
}
|
||||
|
||||
//SetPlugin 设置plugin
|
||||
func (s *StoreHandler) SetPlugin(name string) {
|
||||
s.CurrentPluginName = name
|
||||
}
|
||||
|
||||
//SetCache 设置缓存
|
||||
func (s *StoreHandler) SetCache(name string, value interface{}) {
|
||||
if s.Cache == nil {
|
||||
s.Cache = make(map[string]interface{})
|
||||
}
|
||||
s.Cache[name] = value
|
||||
}
|
||||
|
||||
//GetCache 获取缓存
|
||||
func (s *StoreHandler) GetCache(name string) (value interface{}, has bool) {
|
||||
if s.Cache == nil {
|
||||
return nil, false
|
||||
}
|
||||
value, has = s.Cache[name]
|
||||
return
|
||||
}
|
||||
|
||||
//Store 存储器
|
||||
func (s *StoreHandler) Store() goku_plugin.Store {
|
||||
if s.Stores == nil {
|
||||
s.Stores = make(map[string]goku_plugin.Store)
|
||||
}
|
||||
store, has := s.Stores[s.CurrentPluginName]
|
||||
if !has {
|
||||
store = &Store{}
|
||||
s.Stores[s.CurrentPluginName] = store
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
//NewStoreHandler 储存器
|
||||
func NewStoreHandler() *StoreHandler {
|
||||
return new(StoreHandler)
|
||||
}
|
||||
49
router/router-http/cert.go
Normal file
49
router/router-http/cert.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/eolinker/eosc/log"
|
||||
)
|
||||
|
||||
type Certs struct {
|
||||
certs map[string]*tls.Certificate
|
||||
}
|
||||
|
||||
func (c *Certs) Get(hostName string) (*tls.Certificate, bool) {
|
||||
cert, has := c.certs[hostName]
|
||||
if has {
|
||||
return cert, true
|
||||
}
|
||||
hs := strings.Split(hostName, ".")
|
||||
if len(hs) < 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cert, has = c.certs[fmt.Sprintf("*.%s", strings.Join(hs[1:], "."))]
|
||||
return cert, has
|
||||
}
|
||||
|
||||
func newCerts(certs []Cert) *Certs {
|
||||
cs := make(map[string]*tls.Certificate)
|
||||
for _, cert := range certs {
|
||||
x509KeyPair, err := tls.X509KeyPair([]byte(cert.Crt), []byte(cert.Key))
|
||||
if err != nil {
|
||||
log.Warn("parse ca error:", err)
|
||||
continue
|
||||
}
|
||||
certificate, err := x509.ParseCertificate(x509KeyPair.Certificate[0])
|
||||
if err != nil {
|
||||
log.Warn("parse cert error:", err)
|
||||
continue
|
||||
}
|
||||
cs[certificate.Subject.CommonName] = &x509KeyPair
|
||||
for _, dnsName := range certificate.DNSNames {
|
||||
cs[dnsName] = &x509KeyPair
|
||||
}
|
||||
}
|
||||
return &Certs{certs: cs}
|
||||
}
|
||||
63
router/router-http/cmd.go
Normal file
63
router/router-http/cmd.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||
* Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||
*/
|
||||
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
cmdLocation = "LOCATION"
|
||||
cmdHeader = "HEADER"
|
||||
cmdQuery = "QUERY"
|
||||
cmdHost = "HOST"
|
||||
cmdMethod = "METHOD"
|
||||
)
|
||||
|
||||
func toMethod() string {
|
||||
return cmdMethod
|
||||
}
|
||||
func toLocation() string {
|
||||
return cmdLocation
|
||||
}
|
||||
func toHeader(key string) string {
|
||||
return fmt.Sprint(cmdHeader, ":", textproto.CanonicalMIMEHeaderKey(key))
|
||||
}
|
||||
func toQuery(key string) string {
|
||||
return fmt.Sprint(cmdQuery, ":", key)
|
||||
|
||||
}
|
||||
func toHost() string {
|
||||
return cmdHost
|
||||
}
|
||||
func headerName(cmd string) (string, bool) {
|
||||
if b := strings.HasPrefix(cmd, "HEADER:"); b {
|
||||
return strings.TrimPrefix(cmd, "HEADER:"), true
|
||||
}
|
||||
return "", false
|
||||
|
||||
}
|
||||
func queryName(cmd string) (string, bool) {
|
||||
if b := strings.HasPrefix(cmd, "QUERY:"); b {
|
||||
return strings.TrimPrefix(cmd, "QUERY:"), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
func isLocation(cmd string) bool {
|
||||
return cmd == cmdLocation
|
||||
}
|
||||
func isHost(cmd string) bool {
|
||||
return cmd == cmdHost
|
||||
}
|
||||
|
||||
func isMethod(cmd string) bool {
|
||||
return cmd == cmdMethod
|
||||
}
|
||||
76
router/router-http/config.go
Normal file
76
router/router-http/config.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"github.com/eolinker/goku-eosc/router"
|
||||
"github.com/eolinker/goku-eosc/router/checker"
|
||||
"github.com/eolinker/goku-eosc/service"
|
||||
)
|
||||
|
||||
type HeaderItem struct {
|
||||
Name string
|
||||
Pattern string
|
||||
}
|
||||
type QueryItem struct {
|
||||
Name string
|
||||
Pattern string
|
||||
}
|
||||
type Rule struct {
|
||||
Location string
|
||||
Header []HeaderItem
|
||||
Query []QueryItem
|
||||
}
|
||||
|
||||
type Cert struct {
|
||||
Crt string
|
||||
Key string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Id string
|
||||
Name string
|
||||
Protocol string
|
||||
Cert []Cert
|
||||
Hosts []string
|
||||
Methods []string
|
||||
Target service.IService
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
func (r *Rule) toPath() ([]router.RulePath, error) {
|
||||
|
||||
path := make([]router.RulePath, 0, len(r.Header)+len(r.Query)+1)
|
||||
|
||||
if len(r.Location) > 0 {
|
||||
locationChecker, err := checker.Parse(r.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path = append(path, router.RulePath{
|
||||
CMD: toLocation(),
|
||||
Checker: locationChecker,
|
||||
})
|
||||
}
|
||||
|
||||
for _, h := range r.Header {
|
||||
ck, err := checker.Parse(h.Pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path = append(path, router.RulePath{
|
||||
CMD: toHeader(h.Name),
|
||||
Checker: ck,
|
||||
})
|
||||
}
|
||||
|
||||
for _, h := range r.Query {
|
||||
ck, err := checker.Parse(h.Pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path = append(path, router.RulePath{
|
||||
CMD: toQuery(h.Name),
|
||||
Checker: ck,
|
||||
})
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
71
router/router-http/endpoint.go
Normal file
71
router/router-http/endpoint.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||
* Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||
*/
|
||||
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/eolinker/goku-eosc/router"
|
||||
"github.com/eolinker/goku-eosc/router/checker"
|
||||
"github.com/eolinker/goku-eosc/service"
|
||||
)
|
||||
|
||||
var _ service.IRouterEndpoint = (*EndPoint)(nil)
|
||||
|
||||
type EndPoint struct {
|
||||
endpoint router.IEndPoint
|
||||
|
||||
headers []string
|
||||
queries []string
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (e *EndPoint) Header(name string) (checker.Checker, bool) {
|
||||
return e.endpoint.Get(toHeader(name))
|
||||
}
|
||||
|
||||
func (e *EndPoint) Query(name string) (checker.Checker, bool) {
|
||||
return e.endpoint.Get(toQuery(name))
|
||||
}
|
||||
|
||||
func (e *EndPoint) initCMD() {
|
||||
e.once.Do(func() {
|
||||
cs := e.endpoint.CMDs()
|
||||
e.headers = make([]string, 0, len(cs))
|
||||
e.queries = make([]string, 0, len(cs))
|
||||
for _, c := range cs {
|
||||
if h, yes := headerName(c); yes {
|
||||
e.headers = append(e.headers, h)
|
||||
continue
|
||||
}
|
||||
if q, yes := queryName(c); yes {
|
||||
e.queries = append(e.queries, q)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (e *EndPoint) Headers() []string {
|
||||
e.initCMD()
|
||||
return e.headers
|
||||
}
|
||||
|
||||
func (e *EndPoint) Queries() []string {
|
||||
e.initCMD()
|
||||
return e.queries
|
||||
}
|
||||
|
||||
func NewEndPoint(endpoint router.IEndPoint) *EndPoint {
|
||||
return &EndPoint{endpoint: endpoint}
|
||||
}
|
||||
|
||||
func (e *EndPoint) Location() (checker.Checker, bool) {
|
||||
return e.endpoint.Get(cmdLocation)
|
||||
}
|
||||
62
router/router-http/helper.go
Normal file
62
router/router-http/helper.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||
* Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||
*/
|
||||
|
||||
package router_http
|
||||
|
||||
import "strings"
|
||||
|
||||
var cmds = []string{
|
||||
cmdHost,
|
||||
cmdLocation,
|
||||
cmdHeader,
|
||||
cmdQuery,
|
||||
}
|
||||
|
||||
type HttpRouterHelper struct {
|
||||
index map[string]int
|
||||
}
|
||||
|
||||
func NewHttpRouterHelper() *HttpRouterHelper {
|
||||
index := make(map[string]int)
|
||||
for i, cmd := range cmds {
|
||||
index[cmd] = i
|
||||
}
|
||||
return &HttpRouterHelper{index: index}
|
||||
}
|
||||
func (h *HttpRouterHelper) cmdType(cmd string) (string, string) {
|
||||
i := strings.Index(cmd, ":")
|
||||
if i < 0 {
|
||||
return cmd, ""
|
||||
}
|
||||
if i == 0 {
|
||||
return strings.ToLower(cmd[1:]), ""
|
||||
}
|
||||
|
||||
return strings.ToLower(cmd[:i]), strings.ToLower(cmd[i+1:])
|
||||
|
||||
}
|
||||
|
||||
func (h *HttpRouterHelper) Less(i, j string) bool {
|
||||
cmdI, keyI := h.cmdType(i)
|
||||
cmdJ, keyJ := h.cmdType(j)
|
||||
if cmdI != cmdJ {
|
||||
ii, hasI := h.index[cmdI]
|
||||
jj, hasJ := h.index[cmdJ]
|
||||
if !hasI && !hasJ {
|
||||
return cmdI < cmdJ
|
||||
}
|
||||
if !hasJ {
|
||||
return true
|
||||
}
|
||||
if !hasI {
|
||||
return false
|
||||
}
|
||||
return ii < jj
|
||||
}
|
||||
return keyI < keyJ
|
||||
}
|
||||
152
router/router-http/manager.go
Normal file
152
router/router-http/manager.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/eolinker/eosc/listener"
|
||||
)
|
||||
|
||||
var _ iManager = (*Manager)(nil)
|
||||
var (
|
||||
sign = ""
|
||||
_ErrorCertificateNotExit = errors.New("not exit ca")
|
||||
)
|
||||
|
||||
func init() {
|
||||
n := time.Now().UnixNano()
|
||||
data := make([]byte, 9)
|
||||
binary.PutVarint(data, n)
|
||||
sign = hex.EncodeToString(data)
|
||||
}
|
||||
|
||||
type iManager interface {
|
||||
Add(port int, id string, config *Config) error
|
||||
Del(port int, id string) error
|
||||
Cancel()
|
||||
}
|
||||
|
||||
var manager = NewManager()
|
||||
|
||||
type Manager struct {
|
||||
locker sync.Mutex
|
||||
routers IRouters
|
||||
servers map[int]*httpServer
|
||||
listeners map[int]net.Listener
|
||||
}
|
||||
|
||||
type httpServer struct {
|
||||
tlsConfig *tls.Config
|
||||
port int
|
||||
protocol string
|
||||
srv *fasthttp.Server
|
||||
certs *Certs
|
||||
}
|
||||
|
||||
func (s *httpServer) shutdown() {
|
||||
s.srv.Shutdown()
|
||||
}
|
||||
|
||||
func (a *httpServer) GetCertificate(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if a.certs == nil {
|
||||
return nil, _ErrorCertificateNotExit
|
||||
}
|
||||
certificate, has := a.certs.Get(strings.ToLower(info.ServerName))
|
||||
if !has {
|
||||
return nil, _ErrorCertificateNotExit
|
||||
}
|
||||
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Cancel() {
|
||||
m.locker.Lock()
|
||||
defer m.locker.Unlock()
|
||||
for p, s := range m.servers {
|
||||
s.shutdown()
|
||||
delete(m.servers, p)
|
||||
}
|
||||
|
||||
for k, l := range m.listeners {
|
||||
l.Close()
|
||||
delete(m.listeners, k)
|
||||
}
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
routers: NewRouters(),
|
||||
servers: make(map[int]*httpServer),
|
||||
listeners: make(map[int]net.Listener),
|
||||
locker: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Add(port int, id string, config *Config) error {
|
||||
m.locker.Lock()
|
||||
defer m.locker.Unlock()
|
||||
|
||||
router, isCreate, err := m.routers.Set(port, id, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isCreate {
|
||||
s, has := m.servers[port]
|
||||
if !has {
|
||||
s = &httpServer{srv: &fasthttp.Server{}}
|
||||
|
||||
s.srv.Handler = router.Handler()
|
||||
l, err := listener.ListenTCP(port, sign)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Protocol == "https" {
|
||||
s.certs = newCerts(config.Cert)
|
||||
s.tlsConfig = &tls.Config{GetCertificate: s.GetCertificate}
|
||||
l = tls.NewListener(l, s.tlsConfig)
|
||||
}
|
||||
go s.srv.Serve(l)
|
||||
|
||||
m.servers[port] = s
|
||||
m.listeners[port] = l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Del(port int, id string) error {
|
||||
m.locker.Lock()
|
||||
defer m.locker.Unlock()
|
||||
if r, has := m.routers.Del(port, id); has {
|
||||
if r.Count() == 0 {
|
||||
if s, has := m.servers[port]; has {
|
||||
err := s.srv.Shutdown()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(m.servers, port)
|
||||
m.listeners[port].Close()
|
||||
delete(m.listeners, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func Add(port int, id string, config *Config) error {
|
||||
return manager.Add(port, id, config)
|
||||
}
|
||||
|
||||
func Del(port int, id string) error {
|
||||
return manager.Del(port, id)
|
||||
}
|
||||
75
router/router-http/match.go
Normal file
75
router/router-http/match.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/eolinker/goku-eosc/router"
|
||||
"github.com/eolinker/goku-eosc/service"
|
||||
)
|
||||
|
||||
type IMatcher interface {
|
||||
Match(req *http.Request) (service.IService, router.IEndPoint, bool)
|
||||
}
|
||||
|
||||
type Matcher struct {
|
||||
r router.IRouter
|
||||
services map[string]service.IService
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(req *http.Request) (service.IService, router.IEndPoint, bool) {
|
||||
|
||||
sources := newHttpSources(req)
|
||||
endpoint, has := m.r.Router(sources)
|
||||
if !has {
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
s, has := m.services[endpoint.Target()]
|
||||
|
||||
return s, endpoint, has
|
||||
}
|
||||
|
||||
type HttpSources struct {
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func newHttpSources(req *http.Request) *HttpSources {
|
||||
index := strings.Index(req.Host, ":")
|
||||
if index > 0 {
|
||||
req.Host = req.Host[:index]
|
||||
}
|
||||
return &HttpSources{req: req}
|
||||
}
|
||||
|
||||
func (h *HttpSources) Get(cmd string) (string, bool) {
|
||||
|
||||
if isHost(cmd) {
|
||||
return h.req.Host, true
|
||||
}
|
||||
if isMethod(cmd) {
|
||||
return h.req.Method, true
|
||||
}
|
||||
|
||||
if isLocation(cmd) {
|
||||
return h.req.URL.Path, true
|
||||
}
|
||||
if hn, yes := headerName(cmd); yes {
|
||||
if vs, has := h.req.Header[hn]; has {
|
||||
if len(vs) == 0 {
|
||||
return "", true
|
||||
}
|
||||
return vs[0], true
|
||||
}
|
||||
}
|
||||
|
||||
if qn, yes := queryName(cmd); yes {
|
||||
if vs, has := h.req.URL.Query()[qn]; has {
|
||||
if len(vs) == 0 {
|
||||
return "", true
|
||||
}
|
||||
return vs[0], true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
89
router/router-http/parse.go
Normal file
89
router/router-http/parse.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"github.com/eolinker/goku-eosc/router"
|
||||
"github.com/eolinker/goku-eosc/router/checker"
|
||||
"github.com/eolinker/goku-eosc/service"
|
||||
)
|
||||
|
||||
func parse(cs []*Config) (IMatcher, error) {
|
||||
|
||||
count := 0
|
||||
for i := range cs {
|
||||
hSize := len(cs[i].Hosts)
|
||||
mSize := len(cs[i].Methods)
|
||||
|
||||
count += len(cs[i].Rules) * hSize * mSize
|
||||
}
|
||||
|
||||
rules := make([]router.Rule, 0, count)
|
||||
|
||||
targets := make(map[string]service.IService)
|
||||
|
||||
for _, c := range cs {
|
||||
|
||||
hosts := make([]router.RulePath, 0, len(c.Hosts))
|
||||
for _, h := range c.Hosts {
|
||||
hck, e := checker.Parse(h)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
hosts = append(hosts, router.RulePath{
|
||||
CMD: toHost(),
|
||||
Checker: hck,
|
||||
})
|
||||
}
|
||||
methods := make([]router.RulePath, 0, len(c.Methods))
|
||||
for _, m := range c.Methods {
|
||||
mck, e := checker.Parse(m)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
methods = append(methods, router.RulePath{
|
||||
CMD: toMethod(),
|
||||
Checker: mck,
|
||||
})
|
||||
}
|
||||
targets[c.Id] = c.Target
|
||||
|
||||
//若配置里的rules为空时
|
||||
if len(c.Rules) == 0 {
|
||||
for _, hp := range hosts {
|
||||
for _, mp := range methods {
|
||||
pathWithHost := append(make([]router.RulePath, 0, 2), hp, mp)
|
||||
rules = append(rules, router.Rule{
|
||||
Path: pathWithHost,
|
||||
Target: c.Id,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range c.Rules {
|
||||
|
||||
path, err := r.toPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, hp := range hosts {
|
||||
for _, mp := range methods {
|
||||
pathWithHost := append(make([]router.RulePath, 0, len(path)+2), hp, mp)
|
||||
pathWithHost = append(pathWithHost, path...)
|
||||
rules = append(rules, router.Rule{
|
||||
Path: pathWithHost,
|
||||
Target: c.Id,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
r, err := router.ParseRouter(rules, NewHttpRouterHelper())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Matcher{
|
||||
r: r,
|
||||
services: targets,
|
||||
}, nil
|
||||
}
|
||||
102
router/router-http/router.go
Normal file
102
router/router-http/router.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
http_context "github.com/eolinker/goku-eosc/node/http-context"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var _ IRouter = (*Router)(nil)
|
||||
|
||||
type IRouter interface {
|
||||
SetRouter(id string, config *Config) error
|
||||
Count() int
|
||||
Del(id string) int
|
||||
Handler() fasthttp.RequestHandler
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
locker sync.Locker
|
||||
data eosc.IUntyped
|
||||
match IMatcher
|
||||
handler fasthttp.RequestHandler
|
||||
}
|
||||
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
locker: &sync.Mutex{},
|
||||
data: eosc.NewUntyped(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) Count() int {
|
||||
return r.data.Count()
|
||||
}
|
||||
|
||||
func (r *Router) Handler() fasthttp.RequestHandler {
|
||||
return func(requestCtx *fasthttp.RequestCtx) {
|
||||
ctx := http_context.NewContext(requestCtx)
|
||||
h, e, has := r.match.Match(ctx.RequestOrg.Request())
|
||||
if !has {
|
||||
http_context.NotFound(ctx)
|
||||
return
|
||||
}
|
||||
h.Handle(ctx, NewEndPoint(e))
|
||||
}
|
||||
}
|
||||
|
||||
//func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// h, e, has := r.match.Match(req)
|
||||
// if !has {
|
||||
// http.NotFound(w, req)
|
||||
// return
|
||||
// }
|
||||
// h.Handle(w, req, NewEndPoint(e))
|
||||
//}
|
||||
|
||||
func (r *Router) SetRouter(id string, config *Config) error {
|
||||
r.locker.Lock()
|
||||
defer r.locker.Unlock()
|
||||
data := r.data.Clone()
|
||||
data.Set(id, config)
|
||||
list := data.List()
|
||||
cs := make([]*Config, 0, len(list))
|
||||
for _, i := range list {
|
||||
cs = append(cs, i.(*Config))
|
||||
}
|
||||
matcher, err := parse(cs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.match = matcher
|
||||
r.data = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) Del(id string) int {
|
||||
r.locker.Lock()
|
||||
defer r.locker.Unlock()
|
||||
|
||||
data := r.data.Clone()
|
||||
data.Del(id)
|
||||
if data.Count() == 0 {
|
||||
r.match = nil
|
||||
} else {
|
||||
list := data.List()
|
||||
cs := make([]*Config, 0, len(list))
|
||||
for _, i := range list {
|
||||
cs = append(cs, i.(*Config))
|
||||
}
|
||||
m, err := parse(cs)
|
||||
if err != nil {
|
||||
return r.data.Count()
|
||||
}
|
||||
r.match = m
|
||||
}
|
||||
|
||||
return r.data.Count()
|
||||
}
|
||||
71
router/router-http/routers.go
Normal file
71
router/router-http/routers.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package router_http
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var _ IRouters = (*Routers)(nil)
|
||||
|
||||
type IRouters interface {
|
||||
Set(port int, id string, conf *Config) (IRouter, bool, error)
|
||||
Del(port int, id string) (IRouter, bool)
|
||||
}
|
||||
type Routers struct {
|
||||
data eosc.IUntyped
|
||||
}
|
||||
|
||||
func (rs *Routers) Set(port int, id string, conf *Config) (IRouter, bool, error) {
|
||||
name := strconv.Itoa(port)
|
||||
r, has := rs.data.Get(name)
|
||||
|
||||
if !has {
|
||||
router := NewRouter()
|
||||
err := router.SetRouter(id, conf)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
rs.data.Set(name, router)
|
||||
return router, true, nil
|
||||
} else {
|
||||
router := r.(IRouter)
|
||||
err := router.SetRouter(id, conf)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return router, false, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func NewRouters() *Routers {
|
||||
return &Routers{
|
||||
data: eosc.NewUntyped(),
|
||||
}
|
||||
}
|
||||
|
||||
//func (rs *Routers) GetEmployee(port int) (IRouter, bool) {
|
||||
// name := strconv.Itoa(port)
|
||||
// r, has := rs.data.GetEmployee(name)
|
||||
// if !has {
|
||||
// var router IRouter = NewRouter()
|
||||
// rs.data.Set(name, router)
|
||||
// return router, true
|
||||
// }
|
||||
// return r.(IRouter), false
|
||||
//}
|
||||
|
||||
func (rs *Routers) Del(port int, id string) (IRouter, bool) {
|
||||
name := strconv.Itoa(port)
|
||||
if i, has := rs.data.Get(name); has {
|
||||
r := i.(IRouter)
|
||||
count := r.Del(id)
|
||||
if count == 0 {
|
||||
rs.data.Del(name)
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
return nil, false
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user