fc/http_handler.go (114 lines of code) (raw):
// Copyright 2021 Alibaba Group Holding Limited. All Rights Reserved.
// Copyright 2021 Alibaba Group Holding Limited. All Rights Reserved.
package fc
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"reflect"
)
type HttpHandler interface {
Invoke(ctx context.Context, w http.ResponseWriter, req *http.Request) error
}
// fcHandler is the generic function type
type fcHttpHandler func(context.Context, http.ResponseWriter, *http.Request) error
// Invoke calls the handler, and serializes the response.
// If the underlying handler returned an error, or an error occurs during serialization, error is returned.
func (handler fcHttpHandler) Invoke(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
return handler(ctx, w, req)
}
func errorHttpHandler(e error) fcHttpHandler {
return func(ctx context.Context, rsp http.ResponseWriter, req *http.Request) error {
return e
}
}
func validateHttpArguments(handler reflect.Type) error {
if handler.NumIn() != 3 {
return fmt.Errorf("http handler should takes three arguments, but handler takes %d", handler.NumIn())
}
contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
argumentType := handler.In(0)
if !argumentType.Implements(contextType) {
return fmt.Errorf("handler takes three arguments, but the first is not Context. got %s",
argumentType.Kind())
}
responseWriterType := reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
argumentType = handler.In(1)
if !argumentType.Implements(responseWriterType) {
return fmt.Errorf("handler takes three arguments, but the second is not http.ResponseWriter. got %s",
argumentType.Kind())
}
return nil
}
func validateHttpReturns(handler reflect.Type) error {
errorType := reflect.TypeOf((*error)(nil)).Elem()
switch n := handler.NumOut(); {
case n > 1:
return fmt.Errorf("handler may not return more than one values")
case n == 1:
if !handler.Out(0).Implements(errorType) {
return fmt.Errorf("handler returns a single value, but it does not implement error")
}
}
return nil
}
// NewHttpHandler creates a http fc handler from the given handler function. The
// returned Handler performs JSON serialization and deserialization, and
// delegates to the input handler function. The handler function parameter must
// satisfy the rules documented by Start. If handlerFunc is not a valid
// handler, the returned Handler simply reports the validation error.
func NewHttpHandler(handlerFunc interface{}) HttpHandler {
if handlerFunc == nil {
return errorHttpHandler(fmt.Errorf("handler is nil"))
}
handler := reflect.ValueOf(handlerFunc)
handlerType := reflect.TypeOf(handlerFunc)
if handlerType.Kind() != reflect.Func {
return errorHttpHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
}
err := validateHttpArguments(handlerType)
if err != nil {
return errorHttpHandler(err)
}
if err := validateHttpReturns(handlerType); err != nil {
return errorHttpHandler(err)
}
return fcHttpHandler(func(ctx context.Context, rsp http.ResponseWriter, req *http.Request) error {
// construct arguments
args := []reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(rsp),
reflect.ValueOf(req),
}
response := handler.Call(args)
// convert return values into ( error)
var err error
if len(response) > 0 {
if errVal, ok := response[len(response)-1].Interface().(error); ok {
err = errVal
}
}
return err
})
}
type HTTPParams struct {
Path string `json:"path"`
Method string `json:"method"`
RequestURI string `json:"requestURI"`
ClientIP string `json:"clientIP"`
Host string `json:"host"`
QueriesMap map[string][]string `json:"queriesMap"`
HeadersMap map[string][]string `json:"headersMap"`
}
func genHttpRequest(httpParams string, payload []byte) (*http.Request, error) {
data, err := base64.StdEncoding.DecodeString(httpParams)
if err != nil {
return nil, err
}
var params HTTPParams
if err = json.Unmarshal(data, ¶ms); err != nil {
return nil, err
}
// generate req.RequestURI, req.URL.{Path, RawQuery}
req, err := http.NewRequest(params.Method, params.RequestURI, bytes.NewBuffer(payload))
if err != nil {
return nil, err
}
req.URL.Path = params.Path
req.URL.Host = params.Host
req.RemoteAddr = params.ClientIP
req.Host = params.ClientIP
req.Header = params.HeadersMap
req.RequestURI = params.RequestURI
return req, nil
}