From 8112344e0a1722af0e569767519aba8a51d2af38 Mon Sep 17 00:00:00 2001 From: Liujian <824010343@qq.com> Date: Mon, 27 Feb 2023 14:55:25 +0800 Subject: [PATCH 01/25] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/router/dubbo2-router/driver.go | 3 +- drivers/router/http-router/config.go | 23 +++++--- drivers/router/http-router/driver.go | 17 +++--- .../{complate.go => complete.go} | 35 ++++++++++++ drivers/router/http-router/http-handler.go | 5 +- drivers/router/http-router/router.go | 57 +++++++++---------- example/grpc/server/server.go | 5 -- 7 files changed, 93 insertions(+), 52 deletions(-) rename drivers/router/http-router/http-complete/{complate.go => complete.go} (67%) diff --git a/drivers/router/dubbo2-router/driver.go b/drivers/router/dubbo2-router/driver.go index fdb74cf7..58f7a1c4 100644 --- a/drivers/router/dubbo2-router/driver.go +++ b/drivers/router/dubbo2-router/driver.go @@ -2,6 +2,8 @@ package dubbo2_router import ( "fmt" + "sync" + "github.com/eolinker/apinto/drivers/router/dubbo2-router/manager" "github.com/eolinker/apinto/plugin" "github.com/eolinker/apinto/service" @@ -9,7 +11,6 @@ import ( "github.com/eolinker/eosc" "github.com/eolinker/eosc/log" "github.com/eolinker/eosc/utils/config" - "sync" ) var ( diff --git a/drivers/router/http-router/config.go b/drivers/router/http-router/config.go index 24682974..7441ab64 100644 --- a/drivers/router/http-router/config.go +++ b/drivers/router/http-router/config.go @@ -6,19 +6,24 @@ import ( ) type Config struct { - Listen int `json:"listen" yaml:"listen" title:"port" description:"使用端口" default:"80" label:"端口号" maximum:"65535"` - Method []string `json:"method" yaml:"method" enum:"GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS" label:"请求方式"` - Host []string `json:"host" yaml:"host" label:"域名"` - Path string `json:"location"` - Rules []Rule `json:"rules" yaml:"rules" label:"路由规则"` - Service eosc.RequireId `json:"service" yaml:"service" skill:"github.com/eolinker/apinto/service.service.IService" required:"true" label:"目标服务"` + Listen int `json:"listen" yaml:"listen" title:"port" description:"使用端口" default:"80" label:"端口号" maximum:"65535"` + Method []string `json:"method" yaml:"method" enum:"GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS" label:"请求方式"` + Host []string `json:"host" yaml:"host" label:"域名"` + Path string `json:"location"` + Rules []Rule `json:"rules" yaml:"rules" label:"路由规则"` + Service eosc.RequireId `json:"service" yaml:"service" skill:"github.com/eolinker/apinto/service.service.IService" required:"false" empty_label:"使用匿名服务" label:"目标服务"` + + Status int `json:"status" yaml:"status" label:"响应状态码" switch:"service===''" default:"200" maximum:"1000" minimum:"100"` + Header map[string]string `json:"header" yaml:"header" label:"响应头部" switch:"service===''"` + Body string `json:"body" yaml:"status" format:"text" label:"响应Body" switch:"service===''"` + Template eosc.RequireId `json:"template" yaml:"template" skill:"github.com/eolinker/apinto/template.template.ITemplate" required:"false" label:"插件模版"` - Websocket bool `json:"websocket" yaml:"websocket" label:"Websocket"` + Websocket bool `json:"websocket" yaml:"websocket" label:"Websocket" switch:"service!==''"` Disable bool `json:"disable" yaml:"disable" label:"禁用路由"` Plugins plugin.Plugins `json:"plugins" yaml:"plugins" label:"插件配置"` - Retry int `json:"retry" label:"重试次数" yaml:"retry"` - TimeOut int `json:"time_out" label:"超时时间"` + Retry int `json:"retry" label:"重试次数" yaml:"retry" switch:"service!==''"` + TimeOut int `json:"time_out" label:"超时时间" switch:"service!==''"` } // Rule 规则 diff --git a/drivers/router/http-router/driver.go b/drivers/router/http-router/driver.go index ebff50aa..83d6f0b9 100644 --- a/drivers/router/http-router/driver.go +++ b/drivers/router/http-router/driver.go @@ -2,13 +2,14 @@ package http_router import ( "fmt" + "sync" + "github.com/eolinker/apinto/drivers/router/http-router/manager" "github.com/eolinker/apinto/plugin" "github.com/eolinker/apinto/service" "github.com/eolinker/apinto/template" "github.com/eolinker/eosc/log" "github.com/eolinker/eosc/utils/config" - "sync" "github.com/eolinker/eosc" ) @@ -50,14 +51,16 @@ func check(v interface{}, workers map[eosc.RequireId]eosc.IWorker) (*Config, ser if !ok { return nil, nil, nil, fmt.Errorf("get %s but %s %w", config.TypeNameOf(v), config.TypeNameOf(new(Config)), eosc.ErrorRequire) } + var target service.IService ser, has := workers[conf.Service] - if !has { - return nil, nil, nil, fmt.Errorf("target %s: %w", conf.Service, eosc.ErrorRequire) - } - target, ok := ser.(service.IService) - if !ok { - return nil, nil, nil, fmt.Errorf("target name: %s type of %s,target %w", conf.Service, config.TypeNameOf(ser), eosc.ErrorNotGetSillForRequire) + if has { + target, ok = ser.(service.IService) + if !ok { + return nil, nil, nil, fmt.Errorf("target name: %s type of %s,target %w", conf.Service, config.TypeNameOf(ser), eosc.ErrorNotGetSillForRequire) + } + //return nil, nil, nil, fmt.Errorf("target %s: %w", conf.Service, eosc.ErrorRequire) } + var tmp template.ITemplate if conf.Template != "" { tp, has := workers[conf.Template] diff --git a/drivers/router/http-router/http-complete/complate.go b/drivers/router/http-router/http-complete/complete.go similarity index 67% rename from drivers/router/http-router/http-complete/complate.go rename to drivers/router/http-router/http-complete/complete.go index 820fcbd7..d6a14df5 100644 --- a/drivers/router/http-router/http-complete/complate.go +++ b/drivers/router/http-router/http-complete/complete.go @@ -3,6 +3,7 @@ package http_complete import ( "errors" "fmt" + "strconv" "strings" "time" @@ -80,6 +81,40 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error { return lastErr } +type NoServiceCompleteHandler struct { + status int + header map[string]string + body string +} + +func NewNoServiceCompleteHandler(status int, header map[string]string, body string) *NoServiceCompleteHandler { + return &NoServiceCompleteHandler{status: status, header: header, body: body} +} + +func (n *NoServiceCompleteHandler) Complete(org eocontext.EoContext) error { + ctx, err := http_service.Assert(org) + if err != nil { + return err + } + //设置响应开始时间 + proxyTime := time.Now() + + defer func() { + //设置原始响应状态码 + ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "") + //设置上游响应总时间, 单位为毫秒 + //ctx.WithValue("response_time", time.Now().Sub(proxyTime).Milliseconds()) + ctx.Response().SetResponseTime(time.Now().Sub(proxyTime)) + ctx.SetLabel("handler", "proxy") + }() + for key, value := range n.header { + ctx.Response().SetHeader(key, value) + } + ctx.Response().SetBody([]byte(n.body)) + ctx.Response().SetStatus(n.status, strconv.Itoa(n.status)) + return nil +} + type httpCompleteCaller struct { } diff --git a/drivers/router/http-router/http-handler.go b/drivers/router/http-router/http-handler.go index bc103c20..aa42d7c2 100644 --- a/drivers/router/http-router/http-handler.go +++ b/drivers/router/http-router/http-handler.go @@ -55,7 +55,10 @@ func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) { ctx.SetLabel("api", h.routerName) ctx.SetLabel("api_id", h.routerId) ctx.SetLabel("service", h.serviceName) - ctx.SetLabel("service_id", h.service.Id()) + if h.service != nil { + ctx.SetLabel("service_id", h.service.Id()) + } + ctx.SetLabel("ip", httpContext.Request().ReadIP()) ctx.SetCompleteHandler(h.completeHandler) diff --git a/drivers/router/http-router/router.go b/drivers/router/http-router/router.go index 86477654..021e5fcb 100644 --- a/drivers/router/http-router/router.go +++ b/drivers/router/http-router/router.go @@ -5,20 +5,19 @@ import ( "strings" "time" - "github.com/eolinker/apinto/drivers" - http_router "github.com/eolinker/apinto/router" - "github.com/eolinker/apinto/drivers/router/http-router/websocket" - http_complete "github.com/eolinker/apinto/drivers/router/http-router/http-complete" + "github.com/eolinker/apinto/service" + "github.com/eolinker/eosc/eocontext" + + "github.com/eolinker/apinto/drivers" + http_complete "github.com/eolinker/apinto/drivers/router/http-router/http-complete" "github.com/eolinker/apinto/drivers/router/http-router/manager" "github.com/eolinker/apinto/plugin" - "github.com/eolinker/apinto/service" + http_router "github.com/eolinker/apinto/router" "github.com/eolinker/apinto/template" - "github.com/eolinker/eosc" - "github.com/eolinker/eosc/eocontext" ) type HttpRouter struct { @@ -55,24 +54,16 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker) methods := cfg.Method handler := &httpHandler{ - routerName: h.name, - routerId: h.id, - serviceName: strings.TrimSuffix(string(cfg.Service), "@service"), - completeHandler: http_complete.NewHttpComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond), - finisher: defaultFinisher, - service: nil, - filters: nil, - disable: cfg.Disable, - websocket: cfg.Websocket, + routerName: h.name, + routerId: h.id, + serviceName: strings.TrimSuffix(string(cfg.Service), "@service"), + finisher: defaultFinisher, + disable: cfg.Disable, + websocket: cfg.Websocket, } if !cfg.Disable { - serviceWorker, has := workers[cfg.Service] - if !has || !serviceWorker.CheckSkill(service.ServiceSkill) { - return eosc.ErrorNotGetSillForRequire - } - if cfg.Plugins == nil { cfg.Plugins = map[string]*plugin.Config{} } @@ -87,16 +78,24 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker) } else { plugins = h.pluginManager.CreateRequest(h.id, cfg.Plugins) } - - serviceHandler := serviceWorker.(service.IService) - - handler.service = serviceHandler handler.filters = plugins - if cfg.Websocket { - handler.completeHandler = websocket.NewComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond) - methods = []string{http.MethodGet} - //handler.finisher = &websocket.Finisher{} + if cfg.Service == "" { + // 当service未指定,使用默认返回 + handler.completeHandler = http_complete.NewNoServiceCompleteHandler(cfg.Status, cfg.Header, cfg.Body) + } else { + serviceWorker, has := workers[cfg.Service] + if !has || !serviceWorker.CheckSkill(service.ServiceSkill) { + return eosc.ErrorNotGetSillForRequire + } + serviceHandler := serviceWorker.(service.IService) + handler.service = serviceHandler + if cfg.Websocket { + handler.completeHandler = websocket.NewComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond) + methods = []string{http.MethodGet} + } else { + handler.completeHandler = http_complete.NewHttpComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond) + } } } diff --git a/example/grpc/server/server.go b/example/grpc/server/server.go index 3a74e116..ed64c5b2 100644 --- a/example/grpc/server/server.go +++ b/example/grpc/server/server.go @@ -36,11 +36,6 @@ func (s *Server) Hello(ctx context.Context, request *service.HelloRequest) (*ser }, nil } -type Request struct { - Name string - err error -} - func (s *Server) StreamRequest(server service.Hello_StreamRequestServer) error { trailingMD, ok := metadata.FromIncomingContext(server.Context()) if ok { From 42961e8d0c3aadd5f9f3c601ce448ac6c3f978d6 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Mon, 27 Feb 2023 16:21:44 +0800 Subject: [PATCH 02/25] =?UTF-8?q?apinto=20Upgrade=E8=84=9A=E6=9C=AC=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0sleep10=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/resources/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build/resources/install.sh b/build/resources/install.sh index 3573927f..cc0303f2 100755 --- a/build/resources/install.sh +++ b/build/resources/install.sh @@ -17,6 +17,7 @@ install() { upgrade() { apinto stop install + sleep 10s apinto start } From f840e3c3dbd334568c887f2de6deab3996702dfb Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Mon, 27 Feb 2023 17:14:51 +0800 Subject: [PATCH 03/25] =?UTF-8?q?=E5=93=8D=E5=BA=94=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=89=A7=E8=A1=8C=E6=97=B6=EF=BC=8C=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E4=B8=8A=E4=B8=80=E4=B8=AA=E6=89=A7=E8=A1=8C=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/response-rewrite/response-rewrite.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/plugins/response-rewrite/response-rewrite.go b/drivers/plugins/response-rewrite/response-rewrite.go index 9d30e288..8dbc2985 100644 --- a/drivers/plugins/response-rewrite/response-rewrite.go +++ b/drivers/plugins/response-rewrite/response-rewrite.go @@ -4,6 +4,7 @@ import ( "github.com/eolinker/apinto/drivers" "github.com/eolinker/apinto/utils" "github.com/eolinker/eosc/eocontext" + log "github.com/eolinker/goku-api-gateway/goku-log" "strconv" "github.com/eolinker/eosc" @@ -66,9 +67,12 @@ func (r *ResponseRewrite) CheckSkill(skill string) bool { return http_service.FilterSkillName == skill } -func (r *ResponseRewrite) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) (err error) { +func (r *ResponseRewrite) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) error { if next != nil { - err = next.DoChain(ctx) + err := next.DoChain(ctx) + if err != nil { + log.Error(err) + } } return r.rewrite(ctx) From d0792f2feaf5eca6795af0c3f4091669e701df10 Mon Sep 17 00:00:00 2001 From: zhangzeyi Date: Tue, 28 Feb 2023 10:50:43 +0800 Subject: [PATCH 04/25] =?UTF-8?q?=E5=AE=8C=E6=88=90mocking=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apinto/worker.go | 3 + drivers/plugins/mocking/complete.go | 177 ++++++++++++++++++++++++++++ drivers/plugins/mocking/config.go | 16 +++ drivers/plugins/mocking/driver.go | 99 ++++++++++++++++ drivers/plugins/mocking/factory.go | 25 ++++ drivers/plugins/mocking/mocking.go | 82 +++++++++++++ go.mod | 1 + 7 files changed, 403 insertions(+) create mode 100644 drivers/plugins/mocking/complete.go create mode 100644 drivers/plugins/mocking/config.go create mode 100644 drivers/plugins/mocking/driver.go create mode 100644 drivers/plugins/mocking/factory.go create mode 100644 drivers/plugins/mocking/mocking.go diff --git a/app/apinto/worker.go b/app/apinto/worker.go index e94d25a7..4e318af9 100644 --- a/app/apinto/worker.go +++ b/app/apinto/worker.go @@ -28,6 +28,7 @@ import ( http_to_dubbo2 "github.com/eolinker/apinto/drivers/plugins/http-to-dubbo2" http_to_grpc "github.com/eolinker/apinto/drivers/plugins/http-to-gRPC" ip_restriction "github.com/eolinker/apinto/drivers/plugins/ip-restriction" + "github.com/eolinker/apinto/drivers/plugins/mocking" "github.com/eolinker/apinto/drivers/plugins/monitor" params_transformer "github.com/eolinker/apinto/drivers/plugins/params-transformer" prometheus_plugin "github.com/eolinker/apinto/drivers/plugins/prometheus" @@ -146,4 +147,6 @@ func Register(extenderRegister eosc.IExtenderDriverRegister) { http_to_grpc.Register(extenderRegister) protocbuf.Register(extenderRegister) grpc_to_http.Register(extenderRegister) + + mocking.Register(extenderRegister) } diff --git a/drivers/plugins/mocking/complete.go b/drivers/plugins/mocking/complete.go new file mode 100644 index 00000000..d7661f4f --- /dev/null +++ b/drivers/plugins/mocking/complete.go @@ -0,0 +1,177 @@ +package mocking + +import ( + "dubbo.apache.org/dubbo-go/v3/protocol" + "dubbo.apache.org/dubbo-go/v3/protocol/dubbo/impl" + "encoding/json" + "errors" + "fmt" + "github.com/brianvoe/gofakeit/v6" + grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor" + "github.com/eolinker/eosc/eocontext" + dubbo2_context "github.com/eolinker/eosc/eocontext/dubbo2-context" + grpc_context "github.com/eolinker/eosc/eocontext/grpc-context" + http_context "github.com/eolinker/eosc/eocontext/http-context" + "github.com/eolinker/eosc/log" + "github.com/jhump/protoreflect/dynamic" + "google.golang.org/protobuf/types/descriptorpb" +) + +type complete struct { + responseStatus int + contentType string + responseExample string + responseSchema map[string]interface{} + descriptor grpc_descriptor.IDescriptor +} + +func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}, descriptor grpc_descriptor.IDescriptor) *complete { + return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema, descriptor: descriptor} +} + +func (c *complete) Complete(org eocontext.EoContext) error { + switch ctx := org.(type) { + case dubbo2_context.IDubbo2Context: + return c.writeDubbo2(ctx) + case http_context.IHttpContext: + return c.writeHttp(ctx) + case grpc_context.IGrpcContext: + return c.writeGrpc(ctx) + } + + return errors.New("eoContext unrealized") +} + +func (c *complete) writeHttp(ctx http_context.IHttpContext) error { + ctx.Response().SetHeader("Content-Type", c.contentType) + ctx.Response().SetStatus(c.responseStatus, "") + + if c.responseExample != "" { + ctx.Response().SetBody([]byte(c.responseExample)) + return nil + } + + schema := jsonSchemaUnmarshal(c.responseSchema) + bytes, err := json.Marshal(schema) + if err != nil { + log.Errorf("mocking complete err=%s", err.Error()) + return err + } + + ctx.Response().SetBody(bytes) + return nil +} + +func (c *complete) writeDubbo2(ctx dubbo2_context.IDubbo2Context) error { + if c.responseExample != "" { + + var val interface{} + if err := json.Unmarshal([]byte(c.responseExample), &val); err != nil { + ctx.Response().SetBody(Dubbo2ErrorResult(err)) + return err + } + + ctx.Response().SetBody(getDubbo2Response(val, ctx.Proxy().Attachments())) + return nil + } + + schema := jsonSchemaUnmarshal(c.responseSchema) + ctx.Response().SetBody(getDubbo2Response(schema, ctx.Proxy().Attachments())) + return nil +} + +func (c *complete) writeGrpc(ctx grpc_context.IGrpcContext) error { + descriptor, err := c.descriptor.Descriptor().FindSymbol(fmt.Sprintf("%s.%s", ctx.Proxy().Service(), ctx.Proxy().Method())) + if err != nil { + return err + } + methodDesc := descriptor.GetFile().FindService(ctx.Proxy().Service()).FindMethodByName(ctx.Proxy().Method()) + + message := dynamic.NewMessage(methodDesc.GetOutputType()) + + fields := message.GetKnownFields() + for _, field := range fields { + switch field.GetType() { + case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, descriptorpb.FieldDescriptorProto_TYPE_FLOAT: //float32 + message.SetField(field, gofakeit.Float32()) + case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: //int64 + message.SetField(field, gofakeit.Int64()) + case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: //int32 + message.SetField(field, gofakeit.Int32()) + case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: //uint32 + message.SetField(field, gofakeit.Uint32()) + case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: //uint64 + message.SetField(field, gofakeit.Uint64()) + case descriptorpb.FieldDescriptorProto_TYPE_BOOL: //bool + message.SetField(field, gofakeit.Bool()) + case descriptorpb.FieldDescriptorProto_TYPE_STRING: //string + message.SetField(field, gofakeit.LetterN(5)) + case descriptorpb.FieldDescriptorProto_TYPE_BYTES: //bytes + message.SetField(field, []byte(gofakeit.LetterN(5))) + } + + } + + ctx.Response().Write(message) + return nil +} + +func jsonSchemaUnmarshal(properties interface{}) interface{} { + propertiesMap, ok := properties.(map[string]interface{}) + if !ok { + return "son schema 格式错误" + } + if val, ok := propertiesMap["example"]; ok { + return val + } else { + if t, tOk := propertiesMap["type"].(string); tOk { + switch t { + case "string": + return gofakeit.LetterN(10) + case "number": + return gofakeit.Float64() + case "integer": + return gofakeit.Int64() + case "boolean": + return gofakeit.Bool() + case "object": + propertiesMaps, pOk := propertiesMap["properties"].(map[string]interface{}) + if !pOk { + return "json schema 格式错误" + } + resultMap := make(map[string]interface{}) + for key, vProperties := range propertiesMaps { + resultMap[key] = jsonSchemaUnmarshal(vProperties) + } + return resultMap + case "array": + items, iOk := propertiesMap["items"].(map[string]interface{}) + if !iOk { + return "json schema 格式错误" + } + resultList := make([]interface{}, 0) + resultList = append(resultList, jsonSchemaUnmarshal(items)) + return resultList + } + } + return "json schema 格式错误" + } +} + +func getDubbo2Response(obj interface{}, attachments map[string]interface{}) protocol.RPCResult { + payload := impl.NewResponsePayload(obj, nil, attachments) + return protocol.RPCResult{ + Attrs: payload.Attachments, + Err: payload.Exception, + Rest: payload.RspObj, + } +} + +func Dubbo2ErrorResult(err error) protocol.RPCResult { + payload := impl.NewResponsePayload(nil, err, nil) + return protocol.RPCResult{ + Attrs: payload.Attachments, + Err: payload.Exception, + Rest: payload.RspObj, + } +} diff --git a/drivers/plugins/mocking/config.go b/drivers/plugins/mocking/config.go new file mode 100644 index 00000000..bdb88f47 --- /dev/null +++ b/drivers/plugins/mocking/config.go @@ -0,0 +1,16 @@ +package mocking + +import "github.com/eolinker/eosc" + +type Config struct { + ResponseStatus int `json:"response_status" label:"返回响应的 HTTP 状态码(仅http路由有效)"` + ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json,application/grpc"` + ProtobufID eosc.RequireId `json:"protobuf_id" label:"Protobuf ID" switch:"content_type==='application/grpc'" skill:"github.com/eolinker/apinto/grpc-transcode.transcode.IDescriptor"` + ResponseExample string `json:"response_example" switch:"content_type==='application/json'" label:"返回响应的Body,与jsonschema字段二选一"` + ResponseSchema string `json:"response_schema" switch:"content_type==='application/json'" label:"指定响应的jsonschema对象"` +} + +const ( + contentTypeJson = "application/json" + contentTypeGrpc = "application/grpc" +) diff --git a/drivers/plugins/mocking/driver.go b/drivers/plugins/mocking/driver.go new file mode 100644 index 00000000..0e60fcce --- /dev/null +++ b/drivers/plugins/mocking/driver.go @@ -0,0 +1,99 @@ +package mocking + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/eolinker/apinto/drivers" + grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor" + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/common/bean" + "github.com/eolinker/eosc/log" + "strings" +) + +func check(v interface{}) (*Config, error) { + conf, err := drivers.Assert[Config](v) + if err != nil { + return nil, err + } + + if conf.ResponseStatus < 100 { + conf.ResponseStatus = 200 + } + + if conf.ContentType == contentTypeJson { + if len(strings.TrimSpace(conf.ResponseSchema)) == 0 && len(strings.TrimSpace(conf.ResponseExample)) == 0 { + log.Errorf("mocking check schema is null && example is null ") + return nil, errors.New("param err") + } + if len(strings.TrimSpace(conf.ResponseExample)) > 0 { + var val interface{} + + if err = json.Unmarshal([]byte(conf.ResponseExample), &val); err != nil { + log.Errorf("mocking check example Format err = %s example=%s", err.Error(), conf.ResponseExample) + return nil, errors.New("param err") + } + + } + + if len(strings.TrimSpace(conf.ResponseSchema)) > 0 { + var val interface{} + + if err = json.Unmarshal([]byte(conf.ResponseSchema), &val); err != nil { + log.Errorf("mocking check Schema Format err = %s Schema=%s", err.Error(), conf.ResponseSchema) + return nil, errors.New("param err") + } + + } + } else if conf.ContentType == contentTypeGrpc && conf.ProtobufID == "" { + log.Errorf("mocking check ProtobufID is null ") + return nil, errors.New("param err protobufID is null") + } + + return conf, nil +} + +func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + + once.Do(func() { + bean.Autowired(&worker) + }) + + var descriptor grpc_descriptor.IDescriptor + if conf.ContentType == contentTypeGrpc { + descSource, err := getDescSource(string(conf.ProtobufID)) + if err != nil { + return nil, err + } + descriptor = descSource + } + + jsonSchema := make(map[string]interface{}) + + if conf.ResponseSchema != "" { + if err := json.Unmarshal([]byte(conf.ResponseSchema), &jsonSchema); err != nil { + log.Errorf("create mocking err=%s,jsonSchema=%s", err.Error(), conf.ResponseSchema) + return nil, err + } + } + + return &Mocking{ + WorkerBase: drivers.Worker(id, name), + handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema, descriptor), + }, nil +} + +func getDescSource(protobufID string) (grpc_descriptor.IDescriptor, error) { + + w, ok := worker.Get(protobufID) + if ok { + v, vOk := w.(grpc_descriptor.IDescriptor) + if !vOk { + return nil, fmt.Errorf("invalid protobuf id: %s", protobufID) + } + return v, nil + } + return nil, fmt.Errorf("protobuf worker(%s) is not exist", protobufID) + +} diff --git a/drivers/plugins/mocking/factory.go b/drivers/plugins/mocking/factory.go new file mode 100644 index 00000000..c9f011e0 --- /dev/null +++ b/drivers/plugins/mocking/factory.go @@ -0,0 +1,25 @@ +package mocking + +import ( + "sync" + + "github.com/eolinker/apinto/drivers" + "github.com/eolinker/eosc" +) + +const ( + Name = "mocking" +) + +var ( + once = sync.Once{} + worker eosc.IWorkers +) + +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(Name, NewFactory()) +} + +func NewFactory() eosc.IExtenderDriverFactory { + return drivers.NewFactory[Config](Create) +} diff --git a/drivers/plugins/mocking/mocking.go b/drivers/plugins/mocking/mocking.go new file mode 100644 index 00000000..1bde8a49 --- /dev/null +++ b/drivers/plugins/mocking/mocking.go @@ -0,0 +1,82 @@ +package mocking + +import ( + "encoding/json" + "github.com/eolinker/apinto/drivers" + grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor" + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/eocontext" + log "github.com/eolinker/eosc/log" +) + +var _ eocontext.IFilter = (*Mocking)(nil) + +type Mocking struct { + drivers.WorkerBase + responseStatus int + contentType string + responseExample string + responseSchema string + handler eocontext.CompleteHandler +} + +func (t *Mocking) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) { + + if t.handler != nil { + ctx.SetCompleteHandler(t.handler) + } + + if next != nil { + return next.DoChain(ctx) + } + + return nil +} + +func (t *Mocking) Destroy() { + return +} + +func (t *Mocking) Start() error { + return nil +} + +func (t *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + conf, err := check(v) + if err != nil { + return err + } + t.responseSchema = conf.ResponseSchema + t.responseExample = conf.ResponseExample + t.contentType = conf.ContentType + t.responseStatus = conf.ResponseStatus + + var descriptor grpc_descriptor.IDescriptor + if t.contentType == contentTypeGrpc { + descriptor, err = getDescSource(string(conf.ProtobufID)) + if err != nil { + return err + } + } + + jsonSchema := make(map[string]interface{}) + + if conf.ResponseSchema != "" { + if err = json.Unmarshal([]byte(conf.ResponseSchema), &jsonSchema); err != nil { + log.Errorf("create mocking err=%s,jsonSchema=%s", err.Error(), conf.ResponseSchema) + return err + } + } + + t.handler = NewComplete(t.responseStatus, t.contentType, t.responseExample, jsonSchema, descriptor) + + return nil +} + +func (t *Mocking) Stop() error { + return nil +} + +func (t *Mocking) CheckSkill(skill string) bool { + return true +} diff --git a/go.mod b/go.mod index a61656fc..e7549563 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/Shopify/sarama v1.32.0 + github.com/brianvoe/gofakeit/v6 v6.20.1 github.com/coocood/freecache v1.2.2 github.com/dubbogo/gost v1.13.1 github.com/eolinker/eosc v0.10.1 From a21a359473e9f0dd49adf10b9e59e22ab3abf2d2 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Tue, 28 Feb 2023 15:36:02 +0800 Subject: [PATCH 05/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/config.go | 30 ++++++++++ drivers/plugins/proxy-mirror/driver.go | 39 +++++++++++++ drivers/plugins/proxy-mirror/error.go | 1 + drivers/plugins/proxy-mirror/factory.go | 18 ++++++ drivers/plugins/proxy-mirror/proxy-mirror.go | 60 ++++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 drivers/plugins/proxy-mirror/config.go create mode 100644 drivers/plugins/proxy-mirror/driver.go create mode 100644 drivers/plugins/proxy-mirror/error.go create mode 100644 drivers/plugins/proxy-mirror/factory.go create mode 100644 drivers/plugins/proxy-mirror/proxy-mirror.go diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go new file mode 100644 index 00000000..16aff09d --- /dev/null +++ b/drivers/plugins/proxy-mirror/config.go @@ -0,0 +1,30 @@ +package proxy_mirror + +type Config struct { + Host string `json:"host" label:"服务地址" description:"镜像服务地址, 需要包含scheme"` + Path string `json:"path" label:"请求路径" description:"镜像的请求路径, 不填则默认使用当前路径"` + PathMode string `json:"path_mode" label:"请求路径拼接模式" enum:"replace,prefix"` + SampleConf *SampleConfig `json:"sample_conf" label:"采样配置"` + Timeout int `json:"timeout" label:"请求超时时间"` +} + +type SampleConfig struct { + RandomRange int `json:"random_range" label:"随机数范围"` + RandomPivot int `json:"random_pivot" label:"随机数锚点"` +} + +func (c *Config) doCheck() error { + //TODO + + //校验host + + //校验path + + //校验path_mode + + //校验采样配置 + + //校验镜像请求超时时间 + + return nil +} diff --git a/drivers/plugins/proxy-mirror/driver.go b/drivers/plugins/proxy-mirror/driver.go new file mode 100644 index 00000000..e13879b0 --- /dev/null +++ b/drivers/plugins/proxy-mirror/driver.go @@ -0,0 +1,39 @@ +package proxy_mirror + +import ( + "github.com/eolinker/apinto/drivers" + "github.com/eolinker/eosc" +) + +func Check(v *Config, workers map[eosc.RequireId]eosc.IWorker) error { + return v.doCheck() +} + +func check(v interface{}) (*Config, error) { + + conf, err := drivers.Assert[Config](v) + if err != nil { + return nil, err + } + err = conf.doCheck() + if err != nil { + return nil, err + } + + return conf, nil +} + +func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) { + + err := conf.doCheck() + if err != nil { + return nil, err + } + + pm := &proxyMirror{ + WorkerBase: drivers.Worker(id, name), + proxyConf: conf, + } + + return pm, nil +} diff --git a/drivers/plugins/proxy-mirror/error.go b/drivers/plugins/proxy-mirror/error.go new file mode 100644 index 00000000..f2ec70d3 --- /dev/null +++ b/drivers/plugins/proxy-mirror/error.go @@ -0,0 +1 @@ +package proxy_mirror diff --git a/drivers/plugins/proxy-mirror/factory.go b/drivers/plugins/proxy-mirror/factory.go new file mode 100644 index 00000000..7cfac0cb --- /dev/null +++ b/drivers/plugins/proxy-mirror/factory.go @@ -0,0 +1,18 @@ +package proxy_mirror + +import ( + "github.com/eolinker/apinto/drivers" + "github.com/eolinker/eosc" +) + +const ( + Name = "proxy_mirror" +) + +func Register(register eosc.IExtenderDriverRegister) { + register.RegisterExtenderDriver(Name, NewFactory()) +} + +func NewFactory() eosc.IExtenderDriverFactory { + return drivers.NewFactory[Config](Create, Check) +} diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go new file mode 100644 index 00000000..ea1596ff --- /dev/null +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -0,0 +1,60 @@ +package proxy_mirror + +import ( + "github.com/eolinker/apinto/drivers" + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/eocontext" + http_service "github.com/eolinker/eosc/eocontext/http-context" + "github.com/eolinker/eosc/log" +) + +var _ eocontext.IFilter = (*proxyMirror)(nil) +var _ http_service.HttpFilter = (*proxyMirror)(nil) + +type proxyMirror struct { + drivers.WorkerBase + proxyConf *Config +} + +func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) error { + return http_service.DoHttpFilter(p, ctx, next) +} + +func (p *proxyMirror) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) error { + if next != nil { + err := next.DoChain(ctx) + if err != nil { + log.Error(err) + } + } + //进行采样, 生成随机数判断 + + //进行转发 + + return nil +} + +func (p *proxyMirror) Start() error { + return nil +} + +func (p *proxyMirror) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error { + conf, err := check(v) + if err != nil { + return err + } + p.proxyConf = conf + + return nil +} + +func (p *proxyMirror) Stop() error { + return nil +} + +func (p *proxyMirror) Destroy() { +} + +func (p *proxyMirror) CheckSkill(skill string) bool { + return http_service.FilterSkillName == skill +} From 9b92d8db49af56e921dae1ae718c53a613a29f36 Mon Sep 17 00:00:00 2001 From: zhangzeyi Date: Tue, 28 Feb 2023 17:49:09 +0800 Subject: [PATCH 06/25] =?UTF-8?q?mocking=E6=8F=92=E4=BB=B6=E6=94=B9?= =?UTF-8?q?=E6=88=90http=5Fmocking=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsonSchema支持mockJs导出的jsonSchema(@mock内置方法只能支持一部分) --- app/apinto/worker.go | 4 +- drivers/plugins/http_mocking/complete.go | 102 +++++++ drivers/plugins/http_mocking/config.go | 12 + .../{mocking => http_mocking}/driver.go | 16 +- .../{mocking => http_mocking}/factory.go | 4 +- .../{mocking => http_mocking}/mocking.go | 44 ++- drivers/plugins/mocking/complete.go | 177 ----------- drivers/plugins/mocking/config.go | 16 - utils/float.go | 7 + utils/int.go | 10 + utils/json.go | 278 +++++++++++++++++- 11 files changed, 433 insertions(+), 237 deletions(-) create mode 100644 drivers/plugins/http_mocking/complete.go create mode 100644 drivers/plugins/http_mocking/config.go rename drivers/plugins/{mocking => http_mocking}/driver.go (82%) rename drivers/plugins/{mocking => http_mocking}/factory.go (89%) rename drivers/plugins/{mocking => http_mocking}/mocking.go (51%) delete mode 100644 drivers/plugins/mocking/complete.go delete mode 100644 drivers/plugins/mocking/config.go create mode 100644 utils/float.go create mode 100644 utils/int.go diff --git a/app/apinto/worker.go b/app/apinto/worker.go index 4e318af9..09308b9d 100644 --- a/app/apinto/worker.go +++ b/app/apinto/worker.go @@ -27,8 +27,8 @@ import ( "github.com/eolinker/apinto/drivers/plugins/gzip" http_to_dubbo2 "github.com/eolinker/apinto/drivers/plugins/http-to-dubbo2" http_to_grpc "github.com/eolinker/apinto/drivers/plugins/http-to-gRPC" + "github.com/eolinker/apinto/drivers/plugins/http_mocking" ip_restriction "github.com/eolinker/apinto/drivers/plugins/ip-restriction" - "github.com/eolinker/apinto/drivers/plugins/mocking" "github.com/eolinker/apinto/drivers/plugins/monitor" params_transformer "github.com/eolinker/apinto/drivers/plugins/params-transformer" prometheus_plugin "github.com/eolinker/apinto/drivers/plugins/prometheus" @@ -148,5 +148,5 @@ func Register(extenderRegister eosc.IExtenderDriverRegister) { protocbuf.Register(extenderRegister) grpc_to_http.Register(extenderRegister) - mocking.Register(extenderRegister) + http_mocking.Register(extenderRegister) } diff --git a/drivers/plugins/http_mocking/complete.go b/drivers/plugins/http_mocking/complete.go new file mode 100644 index 00000000..5c185b1d --- /dev/null +++ b/drivers/plugins/http_mocking/complete.go @@ -0,0 +1,102 @@ +package http_mocking + +import ( + "encoding/json" + "github.com/eolinker/apinto/utils" + "github.com/eolinker/eosc/eocontext" + http_context "github.com/eolinker/eosc/eocontext/http-context" + "github.com/eolinker/eosc/log" +) + +type complete struct { + responseStatus int + contentType string + responseExample string + responseSchema map[string]interface{} +} + +func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}) *complete { + return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema} +} + +func (c *complete) Complete(org eocontext.EoContext) error { + ctx, err := http_context.Assert(org) + if err != nil { + return err + } + return c.writeHttp(ctx) +} + +func (c *complete) writeHttp(ctx http_context.IHttpContext) error { + ctx.Response().SetHeader("Content-Type", c.contentType) + ctx.Response().SetStatus(c.responseStatus, "") + + if c.responseExample != "" { + ctx.Response().SetBody([]byte(c.responseExample)) + return nil + } + + schema := utils.JsonSchemaMockJsUnmarshal(c.responseSchema) + bytes, err := json.Marshal(schema) + if err != nil { + log.Errorf("mocking complete err=%s", err.Error()) + return err + } + + ctx.Response().SetBody(bytes) + return nil +} + +//func (c *complete) writeDubbo2(ctx dubbo2_context.IDubbo2Context) error { +// if c.responseExample != "" { +// +// var val interface{} +// if err := json.Unmarshal([]byte(c.responseExample), &val); err != nil { +// ctx.Response().SetBody(Dubbo2ErrorResult(err)) +// return err +// } +// +// ctx.Response().SetBody(getDubbo2Response(val, ctx.Proxy().Attachments())) +// return nil +// } +// +// schema := jsonSchemaUnmarshal(c.responseSchema) +// ctx.Response().SetBody(getDubbo2Response(schema, ctx.Proxy().Attachments())) +// return nil +//} + +//func (c *complete) writeGrpc(ctx grpc_context.IGrpcContext) error { +// descriptor, err := c.descriptor.Descriptor().FindSymbol(fmt.Sprintf("%s.%s", ctx.Proxy().Service(), ctx.Proxy().Method())) +// if err != nil { +// return err +// } +// methodDesc := descriptor.GetFile().FindService(ctx.Proxy().Service()).FindMethodByName(ctx.Proxy().Method()) +// +// message := dynamic.NewMessage(methodDesc.GetOutputType()) +// +// fields := message.GetKnownFields() +// for _, field := range fields { +// switch field.GetType() { +// case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, descriptorpb.FieldDescriptorProto_TYPE_FLOAT: //float32 +// message.SetField(field, gofakeit.Float32()) +// case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: //int64 +// message.SetField(field, gofakeit.Int64()) +// case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: //int32 +// message.SetField(field, gofakeit.Int32()) +// case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: //uint32 +// message.SetField(field, gofakeit.Uint32()) +// case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: //uint64 +// message.SetField(field, gofakeit.Uint64()) +// case descriptorpb.FieldDescriptorProto_TYPE_BOOL: //bool +// message.SetField(field, gofakeit.Bool()) +// case descriptorpb.FieldDescriptorProto_TYPE_STRING: //string +// message.SetField(field, gofakeit.LetterN(5)) +// case descriptorpb.FieldDescriptorProto_TYPE_BYTES: //bytes +// message.SetField(field, []byte(gofakeit.LetterN(5))) +// } +// +// } +// +// ctx.Response().Write(message) +// return nil +//} diff --git a/drivers/plugins/http_mocking/config.go b/drivers/plugins/http_mocking/config.go new file mode 100644 index 00000000..e0ca99c8 --- /dev/null +++ b/drivers/plugins/http_mocking/config.go @@ -0,0 +1,12 @@ +package http_mocking + +type Config struct { + ResponseStatus int `json:"response_status" default:"200" label:"返回响应的 HTTP 状态码(仅http路由有效)"` + ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json"` + ResponseExample string `json:"response_example" switch:"content_type==='application/json'" label:"返回响应的Body,与jsonschema字段二选一"` + ResponseSchema string `json:"response_schema" switch:"content_type==='application/json'" label:"指定响应的jsonschema对象"` +} + +const ( + contentTypeJson = "application/json" +) diff --git a/drivers/plugins/mocking/driver.go b/drivers/plugins/http_mocking/driver.go similarity index 82% rename from drivers/plugins/mocking/driver.go rename to drivers/plugins/http_mocking/driver.go index 0e60fcce..049b5ba0 100644 --- a/drivers/plugins/mocking/driver.go +++ b/drivers/plugins/http_mocking/driver.go @@ -1,4 +1,4 @@ -package mocking +package http_mocking import ( "encoding/json" @@ -46,9 +46,6 @@ func check(v interface{}) (*Config, error) { } } - } else if conf.ContentType == contentTypeGrpc && conf.ProtobufID == "" { - log.Errorf("mocking check ProtobufID is null ") - return nil, errors.New("param err protobufID is null") } return conf, nil @@ -60,15 +57,6 @@ func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWork bean.Autowired(&worker) }) - var descriptor grpc_descriptor.IDescriptor - if conf.ContentType == contentTypeGrpc { - descSource, err := getDescSource(string(conf.ProtobufID)) - if err != nil { - return nil, err - } - descriptor = descSource - } - jsonSchema := make(map[string]interface{}) if conf.ResponseSchema != "" { @@ -80,7 +68,7 @@ func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWork return &Mocking{ WorkerBase: drivers.Worker(id, name), - handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema, descriptor), + handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema), }, nil } diff --git a/drivers/plugins/mocking/factory.go b/drivers/plugins/http_mocking/factory.go similarity index 89% rename from drivers/plugins/mocking/factory.go rename to drivers/plugins/http_mocking/factory.go index c9f011e0..0acee001 100644 --- a/drivers/plugins/mocking/factory.go +++ b/drivers/plugins/http_mocking/factory.go @@ -1,4 +1,4 @@ -package mocking +package http_mocking import ( "sync" @@ -8,7 +8,7 @@ import ( ) const ( - Name = "mocking" + Name = "http_mocking" ) var ( diff --git a/drivers/plugins/mocking/mocking.go b/drivers/plugins/http_mocking/mocking.go similarity index 51% rename from drivers/plugins/mocking/mocking.go rename to drivers/plugins/http_mocking/mocking.go index 1bde8a49..f2ffc021 100644 --- a/drivers/plugins/mocking/mocking.go +++ b/drivers/plugins/http_mocking/mocking.go @@ -1,15 +1,16 @@ -package mocking +package http_mocking import ( "encoding/json" "github.com/eolinker/apinto/drivers" - grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor" "github.com/eolinker/eosc" "github.com/eolinker/eosc/eocontext" + http_context "github.com/eolinker/eosc/eocontext/http-context" log "github.com/eolinker/eosc/log" ) var _ eocontext.IFilter = (*Mocking)(nil) +var _ http_context.HttpFilter = (*Mocking)(nil) type Mocking struct { drivers.WorkerBase @@ -20,10 +21,9 @@ type Mocking struct { handler eocontext.CompleteHandler } -func (t *Mocking) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) { - - if t.handler != nil { - ctx.SetCompleteHandler(t.handler) +func (m *Mocking) DoHttpFilter(ctx http_context.IHttpContext, next eocontext.IChain) (err error) { + if m.handler != nil { + ctx.SetCompleteHandler(m.handler) } if next != nil { @@ -33,31 +33,27 @@ func (t *Mocking) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err return nil } -func (t *Mocking) Destroy() { +func (m *Mocking) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) { + return http_context.DoHttpFilter(m, ctx, next) +} + +func (m *Mocking) Destroy() { return } -func (t *Mocking) Start() error { +func (m *Mocking) Start() error { return nil } -func (t *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error { +func (m *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error { conf, err := check(v) if err != nil { return err } - t.responseSchema = conf.ResponseSchema - t.responseExample = conf.ResponseExample - t.contentType = conf.ContentType - t.responseStatus = conf.ResponseStatus - - var descriptor grpc_descriptor.IDescriptor - if t.contentType == contentTypeGrpc { - descriptor, err = getDescSource(string(conf.ProtobufID)) - if err != nil { - return err - } - } + m.responseSchema = conf.ResponseSchema + m.responseExample = conf.ResponseExample + m.contentType = conf.ContentType + m.responseStatus = conf.ResponseStatus jsonSchema := make(map[string]interface{}) @@ -68,15 +64,15 @@ func (t *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) } } - t.handler = NewComplete(t.responseStatus, t.contentType, t.responseExample, jsonSchema, descriptor) + m.handler = NewComplete(m.responseStatus, m.contentType, m.responseExample, jsonSchema) return nil } -func (t *Mocking) Stop() error { +func (m *Mocking) Stop() error { return nil } -func (t *Mocking) CheckSkill(skill string) bool { +func (m *Mocking) CheckSkill(skill string) bool { return true } diff --git a/drivers/plugins/mocking/complete.go b/drivers/plugins/mocking/complete.go deleted file mode 100644 index d7661f4f..00000000 --- a/drivers/plugins/mocking/complete.go +++ /dev/null @@ -1,177 +0,0 @@ -package mocking - -import ( - "dubbo.apache.org/dubbo-go/v3/protocol" - "dubbo.apache.org/dubbo-go/v3/protocol/dubbo/impl" - "encoding/json" - "errors" - "fmt" - "github.com/brianvoe/gofakeit/v6" - grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor" - "github.com/eolinker/eosc/eocontext" - dubbo2_context "github.com/eolinker/eosc/eocontext/dubbo2-context" - grpc_context "github.com/eolinker/eosc/eocontext/grpc-context" - http_context "github.com/eolinker/eosc/eocontext/http-context" - "github.com/eolinker/eosc/log" - "github.com/jhump/protoreflect/dynamic" - "google.golang.org/protobuf/types/descriptorpb" -) - -type complete struct { - responseStatus int - contentType string - responseExample string - responseSchema map[string]interface{} - descriptor grpc_descriptor.IDescriptor -} - -func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}, descriptor grpc_descriptor.IDescriptor) *complete { - return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema, descriptor: descriptor} -} - -func (c *complete) Complete(org eocontext.EoContext) error { - switch ctx := org.(type) { - case dubbo2_context.IDubbo2Context: - return c.writeDubbo2(ctx) - case http_context.IHttpContext: - return c.writeHttp(ctx) - case grpc_context.IGrpcContext: - return c.writeGrpc(ctx) - } - - return errors.New("eoContext unrealized") -} - -func (c *complete) writeHttp(ctx http_context.IHttpContext) error { - ctx.Response().SetHeader("Content-Type", c.contentType) - ctx.Response().SetStatus(c.responseStatus, "") - - if c.responseExample != "" { - ctx.Response().SetBody([]byte(c.responseExample)) - return nil - } - - schema := jsonSchemaUnmarshal(c.responseSchema) - bytes, err := json.Marshal(schema) - if err != nil { - log.Errorf("mocking complete err=%s", err.Error()) - return err - } - - ctx.Response().SetBody(bytes) - return nil -} - -func (c *complete) writeDubbo2(ctx dubbo2_context.IDubbo2Context) error { - if c.responseExample != "" { - - var val interface{} - if err := json.Unmarshal([]byte(c.responseExample), &val); err != nil { - ctx.Response().SetBody(Dubbo2ErrorResult(err)) - return err - } - - ctx.Response().SetBody(getDubbo2Response(val, ctx.Proxy().Attachments())) - return nil - } - - schema := jsonSchemaUnmarshal(c.responseSchema) - ctx.Response().SetBody(getDubbo2Response(schema, ctx.Proxy().Attachments())) - return nil -} - -func (c *complete) writeGrpc(ctx grpc_context.IGrpcContext) error { - descriptor, err := c.descriptor.Descriptor().FindSymbol(fmt.Sprintf("%s.%s", ctx.Proxy().Service(), ctx.Proxy().Method())) - if err != nil { - return err - } - methodDesc := descriptor.GetFile().FindService(ctx.Proxy().Service()).FindMethodByName(ctx.Proxy().Method()) - - message := dynamic.NewMessage(methodDesc.GetOutputType()) - - fields := message.GetKnownFields() - for _, field := range fields { - switch field.GetType() { - case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, descriptorpb.FieldDescriptorProto_TYPE_FLOAT: //float32 - message.SetField(field, gofakeit.Float32()) - case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: //int64 - message.SetField(field, gofakeit.Int64()) - case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: //int32 - message.SetField(field, gofakeit.Int32()) - case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: //uint32 - message.SetField(field, gofakeit.Uint32()) - case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: //uint64 - message.SetField(field, gofakeit.Uint64()) - case descriptorpb.FieldDescriptorProto_TYPE_BOOL: //bool - message.SetField(field, gofakeit.Bool()) - case descriptorpb.FieldDescriptorProto_TYPE_STRING: //string - message.SetField(field, gofakeit.LetterN(5)) - case descriptorpb.FieldDescriptorProto_TYPE_BYTES: //bytes - message.SetField(field, []byte(gofakeit.LetterN(5))) - } - - } - - ctx.Response().Write(message) - return nil -} - -func jsonSchemaUnmarshal(properties interface{}) interface{} { - propertiesMap, ok := properties.(map[string]interface{}) - if !ok { - return "son schema 格式错误" - } - if val, ok := propertiesMap["example"]; ok { - return val - } else { - if t, tOk := propertiesMap["type"].(string); tOk { - switch t { - case "string": - return gofakeit.LetterN(10) - case "number": - return gofakeit.Float64() - case "integer": - return gofakeit.Int64() - case "boolean": - return gofakeit.Bool() - case "object": - propertiesMaps, pOk := propertiesMap["properties"].(map[string]interface{}) - if !pOk { - return "json schema 格式错误" - } - resultMap := make(map[string]interface{}) - for key, vProperties := range propertiesMaps { - resultMap[key] = jsonSchemaUnmarshal(vProperties) - } - return resultMap - case "array": - items, iOk := propertiesMap["items"].(map[string]interface{}) - if !iOk { - return "json schema 格式错误" - } - resultList := make([]interface{}, 0) - resultList = append(resultList, jsonSchemaUnmarshal(items)) - return resultList - } - } - return "json schema 格式错误" - } -} - -func getDubbo2Response(obj interface{}, attachments map[string]interface{}) protocol.RPCResult { - payload := impl.NewResponsePayload(obj, nil, attachments) - return protocol.RPCResult{ - Attrs: payload.Attachments, - Err: payload.Exception, - Rest: payload.RspObj, - } -} - -func Dubbo2ErrorResult(err error) protocol.RPCResult { - payload := impl.NewResponsePayload(nil, err, nil) - return protocol.RPCResult{ - Attrs: payload.Attachments, - Err: payload.Exception, - Rest: payload.RspObj, - } -} diff --git a/drivers/plugins/mocking/config.go b/drivers/plugins/mocking/config.go deleted file mode 100644 index bdb88f47..00000000 --- a/drivers/plugins/mocking/config.go +++ /dev/null @@ -1,16 +0,0 @@ -package mocking - -import "github.com/eolinker/eosc" - -type Config struct { - ResponseStatus int `json:"response_status" label:"返回响应的 HTTP 状态码(仅http路由有效)"` - ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json,application/grpc"` - ProtobufID eosc.RequireId `json:"protobuf_id" label:"Protobuf ID" switch:"content_type==='application/grpc'" skill:"github.com/eolinker/apinto/grpc-transcode.transcode.IDescriptor"` - ResponseExample string `json:"response_example" switch:"content_type==='application/json'" label:"返回响应的Body,与jsonschema字段二选一"` - ResponseSchema string `json:"response_schema" switch:"content_type==='application/json'" label:"指定响应的jsonschema对象"` -} - -const ( - contentTypeJson = "application/json" - contentTypeGrpc = "application/grpc" -) diff --git a/utils/float.go b/utils/float.go new file mode 100644 index 00000000..00b0a185 --- /dev/null +++ b/utils/float.go @@ -0,0 +1,7 @@ +package utils + +import "math/rand" + +func RandFloats(min, max float64) float64 { + return min + rand.Float64()*(max-min) +} diff --git a/utils/int.go b/utils/int.go new file mode 100644 index 00000000..16acb4de --- /dev/null +++ b/utils/int.go @@ -0,0 +1,10 @@ +package utils + +import "math/rand" + +func RandInt64(min, max int64) int64 { + if min >= max || min == 0 || max == 0 { + return max + } + return rand.Int63n(max-min) + min +} diff --git a/utils/json.go b/utils/json.go index 050ed079..c64bb71d 100644 --- a/utils/json.go +++ b/utils/json.go @@ -2,12 +2,17 @@ package utils import ( "encoding/json" + "errors" "fmt" + "github.com/brianvoe/gofakeit/v6" + "math/rand" + "strconv" + "time" "github.com/robertkrimen/otto" ) -//JSObjectToJSON 将js对象转为json +// JSObjectToJSON 将js对象转为json func JSObjectToJSON(s string) ([]byte, error) { vm := otto.New() v, err := vm.Run(fmt.Sprintf(` @@ -20,7 +25,7 @@ func JSObjectToJSON(s string) ([]byte, error) { return []byte(v.String()), nil } -//JSONUnmarshal 将json格式的s解码成v所需的json格式 +// JSONUnmarshal 将json格式的s解码成v所需的json格式 func JSONUnmarshal(s, v interface{}) error { data, err := json.Marshal(s) if err != nil { @@ -28,3 +33,272 @@ func JSONUnmarshal(s, v interface{}) error { } return json.Unmarshal(data, v) } + +// JsonSchemaMockJsUnmarshal +// 解析mockJs生成的json schema 并根据规则生成随机值 +// 不是mockJs生成的json 走默认规则解析 +func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { + rand.Seed(time.Now().UnixMicro()) + gofakeit.Seed(time.Now().UnixMicro()) + + value, vOk := valueMap.(map[string]interface{}) + if vOk { + switch valType := value["properties"].(type) { + case []interface{}: + resultMap := make(map[string]interface{}) + for _, v := range valType { + if m, ok := v.(map[string]interface{}); ok { + name, nameOk := m["name"].(string) + if !nameOk { + return jsonSchemaFormat + } + template := m["template"] + if t, tOk := m["type"].(string); tOk { + rule, ruleOk := m["rule"].(map[string]interface{}) + if !ruleOk || len(rule) == 0 { + if template != nil { + if templateStr, ok := template.(string); ok { + switch templateStr { + case "@cname": + resultMap[name] = gofakeit.Username() + case "@cfirst": + resultMap[name] = gofakeit.FirstName() + case "@clast": + resultMap[name] = gofakeit.LastName() + case "@name", "@name(true)": + resultMap[name] = gofakeit.Name() + case "@first": + resultMap[name] = gofakeit.FirstName() + case "@last": + resultMap[name] = gofakeit.LastName() + case "@email": + resultMap[name] = gofakeit.Email() + case "@ip": + resultMap[name] = gofakeit.IPv4Address() + case "@zip": + resultMap[name] = gofakeit.Address().Zip + case "@city", "@city(true)": + resultMap[name] = gofakeit.Address().Address + case "@url": + resultMap[name] = gofakeit.URL() + default: + resultMap[name] = template + } + continue + } + resultMap[name] = template + continue + } + } + + switch t { + case "string": + + minVal, maxVal, _, _, err := getMinMaxDminDmax(rule) + if err != nil { + return err + } + + randomNum := 0 + if minVal > 0 && maxVal == 0 { + randomNum = int(minVal) + } + + if minVal > 0 && maxVal > 0 { + randomNum = int(RandInt64(int64(minVal), int64(maxVal))) + } + + if template != nil { + templateStr, sOk := template.(string) + if !sOk { + return jsonSchemaFormat + } + temp := "" + for i := 0; i < randomNum; i++ { + temp = temp + templateStr + } + resultMap[name] = temp + continue + } + + case "number": + minVal, maxVal, dminVal, dmaxVal, err := getMinMaxDminDmax(rule) + if err != nil { + return err + } + + randomValue := 0.0 + if minVal > 0.0 && maxVal == 0.0 { + randomValue = minVal + randomValue += RandFloats(0, 1) + } else if minVal > 0.0 && maxVal > 0.0 { + randomValue = RandFloats(minVal, maxVal) + } + + if randomValue == 0.0 { + resultMap[name] = template + continue + } + + if dminVal > 0.0 && dmaxVal == 0.0 { + randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', int(dminVal), 64), 64) + } else if dminVal > 0.0 && dmaxVal > 0.0 { + floats := RandFloats(dminVal, dmaxVal) + randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', int(floats), 64), 64) + } else { + + } + + resultMap[name] = randomValue + + case "boolean": + resultMap[name] = gofakeit.Bool() + case "object": + templateMap, templateOk := template.(map[string]interface{}) + if templateOk { + minVal, maxVal, _, _, err := getMinMaxDminDmax(rule) + if err != nil { + return err + } + randomNum := 0 + if minVal > 0 && maxVal == 0 { + randomNum = int(minVal) + } + + if minVal > 0 && maxVal > 0 { + randomNum = int(RandInt64(int64(minVal), int64(maxVal))) + } + tempMap := make(map[string]interface{}) + i := 1 + for k, v := range templateMap { + tempMap[k] = v + if i == randomNum { + break + } + i++ + } + + resultMap[name] = tempMap + + } + case "array": + templateList, templateOk := template.([]interface{}) + if templateOk { + minVal, maxVal, _, _, err := getMinMaxDminDmax(rule) + if err != nil { + return err + } + + randomNum := 0 + if minVal > 0 && maxVal == 0 { + randomNum = int(minVal) + } + + if minVal > 0 && maxVal > 0 { + randomNum = int(RandInt64(int64(minVal), int64(maxVal))) + } + tempList := make([]interface{}, 0) + + for i := 0; i < randomNum; i++ { + tempList = append(tempList, templateList[rand.Intn(len(templateList))]) + } + + resultMap[name] = tempList + + } + } + } + } + } + return resultMap + } + } + return jsonSchemaUnmarshal(value) +} + +var jsonSchemaFormat = errors.New("json schema format err") + +func getMinMaxDminDmax(rule map[string]interface{}) (float64, float64, float64, float64, error) { + minVal := 0.0 + min, minOk := rule["min"] + if minOk && min != nil { + mOk := false + minVal, mOk = min.(float64) + if !mOk { + return 0, 0, 0, 0, jsonSchemaFormat + } + + } + + maxVal := 0.0 + max, maxOk := rule["max"] + if maxOk && max != nil { + mOk := false + maxVal, mOk = max.(float64) + if !mOk { + return 0, 0, 0, 0, jsonSchemaFormat + } + } + dminVal := 0.0 + dmin, dminOk := rule["dmin"] + if dminOk && dmin != nil { + mOk := false + dminVal, mOk = dmin.(float64) + if !mOk { + return 0, 0, 0, 0, jsonSchemaFormat + } + } + + dmaxVal := 0.0 + dmax, dmaxOk := rule["dmax"] + if dmaxOk && dmax != nil { + mOk := false + dmaxVal, mOk = dmax.(float64) + if !mOk { + return 0, 0, 0, 0, jsonSchemaFormat + } + } + return minVal, maxVal, dminVal, dmaxVal, nil +} + +func jsonSchemaUnmarshal(properties interface{}) interface{} { + propertiesMap, ok := properties.(map[string]interface{}) + if !ok { + return jsonSchemaFormat + } + if val, ok := propertiesMap["example"]; ok { + return val + } else { + if t, tOk := propertiesMap["type"].(string); tOk { + switch t { + case "string": + return gofakeit.LetterN(10) + case "number": + return gofakeit.Float64() + case "integer": + return gofakeit.Int64() + case "boolean": + return gofakeit.Bool() + case "object": + propertiesMaps, pOk := propertiesMap["properties"].(map[string]interface{}) + if !pOk { + return jsonSchemaFormat + } + resultMap := make(map[string]interface{}) + for key, vProperties := range propertiesMaps { + resultMap[key] = jsonSchemaUnmarshal(vProperties) + } + return resultMap + case "array": + items, iOk := propertiesMap["items"].(map[string]interface{}) + if !iOk { + return jsonSchemaFormat + } + resultList := make([]interface{}, 0) + resultList = append(resultList, jsonSchemaUnmarshal(items)) + return resultList + } + } + return jsonSchemaFormat + } +} From cc04dd43a92769bc658140150284dbdc574f0059 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Tue, 28 Feb 2023 18:29:04 +0800 Subject: [PATCH 07/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/proxy-mirror.go | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go index ea1596ff..67ba4cc7 100644 --- a/drivers/plugins/proxy-mirror/proxy-mirror.go +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -6,6 +6,8 @@ import ( "github.com/eolinker/eosc/eocontext" http_service "github.com/eolinker/eosc/eocontext/http-context" "github.com/eolinker/eosc/log" + "math/rand" + "time" ) var _ eocontext.IFilter = (*proxyMirror)(nil) @@ -27,13 +29,26 @@ func (p *proxyMirror) DoHttpFilter(ctx http_service.IHttpContext, next eocontext log.Error(err) } } - //进行采样, 生成随机数判断 - - //进行转发 + if p.proxyConf != nil { + //进行采样, 生成随机数判断 + rand.Seed(time.Now().UnixNano()) + randomNum := rand.Intn(p.proxyConf.SampleConf.RandomRange + 1) //[0,range]范围内整型 + if randomNum <= p.proxyConf.SampleConf.RandomPivot { //若随机数在[0,pivot]范围内则进行转发 + //进行转发 + go sendMirrorProxy(p.proxyConf, ctx) + } + } return nil } +func sendMirrorProxy(proxyCfg *Config, ctx http_service.IHttpContext) { + //先判断当前Ctx是否能Copy,若可以就进行copy并且设置新的APP + + //send + +} + func (p *proxyMirror) Start() error { return nil } @@ -53,6 +68,7 @@ func (p *proxyMirror) Stop() error { } func (p *proxyMirror) Destroy() { + p.proxyConf = nil } func (p *proxyMirror) CheckSkill(skill string) bool { From 8e8b2766e41112397f2db2c157d4d4b096a5d063 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Wed, 1 Mar 2023 10:19:27 +0800 Subject: [PATCH 08/25] =?UTF-8?q?DubboCtx,grpcCtx,WebSocketCtx,=E8=A1=A5?= =?UTF-8?q?=E5=85=85IsCloneable,Clone=E6=96=B9=E6=B3=95=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- node/dubbo2-context/context.go | 12 ++++++++++++ node/grpc-context/context.go | 12 ++++++++++++ node/http-context/context.go | 12 ++++++++++++ node/http-context/websocket-context.go | 13 +++++++++++++ 4 files changed, 49 insertions(+) diff --git a/node/dubbo2-context/context.go b/node/dubbo2-context/context.go index 82ac55db..96022480 100644 --- a/node/dubbo2-context/context.go +++ b/node/dubbo2-context/context.go @@ -290,6 +290,18 @@ func (d *DubboContext) LocalPort() int { return d.port } +func (d *DubboContext) IsCloneable() bool { + return false +} + +func (d *DubboContext) Clone() (eocontext.EoContext, error) { + if !d.IsCloneable() { + return nil, fmt.Errorf("%s %w", "DubboContext", eocontext.ErrEoCtxUnCloneable) + } + //TODO + return nil, nil +} + func addrToIP(addr net.Addr) net.IP { x, ok := addr.(*net.TCPAddr) if !ok { diff --git a/node/grpc-context/context.go b/node/grpc-context/context.go index 90432287..75a24685 100644 --- a/node/grpc-context/context.go +++ b/node/grpc-context/context.go @@ -260,3 +260,15 @@ func (c *Context) reset() { pool.Put(c) } + +func (c *Context) IsCloneable() bool { + return false +} + +func (c *Context) Clone() (eocontext.EoContext, error) { + if !c.IsCloneable() { + return nil, fmt.Errorf("%s %w", "GrpcContext", eocontext.ErrEoCtxUnCloneable) + } + //TODO + return nil, nil +} diff --git a/node/http-context/context.go b/node/http-context/context.go index 0b9576b8..a64683be 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -179,6 +179,18 @@ func (ctx *HttpContext) Request() http_service.IRequestReader { return &ctx.requestReader } +func (ctx *HttpContext) IsCloneable() bool { + return true +} + +func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { + if !ctx.IsCloneable() { + return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) + } + //TODO + return nil, nil +} + // NewContext 创建Context func NewContext(ctx *fasthttp.RequestCtx, port int) *HttpContext { diff --git a/node/http-context/websocket-context.go b/node/http-context/websocket-context.go index 627c077c..6004b4bc 100644 --- a/node/http-context/websocket-context.go +++ b/node/http-context/websocket-context.go @@ -3,6 +3,7 @@ package http_context import ( "errors" "fmt" + eoscContext "github.com/eolinker/eosc/eocontext" "io" "net" "sync" @@ -79,3 +80,15 @@ func (w *WebsocketContext) Assert(i interface{}) error { } return fmt.Errorf("not suport:%s", config.TypeNameOf(i)) } + +func (w *WebsocketContext) IsCloneable() bool { + return false +} + +func (w *WebsocketContext) Clone() (eoscContext.EoContext, error) { + if !w.IsCloneable() { + return nil, fmt.Errorf("%s %w", "WebsocketContext", eoscContext.ErrEoCtxUnCloneable) + } + //TODO + return nil, nil +} From 1c0e58563dc95dd9a07317552ed29f882985b354 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Wed, 1 Mar 2023 12:07:53 +0800 Subject: [PATCH 09/25] =?UTF-8?q?=E5=AE=8C=E6=88=90http-ctx=20Clone?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response-rewrite/response-rewrite.go | 2 +- node/http-context/context.go | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/drivers/plugins/response-rewrite/response-rewrite.go b/drivers/plugins/response-rewrite/response-rewrite.go index 8dbc2985..34dda3eb 100644 --- a/drivers/plugins/response-rewrite/response-rewrite.go +++ b/drivers/plugins/response-rewrite/response-rewrite.go @@ -4,7 +4,7 @@ import ( "github.com/eolinker/apinto/drivers" "github.com/eolinker/apinto/utils" "github.com/eolinker/eosc/eocontext" - log "github.com/eolinker/goku-api-gateway/goku-log" + "github.com/eolinker/eosc/log" "strconv" "github.com/eolinker/eosc" diff --git a/node/http-context/context.go b/node/http-context/context.go index a64683be..8ecd1d0f 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -187,8 +187,38 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { if !ctx.IsCloneable() { return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) } - //TODO - return nil, nil + + cloneCtx := pool.Get().(*HttpContext) + remoteAddr := ctx.fastHttpRequestCtx.RemoteAddr().String() + + cloneReq := fasthttp.AcquireRequest() + ctx.proxyRequest.Request().CopyTo(cloneReq) + cloneResp := fasthttp.AcquireResponse() + ctx.response.Response.CopyTo(cloneResp) + + cloneCtx.fastHttpRequestCtx = ctx.fastHttpRequestCtx //TODO + cloneCtx.requestID = ctx.requestID //TODO + cloneCtx.requestReader.reset(cloneReq, remoteAddr) + cloneCtx.proxyRequest.reset(cloneReq, remoteAddr) + cloneCtx.proxyRequests = cloneCtx.proxyRequests[:0] + cloneCtx.response.reset(cloneResp) + + cloneCtx.port = ctx.port + cloneCtx.ctx = ctx.ctx + + cloneCtx.completeHandler = ctx.completeHandler + cloneCtx.finishHandler = ctx.finishHandler + cloneCtx.upstreamHostHandler = ctx.upstreamHostHandler + cloneCtx.app = ctx.app + cloneCtx.balance = ctx.balance + + cLabels := make(map[string]string, len(ctx.labels)) + for k, v := range ctx.labels { + cLabels[k] = v + } + cloneCtx.labels = cLabels + + return cloneCtx, nil } // NewContext 创建Context From 2cfa42094156795b6ad624f4619bd9da4a3fdd94 Mon Sep 17 00:00:00 2001 From: zhangzeyi Date: Wed, 1 Mar 2023 14:20:08 +0800 Subject: [PATCH 10/25] =?UTF-8?q?http=5Fmocking=E6=8F=92=E4=BB=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9Eheader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsonSchema新增测试用例 --- drivers/plugins/http_mocking/complete.go | 9 +- drivers/plugins/http_mocking/config.go | 9 +- drivers/plugins/http_mocking/driver.go | 2 +- drivers/plugins/http_mocking/mocking.go | 4 +- utils/aes_test.go | 6 - utils/json.go | 100 +- utils/json_test.go | 1329 ++++++++++++++++++++++ 7 files changed, 1409 insertions(+), 50 deletions(-) create mode 100644 utils/json_test.go diff --git a/drivers/plugins/http_mocking/complete.go b/drivers/plugins/http_mocking/complete.go index 5c185b1d..33a4a3d5 100644 --- a/drivers/plugins/http_mocking/complete.go +++ b/drivers/plugins/http_mocking/complete.go @@ -13,10 +13,11 @@ type complete struct { contentType string responseExample string responseSchema map[string]interface{} + responseHeader map[string]string } -func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}) *complete { - return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema} +func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}, responseHeader map[string]string) *complete { + return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema, responseHeader: responseHeader} } func (c *complete) Complete(org eocontext.EoContext) error { @@ -31,6 +32,10 @@ func (c *complete) writeHttp(ctx http_context.IHttpContext) error { ctx.Response().SetHeader("Content-Type", c.contentType) ctx.Response().SetStatus(c.responseStatus, "") + for k, v := range c.responseHeader { + ctx.Response().SetHeader(k, v) + } + if c.responseExample != "" { ctx.Response().SetBody([]byte(c.responseExample)) return nil diff --git a/drivers/plugins/http_mocking/config.go b/drivers/plugins/http_mocking/config.go index e0ca99c8..85e745e8 100644 --- a/drivers/plugins/http_mocking/config.go +++ b/drivers/plugins/http_mocking/config.go @@ -1,10 +1,11 @@ package http_mocking type Config struct { - ResponseStatus int `json:"response_status" default:"200" label:"返回响应的 HTTP 状态码(仅http路由有效)"` - ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json"` - ResponseExample string `json:"response_example" switch:"content_type==='application/json'" label:"返回响应的Body,与jsonschema字段二选一"` - ResponseSchema string `json:"response_schema" switch:"content_type==='application/json'" label:"指定响应的jsonschema对象"` + ResponseStatus int `json:"response_status" default:"200" label:"返回响应的 HTTP 状态码(仅http路由有效)"` + ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json"` + ResponseExample string `json:"response_example" format:"text" label:"返回响应的Body,与jsonschema字段二选一"` + ResponseSchema string `json:"response_schema" format:"text" label:"指定响应的jsonschema对象"` + ResponseHeader map[string]string `json:"response_header" label:"响应头"` } const ( diff --git a/drivers/plugins/http_mocking/driver.go b/drivers/plugins/http_mocking/driver.go index 049b5ba0..4a19b7ba 100644 --- a/drivers/plugins/http_mocking/driver.go +++ b/drivers/plugins/http_mocking/driver.go @@ -68,7 +68,7 @@ func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWork return &Mocking{ WorkerBase: drivers.Worker(id, name), - handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema), + handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema, conf.ResponseHeader), }, nil } diff --git a/drivers/plugins/http_mocking/mocking.go b/drivers/plugins/http_mocking/mocking.go index f2ffc021..e280c0c6 100644 --- a/drivers/plugins/http_mocking/mocking.go +++ b/drivers/plugins/http_mocking/mocking.go @@ -18,6 +18,7 @@ type Mocking struct { contentType string responseExample string responseSchema string + responseHeader map[string]string handler eocontext.CompleteHandler } @@ -54,6 +55,7 @@ func (m *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) m.responseExample = conf.ResponseExample m.contentType = conf.ContentType m.responseStatus = conf.ResponseStatus + m.responseHeader = conf.ResponseHeader jsonSchema := make(map[string]interface{}) @@ -64,7 +66,7 @@ func (m *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) } } - m.handler = NewComplete(m.responseStatus, m.contentType, m.responseExample, jsonSchema) + m.handler = NewComplete(m.responseStatus, m.contentType, m.responseExample, jsonSchema, m.responseHeader) return nil } diff --git a/utils/aes_test.go b/utils/aes_test.go index 9b6727a0..3a6325d2 100644 --- a/utils/aes_test.go +++ b/utils/aes_test.go @@ -1,14 +1,8 @@ package utils import ( - "fmt" "testing" ) func TestAes(t *testing.T) { - key := Md5("open-api") - enValue := AES_CBC_Encrypt([]byte(Md5("Key123qaz:open-api")), []byte(key)) - deValue := AES_CBC_Decrypt(enValue, []byte(key)) - log.Debug(enValue) - log.Debug(string(deValue)) } diff --git a/utils/json.go b/utils/json.go index c64bb71d..79df7fd2 100644 --- a/utils/json.go +++ b/utils/json.go @@ -7,6 +7,7 @@ import ( "github.com/brianvoe/gofakeit/v6" "math/rand" "strconv" + "strings" "time" "github.com/robertkrimen/otto" @@ -57,36 +58,7 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { rule, ruleOk := m["rule"].(map[string]interface{}) if !ruleOk || len(rule) == 0 { if template != nil { - if templateStr, ok := template.(string); ok { - switch templateStr { - case "@cname": - resultMap[name] = gofakeit.Username() - case "@cfirst": - resultMap[name] = gofakeit.FirstName() - case "@clast": - resultMap[name] = gofakeit.LastName() - case "@name", "@name(true)": - resultMap[name] = gofakeit.Name() - case "@first": - resultMap[name] = gofakeit.FirstName() - case "@last": - resultMap[name] = gofakeit.LastName() - case "@email": - resultMap[name] = gofakeit.Email() - case "@ip": - resultMap[name] = gofakeit.IPv4Address() - case "@zip": - resultMap[name] = gofakeit.Address().Zip - case "@city", "@city(true)": - resultMap[name] = gofakeit.Address().Address - case "@url": - resultMap[name] = gofakeit.URL() - default: - resultMap[name] = template - } - continue - } - resultMap[name] = template + resultMap[name] = mockConstant(template) continue } } @@ -146,7 +118,7 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { floats := RandFloats(dminVal, dmaxVal) randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', int(floats), 64), 64) } else { - + randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', 0, 64), 64) } resultMap[name] = randomValue @@ -170,8 +142,11 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { } tempMap := make(map[string]interface{}) i := 1 - for k, v := range templateMap { - tempMap[k] = v + for key, val := range templateMap { + + split := strings.Split(key, "|") + tempMap[split[0]] = mockConstant(val) + if i == randomNum { break } @@ -190,17 +165,37 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { } randomNum := 0 - if minVal > 0 && maxVal == 0 { + if minVal > 0.0 && maxVal == 0.0 { randomNum = int(minVal) } - if minVal > 0 && maxVal > 0 { + if minVal > 0.0 && maxVal > 0.0 { randomNum = int(RandInt64(int64(minVal), int64(maxVal))) } + + if randomNum == 1 { //随机选取一个 + resultMap[name] = templateList[rand.Intn(len(templateList))] + continue + } + tempList := make([]interface{}, 0) for i := 0; i < randomNum; i++ { - tempList = append(tempList, templateList[rand.Intn(len(templateList))]) + + for _, templateType := range templateList { + switch templateVal := templateType.(type) { + case map[string]interface{}: + tempMap := make(map[string]interface{}) + for key, val := range templateVal { + split := strings.Split(key, "|") + tempMap[split[0]] = mockConstant(val) + } + tempList = append(tempList, tempMap) + default: + tempList = append(tempList, templateType) + } + } + } resultMap[name] = tempList @@ -216,6 +211,39 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { return jsonSchemaUnmarshal(value) } +func mockConstant(v interface{}) interface{} { + if templateStr, ok := v.(string); ok { + templateStr = strings.ToLower(templateStr) + switch templateStr { + case "@cname": + return gofakeit.Username() + case "@cfirst": + return gofakeit.FirstName() + case "@clast": + return gofakeit.LastName() + case "@name", "@name(true)": + return gofakeit.Name() + case "@first": + return gofakeit.FirstName() + case "@last": + return gofakeit.LastName() + case "@email": + return gofakeit.Email() + case "@ip": + return gofakeit.IPv4Address() + case "@zip": + return gofakeit.Address().Zip + case "@city", "@city(true)": + return gofakeit.Address().Address + case "@url": + return gofakeit.URL() + default: + return v + } + } + return v +} + var jsonSchemaFormat = errors.New("json schema format err") func getMinMaxDminDmax(rule map[string]interface{}) (float64, float64, float64, float64, error) { diff --git a/utils/json_test.go b/utils/json_test.go new file mode 100644 index 00000000..1366699a --- /dev/null +++ b/utils/json_test.go @@ -0,0 +1,1329 @@ +package utils + +import ( + "encoding/json" + "testing" +) + +// 官方格式json Schema +var test1 = ` +{ + "properties":{ + "field0":{ + "example":"abcd", + "type":"string" + }, + "field1":{ + "example":123.12, + "type":"number" + }, + "field3":{ + "properties":{ + "field3_1":{ + "type":"string" + }, + "field3_2":{ + "properties":{ + "field3_2_1":{ + "example":true, + "type":"boolean" + }, + "field3_2_2":{ + "items":{ + "example":155.55, + "type":"integer" + }, + "type":"array" + } + }, + "type":"object" + } + }, + "type":"object" + }, + "field2":{ + "items":{ + "type":"string" + }, + "type":"array" + } + }, + "type":"object" +}` + +/* +mockJs template + + var template = { + 'name': '@cname', // 生成中文名字 + 'age|18-30': 20, // 生成18~30之间的随机整数 + 'gender|1': ['男', '女'], // 从数组中随机选取一个元素 + 'email': '@email' // 生成邮箱 + } + +Mock.toJSONSchema(template)生成的json schema +*/ +var mock1 = `{ + "template": { + "name": "@cname", + "age|18-30": 20, + "gender|1": [ + "男", + "女" + ], + "email": "@email" + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "name", + "template": "@cname", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "name" + ] + }, + { + "name": "age", + "template": 20, + "type": "number", + "rule": { + "parameters": [ + "age|18-30", + "age", + null, + "18-30", + null + ], + "range": [ + "18-30", + "18", + "30" + ], + "min": 18, + "max": 30, + "count": 22 + }, + "path": [ + "ROOT", + "age" + ] + }, + { + "name": "gender", + "template": [ + "男", + "女" + ], + "type": "array", + "rule": { + "parameters": [ + "gender|1", + "gender", + null, + "1", + null + ], + "range": [ + "1", + "1", + null + ], + "min": 1, + "count": 1 + }, + "path": [ + "ROOT", + "gender" + ], + "items": [ + { + "name": 0, + "template": "男", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "gender", + 0 + ] + }, + { + "name": 1, + "template": "女", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "gender", + 1 + ] + } + ] + }, + { + "name": "email", + "template": "@email", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "email" + ] + } + ] +}` + +/* +mockJs template + + var template = { + 'list|1-10': [{ + 'id|+1': 1, + 'email': '@EMAIL' + }] +} + +Mock.toJSONSchema(template)生成的json schema +*/ + +var mock2 = `{ + "template": { + "list|1-10": [ + { + "id|+1": 7, + "email": "@EMAIL" + } + ] + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "list", + "template": [ + { + "id|+1": 7, + "email": "@EMAIL" + } + ], + "type": "array", + "rule": { + "parameters": [ + "list|1-10", + "list", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 2 + }, + "path": [ + "ROOT", + "list" + ], + "items": [ + { + "name": 0, + "template": { + "id|+1": 7, + "email": "@EMAIL" + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT", + "list", + 0 + ], + "properties": [ + { + "name": "id", + "template": 7, + "type": "number", + "rule": { + "parameters": [ + "id|+1", + "id", + "1", + null, + null + ] + }, + "path": [ + "ROOT", + "list", + 0, + "id" + ] + }, + { + "name": "email", + "template": "@EMAIL", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "list", + 0, + "email" + ] + } + ] + } + ] + } + ] +}` + +/* + var template = { + 'list|1-10': { + 'id|+1': 1, + 'email': '@EMAIL' + } + } +*/ +var mock3 = `{ + "template": { + "list|1-10": { + "id|+1": 1, + "email": "@EMAIL" + } + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "list", + "template": { + "id|+1": 1, + "email": "@EMAIL" + }, + "type": "object", + "rule": { + "parameters": [ + "list|1-10", + "list", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 4 + }, + "path": [ + "ROOT", + "list" + ], + "properties": [ + { + "name": "id", + "template": 1, + "type": "number", + "rule": { + "parameters": [ + "id|+1", + "id", + "1", + null, + null + ] + }, + "path": [ + "ROOT", + "list", + "id" + ] + }, + { + "name": "email", + "template": "@EMAIL", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "list", + "email" + ] + } + ] + } + ] +}` + +/* + var template = { + 'key|1-10': '★' + } +*/ +var mock4 = `{ + "template": { + "key|1-10": "★" + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "key", + "template": "★", + "type": "string", + "rule": { + "parameters": [ + "key|1-10", + "key", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 2 + }, + "path": [ + "ROOT", + "key" + ] + } + ] +}` + +/* + var template = { + 'title': 'Syntax Demo', + + 'string1|1-10': '★', + 'string2|3': 'value', + + 'number1|+1': 100, + 'number2|1-100': 100, + 'number3|1-100.1-10': 1, + 'number4|123.1-10': 1, + 'number5|123.3': 1, + 'number6|123.10': 1.123, + + 'boolean1|1': true, + 'boolean2|1-2': true, + + 'object1|2-4': { + '110000': '北京市', + '120000': '天津市', + '130000': '河北省', + '140000': '山西省' + }, + 'object2|2': { + '310000': '上海市', + '320000': '江苏省', + '330000': '浙江省', + '340000': '安徽省' + }, + 'object3|2': { + '310000': '@name', + '320000': '@ip', + '330000': '@email' + }, + + 'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'], + 'array2|1-10': [{ + 'id':10, + 'ip':'@ip' + }], + 'array3|3': ['Mock.js'], + 'array4|3-5': [10,20,30,40] +} +*/ + +var mock5 = `{ + "template": { + "title": "Syntax Demo", + "string1|1-10": "★", + "string2|3": "value", + "number1|+1": 101, + "number2|1-100": 100, + "number3|1-100.1-10": 1, + "number4|123.1-10": 1, + "number5|123.3": 1, + "number6|123.10": 1.123, + "boolean1|1": true, + "boolean2|1-2": true, + "object1|2-4": { + "110000": "北京市", + "120000": "天津市", + "130000": "河北省", + "140000": "山西省" + }, + "object2|2": { + "310000": "上海市", + "320000": "江苏省", + "330000": "浙江省", + "340000": "安徽省" + }, + "object3|2": { + "310000": "@name", + "320000": "@ip", + "330000": "@email" + }, + "array1|1": [ + "AMD", + "CMD", + "KMD", + "UMD" + ], + "array2|1-10": [ + { + "id": 10, + "ip": "@ip" + } + ], + "array3|3": [ + "Mock.js" + ], + "array4|3-5": [ + 10, + 20, + 30, + 40 + ] + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "title", + "template": "Syntax Demo", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "title" + ] + }, + { + "name": "string1", + "template": "★", + "type": "string", + "rule": { + "parameters": [ + "string1|1-10", + "string1", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 5 + }, + "path": [ + "ROOT", + "string1" + ] + }, + { + "name": "string2", + "template": "value", + "type": "string", + "rule": { + "parameters": [ + "string2|3", + "string2", + null, + "3", + null + ], + "range": [ + "3", + "3", + null + ], + "min": 3, + "count": 3 + }, + "path": [ + "ROOT", + "string2" + ] + }, + { + "name": "number1", + "template": 101, + "type": "number", + "rule": { + "parameters": [ + "number1|+1", + "number1", + "1", + null, + null + ] + }, + "path": [ + "ROOT", + "number1" + ] + }, + { + "name": "number2", + "template": 100, + "type": "number", + "rule": { + "parameters": [ + "number2|1-100", + "number2", + null, + "1-100", + null + ], + "range": [ + "1-100", + "1", + "100" + ], + "min": 1, + "max": 100, + "count": 61 + }, + "path": [ + "ROOT", + "number2" + ] + }, + { + "name": "number3", + "template": 1, + "type": "number", + "rule": { + "parameters": [ + "number3|1-100.1-10", + "number3", + null, + "1-100", + "1-10" + ], + "range": [ + "1-100", + "1", + "100" + ], + "min": 1, + "max": 100, + "count": 70, + "decimal": [ + "1-10", + "1", + "10" + ], + "dmin": 1, + "dmax": 10, + "dcount": 6 + }, + "path": [ + "ROOT", + "number3" + ] + }, + { + "name": "number4", + "template": 1, + "type": "number", + "rule": { + "parameters": [ + "number4|123.1-10", + "number4", + null, + "123", + "1-10" + ], + "range": [ + "123", + "123", + null + ], + "min": 123, + "count": 123, + "decimal": [ + "1-10", + "1", + "10" + ], + "dmin": 1, + "dmax": 10, + "dcount": 7 + }, + "path": [ + "ROOT", + "number4" + ] + }, + { + "name": "number5", + "template": 1, + "type": "number", + "rule": { + "parameters": [ + "number5|123.3", + "number5", + null, + "123", + "3" + ], + "range": [ + "123", + "123", + null + ], + "min": 123, + "count": 123, + "decimal": [ + "3", + "3", + null + ], + "dmin": 3, + "dmax": null, + "dcount": 3 + }, + "path": [ + "ROOT", + "number5" + ] + }, + { + "name": "number6", + "template": 1.123, + "type": "number", + "rule": { + "parameters": [ + "number6|123.10", + "number6", + null, + "123", + "10" + ], + "range": [ + "123", + "123", + null + ], + "min": 123, + "count": 123, + "decimal": [ + "10", + "10", + null + ], + "dmin": 10, + "dmax": null, + "dcount": 10 + }, + "path": [ + "ROOT", + "number6" + ] + }, + { + "name": "boolean1", + "template": true, + "type": "boolean", + "rule": { + "parameters": [ + "boolean1|1", + "boolean1", + null, + "1", + null + ], + "range": [ + "1", + "1", + null + ], + "min": 1, + "count": 1 + }, + "path": [ + "ROOT", + "boolean1" + ] + }, + { + "name": "boolean2", + "template": true, + "type": "boolean", + "rule": { + "parameters": [ + "boolean2|1-2", + "boolean2", + null, + "1-2", + null + ], + "range": [ + "1-2", + "1", + "2" + ], + "min": 1, + "max": 2, + "count": 2 + }, + "path": [ + "ROOT", + "boolean2" + ] + }, + { + "name": "object1", + "template": { + "110000": "北京市", + "120000": "天津市", + "130000": "河北省", + "140000": "山西省" + }, + "type": "object", + "rule": { + "parameters": [ + "object1|2-4", + "object1", + null, + "2-4", + null + ], + "range": [ + "2-4", + "2", + "4" + ], + "min": 2, + "max": 4, + "count": 4 + }, + "path": [ + "ROOT", + "object1" + ], + "properties": [ + { + "name": "110000", + "template": "北京市", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object1", + "110000" + ] + }, + { + "name": "120000", + "template": "天津市", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object1", + "120000" + ] + }, + { + "name": "130000", + "template": "河北省", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object1", + "130000" + ] + }, + { + "name": "140000", + "template": "山西省", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object1", + "140000" + ] + } + ] + }, + { + "name": "object2", + "template": { + "310000": "上海市", + "320000": "江苏省", + "330000": "浙江省", + "340000": "安徽省" + }, + "type": "object", + "rule": { + "parameters": [ + "object2|2", + "object2", + null, + "2", + null + ], + "range": [ + "2", + "2", + null + ], + "min": 2, + "count": 2 + }, + "path": [ + "ROOT", + "object2" + ], + "properties": [ + { + "name": "310000", + "template": "上海市", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object2", + "310000" + ] + }, + { + "name": "320000", + "template": "江苏省", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object2", + "320000" + ] + }, + { + "name": "330000", + "template": "浙江省", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object2", + "330000" + ] + }, + { + "name": "340000", + "template": "安徽省", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object2", + "340000" + ] + } + ] + }, + { + "name": "object3", + "template": { + "310000": "@name", + "320000": "@ip", + "330000": "@email" + }, + "type": "object", + "rule": { + "parameters": [ + "object3|2", + "object3", + null, + "2", + null + ], + "range": [ + "2", + "2", + null + ], + "min": 2, + "count": 2 + }, + "path": [ + "ROOT", + "object3" + ], + "properties": [ + { + "name": "310000", + "template": "@name", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object3", + "310000" + ] + }, + { + "name": "320000", + "template": "@ip", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object3", + "320000" + ] + }, + { + "name": "330000", + "template": "@email", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "object3", + "330000" + ] + } + ] + }, + { + "name": "array1", + "template": [ + "AMD", + "CMD", + "KMD", + "UMD" + ], + "type": "array", + "rule": { + "parameters": [ + "array1|1", + "array1", + null, + "1", + null + ], + "range": [ + "1", + "1", + null + ], + "min": 1, + "count": 1 + }, + "path": [ + "ROOT", + "array1" + ], + "items": [ + { + "name": 0, + "template": "AMD", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array1", + 0 + ] + }, + { + "name": 1, + "template": "CMD", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array1", + 1 + ] + }, + { + "name": 2, + "template": "KMD", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array1", + 2 + ] + }, + { + "name": 3, + "template": "UMD", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array1", + 3 + ] + } + ] + }, + { + "name": "array2", + "template": [ + { + "id": 10, + "ip": "@ip" + } + ], + "type": "array", + "rule": { + "parameters": [ + "array2|1-10", + "array2", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 1 + }, + "path": [ + "ROOT", + "array2" + ], + "items": [ + { + "name": 0, + "template": { + "id": 10, + "ip": "@ip" + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT", + "array2", + 0 + ], + "properties": [ + { + "name": "id", + "template": 10, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "array2", + 0, + "id" + ] + }, + { + "name": "ip", + "template": "@ip", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array2", + 0, + "ip" + ] + } + ] + } + ] + }, + { + "name": "array3", + "template": [ + "Mock.js" + ], + "type": "array", + "rule": { + "parameters": [ + "array3|3", + "array3", + null, + "3", + null + ], + "range": [ + "3", + "3", + null + ], + "min": 3, + "count": 3 + }, + "path": [ + "ROOT", + "array3" + ], + "items": [ + { + "name": 0, + "template": "Mock.js", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "array3", + 0 + ] + } + ] + }, + { + "name": "array4", + "template": [ + 10, + 20, + 30, + 40 + ], + "type": "array", + "rule": { + "parameters": [ + "array4|3-5", + "array4", + null, + "3-5", + null + ], + "range": [ + "3-5", + "3", + "5" + ], + "min": 3, + "max": 5, + "count": 5 + }, + "path": [ + "ROOT", + "array4" + ], + "items": [ + { + "name": 0, + "template": 10, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "array4", + 0 + ] + }, + { + "name": 1, + "template": 20, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "array4", + 1 + ] + }, + { + "name": 2, + "template": 30, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "array4", + 2 + ] + }, + { + "name": 3, + "template": 40, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "array4", + 3 + ] + } + ] + } + ] +}` + +func TestJsonSchemaMockJsUnmarshal(t *testing.T) { + type args struct { + valueMap interface{} + } + + valueMap := make(map[string]interface{}) + json.Unmarshal([]byte(mock5), &valueMap) + tests := []struct { + name string + args args + want interface{} + }{ + { + name: "", + args: args{ + valueMap: valueMap, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := JsonSchemaMockJsUnmarshal(tt.args.valueMap) + bytes, _ := json.Marshal(got) + t.Logf(string(bytes)) + }) + } +} From e10b07ff4ffed15f25c04499dff6f05897951293 Mon Sep 17 00:00:00 2001 From: zhangzeyi Date: Wed, 1 Mar 2023 14:38:11 +0800 Subject: [PATCH 11/25] =?UTF-8?q?http=5Fmocking=E6=94=B9=E4=B8=BAhttp-mock?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/http_mocking/factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/plugins/http_mocking/factory.go b/drivers/plugins/http_mocking/factory.go index 0acee001..dc8b42e1 100644 --- a/drivers/plugins/http_mocking/factory.go +++ b/drivers/plugins/http_mocking/factory.go @@ -8,7 +8,7 @@ import ( ) const ( - Name = "http_mocking" + Name = "http-mocking" ) var ( From bf7143c930a02cb4838c01404f182923bd72614c Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Wed, 1 Mar 2023 18:09:23 +0800 Subject: [PATCH 12/25] =?UTF-8?q?=E5=AE=8C=E6=88=90complete=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/complete.go | 106 +++++++++++++++++++ drivers/plugins/proxy-mirror/config.go | 5 + drivers/plugins/proxy-mirror/error.go | 4 + drivers/plugins/proxy-mirror/handler.go | 52 +++++++++ drivers/plugins/proxy-mirror/proxy-mirror.go | 26 ++--- node/http-context/context.go | 6 +- 6 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 drivers/plugins/proxy-mirror/complete.go create mode 100644 drivers/plugins/proxy-mirror/handler.go diff --git a/drivers/plugins/proxy-mirror/complete.go b/drivers/plugins/proxy-mirror/complete.go new file mode 100644 index 00000000..39c8b469 --- /dev/null +++ b/drivers/plugins/proxy-mirror/complete.go @@ -0,0 +1,106 @@ +package proxy_mirror + +import ( + "fmt" + "github.com/eolinker/eosc/eocontext" + http_service "github.com/eolinker/eosc/eocontext/http-context" + "github.com/eolinker/eosc/log" + "time" +) + +type httpComplete struct { + proxyCfg *Config +} + +func newHttpMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { + return &httpComplete{ + proxyCfg: proxyCfg, + } +} + +func (h *httpComplete) Complete(org eocontext.EoContext) error { + ctx, err := http_service.Assert(org) + if err != nil { + return err + } + //设置响应开始时间 + proxyTime := time.Now() + + defer func() { + //设置原始响应状态码 + ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "") + //设置上游响应总时间, 单位为毫秒 + //ctx.WithValue("response_time", time.Now().Sub(proxyTime).Milliseconds()) + ctx.Response().SetResponseTime(time.Now().Sub(proxyTime)) + ctx.SetLabel("handler", "proxy") + }() + + var lastErr error + + timeOut := time.Duration(h.proxyCfg.Timeout) * time.Millisecond + + //构造addr + path := ctx.Request().URI().Path() + if h.proxyCfg.Path != "" { + switch h.proxyCfg.PathMode { + case pathModeReplace: + path = h.proxyCfg.Path + case pathModePrefix: + path = fmt.Sprintf("%s%s", h.proxyCfg.Path, path) + } + } + addr := fmt.Sprintf("%s%s", h.proxyCfg.Host, path) + + lastErr = ctx.SendTo(addr, timeOut) + if lastErr == nil { + return nil + } + log.Error("http proxyMirror send error: ", lastErr) + + return lastErr +} + +type dubbo2Complete struct { + proxyCfg *Config +} + +func newDubbo2MirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { + return &httpComplete{ + proxyCfg: proxyCfg, + } +} + +func (d *dubbo2Complete) Complete(ctx eocontext.EoContext) error { + //TODO implement me + return nil +} + +type grpcComplete struct { + proxyCfg *Config +} + +func newGrpcMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { + return &httpComplete{ + proxyCfg: proxyCfg, + } +} + +func (g *grpcComplete) Complete(ctx eocontext.EoContext) error { + //TODO implement me + return nil +} + +type websocketComplete struct { + proxyCfg *Config +} + +func newWebsocketMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { + return &httpComplete{ + proxyCfg: proxyCfg, + } +} + +func (w *websocketComplete) Complete(ctx eocontext.EoContext) error { + //TODO implement me + return nil +} diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go index 16aff09d..8df93980 100644 --- a/drivers/plugins/proxy-mirror/config.go +++ b/drivers/plugins/proxy-mirror/config.go @@ -13,6 +13,11 @@ type SampleConfig struct { RandomPivot int `json:"random_pivot" label:"随机数锚点"` } +const ( + pathModeReplace = "replace" + pathModePrefix = "prefix" +) + func (c *Config) doCheck() error { //TODO diff --git a/drivers/plugins/proxy-mirror/error.go b/drivers/plugins/proxy-mirror/error.go index f2ec70d3..1ff39ea5 100644 --- a/drivers/plugins/proxy-mirror/error.go +++ b/drivers/plugins/proxy-mirror/error.go @@ -1 +1,5 @@ package proxy_mirror + +import "github.com/pkg/errors" + +var ErrUnsupportedType = errors.New("send mirror proxy fail. Unsupported Context Type") diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go new file mode 100644 index 00000000..3dd27016 --- /dev/null +++ b/drivers/plugins/proxy-mirror/handler.go @@ -0,0 +1,52 @@ +package proxy_mirror + +import ( + "github.com/eolinker/eosc/eocontext" + dubbo2_context "github.com/eolinker/eosc/eocontext/dubbo2-context" + grpc_context "github.com/eolinker/eosc/eocontext/grpc-context" + http_service "github.com/eolinker/eosc/eocontext/http-context" + log "github.com/eolinker/goku-api-gateway/goku-log" +) + +type proxyMirrorCompleteHandler struct { + orgComplete eocontext.CompleteHandler + mirrorComplete eocontext.CompleteHandler +} + +func newMirrorHandler(eoCtx eocontext.EoContext, proxyCfg *Config) (eocontext.CompleteHandler, error) { + handler := &proxyMirrorCompleteHandler{ + orgComplete: eoCtx.GetComplete(), + } + + if _, success := eoCtx.(http_service.IHttpContext); success { + handler.mirrorComplete = newHttpMirrorComplete(proxyCfg) + } else if _, success = eoCtx.(grpc_context.IGrpcContext); success { + handler.mirrorComplete = newGrpcMirrorComplete(proxyCfg) + } else if _, success = eoCtx.(dubbo2_context.IDubbo2Context); success { + handler.mirrorComplete = newDubbo2MirrorComplete(proxyCfg) + } else if _, success = eoCtx.(http_service.IWebsocketContext); success { + handler.mirrorComplete = newWebsocketMirrorComplete(proxyCfg) + } else { + return nil, ErrUnsupportedType + } + + return handler, nil +} + +func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error { + //执行镜像请求的Complete + cloneCtx, err := ctx.Clone() + if err != nil { + return err + } + + go func() { + err = p.mirrorComplete.Complete(cloneCtx) + if err != nil { + log.Error(err) + } + }() + + //执行原始Complete + return p.orgComplete.Complete(ctx) +} diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go index 67ba4cc7..b14ea488 100644 --- a/drivers/plugins/proxy-mirror/proxy-mirror.go +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -11,7 +11,6 @@ import ( ) var _ eocontext.IFilter = (*proxyMirror)(nil) -var _ http_service.HttpFilter = (*proxyMirror)(nil) type proxyMirror struct { drivers.WorkerBase @@ -19,10 +18,6 @@ type proxyMirror struct { } func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) error { - return http_service.DoHttpFilter(p, ctx, next) -} - -func (p *proxyMirror) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) error { if next != nil { err := next.DoChain(ctx) if err != nil { @@ -34,19 +29,26 @@ func (p *proxyMirror) DoHttpFilter(ctx http_service.IHttpContext, next eocontext rand.Seed(time.Now().UnixNano()) randomNum := rand.Intn(p.proxyConf.SampleConf.RandomRange + 1) //[0,range]范围内整型 if randomNum <= p.proxyConf.SampleConf.RandomPivot { //若随机数在[0,pivot]范围内则进行转发 - //进行转发 - go sendMirrorProxy(p.proxyConf, ctx) + setMirrorProxy(p.proxyConf, ctx) } } return nil } -func sendMirrorProxy(proxyCfg *Config, ctx http_service.IHttpContext) { - //先判断当前Ctx是否能Copy,若可以就进行copy并且设置新的APP - - //send - +func setMirrorProxy(proxyCfg *Config, ctx eocontext.EoContext) { + //先判断当前Ctx是否能Copy + if !ctx.IsCloneable() { + log.Info(ErrUnsupportedType) + return + } + //给ctx设置新的FinishHandler + newCompleteHandler, err := newMirrorHandler(ctx, proxyCfg) + if err != nil { + log.Info(err) + return + } + ctx.SetCompleteHandler(newCompleteHandler) } func (p *proxyMirror) Start() error { diff --git a/node/http-context/context.go b/node/http-context/context.go index 8ecd1d0f..a548c3c9 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -196,8 +196,12 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { cloneResp := fasthttp.AcquireResponse() ctx.response.Response.CopyTo(cloneResp) + //cloneRequestCtx := new(fasthttp.RequestCtx) + //cloneRequestCtx.Request = *cloneReq + //cloneRequestCtx.Response = *cloneResp cloneCtx.fastHttpRequestCtx = ctx.fastHttpRequestCtx //TODO - cloneCtx.requestID = ctx.requestID //TODO + + cloneCtx.requestID = ctx.requestID //TODO cloneCtx.requestReader.reset(cloneReq, remoteAddr) cloneCtx.proxyRequest.reset(cloneReq, remoteAddr) cloneCtx.proxyRequests = cloneCtx.proxyRequests[:0] From e9888ae53368920a330084febb29a0c0239fdc23 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Wed, 1 Mar 2023 19:57:42 +0800 Subject: [PATCH 13/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/complete.go | 4 ++-- drivers/plugins/proxy-mirror/handler.go | 13 +++++++------ node/http-context/context.go | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/plugins/proxy-mirror/complete.go b/drivers/plugins/proxy-mirror/complete.go index 39c8b469..579b0e26 100644 --- a/drivers/plugins/proxy-mirror/complete.go +++ b/drivers/plugins/proxy-mirror/complete.go @@ -49,9 +49,9 @@ func (h *httpComplete) Complete(org eocontext.EoContext) error { path = fmt.Sprintf("%s%s", h.proxyCfg.Path, path) } } - addr := fmt.Sprintf("%s%s", h.proxyCfg.Host, path) + ctx.Proxy().URI().SetPath(path) - lastErr = ctx.SendTo(addr, timeOut) + lastErr = ctx.SendTo(h.proxyCfg.Host, timeOut) if lastErr == nil { return nil } diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go index 3dd27016..4b344bb7 100644 --- a/drivers/plugins/proxy-mirror/handler.go +++ b/drivers/plugins/proxy-mirror/handler.go @@ -34,19 +34,20 @@ func newMirrorHandler(eoCtx eocontext.EoContext, proxyCfg *Config) (eocontext.Co } func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error { - //执行镜像请求的Complete cloneCtx, err := ctx.Clone() if err != nil { return err } + //先执行原始Complete, 再执行镜像请求的Complete + err = p.orgComplete.Complete(ctx) + go func() { - err = p.mirrorComplete.Complete(cloneCtx) - if err != nil { - log.Error(err) + mErr := p.mirrorComplete.Complete(cloneCtx) + if mErr != nil { + log.Error(mErr) } }() - //执行原始Complete - return p.orgComplete.Complete(ctx) + return err } diff --git a/node/http-context/context.go b/node/http-context/context.go index a548c3c9..c4c77f90 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -153,6 +153,7 @@ func (ctx *HttpContext) SendTo(address string, timeout time.Duration) error { func (ctx *HttpContext) Context() context.Context { if ctx.ctx == nil { + ctx.ctx = context.Background() } return ctx.ctx From 44678168a44f02f27d79c30c4a8b4aca2f4a883c Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 15:31:42 +0800 Subject: [PATCH 14/25] =?UTF-8?q?httpCtx=20=E5=89=AF=E6=9C=AC=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- node/http-context/context.go | 45 +-- node/http-context/http-context-copy/body.go | 293 ++++++++++++++++++ .../http-context/http-context-copy/context.go | 266 ++++++++++++++++ node/http-context/http-context-copy/error.go | 10 + node/http-context/http-context-copy/header.go | 140 +++++++++ node/http-context/http-context-copy/pool.go | 19 ++ .../http-context-copy/proxy-agent.go | 89 ++++++ node/http-context/http-context-copy/proxy.go | 72 +++++ .../http-context-copy/request-reader.go | 104 +++++++ .../http-context-copy/response.go | 121 ++++++++ node/http-context/http-context-copy/uri.go | 82 +++++ 11 files changed, 1207 insertions(+), 34 deletions(-) create mode 100644 node/http-context/http-context-copy/body.go create mode 100644 node/http-context/http-context-copy/context.go create mode 100644 node/http-context/http-context-copy/error.go create mode 100644 node/http-context/http-context-copy/header.go create mode 100644 node/http-context/http-context-copy/pool.go create mode 100644 node/http-context/http-context-copy/proxy-agent.go create mode 100644 node/http-context/http-context-copy/proxy.go create mode 100644 node/http-context/http-context-copy/request-reader.go create mode 100644 node/http-context/http-context-copy/response.go create mode 100644 node/http-context/http-context-copy/uri.go diff --git a/node/http-context/context.go b/node/http-context/context.go index c4c77f90..2d23223d 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -3,6 +3,7 @@ package http_context import ( "context" "fmt" + http_context_copy "github.com/eolinker/apinto/node/http-context/http-context-copy" "net" "strings" "time" @@ -189,41 +190,17 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) } - cloneCtx := pool.Get().(*HttpContext) - remoteAddr := ctx.fastHttpRequestCtx.RemoteAddr().String() - - cloneReq := fasthttp.AcquireRequest() - ctx.proxyRequest.Request().CopyTo(cloneReq) - cloneResp := fasthttp.AcquireResponse() - ctx.response.Response.CopyTo(cloneResp) - - //cloneRequestCtx := new(fasthttp.RequestCtx) - //cloneRequestCtx.Request = *cloneReq - //cloneRequestCtx.Response = *cloneResp - cloneCtx.fastHttpRequestCtx = ctx.fastHttpRequestCtx //TODO - - cloneCtx.requestID = ctx.requestID //TODO - cloneCtx.requestReader.reset(cloneReq, remoteAddr) - cloneCtx.proxyRequest.reset(cloneReq, remoteAddr) - cloneCtx.proxyRequests = cloneCtx.proxyRequests[:0] - cloneCtx.response.reset(cloneResp) - - cloneCtx.port = ctx.port - cloneCtx.ctx = ctx.ctx - - cloneCtx.completeHandler = ctx.completeHandler - cloneCtx.finishHandler = ctx.finishHandler - cloneCtx.upstreamHostHandler = ctx.upstreamHostHandler - cloneCtx.app = ctx.app - cloneCtx.balance = ctx.balance - - cLabels := make(map[string]string, len(ctx.labels)) - for k, v := range ctx.labels { - cLabels[k] = v - } - cloneCtx.labels = cLabels + ctxCopy := http_context_copy.NewContextCopy(ctx.fastHttpRequestCtx, ctx.requestID, ctx.port, ctx.labels) + + ctxCopy.SetCompleteHandler(ctx.completeHandler) + ctxCopy.SetFinish(ctx.finishHandler) + ctxCopy.SetUpstreamHostHandler(ctx.upstreamHostHandler) + ctxCopy.SetApp(ctx.app) + ctxCopy.SetBalance(ctx.balance) + + //Ctx set retry,timeout TODO - return cloneCtx, nil + return ctxCopy, nil } // NewContext 创建Context diff --git a/node/http-context/http-context-copy/body.go b/node/http-context/http-context-copy/body.go new file mode 100644 index 00000000..87a24f6b --- /dev/null +++ b/node/http-context/http-context-copy/body.go @@ -0,0 +1,293 @@ +package http_context_copy + +import ( + "bytes" + "strings" + + http_context "github.com/eolinker/eosc/eocontext/http-context" + "github.com/valyala/fasthttp" + + "io/ioutil" + + "mime" + "mime/multipart" + "net/url" +) + +const defaultMultipartMemory = 32 << 20 // 32 MB +var ( + _ http_context.IBodyDataWriter = (*BodyRequestHandler)(nil) +) + +const ( + MultipartForm = "multipart/form-data" + FormData = "application/x-www-form-urlencoded" + TEXT = "text/plain" + JSON = "application/json" + JavaScript = "application/javascript" + AppLicationXML = "application/xml" + TextXML = "text/xml" + Html = "text/html" +) + +// BodyRequestHandler body请求处理器 +type BodyRequestHandler struct { + request *fasthttp.Request + + formdata *multipart.Form +} + +func (b *BodyRequestHandler) MultipartForm() (*multipart.Form, error) { + if b.formdata != nil { + return b.formdata, nil + } + if !strings.Contains(b.ContentType(), MultipartForm) { + return nil, ErrorNotMultipart + } + form, err := b.request.MultipartForm() + if err != nil { + return nil, err + } + + b.formdata = &multipart.Form{ + Value: form.Value, + File: form.File, + } + b.resetFile() + return form, nil +} +func (b *BodyRequestHandler) Files() (map[string][]*multipart.FileHeader, error) { + form, err := b.MultipartForm() + if err != nil { + return nil, err + } + return form.File, nil +} +func (b *BodyRequestHandler) reset(request *fasthttp.Request) { + b.request = request + b.formdata = nil +} +func NewBodyRequestHandler(request *fasthttp.Request) *BodyRequestHandler { + return &BodyRequestHandler{request: request} +} + +// GetForm 获取表单参数 +func (b *BodyRequestHandler) GetForm(key string) string { + contentType, _, _ := mime.ParseMediaType(b.ContentType()) + + switch contentType { + case FormData: + args := b.request.PostArgs() + if args == nil { + return "" + } + return string(args.Peek(key)) + case MultipartForm: + form, err := b.MultipartForm() + if err != nil { + return "" + } + vs := form.Value[key] + if len(vs) > 0 { + return vs[0] + } + return "" + + } + return "" +} + +// ContentType 获取contentType +func (b *BodyRequestHandler) ContentType() string { + return string(b.request.Header.ContentType()) +} + +// BodyForm 获取表单参数 +func (b *BodyRequestHandler) BodyForm() (url.Values, error) { + + contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) + switch contentType { + case FormData: + return url.ParseQuery(string(b.request.Body())) + case MultipartForm: + multipartForm, err := b.MultipartForm() + if err != nil { + return nil, err + } + return multipartForm.Value, nil + default: + return nil, ErrorNotForm + } + +} + +// RawBody 获取raw数据 +func (b *BodyRequestHandler) RawBody() ([]byte, error) { + return b.request.Body(), nil +} + +func (b *BodyRequestHandler) GetFile(key string) ([]*multipart.FileHeader, bool) { + multipartForm, err := b.MultipartForm() + if err != nil { + return nil, false + } + fl, has := multipartForm.File[key] + + return fl, has + +} + +func (b *BodyRequestHandler) SetToForm(key, value string) error { + contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) + switch contentType { + case FormData: + b.request.PostArgs().Set(key, value) + b.request.SetBodyRaw(b.request.PostArgs().QueryString()) + return nil + case MultipartForm: + multipartForm, err := b.MultipartForm() + if err != nil { + return err + } + multipartForm.Value[key] = []string{value} + return b.resetFile() + default: + return ErrorNotForm + } +} + +// AddForm 新增表单参数 +func (b *BodyRequestHandler) AddForm(key, value string) error { + + contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) + switch contentType { + case FormData: + b.request.PostArgs().Add(key, value) + b.request.SetBody(b.request.PostArgs().QueryString()) + return nil + case MultipartForm: + multipartForm, err := b.MultipartForm() + if err != nil { + return err + } + multipartForm.Value[key] = append(multipartForm.Value[key], value) + return b.resetFile() + default: + return ErrorNotForm + } +} + +// AddFile 新增文件参数 +func (b *BodyRequestHandler) AddFile(key string, file *multipart.FileHeader) error { + + contentType, _, _ := mime.ParseMediaType(b.ContentType()) + if contentType != FormData && contentType != MultipartForm { + return ErrorNotMultipart + } + multipartForm, err := b.MultipartForm() + if err != nil { + return err + } + multipartForm.File[key] = append(multipartForm.File[key], file) + + return b.resetFile() +} + +// SetFile 设置文件参数 +func (b *BodyRequestHandler) SetFile(files map[string][]*multipart.FileHeader) error { + + multipartForm, err := b.MultipartForm() + if err != nil { + return err + } + multipartForm.File = files + + return b.resetFile() +} + +func (b *BodyRequestHandler) resetFile() error { + multipartForm := b.formdata + if multipartForm == nil { + return nil + } + + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + + for name, fs := range multipartForm.File { + for _, f := range fs { + fio, err := f.Open() + if err != nil { + return err + } + + part, err := writer.CreateFormFile(name, f.Filename) + if err != nil { + fio.Close() + return err + } + + data, err := ioutil.ReadAll(fio) + if err != nil { + fio.Close() + return err + } + _, err = part.Write(data) + if err != nil { + fio.Close() + return err + } + fio.Close() + } + } + + for key, values := range multipartForm.Value { + //temp := make(url.Values) + //temp[key] = values + //value := temp.Encode() + for _, value := range values { + err := writer.WriteField(key, value) + if err != nil { + return err + } + } + + } + err := writer.Close() + if err != nil { + return err + } + b.request.Header.SetContentType(writer.FormDataContentType()) + b.request.SetBodyRaw(body.Bytes()) + return nil +} + +// SetForm 设置表单参数 +func (b *BodyRequestHandler) SetForm(values url.Values) error { + + contentType, _, _ := mime.ParseMediaType(b.ContentType()) + if contentType != FormData && contentType != MultipartForm { + return ErrorNotForm + } + switch contentType { + case FormData: + b.request.SetBodyString(values.Encode()) + case MultipartForm: + multipartForm, err := b.MultipartForm() + if err != nil { + return err + } + multipartForm.Value = values + return b.resetFile() + } + + return ErrorNotForm +} + +// SetRaw 设置raw数据 +func (b *BodyRequestHandler) SetRaw(contentType string, body []byte) { + b.request.SetBodyRaw(body) + b.request.Header.SetContentType(contentType) + return + +} diff --git a/node/http-context/http-context-copy/context.go b/node/http-context/http-context-copy/context.go new file mode 100644 index 00000000..dc0d7dd7 --- /dev/null +++ b/node/http-context/http-context-copy/context.go @@ -0,0 +1,266 @@ +package http_context_copy + +import ( + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/eolinker/eosc/utils/config" + + fasthttp_client "github.com/eolinker/apinto/node/fasthttp-client" + + eoscContext "github.com/eolinker/eosc/eocontext" + http_service "github.com/eolinker/eosc/eocontext/http-context" + "github.com/valyala/fasthttp" +) + +var _ http_service.IHttpContext = (*HttpContextCopy)(nil) + +// HttpContextCopy fasthttpRequestCtx +type HttpContextCopy struct { + proxyRequest ProxyRequest + proxyRequests []http_service.IProxy + requestID string + response Response + requestReader RequestReader + ctx context.Context + completeHandler eoscContext.CompleteHandler + finishHandler eoscContext.FinishHandler + app eoscContext.EoApp + balance eoscContext.BalanceHandler + upstreamHostHandler eoscContext.UpstreamHostHandler + labels map[string]string + port int + + localIP net.IP + netAddr net.Addr + acceptTime time.Time +} + +func (ctx *HttpContextCopy) GetUpstreamHostHandler() eoscContext.UpstreamHostHandler { + return ctx.upstreamHostHandler +} + +func (ctx *HttpContextCopy) SetUpstreamHostHandler(handler eoscContext.UpstreamHostHandler) { + ctx.upstreamHostHandler = handler +} + +func (ctx *HttpContextCopy) LocalIP() net.IP { + return ctx.localIP +} + +func (ctx *HttpContextCopy) LocalAddr() net.Addr { + return ctx.netAddr +} + +func (ctx *HttpContextCopy) LocalPort() int { + return ctx.port +} + +func (ctx *HttpContextCopy) GetApp() eoscContext.EoApp { + return ctx.app +} + +func (ctx *HttpContextCopy) SetApp(app eoscContext.EoApp) { + ctx.app = app +} + +func (ctx *HttpContextCopy) GetBalance() eoscContext.BalanceHandler { + return ctx.balance +} + +func (ctx *HttpContextCopy) SetBalance(handler eoscContext.BalanceHandler) { + ctx.balance = handler +} + +func (ctx *HttpContextCopy) SetLabel(name, value string) { + ctx.labels[name] = value +} + +func (ctx *HttpContextCopy) GetLabel(name string) string { + return ctx.labels[name] +} + +func (ctx *HttpContextCopy) Labels() map[string]string { + return ctx.labels +} + +func (ctx *HttpContextCopy) GetComplete() eoscContext.CompleteHandler { + return ctx.completeHandler +} + +func (ctx *HttpContextCopy) SetCompleteHandler(handler eoscContext.CompleteHandler) { + ctx.completeHandler = handler +} + +func (ctx *HttpContextCopy) GetFinish() eoscContext.FinishHandler { + return ctx.finishHandler +} + +func (ctx *HttpContextCopy) SetFinish(handler eoscContext.FinishHandler) { + ctx.finishHandler = handler +} + +func (ctx *HttpContextCopy) Scheme() string { + return string(ctx.requestReader.req.URI().Scheme()) +} + +func (ctx *HttpContextCopy) Assert(i interface{}) error { + if v, ok := i.(*http_service.IHttpContext); ok { + *v = ctx + return nil + } + return fmt.Errorf("not suport:%s", config.TypeNameOf(i)) +} + +func (ctx *HttpContextCopy) Proxies() []http_service.IProxy { + return ctx.proxyRequests +} + +func (ctx *HttpContextCopy) Response() http_service.IResponse { + return &ctx.response +} + +func (ctx *HttpContextCopy) SendTo(address string, timeout time.Duration) error { + + scheme, host := readAddress(address) + request := ctx.proxyRequest.Request() + + passHost, targetHost := ctx.GetUpstreamHostHandler().PassHost() + switch passHost { + case eoscContext.PassHost: + case eoscContext.NodeHost: + request.URI().SetHost(host) + case eoscContext.ReWriteHost: + request.URI().SetHost(targetHost) + } + + beginTime := time.Now() + ctx.response.responseError = fasthttp_client.ProxyTimeout(address, request, ctx.response.Response, timeout) + agent := newRequestAgent(&ctx.proxyRequest, host, scheme, beginTime, time.Now()) + if ctx.response.responseError != nil { + agent.setStatusCode(504) + } else { + agent.setStatusCode(ctx.response.Response.StatusCode()) + } + + agent.setResponseLength(ctx.response.Response.Header.ContentLength()) + + ctx.proxyRequests = append(ctx.proxyRequests, agent) + return ctx.response.responseError + +} + +func (ctx *HttpContextCopy) Context() context.Context { + if ctx.ctx == nil { + + ctx.ctx = context.Background() + } + return ctx.ctx +} + +func (ctx *HttpContextCopy) AcceptTime() time.Time { + return ctx.acceptTime +} + +func (ctx *HttpContextCopy) Value(key interface{}) interface{} { + return ctx.Context().Value(key) +} + +func (ctx *HttpContextCopy) WithValue(key, val interface{}) { + ctx.ctx = context.WithValue(ctx.Context(), key, val) +} + +func (ctx *HttpContextCopy) Proxy() http_service.IRequest { + return &ctx.proxyRequest +} + +func (ctx *HttpContextCopy) Request() http_service.IRequestReader { + + return &ctx.requestReader +} + +func (ctx *HttpContextCopy) IsCloneable() bool { + return false +} + +func (ctx *HttpContextCopy) Clone() (eoscContext.EoContext, error) { + return nil, fmt.Errorf("%s %w", "HttpContextCopy", eoscContext.ErrEoCtxUnCloneable) +} + +// NewContextCopy 创建Context-Copy +func NewContextCopy(requestCtx *fasthttp.RequestCtx, requestID string, port int, labels map[string]string) *HttpContextCopy { + ctxCopy := pool.Get().(*HttpContextCopy) + remoteAddr := requestCtx.RemoteAddr().String() + + cloneReq := fasthttp.AcquireRequest() + requestCtx.Request.CopyTo(cloneReq) + cloneResp := fasthttp.AcquireResponse() + requestCtx.Response.CopyTo(cloneResp) + + ctxCopy.requestReader.reset(cloneReq, remoteAddr) + ctxCopy.proxyRequest.reset(cloneReq, remoteAddr) + ctxCopy.proxyRequests = ctxCopy.proxyRequests[:0] + ctxCopy.response.reset(cloneResp) + + ctxCopy.localIP = requestCtx.LocalIP() + ctxCopy.netAddr = requestCtx.LocalAddr() + ctxCopy.acceptTime = requestCtx.Time() + + ctxCopy.requestID = requestID + ctxCopy.port = port + + ctxCopy.ctx = context.Background() + ctxCopy.WithValue("request_time", ctxCopy.acceptTime) + + cLabels := make(map[string]string, len(labels)) + for k, v := range labels { + cLabels[k] = v + } + ctxCopy.labels = cLabels + + return ctxCopy + +} + +// RequestId 请求ID +func (ctx *HttpContextCopy) RequestId() string { + return ctx.requestID +} + +// FastFinish finish +func (ctx *HttpContextCopy) FastFinish() { + if ctx.response.responseError != nil { + ctx.response.Response.SetStatusCode(504) + ctx.response.Response.SetBodyString(ctx.response.responseError.Error()) + return + } + + ctx.port = 0 + ctx.ctx = nil + ctx.app = nil + ctx.balance = nil + ctx.upstreamHostHandler = nil + ctx.finishHandler = nil + ctx.completeHandler = nil + + ctx.requestReader.Finish() + ctx.proxyRequest.Finish() + ctx.response.Finish() + pool.Put(ctx) + return +} + +func NotFound(ctx *HttpContextCopy) { + ctx.response.Response.SetStatusCode(404) + ctx.response.Response.SetBody([]byte("404 Not Found")) +} + +func readAddress(addr string) (scheme, host string) { + if i := strings.Index(addr, "://"); i > 0 { + return strings.ToLower(addr[:i]), addr[i+3:] + } + return "http", addr +} diff --git a/node/http-context/http-context-copy/error.go b/node/http-context/http-context-copy/error.go new file mode 100644 index 00000000..4b1b9264 --- /dev/null +++ b/node/http-context/http-context-copy/error.go @@ -0,0 +1,10 @@ +package http_context_copy + +import "errors" + +var ( + ErrorNotForm = errors.New("contentType is not Form") + ErrorNotMultipart = errors.New("contentType is not Multipart") + ErrorNotAllowRaw = errors.New("contentType is not allow Raw") + ErrorNotSend = errors.New("not send") +) diff --git a/node/http-context/http-context-copy/header.go b/node/http-context/http-context-copy/header.go new file mode 100644 index 00000000..c24159de --- /dev/null +++ b/node/http-context/http-context-copy/header.go @@ -0,0 +1,140 @@ +package http_context_copy + +import ( + "bytes" + "net/http" + "strings" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/valyala/fasthttp" +) + +var _ http_service.IHeaderWriter = (*RequestHeader)(nil) + +type RequestHeader struct { + header *fasthttp.RequestHeader + tmp http.Header +} + +func (h *RequestHeader) RawHeader() string { + return h.header.String() +} + +func (h *RequestHeader) reset(header *fasthttp.RequestHeader) { + h.header = header + h.tmp = nil +} + +func (h *RequestHeader) initHeader() { + if h.tmp == nil { + h.tmp = make(http.Header) + h.header.VisitAll(func(key, value []byte) { + bytes.SplitN(value, []byte(":"), 2) + h.tmp[string(key)] = []string{string(value)} + }) + } +} + +func (h *RequestHeader) Host() string { + return string(h.header.Host()) +} + +func (h *RequestHeader) GetHeader(name string) string { + return h.Headers().Get(name) +} + +func (h *RequestHeader) Headers() http.Header { + h.initHeader() + return h.tmp +} + +func (h *RequestHeader) SetHeader(key, value string) { + if h.tmp != nil { + h.tmp.Set(key, value) + } + h.header.Set(key, value) +} + +func (h *RequestHeader) AddHeader(key, value string) { + if h.tmp != nil { + h.tmp.Add(key, value) + } + h.header.Add(key, value) +} + +func (h *RequestHeader) DelHeader(key string) { + if h.tmp != nil { + h.tmp.Del(key) + } + h.header.Del(key) +} + +func (h *RequestHeader) SetHost(host string) { + if h.tmp != nil { + h.tmp.Set("Host", host) + } + h.header.SetHost(host) +} + +type ResponseHeader struct { + header *fasthttp.ResponseHeader + tmp http.Header +} + +func (r *ResponseHeader) reset(header *fasthttp.ResponseHeader) { + r.header = header + r.tmp = nil +} +func NewResponseHeader(header *fasthttp.ResponseHeader) *ResponseHeader { + return &ResponseHeader{header: header} +} + +func (r *ResponseHeader) GetHeader(name string) string { + return r.Headers().Get(name) +} + +func (r *ResponseHeader) Headers() http.Header { + + if r.tmp == nil { + r.tmp = make(http.Header) + hs := strings.Split(r.header.String(), "\r\n") + for _, t := range hs { + vs := strings.Split(t, ":") + if len(vs) < 2 { + if vs[0] == "" { + continue + } + r.tmp[vs[0]] = []string{""} + continue + } + r.tmp[vs[0]] = []string{strings.TrimSpace(vs[1])} + } + } + return r.tmp +} + +func (r *ResponseHeader) SetHeader(key, value string) { + if r.tmp != nil { + r.tmp.Set(key, value) + } + r.header.Set(key, value) +} + +func (r *ResponseHeader) AddHeader(key, value string) { + if r.tmp != nil { + r.tmp.Add(key, value) + } + r.header.Add(key, value) +} + +func (r *ResponseHeader) DelHeader(key string) { + if r.tmp != nil { + r.tmp.Del(key) + } + r.header.Del(key) +} + +func (h *RequestHeader) GetCookie(key string) string { + return string(h.header.Cookie(key)) +} diff --git a/node/http-context/http-context-copy/pool.go b/node/http-context/http-context-copy/pool.go new file mode 100644 index 00000000..d4600acc --- /dev/null +++ b/node/http-context/http-context-copy/pool.go @@ -0,0 +1,19 @@ +package http_context_copy + +import ( + "sync" + + http_service "github.com/eolinker/eosc/eocontext/http-context" +) + +var ( + pool = sync.Pool{ + New: newContext, + } +) + +func newContext() interface{} { + h := new(HttpContextCopy) + h.proxyRequests = make([]http_service.IProxy, 0, 5) + return h +} diff --git a/node/http-context/http-context-copy/proxy-agent.go b/node/http-context/http-context-copy/proxy-agent.go new file mode 100644 index 00000000..09343f1e --- /dev/null +++ b/node/http-context/http-context-copy/proxy-agent.go @@ -0,0 +1,89 @@ +package http_context_copy + +import ( + "strconv" + "time" + + http_service "github.com/eolinker/eosc/eocontext/http-context" +) + +var _ http_service.IProxy = (*requestAgent)(nil) + +type requestAgent struct { + http_service.IRequest + host string + scheme string + statusCode int + status string + responseLength int + beginTime time.Time + endTime time.Time + hostAgent *UrlAgent +} + +func (a *requestAgent) ProxyTime() time.Time { + return a.beginTime +} + +func (a *requestAgent) StatusCode() int { + return a.statusCode +} + +func (a *requestAgent) Status() string { + return a.status +} + +func (a *requestAgent) setStatusCode(code int) { + a.statusCode = code + a.status = strconv.Itoa(code) +} + +func (a *requestAgent) ResponseLength() int { + return a.responseLength +} + +func (a *requestAgent) setResponseLength(length int) { + if length > 0 { + a.responseLength = length + } +} + +func newRequestAgent(IRequest http_service.IRequest, host string, scheme string, beginTime, endTime time.Time) *requestAgent { + return &requestAgent{IRequest: IRequest, host: host, scheme: scheme, beginTime: beginTime, endTime: endTime} +} + +func (a *requestAgent) ResponseTime() int64 { + return a.endTime.Sub(a.beginTime).Milliseconds() +} + +func (a *requestAgent) URI() http_service.IURIWriter { + if a.hostAgent == nil { + a.hostAgent = NewUrlAgent(a.IRequest.URI(), a.host, a.scheme) + } + return a.hostAgent +} + +type UrlAgent struct { + http_service.IURIWriter + host string + scheme string +} + +func (u *UrlAgent) SetScheme(scheme string) { + u.scheme = scheme +} +func (u *UrlAgent) Scheme() string { + return u.scheme +} + +func (u *UrlAgent) Host() string { + return u.host +} + +func (u *UrlAgent) SetHost(host string) { + u.host = host +} + +func NewUrlAgent(IURIWriter http_service.IURIWriter, host string, scheme string) *UrlAgent { + return &UrlAgent{IURIWriter: IURIWriter, host: host, scheme: scheme} +} diff --git a/node/http-context/http-context-copy/proxy.go b/node/http-context/http-context-copy/proxy.go new file mode 100644 index 00000000..871becbb --- /dev/null +++ b/node/http-context/http-context-copy/proxy.go @@ -0,0 +1,72 @@ +package http_context_copy + +import ( + "bytes" + "fmt" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + "github.com/valyala/fasthttp" +) + +var _ http_service.IRequest = (*ProxyRequest)(nil) + +type ProxyRequest struct { + RequestReader +} + +//func (r *ProxyRequest) clone() *ProxyRequest { +// return NewProxyRequest(r.Request(), r.remoteAddr) +//} + +func (r *ProxyRequest) Finish() error { + fasthttp.ReleaseRequest(r.req) + r.RequestReader.Finish() + return nil +} +func (r *ProxyRequest) Header() http_service.IHeaderWriter { + return &r.headers +} + +func (r *ProxyRequest) Body() http_service.IBodyDataWriter { + return &r.body +} + +func (r *ProxyRequest) URI() http_service.IURIWriter { + return &r.uri +} + +var ( + xforwardedforKey = []byte("x-forwarded-for") +) + +func (r *ProxyRequest) reset(request *fasthttp.Request, remoteAddr string) { + proxyRequest := fasthttp.AcquireRequest() + request.CopyTo(proxyRequest) + + forwardedFor := proxyRequest.Header.PeekBytes(xforwardedforKey) + if len(forwardedFor) > 0 { + if i := bytes.IndexByte(forwardedFor, ','); i > 0 { + r.realIP = string(forwardedFor[:i]) + } else { + r.realIP = string(forwardedFor) + } + proxyRequest.Header.Set("x-forwarded-for", fmt.Sprint(string(forwardedFor), ",", r.remoteAddr)) + } else { + proxyRequest.Header.Set("x-forwarded-for", r.remoteAddr) + r.realIP = r.remoteAddr + } + + r.RequestReader.reset(proxyRequest, remoteAddr) +} + +//func NewProxyRequest(request *fasthttp.Request, remoteAddr string) *ProxyRequest { +// proxyRequest := fasthttp.AcquireRequest() +// request.CopyTo(proxyRequest) +// return &ProxyRequest{ +// RequestReader: NewRequestReader(proxyRequest, remoteAddr), +// } +//} + +func (r *ProxyRequest) SetMethod(s string) { + r.Request().Header.SetMethod(s) +} diff --git a/node/http-context/http-context-copy/request-reader.go b/node/http-context/http-context-copy/request-reader.go new file mode 100644 index 00000000..ebc442f1 --- /dev/null +++ b/node/http-context/http-context-copy/request-reader.go @@ -0,0 +1,104 @@ +package http_context_copy + +import ( + "strings" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/valyala/fasthttp" +) + +var _ http_service.IRequestReader = (*RequestReader)(nil) + +type RequestReader struct { + body BodyRequestHandler + req *fasthttp.Request + headers RequestHeader + uri URIRequest + remoteAddr string + remotePort string + realIP string + length int +} + +func (r *RequestReader) ContentLength() int { + return r.length +} + +func (r *RequestReader) ContentType() string { + return string(r.req.Header.ContentType()) +} + +func (r *RequestReader) String() string { + return r.req.String() +} + +func (r *RequestReader) Method() string { + return string(r.req.Header.Method()) +} + +func (r *RequestReader) Header() http_service.IHeaderReader { + return &r.headers +} + +func (r *RequestReader) Body() http_service.IBodyDataReader { + return &r.body +} + +func (r *RequestReader) URI() http_service.IURIReader { + return &r.uri +} + +func (r *RequestReader) ReadIP() string { + if r.realIP == "" { + realIP := r.headers.GetHeader("x-real-ip") + if realIP == "" { + realIP = r.remoteAddr + } + r.realIP = realIP + } + return r.realIP +} + +func (r *RequestReader) ForwardIP() string { + return r.headers.GetHeader("x-forwarded-for") +} + +func (r *RequestReader) RemoteAddr() string { + return r.remoteAddr +} + +func (r *RequestReader) RemotePort() string { + return r.remotePort +} +func (r *RequestReader) Finish() error { + r.req = nil + r.body.reset(nil) + r.headers.reset(nil) + r.uri.reset(nil) + return nil +} +func (r *RequestReader) reset(req *fasthttp.Request, remoteAddr string) { + r.req = req + r.remoteAddr = remoteAddr + + r.body.reset(req) + + r.headers.reset(&req.Header) + r.uri.uri = req.URI() + + idx := strings.LastIndex(remoteAddr, ":") + if idx != -1 { + r.remoteAddr = remoteAddr[:idx] + r.remotePort = remoteAddr[idx+1:] + } + length := r.req.Header.ContentLength() + if length > 0 { + r.length = length + } + +} + +func (r *RequestReader) Request() *fasthttp.Request { + return r.req +} diff --git a/node/http-context/http-context-copy/response.go b/node/http-context/http-context-copy/response.go new file mode 100644 index 00000000..124c517f --- /dev/null +++ b/node/http-context/http-context-copy/response.go @@ -0,0 +1,121 @@ +package http_context_copy + +import ( + "strconv" + "strings" + "time" + + http_service "github.com/eolinker/eosc/eocontext/http-context" + + "github.com/valyala/fasthttp" +) + +var _ http_service.IResponse = (*Response)(nil) + +type Response struct { + ResponseHeader + *fasthttp.Response + length int + responseTime time.Duration + proxyStatusCode int + responseError error +} + +func (r *Response) ContentLength() int { + if r.length == 0 { + return r.Response.Header.ContentLength() + } + return r.length +} + +func (r *Response) ContentType() string { + return string(r.Response.Header.ContentType()) +} + +func (r *Response) HeadersString() string { + return r.header.String() +} + +func (r *Response) ResponseError() error { + return r.responseError +} + +func (r *Response) ClearError() { + r.responseError = nil +} +func (r *Response) Finish() error { + r.Response = nil + r.ResponseHeader.reset(nil) + r.responseError = nil + r.proxyStatusCode = 0 + return nil +} +func (r *Response) reset(resp *fasthttp.Response) { + r.Response = resp + r.ResponseHeader.reset(&resp.Header) + r.responseError = nil + r.proxyStatusCode = 0 + +} + +func (r *Response) BodyLen() int { + return r.header.ContentLength() +} + +func (r *Response) GetBody() []byte { + if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") { + body, _ := r.BodyGunzip() + r.DelHeader("Content-Encoding") + r.SetHeader("Content-Length", strconv.Itoa(len(body))) + r.Response.SetBody(body) + } + + return r.Response.Body() +} + +func (r *Response) SetBody(bytes []byte) { + if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") { + r.DelHeader("Content-Encoding") + } + r.Response.SetBody(bytes) + r.length = len(bytes) + r.SetHeader("Content-Length", strconv.Itoa(r.length)) + r.responseError = nil +} + +func (r *Response) StatusCode() int { + if r.responseError != nil { + return 504 + } + return r.Response.StatusCode() +} + +func (r *Response) Status() string { + return strconv.Itoa(r.StatusCode()) +} + +func (r *Response) SetStatus(code int, status string) { + r.Response.SetStatusCode(code) + r.responseError = nil +} + +// 原始的响应状态码 +func (r *Response) ProxyStatusCode() int { + return r.proxyStatusCode +} + +func (r *Response) ProxyStatus() string { + return strconv.Itoa(r.proxyStatusCode) +} + +func (r *Response) SetProxyStatus(code int, status string) { + r.proxyStatusCode = code +} + +func (r *Response) SetResponseTime(t time.Duration) { + r.responseTime = t +} + +func (r *Response) ResponseTime() time.Duration { + return r.responseTime +} diff --git a/node/http-context/http-context-copy/uri.go b/node/http-context/http-context-copy/uri.go new file mode 100644 index 00000000..7ab1a150 --- /dev/null +++ b/node/http-context/http-context-copy/uri.go @@ -0,0 +1,82 @@ +package http_context_copy + +import ( + http_service "github.com/eolinker/eosc/eocontext/http-context" + "github.com/valyala/fasthttp" +) + +var _ http_service.IURIWriter = (*URIRequest)(nil) + +type URIRequest struct { + uri *fasthttp.URI +} + +func (ur *URIRequest) reset(uri *fasthttp.URI) { + ur.uri = uri +} +func (ur *URIRequest) Path() string { + return string(ur.uri.Path()) +} + +func (ur *URIRequest) SetScheme(scheme string) { + ur.uri.SetScheme(scheme) +} + +func (ur *URIRequest) Host() string { + return string(ur.uri.Host()) +} + +func (ur *URIRequest) SetQuery(key, value string) { + + ur.uri.QueryArgs().Set(key, value) +} + +func (ur *URIRequest) AddQuery(key, value string) { + ur.uri.QueryArgs().Add(key, value) +} + +func (ur *URIRequest) DelQuery(key string) { + queryArgs := ur.uri.QueryArgs() + queryArgs.Del(key) + if queryArgs.Len() == 0 { + ur.uri.SetQueryStringBytes(nil) + } +} + +func (ur *URIRequest) SetRawQuery(raw string) { + ur.uri.SetQueryString(raw) +} + +func NewURIRequest(uri *fasthttp.URI) *URIRequest { + return &URIRequest{uri: uri} +} + +func (ur *URIRequest) RequestURI() string { + return string(ur.uri.RequestURI()) +} + +func (ur *URIRequest) Scheme() string { + return string(ur.uri.Scheme()) +} + +func (ur *URIRequest) RawURL() string { + return string(ur.uri.FullURI()) +} + +func (ur *URIRequest) GetQuery(key string) string { + + return string(ur.uri.QueryArgs().Peek(key)) +} + +func (ur *URIRequest) RawQuery() string { + return string(ur.uri.QueryString()) +} + +func (ur *URIRequest) SetPath(s string) { + ur.uri.SetPath(s) + +} + +func (ur *URIRequest) SetHost(host string) { + ur.uri.SetHost(host) +} From bb7d82daca3bcce0d48585c7cb68d3963037b78c Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 16:35:52 +0800 Subject: [PATCH 15/25] =?UTF-8?q?retry=20=E5=92=8C=20timeout=E5=AD=98?= =?UTF-8?q?=E5=9C=A8httpHandler=EF=BC=8C=E5=B9=B6=E4=BD=BF=E7=94=A8ctx?= =?UTF-8?q?=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http-router/http-complete/complete.go | 28 +++++++++++++++---- drivers/router/http-router/http-handler.go | 8 +++++- drivers/router/http-router/router.go | 4 ++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/drivers/router/http-router/http-complete/complete.go b/drivers/router/http-router/http-complete/complete.go index d6a14df5..9a6f54f1 100644 --- a/drivers/router/http-router/http-complete/complete.go +++ b/drivers/router/http-router/http-complete/complete.go @@ -12,17 +12,20 @@ import ( "github.com/eolinker/eosc/log" ) +const ( + KeyHttpRetry = "http_retry" + KeyHttpTimeout = "http_timeout" +) + var ( ErrorTimeoutComplete = errors.New("complete timeout") ) type HttpComplete struct { - retry int - timeOut time.Duration } -func NewHttpComplete(retry int, timeOut time.Duration) *HttpComplete { - return &HttpComplete{retry: retry, timeOut: timeOut} +func NewHttpComplete() *HttpComplete { + return &HttpComplete{} } func (h *HttpComplete) Complete(org eocontext.EoContext) error { @@ -56,9 +59,22 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error { } timeOut := app.TimeOut() - for index := 0; index <= h.retry; index++ { - if h.timeOut > 0 && time.Now().Sub(proxyTime) > h.timeOut { + retryValue := ctx.Value(KeyHttpRetry) + retry, ok := retryValue.(int) + if !ok { + retry = 1 + } + + timeoutValue := ctx.Value(KeyHttpTimeout) + timeout, ok := timeoutValue.(time.Duration) + if !ok { + timeout = 3000 * time.Millisecond + } + + for index := 0; index <= retry; index++ { + + if timeout > 0 && time.Now().Sub(proxyTime) > timeout { return ErrorTimeoutComplete } node, err := balance.Select(ctx) diff --git a/drivers/router/http-router/http-handler.go b/drivers/router/http-router/http-handler.go index aa42d7c2..fa1fba5e 100644 --- a/drivers/router/http-router/http-handler.go +++ b/drivers/router/http-router/http-handler.go @@ -2,6 +2,7 @@ package http_router import ( "net/http" + "time" http_service "github.com/eolinker/apinto/node/http-context" @@ -26,10 +27,12 @@ type httpHandler struct { filters eocontext.IChainPro disable bool websocket bool + + retry int + timeout time.Duration } func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) { - httpContext, err := http_context.Assert(ctx) if err != nil { return @@ -50,6 +53,9 @@ func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) { } ctx = wsCtx } + //set retry timeout + ctx.WithValue(http_complete.KeyHttpRetry, h.retry) + ctx.WithValue(http_complete.KeyHttpTimeout, h.timeout) //Set Label ctx.SetLabel("api", h.routerName) diff --git a/drivers/router/http-router/router.go b/drivers/router/http-router/router.go index 021e5fcb..2618983d 100644 --- a/drivers/router/http-router/router.go +++ b/drivers/router/http-router/router.go @@ -60,6 +60,8 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker) finisher: defaultFinisher, disable: cfg.Disable, websocket: cfg.Websocket, + retry: cfg.Retry, + timeout: time.Duration(cfg.TimeOut) * time.Millisecond, } if !cfg.Disable { @@ -94,7 +96,7 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker) handler.completeHandler = websocket.NewComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond) methods = []string{http.MethodGet} } else { - handler.completeHandler = http_complete.NewHttpComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond) + handler.completeHandler = http_complete.NewHttpComplete() } } } From 9cb08b3ebcbd27e5f26f693f805fb6228da5bf5c Mon Sep 17 00:00:00 2001 From: zhangzeyi Date: Thu, 2 Mar 2023 17:26:06 +0800 Subject: [PATCH 16/25] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86json=20schema?= =?UTF-8?q?=20=E6=A0=BC=E5=BC=8F=E4=B8=AD=20array=E4=B8=8B=E5=A6=82?= =?UTF-8?q?=E6=9E=9C=E5=8F=AA=E6=9C=89=E4=B8=80=E4=B8=AA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E4=B8=BA?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E6=97=B6=20=20=E8=A7=A3=E6=9E=90=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/json.go | 19 ++++- utils/json_test.go | 176 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 3 deletions(-) diff --git a/utils/json.go b/utils/json.go index 79df7fd2..44ece525 100644 --- a/utils/json.go +++ b/utils/json.go @@ -173,8 +173,23 @@ func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} { randomNum = int(RandInt64(int64(minVal), int64(maxVal))) } - if randomNum == 1 { //随机选取一个 - resultMap[name] = templateList[rand.Intn(len(templateList))] + if randomNum == 1 { + if len(templateList) > 1 { + resultMap[name] = templateList[rand.Intn(len(templateList))] + continue + } + switch templateVal := templateList[0].(type) { + case map[string]interface{}: + tempMap := make(map[string]interface{}) + for key, val := range templateVal { + split := strings.Split(key, "|") + tempMap[split[0]] = mockConstant(val) + } + resultMap[name] = tempMap + default: + resultMap[name] = templateVal + } + continue } diff --git a/utils/json_test.go b/utils/json_test.go index 1366699a..2f2121b7 100644 --- a/utils/json_test.go +++ b/utils/json_test.go @@ -1299,13 +1299,187 @@ var mock5 = `{ ] }` +var mock6 = `{ + "template": { + "users|1": [ + { + "email": "@email", + "name": "@name", + "ip": "@ip" + } + ] + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "users", + "template": [ + { + "email": "@email", + "name": "@name", + "ip": "@ip" + } + ], + "type": "array", + "rule": { + "parameters": [ + "users|1", + "users", + null, + "1", + null + ], + "range": [ + "1", + "1", + null + ], + "min": 1, + "count": 1 + }, + "path": [ + "ROOT", + "users" + ], + "items": [ + { + "name": 0, + "template": { + "email": "@email", + "name": "@name", + "ip": "@ip" + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT", + "users", + 0 + ], + "properties": [ + { + "name": "email", + "template": "@email", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "users", + 0, + "email" + ] + }, + { + "name": "name", + "template": "@name", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "users", + 0, + "name" + ] + }, + { + "name": "ip", + "template": "@ip", + "type": "string", + "rule": {}, + "path": [ + "ROOT", + "users", + 0, + "ip" + ] + } + ] + } + ] + } + ] +}` + +var mock7 = `{ + "template": { + "users|1-10": [ + 10, + 20 + ] + }, + "type": "object", + "rule": {}, + "path": [ + "ROOT" + ], + "properties": [ + { + "name": "users", + "template": [ + 10, + 20 + ], + "type": "array", + "rule": { + "parameters": [ + "users|1-10", + "users", + null, + "1-10", + null + ], + "range": [ + "1-10", + "1", + "10" + ], + "min": 1, + "max": 10, + "count": 2 + }, + "path": [ + "ROOT", + "users" + ], + "items": [ + { + "name": 0, + "template": 10, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "users", + 0 + ] + }, + { + "name": 1, + "template": 20, + "type": "number", + "rule": {}, + "path": [ + "ROOT", + "users", + 1 + ] + } + ] + } + ] +}` + func TestJsonSchemaMockJsUnmarshal(t *testing.T) { type args struct { valueMap interface{} } valueMap := make(map[string]interface{}) - json.Unmarshal([]byte(mock5), &valueMap) + json.Unmarshal([]byte(mock7), &valueMap) tests := []struct { name string args args From 3bd14c724a19c65188027d43dc4204c397fcb212 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 18:06:54 +0800 Subject: [PATCH 17/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/complete.go | 106 ------------------ drivers/plugins/proxy-mirror/config.go | 15 +-- drivers/plugins/proxy-mirror/driver.go | 7 +- drivers/plugins/proxy-mirror/handler.go | 49 ++++---- drivers/plugins/proxy-mirror/mirror-node.go | 83 ++++++++++++++ .../plugins/proxy-mirror/mirror-service.go | 77 +++++++++++++ drivers/plugins/proxy-mirror/proxy-mirror.go | 32 +++--- go.mod | 2 +- 8 files changed, 207 insertions(+), 164 deletions(-) delete mode 100644 drivers/plugins/proxy-mirror/complete.go create mode 100644 drivers/plugins/proxy-mirror/mirror-node.go create mode 100644 drivers/plugins/proxy-mirror/mirror-service.go diff --git a/drivers/plugins/proxy-mirror/complete.go b/drivers/plugins/proxy-mirror/complete.go deleted file mode 100644 index 579b0e26..00000000 --- a/drivers/plugins/proxy-mirror/complete.go +++ /dev/null @@ -1,106 +0,0 @@ -package proxy_mirror - -import ( - "fmt" - "github.com/eolinker/eosc/eocontext" - http_service "github.com/eolinker/eosc/eocontext/http-context" - "github.com/eolinker/eosc/log" - "time" -) - -type httpComplete struct { - proxyCfg *Config -} - -func newHttpMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { - return &httpComplete{ - proxyCfg: proxyCfg, - } -} - -func (h *httpComplete) Complete(org eocontext.EoContext) error { - ctx, err := http_service.Assert(org) - if err != nil { - return err - } - //设置响应开始时间 - proxyTime := time.Now() - - defer func() { - //设置原始响应状态码 - ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "") - //设置上游响应总时间, 单位为毫秒 - //ctx.WithValue("response_time", time.Now().Sub(proxyTime).Milliseconds()) - ctx.Response().SetResponseTime(time.Now().Sub(proxyTime)) - ctx.SetLabel("handler", "proxy") - }() - - var lastErr error - - timeOut := time.Duration(h.proxyCfg.Timeout) * time.Millisecond - - //构造addr - path := ctx.Request().URI().Path() - if h.proxyCfg.Path != "" { - switch h.proxyCfg.PathMode { - case pathModeReplace: - path = h.proxyCfg.Path - case pathModePrefix: - path = fmt.Sprintf("%s%s", h.proxyCfg.Path, path) - } - } - ctx.Proxy().URI().SetPath(path) - - lastErr = ctx.SendTo(h.proxyCfg.Host, timeOut) - if lastErr == nil { - return nil - } - log.Error("http proxyMirror send error: ", lastErr) - - return lastErr -} - -type dubbo2Complete struct { - proxyCfg *Config -} - -func newDubbo2MirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { - return &httpComplete{ - proxyCfg: proxyCfg, - } -} - -func (d *dubbo2Complete) Complete(ctx eocontext.EoContext) error { - //TODO implement me - return nil -} - -type grpcComplete struct { - proxyCfg *Config -} - -func newGrpcMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { - return &httpComplete{ - proxyCfg: proxyCfg, - } -} - -func (g *grpcComplete) Complete(ctx eocontext.EoContext) error { - //TODO implement me - return nil -} - -type websocketComplete struct { - proxyCfg *Config -} - -func newWebsocketMirrorComplete(proxyCfg *Config) eocontext.CompleteHandler { - return &httpComplete{ - proxyCfg: proxyCfg, - } -} - -func (w *websocketComplete) Complete(ctx eocontext.EoContext) error { - //TODO implement me - return nil -} diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go index 8df93980..54b4782f 100644 --- a/drivers/plugins/proxy-mirror/config.go +++ b/drivers/plugins/proxy-mirror/config.go @@ -1,11 +1,11 @@ package proxy_mirror type Config struct { - Host string `json:"host" label:"服务地址" description:"镜像服务地址, 需要包含scheme"` - Path string `json:"path" label:"请求路径" description:"镜像的请求路径, 不填则默认使用当前路径"` - PathMode string `json:"path_mode" label:"请求路径拼接模式" enum:"replace,prefix"` + Addr string `json:"Addr" label:"服务地址" description:"镜像服务地址, 需要包含scheme"` SampleConf *SampleConfig `json:"sample_conf" label:"采样配置"` Timeout int `json:"timeout" label:"请求超时时间"` + PassHost string `json:"pass_host" enum:"pass,node,rewrite" default:"pass" label:"转发域名" description:"请求发给上游时的 host 设置选型,pass:将客户端的 host 透传给上游,node:使用addr中配置的host,rewrite:使用下面指定的host值"` + Host string `json:"upstream_host" label:"上游host" description:"指定上游请求的host,只有在 转发域名 配置为 rewrite 时有效" switch:"pass_host==='rewrite'"` } type SampleConfig struct { @@ -14,8 +14,9 @@ type SampleConfig struct { } const ( - pathModeReplace = "replace" - pathModePrefix = "prefix" + modePass = "pass" + modeNode = "node" + modeRewrite = "rewrite" ) func (c *Config) doCheck() error { @@ -23,10 +24,6 @@ func (c *Config) doCheck() error { //校验host - //校验path - - //校验path_mode - //校验采样配置 //校验镜像请求超时时间 diff --git a/drivers/plugins/proxy-mirror/driver.go b/drivers/plugins/proxy-mirror/driver.go index e13879b0..fb9637ca 100644 --- a/drivers/plugins/proxy-mirror/driver.go +++ b/drivers/plugins/proxy-mirror/driver.go @@ -3,6 +3,7 @@ package proxy_mirror import ( "github.com/eolinker/apinto/drivers" "github.com/eolinker/eosc" + "time" ) func Check(v *Config, workers map[eosc.RequireId]eosc.IWorker) error { @@ -31,8 +32,10 @@ func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWork } pm := &proxyMirror{ - WorkerBase: drivers.Worker(id, name), - proxyConf: conf, + WorkerBase: drivers.Worker(id, name), + randomRange: conf.SampleConf.RandomRange, + randomPivot: conf.SampleConf.RandomPivot, + service: newMirrorService(conf.Addr, conf.PassHost, conf.Host, time.Duration(conf.Timeout)), } return pm, nil diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go index 4b344bb7..79bd3a1c 100644 --- a/drivers/plugins/proxy-mirror/handler.go +++ b/drivers/plugins/proxy-mirror/handler.go @@ -2,32 +2,18 @@ package proxy_mirror import ( "github.com/eolinker/eosc/eocontext" - dubbo2_context "github.com/eolinker/eosc/eocontext/dubbo2-context" - grpc_context "github.com/eolinker/eosc/eocontext/grpc-context" - http_service "github.com/eolinker/eosc/eocontext/http-context" - log "github.com/eolinker/goku-api-gateway/goku-log" + "github.com/eolinker/eosc/log" ) type proxyMirrorCompleteHandler struct { - orgComplete eocontext.CompleteHandler - mirrorComplete eocontext.CompleteHandler + orgComplete eocontext.CompleteHandler + app eocontext.EoApp } -func newMirrorHandler(eoCtx eocontext.EoContext, proxyCfg *Config) (eocontext.CompleteHandler, error) { +func newMirrorHandler(eoCtx eocontext.EoContext, app eocontext.EoApp) (eocontext.CompleteHandler, error) { handler := &proxyMirrorCompleteHandler{ orgComplete: eoCtx.GetComplete(), - } - - if _, success := eoCtx.(http_service.IHttpContext); success { - handler.mirrorComplete = newHttpMirrorComplete(proxyCfg) - } else if _, success = eoCtx.(grpc_context.IGrpcContext); success { - handler.mirrorComplete = newGrpcMirrorComplete(proxyCfg) - } else if _, success = eoCtx.(dubbo2_context.IDubbo2Context); success { - handler.mirrorComplete = newDubbo2MirrorComplete(proxyCfg) - } else if _, success = eoCtx.(http_service.IWebsocketContext); success { - handler.mirrorComplete = newWebsocketMirrorComplete(proxyCfg) - } else { - return nil, ErrUnsupportedType + app: app, } return handler, nil @@ -35,19 +21,22 @@ func newMirrorHandler(eoCtx eocontext.EoContext, proxyCfg *Config) (eocontext.Co func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error { cloneCtx, err := ctx.Clone() - if err != nil { - return err - } //先执行原始Complete, 再执行镜像请求的Complete - err = p.orgComplete.Complete(ctx) + orgErr := p.orgComplete.Complete(ctx) - go func() { - mErr := p.mirrorComplete.Complete(cloneCtx) - if mErr != nil { - log.Error(mErr) - } - }() + if err == nil { + cloneCtx.SetApp(p.app) + + go func() { + err = p.orgComplete.Complete(cloneCtx) + if err != nil { + log.Error(err) + } + }() + } else { + log.Error(err) + } - return err + return orgErr } diff --git a/drivers/plugins/proxy-mirror/mirror-node.go b/drivers/plugins/proxy-mirror/mirror-node.go new file mode 100644 index 00000000..6149f6c7 --- /dev/null +++ b/drivers/plugins/proxy-mirror/mirror-node.go @@ -0,0 +1,83 @@ +package proxy_mirror + +import ( + "errors" + "fmt" + "github.com/eolinker/eosc/eocontext" +) + +var ( + errNoValidNode = errors.New("no valid node") +) + +type node struct { + labels eocontext.Attrs + id string + ip string + port int + status eocontext.NodeStatus +} + +// newNode 创建新节点 +func newNode(labels map[string]string, id string, ip string, port int) eocontext.INode { + return &node{labels: labels, id: id, ip: ip, port: port, status: eocontext.Running} +} + +// GetAttrs 获取节点属性集合 +func (n *node) GetAttrs() eocontext.Attrs { + return n.labels +} + +// GetAttrByName 通过属性名获取节点属性 +func (n *node) GetAttrByName(name string) (string, bool) { + v, ok := n.labels[name] + return v, ok +} + +// IP 返回节点IP +func (n *node) IP() string { + return n.ip +} + +// Port 返回节点端口 +func (n *node) Port() int { + return n.port +} + +// ID 返回节点ID +func (n *node) ID() string { + return n.id +} + +// Status 返回节点状态 +func (n *node) Status() eocontext.NodeStatus { + return n.status +} + +// Labels 返回节点标签集合 +func (n *node) Labels() map[string]string { + return n.labels +} + +// Addr 返回节点地址 +func (n *node) Addr() string { + if n.port == 0 { + return n.ip + } + return fmt.Sprintf("%s:%d", n.ip, n.port) +} + +// Up 将节点状态置为运行中 +func (n *node) Up() { + n.status = eocontext.Running +} + +// Down 将节点状态置为不可用 +func (n *node) Down() { + n.status = eocontext.Down +} + +// Leave 将节点状态置为离开 +func (n *node) Leave() { + n.status = eocontext.Leave +} diff --git a/drivers/plugins/proxy-mirror/mirror-service.go b/drivers/plugins/proxy-mirror/mirror-service.go new file mode 100644 index 00000000..99bf0e18 --- /dev/null +++ b/drivers/plugins/proxy-mirror/mirror-service.go @@ -0,0 +1,77 @@ +package proxy_mirror + +import ( + "fmt" + "github.com/eolinker/eosc/eocontext" + "strconv" + "strings" + "time" +) + +type mirrorService struct { + scheme string + passHost eocontext.PassHostMod + host string + timeout time.Duration + nodes []eocontext.INode +} + +func newMirrorService(target, passHost, host string, timeout time.Duration) *mirrorService { + labels := map[string]string{} + + idx := strings.Index(target, "://") + scheme := target[:idx] + addr := target[idx+3:] + + idx = strings.Index(addr, ":") + ip := addr + port := 0 + if idx > 0 { + ip = addr[:idx] + portStr := addr[idx+1:] + port, _ = strconv.Atoi(portStr) + } + + inode := newNode(labels, fmt.Sprintf("%s:%d", ip, port), ip, port) + + var mode eocontext.PassHostMod + switch passHost { + case modePass: + mode = eocontext.PassHost + case modeNode: + mode = eocontext.NodeHost + case modeRewrite: + mode = eocontext.ReWriteHost + } + + return &mirrorService{ + scheme: scheme, + passHost: mode, + host: host, + timeout: timeout, + nodes: []eocontext.INode{inode}, + } +} + +func (m *mirrorService) Nodes() []eocontext.INode { + return m.nodes +} + +func (m *mirrorService) Scheme() string { + return m.scheme +} + +func (m *mirrorService) TimeOut() time.Duration { + return m.timeout +} + +func (m *mirrorService) PassHost() (eocontext.PassHostMod, string) { + return m.passHost, m.host +} + +func (m *mirrorService) Select(ctx eocontext.EoContext) (eocontext.INode, error) { + if len(m.nodes) < 1 { + return nil, errNoValidNode + } + return m.nodes[0], nil +} diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go index b14ea488..69faab50 100644 --- a/drivers/plugins/proxy-mirror/proxy-mirror.go +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -14,29 +14,27 @@ var _ eocontext.IFilter = (*proxyMirror)(nil) type proxyMirror struct { drivers.WorkerBase - proxyConf *Config + randomRange int + randomPivot int + service *mirrorService } func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) error { - if next != nil { - err := next.DoChain(ctx) - if err != nil { - log.Error(err) - } + //进行采样, 生成随机数判断 + rand.Seed(time.Now().UnixNano()) + randomNum := rand.Intn(p.randomRange + 1) //[0,range]范围内整型 + if randomNum <= p.randomPivot { //若随机数在[0,pivot]范围内则进行转发 + setMirrorProxy(p.service, ctx) } - if p.proxyConf != nil { - //进行采样, 生成随机数判断 - rand.Seed(time.Now().UnixNano()) - randomNum := rand.Intn(p.proxyConf.SampleConf.RandomRange + 1) //[0,range]范围内整型 - if randomNum <= p.proxyConf.SampleConf.RandomPivot { //若随机数在[0,pivot]范围内则进行转发 - setMirrorProxy(p.proxyConf, ctx) - } + + if next != nil { + return next.DoChain(ctx) } return nil } -func setMirrorProxy(proxyCfg *Config, ctx eocontext.EoContext) { +func setMirrorProxy(proxyCfg *mirrorService, ctx eocontext.EoContext) { //先判断当前Ctx是否能Copy if !ctx.IsCloneable() { log.Info(ErrUnsupportedType) @@ -60,7 +58,10 @@ func (p *proxyMirror) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWork if err != nil { return err } - p.proxyConf = conf + + p.service = newMirrorService(conf.Addr, conf.PassHost, conf.Host, time.Duration(conf.Timeout)) + p.randomRange = conf.SampleConf.RandomRange + p.randomPivot = conf.SampleConf.RandomPivot return nil } @@ -70,7 +71,6 @@ func (p *proxyMirror) Stop() error { } func (p *proxyMirror) Destroy() { - p.proxyConf = nil } func (p *proxyMirror) CheckSkill(skill string) bool { diff --git a/go.mod b/go.mod index a61656fc..daacb0f7 100644 --- a/go.mod +++ b/go.mod @@ -197,4 +197,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -//replace github.com/eolinker/eosc => ../eosc +replace github.com/eolinker/eosc => ../eosc From c9735afbfecd64f9a85bd7c6eab58525fff4a39d Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 18:38:52 +0800 Subject: [PATCH 18/25] =?UTF-8?q?utils=E6=96=B0=E5=A2=9E=E5=9B=9B=E4=B8=AA?= =?UTF-8?q?=E6=AD=A3=E5=88=99=E6=A0=A1=E9=AA=8C=EF=BC=8C=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=96=B0=E5=A2=9E=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/config.go | 40 ++++++++++++++++++-- drivers/plugins/proxy-mirror/error.go | 13 ++++++- drivers/plugins/proxy-mirror/proxy-mirror.go | 2 +- utils/regex.go | 36 +++++++++++++++++- 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go index 54b4782f..542d8274 100644 --- a/drivers/plugins/proxy-mirror/config.go +++ b/drivers/plugins/proxy-mirror/config.go @@ -1,11 +1,13 @@ package proxy_mirror +import "github.com/eolinker/apinto/utils" + type Config struct { Addr string `json:"Addr" label:"服务地址" description:"镜像服务地址, 需要包含scheme"` SampleConf *SampleConfig `json:"sample_conf" label:"采样配置"` Timeout int `json:"timeout" label:"请求超时时间"` PassHost string `json:"pass_host" enum:"pass,node,rewrite" default:"pass" label:"转发域名" description:"请求发给上游时的 host 设置选型,pass:将客户端的 host 透传给上游,node:使用addr中配置的host,rewrite:使用下面指定的host值"` - Host string `json:"upstream_host" label:"上游host" description:"指定上游请求的host,只有在 转发域名 配置为 rewrite 时有效" switch:"pass_host==='rewrite'"` + Host string `json:"host" label:"新host" description:"指定上游请求的host,只有在 转发域名 配置为 rewrite 时有效" switch:"pass_host==='rewrite'"` } type SampleConfig struct { @@ -20,13 +22,43 @@ const ( ) func (c *Config) doCheck() error { - //TODO - - //校验host + //校验addr + if !utils.IsMatchSchemeIpPort(c.Addr) && !utils.IsMatchSchemeDomainPort(c.Addr) { + return errAddr + } //校验采样配置 + if c.SampleConf.RandomRange <= 0 { + return errRandomRangeNum + } + if c.SampleConf.RandomPivot <= 0 { + return errRandomPivotNum + } + if c.SampleConf.RandomPivot > c.SampleConf.RandomRange { + return errRandomPivot + } //校验镜像请求超时时间 + if c.Timeout < 0 { + return errTimeout + } + + //校验passHost + switch c.PassHost { + case modePass: + case modeNode: + case modeRewrite: + default: + return errUnsupportedPassHost + } + + //校验host + if c.PassHost == modeRewrite && c.Host == "" { + return errHostNull + } + if !utils.IsMatchIpPort(c.Addr) && !utils.IsMatchDomainPort(c.Addr) { + return errAddr + } return nil } diff --git a/drivers/plugins/proxy-mirror/error.go b/drivers/plugins/proxy-mirror/error.go index 1ff39ea5..ae5234be 100644 --- a/drivers/plugins/proxy-mirror/error.go +++ b/drivers/plugins/proxy-mirror/error.go @@ -2,4 +2,15 @@ package proxy_mirror import "github.com/pkg/errors" -var ErrUnsupportedType = errors.New("send mirror proxy fail. Unsupported Context Type") +var ( + errUnsupportedContextType = errors.New("send mirror proxy fail. Unsupported Context Type") + errHostNull = errors.New("host can't be null when pass_host is rewrite. ") + errUnsupportedPassHost = errors.New("unsupported pass_host. ") + errTimeout = errors.New("timeout can't be smaller than 0. ") + + errRandomRangeNum = errors.New("random_range should be bigger than 0. ") + errRandomPivotNum = errors.New("random_pivot should be bigger than 0. ") + errRandomPivot = errors.New("random_pivot should be smaller than random_range. ") + + errAddr = errors.New("addr is illegal. ") +) diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go index 69faab50..ce1162df 100644 --- a/drivers/plugins/proxy-mirror/proxy-mirror.go +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -37,7 +37,7 @@ func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) e func setMirrorProxy(proxyCfg *mirrorService, ctx eocontext.EoContext) { //先判断当前Ctx是否能Copy if !ctx.IsCloneable() { - log.Info(ErrUnsupportedType) + log.Info(errUnsupportedContextType) return } //给ctx设置新的FinishHandler diff --git a/utils/regex.go b/utils/regex.go index 87b427c4..082b40df 100644 --- a/utils/regex.go +++ b/utils/regex.go @@ -7,11 +7,23 @@ const ( regexUrlPathStr = `^\/[a-zA-Z0-9\/_\-\.]*$` //objectivesExp 校验0.5:0.1,0.9:0.001的格式 objectivesExp = `^((0\.[0-9]+)\:(0\.[0-9]+)(\,)?)+$` + // schemeIPPortExp scheme://IP:PORT + schemeIPPortExp = `^[a-zA-z]+://((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]+$` + // schemeDomainPortExp scheme://域名或者域名:端口 + schemeDomainPortExp = `^[a-zA-z]+://[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?(:[0-9]+)?$` + // domainPortExp IP:PORT或者IP + ipPortExp = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}(:[0-9]+)?$` + // domainPortExp 域名或者域名:端口 + domainPortExp = `^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?(:[0-9]+)?$` ) var ( - regexUrlPath = regexp.MustCompile(regexUrlPathStr) - objectivesRegexp = regexp.MustCompile(objectivesExp) + regexUrlPath = regexp.MustCompile(regexUrlPathStr) + objectivesRegexp = regexp.MustCompile(objectivesExp) + schemeIPPortReg = regexp.MustCompile(schemeIPPortExp) + schemeDomainPortReg = regexp.MustCompile(schemeDomainPortExp) + ipPortReg = regexp.MustCompile(ipPortExp) + domainPortReg = regexp.MustCompile(domainPortExp) ) func CheckUrlPath(path string) bool { @@ -22,3 +34,23 @@ func CheckUrlPath(path string) bool { func CheckObjectives(objectives string) bool { return objectivesRegexp.MatchString(objectives) } + +// IsMatchSchemeIpPort 判断字符串是否符合scheme://ip:port +func IsMatchSchemeIpPort(s string) bool { + return schemeIPPortReg.MatchString(s) +} + +// IsMatchSchemeDomainPort 判断字符串是否符合 scheme://域名或者域名:port +func IsMatchSchemeDomainPort(s string) bool { + return schemeDomainPortReg.MatchString(s) +} + +// IsMatchIpPort 判断字符串是否符合 ip:port或者ip +func IsMatchIpPort(s string) bool { + return ipPortReg.MatchString(s) +} + +// IsMatchDomainPort 判断字符串是否符合 域名或者域名:port +func IsMatchDomainPort(s string) bool { + return domainPortReg.MatchString(s) +} From c84af3f9eb7eb46055cf070b334b3f6b22f87f79 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 18:48:14 +0800 Subject: [PATCH 19/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/handler.go | 10 ++++++---- drivers/plugins/proxy-mirror/proxy-mirror.go | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go index 79bd3a1c..2c660420 100644 --- a/drivers/plugins/proxy-mirror/handler.go +++ b/drivers/plugins/proxy-mirror/handler.go @@ -7,13 +7,13 @@ import ( type proxyMirrorCompleteHandler struct { orgComplete eocontext.CompleteHandler - app eocontext.EoApp + service *mirrorService } -func newMirrorHandler(eoCtx eocontext.EoContext, app eocontext.EoApp) (eocontext.CompleteHandler, error) { +func newMirrorHandler(eoCtx eocontext.EoContext, service *mirrorService) (eocontext.CompleteHandler, error) { handler := &proxyMirrorCompleteHandler{ orgComplete: eoCtx.GetComplete(), - app: app, + service: service, } return handler, nil @@ -26,7 +26,9 @@ func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error { orgErr := p.orgComplete.Complete(ctx) if err == nil { - cloneCtx.SetApp(p.app) + cloneCtx.SetApp(p.service) + cloneCtx.SetBalance(p.service) + cloneCtx.SetUpstreamHostHandler(p.service) go func() { err = p.orgComplete.Complete(cloneCtx) diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go index ce1162df..dd41c419 100644 --- a/drivers/plugins/proxy-mirror/proxy-mirror.go +++ b/drivers/plugins/proxy-mirror/proxy-mirror.go @@ -34,14 +34,14 @@ func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) e return nil } -func setMirrorProxy(proxyCfg *mirrorService, ctx eocontext.EoContext) { +func setMirrorProxy(service *mirrorService, ctx eocontext.EoContext) { //先判断当前Ctx是否能Copy if !ctx.IsCloneable() { log.Info(errUnsupportedContextType) return } //给ctx设置新的FinishHandler - newCompleteHandler, err := newMirrorHandler(ctx, proxyCfg) + newCompleteHandler, err := newMirrorHandler(ctx, service) if err != nil { log.Info(err) return From a077ec7a20bb4be1e6f4281cc2a1468125c1f9e8 Mon Sep 17 00:00:00 2001 From: huangmengzhu Date: Thu, 2 Mar 2023 19:46:51 +0800 Subject: [PATCH 20/25] =?UTF-8?q?=E9=87=8D=E6=9E=84=20clone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/handler.go | 27 +- .../http-router/http-complete/complete.go | 9 +- node/http-context/body.go | 6 +- node/http-context/clone.go | 206 ++++++++++++ node/http-context/context.go | 27 +- node/http-context/http-context-copy/body.go | 293 ------------------ .../http-context/http-context-copy/context.go | 266 ---------------- node/http-context/http-context-copy/error.go | 10 - node/http-context/http-context-copy/header.go | 140 --------- node/http-context/http-context-copy/pool.go | 19 -- .../http-context-copy/proxy-agent.go | 89 ------ node/http-context/http-context-copy/proxy.go | 72 ----- .../http-context-copy/request-reader.go | 104 ------- .../http-context-copy/response.go | 121 -------- node/http-context/http-context-copy/uri.go | 82 ----- node/http-context/proxy.go | 6 +- 16 files changed, 244 insertions(+), 1233 deletions(-) create mode 100644 node/http-context/clone.go delete mode 100644 node/http-context/http-context-copy/body.go delete mode 100644 node/http-context/http-context-copy/context.go delete mode 100644 node/http-context/http-context-copy/error.go delete mode 100644 node/http-context/http-context-copy/header.go delete mode 100644 node/http-context/http-context-copy/pool.go delete mode 100644 node/http-context/http-context-copy/proxy-agent.go delete mode 100644 node/http-context/http-context-copy/proxy.go delete mode 100644 node/http-context/http-context-copy/request-reader.go delete mode 100644 node/http-context/http-context-copy/response.go delete mode 100644 node/http-context/http-context-copy/uri.go diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go index 2c660420..7ab44ad1 100644 --- a/drivers/plugins/proxy-mirror/handler.go +++ b/drivers/plugins/proxy-mirror/handler.go @@ -25,20 +25,21 @@ func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error { //先执行原始Complete, 再执行镜像请求的Complete orgErr := p.orgComplete.Complete(ctx) - if err == nil { - cloneCtx.SetApp(p.service) - cloneCtx.SetBalance(p.service) - cloneCtx.SetUpstreamHostHandler(p.service) - - go func() { - err = p.orgComplete.Complete(cloneCtx) - if err != nil { - log.Error(err) - } - }() - } else { - log.Error(err) + if err != nil { + log.Warn(err) + return orgErr } + cloneCtx.SetApp(p.service) + cloneCtx.SetBalance(p.service) + cloneCtx.SetUpstreamHostHandler(p.service) + + go func() { + err = p.orgComplete.Complete(cloneCtx) + if err != nil { + log.Error(err) + } + }() + return orgErr } diff --git a/drivers/router/http-router/http-complete/complete.go b/drivers/router/http-router/http-complete/complete.go index 9a6f54f1..154ba946 100644 --- a/drivers/router/http-router/http-complete/complete.go +++ b/drivers/router/http-router/http-complete/complete.go @@ -12,11 +12,6 @@ import ( "github.com/eolinker/eosc/log" ) -const ( - KeyHttpRetry = "http_retry" - KeyHttpTimeout = "http_timeout" -) - var ( ErrorTimeoutComplete = errors.New("complete timeout") ) @@ -60,13 +55,13 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error { } timeOut := app.TimeOut() - retryValue := ctx.Value(KeyHttpRetry) + retryValue := ctx.Value(http_service.KeyHttpRetry) retry, ok := retryValue.(int) if !ok { retry = 1 } - timeoutValue := ctx.Value(KeyHttpTimeout) + timeoutValue := ctx.Value(http_service.KeyHttpTimeout) timeout, ok := timeoutValue.(time.Duration) if !ok { timeout = 3000 * time.Millisecond diff --git a/node/http-context/body.go b/node/http-context/body.go index 2785da4c..6b95d856 100644 --- a/node/http-context/body.go +++ b/node/http-context/body.go @@ -2,13 +2,12 @@ package http_context import ( "bytes" + "io" "strings" http_context "github.com/eolinker/eosc/eocontext/http-context" "github.com/valyala/fasthttp" - "io/ioutil" - "mime" "mime/multipart" "net/url" @@ -227,7 +226,7 @@ func (b *BodyRequestHandler) resetFile() error { return err } - data, err := ioutil.ReadAll(fio) + data, err := io.ReadAll(fio) if err != nil { fio.Close() return err @@ -288,6 +287,5 @@ func (b *BodyRequestHandler) SetForm(values url.Values) error { func (b *BodyRequestHandler) SetRaw(contentType string, body []byte) { b.request.SetBodyRaw(body) b.request.Header.SetContentType(contentType) - return } diff --git a/node/http-context/clone.go b/node/http-context/clone.go new file mode 100644 index 00000000..ee265790 --- /dev/null +++ b/node/http-context/clone.go @@ -0,0 +1,206 @@ +package http_context + +import ( + "context" + "fmt" + "github.com/valyala/fasthttp" + "net" + "time" + + "github.com/eolinker/eosc/utils/config" + + fasthttp_client "github.com/eolinker/apinto/node/fasthttp-client" + + eoscContext "github.com/eolinker/eosc/eocontext" + http_service "github.com/eolinker/eosc/eocontext/http-context" +) + +var _ http_service.IHttpContext = (*cloneContext)(nil) + +// HttpContext fasthttpRequestCtx +type cloneContext struct { + org *HttpContext + proxyRequest ProxyRequest + proxyRequests []http_service.IProxy + + ctx context.Context + completeHandler eoscContext.CompleteHandler + finishHandler eoscContext.FinishHandler + app eoscContext.EoApp + balance eoscContext.BalanceHandler + upstreamHostHandler eoscContext.UpstreamHostHandler + labels map[string]string + + responseError error +} + +func (ctx *cloneContext) GetUpstreamHostHandler() eoscContext.UpstreamHostHandler { + return ctx.upstreamHostHandler +} + +func (ctx *cloneContext) SetUpstreamHostHandler(handler eoscContext.UpstreamHostHandler) { + ctx.upstreamHostHandler = handler +} + +func (ctx *cloneContext) LocalIP() net.IP { + return ctx.org.LocalIP() +} + +func (ctx *cloneContext) LocalAddr() net.Addr { + return ctx.org.LocalAddr() +} + +func (ctx *cloneContext) LocalPort() int { + return ctx.org.LocalPort() +} + +func (ctx *cloneContext) GetApp() eoscContext.EoApp { + return ctx.app +} + +func (ctx *cloneContext) SetApp(app eoscContext.EoApp) { + ctx.app = app +} + +func (ctx *cloneContext) GetBalance() eoscContext.BalanceHandler { + return ctx.balance +} + +func (ctx *cloneContext) SetBalance(handler eoscContext.BalanceHandler) { + ctx.balance = handler +} + +func (ctx *cloneContext) SetLabel(name, value string) { + ctx.labels[name] = value +} + +func (ctx *cloneContext) GetLabel(name string) string { + return ctx.labels[name] +} + +func (ctx *cloneContext) Labels() map[string]string { + return ctx.labels +} + +func (ctx *cloneContext) GetComplete() eoscContext.CompleteHandler { + return ctx.completeHandler +} + +func (ctx *cloneContext) SetCompleteHandler(handler eoscContext.CompleteHandler) { + ctx.completeHandler = handler +} + +func (ctx *cloneContext) GetFinish() eoscContext.FinishHandler { + return ctx.finishHandler +} + +func (ctx *cloneContext) SetFinish(handler eoscContext.FinishHandler) { + ctx.finishHandler = handler +} + +func (ctx *cloneContext) Scheme() string { + return ctx.org.Scheme() +} + +func (ctx *cloneContext) Assert(i interface{}) error { + if v, ok := i.(*http_service.IHttpContext); ok { + *v = ctx + return nil + } + return fmt.Errorf("not suport:%s", config.TypeNameOf(i)) +} + +func (ctx *cloneContext) Proxies() []http_service.IProxy { + return ctx.proxyRequests +} + +func (ctx *cloneContext) Response() http_service.IResponse { + return nil +} + +func (ctx *cloneContext) SendTo(address string, timeout time.Duration) error { + + scheme, host := readAddress(address) + request := ctx.proxyRequest.Request() + + passHost, targetHost := ctx.GetUpstreamHostHandler().PassHost() + switch passHost { + case eoscContext.PassHost: + case eoscContext.NodeHost: + request.URI().SetHost(host) + case eoscContext.ReWriteHost: + request.URI().SetHost(targetHost) + } + response := fasthttp.AcquireResponse() + beginTime := time.Now() + ctx.responseError = fasthttp_client.ProxyTimeout(address, request, response, timeout) + agent := newRequestAgent(&ctx.proxyRequest, host, scheme, beginTime, time.Now()) + if ctx.responseError != nil { + agent.setStatusCode(504) + } else { + agent.setStatusCode(response.StatusCode()) + } + + agent.setResponseLength(response.Header.ContentLength()) + + ctx.proxyRequests = append(ctx.proxyRequests, agent) + return ctx.responseError + +} + +func (ctx *cloneContext) Context() context.Context { + + return ctx.ctx +} + +func (ctx *cloneContext) AcceptTime() time.Time { + return ctx.org.AcceptTime() +} + +func (ctx *cloneContext) Value(key interface{}) interface{} { + return ctx.org.Value(key) +} + +func (ctx *cloneContext) WithValue(key, val interface{}) { + ctx.ctx = context.WithValue(ctx.Context(), key, val) +} + +func (ctx *cloneContext) Proxy() http_service.IRequest { + return &ctx.proxyRequest +} + +func (ctx *cloneContext) Request() http_service.IRequestReader { + + return ctx.org.Request() +} + +func (ctx *cloneContext) IsCloneable() bool { + return false +} + +func (ctx *cloneContext) Clone() (eoscContext.EoContext, error) { + + return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) + +} + +var copyKey = struct{}{} + +// RequestId 请求ID +func (ctx *cloneContext) RequestId() string { + return ctx.org.requestID +} + +// Finish finish +func (ctx *cloneContext) FastFinish() { + + ctx.ctx = nil + ctx.app = nil + ctx.balance = nil + ctx.upstreamHostHandler = nil + ctx.finishHandler = nil + ctx.completeHandler = nil + + ctx.proxyRequest.Finish() + +} diff --git a/node/http-context/context.go b/node/http-context/context.go index 2d23223d..12ad533d 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -3,7 +3,6 @@ package http_context import ( "context" "fmt" - http_context_copy "github.com/eolinker/apinto/node/http-context/http-context-copy" "net" "strings" "time" @@ -186,21 +185,25 @@ func (ctx *HttpContext) IsCloneable() bool { } func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { - if !ctx.IsCloneable() { - return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) + + copyContext := &cloneContext{ + org: ctx, + proxyRequests: make([]http_service.IProxy, 0, 5), } - ctxCopy := http_context_copy.NewContextCopy(ctx.fastHttpRequestCtx, ctx.requestID, ctx.port, ctx.labels) + req := fasthttp.AcquireRequest() + ctx.fastHttpRequestCtx.Request.CopyTo(req) - ctxCopy.SetCompleteHandler(ctx.completeHandler) - ctxCopy.SetFinish(ctx.finishHandler) - ctxCopy.SetUpstreamHostHandler(ctx.upstreamHostHandler) - ctxCopy.SetApp(ctx.app) - ctxCopy.SetBalance(ctx.balance) + copyContext.proxyRequest.reset(req, ctx.requestReader.remoteAddr) + copyContext.proxyRequests = copyContext.proxyRequests[:0] - //Ctx set retry,timeout TODO + copyContext.labels = ctx.Labels() - return ctxCopy, nil + //记录请求时间 + copyContext.ctx = context.WithValue(ctx.Context(), copyKey, true) + copyContext.WithValue(http_service.KeyHttpRetry, 0) + copyContext.WithValue(http_service.KeyHttpTimeout, time.Duration(0)) + return copyContext, nil } // NewContext 创建Context @@ -253,7 +256,7 @@ func (ctx *HttpContext) FastFinish() { ctx.response.Finish() ctx.fastHttpRequestCtx = nil pool.Put(ctx) - return + } func NotFound(ctx *HttpContext) { diff --git a/node/http-context/http-context-copy/body.go b/node/http-context/http-context-copy/body.go deleted file mode 100644 index 87a24f6b..00000000 --- a/node/http-context/http-context-copy/body.go +++ /dev/null @@ -1,293 +0,0 @@ -package http_context_copy - -import ( - "bytes" - "strings" - - http_context "github.com/eolinker/eosc/eocontext/http-context" - "github.com/valyala/fasthttp" - - "io/ioutil" - - "mime" - "mime/multipart" - "net/url" -) - -const defaultMultipartMemory = 32 << 20 // 32 MB -var ( - _ http_context.IBodyDataWriter = (*BodyRequestHandler)(nil) -) - -const ( - MultipartForm = "multipart/form-data" - FormData = "application/x-www-form-urlencoded" - TEXT = "text/plain" - JSON = "application/json" - JavaScript = "application/javascript" - AppLicationXML = "application/xml" - TextXML = "text/xml" - Html = "text/html" -) - -// BodyRequestHandler body请求处理器 -type BodyRequestHandler struct { - request *fasthttp.Request - - formdata *multipart.Form -} - -func (b *BodyRequestHandler) MultipartForm() (*multipart.Form, error) { - if b.formdata != nil { - return b.formdata, nil - } - if !strings.Contains(b.ContentType(), MultipartForm) { - return nil, ErrorNotMultipart - } - form, err := b.request.MultipartForm() - if err != nil { - return nil, err - } - - b.formdata = &multipart.Form{ - Value: form.Value, - File: form.File, - } - b.resetFile() - return form, nil -} -func (b *BodyRequestHandler) Files() (map[string][]*multipart.FileHeader, error) { - form, err := b.MultipartForm() - if err != nil { - return nil, err - } - return form.File, nil -} -func (b *BodyRequestHandler) reset(request *fasthttp.Request) { - b.request = request - b.formdata = nil -} -func NewBodyRequestHandler(request *fasthttp.Request) *BodyRequestHandler { - return &BodyRequestHandler{request: request} -} - -// GetForm 获取表单参数 -func (b *BodyRequestHandler) GetForm(key string) string { - contentType, _, _ := mime.ParseMediaType(b.ContentType()) - - switch contentType { - case FormData: - args := b.request.PostArgs() - if args == nil { - return "" - } - return string(args.Peek(key)) - case MultipartForm: - form, err := b.MultipartForm() - if err != nil { - return "" - } - vs := form.Value[key] - if len(vs) > 0 { - return vs[0] - } - return "" - - } - return "" -} - -// ContentType 获取contentType -func (b *BodyRequestHandler) ContentType() string { - return string(b.request.Header.ContentType()) -} - -// BodyForm 获取表单参数 -func (b *BodyRequestHandler) BodyForm() (url.Values, error) { - - contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) - switch contentType { - case FormData: - return url.ParseQuery(string(b.request.Body())) - case MultipartForm: - multipartForm, err := b.MultipartForm() - if err != nil { - return nil, err - } - return multipartForm.Value, nil - default: - return nil, ErrorNotForm - } - -} - -// RawBody 获取raw数据 -func (b *BodyRequestHandler) RawBody() ([]byte, error) { - return b.request.Body(), nil -} - -func (b *BodyRequestHandler) GetFile(key string) ([]*multipart.FileHeader, bool) { - multipartForm, err := b.MultipartForm() - if err != nil { - return nil, false - } - fl, has := multipartForm.File[key] - - return fl, has - -} - -func (b *BodyRequestHandler) SetToForm(key, value string) error { - contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) - switch contentType { - case FormData: - b.request.PostArgs().Set(key, value) - b.request.SetBodyRaw(b.request.PostArgs().QueryString()) - return nil - case MultipartForm: - multipartForm, err := b.MultipartForm() - if err != nil { - return err - } - multipartForm.Value[key] = []string{value} - return b.resetFile() - default: - return ErrorNotForm - } -} - -// AddForm 新增表单参数 -func (b *BodyRequestHandler) AddForm(key, value string) error { - - contentType, _, _ := mime.ParseMediaType(string(b.request.Header.ContentType())) - switch contentType { - case FormData: - b.request.PostArgs().Add(key, value) - b.request.SetBody(b.request.PostArgs().QueryString()) - return nil - case MultipartForm: - multipartForm, err := b.MultipartForm() - if err != nil { - return err - } - multipartForm.Value[key] = append(multipartForm.Value[key], value) - return b.resetFile() - default: - return ErrorNotForm - } -} - -// AddFile 新增文件参数 -func (b *BodyRequestHandler) AddFile(key string, file *multipart.FileHeader) error { - - contentType, _, _ := mime.ParseMediaType(b.ContentType()) - if contentType != FormData && contentType != MultipartForm { - return ErrorNotMultipart - } - multipartForm, err := b.MultipartForm() - if err != nil { - return err - } - multipartForm.File[key] = append(multipartForm.File[key], file) - - return b.resetFile() -} - -// SetFile 设置文件参数 -func (b *BodyRequestHandler) SetFile(files map[string][]*multipart.FileHeader) error { - - multipartForm, err := b.MultipartForm() - if err != nil { - return err - } - multipartForm.File = files - - return b.resetFile() -} - -func (b *BodyRequestHandler) resetFile() error { - multipartForm := b.formdata - if multipartForm == nil { - return nil - } - - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - - for name, fs := range multipartForm.File { - for _, f := range fs { - fio, err := f.Open() - if err != nil { - return err - } - - part, err := writer.CreateFormFile(name, f.Filename) - if err != nil { - fio.Close() - return err - } - - data, err := ioutil.ReadAll(fio) - if err != nil { - fio.Close() - return err - } - _, err = part.Write(data) - if err != nil { - fio.Close() - return err - } - fio.Close() - } - } - - for key, values := range multipartForm.Value { - //temp := make(url.Values) - //temp[key] = values - //value := temp.Encode() - for _, value := range values { - err := writer.WriteField(key, value) - if err != nil { - return err - } - } - - } - err := writer.Close() - if err != nil { - return err - } - b.request.Header.SetContentType(writer.FormDataContentType()) - b.request.SetBodyRaw(body.Bytes()) - return nil -} - -// SetForm 设置表单参数 -func (b *BodyRequestHandler) SetForm(values url.Values) error { - - contentType, _, _ := mime.ParseMediaType(b.ContentType()) - if contentType != FormData && contentType != MultipartForm { - return ErrorNotForm - } - switch contentType { - case FormData: - b.request.SetBodyString(values.Encode()) - case MultipartForm: - multipartForm, err := b.MultipartForm() - if err != nil { - return err - } - multipartForm.Value = values - return b.resetFile() - } - - return ErrorNotForm -} - -// SetRaw 设置raw数据 -func (b *BodyRequestHandler) SetRaw(contentType string, body []byte) { - b.request.SetBodyRaw(body) - b.request.Header.SetContentType(contentType) - return - -} diff --git a/node/http-context/http-context-copy/context.go b/node/http-context/http-context-copy/context.go deleted file mode 100644 index dc0d7dd7..00000000 --- a/node/http-context/http-context-copy/context.go +++ /dev/null @@ -1,266 +0,0 @@ -package http_context_copy - -import ( - "context" - "fmt" - "net" - "strings" - "time" - - "github.com/eolinker/eosc/utils/config" - - fasthttp_client "github.com/eolinker/apinto/node/fasthttp-client" - - eoscContext "github.com/eolinker/eosc/eocontext" - http_service "github.com/eolinker/eosc/eocontext/http-context" - "github.com/valyala/fasthttp" -) - -var _ http_service.IHttpContext = (*HttpContextCopy)(nil) - -// HttpContextCopy fasthttpRequestCtx -type HttpContextCopy struct { - proxyRequest ProxyRequest - proxyRequests []http_service.IProxy - requestID string - response Response - requestReader RequestReader - ctx context.Context - completeHandler eoscContext.CompleteHandler - finishHandler eoscContext.FinishHandler - app eoscContext.EoApp - balance eoscContext.BalanceHandler - upstreamHostHandler eoscContext.UpstreamHostHandler - labels map[string]string - port int - - localIP net.IP - netAddr net.Addr - acceptTime time.Time -} - -func (ctx *HttpContextCopy) GetUpstreamHostHandler() eoscContext.UpstreamHostHandler { - return ctx.upstreamHostHandler -} - -func (ctx *HttpContextCopy) SetUpstreamHostHandler(handler eoscContext.UpstreamHostHandler) { - ctx.upstreamHostHandler = handler -} - -func (ctx *HttpContextCopy) LocalIP() net.IP { - return ctx.localIP -} - -func (ctx *HttpContextCopy) LocalAddr() net.Addr { - return ctx.netAddr -} - -func (ctx *HttpContextCopy) LocalPort() int { - return ctx.port -} - -func (ctx *HttpContextCopy) GetApp() eoscContext.EoApp { - return ctx.app -} - -func (ctx *HttpContextCopy) SetApp(app eoscContext.EoApp) { - ctx.app = app -} - -func (ctx *HttpContextCopy) GetBalance() eoscContext.BalanceHandler { - return ctx.balance -} - -func (ctx *HttpContextCopy) SetBalance(handler eoscContext.BalanceHandler) { - ctx.balance = handler -} - -func (ctx *HttpContextCopy) SetLabel(name, value string) { - ctx.labels[name] = value -} - -func (ctx *HttpContextCopy) GetLabel(name string) string { - return ctx.labels[name] -} - -func (ctx *HttpContextCopy) Labels() map[string]string { - return ctx.labels -} - -func (ctx *HttpContextCopy) GetComplete() eoscContext.CompleteHandler { - return ctx.completeHandler -} - -func (ctx *HttpContextCopy) SetCompleteHandler(handler eoscContext.CompleteHandler) { - ctx.completeHandler = handler -} - -func (ctx *HttpContextCopy) GetFinish() eoscContext.FinishHandler { - return ctx.finishHandler -} - -func (ctx *HttpContextCopy) SetFinish(handler eoscContext.FinishHandler) { - ctx.finishHandler = handler -} - -func (ctx *HttpContextCopy) Scheme() string { - return string(ctx.requestReader.req.URI().Scheme()) -} - -func (ctx *HttpContextCopy) Assert(i interface{}) error { - if v, ok := i.(*http_service.IHttpContext); ok { - *v = ctx - return nil - } - return fmt.Errorf("not suport:%s", config.TypeNameOf(i)) -} - -func (ctx *HttpContextCopy) Proxies() []http_service.IProxy { - return ctx.proxyRequests -} - -func (ctx *HttpContextCopy) Response() http_service.IResponse { - return &ctx.response -} - -func (ctx *HttpContextCopy) SendTo(address string, timeout time.Duration) error { - - scheme, host := readAddress(address) - request := ctx.proxyRequest.Request() - - passHost, targetHost := ctx.GetUpstreamHostHandler().PassHost() - switch passHost { - case eoscContext.PassHost: - case eoscContext.NodeHost: - request.URI().SetHost(host) - case eoscContext.ReWriteHost: - request.URI().SetHost(targetHost) - } - - beginTime := time.Now() - ctx.response.responseError = fasthttp_client.ProxyTimeout(address, request, ctx.response.Response, timeout) - agent := newRequestAgent(&ctx.proxyRequest, host, scheme, beginTime, time.Now()) - if ctx.response.responseError != nil { - agent.setStatusCode(504) - } else { - agent.setStatusCode(ctx.response.Response.StatusCode()) - } - - agent.setResponseLength(ctx.response.Response.Header.ContentLength()) - - ctx.proxyRequests = append(ctx.proxyRequests, agent) - return ctx.response.responseError - -} - -func (ctx *HttpContextCopy) Context() context.Context { - if ctx.ctx == nil { - - ctx.ctx = context.Background() - } - return ctx.ctx -} - -func (ctx *HttpContextCopy) AcceptTime() time.Time { - return ctx.acceptTime -} - -func (ctx *HttpContextCopy) Value(key interface{}) interface{} { - return ctx.Context().Value(key) -} - -func (ctx *HttpContextCopy) WithValue(key, val interface{}) { - ctx.ctx = context.WithValue(ctx.Context(), key, val) -} - -func (ctx *HttpContextCopy) Proxy() http_service.IRequest { - return &ctx.proxyRequest -} - -func (ctx *HttpContextCopy) Request() http_service.IRequestReader { - - return &ctx.requestReader -} - -func (ctx *HttpContextCopy) IsCloneable() bool { - return false -} - -func (ctx *HttpContextCopy) Clone() (eoscContext.EoContext, error) { - return nil, fmt.Errorf("%s %w", "HttpContextCopy", eoscContext.ErrEoCtxUnCloneable) -} - -// NewContextCopy 创建Context-Copy -func NewContextCopy(requestCtx *fasthttp.RequestCtx, requestID string, port int, labels map[string]string) *HttpContextCopy { - ctxCopy := pool.Get().(*HttpContextCopy) - remoteAddr := requestCtx.RemoteAddr().String() - - cloneReq := fasthttp.AcquireRequest() - requestCtx.Request.CopyTo(cloneReq) - cloneResp := fasthttp.AcquireResponse() - requestCtx.Response.CopyTo(cloneResp) - - ctxCopy.requestReader.reset(cloneReq, remoteAddr) - ctxCopy.proxyRequest.reset(cloneReq, remoteAddr) - ctxCopy.proxyRequests = ctxCopy.proxyRequests[:0] - ctxCopy.response.reset(cloneResp) - - ctxCopy.localIP = requestCtx.LocalIP() - ctxCopy.netAddr = requestCtx.LocalAddr() - ctxCopy.acceptTime = requestCtx.Time() - - ctxCopy.requestID = requestID - ctxCopy.port = port - - ctxCopy.ctx = context.Background() - ctxCopy.WithValue("request_time", ctxCopy.acceptTime) - - cLabels := make(map[string]string, len(labels)) - for k, v := range labels { - cLabels[k] = v - } - ctxCopy.labels = cLabels - - return ctxCopy - -} - -// RequestId 请求ID -func (ctx *HttpContextCopy) RequestId() string { - return ctx.requestID -} - -// FastFinish finish -func (ctx *HttpContextCopy) FastFinish() { - if ctx.response.responseError != nil { - ctx.response.Response.SetStatusCode(504) - ctx.response.Response.SetBodyString(ctx.response.responseError.Error()) - return - } - - ctx.port = 0 - ctx.ctx = nil - ctx.app = nil - ctx.balance = nil - ctx.upstreamHostHandler = nil - ctx.finishHandler = nil - ctx.completeHandler = nil - - ctx.requestReader.Finish() - ctx.proxyRequest.Finish() - ctx.response.Finish() - pool.Put(ctx) - return -} - -func NotFound(ctx *HttpContextCopy) { - ctx.response.Response.SetStatusCode(404) - ctx.response.Response.SetBody([]byte("404 Not Found")) -} - -func readAddress(addr string) (scheme, host string) { - if i := strings.Index(addr, "://"); i > 0 { - return strings.ToLower(addr[:i]), addr[i+3:] - } - return "http", addr -} diff --git a/node/http-context/http-context-copy/error.go b/node/http-context/http-context-copy/error.go deleted file mode 100644 index 4b1b9264..00000000 --- a/node/http-context/http-context-copy/error.go +++ /dev/null @@ -1,10 +0,0 @@ -package http_context_copy - -import "errors" - -var ( - ErrorNotForm = errors.New("contentType is not Form") - ErrorNotMultipart = errors.New("contentType is not Multipart") - ErrorNotAllowRaw = errors.New("contentType is not allow Raw") - ErrorNotSend = errors.New("not send") -) diff --git a/node/http-context/http-context-copy/header.go b/node/http-context/http-context-copy/header.go deleted file mode 100644 index c24159de..00000000 --- a/node/http-context/http-context-copy/header.go +++ /dev/null @@ -1,140 +0,0 @@ -package http_context_copy - -import ( - "bytes" - "net/http" - "strings" - - http_service "github.com/eolinker/eosc/eocontext/http-context" - - "github.com/valyala/fasthttp" -) - -var _ http_service.IHeaderWriter = (*RequestHeader)(nil) - -type RequestHeader struct { - header *fasthttp.RequestHeader - tmp http.Header -} - -func (h *RequestHeader) RawHeader() string { - return h.header.String() -} - -func (h *RequestHeader) reset(header *fasthttp.RequestHeader) { - h.header = header - h.tmp = nil -} - -func (h *RequestHeader) initHeader() { - if h.tmp == nil { - h.tmp = make(http.Header) - h.header.VisitAll(func(key, value []byte) { - bytes.SplitN(value, []byte(":"), 2) - h.tmp[string(key)] = []string{string(value)} - }) - } -} - -func (h *RequestHeader) Host() string { - return string(h.header.Host()) -} - -func (h *RequestHeader) GetHeader(name string) string { - return h.Headers().Get(name) -} - -func (h *RequestHeader) Headers() http.Header { - h.initHeader() - return h.tmp -} - -func (h *RequestHeader) SetHeader(key, value string) { - if h.tmp != nil { - h.tmp.Set(key, value) - } - h.header.Set(key, value) -} - -func (h *RequestHeader) AddHeader(key, value string) { - if h.tmp != nil { - h.tmp.Add(key, value) - } - h.header.Add(key, value) -} - -func (h *RequestHeader) DelHeader(key string) { - if h.tmp != nil { - h.tmp.Del(key) - } - h.header.Del(key) -} - -func (h *RequestHeader) SetHost(host string) { - if h.tmp != nil { - h.tmp.Set("Host", host) - } - h.header.SetHost(host) -} - -type ResponseHeader struct { - header *fasthttp.ResponseHeader - tmp http.Header -} - -func (r *ResponseHeader) reset(header *fasthttp.ResponseHeader) { - r.header = header - r.tmp = nil -} -func NewResponseHeader(header *fasthttp.ResponseHeader) *ResponseHeader { - return &ResponseHeader{header: header} -} - -func (r *ResponseHeader) GetHeader(name string) string { - return r.Headers().Get(name) -} - -func (r *ResponseHeader) Headers() http.Header { - - if r.tmp == nil { - r.tmp = make(http.Header) - hs := strings.Split(r.header.String(), "\r\n") - for _, t := range hs { - vs := strings.Split(t, ":") - if len(vs) < 2 { - if vs[0] == "" { - continue - } - r.tmp[vs[0]] = []string{""} - continue - } - r.tmp[vs[0]] = []string{strings.TrimSpace(vs[1])} - } - } - return r.tmp -} - -func (r *ResponseHeader) SetHeader(key, value string) { - if r.tmp != nil { - r.tmp.Set(key, value) - } - r.header.Set(key, value) -} - -func (r *ResponseHeader) AddHeader(key, value string) { - if r.tmp != nil { - r.tmp.Add(key, value) - } - r.header.Add(key, value) -} - -func (r *ResponseHeader) DelHeader(key string) { - if r.tmp != nil { - r.tmp.Del(key) - } - r.header.Del(key) -} - -func (h *RequestHeader) GetCookie(key string) string { - return string(h.header.Cookie(key)) -} diff --git a/node/http-context/http-context-copy/pool.go b/node/http-context/http-context-copy/pool.go deleted file mode 100644 index d4600acc..00000000 --- a/node/http-context/http-context-copy/pool.go +++ /dev/null @@ -1,19 +0,0 @@ -package http_context_copy - -import ( - "sync" - - http_service "github.com/eolinker/eosc/eocontext/http-context" -) - -var ( - pool = sync.Pool{ - New: newContext, - } -) - -func newContext() interface{} { - h := new(HttpContextCopy) - h.proxyRequests = make([]http_service.IProxy, 0, 5) - return h -} diff --git a/node/http-context/http-context-copy/proxy-agent.go b/node/http-context/http-context-copy/proxy-agent.go deleted file mode 100644 index 09343f1e..00000000 --- a/node/http-context/http-context-copy/proxy-agent.go +++ /dev/null @@ -1,89 +0,0 @@ -package http_context_copy - -import ( - "strconv" - "time" - - http_service "github.com/eolinker/eosc/eocontext/http-context" -) - -var _ http_service.IProxy = (*requestAgent)(nil) - -type requestAgent struct { - http_service.IRequest - host string - scheme string - statusCode int - status string - responseLength int - beginTime time.Time - endTime time.Time - hostAgent *UrlAgent -} - -func (a *requestAgent) ProxyTime() time.Time { - return a.beginTime -} - -func (a *requestAgent) StatusCode() int { - return a.statusCode -} - -func (a *requestAgent) Status() string { - return a.status -} - -func (a *requestAgent) setStatusCode(code int) { - a.statusCode = code - a.status = strconv.Itoa(code) -} - -func (a *requestAgent) ResponseLength() int { - return a.responseLength -} - -func (a *requestAgent) setResponseLength(length int) { - if length > 0 { - a.responseLength = length - } -} - -func newRequestAgent(IRequest http_service.IRequest, host string, scheme string, beginTime, endTime time.Time) *requestAgent { - return &requestAgent{IRequest: IRequest, host: host, scheme: scheme, beginTime: beginTime, endTime: endTime} -} - -func (a *requestAgent) ResponseTime() int64 { - return a.endTime.Sub(a.beginTime).Milliseconds() -} - -func (a *requestAgent) URI() http_service.IURIWriter { - if a.hostAgent == nil { - a.hostAgent = NewUrlAgent(a.IRequest.URI(), a.host, a.scheme) - } - return a.hostAgent -} - -type UrlAgent struct { - http_service.IURIWriter - host string - scheme string -} - -func (u *UrlAgent) SetScheme(scheme string) { - u.scheme = scheme -} -func (u *UrlAgent) Scheme() string { - return u.scheme -} - -func (u *UrlAgent) Host() string { - return u.host -} - -func (u *UrlAgent) SetHost(host string) { - u.host = host -} - -func NewUrlAgent(IURIWriter http_service.IURIWriter, host string, scheme string) *UrlAgent { - return &UrlAgent{IURIWriter: IURIWriter, host: host, scheme: scheme} -} diff --git a/node/http-context/http-context-copy/proxy.go b/node/http-context/http-context-copy/proxy.go deleted file mode 100644 index 871becbb..00000000 --- a/node/http-context/http-context-copy/proxy.go +++ /dev/null @@ -1,72 +0,0 @@ -package http_context_copy - -import ( - "bytes" - "fmt" - - http_service "github.com/eolinker/eosc/eocontext/http-context" - "github.com/valyala/fasthttp" -) - -var _ http_service.IRequest = (*ProxyRequest)(nil) - -type ProxyRequest struct { - RequestReader -} - -//func (r *ProxyRequest) clone() *ProxyRequest { -// return NewProxyRequest(r.Request(), r.remoteAddr) -//} - -func (r *ProxyRequest) Finish() error { - fasthttp.ReleaseRequest(r.req) - r.RequestReader.Finish() - return nil -} -func (r *ProxyRequest) Header() http_service.IHeaderWriter { - return &r.headers -} - -func (r *ProxyRequest) Body() http_service.IBodyDataWriter { - return &r.body -} - -func (r *ProxyRequest) URI() http_service.IURIWriter { - return &r.uri -} - -var ( - xforwardedforKey = []byte("x-forwarded-for") -) - -func (r *ProxyRequest) reset(request *fasthttp.Request, remoteAddr string) { - proxyRequest := fasthttp.AcquireRequest() - request.CopyTo(proxyRequest) - - forwardedFor := proxyRequest.Header.PeekBytes(xforwardedforKey) - if len(forwardedFor) > 0 { - if i := bytes.IndexByte(forwardedFor, ','); i > 0 { - r.realIP = string(forwardedFor[:i]) - } else { - r.realIP = string(forwardedFor) - } - proxyRequest.Header.Set("x-forwarded-for", fmt.Sprint(string(forwardedFor), ",", r.remoteAddr)) - } else { - proxyRequest.Header.Set("x-forwarded-for", r.remoteAddr) - r.realIP = r.remoteAddr - } - - r.RequestReader.reset(proxyRequest, remoteAddr) -} - -//func NewProxyRequest(request *fasthttp.Request, remoteAddr string) *ProxyRequest { -// proxyRequest := fasthttp.AcquireRequest() -// request.CopyTo(proxyRequest) -// return &ProxyRequest{ -// RequestReader: NewRequestReader(proxyRequest, remoteAddr), -// } -//} - -func (r *ProxyRequest) SetMethod(s string) { - r.Request().Header.SetMethod(s) -} diff --git a/node/http-context/http-context-copy/request-reader.go b/node/http-context/http-context-copy/request-reader.go deleted file mode 100644 index ebc442f1..00000000 --- a/node/http-context/http-context-copy/request-reader.go +++ /dev/null @@ -1,104 +0,0 @@ -package http_context_copy - -import ( - "strings" - - http_service "github.com/eolinker/eosc/eocontext/http-context" - - "github.com/valyala/fasthttp" -) - -var _ http_service.IRequestReader = (*RequestReader)(nil) - -type RequestReader struct { - body BodyRequestHandler - req *fasthttp.Request - headers RequestHeader - uri URIRequest - remoteAddr string - remotePort string - realIP string - length int -} - -func (r *RequestReader) ContentLength() int { - return r.length -} - -func (r *RequestReader) ContentType() string { - return string(r.req.Header.ContentType()) -} - -func (r *RequestReader) String() string { - return r.req.String() -} - -func (r *RequestReader) Method() string { - return string(r.req.Header.Method()) -} - -func (r *RequestReader) Header() http_service.IHeaderReader { - return &r.headers -} - -func (r *RequestReader) Body() http_service.IBodyDataReader { - return &r.body -} - -func (r *RequestReader) URI() http_service.IURIReader { - return &r.uri -} - -func (r *RequestReader) ReadIP() string { - if r.realIP == "" { - realIP := r.headers.GetHeader("x-real-ip") - if realIP == "" { - realIP = r.remoteAddr - } - r.realIP = realIP - } - return r.realIP -} - -func (r *RequestReader) ForwardIP() string { - return r.headers.GetHeader("x-forwarded-for") -} - -func (r *RequestReader) RemoteAddr() string { - return r.remoteAddr -} - -func (r *RequestReader) RemotePort() string { - return r.remotePort -} -func (r *RequestReader) Finish() error { - r.req = nil - r.body.reset(nil) - r.headers.reset(nil) - r.uri.reset(nil) - return nil -} -func (r *RequestReader) reset(req *fasthttp.Request, remoteAddr string) { - r.req = req - r.remoteAddr = remoteAddr - - r.body.reset(req) - - r.headers.reset(&req.Header) - r.uri.uri = req.URI() - - idx := strings.LastIndex(remoteAddr, ":") - if idx != -1 { - r.remoteAddr = remoteAddr[:idx] - r.remotePort = remoteAddr[idx+1:] - } - length := r.req.Header.ContentLength() - if length > 0 { - r.length = length - } - -} - -func (r *RequestReader) Request() *fasthttp.Request { - return r.req -} diff --git a/node/http-context/http-context-copy/response.go b/node/http-context/http-context-copy/response.go deleted file mode 100644 index 124c517f..00000000 --- a/node/http-context/http-context-copy/response.go +++ /dev/null @@ -1,121 +0,0 @@ -package http_context_copy - -import ( - "strconv" - "strings" - "time" - - http_service "github.com/eolinker/eosc/eocontext/http-context" - - "github.com/valyala/fasthttp" -) - -var _ http_service.IResponse = (*Response)(nil) - -type Response struct { - ResponseHeader - *fasthttp.Response - length int - responseTime time.Duration - proxyStatusCode int - responseError error -} - -func (r *Response) ContentLength() int { - if r.length == 0 { - return r.Response.Header.ContentLength() - } - return r.length -} - -func (r *Response) ContentType() string { - return string(r.Response.Header.ContentType()) -} - -func (r *Response) HeadersString() string { - return r.header.String() -} - -func (r *Response) ResponseError() error { - return r.responseError -} - -func (r *Response) ClearError() { - r.responseError = nil -} -func (r *Response) Finish() error { - r.Response = nil - r.ResponseHeader.reset(nil) - r.responseError = nil - r.proxyStatusCode = 0 - return nil -} -func (r *Response) reset(resp *fasthttp.Response) { - r.Response = resp - r.ResponseHeader.reset(&resp.Header) - r.responseError = nil - r.proxyStatusCode = 0 - -} - -func (r *Response) BodyLen() int { - return r.header.ContentLength() -} - -func (r *Response) GetBody() []byte { - if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") { - body, _ := r.BodyGunzip() - r.DelHeader("Content-Encoding") - r.SetHeader("Content-Length", strconv.Itoa(len(body))) - r.Response.SetBody(body) - } - - return r.Response.Body() -} - -func (r *Response) SetBody(bytes []byte) { - if strings.Contains(r.GetHeader("Content-Encoding"), "gzip") { - r.DelHeader("Content-Encoding") - } - r.Response.SetBody(bytes) - r.length = len(bytes) - r.SetHeader("Content-Length", strconv.Itoa(r.length)) - r.responseError = nil -} - -func (r *Response) StatusCode() int { - if r.responseError != nil { - return 504 - } - return r.Response.StatusCode() -} - -func (r *Response) Status() string { - return strconv.Itoa(r.StatusCode()) -} - -func (r *Response) SetStatus(code int, status string) { - r.Response.SetStatusCode(code) - r.responseError = nil -} - -// 原始的响应状态码 -func (r *Response) ProxyStatusCode() int { - return r.proxyStatusCode -} - -func (r *Response) ProxyStatus() string { - return strconv.Itoa(r.proxyStatusCode) -} - -func (r *Response) SetProxyStatus(code int, status string) { - r.proxyStatusCode = code -} - -func (r *Response) SetResponseTime(t time.Duration) { - r.responseTime = t -} - -func (r *Response) ResponseTime() time.Duration { - return r.responseTime -} diff --git a/node/http-context/http-context-copy/uri.go b/node/http-context/http-context-copy/uri.go deleted file mode 100644 index 7ab1a150..00000000 --- a/node/http-context/http-context-copy/uri.go +++ /dev/null @@ -1,82 +0,0 @@ -package http_context_copy - -import ( - http_service "github.com/eolinker/eosc/eocontext/http-context" - "github.com/valyala/fasthttp" -) - -var _ http_service.IURIWriter = (*URIRequest)(nil) - -type URIRequest struct { - uri *fasthttp.URI -} - -func (ur *URIRequest) reset(uri *fasthttp.URI) { - ur.uri = uri -} -func (ur *URIRequest) Path() string { - return string(ur.uri.Path()) -} - -func (ur *URIRequest) SetScheme(scheme string) { - ur.uri.SetScheme(scheme) -} - -func (ur *URIRequest) Host() string { - return string(ur.uri.Host()) -} - -func (ur *URIRequest) SetQuery(key, value string) { - - ur.uri.QueryArgs().Set(key, value) -} - -func (ur *URIRequest) AddQuery(key, value string) { - ur.uri.QueryArgs().Add(key, value) -} - -func (ur *URIRequest) DelQuery(key string) { - queryArgs := ur.uri.QueryArgs() - queryArgs.Del(key) - if queryArgs.Len() == 0 { - ur.uri.SetQueryStringBytes(nil) - } -} - -func (ur *URIRequest) SetRawQuery(raw string) { - ur.uri.SetQueryString(raw) -} - -func NewURIRequest(uri *fasthttp.URI) *URIRequest { - return &URIRequest{uri: uri} -} - -func (ur *URIRequest) RequestURI() string { - return string(ur.uri.RequestURI()) -} - -func (ur *URIRequest) Scheme() string { - return string(ur.uri.Scheme()) -} - -func (ur *URIRequest) RawURL() string { - return string(ur.uri.FullURI()) -} - -func (ur *URIRequest) GetQuery(key string) string { - - return string(ur.uri.QueryArgs().Peek(key)) -} - -func (ur *URIRequest) RawQuery() string { - return string(ur.uri.QueryString()) -} - -func (ur *URIRequest) SetPath(s string) { - ur.uri.SetPath(s) - -} - -func (ur *URIRequest) SetHost(host string) { - ur.uri.SetHost(host) -} diff --git a/node/http-context/proxy.go b/node/http-context/proxy.go index e06cff7e..e4f8fb68 100644 --- a/node/http-context/proxy.go +++ b/node/http-context/proxy.go @@ -3,6 +3,7 @@ package http_context import ( "bytes" "fmt" + "github.com/eolinker/eosc/log" http_service "github.com/eolinker/eosc/eocontext/http-context" "github.com/valyala/fasthttp" @@ -20,7 +21,10 @@ type ProxyRequest struct { func (r *ProxyRequest) Finish() error { fasthttp.ReleaseRequest(r.req) - r.RequestReader.Finish() + err := r.RequestReader.Finish() + if err != nil { + log.Warn(err) + } return nil } func (r *ProxyRequest) Header() http_service.IHeaderWriter { From 81dbcdca32348f08543425901abcf9737fe9cea9 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Thu, 2 Mar 2023 20:18:09 +0800 Subject: [PATCH 21/25] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/mirror-node.go | 83 ------------------- .../plugins/proxy-mirror/mirror-service.go | 8 +- drivers/router/http-router/http-handler.go | 4 +- node/dubbo2-context/context.go | 5 +- node/grpc-context/context.go | 5 +- node/http-context/clone.go | 2 - node/http-context/context.go | 24 ++++-- node/http-context/pool.go | 9 ++ node/http-context/websocket-context.go | 5 +- 9 files changed, 36 insertions(+), 109 deletions(-) delete mode 100644 drivers/plugins/proxy-mirror/mirror-node.go diff --git a/drivers/plugins/proxy-mirror/mirror-node.go b/drivers/plugins/proxy-mirror/mirror-node.go deleted file mode 100644 index 6149f6c7..00000000 --- a/drivers/plugins/proxy-mirror/mirror-node.go +++ /dev/null @@ -1,83 +0,0 @@ -package proxy_mirror - -import ( - "errors" - "fmt" - "github.com/eolinker/eosc/eocontext" -) - -var ( - errNoValidNode = errors.New("no valid node") -) - -type node struct { - labels eocontext.Attrs - id string - ip string - port int - status eocontext.NodeStatus -} - -// newNode 创建新节点 -func newNode(labels map[string]string, id string, ip string, port int) eocontext.INode { - return &node{labels: labels, id: id, ip: ip, port: port, status: eocontext.Running} -} - -// GetAttrs 获取节点属性集合 -func (n *node) GetAttrs() eocontext.Attrs { - return n.labels -} - -// GetAttrByName 通过属性名获取节点属性 -func (n *node) GetAttrByName(name string) (string, bool) { - v, ok := n.labels[name] - return v, ok -} - -// IP 返回节点IP -func (n *node) IP() string { - return n.ip -} - -// Port 返回节点端口 -func (n *node) Port() int { - return n.port -} - -// ID 返回节点ID -func (n *node) ID() string { - return n.id -} - -// Status 返回节点状态 -func (n *node) Status() eocontext.NodeStatus { - return n.status -} - -// Labels 返回节点标签集合 -func (n *node) Labels() map[string]string { - return n.labels -} - -// Addr 返回节点地址 -func (n *node) Addr() string { - if n.port == 0 { - return n.ip - } - return fmt.Sprintf("%s:%d", n.ip, n.port) -} - -// Up 将节点状态置为运行中 -func (n *node) Up() { - n.status = eocontext.Running -} - -// Down 将节点状态置为不可用 -func (n *node) Down() { - n.status = eocontext.Down -} - -// Leave 将节点状态置为离开 -func (n *node) Leave() { - n.status = eocontext.Leave -} diff --git a/drivers/plugins/proxy-mirror/mirror-service.go b/drivers/plugins/proxy-mirror/mirror-service.go index 99bf0e18..593335e6 100644 --- a/drivers/plugins/proxy-mirror/mirror-service.go +++ b/drivers/plugins/proxy-mirror/mirror-service.go @@ -1,13 +1,19 @@ package proxy_mirror import ( + "errors" "fmt" + "github.com/eolinker/apinto/discovery" "github.com/eolinker/eosc/eocontext" "strconv" "strings" "time" ) +var ( + errNoValidNode = errors.New("no valid node") +) + type mirrorService struct { scheme string passHost eocontext.PassHostMod @@ -32,7 +38,7 @@ func newMirrorService(target, passHost, host string, timeout time.Duration) *mir port, _ = strconv.Atoi(portStr) } - inode := newNode(labels, fmt.Sprintf("%s:%d", ip, port), ip, port) + inode := discovery.NewNode(labels, fmt.Sprintf("%s:%d", ip, port), ip, port) var mode eocontext.PassHostMod switch passHost { diff --git a/drivers/router/http-router/http-handler.go b/drivers/router/http-router/http-handler.go index fa1fba5e..fb6559d0 100644 --- a/drivers/router/http-router/http-handler.go +++ b/drivers/router/http-router/http-handler.go @@ -54,8 +54,8 @@ func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) { ctx = wsCtx } //set retry timeout - ctx.WithValue(http_complete.KeyHttpRetry, h.retry) - ctx.WithValue(http_complete.KeyHttpTimeout, h.timeout) + ctx.WithValue(http_context.KeyHttpRetry, h.retry) + ctx.WithValue(http_context.KeyHttpTimeout, h.timeout) //Set Label ctx.SetLabel("api", h.routerName) diff --git a/node/dubbo2-context/context.go b/node/dubbo2-context/context.go index 96022480..29fc996f 100644 --- a/node/dubbo2-context/context.go +++ b/node/dubbo2-context/context.go @@ -295,11 +295,8 @@ func (d *DubboContext) IsCloneable() bool { } func (d *DubboContext) Clone() (eocontext.EoContext, error) { - if !d.IsCloneable() { - return nil, fmt.Errorf("%s %w", "DubboContext", eocontext.ErrEoCtxUnCloneable) - } //TODO - return nil, nil + return nil, fmt.Errorf("%s %w", "DubboContext", eocontext.ErrEoCtxUnCloneable) } func addrToIP(addr net.Addr) net.IP { diff --git a/node/grpc-context/context.go b/node/grpc-context/context.go index 75a24685..99644c45 100644 --- a/node/grpc-context/context.go +++ b/node/grpc-context/context.go @@ -266,9 +266,6 @@ func (c *Context) IsCloneable() bool { } func (c *Context) Clone() (eocontext.EoContext, error) { - if !c.IsCloneable() { - return nil, fmt.Errorf("%s %w", "GrpcContext", eocontext.ErrEoCtxUnCloneable) - } //TODO - return nil, nil + return nil, fmt.Errorf("%s %w", "GrpcContext", eocontext.ErrEoCtxUnCloneable) } diff --git a/node/http-context/clone.go b/node/http-context/clone.go index ee265790..ea7aa54f 100644 --- a/node/http-context/clone.go +++ b/node/http-context/clone.go @@ -179,9 +179,7 @@ func (ctx *cloneContext) IsCloneable() bool { } func (ctx *cloneContext) Clone() (eoscContext.EoContext, error) { - return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) - } var copyKey = struct{}{} diff --git a/node/http-context/context.go b/node/http-context/context.go index 12ad533d..27e92fda 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -13,7 +13,7 @@ import ( eoscContext "github.com/eolinker/eosc/eocontext" http_service "github.com/eolinker/eosc/eocontext/http-context" - uuid "github.com/google/uuid" + "github.com/google/uuid" "github.com/valyala/fasthttp" ) @@ -185,19 +185,25 @@ func (ctx *HttpContext) IsCloneable() bool { } func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { - - copyContext := &cloneContext{ - org: ctx, - proxyRequests: make([]http_service.IProxy, 0, 5), - } + copyContext := copyPool.Get().(*cloneContext) + copyContext.org = ctx + copyContext.proxyRequests = make([]http_service.IProxy, 0, 2) req := fasthttp.AcquireRequest() ctx.fastHttpRequestCtx.Request.CopyTo(req) copyContext.proxyRequest.reset(req, ctx.requestReader.remoteAddr) - copyContext.proxyRequests = copyContext.proxyRequests[:0] - - copyContext.labels = ctx.Labels() + copyContext.app = ctx.app + copyContext.balance = ctx.balance + copyContext.upstreamHostHandler = ctx.upstreamHostHandler + copyContext.completeHandler = ctx.completeHandler + copyContext.finishHandler = ctx.finishHandler + + cloneLabels := make(map[string]string, len(ctx.labels)) + for k, v := range ctx.labels { + cloneLabels[k] = v + } + copyContext.labels = cloneLabels //记录请求时间 copyContext.ctx = context.WithValue(ctx.Context(), copyKey, true) diff --git a/node/http-context/pool.go b/node/http-context/pool.go index 4fdf83c9..b3346637 100644 --- a/node/http-context/pool.go +++ b/node/http-context/pool.go @@ -10,6 +10,9 @@ var ( pool = sync.Pool{ New: newContext, } + copyPool = sync.Pool{ + New: newCopyContext, + } ) func newContext() interface{} { @@ -17,3 +20,9 @@ func newContext() interface{} { h.proxyRequests = make([]http_service.IProxy, 0, 5) return h } + +func newCopyContext() interface{} { + h := new(cloneContext) + h.proxyRequests = make([]http_service.IProxy, 0, 5) + return h +} diff --git a/node/http-context/websocket-context.go b/node/http-context/websocket-context.go index 6004b4bc..1b7417f3 100644 --- a/node/http-context/websocket-context.go +++ b/node/http-context/websocket-context.go @@ -86,9 +86,6 @@ func (w *WebsocketContext) IsCloneable() bool { } func (w *WebsocketContext) Clone() (eoscContext.EoContext, error) { - if !w.IsCloneable() { - return nil, fmt.Errorf("%s %w", "WebsocketContext", eoscContext.ErrEoCtxUnCloneable) - } //TODO - return nil, nil + return nil, fmt.Errorf("%s %w", "WebsocketContext", eoscContext.ErrEoCtxUnCloneable) } From 144bb17ab63b849dcce4a3a714da7ebb0d3f4030 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Fri, 3 Mar 2023 09:59:46 +0800 Subject: [PATCH 22/25] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apinto/worker.go | 3 +++ drivers/plugins/proxy-mirror/config.go | 2 +- drivers/plugins/proxy-mirror/error.go | 1 - node/http-context/context.go | 4 +--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/apinto/worker.go b/app/apinto/worker.go index e94d25a7..c838ae5b 100644 --- a/app/apinto/worker.go +++ b/app/apinto/worker.go @@ -31,6 +31,7 @@ import ( "github.com/eolinker/apinto/drivers/plugins/monitor" params_transformer "github.com/eolinker/apinto/drivers/plugins/params-transformer" prometheus_plugin "github.com/eolinker/apinto/drivers/plugins/prometheus" + proxy_mirror "github.com/eolinker/apinto/drivers/plugins/proxy-mirror" proxy_rewrite "github.com/eolinker/apinto/drivers/plugins/proxy-rewrite" proxy_rewriteV2 "github.com/eolinker/apinto/drivers/plugins/proxy_rewrite_v2" rate_limiting "github.com/eolinker/apinto/drivers/plugins/rate-limiting" @@ -146,4 +147,6 @@ func Register(extenderRegister eosc.IExtenderDriverRegister) { http_to_grpc.Register(extenderRegister) protocbuf.Register(extenderRegister) grpc_to_http.Register(extenderRegister) + + proxy_mirror.Register(extenderRegister) } diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go index 542d8274..01ae19fb 100644 --- a/drivers/plugins/proxy-mirror/config.go +++ b/drivers/plugins/proxy-mirror/config.go @@ -40,7 +40,7 @@ func (c *Config) doCheck() error { //校验镜像请求超时时间 if c.Timeout < 0 { - return errTimeout + c.Timeout = 3000 } //校验passHost diff --git a/drivers/plugins/proxy-mirror/error.go b/drivers/plugins/proxy-mirror/error.go index ae5234be..d56f72fd 100644 --- a/drivers/plugins/proxy-mirror/error.go +++ b/drivers/plugins/proxy-mirror/error.go @@ -6,7 +6,6 @@ var ( errUnsupportedContextType = errors.New("send mirror proxy fail. Unsupported Context Type") errHostNull = errors.New("host can't be null when pass_host is rewrite. ") errUnsupportedPassHost = errors.New("unsupported pass_host. ") - errTimeout = errors.New("timeout can't be smaller than 0. ") errRandomRangeNum = errors.New("random_range should be bigger than 0. ") errRandomPivotNum = errors.New("random_pivot should be bigger than 0. ") diff --git a/node/http-context/context.go b/node/http-context/context.go index 27e92fda..0c03d5ac 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -193,9 +193,7 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { ctx.fastHttpRequestCtx.Request.CopyTo(req) copyContext.proxyRequest.reset(req, ctx.requestReader.remoteAddr) - copyContext.app = ctx.app - copyContext.balance = ctx.balance - copyContext.upstreamHostHandler = ctx.upstreamHostHandler + copyContext.completeHandler = ctx.completeHandler copyContext.finishHandler = ctx.finishHandler From a41a7aa84897d03fc82eda1a9603f0fd2483abaf Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Fri, 3 Mar 2023 12:29:50 +0800 Subject: [PATCH 23/25] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/plugins/proxy-mirror/config.go | 19 +++++++++++++------ node/http-context/clone.go | 21 ++++++++++----------- node/http-context/context.go | 6 +++++- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go index 01ae19fb..b0b329c8 100644 --- a/drivers/plugins/proxy-mirror/config.go +++ b/drivers/plugins/proxy-mirror/config.go @@ -1,6 +1,9 @@ package proxy_mirror -import "github.com/eolinker/apinto/utils" +import ( + "github.com/eolinker/apinto/utils" + "strings" +) type Config struct { Addr string `json:"Addr" label:"服务地址" description:"镜像服务地址, 需要包含scheme"` @@ -26,6 +29,9 @@ func (c *Config) doCheck() error { if !utils.IsMatchSchemeIpPort(c.Addr) && !utils.IsMatchSchemeDomainPort(c.Addr) { return errAddr } + //scheme小写 + schemeIdx := strings.Index(c.Addr, "://") + c.Addr = strings.ToLower(c.Addr[:schemeIdx]) + c.Addr[schemeIdx:] //校验采样配置 if c.SampleConf.RandomRange <= 0 { @@ -53,11 +59,12 @@ func (c *Config) doCheck() error { } //校验host - if c.PassHost == modeRewrite && c.Host == "" { - return errHostNull - } - if !utils.IsMatchIpPort(c.Addr) && !utils.IsMatchDomainPort(c.Addr) { - return errAddr + if c.PassHost == modeRewrite { + if c.Host == "" { + return errHostNull + } else if !utils.IsMatchIpPort(c.Host) && !utils.IsMatchDomainPort(c.Host) { + return errAddr + } } return nil diff --git a/node/http-context/clone.go b/node/http-context/clone.go index ea7aa54f..d0573783 100644 --- a/node/http-context/clone.go +++ b/node/http-context/clone.go @@ -3,7 +3,6 @@ package http_context import ( "context" "fmt" - "github.com/valyala/fasthttp" "net" "time" @@ -19,10 +18,10 @@ var _ http_service.IHttpContext = (*cloneContext)(nil) // HttpContext fasthttpRequestCtx type cloneContext struct { - org *HttpContext - proxyRequest ProxyRequest - proxyRequests []http_service.IProxy - + org *HttpContext + proxyRequest ProxyRequest + response Response + proxyRequests []http_service.IProxy ctx context.Context completeHandler eoscContext.CompleteHandler finishHandler eoscContext.FinishHandler @@ -115,7 +114,7 @@ func (ctx *cloneContext) Proxies() []http_service.IProxy { } func (ctx *cloneContext) Response() http_service.IResponse { - return nil + return &ctx.response } func (ctx *cloneContext) SendTo(address string, timeout time.Duration) error { @@ -131,17 +130,17 @@ func (ctx *cloneContext) SendTo(address string, timeout time.Duration) error { case eoscContext.ReWriteHost: request.URI().SetHost(targetHost) } - response := fasthttp.AcquireResponse() + beginTime := time.Now() - ctx.responseError = fasthttp_client.ProxyTimeout(address, request, response, timeout) + ctx.responseError = fasthttp_client.ProxyTimeout(address, request, ctx.response.Response, timeout) agent := newRequestAgent(&ctx.proxyRequest, host, scheme, beginTime, time.Now()) if ctx.responseError != nil { agent.setStatusCode(504) } else { - agent.setStatusCode(response.StatusCode()) + agent.setStatusCode(ctx.response.Response.StatusCode()) } - agent.setResponseLength(response.Header.ContentLength()) + agent.setResponseLength(ctx.response.Response.Header.ContentLength()) ctx.proxyRequests = append(ctx.proxyRequests, agent) return ctx.responseError @@ -158,7 +157,7 @@ func (ctx *cloneContext) AcceptTime() time.Time { } func (ctx *cloneContext) Value(key interface{}) interface{} { - return ctx.org.Value(key) + return ctx.ctx.Value(key) } func (ctx *cloneContext) WithValue(key, val interface{}) { diff --git a/node/http-context/context.go b/node/http-context/context.go index 0c03d5ac..38f66891 100644 --- a/node/http-context/context.go +++ b/node/http-context/context.go @@ -192,7 +192,11 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { req := fasthttp.AcquireRequest() ctx.fastHttpRequestCtx.Request.CopyTo(req) + resp := fasthttp.AcquireResponse() + ctx.fastHttpRequestCtx.Response.CopyTo(resp) + copyContext.proxyRequest.reset(req, ctx.requestReader.remoteAddr) + copyContext.response.reset(resp) copyContext.completeHandler = ctx.completeHandler copyContext.finishHandler = ctx.finishHandler @@ -204,7 +208,7 @@ func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) { copyContext.labels = cloneLabels //记录请求时间 - copyContext.ctx = context.WithValue(ctx.Context(), copyKey, true) + copyContext.ctx = context.WithValue(ctx.Context(), http_service.KeyCloneCtx, true) copyContext.WithValue(http_service.KeyHttpRetry, 0) copyContext.WithValue(http_service.KeyHttpTimeout, time.Duration(0)) return copyContext, nil From 72aebb21e52d61531c547dc43aad72f032edcf46 Mon Sep 17 00:00:00 2001 From: chenjiekun Date: Fri, 3 Mar 2023 14:26:52 +0800 Subject: [PATCH 24/25] del copyKey --- node/http-context/clone.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/node/http-context/clone.go b/node/http-context/clone.go index d0573783..4b06ba85 100644 --- a/node/http-context/clone.go +++ b/node/http-context/clone.go @@ -181,8 +181,6 @@ func (ctx *cloneContext) Clone() (eoscContext.EoContext, error) { return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable) } -var copyKey = struct{}{} - // RequestId 请求ID func (ctx *cloneContext) RequestId() string { return ctx.org.requestID From f1613e0c19e655070baaa354b1a7c593fd1fa022 Mon Sep 17 00:00:00 2001 From: Liujian <824010343@qq.com> Date: Fri, 3 Mar 2023 21:21:33 +0800 Subject: [PATCH 25/25] =?UTF-8?q?=E4=BF=AE=E6=94=B9go.mod=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=EF=BC=8C=E4=BF=AE=E6=94=B9readme=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CODE_OF_CONDUCT.md | 155 ++++++++++++++----------------------- CODE_OF_CONDUCT_CN.md | 94 ---------------------- CODE_OF_CONDUCT_EN.md | 133 +++++++++++++++++++++++++++++++ README.md | 176 ++++++++++++++++++++++-------------------- README_CN.md | 136 -------------------------------- README_EN.md | 126 ++++++++++++++++++++++++++++++ go.mod | 4 +- 7 files changed, 412 insertions(+), 412 deletions(-) delete mode 100644 CODE_OF_CONDUCT_CN.md create mode 100644 CODE_OF_CONDUCT_EN.md delete mode 100644 README_CN.md create mode 100644 README_EN.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e96b1b22..4e2bfd25 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,133 +1,94 @@ +## 贡献者契约行为准则 -# Contributor Covenant Code of Conduct +### 我们的承诺 -## Our Pledge +--- -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. +作为成员、贡献者和领导者,我们承诺让每个人参与我们的社区成为一种无骚扰的体验,无论年龄、体型、可见或不可见的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度如何、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性身份和性取向。 -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +我们承诺通过建立一个开有助于开放、热情、多元化、包容和健康的社区的方式采取行动和互动。 -## Our Standards +### 我们的标准 -Examples of behavior that contributes to a positive environment for our -community include: +--- -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community +为我们的社区营造积极环境的行为示例包括: -Examples of unacceptable behavior include: +* 表现出对他人的同理心和善意 +* 尊重不同的意见、观点和经验 +* 给予并优雅地接受建设性的反馈 +* 承担责任并向受我们错误影响的人道歉,并从经验中学习 +* 关注那些对我们个人和社区都最好的事情 -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting -## Enforcement Responsibilities -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +不可接受的行为示例包括: -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +* 使用色情语言或图像,以及任何形式的性关注或性推动 +* 拖钓、侮辱或贬损评论,以及人身或政治攻击 +* 公共或私人骚扰 +* 未经他人明确许可,发布他人的私人信息,例如物理地址或电子邮件地址 +* 在专业环境中可能被合理认为不适当的其他行为 -## Scope +### 执法责任 -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. +--- -## Enforcement +社区领袖有责任澄清和执行我们可接受的行为标准,并将针对他们认为不恰当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。 -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[apinto@eolink.com]. -All complaints will be reviewed and investigated promptly and fairly. +社区领袖有权利和责任删除、编辑或拒绝与本行为准则不一致的评论、commits、代码、wiki 编辑、issues和其他贡献,并会在适当时传达审核决定的原因。 -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. +### 范围 -## Enforcement Guidelines +--- -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: +本行为准则适用于所有社区空间,包括个人在公共场所正式代表社区的时候。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在在线或离线活动中担任指定代表。 -### 1. Correction +### 执法 -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. +--- -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. +辱骂、骚扰或其他不可接受的行为可通过 [apinto@eolink.com] 向负责执法的社区领袖报告。所有投诉都将得到及时和公平的审查和调查。 -### 2. Warning +所有社区领袖都有义务尊重任何事件报告者的隐私和安全。 -**Community Impact**: A violation through a single incident or series of -actions. +### 执法指南 -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. +--- -### 3. Temporary Ban +社区领袖将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果: -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. +#### 1. 更正 -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. +**社区影响**:使用不恰当的语言或其他在社区中被认为不专业或不受欢迎的行为。 -### 4. Permanent Ban +**后果**:来自社区领袖的私下书面警告,清楚说明违规的性质,并解释行为不恰当的原因。可能会要求公开道歉。 -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. +#### 2. 警告 -**Consequence**: A permanent ban from any sort of public interaction within the -community. +**社区影响**:通过单一事件或一系列行动的违规行为。 -## Attribution +**后果**:对持续行为产生后果的警告。在指定的时间段内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区空间以及社交媒体等外部渠道中进行互动。违反这些条款可能会导致临时或永久禁令。 -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. +#### 2. 临时禁令 -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. +**社区影响**:严重违反社区标准,包括持续的不当行为。 -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. +**后果**:在指定时间段内暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与《行为准则》执行者互动。违反这些条款可能会导致永久禁令。 + +#### 4. 永久禁令 + +**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、骚扰个人,或攻击或贬低某一类个人。 + +**后果**:永久禁止在社区内进行任何形式的公共互动。 + +### 归属 + +本行为准则改编自贡献者公约2.1 版,可在 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html获得。 + +社区影响指南的灵感来自 Mozilla 的行为准则执行阶梯。 + +有关此行为准则的常见问题的答案,请参阅 https://www.contributor-covenant.org/faq上的常见问题解答。 + +翻译可在 https://www.contributor-covenant.org/translations获得。 -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/CODE_OF_CONDUCT_CN.md b/CODE_OF_CONDUCT_CN.md deleted file mode 100644 index 4e2bfd25..00000000 --- a/CODE_OF_CONDUCT_CN.md +++ /dev/null @@ -1,94 +0,0 @@ -## 贡献者契约行为准则 - -### 我们的承诺 - ---- - -作为成员、贡献者和领导者,我们承诺让每个人参与我们的社区成为一种无骚扰的体验,无论年龄、体型、可见或不可见的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度如何、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性身份和性取向。 - -我们承诺通过建立一个开有助于开放、热情、多元化、包容和健康的社区的方式采取行动和互动。 - -### 我们的标准 - ---- - -为我们的社区营造积极环境的行为示例包括: - -* 表现出对他人的同理心和善意 -* 尊重不同的意见、观点和经验 -* 给予并优雅地接受建设性的反馈 -* 承担责任并向受我们错误影响的人道歉,并从经验中学习 -* 关注那些对我们个人和社区都最好的事情 - - - -不可接受的行为示例包括: - -* 使用色情语言或图像,以及任何形式的性关注或性推动 -* 拖钓、侮辱或贬损评论,以及人身或政治攻击 -* 公共或私人骚扰 -* 未经他人明确许可,发布他人的私人信息,例如物理地址或电子邮件地址 -* 在专业环境中可能被合理认为不适当的其他行为 - -### 执法责任 - ---- - -社区领袖有责任澄清和执行我们可接受的行为标准,并将针对他们认为不恰当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。 - -社区领袖有权利和责任删除、编辑或拒绝与本行为准则不一致的评论、commits、代码、wiki 编辑、issues和其他贡献,并会在适当时传达审核决定的原因。 - -### 范围 - ---- - -本行为准则适用于所有社区空间,包括个人在公共场所正式代表社区的时候。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在在线或离线活动中担任指定代表。 - -### 执法 - ---- - -辱骂、骚扰或其他不可接受的行为可通过 [apinto@eolink.com] 向负责执法的社区领袖报告。所有投诉都将得到及时和公平的审查和调查。 - -所有社区领袖都有义务尊重任何事件报告者的隐私和安全。 - -### 执法指南 - ---- - -社区领袖将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果: - -#### 1. 更正 - -**社区影响**:使用不恰当的语言或其他在社区中被认为不专业或不受欢迎的行为。 - -**后果**:来自社区领袖的私下书面警告,清楚说明违规的性质,并解释行为不恰当的原因。可能会要求公开道歉。 - -#### 2. 警告 - -**社区影响**:通过单一事件或一系列行动的违规行为。 - -**后果**:对持续行为产生后果的警告。在指定的时间段内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区空间以及社交媒体等外部渠道中进行互动。违反这些条款可能会导致临时或永久禁令。 - -#### 2. 临时禁令 - -**社区影响**:严重违反社区标准,包括持续的不当行为。 - -**后果**:在指定时间段内暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与《行为准则》执行者互动。违反这些条款可能会导致永久禁令。 - -#### 4. 永久禁令 - -**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、骚扰个人,或攻击或贬低某一类个人。 - -**后果**:永久禁止在社区内进行任何形式的公共互动。 - -### 归属 - -本行为准则改编自贡献者公约2.1 版,可在 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html获得。 - -社区影响指南的灵感来自 Mozilla 的行为准则执行阶梯。 - -有关此行为准则的常见问题的答案,请参阅 https://www.contributor-covenant.org/faq上的常见问题解答。 - -翻译可在 https://www.contributor-covenant.org/translations获得。 - diff --git a/CODE_OF_CONDUCT_EN.md b/CODE_OF_CONDUCT_EN.md new file mode 100644 index 00000000..e96b1b22 --- /dev/null +++ b/CODE_OF_CONDUCT_EN.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[apinto@eolink.com]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/README.md b/README.md index 1f17d3c1..0484788f 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,136 @@ ## Apinto -[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/Apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) -![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png) +[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT_CN.md) -Apinto is a microservice gateway developed based on golang. It can achieve the purposes of high-performance HTTP API forwarding, multi tenant management, API access control, etc. it has a powerful user-defined plug-in system, which can be expanded by itself, and can quickly help enterprises manage API services and improve the stability and security of API services. In the future, we will provide the plug-in market. Through the strong plug-in expansion ability of **Apinto**, users can expand **Apinto** plug-ins as needed like Lego blocks to enrich **Apinto** capabilities. +------------ +![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png) -**Note**:The **main** branch is the main development branch. Frequent updates may lead to unstable use. If you need to use a stable version, please look [release](https://github.com/eolinker/apinto/releases) +Apinto是一个基于 Golang 开发的微服务网关,能够实现高性能 HTTP API 转发、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。未来我们将提供插件市场,通过**Apinto**强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展**Apinto**的插件,丰富**Apinto**的能力。 -**Apinto** integrates configuration and forwarding functions. Users can configure it through OpenAPI or through visual UI items [apinto dashboard](https://github.com/eolinker/apinto-dashboard) for configuration, click [apinto dashboard deployment document](https://help.apinto.com/docs/dashboard/quick/arrange) for relevant documents +注意:**main**分支为开发主要分支,频繁更新可能导致使用不稳定,若需要使用稳定版本,请查看[release](https://github.com/eolinker/apinto/releases) -### Summary / [中文介绍](https://github.com/eolinker/apinto/blob/main/README_CN.md) +**Apinto** 集合了配置和转发功能,使用者可以通过openAPI进行配置,也可通过可视化UI项目[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)进行配置,相关文档可点击[Apinto Dashboard部署文档](https://help.apinto.com/docs/dashboard/quick/arrange) +### 概况 | [English Introduction](https://github.com/eolinker/apinto/blob/main/README_EN.md) -- [Why Apinto](#WhyApinto "Why Apinto") -- [Feature](#Feature) -- [Benchmark](#Benchmark) -- [Deployment](#Deployment) -- [GetStart](#GetStart "Get Start") -- [Contact](#Contact) -- [About](#About) +- [为什么要使用Apinto](#为什么要使用Apinto "Apinto") +- [产品特性](#产品特性 "产品特性") +- [基准测试](#基准测试 "基准测试") +- [部署](#部署 "部署") +- [启动](#启动 "启动") +- [联系我们](#联系我们 "联系我们") +- [关于我们](#关于我们 "关于我们") +- [授权协议](#授权协议 "授权协议") -### Why Apinto +### 为什么要使用Apinto -Apinto API gateway is a microservice gateway running on the service boundary of enterprise system. When you build websites, apps, iots and even open API transactions, Apinto API gateway can help you extract duplicate components from your internal system and run them on Apinto gateway, such as user authorization, access control, firewall, data conversion, etc; Moreover, Apinto provides the function of service arrangement, so that enterprises can quickly obtain the required data from various services and realize rapid response to business. +**Apinto**是运行在企业系统服务边界上的API网关。当您构建网站、App、IOT甚至是开放API交易时,Apinto 能够帮你将内部系统中重复的组件抽取出来并放置在Apinto网关上运行,如进行用户授权、访问控制、防火墙、数据转换等;并且Apinto 提供服务编排的功能,让企业可以快速从各类服务上获取需要的数据,对业务实现快速响应。 -Apinto API gateway has the following advantages: +**Apinto**具有以下优势: -- Completely open source: the Apinto project is initiated and maintained by eolinker for a long time. We hope to work with global developers to build the infrastructure of micro service ecology. -- Excellent performance: under the same environment, Apinto is about 50% faster than nginx, Kong and other products, and its stability is also optimized. -- Rich functions: Apinto provides all the functions of a standard gateway, and you can quickly connect your micro services and manage network traffic. -- Extremely low use and maintenance cost: Apinto is an open source gateway developed in pure go language. It has no cumbersome deployment and no external product dependence. It only needs to download and run, which is extremely simple. -- Good scalability: most of Apinto's functions are modular, so you can easily expand its capabilities. +- 完全开源:Apinto 项目由 Eolinker 发起并长期维护,我们希望与全球开发者共同打造微服务生态的基础设施。 +- 优异的性能表现:相同环境下,Apinto比Nginx、Kong等产品快约50%,并且在稳定性上也有所优化。 +- 丰富的功能:Apinto 提供了一个标准网关应有的所有功能,并且你可以快速连接你的各个微服务以及管理网络流量。 +- 极低的使用和维护成本:Apinto 是纯 Go 语言开发的开源网关,没有繁琐的部署,没有外部产品依赖,只需要下载并运行即可,极为简单。 +- 良好的扩展性:Apinto 的绝大部分功能都是模块化的,因此你可以很容易扩展它的能力。 -In a word, Apinto API gateway enables the business development team to focus more on business implementation. +总而言之,Apinto 能让业务开发团队更加专注地实现业务。 -### Star History +### Star 历史 [![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date) -### Feture - -| Feture | Description | -| --------------------- | ------------------------------------------------------------ | -| Dynamic router | Match the corresponding service by setting parameters such as location, query, header, host and method | -| Service discovery | Support such as Eureka, Nacos and Consul | -| Load Balance | Support polling weight algorithm | -| Authentication | Anonymous, basic, apikey, JWT, AK / SK authentication | -| SSL certificate | Manage multiple certificates | -| Access Domain | The access domain can be set for the gateway | -| Health check | Support health check of load nodes to ensure service robustness | -| Protocol | HTTP/HTTPS、Webservice | -| Plugin | The process is plug-in, and the required modules are loaded on demand | -| OPEN API | Gateway configuration using open API is supported | -| Log | Provide the operation log of the node, and set the level output of the log | -| Multiple log output | The node's request log can be output to different log receivers, such as file, NSQ, Kafka,etc | -| Cli | The gateway is controlled by cli command. The plug-in installation, download, opening and closing of the gateway can be controlled by one click command | -| Black and white list | Support setting black-and-white list IP to intercept illegal IP | -| Parameter mapping | Mapping the request parameters of the client to the forwarding request, you can change the location and name of the parameters as needed | -| Additional parameters | When forwarding the request, add back-end verification parameters, such as apikey, etc | -| Proxy rewrite | It supports rewriting of 'scheme', 'URI', 'host', and adding or deleting the value of the request header of the forwarding request | -| flow control | Intercept abnormal traffic | - -#### RoadMap - -- **UI**: The gateway configuration can be operated through the UI interface, and different UI interfaces (Themes) can be customized by loading as required -- **Multi protocol**:Support a variety of protocols, including but not limited to grpc, websocket, TCP / UDP and Dubbo -- **Plugin Market**:Because Apinto mainly loads the required modules through plug-in loading, users can compile the required functions into plug-ins, or download and update the plug-ins developed by contributors from the plug-in market for one click installation -- **Service Orchestration**:An orchestration API corresponds to multiple backends. The input parameters of backends support client input and parameter transfer between backends; The returned data of backend supports filtering, deleting, moving, renaming, unpacking and packaging of fields; The orchestration API can set the exception return when the orchestration call fails -- **Monitor**:Capture the gateway request data and export it to Promethus and graphite for analysis -- ..... +### 产品特性 -#### RoadMap for 2022 +| 功能 | 描述 | +| ------------ | ------------------------------------------------------------ | +| 动态路由 | 可通过设置location、query、header、host、method等参数匹配对应的服务 | +| 服务发现 | 支持对接Eureka、Nacos、Consul | +| 负载均衡 | 支持轮询权重算法 | +| 用户鉴权 | 匿名、Basic、Apikey、JWT、AK/SK认证 | +| SSL证书 | 管理多个证书 | +| 访问域名 | 可为网关设置访问域名 | +| 健康检查 | 支持对负载的节点进行健康检查,确保服务健壮性 | +| 协议 | HTTP/HTTPS、Webservice、Restful、gRPC、Dubbo2、SOAP | +| 插件化 | 流程插件化,按需加载所需模块 | +| OPEN API | 支持使用open api配置网关 | +| 日志 | 提供节点的运行日志,可根据日志设置的等级输出 | +| 多种日志输出 | 可将节点的请求日志输出到不同的日志接收器,如file、nsq、kafka等 | +| Cli命令支持 | 通过Cli命令操控网关,插件安装、下载和网关的开启、关闭等操作均可使用一键命令操控 | +| 黑白名单 | 支持设置黑白名单IP,拦截非法IP | +| 参数映射 | 将客户端的请求参数映射到转发请求中,可按需改变参数的位置及名称 | +| 额外参数 | 转发请求时,额外加上后端验证参数,如apikey等 | +| 转发重写 | 支持对 `scheme`、`uri`、`host` 的重写,同时支持对转发请求的请求头部header的值进行新增或者删除 | +| 流量控制 | 拦截异常流量 | -![roadmap_en](https://user-images.githubusercontent.com/14105999/170408557-478830d5-3725-4fbe-a6f6-0ff0dd91d90e.jpeg) +#### 迭代计划 +- **UI界面支持**: 通过UI界面操作网关配置,可以通过需要加载定制不同的UI界面(主题) -### Benchmark +- **多协议支持**:支持多种协议,包括但不限于:gRPC、Websocket、tcp/udp、Dubbo -![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png) +- **插件市场**:由于apinto主要是通过插件加载的方式加载所需模块,用户可将所需功能编译成插件,也可从插件市场中下载更新贡献者开发的插件,一键安装使用 +- **服务编排**:一个编排API对应多个backend,backend的入参支持客户端传入,也支持backend间的参数传递;backend的返回数据支持字段的过滤、删除、移动、重命名、拆包和封包;编排API能够设定编排调用失败时的异常返回 +- **监控**:捕获网关请求数据,并可将其导出到promethus、Graphite中进行分析 +- ..... -### Deployment +#### 2022年迭代计划 +![roadmap_cn](https://user-images.githubusercontent.com/14105999/170409057-407055ef-2d30-4272-ae8c-3c46b95af8d1.jpeg) + +### 基准测试 + + +![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png) -* Direct Deployment:[Deployment Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html) -* [Quick Start Tutorial](https://help.apinto.com/docs/apinto/quick/quick_course.html) -* [Source Code Compilation Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html) -* [Docker](https://hub.docker.com/r/eolinker/apinto-gateway) -* Kubernetes:Follow up support +### 部署 -### Get start +* 直接部署:[部署教程](https://help.apinto.com/docs/apinto/quick/arrange) +* [快速入门教程](https://help.apinto.com/docs/apinto/quick/quick_course) +* [源码编译教程](https://help.apinto.com/docs/apinto/quick/arrange) +* [Docker部署](https://hub.docker.com/r/eolinker/apinto-gateway) +* Kubernetes部署:后续支持 -1. Download and unzip the installation package (here is an example of the installation package of version v0.11.1) +### 启动 + +1.下载安装包并解压(此处以v0.12.1版本的安装包示例) ``` -wget https://github.com/eolinker/apinto/releases/download/v0.11.1/apinto_v0.11.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.11.1_linux_amd64.tar.gz && cd apinto +wget https://github.com/eolinker/apinto/releases/download/v0.12.1/apinto_v0.12.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.12.1_linux_amd64.tar.gz && cd apinto ``` -Apinto supports running on the arm64 and amd64 architectures. -Please download the installation package of the corresponding architecture and system as required. [Click](https://github.com/eolinker/apinto/releases/) to jump to download the installation package. +Apinto支持在arm64、amd64架构上运行。 + +请根据需要下载对应架构及系统的安装包,安装包下载请[点击](https://github.com/eolinker/apinto/releases/)跳转 -2. Start gateway: +2.启动网关: ``` ./apinto start ``` -3.To configure the gateway through the visual interface, click [apinto dashboard](https://github.com/eolinker/apinto-dashboard) +3.如需可视化界面请点击[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard) + + +- ### **联系我们** + + +* **帮助文档**:[https://help.apinto.com](https://help.apinto.com/docs) + +- **QQ群**: 725853895 + +- **Slack**:[加入我们](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ) -### Contact -- **Help documentation**:[https://help.apinto.com](https://help.apinto.com/docs) -- **QQ group**: 725853895 -- **Slack**:[Join us](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ) -- **Official website**:[https://www.apinto.com](https://www.apinto.com) -- **Forum**:[https://community.apinto.com](https://community.apinto.com) -- **Wechat**: +- **官网**:[https://www.apinto.com](https://www.apinto.com/) +- **论坛**:[https://community.apinto.com](https://community.apinto.com/) +- **微信群**: +### 关于我们 -### About +EOLINK 是领先的 API 管理服务供应商,为全球超过3000家企业提供专业的 API 研发管理、API自动化测试、API监控、API网关等服务。是首家为ITSS(中国电子工业标准化技术协会)制定API研发管理行业规范的企业。 -Eolink is a leading API management service provider, providing professional API R & D management, API automation testing, API monitoring, API gateway and other services to more than 3000 enterprises around the world. It is the first enterprise to formulate API R & D management industry specifications for ITSS (China Electronics Industry Standardization Technology Association). +官方网站:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站") -Official website:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站") -Download PC desktop for free:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端") +免费下载PC桌面端:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端") diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 64228caf..00000000 --- a/README_CN.md +++ /dev/null @@ -1,136 +0,0 @@ -## Apinto - -[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT_CN.md) - ------------- -![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png) - -Apinto是一个基于 Golang 开发的微服务网关,能够实现高性能 HTTP API 转发、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。未来我们将提供插件市场,通过**Apinto**强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展**Apinto**的插件,丰富**Apinto**的能力。 - -注意:**main**分支为开发主要分支,频繁更新可能导致使用不稳定,若需要使用稳定版本,请查看[release](https://github.com/eolinker/apinto/releases) - -**Apinto** 集合了配置和转发功能,使用者可以通过openAPI进行配置,也可通过可视化UI项目[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)进行配置,相关文档可点击[Apinto Dashboard部署文档](https://help.apinto.com/docs/dashboard/quick/arrange) -### 概况 | [English Introduction](https://github.com/eolinker/apinto/blob/main/README.md) - -- [为什么要使用Apinto](#为什么要使用Apinto "Apinto") -- [产品特性](#产品特性 "产品特性") -- [基准测试](#基准测试 "基准测试") -- [部署](#部署 "部署") -- [启动](#启动 "启动") -- [联系我们](#联系我们 "联系我们") -- [关于我们](#关于我们 "关于我们") -- [授权协议](#授权协议 "授权协议") - -### 为什么要使用Apinto - -**Apinto**是运行在企业系统服务边界上的API网关。当您构建网站、App、IOT甚至是开放API交易时,Apinto 能够帮你将内部系统中重复的组件抽取出来并放置在Apinto网关上运行,如进行用户授权、访问控制、防火墙、数据转换等;并且Apinto 提供服务编排的功能,让企业可以快速从各类服务上获取需要的数据,对业务实现快速响应。 - -**Apinto**具有以下优势: - -- 完全开源:Apinto 项目由 Eolinker 发起并长期维护,我们希望与全球开发者共同打造微服务生态的基础设施。 -- 优异的性能表现:相同环境下,Apinto比Nginx、Kong等产品快约50%,并且在稳定性上也有所优化。 -- 丰富的功能:Apinto 提供了一个标准网关应有的所有功能,并且你可以快速连接你的各个微服务以及管理网络流量。 -- 极低的使用和维护成本:Apinto 是纯 Go 语言开发的开源网关,没有繁琐的部署,没有外部产品依赖,只需要下载并运行即可,极为简单。 -- 良好的扩展性:Apinto 的绝大部分功能都是模块化的,因此你可以很容易扩展它的能力。 - -总而言之,Apinto 能让业务开发团队更加专注地实现业务。 - -### Star 历史 - -[![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date) - - -### 产品特性 - -| 功能 | 描述 | -| ------------ | ------------------------------------------------------------ | -| 动态路由 | 可通过设置location、query、header、host、method等参数匹配对应的服务 | -| 服务发现 | 支持对接Eureka、Nacos、Consul | -| 负载均衡 | 支持轮询权重算法 | -| 用户鉴权 | 匿名、Basic、Apikey、JWT、AK/SK认证 | -| SSL证书 | 管理多个证书 | -| 访问域名 | 可为网关设置访问域名 | -| 健康检查 | 支持对负载的节点进行健康检查,确保服务健壮性 | -| 协议 | HTTP/HTTPS、Webservice、Restful、gRPC、Dubbo2、SOAP | -| 插件化 | 流程插件化,按需加载所需模块 | -| OPEN API | 支持使用open api配置网关 | -| 日志 | 提供节点的运行日志,可根据日志设置的等级输出 | -| 多种日志输出 | 可将节点的请求日志输出到不同的日志接收器,如file、nsq、kafka等 | -| Cli命令支持 | 通过Cli命令操控网关,插件安装、下载和网关的开启、关闭等操作均可使用一键命令操控 | -| 黑白名单 | 支持设置黑白名单IP,拦截非法IP | -| 参数映射 | 将客户端的请求参数映射到转发请求中,可按需改变参数的位置及名称 | -| 额外参数 | 转发请求时,额外加上后端验证参数,如apikey等 | -| 转发重写 | 支持对 `scheme`、`uri`、`host` 的重写,同时支持对转发请求的请求头部header的值进行新增或者删除 | -| 流量控制 | 拦截异常流量 | - -#### 迭代计划 - -- **UI界面支持**: 通过UI界面操作网关配置,可以通过需要加载定制不同的UI界面(主题) - -- **多协议支持**:支持多种协议,包括但不限于:gRPC、Websocket、tcp/udp、Dubbo - -- **插件市场**:由于apinto主要是通过插件加载的方式加载所需模块,用户可将所需功能编译成插件,也可从插件市场中下载更新贡献者开发的插件,一键安装使用 - -- **服务编排**:一个编排API对应多个backend,backend的入参支持客户端传入,也支持backend间的参数传递;backend的返回数据支持字段的过滤、删除、移动、重命名、拆包和封包;编排API能够设定编排调用失败时的异常返回 - -- **监控**:捕获网关请求数据,并可将其导出到promethus、Graphite中进行分析 -- ..... - -#### 2022年迭代计划 -![roadmap_cn](https://user-images.githubusercontent.com/14105999/170409057-407055ef-2d30-4272-ae8c-3c46b95af8d1.jpeg) - -### 基准测试 - - -![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png) - -### 部署 - -* 直接部署:[部署教程](https://help.apinto.com/docs/apinto/quick/arrange) -* [快速入门教程](https://help.apinto.com/docs/apinto/quick/quick_course) -* [源码编译教程](https://help.apinto.com/docs/apinto/quick/arrange) -* [Docker部署](https://hub.docker.com/r/eolinker/apinto-gateway) -* Kubernetes部署:后续支持 - -### 启动 - -1.下载安装包并解压(此处以v0.11.1版本的安装包示例) - -``` -wget https://github.com/eolinker/apinto/releases/download/v0.11.1/apinto_v0.11.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.11.1_linux_amd64.tar.gz && cd apinto -``` - -Apinto支持在arm64、amd64架构上运行。 - -请根据需要下载对应架构及系统的安装包,安装包下载请[点击](https://github.com/eolinker/apinto/releases/)跳转 - -2.启动网关: - -``` -./apinto start -``` - -3.如需可视化界面请点击[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard) - - -- ### **联系我们** - - -* **帮助文档**:[https://help.apinto.com](https://help.apinto.com/docs) - -- **QQ群**: 725853895 - -- **Slack**:[加入我们](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ) - -- **官网**:[https://www.apinto.com](https://www.apinto.com/) -- **论坛**:[https://community.apinto.com](https://community.apinto.com/) -- **微信群**: - -### 关于我们 - -EOLINK 是领先的 API 管理服务供应商,为全球超过3000家企业提供专业的 API 研发管理、API自动化测试、API监控、API网关等服务。是首家为ITSS(中国电子工业标准化技术协会)制定API研发管理行业规范的企业。 - -官方网站:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站") - -免费下载PC桌面端:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端") diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 00000000..3cf92e10 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,126 @@ +## Apinto + +[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/Apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) +![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png) + +Apinto is a microservice gateway developed based on golang. It can achieve the purposes of high-performance HTTP API forwarding, multi tenant management, API access control, etc. it has a powerful user-defined plug-in system, which can be expanded by itself, and can quickly help enterprises manage API services and improve the stability and security of API services. In the future, we will provide the plug-in market. Through the strong plug-in expansion ability of **Apinto**, users can expand **Apinto** plug-ins as needed like Lego blocks to enrich **Apinto** capabilities. + +**Note**:The **main** branch is the main development branch. Frequent updates may lead to unstable use. If you need to use a stable version, please look [release](https://github.com/eolinker/apinto/releases) + +**Apinto** integrates configuration and forwarding functions. Users can configure it through OpenAPI or through visual UI items [apinto dashboard](https://github.com/eolinker/apinto-dashboard) for configuration, click [apinto dashboard deployment document](https://help.apinto.com/docs/dashboard/quick/arrange) for relevant documents + +### Summary / [中文介绍](https://github.com/eolinker/apinto/blob/main/README.md) + +- [Why Apinto](#WhyApinto "Why Apinto") +- [Feature](#Feature) +- [Benchmark](#Benchmark) +- [Deployment](#Deployment) +- [GetStart](#GetStart "Get Start") +- [Contact](#Contact) +- [About](#About) + +### Why Apinto + +Apinto API gateway is a microservice gateway running on the service boundary of enterprise system. When you build websites, apps, iots and even open API transactions, Apinto API gateway can help you extract duplicate components from your internal system and run them on Apinto gateway, such as user authorization, access control, firewall, data conversion, etc; Moreover, Apinto provides the function of service arrangement, so that enterprises can quickly obtain the required data from various services and realize rapid response to business. + +Apinto API gateway has the following advantages: + +- Completely open source: the Apinto project is initiated and maintained by eolinker for a long time. We hope to work with global developers to build the infrastructure of micro service ecology. +- Excellent performance: under the same environment, Apinto is about 50% faster than nginx, Kong and other products, and its stability is also optimized. +- Rich functions: Apinto provides all the functions of a standard gateway, and you can quickly connect your micro services and manage network traffic. +- Extremely low use and maintenance cost: Apinto is an open source gateway developed in pure go language. It has no cumbersome deployment and no external product dependence. It only needs to download and run, which is extremely simple. +- Good scalability: most of Apinto's functions are modular, so you can easily expand its capabilities. + +In a word, Apinto API gateway enables the business development team to focus more on business implementation. + +### Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date) + + +### Feture + +| Feture | Description | +| --------------------- | ------------------------------------------------------------ | +| Dynamic router | Match the corresponding service by setting parameters such as location, query, header, host and method | +| Service discovery | Support such as Eureka, Nacos and Consul | +| Load Balance | Support polling weight algorithm | +| Authentication | Anonymous, basic, apikey, JWT, AK / SK authentication | +| SSL certificate | Manage multiple certificates | +| Access Domain | The access domain can be set for the gateway | +| Health check | Support health check of load nodes to ensure service robustness | +| Protocol | HTTP/HTTPS、Webservice | +| Plugin | The process is plug-in, and the required modules are loaded on demand | +| OPEN API | Gateway configuration using open API is supported | +| Log | Provide the operation log of the node, and set the level output of the log | +| Multiple log output | The node's request log can be output to different log receivers, such as file, NSQ, Kafka,etc | +| Cli | The gateway is controlled by cli command. The plug-in installation, download, opening and closing of the gateway can be controlled by one click command | +| Black and white list | Support setting black-and-white list IP to intercept illegal IP | +| Parameter mapping | Mapping the request parameters of the client to the forwarding request, you can change the location and name of the parameters as needed | +| Additional parameters | When forwarding the request, add back-end verification parameters, such as apikey, etc | +| Proxy rewrite | It supports rewriting of 'scheme', 'URI', 'host', and adding or deleting the value of the request header of the forwarding request | +| flow control | Intercept abnormal traffic | + +#### RoadMap + +- **UI**: The gateway configuration can be operated through the UI interface, and different UI interfaces (Themes) can be customized by loading as required +- **Multi protocol**:Support a variety of protocols, including but not limited to grpc, websocket, TCP / UDP and Dubbo +- **Plugin Market**:Because Apinto mainly loads the required modules through plug-in loading, users can compile the required functions into plug-ins, or download and update the plug-ins developed by contributors from the plug-in market for one click installation +- **Service Orchestration**:An orchestration API corresponds to multiple backends. The input parameters of backends support client input and parameter transfer between backends; The returned data of backend supports filtering, deleting, moving, renaming, unpacking and packaging of fields; The orchestration API can set the exception return when the orchestration call fails +- **Monitor**:Capture the gateway request data and export it to Promethus and graphite for analysis +- ..... + +#### RoadMap for 2022 + +![roadmap_en](https://user-images.githubusercontent.com/14105999/170408557-478830d5-3725-4fbe-a6f6-0ff0dd91d90e.jpeg) + + +### Benchmark + +![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png) + + + +### Deployment + +* Direct Deployment:[Deployment Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html) +* [Quick Start Tutorial](https://help.apinto.com/docs/apinto/quick/quick_course.html) +* [Source Code Compilation Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html) +* [Docker](https://hub.docker.com/r/eolinker/apinto-gateway) +* Kubernetes:Follow up support + +### Get start + +1. Download and unzip the installation package (here is an example of the installation package of version v0.12.1) + +``` +wget https://github.com/eolinker/apinto/releases/download/v0.12.1/apinto_v0.12.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.12.1_linux_amd64.tar.gz && cd apinto +``` +Apinto supports running on the arm64 and amd64 architectures. + +Please download the installation package of the corresponding architecture and system as required. [Click](https://github.com/eolinker/apinto/releases/) to jump to download the installation package. + +2. Start gateway: + +``` +./apinto start +``` + +3.To configure the gateway through the visual interface, click [apinto dashboard](https://github.com/eolinker/apinto-dashboard) + +### Contact +- **Help documentation**:[https://help.apinto.com](https://help.apinto.com/docs) +- **QQ group**: 725853895 +- **Slack**:[Join us](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ) +- **Official website**:[https://www.apinto.com](https://www.apinto.com) +- **Forum**:[https://community.apinto.com](https://community.apinto.com) +- **Wechat**: + + +### About + +Eolink is a leading API management service provider, providing professional API R & D management, API automation testing, API monitoring, API gateway and other services to more than 3000 enterprises around the world. It is the first enterprise to formulate API R & D management industry specifications for ITSS (China Electronics Industry Standardization Technology Association). + +Official website:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站") +Download PC desktop for free:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端") diff --git a/go.mod b/go.mod index 4868b903..27a8e183 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/brianvoe/gofakeit/v6 v6.20.1 github.com/coocood/freecache v1.2.2 github.com/dubbogo/gost v1.13.1 - github.com/eolinker/eosc v0.10.1 + github.com/eolinker/eosc v0.11.0 github.com/fasthttp/websocket v1.5.0 github.com/fullstorydev/grpcurl v1.8.7 github.com/go-redis/redis/v8 v8.11.5 @@ -198,4 +198,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace github.com/eolinker/eosc => ../eosc +//replace github.com/eolinker/eosc => ../eosc