From 5376c788db483f9ea2c8505aedba2b811b430b1b Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Mon, 30 Mar 2020 11:04:59 +0300 Subject: [PATCH] api: add static router and improve path parser in rpc handler (#1437) * api: add static router and improve path parser in rpc handler Signed-off-by: Vasiliy Tolstov * expose metadata context key to be able to get unmodified map keys Signed-off-by: Vasiliy Tolstov * server/grpc: fix jsonpb codec for protobuf msg Signed-off-by: Vasiliy Tolstov * api/handler/rpc: write 204 status code when rsp is nil Signed-off-by: Vasiliy Tolstov * api/handler/rpc: add check for nil response for non javascript Signed-off-by: Vasiliy Tolstov --- rpc.go | 121 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 27 deletions(-) diff --git a/rpc.go b/rpc.go index 13c4207..c6bd6da 100644 --- a/rpc.go +++ b/rpc.go @@ -12,7 +12,7 @@ import ( "github.com/joncalhoun/qson" "github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v2/api/handler" - proto "github.com/micro/go-micro/v2/api/internal/proto" + "github.com/micro/go-micro/v2/api/internal/proto" "github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/codec" @@ -20,6 +20,7 @@ import ( "github.com/micro/go-micro/v2/codec/protorpc" "github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/logger" + "github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v2/util/ctx" "github.com/oxtoacart/bpool" @@ -128,7 +129,6 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { so := selector.WithStrategy(strategy(service.Services)) // walk the standard call path - // get payload br, err := requestPayload(r) if err != nil { @@ -164,7 +164,12 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // marshall response - rsp, _ = response.Marshal() + rsp, err = response.Marshal() + if err != nil { + writeError(w, r, err) + return + } + default: // if json codec is not present set to json if !hasCodec(ct, jsonCodecs) { @@ -195,7 +200,11 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // marshall response - rsp, _ = response.MarshalJSON() + rsp, err = response.MarshalJSON() + if err != nil { + writeError(w, r, err) + return + } } // write the response @@ -219,8 +228,11 @@ func hasCodec(ct string, codecs []string) bool { // If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned. // If the request method is a POST the request body is read and returned func requestPayload(r *http.Request) ([]byte, error) { + var err error + // we have to decode json-rpc and proto-rpc because we suck // well actually because there's no proxy codec right now + ct := r.Header.Get("Content-Type") switch { case strings.Contains(ct, "application/json-rpc"): @@ -229,11 +241,11 @@ func requestPayload(r *http.Request) ([]byte, error) { Header: make(map[string]string), } c := jsonrpc.NewCodec(&buffer{r.Body}) - if err := c.ReadHeader(&msg, codec.Request); err != nil { + if err = c.ReadHeader(&msg, codec.Request); err != nil { return nil, err } var raw json.RawMessage - if err := c.ReadBody(&raw); err != nil { + if err = c.ReadBody(&raw); err != nil { return nil, err } return ([]byte)(raw), nil @@ -243,15 +255,14 @@ func requestPayload(r *http.Request) ([]byte, error) { Header: make(map[string]string), } c := protorpc.NewCodec(&buffer{r.Body}) - if err := c.ReadHeader(&msg, codec.Request); err != nil { + if err = c.ReadHeader(&msg, codec.Request); err != nil { return nil, err } var raw proto.Message - if err := c.ReadBody(&raw); err != nil { + if err = c.ReadBody(&raw); err != nil { return nil, err } - b, err := raw.Marshal() - return b, err + return raw.Marshal() case strings.Contains(ct, "application/www-x-form-urlencoded"): r.ParseForm() @@ -262,43 +273,94 @@ func requestPayload(r *http.Request) ([]byte, error) { } // marshal - b, err := json.Marshal(vals) - return b, err + return json.Marshal(vals) // TODO: application/grpc } // otherwise as per usual + ctx := r.Context() + // dont user meadata.FromContext as it mangles names + md, ok := ctx.Value(metadata.MetadataKey{}).(metadata.Metadata) + if !ok { + md = make(map[string]string) + } + // allocate maximum + matches := make(map[string]string, len(md)) + for k, v := range md { + if strings.HasPrefix(k, "x-api-field-") { + matches[strings.TrimPrefix(k, "x-api-field-")] = v + } + delete(md, k) + } + + // restore context without fields + ctx = metadata.NewContext(ctx, md) + *r = *r.WithContext(ctx) + req := make(map[string]interface{}, len(md)) + for k, v := range matches { + ps := strings.Split(k, ".") + if len(ps) == 1 { + req[k] = v + continue + } + + em := make(map[string]interface{}) + em[ps[len(ps)-1]] = v + for i := len(ps) - 2; i > 0; i-- { + nm := make(map[string]interface{}) + nm[ps[i]] = em + em = nm + } + req[ps[0]] = em + } + pathbuf := []byte("{}") + if len(req) > 0 { + pathbuf, err = json.Marshal(req) + if err != nil { + return nil, err + } + } + urlbuf := []byte("{}") + if len(r.URL.RawQuery) > 0 { + urlbuf, err = qson.ToJSON(r.URL.RawQuery) + if err != nil { + return nil, err + } + } + + out, err := jsonpatch.MergeMergePatches(urlbuf, pathbuf) + if err != nil { + return nil, err + } switch r.Method { case "GET": - if len(r.URL.RawQuery) > 0 { - return qson.ToJSON(r.URL.RawQuery) + // empty response + if strings.Contains(ct, "application/json") && string(out) == "{}" { + return out, nil + } else if string(out) == "{}" && !strings.Contains(ct, "application/json") { + return []byte{}, nil } + return out, nil case "PATCH", "POST", "PUT", "DELETE": - urlParams := []byte("{}") - bodyParams := []byte("{}") - var err error - if len(r.URL.RawQuery) > 0 { - if urlParams, err = qson.ToJSON(r.URL.RawQuery); err != nil { - return nil, err - } - } - + bodybuf := []byte("{}") buf := bufferPool.Get() defer bufferPool.Put(buf) if _, err := buf.ReadFrom(r.Body); err != nil { return nil, err } if b := buf.Bytes(); len(b) > 0 { - bodyParams = b + bodybuf = b + } else { + return []byte{}, nil } - if out, err := jsonpatch.MergeMergePatches(urlParams, bodyParams); err == nil { + if out, err = jsonpatch.MergeMergePatches(out, bodybuf); err == nil { return out, nil } //fallback to previous unknown behaviour - return buf.Bytes(), nil + return bodybuf, nil } @@ -332,7 +394,7 @@ func writeError(w http.ResponseWriter, r *http.Request, err error) { } _, werr := w.Write([]byte(ce.Error())) - if err != nil { + if werr != nil { if logger.V(logger.ErrorLevel, logger.DefaultLogger) { logger.Error(werr) } @@ -351,6 +413,11 @@ func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) { w.Header().Set("grpc-message", "") } + // write 204 status if rsp is nil + if len(rsp) == 0 { + w.WriteHeader(http.StatusNoContent) + } + // write response _, err := w.Write(rsp) if err != nil {