fasthttp替换完成

This commit is contained in:
Liujian
2021-08-06 17:44:40 +08:00
parent 49c8886615
commit 4f9250c552
20 changed files with 1896 additions and 0 deletions

View 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
View 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}
}

View 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"))
}

View 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},
}
}

View 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,
}
}

View 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
}

View 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(),
}
}

View 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
}

View 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)
}

View 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)
}

View 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
View 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
}

View 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
}

View 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)
}

View 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
}

View 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)
}

View 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
}

View 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
}

View 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()
}

View 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
}