Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
ca6cea861f | |||
002d5c9c53 | |||
1d25e59860 | |||
ce26e8bf63 | |||
8e09142b90 | |||
13bf2bbb1e | |||
ba292901d7 | |||
6d1b6c7014 | |||
1276f99159 | |||
ee5cbbbcb6 | |||
ce0abd2de3 | |||
647f731f50 | |||
724ffb8c3a | |||
25f306425b | |||
|
dad2d8d5bd | ||
ca148cd8fc | |||
cd8b3e37f2 | |||
c908274c8b | |||
fd3793d50f | |||
|
e67021895e | ||
9f4dbbf296 | |||
eea8ae605b | |||
4341600c01 | |||
d3fc61eada | |||
e8281da860 |
13
go.mod
13
go.mod
@@ -1,9 +1,16 @@
|
||||
module go.unistack.org/micro-server-http/v3
|
||||
|
||||
go 1.16
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
go.unistack.org/micro/v3 v3.10.11
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
go.unistack.org/micro-proto/v3 v3.3.1
|
||||
go.unistack.org/micro/v3 v3.10.14
|
||||
golang.org/x/net v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
22
go.sum
22
go.sum
@@ -10,7 +10,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
@@ -45,19 +44,16 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -74,8 +70,8 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.unistack.org/micro-proto/v3 v3.3.1 h1:nQ0MtWvP2G3QrpOgawVOPhpZZYkq6umTGDqs8FxJYIo=
|
||||
go.unistack.org/micro-proto/v3 v3.3.1/go.mod h1:cwRyv8uInM2I7EbU7O8Fx2Ls3N90Uw9UCCcq4olOdfE=
|
||||
go.unistack.org/micro/v3 v3.10.11 h1:IPUEVQ5oGZ6Ft2/2zQAOIkp+Ztt6GIHxwMoG4l+eJGQ=
|
||||
go.unistack.org/micro/v3 v3.10.11/go.mod h1:gI4RkJKHLPW7KV6h4+ZBOZD997MRvFRXMPQIHpozikI=
|
||||
go.unistack.org/micro/v3 v3.10.14 h1:7fgLpwGlCN67twhwtngJDEQvrMkUBDSA5vzZqxIDqNE=
|
||||
go.unistack.org/micro/v3 v3.10.14/go.mod h1:uMAc0U/x7dmtICCrblGf0ZLgYegu3VwQAquu+OFCw1Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -91,8 +87,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -107,15 +103,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
319
handler.go
319
handler.go
@@ -60,13 +60,280 @@ func (h *httpHandler) Options() server.HandlerOptions {
|
||||
return h.opts
|
||||
}
|
||||
|
||||
func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// check for http.HandlerFunc handlers
|
||||
if ph, _, err := h.pathHandlers.Search(r.Method, r.URL.Path); err == nil {
|
||||
ph.(http.HandlerFunc)(w, r)
|
||||
return
|
||||
func (h *Server) HTTPHandlerFunc(handler interface{}) (http.HandlerFunc, error) {
|
||||
if handler == nil {
|
||||
return nil, fmt.Errorf("invalid handler specified: %v", handler)
|
||||
}
|
||||
|
||||
rtype := reflect.TypeOf(handler)
|
||||
if rtype.NumIn() != 3 {
|
||||
return nil, fmt.Errorf("invalid handler, NumIn != 3: %v", rtype.NumIn())
|
||||
}
|
||||
|
||||
argType := rtype.In(1)
|
||||
replyType := rtype.In(2)
|
||||
|
||||
// First arg need not be a pointer.
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
return nil, fmt.Errorf("invalid handler, argument type not exported: %v", argType)
|
||||
}
|
||||
|
||||
if replyType.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("invalid handler, reply type not a pointer: %v", replyType)
|
||||
}
|
||||
|
||||
// Reply type must be exported.
|
||||
if !isExportedOrBuiltinType(replyType) {
|
||||
return nil, fmt.Errorf("invalid handler, reply type not exported: %v", replyType)
|
||||
}
|
||||
|
||||
if rtype.NumOut() != 1 {
|
||||
return nil, fmt.Errorf("invalid handler, has wrong number of outs: %v", rtype.NumOut())
|
||||
}
|
||||
|
||||
// The return type of the method must be error.
|
||||
if returnType := rtype.Out(0); returnType != typeOfError {
|
||||
return nil, fmt.Errorf("invalid handler, returns %v not error", returnType.String())
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ct := DefaultContentType
|
||||
if htype := r.Header.Get(metadata.HeaderContentType); htype != "" {
|
||||
ct = htype
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), rspCodeKey{}, &rspCodeVal{})
|
||||
ctx = context.WithValue(ctx, rspHeaderKey{}, &rspHeaderVal{})
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(len(r.Header) + 8)
|
||||
}
|
||||
for k, v := range r.Header {
|
||||
md[k] = strings.Join(v, ", ")
|
||||
}
|
||||
md["RemoteAddr"] = r.RemoteAddr
|
||||
md["Method"] = r.Method
|
||||
md["URL"] = r.URL.String()
|
||||
md["Proto"] = r.Proto
|
||||
md["ContentLength"] = fmt.Sprintf("%d", r.ContentLength)
|
||||
md["TransferEncoding"] = strings.Join(r.TransferEncoding, ",")
|
||||
md["Host"] = r.Host
|
||||
md["RequestURI"] = r.RequestURI
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
|
||||
path := r.URL.Path
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
matches := make(map[string]interface{})
|
||||
var match bool
|
||||
var hldr *patHandler
|
||||
var handler *httpHandler
|
||||
|
||||
for _, shdlr := range h.handlers {
|
||||
hdlr := shdlr.(*httpHandler)
|
||||
fh, mp, err := hdlr.handlers.Search(r.Method, path)
|
||||
if err == nil {
|
||||
match = true
|
||||
for k, v := range mp {
|
||||
matches[k] = v
|
||||
}
|
||||
hldr = fh.(*patHandler)
|
||||
handler = hdlr
|
||||
break
|
||||
} else if err == rhttp.ErrMethodNotAllowed && !h.registerRPC {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, _ = w.Write([]byte("not matching route found"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !match && h.registerRPC {
|
||||
microMethod, mok := md.Get(metadata.HeaderEndpoint)
|
||||
if mok {
|
||||
serviceMethod := strings.Split(microMethod, ".")
|
||||
if len(serviceMethod) == 2 {
|
||||
if shdlr, ok := h.handlers[serviceMethod[0]]; ok {
|
||||
hdlr := shdlr.(*httpHandler)
|
||||
fh, mp, err := hdlr.handlers.Search(http.MethodPost, "/"+microMethod)
|
||||
if err == nil {
|
||||
match = true
|
||||
for k, v := range mp {
|
||||
matches[k] = v
|
||||
}
|
||||
hldr = fh.(*patHandler)
|
||||
handler = hdlr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get fields from url values
|
||||
if len(r.URL.RawQuery) > 0 {
|
||||
umd, cerr := rflutil.URLMap(r.URL.RawQuery)
|
||||
if cerr != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(cerr.Error()))
|
||||
return
|
||||
}
|
||||
for k, v := range umd {
|
||||
matches[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
cf, err := h.newCodec(ct)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var argv, replyv reflect.Value
|
||||
|
||||
// Decode the argument value.
|
||||
argIsValue := false // if true, need to indirect before calling.
|
||||
if hldr.mtype.ArgType.Kind() == reflect.Ptr {
|
||||
argv = reflect.New(hldr.mtype.ArgType.Elem())
|
||||
} else {
|
||||
argv = reflect.New(hldr.mtype.ArgType)
|
||||
argIsValue = true
|
||||
}
|
||||
|
||||
if argIsValue {
|
||||
argv = argv.Elem()
|
||||
}
|
||||
|
||||
// reply value
|
||||
replyv = reflect.New(hldr.mtype.ReplyType.Elem())
|
||||
|
||||
function := hldr.mtype.method.Func
|
||||
var returnValues []reflect.Value
|
||||
|
||||
if r.Body != nil {
|
||||
var buf []byte
|
||||
buf, err = io.ReadAll(r.Body)
|
||||
if err != nil && err != io.EOF {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = cf.Unmarshal(buf, argv.Interface()); err != nil {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
matches = rflutil.FlattenMap(matches)
|
||||
if err = rflutil.Merge(argv.Interface(), matches, rflutil.SliceAppend(true), rflutil.Tags([]string{"protobuf", "json"})); err != nil {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
hr := &rpcRequest{
|
||||
codec: cf,
|
||||
service: handler.sopts.Name,
|
||||
contentType: ct,
|
||||
method: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||
endpoint: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
||||
payload: argv.Interface(),
|
||||
header: md,
|
||||
}
|
||||
|
||||
// define the handler func
|
||||
fn := func(fctx context.Context, req server.Request, rsp interface{}) (err error) {
|
||||
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(fctx), argv, reflect.ValueOf(rsp)})
|
||||
|
||||
// The return value for the method is an error.
|
||||
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||
err = rerr.(error)
|
||||
}
|
||||
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
if nmd, ok := metadata.FromOutgoingContext(fctx); ok {
|
||||
for k, v := range nmd {
|
||||
md.Set(k, v)
|
||||
}
|
||||
}
|
||||
metadata.SetOutgoingContext(ctx, md)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// wrap the handler func
|
||||
for i := len(handler.sopts.HdlrWrappers); i > 0; i-- {
|
||||
fn = handler.sopts.HdlrWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
if ct == "application/x-www-form-urlencoded" {
|
||||
cf, err = h.newCodec(DefaultContentType)
|
||||
if err != nil {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ct = DefaultContentType
|
||||
}
|
||||
|
||||
scode := int(200)
|
||||
appErr := fn(ctx, hr, replyv.Interface())
|
||||
|
||||
w.Header().Set(metadata.HeaderContentType, ct)
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
for k, v := range md {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
if md := getRspHeader(ctx); md != nil {
|
||||
for k, v := range md {
|
||||
for _, vv := range v {
|
||||
w.Header().Add(k, vv)
|
||||
}
|
||||
}
|
||||
}
|
||||
if nct := w.Header().Get(metadata.HeaderContentType); nct != ct {
|
||||
if cf, err = h.newCodec(nct); err != nil {
|
||||
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if appErr != nil {
|
||||
switch verr := appErr.(type) {
|
||||
case *errors.Error:
|
||||
scode = int(verr.Code)
|
||||
buf, err = cf.Marshal(verr)
|
||||
case *Error:
|
||||
buf, err = cf.Marshal(verr.err)
|
||||
default:
|
||||
buf, err = cf.Marshal(appErr)
|
||||
}
|
||||
} else {
|
||||
buf, err = cf.Marshal(replyv.Interface())
|
||||
}
|
||||
|
||||
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
|
||||
handler.sopts.Logger.Errorf(handler.sopts.Context, "handler err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if nscode := GetRspCode(ctx); nscode != 0 {
|
||||
scode = nscode
|
||||
}
|
||||
w.WriteHeader(scode)
|
||||
|
||||
if _, cerr := w.Write(buf); cerr != nil {
|
||||
handler.sopts.Logger.Errorf(ctx, "write failed: %v", cerr)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ct := DefaultContentType
|
||||
if htype := r.Header.Get(metadata.HeaderContentType); htype != "" {
|
||||
ct = htype
|
||||
@@ -82,17 +349,22 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
md[k] = strings.Join(v, ", ")
|
||||
}
|
||||
md["RemoteAddr"] = r.RemoteAddr
|
||||
if r.TLS != nil {
|
||||
md["Scheme"] = "https"
|
||||
} else {
|
||||
md["Scheme"] = "http"
|
||||
}
|
||||
md["Method"] = r.Method
|
||||
md["URL"] = r.URL.String()
|
||||
md["Proto"] = r.Proto
|
||||
md["ContentLength"] = fmt.Sprintf("%d", r.ContentLength)
|
||||
md["TransferEncoding"] = strings.Join(r.TransferEncoding, ",")
|
||||
if len(r.TransferEncoding) > 0 {
|
||||
md["TransferEncoding"] = strings.Join(r.TransferEncoding, ",")
|
||||
}
|
||||
md["Host"] = r.Host
|
||||
md["RequestURI"] = r.RequestURI
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
path := r.URL.Path
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
h.errorHandler(ctx, nil, w, r, fmt.Errorf("path must starts with /"), http.StatusBadRequest)
|
||||
@@ -117,7 +389,7 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler = hdlr
|
||||
break
|
||||
} else if err == rhttp.ErrMethodNotAllowed && !h.registerRPC {
|
||||
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
||||
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -149,6 +421,11 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else if !match {
|
||||
// check for http.HandlerFunc handlers
|
||||
if ph, _, err := h.pathHandlers.Search(r.Method, r.URL.Path); err == nil {
|
||||
ph.(http.HandlerFunc)(w, r)
|
||||
return
|
||||
}
|
||||
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
@@ -165,6 +442,10 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
cf, err := h.newCodec(ct)
|
||||
if err != nil {
|
||||
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
||||
@@ -192,15 +473,18 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
function := hldr.mtype.method.Func
|
||||
var returnValues []reflect.Value
|
||||
|
||||
buf, err := io.ReadAll(r.Body)
|
||||
if err != nil && err != io.EOF {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if r.Body != nil {
|
||||
var buf []byte
|
||||
buf, err = io.ReadAll(r.Body)
|
||||
if err != nil && err != io.EOF {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = cf.Unmarshal(buf, argv.Interface()); err != nil {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
if err = cf.Unmarshal(buf, argv.Interface()); err != nil {
|
||||
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
matches = rflutil.FlattenMap(matches)
|
||||
@@ -279,6 +563,7 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if appErr != nil {
|
||||
switch verr := appErr.(type) {
|
||||
case *errors.Error:
|
||||
|
12
handler/generate.go
Normal file
12
handler/generate.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
// import required packages
|
||||
_ "go.unistack.org/micro-proto/v3/openapiv3"
|
||||
)
|
||||
|
||||
//go:generate sh -c "curl -L https://github.com/swagger-api/swagger-ui/archive/refs/tags/v4.18.3.zip -o - | bsdtar -C swagger-ui --strip-components=2 -xv swagger-ui-4.18.3/dist && rm swagger-ui/*.map swagger-ui/*-es-*.js swagger-ui/swagger-ui.js swagger-ui/swagger-initializer.js"
|
||||
|
||||
//go:generate sh -c "protoc -I./ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./ ./meter/meter.proto"
|
||||
|
||||
//go:generate sh -c "protoc -I./ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./ ./health/health.proto"
|
82
handler/health/health.go
Normal file
82
handler/health/health.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package health // import "go.unistack.org/micro-server-http/v3/handler/health"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
codecpb "go.unistack.org/micro-proto/v3/codec"
|
||||
"go.unistack.org/micro/v3/errors"
|
||||
)
|
||||
|
||||
var _ HealthServiceServer = &Handler{}
|
||||
|
||||
type Handler struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type CheckFunc func(context.Context) error
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
Version string
|
||||
Name string
|
||||
LiveChecks []CheckFunc
|
||||
ReadyChecks []CheckFunc
|
||||
}
|
||||
|
||||
func LiveChecks(fns ...CheckFunc) Option {
|
||||
return func(o *Options) {
|
||||
o.LiveChecks = append(o.LiveChecks, fns...)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadyChecks(fns ...CheckFunc) Option {
|
||||
return func(o *Options) {
|
||||
o.ReadyChecks = append(o.ReadyChecks, fns...)
|
||||
}
|
||||
}
|
||||
|
||||
func Name(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func Version(version string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandler(opts ...Option) *Handler {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &Handler{opts: options}
|
||||
}
|
||||
|
||||
func (h *Handler) Live(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||
var err error
|
||||
for _, fn := range h.opts.LiveChecks {
|
||||
if err = fn(ctx); err != nil {
|
||||
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Ready(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||
var err error
|
||||
for _, fn := range h.opts.ReadyChecks {
|
||||
if err = fn(ctx); err != nil {
|
||||
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Version(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||
rsp.Data = []byte(h.opts.Version)
|
||||
return nil
|
||||
}
|
50
handler/health/health.proto
Normal file
50
handler/health/health.proto
Normal file
@@ -0,0 +1,50 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package micro.server.http.v3.handler.health;
|
||||
option go_package = "go.unistack.org/micro-server-http/v3/handler/health;health";
|
||||
|
||||
import "api/annotations.proto";
|
||||
import "openapiv3/annotations.proto";
|
||||
import "codec/frame.proto";
|
||||
|
||||
service HealthService {
|
||||
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv3.openapiv3_operation) = {
|
||||
operation_id: "Live";
|
||||
responses: {
|
||||
default: {
|
||||
reference: {
|
||||
_ref: "micro.codec.Frame";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
option (micro.api.http) = { get: "/live"; };
|
||||
};
|
||||
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv3.openapiv3_operation) = {
|
||||
operation_id: "Ready";
|
||||
responses: {
|
||||
default: {
|
||||
reference: {
|
||||
_ref: "micro.codec.Frame";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
option (micro.api.http) = { get: "/ready"; };
|
||||
};
|
||||
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv3.openapiv3_operation) = {
|
||||
operation_id: "Version";
|
||||
responses: {
|
||||
default: {
|
||||
reference: {
|
||||
_ref: "micro.codec.Frame";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
option (micro.api.http) = { get: "/version"; };
|
||||
};
|
||||
};
|
48
handler/health/health_micro.pb.go
Normal file
48
handler/health/health_micro.pb.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-micro v3.10.2
|
||||
// - protoc v3.21.12
|
||||
// source: health.proto
|
||||
|
||||
package health
|
||||
|
||||
import (
|
||||
context "context"
|
||||
codec "go.unistack.org/micro-proto/v3/codec"
|
||||
v3 "go.unistack.org/micro-server-http/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
HealthServiceName = "HealthService"
|
||||
)
|
||||
var (
|
||||
HealthServiceServerEndpoints = []v3.EndpointMetadata{
|
||||
{
|
||||
Name: "HealthService.Live",
|
||||
Path: "/live",
|
||||
Method: "GET",
|
||||
Body: "",
|
||||
Stream: false,
|
||||
},
|
||||
{
|
||||
Name: "HealthService.Ready",
|
||||
Path: "/ready",
|
||||
Method: "GET",
|
||||
Body: "",
|
||||
Stream: false,
|
||||
},
|
||||
{
|
||||
Name: "HealthService.Version",
|
||||
Path: "/version",
|
||||
Method: "GET",
|
||||
Body: "",
|
||||
Stream: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type HealthServiceServer interface {
|
||||
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
43
handler/health/health_micro_http.pb.go
Normal file
43
handler/health/health_micro_http.pb.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.10.2
|
||||
// source: health.proto
|
||||
|
||||
package health
|
||||
|
||||
import (
|
||||
context "context"
|
||||
codec "go.unistack.org/micro-proto/v3/codec"
|
||||
v3 "go.unistack.org/micro-server-http/v3"
|
||||
server "go.unistack.org/micro/v3/server"
|
||||
)
|
||||
|
||||
type healthServiceServer struct {
|
||||
HealthServiceServer
|
||||
}
|
||||
|
||||
func (h *healthServiceServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServiceServer.Live(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func (h *healthServiceServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServiceServer.Ready(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func (h *healthServiceServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServiceServer.Version(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func RegisterHealthServiceServer(s server.Server, sh HealthServiceServer, opts ...server.HandlerOption) error {
|
||||
type healthService interface {
|
||||
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
||||
type HealthService struct {
|
||||
healthService
|
||||
}
|
||||
h := &healthServiceServer{sh}
|
||||
var nopts []server.HandlerOption
|
||||
nopts = append(nopts, v3.HandlerEndpoints(HealthServiceServerEndpoints))
|
||||
return s.Handle(s.NewHandler(&HealthService{h}, append(nopts, opts...)...))
|
||||
}
|
67
handler/meter/meter.go
Normal file
67
handler/meter/meter.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package meter // import "go.unistack.org/micro-server-http/v3/handler/meter"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
codecpb "go.unistack.org/micro-proto/v3/codec"
|
||||
"go.unistack.org/micro/v3/errors"
|
||||
"go.unistack.org/micro/v3/meter"
|
||||
)
|
||||
|
||||
// guard to fail early
|
||||
var _ MeterServiceServer = &Handler{}
|
||||
|
||||
type Handler struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
Meter meter.Meter
|
||||
Name string
|
||||
MeterOptions []meter.Option
|
||||
}
|
||||
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
func Name(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func MeterOptions(opts ...meter.Option) Option {
|
||||
return func(o *Options) {
|
||||
o.MeterOptions = append(o.MeterOptions, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{Meter: meter.DefaultMeter}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func NewHandler(opts ...Option) *Handler {
|
||||
options := NewOptions(opts...)
|
||||
return &Handler{opts: options}
|
||||
}
|
||||
|
||||
func (h *Handler) Metrics(ctx context.Context, req *codecpb.Frame, rsp *codecpb.Frame) error {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
|
||||
return errors.InternalServerError(h.opts.Name, "%v", err)
|
||||
}
|
||||
|
||||
rsp.Data = buf.Bytes()
|
||||
|
||||
return nil
|
||||
}
|
24
handler/meter/meter.proto
Normal file
24
handler/meter/meter.proto
Normal file
@@ -0,0 +1,24 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package micro.server.http.v3.handler.meter;
|
||||
option go_package = "go.unistack.org/micro-server-http/v3/handler/meter;meter";
|
||||
|
||||
import "api/annotations.proto";
|
||||
import "openapiv3/annotations.proto";
|
||||
import "codec/frame.proto";
|
||||
|
||||
service MeterService {
|
||||
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv3.openapiv3_operation) = {
|
||||
operation_id: "Metrics";
|
||||
responses: {
|
||||
default: {
|
||||
reference: {
|
||||
_ref: "micro.codec.Frame";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
option (micro.api.http) = { get: "/metrics"; };
|
||||
};
|
||||
};
|
32
handler/meter/meter_micro.pb.go
Normal file
32
handler/meter/meter_micro.pb.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-micro v3.10.2
|
||||
// - protoc v3.21.12
|
||||
// source: meter.proto
|
||||
|
||||
package meter
|
||||
|
||||
import (
|
||||
context "context"
|
||||
codec "go.unistack.org/micro-proto/v3/codec"
|
||||
v3 "go.unistack.org/micro-server-http/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
MeterServiceName = "MeterService"
|
||||
)
|
||||
var (
|
||||
MeterServiceServerEndpoints = []v3.EndpointMetadata{
|
||||
{
|
||||
Name: "MeterService.Metrics",
|
||||
Path: "/metrics",
|
||||
Method: "GET",
|
||||
Body: "",
|
||||
Stream: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type MeterServiceServer interface {
|
||||
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
33
handler/meter/meter_micro_http.pb.go
Normal file
33
handler/meter/meter_micro_http.pb.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||
// protoc-gen-go-micro version: v3.10.2
|
||||
// source: meter.proto
|
||||
|
||||
package meter
|
||||
|
||||
import (
|
||||
context "context"
|
||||
codec "go.unistack.org/micro-proto/v3/codec"
|
||||
v3 "go.unistack.org/micro-server-http/v3"
|
||||
server "go.unistack.org/micro/v3/server"
|
||||
)
|
||||
|
||||
type meterServiceServer struct {
|
||||
MeterServiceServer
|
||||
}
|
||||
|
||||
func (h *meterServiceServer) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.MeterServiceServer.Metrics(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func RegisterMeterServiceServer(s server.Server, sh MeterServiceServer, opts ...server.HandlerOption) error {
|
||||
type meterService interface {
|
||||
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
||||
type MeterService struct {
|
||||
meterService
|
||||
}
|
||||
h := &meterServiceServer{sh}
|
||||
var nopts []server.HandlerOption
|
||||
nopts = append(nopts, v3.HandlerEndpoints(MeterServiceServerEndpoints))
|
||||
return s.Handle(s.NewHandler(&MeterService{h}, append(nopts, opts...)...))
|
||||
}
|
BIN
handler/swagger-ui/favicon-16x16.png
Normal file
BIN
handler/swagger-ui/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
handler/swagger-ui/favicon-32x32.png
Normal file
BIN
handler/swagger-ui/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
16
handler/swagger-ui/index.css
Normal file
16
handler/swagger-ui/index.css
Normal file
@@ -0,0 +1,16 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
19
handler/swagger-ui/index.html
Normal file
19
handler/swagger-ui/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="index.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
|
||||
</body>
|
||||
</html>
|
79
handler/swagger-ui/oauth2-redirect.html
Normal file
79
handler/swagger-ui/oauth2-redirect.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
20
handler/swagger-ui/swagger-initializer.js
Normal file
20
handler/swagger-ui/swagger-initializer.js
Normal file
@@ -0,0 +1,20 @@
|
||||
window.onload = function() {
|
||||
//<editor-fold desc="Changeable Configuration Block">
|
||||
|
||||
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "{{ .SWAGGER }}",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
|
||||
//</editor-fold>
|
||||
};
|
3
handler/swagger-ui/swagger-ui-bundle.js
Normal file
3
handler/swagger-ui/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
3
handler/swagger-ui/swagger-ui-standalone-preset.js
Normal file
3
handler/swagger-ui/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
3
handler/swagger-ui/swagger-ui.css
Normal file
3
handler/swagger-ui/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
142
handler/swagger-ui/swaggerui.go
Normal file
142
handler/swagger-ui/swaggerui.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package swaggerui // import "go.unistack.org/micro-server-http/v3/handler/swagger-ui"
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
//go:embed *.js *.css *.html *.png
|
||||
var assets embed.FS
|
||||
|
||||
var (
|
||||
Handler = func(prefix string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet || path.Base(r.URL.Path) != "swagger-initializer.js" {
|
||||
http.StripPrefix(prefix, http.FileServer(http.FS(assets))).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
tpl := template.New("swagger-initializer.js").Funcs(TemplateFuncs)
|
||||
ptpl, err := tpl.Parse(Template)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
if err := ptpl.Execute(w, Config); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateFuncs = template.FuncMap{
|
||||
"isInt": func(i interface{}) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
"isBool": func(i interface{}) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
"isString": func(i interface{}) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
"isSlice": func(i interface{}) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
"isMap": func(i interface{}) bool {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Map:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
Template = `
|
||||
window.onload = function() {
|
||||
//<editor-fold desc="Changeable Configuration Block">
|
||||
|
||||
window.ui = SwaggerUIBundle({
|
||||
{{- range $k, $v := . }}
|
||||
{{- if (eq (printf "%s" $v) "") -}}
|
||||
{{- continue -}}
|
||||
{{ end }}
|
||||
{{ $k }}: {{ if isBool $v -}}
|
||||
{{- $v -}},
|
||||
{{- else if isInt $v -}}
|
||||
{{- $v -}},
|
||||
{{- else if isString $v -}}
|
||||
"{{- $v -}}",
|
||||
{{- else if and (isSlice $v) (or (eq (printf "%s" $k) "presets") (eq (printf "%s" $k) "plugins")) -}}
|
||||
[
|
||||
{{- range $v }}
|
||||
{{ . }},
|
||||
{{- end }}
|
||||
],
|
||||
{{- end -}}
|
||||
{{ end }}
|
||||
});
|
||||
|
||||
//</editor-fold>
|
||||
};`
|
||||
Config = map[string]interface{}{
|
||||
"configUrl": "",
|
||||
"dom_id": "#swagger-ui",
|
||||
/*
|
||||
"domNode": "",
|
||||
"spec": "",
|
||||
"urls": []interface{}{
|
||||
map[string]interface{}{
|
||||
"url": "",
|
||||
"name": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
"url": "https://petstore.swagger.io/v2/swagger.json",
|
||||
"deepLinking": true,
|
||||
"displayOperationId": false,
|
||||
"defaultModelsExpandDepth": 1,
|
||||
"defaultModelExpandDepth": 1,
|
||||
"displayRequestDuration": true,
|
||||
"filter": true,
|
||||
"operationsSorter": "alpha",
|
||||
"showExtensions": true,
|
||||
"tryItOutEnabled": true,
|
||||
"presets": []string{
|
||||
"SwaggerUIBundle.presets.apis",
|
||||
"SwaggerUIStandalonePreset",
|
||||
},
|
||||
"plugins": []string{
|
||||
"SwaggerUIBundle.plugins.DownloadUrl",
|
||||
},
|
||||
"layout": "StandaloneLayout",
|
||||
}
|
||||
)
|
15
handler/swagger-ui/swaggerui_test.go
Normal file
15
handler/swagger-ui/swaggerui_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package swaggerui
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
t.Skip()
|
||||
h := http.NewServeMux()
|
||||
h.HandleFunc("/", Handler(""))
|
||||
if err := http.ListenAndServe(":8080", h); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
103
http.go
103
http.go
@@ -23,9 +23,9 @@ import (
|
||||
"golang.org/x/net/netutil"
|
||||
)
|
||||
|
||||
var _ server.Server = &httpServer{}
|
||||
var _ server.Server = (*Server)(nil)
|
||||
|
||||
type httpServer struct {
|
||||
type Server struct {
|
||||
hd server.Handler
|
||||
rsvc *register.Service
|
||||
handlers map[string]server.Handler
|
||||
@@ -40,7 +40,7 @@ type httpServer struct {
|
||||
init bool
|
||||
}
|
||||
|
||||
func (h *httpServer) newCodec(ct string) (codec.Codec, error) {
|
||||
func (h *Server) newCodec(ct string) (codec.Codec, error) {
|
||||
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
|
||||
ct = ct[:idx]
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func (h *httpServer) newCodec(ct string) (codec.Codec, error) {
|
||||
return nil, codec.ErrUnknownContentType
|
||||
}
|
||||
|
||||
func (h *httpServer) Options() server.Options {
|
||||
func (h *Server) Options() server.Options {
|
||||
h.Lock()
|
||||
opts := h.opts
|
||||
h.Unlock()
|
||||
return opts
|
||||
}
|
||||
|
||||
func (h *httpServer) Init(opts ...server.Option) error {
|
||||
func (h *Server) Init(opts ...server.Option) error {
|
||||
if len(opts) == 0 && h.init {
|
||||
return nil
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (h *httpServer) Init(opts ...server.Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Handle(handler server.Handler) error {
|
||||
func (h *Server) Handle(handler server.Handler) error {
|
||||
// passed unknown handler
|
||||
hdlr, ok := handler.(*httpHandler)
|
||||
if !ok {
|
||||
@@ -159,7 +159,7 @@ func (h *httpServer) Handle(handler server.Handler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
||||
func (h *Server) NewHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
|
||||
options := server.NewHandlerOptions(opts...)
|
||||
|
||||
eps := make([]*register.Endpoint, 0, len(options.Metadata))
|
||||
@@ -235,14 +235,64 @@ func (h *httpServer) NewHandler(handler interface{}, opts ...server.HandlerOptio
|
||||
}
|
||||
}
|
||||
|
||||
metadata, ok := options.Context.Value(handlerEndpointsKey{}).([]EndpointMetadata)
|
||||
if !ok {
|
||||
return hdlr
|
||||
}
|
||||
|
||||
for _, md := range metadata {
|
||||
hn := md.Name
|
||||
var method reflect.Method
|
||||
mname := hn[strings.Index(hn, ".")+1:]
|
||||
for m := 0; m < tp.NumMethod(); m++ {
|
||||
mn := tp.Method(m)
|
||||
if mn.Name != mname {
|
||||
continue
|
||||
}
|
||||
method = mn
|
||||
break
|
||||
}
|
||||
|
||||
if method.Name == "" && h.opts.Logger.V(logger.ErrorLevel) {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "nil method for %s", mname)
|
||||
continue
|
||||
}
|
||||
|
||||
mtype, err := prepareEndpoint(method)
|
||||
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "%v", err)
|
||||
continue
|
||||
} else if mtype == nil {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "nil mtype for %s", mname)
|
||||
continue
|
||||
}
|
||||
|
||||
rcvr := reflect.ValueOf(handler)
|
||||
name := reflect.Indirect(rcvr).Type().Name()
|
||||
|
||||
pth := &patHandler{mtype: mtype, name: name, rcvr: rcvr}
|
||||
hdlr.name = name
|
||||
|
||||
if err := hdlr.handlers.Insert([]string{md.Method}, md.Path, pth); err != nil {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "cant add handler for %s %s", md.Method, md.Path)
|
||||
}
|
||||
|
||||
if h.registerRPC {
|
||||
h.opts.Logger.Infof(h.opts.Context, "register rpc handler for http.MethodPost %s /%s", hn, hn)
|
||||
if err := hdlr.handlers.Insert([]string{http.MethodPost}, "/"+hn, pth); err != nil {
|
||||
h.opts.Logger.Errorf(h.opts.Context, "cant add rpc handler for http.MethodPost %s /%s", hn, hn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hdlr
|
||||
}
|
||||
|
||||
func (h *httpServer) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
||||
func (h *Server) NewSubscriber(topic string, handler interface{}, opts ...server.SubscriberOption) server.Subscriber {
|
||||
return newSubscriber(topic, handler, opts...)
|
||||
}
|
||||
|
||||
func (h *httpServer) Subscribe(sb server.Subscriber) error {
|
||||
func (h *Server) Subscribe(sb server.Subscriber) error {
|
||||
sub, ok := sb.(*httpSubscriber)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid subscriber: expected *httpSubscriber")
|
||||
@@ -267,7 +317,7 @@ func (h *httpServer) Subscribe(sb server.Subscriber) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Register() error {
|
||||
func (h *Server) Register() error {
|
||||
var eps []*register.Endpoint
|
||||
h.RLock()
|
||||
for _, hdlr := range h.handlers {
|
||||
@@ -328,6 +378,17 @@ func (h *httpServer) Register() error {
|
||||
}
|
||||
|
||||
h.Lock()
|
||||
|
||||
h.registered = true
|
||||
h.rsvc = service
|
||||
h.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Server) subscribe() error {
|
||||
config := h.opts
|
||||
|
||||
for sb := range h.subscribers {
|
||||
handler := h.createSubHandler(sb, config)
|
||||
var opts []broker.SubscribeOption
|
||||
@@ -351,14 +412,10 @@ func (h *httpServer) Register() error {
|
||||
h.subscribers[sb] = []broker.Subscriber{sub}
|
||||
}
|
||||
|
||||
h.registered = true
|
||||
h.rsvc = service
|
||||
h.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Deregister() error {
|
||||
func (h *Server) Deregister() error {
|
||||
h.RLock()
|
||||
config := h.opts
|
||||
h.RUnlock()
|
||||
@@ -406,7 +463,7 @@ func (h *httpServer) Deregister() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Start() error {
|
||||
func (h *Server) Start() error {
|
||||
h.RLock()
|
||||
config := h.opts
|
||||
h.RUnlock()
|
||||
@@ -489,6 +546,10 @@ func (h *httpServer) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.subscribe(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fn := handler
|
||||
|
||||
if h.opts.Context != nil {
|
||||
@@ -586,27 +647,27 @@ func (h *httpServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *httpServer) Stop() error {
|
||||
func (h *Server) Stop() error {
|
||||
ch := make(chan error)
|
||||
h.exit <- ch
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (h *httpServer) String() string {
|
||||
func (h *Server) String() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (h *httpServer) Name() string {
|
||||
func (h *Server) Name() string {
|
||||
return h.opts.Name
|
||||
}
|
||||
|
||||
func NewServer(opts ...server.Option) *httpServer {
|
||||
func NewServer(opts ...server.Option) *Server {
|
||||
options := server.NewOptions(opts...)
|
||||
eh := DefaultErrorHandler
|
||||
if v, ok := options.Context.Value(errorHandlerKey{}).(errorHandler); ok && v != nil {
|
||||
eh = v
|
||||
}
|
||||
return &httpServer{
|
||||
return &Server{
|
||||
opts: options,
|
||||
exit: make(chan chan error),
|
||||
subscribers: make(map[*httpSubscriber][]broker.Subscriber),
|
||||
|
18
options.go
18
options.go
@@ -85,8 +85,8 @@ func Middleware(mw ...func(http.Handler) http.Handler) server.Option {
|
||||
|
||||
type serverKey struct{}
|
||||
|
||||
// Server provide ability to pass *http.Server
|
||||
func Server(hs *http.Server) server.Option {
|
||||
// HTTPServer provide ability to pass *http.Server
|
||||
func HTTPServer(hs *http.Server) server.Option {
|
||||
return server.SetOption(serverKey{}, hs)
|
||||
}
|
||||
|
||||
@@ -133,13 +133,19 @@ func RegisterRPCHandler(b bool) server.Option {
|
||||
return server.SetOption(registerRPCHandlerKey{}, b)
|
||||
}
|
||||
|
||||
type handlerMetadataKey struct{}
|
||||
type handlerEndpointsKey struct{}
|
||||
|
||||
func HandlerMetadata(md map[string]map[string]string) server.HandlerOption {
|
||||
return server.SetHandlerOption(handlerMetadataKey{}, md)
|
||||
type EndpointMetadata struct {
|
||||
Name string
|
||||
Path string
|
||||
Method string
|
||||
Body string
|
||||
Stream bool
|
||||
}
|
||||
|
||||
type headerKey struct{}
|
||||
func HandlerEndpoints(md []EndpointMetadata) server.HandlerOption {
|
||||
return server.SetHandlerOption(handlerEndpointsKey{}, md)
|
||||
}
|
||||
|
||||
type handlerOptions struct {
|
||||
headers []string
|
||||
|
@@ -101,7 +101,7 @@ func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOptio
|
||||
}
|
||||
}
|
||||
|
||||
func (s *httpServer) createSubHandler(sb *httpSubscriber, opts server.Options) broker.Handler {
|
||||
func (s *Server) createSubHandler(sb *httpSubscriber, opts server.Options) broker.Handler {
|
||||
return func(p broker.Event) error {
|
||||
msg := p.Message()
|
||||
ct := msg.Header["Content-Type"]
|
||||
|
Reference in New Issue
Block a user