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 <v.tolstov@unistack.org>

* expose metadata context key to be able to get unmodified map keys

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* server/grpc: fix jsonpb codec for protobuf msg

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* api/handler/rpc: write 204 status code when rsp is nil

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* api/handler/rpc: add check for nil response for non javascript

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2020-03-30 11:04:59 +03:00
parent da31e36f93
commit 5376c788db

121
rpc.go
View File

@ -12,7 +12,7 @@ import (
"github.com/joncalhoun/qson" "github.com/joncalhoun/qson"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v2/api"
"github.com/micro/go-micro/v2/api/handler" "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"
"github.com/micro/go-micro/v2/client/selector" "github.com/micro/go-micro/v2/client/selector"
"github.com/micro/go-micro/v2/codec" "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/codec/protorpc"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/logger" "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/registry"
"github.com/micro/go-micro/v2/util/ctx" "github.com/micro/go-micro/v2/util/ctx"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
@ -128,7 +129,6 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
so := selector.WithStrategy(strategy(service.Services)) so := selector.WithStrategy(strategy(service.Services))
// walk the standard call path // walk the standard call path
// get payload // get payload
br, err := requestPayload(r) br, err := requestPayload(r)
if err != nil { if err != nil {
@ -164,7 +164,12 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
// marshall response // marshall response
rsp, _ = response.Marshal() rsp, err = response.Marshal()
if err != nil {
writeError(w, r, err)
return
}
default: default:
// if json codec is not present set to json // if json codec is not present set to json
if !hasCodec(ct, jsonCodecs) { if !hasCodec(ct, jsonCodecs) {
@ -195,7 +200,11 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
// marshall response // marshall response
rsp, _ = response.MarshalJSON() rsp, err = response.MarshalJSON()
if err != nil {
writeError(w, r, err)
return
}
} }
// write the response // 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 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 // If the request method is a POST the request body is read and returned
func requestPayload(r *http.Request) ([]byte, error) { func requestPayload(r *http.Request) ([]byte, error) {
var err error
// we have to decode json-rpc and proto-rpc because we suck // we have to decode json-rpc and proto-rpc because we suck
// well actually because there's no proxy codec right now // well actually because there's no proxy codec right now
ct := r.Header.Get("Content-Type") ct := r.Header.Get("Content-Type")
switch { switch {
case strings.Contains(ct, "application/json-rpc"): case strings.Contains(ct, "application/json-rpc"):
@ -229,11 +241,11 @@ func requestPayload(r *http.Request) ([]byte, error) {
Header: make(map[string]string), Header: make(map[string]string),
} }
c := jsonrpc.NewCodec(&buffer{r.Body}) 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 return nil, err
} }
var raw json.RawMessage var raw json.RawMessage
if err := c.ReadBody(&raw); err != nil { if err = c.ReadBody(&raw); err != nil {
return nil, err return nil, err
} }
return ([]byte)(raw), nil return ([]byte)(raw), nil
@ -243,15 +255,14 @@ func requestPayload(r *http.Request) ([]byte, error) {
Header: make(map[string]string), Header: make(map[string]string),
} }
c := protorpc.NewCodec(&buffer{r.Body}) 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 return nil, err
} }
var raw proto.Message var raw proto.Message
if err := c.ReadBody(&raw); err != nil { if err = c.ReadBody(&raw); err != nil {
return nil, err return nil, err
} }
b, err := raw.Marshal() return raw.Marshal()
return b, err
case strings.Contains(ct, "application/www-x-form-urlencoded"): case strings.Contains(ct, "application/www-x-form-urlencoded"):
r.ParseForm() r.ParseForm()
@ -262,43 +273,94 @@ func requestPayload(r *http.Request) ([]byte, error) {
} }
// marshal // marshal
b, err := json.Marshal(vals) return json.Marshal(vals)
return b, err
// TODO: application/grpc // TODO: application/grpc
} }
// otherwise as per usual // 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 { switch r.Method {
case "GET": case "GET":
if len(r.URL.RawQuery) > 0 { // empty response
return qson.ToJSON(r.URL.RawQuery) 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": case "PATCH", "POST", "PUT", "DELETE":
urlParams := []byte("{}") bodybuf := []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
}
}
buf := bufferPool.Get() buf := bufferPool.Get()
defer bufferPool.Put(buf) defer bufferPool.Put(buf)
if _, err := buf.ReadFrom(r.Body); err != nil { if _, err := buf.ReadFrom(r.Body); err != nil {
return nil, err return nil, err
} }
if b := buf.Bytes(); len(b) > 0 { 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 return out, nil
} }
//fallback to previous unknown behaviour //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())) _, werr := w.Write([]byte(ce.Error()))
if err != nil { if werr != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Error(werr) logger.Error(werr)
} }
@ -351,6 +413,11 @@ func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) {
w.Header().Set("grpc-message", "") w.Header().Set("grpc-message", "")
} }
// write 204 status if rsp is nil
if len(rsp) == 0 {
w.WriteHeader(http.StatusNoContent)
}
// write response // write response
_, err := w.Write(rsp) _, err := w.Write(rsp)
if err != nil { if err != nil {