diff --git a/.github/stale.sh b/.github/stale.sh deleted file mode 100755 index 8a345c4..0000000 --- a/.github/stale.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -ex - -export PATH=$PATH:$(pwd)/bin -export GO111MODULE=on -export GOBIN=$(pwd)/bin - -#go get github.com/rvflash/goup@v0.4.1 - -#goup -v ./... -#go get github.com/psampaz/go-mod-outdated@v0.6.0 -go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true - -#go list -u -m -json all | go-mod-outdated -update diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb19b68..465645d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,10 +34,9 @@ jobs: uses: actions/checkout@v2 - name: lint uses: golangci/golangci-lint-action@v2 - continue-on-error: true with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.30 + version: v1.39 # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 545baf2..e339095 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,10 +34,9 @@ jobs: uses: actions/checkout@v2 - name: lint uses: golangci/golangci-lint-action@v2 - continue-on-error: true with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.30 + version: v1.39 # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6ff842d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,44 @@ +run: + concurrency: 4 + deadline: 5m + issues-exit-code: 1 + tests: true + +linters-settings: + govet: + check-shadowing: true + enable: + - fieldalignment + +linters: + enable: + - govet + - deadcode + - errcheck + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + - bodyclose + - gci + - goconst + - gocritic + - gosimple + - gofmt + - gofumpt + - goimports + - golint + - gosec + - makezero + - misspell + - nakedret + - nestif + - nilerr + - noctx + - prealloc + - unconvert + - unparam + disable-all: false diff --git a/go.mod b/go.mod index 563d76c..5664e64 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/unistack-org/micro-client-http/v3 go 1.16 -require github.com/unistack-org/micro/v3 v3.3.16 +require github.com/unistack-org/micro/v3 v3.3.14 diff --git a/go.sum b/go.sum index e04e3ae..e1e78fa 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= -github.com/unistack-org/micro/v3 v3.3.16 h1:v0h/oC0TO2n1djQJeOjD2jNEqKkiykwI6cpflEVTlQE= -github.com/unistack-org/micro/v3 v3.3.16/go.mod h1:ETGcQQUcjxGaD44LUMX+0fgo8Loh7ExldfIPLvfUmDo= +github.com/unistack-org/micro/v3 v3.3.14 h1:CAkDMjHZT8/D6GGF5h3gK84m6tlWZC17IGPb2GkAn/4= +github.com/unistack-org/micro/v3 v3.3.14/go.mod h1:ETGcQQUcjxGaD44LUMX+0fgo8Loh7ExldfIPLvfUmDo= golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/http.go b/http.go index f0d4615..273670a 100644 --- a/http.go +++ b/http.go @@ -20,21 +20,19 @@ import ( "github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/metadata" - "github.com/unistack-org/micro/v3/router" ) -var ( - DefaultContentType = "application/json" -) +var DefaultContentType = "application/json" +/* func filterLabel(r []router.Route) []router.Route { // selector.FilterLabel("protocol", "http") return r } +*/ type httpClient struct { opts client.Options - dialer *net.Dialer httpcli *http.Client init bool sync.RWMutex @@ -46,6 +44,7 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg var tags []string var scheme string + u, err := url.Parse(addr) if err != nil { hreq.URL = &url.URL{ @@ -55,7 +54,10 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg } hreq.Host = addr scheme = "http" - } else { + } + + // nolint: nestif + if scheme == "" { ep := req.Endpoint() if opts.Context != nil { if m, ok := opts.Context.Value(methodKey{}).(string); ok { @@ -70,7 +72,6 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg if t, ok := opts.Context.Value(structTagsKey{}).([]string); ok && len(t) > 0 { tags = t } - } hreq.URL, err = u.Parse(ep) if err != nil { @@ -147,26 +148,20 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request, hrsp, err := h.httpcli.Do(hreq.WithContext(ctx)) if err != nil { switch err := err.(type) { - case net.Error: - if err.Timeout() { - return errors.Timeout("go.micro.client", err.Error()) - } case *url.Error: if err, ok := err.Err.(net.Error); ok && err.Timeout() { return errors.Timeout("go.micro.client", err.Error()) } + case net.Error: + if err.Timeout() { + return errors.Timeout("go.micro.client", err.Error()) + } } return errors.InternalServerError("go.micro.client", err.Error()) } defer hrsp.Body.Close() - if ct == "application/x-www-form-urlencoded" { - cf, err = h.newCodec(DefaultContentType) - if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) - } - } return h.parseRsp(ctx, hrsp, rsp, opts) } @@ -198,11 +193,6 @@ func (h *httpClient) stream(ctx context.Context, addr string, req client.Request return nil, errors.InternalServerError("go.micro.client", err.Error()) } - dialAddr := addr - u, err := url.Parse(dialAddr) - if err == nil && u.Scheme != "" && u.Host != "" { - dialAddr = u.Host - } cc, err := (h.httpcli.Transport).(*http.Transport).DialContext(ctx, "tcp", addr) if err != nil { return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error dialing: %v", err)) @@ -296,7 +286,7 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface } else { // got a deadline so no need to setup context // but we need to set the timeout we pass along - opt := client.WithRequestTimeout(d.Sub(time.Now())) + opt := client.WithRequestTimeout(time.Until(d)) opt(&callOpts) } @@ -423,7 +413,7 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli } else { // got a deadline so no need to setup context // but we need to set the timeout we pass along - opt := client.WithRequestTimeout(d.Sub(time.Now())) + opt := client.WithRequestTimeout(time.Until(d)) opt(&callOpts) } @@ -476,9 +466,9 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli call := func(i int) (client.Stream, error) { // call backoff first. Someone may want an initial start delay - t, err := callOpts.Backoff(ctx, req, i) - if err != nil { - return nil, errors.InternalServerError("go.micro.client", err.Error()) + t, cerr := callOpts.Backoff(ctx, req, i) + if cerr != nil { + return nil, errors.InternalServerError("go.micro.client", cerr.Error()) } // only sleep if greater than 0 @@ -488,19 +478,19 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli node := next() - stream, err := h.stream(ctx, node, req, callOpts) + stream, cerr := h.stream(ctx, node, req, callOpts) // record the result of the call to inform future routing decisions - if verr := h.opts.Selector.Record(node, err); verr != nil { + if verr := h.opts.Selector.Record(node, cerr); verr != nil { return nil, verr } // try and transform the error to a go-micro error - if verr, ok := err.(*errors.Error); ok { + if verr, ok := cerr.(*errors.Error); ok { return nil, verr } - return stream, err + return stream, cerr } type response struct { @@ -513,8 +503,8 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli for i := 0; i <= callOpts.Retries; i++ { go func() { - s, err := call(i) - ch <- response{s, err} + s, cerr := call(i) + ch <- response{s, cerr} }() select { diff --git a/message.go b/message.go index 2928951..899cfb8 100644 --- a/message.go +++ b/message.go @@ -5,9 +5,9 @@ import ( ) type httpMessage struct { + payload interface{} topic string contentType string - payload interface{} } func newHTTPMessage(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { diff --git a/options.go b/options.go index 8db6f1e..d6cd319 100644 --- a/options.go +++ b/options.go @@ -26,27 +26,28 @@ var ( ) type poolMaxStreams struct{} -type poolMaxIdle struct{} -type codecsKey struct{} -type tlsAuth struct{} -type maxRecvMsgSizeKey struct{} -type maxSendMsgSizeKey struct{} // PoolMaxStreams maximum streams on a connectioin func PoolMaxStreams(n int) client.Option { return client.SetOption(poolMaxStreams{}, n) } +type poolMaxIdle struct{} + // PoolMaxIdle maximum idle conns of a pool func PoolMaxIdle(d int) client.Option { return client.SetOption(poolMaxIdle{}, d) } +type maxRecvMsgSizeKey struct{} + // MaxRecvMsgSize set the maximum size of message that client can receive. func MaxRecvMsgSize(s int) client.Option { return client.SetOption(maxRecvMsgSizeKey{}, s) } +type maxSendMsgSizeKey struct{} + // MaxSendMsgSize set the maximum size of message that client can send. func MaxSendMsgSize(s int) client.Option { return client.SetOption(maxSendMsgSizeKey{}, s) @@ -54,12 +55,14 @@ func MaxSendMsgSize(s int) client.Option { type httpClientKey struct{} +// nolint: golint func HTTPClient(c *http.Client) client.Option { return client.SetOption(httpClientKey{}, c) } type httpDialerKey struct{} +// nolint: golint func HTTPDialer(d *net.Dialer) client.Option { return client.SetOption(httpDialerKey{}, d) } diff --git a/stream.go b/stream.go index f90c3a3..f3cd31b 100644 --- a/stream.go +++ b/stream.go @@ -16,24 +16,21 @@ import ( // Implements the streamer interface type httpStream struct { - sync.RWMutex - address string - opts client.CallOptions - ct string - cf codec.Codec - context context.Context - header http.Header - seq uint64 - closed chan bool err error conn net.Conn - reader *bufio.Reader + cf codec.Codec + context context.Context request client.Request + header http.Header + closed chan bool + reader *bufio.Reader + address string + ct string + opts client.CallOptions + sync.RWMutex } -var ( - errShutdown = fmt.Errorf("connection is shut down") -) +var errShutdown = fmt.Errorf("connection is shut down") func (h *httpStream) isClosed() bool { select { @@ -112,34 +109,39 @@ func (h *httpStream) Close() error { func (h *httpStream) parseRsp(ctx context.Context, hrsp *http.Response, cf codec.Codec, rsp interface{}, opts client.CallOptions) error { var err error - // fast path return - if hrsp.StatusCode == http.StatusNoContent { - return nil - } - - if hrsp.StatusCode < 400 { - if err = cf.ReadBody(hrsp.Body, rsp); err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + select { + case <-ctx.Done(): + err = ctx.Err() + default: + // fast path return + if hrsp.StatusCode == http.StatusNoContent { + return nil } - return nil - } - errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) - if ok && errmap != nil { - if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok { - err, ok = errmap["default"].(error) + if hrsp.StatusCode < 400 { + if err = cf.ReadBody(hrsp.Body, rsp); err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + return nil } - } - if err == nil { - buf, err := io.ReadAll(hrsp.Body) - if err != nil { - errors.InternalServerError("go.micro.client", err.Error()) - } - return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) - } - if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil { - err = errors.InternalServerError("go.micro.client", cerr.Error()) + errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) + if ok && errmap != nil { + if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok { + err, ok = errmap["default"].(error) + } + } + if !ok || err == nil { + buf, cerr := io.ReadAll(hrsp.Body) + if cerr != nil { + return errors.InternalServerError("go.micro.client", cerr.Error()) + } + return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) + } + + if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil { + err = errors.InternalServerError("go.micro.client", cerr.Error()) + } } return err diff --git a/util.go b/util.go index 4f5a4b6..6708605 100644 --- a/util.go +++ b/util.go @@ -96,13 +96,15 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta continue } + // nolint: gocritic if _, ok := fieldsmap[t.name]; ok { - if val.Type().Kind() == reflect.Slice { + switch val.Type().Kind() { + case reflect.Slice: for idx := 0; idx < val.Len(); idx++ { values.Add(t.name, fmt.Sprintf("%v", val.Index(idx).Interface())) } fieldsmapskip[t.name] = struct{}{} - } else { + default: fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface()) } } else if (body == "*" || body == t.name) && method != http.MethodGet { @@ -173,45 +175,52 @@ func newTemplate(path string) (util.Template, error) { } func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp interface{}, opts client.CallOptions) error { - // fast path return - if hrsp.StatusCode == http.StatusNoContent { - return nil - } + var err error - ct := DefaultContentType - - if htype := hrsp.Header.Get("Content-Type"); htype != "" { - ct = htype - } - - cf, err := h.newCodec(ct) - if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) - } - - if hrsp.StatusCode < 400 { - if err := cf.ReadBody(hrsp.Body, rsp); err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + select { + case <-ctx.Done(): + err = ctx.Err() + default: + // fast path return + if hrsp.StatusCode == http.StatusNoContent { + return nil } - return nil - } - errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) - if ok && errmap != nil { - if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok { - err, ok = errmap["default"].(error) - } - } - if err == nil { - buf, err := io.ReadAll(hrsp.Body) - if err != nil { - errors.InternalServerError("go.micro.client", err.Error()) - } - return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) - } + ct := DefaultContentType - if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil { - err = errors.InternalServerError("go.micro.client", cerr.Error()) + if htype := hrsp.Header.Get("Content-Type"); htype != "" { + ct = htype + } + + cf, cerr := h.newCodec(ct) + if cerr != nil { + return errors.InternalServerError("go.micro.client", cerr.Error()) + } + + if hrsp.StatusCode < 400 { + if err = cf.ReadBody(hrsp.Body, rsp); err != nil { + return errors.InternalServerError("go.micro.client", err.Error()) + } + return nil + } + + errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) + if ok && errmap != nil { + if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok { + err, ok = errmap["default"].(error) + } + } + if !ok || err == nil { + buf, rerr := io.ReadAll(hrsp.Body) + if rerr != nil { + return errors.InternalServerError("go.micro.client", rerr.Error()) + } + return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode)) + } + + if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil { + err = errors.InternalServerError("go.micro.client", cerr.Error()) + } } return err diff --git a/util_test.go b/util_test.go index 56e2d77..5cca886 100644 --- a/util_test.go +++ b/util_test.go @@ -18,8 +18,8 @@ func TestNewPathRequest(t *testing.T) { type Message struct { Name string `json:"name"` Val1 string `protobuf:"bytes,1,opt,name=val1,proto3" json:"val1"` - Val2 int64 Val3 []string + Val2 int64 } omsg := &Message{Name: "test_name", Val1: "test_val1", Val2: 100, Val3: []string{"slice"}} @@ -45,8 +45,8 @@ func TestNewPathVarRequest(t *testing.T) { type Message struct { Name string `json:"name"` Val1 string `protobuf:"bytes,1,opt,name=val1,proto3" json:"val1"` - Val2 int64 Val3 []string + Val2 int64 } omsg := &Message{Name: "test_name", Val1: "test_val1", Val2: 100, Val3: []string{"slice"}}