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:
parent
da31e36f93
commit
5376c788db
121
rpc.go
121
rpc.go
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user