2016-06-30 22:21:57 +03:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2021-02-05 18:31:51 +03:00
|
|
|
"context"
|
|
|
|
"fmt"
|
2021-02-06 18:53:25 +03:00
|
|
|
"io"
|
2021-02-05 18:31:51 +03:00
|
|
|
"net/http"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/unistack-org/micro/v3/codec"
|
2021-02-25 14:16:55 +03:00
|
|
|
"github.com/unistack-org/micro/v3/errors"
|
2021-02-05 18:31:51 +03:00
|
|
|
"github.com/unistack-org/micro/v3/logger"
|
|
|
|
"github.com/unistack-org/micro/v3/metadata"
|
2021-01-29 14:32:32 +03:00
|
|
|
"github.com/unistack-org/micro/v3/register"
|
2020-10-10 00:38:35 +03:00
|
|
|
"github.com/unistack-org/micro/v3/server"
|
2021-02-05 18:31:51 +03:00
|
|
|
rflutil "github.com/unistack-org/micro/v3/util/reflect"
|
|
|
|
rutil "github.com/unistack-org/micro/v3/util/router"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
DefaultErrorHandler = func(ctx context.Context, s server.Handler, w http.ResponseWriter, r *http.Request, err error, status int) {
|
|
|
|
w.WriteHeader(status)
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
}
|
|
|
|
DefaultContentType = "application/json"
|
2016-06-30 22:21:57 +03:00
|
|
|
)
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
type patHandler struct {
|
|
|
|
pat rutil.Pattern
|
|
|
|
mtype *methodType
|
|
|
|
name string
|
|
|
|
rcvr reflect.Value
|
|
|
|
}
|
|
|
|
|
2016-06-30 22:21:57 +03:00
|
|
|
type httpHandler struct {
|
2021-03-09 14:19:59 +03:00
|
|
|
name string
|
|
|
|
opts server.HandlerOptions
|
|
|
|
sopts server.Options
|
|
|
|
eps []*register.Endpoint
|
|
|
|
hd interface{}
|
|
|
|
handlers map[string][]patHandler
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *httpHandler) newCodec(ct string) (codec.Codec, error) {
|
|
|
|
if cf, ok := h.sopts.Codecs[ct]; ok {
|
|
|
|
return cf, nil
|
|
|
|
}
|
|
|
|
return nil, codec.ErrUnknownContentType
|
2016-06-30 22:21:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *httpHandler) Name() string {
|
2021-02-05 18:31:51 +03:00
|
|
|
return h.name
|
2016-06-30 22:21:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *httpHandler) Handler() interface{} {
|
|
|
|
return h.hd
|
|
|
|
}
|
|
|
|
|
2021-01-29 14:32:32 +03:00
|
|
|
func (h *httpHandler) Endpoints() []*register.Endpoint {
|
2017-04-03 17:03:46 +03:00
|
|
|
return h.eps
|
2016-06-30 22:21:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *httpHandler) Options() server.HandlerOptions {
|
|
|
|
return h.opts
|
|
|
|
}
|
2021-02-05 18:31:51 +03:00
|
|
|
|
2021-03-09 14:19:59 +03:00
|
|
|
func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2021-03-01 13:02:35 +03:00
|
|
|
ctx := metadata.NewContext(r.Context(), nil)
|
2021-02-05 18:31:51 +03:00
|
|
|
|
2021-02-06 18:53:25 +03:00
|
|
|
defer r.Body.Close()
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
path := r.URL.Path
|
|
|
|
if !strings.HasPrefix(path, "/") {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("path must contains /"), http.StatusBadRequest)
|
2021-03-09 23:54:46 +03:00
|
|
|
return
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
ct := DefaultContentType
|
|
|
|
if htype := r.Header.Get("Content-Type"); htype != "" {
|
|
|
|
ct = htype
|
|
|
|
}
|
|
|
|
|
2021-03-31 00:49:55 +03:00
|
|
|
var cf codec.Codec
|
|
|
|
var err error
|
|
|
|
switch ct {
|
|
|
|
case "application/x-www-form-urlencoded":
|
|
|
|
cf, err = h.newCodec(DefaultContentType)
|
|
|
|
default:
|
|
|
|
cf, err = h.newCodec(ct)
|
|
|
|
}
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
if err != nil {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
2021-03-09 23:54:46 +03:00
|
|
|
return
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
components := strings.Split(path[1:], "/")
|
|
|
|
l := len(components)
|
|
|
|
var verb string
|
|
|
|
idx := strings.LastIndex(components[l-1], ":")
|
|
|
|
if idx == 0 {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not found"), http.StatusNotFound)
|
2021-02-05 18:31:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if idx > 0 {
|
|
|
|
c := components[l-1]
|
|
|
|
components[l-1], verb = c[:idx], c[idx+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
matches := make(map[string]interface{})
|
2021-03-31 00:49:55 +03:00
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
var match bool
|
|
|
|
var hldr patHandler
|
2021-03-09 14:19:59 +03:00
|
|
|
var handler *httpHandler
|
|
|
|
for _, hpat := range h.handlers {
|
|
|
|
handlertmp := hpat.(*httpHandler)
|
|
|
|
for _, hldrtmp := range handlertmp.handlers[r.Method] {
|
|
|
|
mp, err := hldrtmp.pat.Match(components, verb)
|
|
|
|
if err == nil {
|
|
|
|
match = true
|
|
|
|
for k, v := range mp {
|
|
|
|
matches[k] = v
|
|
|
|
}
|
|
|
|
hldr = hldrtmp
|
|
|
|
handler = handlertmp
|
|
|
|
break
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !match {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, nil, w, r, fmt.Errorf("not matching route found"), http.StatusNotFound)
|
2021-02-05 18:31:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-02-10 13:16:28 +03:00
|
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
2021-02-05 18:31:51 +03:00
|
|
|
if !ok {
|
|
|
|
md = metadata.New(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range r.Header {
|
|
|
|
md.Set(k, strings.Join(v, ", "))
|
|
|
|
}
|
|
|
|
|
2021-03-31 00:49:55 +03:00
|
|
|
var query string
|
|
|
|
switch ct {
|
|
|
|
case "application/x-www-form-urlencoded":
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
h.errorHandler(ctx, nil, w, r, err, http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
query = string(buf)
|
|
|
|
default:
|
|
|
|
query = r.URL.RawQuery
|
|
|
|
}
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
// get fields from url values
|
2021-03-31 00:49:55 +03:00
|
|
|
if len(query) > 0 {
|
|
|
|
umd, err := rflutil.URLMap(query)
|
2021-02-05 18:31:51 +03:00
|
|
|
if err != nil {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
2021-03-09 23:54:46 +03:00
|
|
|
return
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
for k, v := range umd {
|
|
|
|
matches[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
//function := hldr.rcvr
|
|
|
|
var returnValues []reflect.Value
|
|
|
|
|
2021-03-31 00:49:55 +03:00
|
|
|
if ct != "application/x-www-form-urlencoded" {
|
|
|
|
if err = cf.ReadBody(r.Body, argv.Interface()); err != nil && err != io.EOF {
|
|
|
|
h.errorHandler(ctx, handler, w, r, err, http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2021-02-06 18:53:25 +03:00
|
|
|
}
|
|
|
|
|
2021-02-06 18:16:29 +03:00
|
|
|
matches = rflutil.FlattenMap(matches)
|
2021-02-05 18:31:51 +03:00
|
|
|
if err = rflutil.MergeMap(argv.Interface(), matches); err != nil {
|
2021-03-09 14:19:59 +03:00
|
|
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
2021-02-05 18:31:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-31 00:49:55 +03:00
|
|
|
var b []byte
|
|
|
|
if ct != "application/x-www-form-urlencoded" {
|
|
|
|
b, err = cf.Marshal(argv.Interface())
|
|
|
|
if err != nil {
|
|
|
|
h.errorHandler(ctx, handler, w, r, err, http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
hr := &rpcRequest{
|
|
|
|
codec: cf,
|
2021-03-09 14:19:59 +03:00
|
|
|
service: handler.sopts.Name,
|
2021-02-05 18:31:51 +03:00
|
|
|
contentType: ct,
|
|
|
|
method: fmt.Sprintf("%s.%s", hldr.name, hldr.mtype.method.Name),
|
|
|
|
body: b,
|
|
|
|
payload: argv.Interface(),
|
|
|
|
}
|
|
|
|
|
|
|
|
var scode int
|
|
|
|
// define the handler func
|
2021-03-01 13:02:35 +03:00
|
|
|
fn := func(fctx context.Context, req server.Request, rsp interface{}) (err error) {
|
|
|
|
fctx = context.WithValue(fctx, rspCodeKey{}, &rspCodeVal{})
|
|
|
|
fctx = metadata.NewIncomingContext(fctx, md)
|
|
|
|
returnValues = function.Call([]reflect.Value{hldr.rcvr, hldr.mtype.prepareContext(fctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)})
|
2021-02-05 18:31:51 +03:00
|
|
|
|
2021-03-01 13:02:35 +03:00
|
|
|
scode = GetRspCode(fctx)
|
2021-02-05 18:31:51 +03:00
|
|
|
// The return value for the method is an error.
|
|
|
|
if rerr := returnValues[0].Interface(); rerr != nil {
|
|
|
|
err = rerr.(error)
|
|
|
|
}
|
|
|
|
|
2021-03-01 13:02:35 +03:00
|
|
|
if md, ok := metadata.FromOutgoingContext(fctx); ok {
|
|
|
|
metadata.SetOutgoingContext(ctx, md)
|
|
|
|
}
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// wrap the handler func
|
2021-03-09 14:19:59 +03:00
|
|
|
for i := len(handler.sopts.HdlrWrappers); i > 0; i-- {
|
|
|
|
fn = handler.sopts.HdlrWrappers[i-1](fn)
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if appErr := fn(ctx, hr, replyv.Interface()); appErr != nil {
|
2021-02-25 14:16:55 +03:00
|
|
|
switch verr := appErr.(type) {
|
|
|
|
case *errors.Error:
|
|
|
|
scode = int(verr.Code)
|
|
|
|
b, err = cf.Marshal(verr)
|
|
|
|
case *Error:
|
|
|
|
b, err = cf.Marshal(verr.err)
|
|
|
|
default:
|
|
|
|
b, err = cf.Marshal(appErr)
|
|
|
|
}
|
2021-02-05 18:31:51 +03:00
|
|
|
} else {
|
|
|
|
b, err = cf.Marshal(replyv.Interface())
|
|
|
|
}
|
2021-03-09 14:19:59 +03:00
|
|
|
if err != nil && handler.sopts.Logger.V(logger.ErrorLevel) {
|
|
|
|
handler.sopts.Logger.Errorf(handler.sopts.Context, "handler err: %v", err)
|
2021-02-05 18:31:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-01 11:35:19 +03:00
|
|
|
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
|
|
|
for k, v := range md {
|
|
|
|
w.Header().Set(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 18:31:51 +03:00
|
|
|
w.Header().Set("content-Type", ct)
|
|
|
|
if scode != 0 {
|
|
|
|
w.WriteHeader(scode)
|
|
|
|
} else {
|
2021-03-24 23:58:27 +03:00
|
|
|
//handler.sopts.Logger.Warn(handler.sopts.Context, "response code not set in handler via SetRspCode(ctx, http.StatusXXX)")
|
|
|
|
w.WriteHeader(200)
|
2021-02-05 18:31:51 +03:00
|
|
|
}
|
|
|
|
w.Write(b)
|
|
|
|
}
|