package http import ( "bytes" "context" "encoding/json" "io/ioutil" "net/http" "net/url" "reflect" "strconv" "strings" "github.com/chuangxin1/httprouter" "github.com/go-kit/kit/circuitbreaker" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" "github.com/sony/gobreaker" "git.chuangxin1.com/csacred/toolkit" httptransport "github.com/go-kit/kit/transport/http" ) // EncodeResponseFunc encodes the passed response object to the HTTP response // writer. It's designed to be used in HTTP servers, for server-side // endpoints. One straightforward EncodeResponseFunc could be something that // JSON encodes the object directly to the response body. type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error // DecodeResponseFunc extracts a user-domain response object from an HTTP // response object. It's designed to be used in HTTP clients, for client-side // endpoints. One straightforward DecodeResponseFunc could be something that // JSON decodes from the response body to the concrete response type. type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error) // RouteVars returns the route variables for the current request, if any. func RouteVars(ctx context.Context) map[string]string { return httprouter.ContextVars(ctx) } // ContextVars returns the func ContextVars(ctx context.Context, key interface{}) interface{} { return ctx.Value(key) } // CopyURL copy url func CopyURL(base *url.URL, path string) *url.URL { next := *base next.Path = path return &next } func routePath(ctx context.Context, req *http.Request) { path := req.URL.Path prefix, _ := ctx.Value(toolkit.ContextKeyGateWayPrefix).(string) routePath := httprouter.ContextRoutePath(ctx) if prefix != "" && routePath != "" { if strings.Contains(routePath, prefix) { absolutePath := routePath[len(prefix):] if absolutePath != path { req.URL.Path = absolutePath } } } } // URLValuesStruct convert struct to url.Values func URLValuesStruct(obj interface{}) url.Values { t := reflect.TypeOf(obj) v := reflect.ValueOf(obj) values := url.Values{} for i := 0; i < t.NumField(); i++ { key := t.Field(i).Tag.Get("form") value := format(v.Field(i), v.Field(i).Interface()) values.Add(key, value) } return values } func format(v reflect.Value, data interface{}) string { var s string switch v.Kind() { case reflect.String: s = data.(string) case reflect.Int: s = strconv.FormatInt(int64(data.(int)), 10) case reflect.Uint: s = strconv.FormatUint(data.(uint64), 10) case reflect.Bool: s = strconv.FormatBool(data.(bool)) case reflect.Float32: case reflect.Float64: s = strconv.FormatFloat(data.(float64), 'f', -1, 32) default: s = "" // fmt.Sprintf("unsupported kind %s", v.Type()) } return s } // ClientEncodeGetRequest client get encode request func ClientEncodeGetRequest(ctx context.Context, req *http.Request, request interface{}) error { values := URLValuesStruct(request) auth, ok := ctx.Value(toolkit.ContextKeyRequestAuthorization).(string) if ok { req.Header.Set(toolkit.HTTPHeaderAuthorization, auth) } routePath(ctx, req) token, _ := ctx.Value(toolkit.ContextKeyAccessToken).(string) if token != "" { values.Set(toolkit.VarUserAuthorization, token) } req.URL.RawQuery = values.Encode() return nil } // ClientEncodeJSONRequest is an EncodeRequestFunc that serializes the request // as a JSON object to the Request body. Many JSON-over-HTTP services can use // it as a sensible default. If the request implements Headerer, the provided // headers will be applied to the request. func ClientEncodeJSONRequest(ctx context.Context, req *http.Request, request interface{}) error { req.Header.Set("Content-Type", "application/json; charset=utf-8") routePath(ctx, req) if headerer, ok := request.(httptransport.Headerer); ok { for k := range headerer.Headers() { req.Header.Set(k, headerer.Headers().Get(k)) } } if auth, ok := ctx.Value(toolkit.ContextKeyRequestAuthorization).(string); ok { req.Header.Set("Authorization", auth) } values := url.Values{} token, _ := ctx.Value(toolkit.ContextKeyAccessToken).(string) if token != "" { values.Set(toolkit.VarUserAuthorization, token) } req.URL.RawQuery = values.Encode() var b bytes.Buffer req.Body = ioutil.NopCloser(&b) return json.NewEncoder(&b).Encode(request) } // ClientRequestEndpoint client request Endpoint func ClientRequestEndpoint(ctx context.Context, u *url.URL, method, router string, dec DecodeResponseFunc) endpoint.Endpoint { var e endpoint.Endpoint options := []httptransport.ClientOption{} var enc httptransport.EncodeRequestFunc switch method { case "POST": enc = ClientEncodeJSONRequest default: enc = ClientEncodeGetRequest } e = httptransport.NewClient( method, CopyURL(u, router), enc, httptransport.DecodeResponseFunc(dec), options..., ).Endpoint() e = circuitbreaker.Gobreaker( gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e) //e = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), qps))(e) return e } // ClientLoadBalancer load balance func ClientLoadBalancer(endpoints sd.FixedEndpointer) endpoint.Endpoint { balancer := lb.NewRoundRobin(endpoints) return lb.Retry(retryMax, retryTimeout, balancer) }