From 564a7eb9ff419e0f2b027f8e65c3bdd3130dbb5c Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Mon, 19 Apr 2021 17:35:59 +0800 Subject: [PATCH 01/29] add http client --- client/httplib/client/client.go | 290 +++++++++++++++++++ client/httplib/client/client_option.go | 33 +++ client/httplib/client/http_request_option.go | 25 ++ 3 files changed, 348 insertions(+) create mode 100644 client/httplib/client/client.go create mode 100644 client/httplib/client/client_option.go create mode 100644 client/httplib/client/http_request_option.go diff --git a/client/httplib/client/client.go b/client/httplib/client/client.go new file mode 100644 index 00000000..2d0afa9c --- /dev/null +++ b/client/httplib/client/client.go @@ -0,0 +1,290 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "net/http" + "strings" + + "github.com/beego/beego/v2/client/httplib" + "github.com/beego/beego/v2/core/berror" +) + +type Client struct { + Name string + Endpoint string + CommonOpts []BeegoHttpRequestOption + + Setting *httplib.BeegoHTTPSettings + pointer *ResponsePointer +} + +type ResponsePointer struct { + response **http.Response + statusCode **int + header **http.Header + headerValues map[string]**string //用户传一个key,然后将key存在map的key里,header的value存在value里 + contentLength **int64 +} + +// NewClient +func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, error) { + res := &Client{ + Name: name, + Endpoint: endpoint, + } + for _, o := range opts { + err := o(res) + if err != nil { + return nil, err + } + } + return res, nil +} + +// Response will set response to the pointer +func (c *Client) Response(resp **http.Response) *Client { + if c.pointer == nil { + newC := *c + newC.pointer = &ResponsePointer{ + response: resp, + } + return &newC + } + c.pointer.response = resp + return c +} + +// StatusCode will set response StatusCode to the pointer +func (c *Client) StatusCode(code **int) *Client { + if c.pointer == nil { + newC := *c + newC.pointer = &ResponsePointer{ + statusCode: code, + } + return &newC + } + c.pointer.statusCode = code + return c +} + +// Headers will set response Headers to the pointer +func (c *Client) Headers(headers **http.Header) *Client { + if c.pointer == nil { + newC := *c + newC.pointer = &ResponsePointer{ + header: headers, + } + return &newC + } + c.pointer.header = headers + return c +} + +// HeaderValue will set response HeaderValue to the pointer +func (c *Client) HeaderValue(key string, value **string) *Client { + if c.pointer == nil { + newC := *c + newC.pointer = &ResponsePointer{ + headerValues: map[string]**string{ + key: value, + }, + } + return &newC + } + if c.pointer.headerValues == nil { + c.pointer.headerValues = map[string]**string{} + } + c.pointer.headerValues[key] = value + return c +} + +// ContentType will set response ContentType to the pointer +func (c *Client) ContentType(contentType **string) *Client { + return c.HeaderValue("Content-Type", contentType) +} + +// ContentLength will set response ContentLength to the pointer +func (c *Client) ContentLength(contentLength **int64) *Client { + if c.pointer == nil { + newC := *c + newC.pointer = &ResponsePointer{ + contentLength: contentLength, + } + return &newC + } + c.pointer.contentLength = contentLength + return c +} + +// setPointers set the http response value to pointer +func (c *Client) setPointers(resp *http.Response) { + if c.pointer == nil { + return + } + if c.pointer.response != nil { + *c.pointer.response = resp + } + if c.pointer.statusCode != nil { + *c.pointer.statusCode = &resp.StatusCode + } + if c.pointer.header != nil { + *c.pointer.header = &resp.Header + } + if c.pointer.headerValues != nil { + for k, v := range c.pointer.headerValues { + s := resp.Header.Get(k) + *v = &s + } + } + if c.pointer.contentLength != nil { + *c.pointer.contentLength = &resp.ContentLength + } +} + +// initRequest will apply all the client setting, common option and request option +func (c *Client) newRequest(method, path string, opts []BeegoHttpRequestOption) (*httplib.BeegoHTTPRequest, error) { + var req *httplib.BeegoHTTPRequest + switch method { + case http.MethodGet: + req = httplib.Get(c.Endpoint + path) + case http.MethodPost: + req = httplib.Post(c.Endpoint + path) + case http.MethodPut: + req = httplib.Put(c.Endpoint + path) + case http.MethodDelete: + req = httplib.Delete(c.Endpoint + path) + case http.MethodHead: + req = httplib.Head(c.Endpoint + path) + } + + req = req.Setting(*c.Setting) + for _, o := range c.CommonOpts { + err := o(req) + if err != nil { + return nil, err + } + } + for _, o := range opts { + err := o(req) + if err != nil { + return nil, err + } + } + return req, nil +} + +// handleResponse try to parse body to meaningful value +func (c *Client) handleResponse(value interface{}, req *httplib.BeegoHTTPRequest) error { + // send request + resp, err := req.Response() + if err != nil { + return err + } + c.setPointers(resp) + + // handle basic type + switch v := value.(type) { + case **string: + s, err := req.String() + if err != nil { + return nil + } + *v = &s + return nil + case **[]byte: + bs, err := req.Bytes() + if err != nil { + return nil + } + *v = &bs + return nil + } + + // try to parse it as content type + switch strings.Split(resp.Header.Get("Content-Type"), ";")[0] { + case "application/json": + return req.ToJSON(value) + case "text/xml": + return req.ToXML(value) + case "text/yaml", "application/x-yaml": + return req.ToYAML(value) + } + + // try to parse it anyway + if err := req.ToJSON(value); err == nil { + return nil + } + if err := req.ToYAML(value); err == nil { + return nil + } + if err := req.ToXML(value); err == nil { + return nil + } + + // TODO add new error type about can't parse body + return berror.Error(httplib.UnsupportedBodyType, "unsupported body data") +} + +// Get Send a GET request and try to give its result value +func (c *Client) Get(value interface{}, path string, opts ...BeegoHttpRequestOption) error { + req, err := c.newRequest(http.MethodGet, path, opts) + if err != nil { + return err + } + return c.handleResponse(value, req) +} + +// Post Send a POST request and try to give its result value +func (c *Client) Post(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { + req, err := c.newRequest(http.MethodPost, path, opts) + if err != nil { + return err + } + if body != nil { + req = req.Body(body) + } + return c.handleResponse(value, req) +} + +// Put Send a Put request and try to give its result value +func (c *Client) Put(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { + req, err := c.newRequest(http.MethodPut, path, opts) + if err != nil { + return err + } + if body != nil { + req = req.Body(body) + } + return c.handleResponse(value, req) +} + +// Delete Send a Delete request and try to give its result value +func (c *Client) Delete(value interface{}, path string, opts ...BeegoHttpRequestOption) error { + req, err := c.newRequest(http.MethodDelete, path, opts) + if err != nil { + return err + } + return c.handleResponse(value, req) +} + +// Head Send a Head request and try to give its result value +func (c *Client) Head(value interface{}, path string, opts ...BeegoHttpRequestOption) error { + req, err := c.newRequest(http.MethodHead, path, opts) + if err != nil { + return err + } + return c.handleResponse(value, req) +} diff --git a/client/httplib/client/client_option.go b/client/httplib/client/client_option.go new file mode 100644 index 00000000..0733b682 --- /dev/null +++ b/client/httplib/client/client_option.go @@ -0,0 +1,33 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "net/http" + "net/url" + "time" +) + +type ClientOption func(client *Client) error + +// client设置 +func WithTimeout(connectTimeout, readWriteTimeout time.Duration) ClientOption +func WithEnableCookie(enable bool) ClientOption +func WithUserAgent(userAgent string) ClientOption +func WithCookie(cookie *http.Cookie) ClientOption +func WithTransport(transport http.RoundTripper) ClientOption +func WithProxy(proxy func(*http.Request) (*url.URL, error)) ClientOption +func WithCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) ClientOption +func WithAccept(accept string) ClientOption diff --git a/client/httplib/client/http_request_option.go b/client/httplib/client/http_request_option.go new file mode 100644 index 00000000..d4030901 --- /dev/null +++ b/client/httplib/client/http_request_option.go @@ -0,0 +1,25 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import "github.com/beego/beego/v2/client/httplib" + +type BeegoHttpRequestOption func(request *httplib.BeegoHTTPRequest) error + +// Req设置 +func WithTokenFactory(tokenFactory func() (string, error)) BeegoHttpRequestOption +func WithBasicAuth(basicAuth func() (string, error)) BeegoHttpRequestOption +func WithFilters(fcs ...httplib.FilterChain) BeegoHttpRequestOption +func WithContentType(contentType string) BeegoHttpRequestOption From 575bf62fd35c555ca2ac9610be0da580786e4879 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 20 Apr 2021 17:52:33 +0800 Subject: [PATCH 02/29] add httpclient add options --- client/httplib/client/client_option.go | 33 --- client/httplib/client/http_request_option.go | 25 -- client/httplib/client_option.go | 175 ++++++++++++ client/httplib/client_option_test.go | 250 +++++++++++++++++ .../{client/client.go => httpclient.go} | 48 ++-- client/httplib/httpclient_test.go | 258 ++++++++++++++++++ client/httplib/httplib_test.go | 2 +- client/httplib/setting.go | 5 + 8 files changed, 716 insertions(+), 80 deletions(-) delete mode 100644 client/httplib/client/client_option.go delete mode 100644 client/httplib/client/http_request_option.go create mode 100644 client/httplib/client_option.go create mode 100644 client/httplib/client_option_test.go rename client/httplib/{client/client.go => httpclient.go} (87%) create mode 100644 client/httplib/httpclient_test.go diff --git a/client/httplib/client/client_option.go b/client/httplib/client/client_option.go deleted file mode 100644 index 0733b682..00000000 --- a/client/httplib/client/client_option.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020 beego -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "net/http" - "net/url" - "time" -) - -type ClientOption func(client *Client) error - -// client设置 -func WithTimeout(connectTimeout, readWriteTimeout time.Duration) ClientOption -func WithEnableCookie(enable bool) ClientOption -func WithUserAgent(userAgent string) ClientOption -func WithCookie(cookie *http.Cookie) ClientOption -func WithTransport(transport http.RoundTripper) ClientOption -func WithProxy(proxy func(*http.Request) (*url.URL, error)) ClientOption -func WithCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) ClientOption -func WithAccept(accept string) ClientOption diff --git a/client/httplib/client/http_request_option.go b/client/httplib/client/http_request_option.go deleted file mode 100644 index d4030901..00000000 --- a/client/httplib/client/http_request_option.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 beego -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import "github.com/beego/beego/v2/client/httplib" - -type BeegoHttpRequestOption func(request *httplib.BeegoHTTPRequest) error - -// Req设置 -func WithTokenFactory(tokenFactory func() (string, error)) BeegoHttpRequestOption -func WithBasicAuth(basicAuth func() (string, error)) BeegoHttpRequestOption -func WithFilters(fcs ...httplib.FilterChain) BeegoHttpRequestOption -func WithContentType(contentType string) BeegoHttpRequestOption diff --git a/client/httplib/client_option.go b/client/httplib/client_option.go new file mode 100644 index 00000000..f38c50dc --- /dev/null +++ b/client/httplib/client_option.go @@ -0,0 +1,175 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httplib + +import ( + "crypto/tls" + "net/http" + "net/url" + "time" +) + +type ClientOption func(client *Client) error +type BeegoHttpRequestOption func(request *BeegoHTTPRequest) error + +// WithEnableCookie will enable cookie in all subsequent request +func WithEnableCookie(enable bool) ClientOption { + return func(client *Client) error { + client.Setting.EnableCookie = enable + return nil + } +} + +// WithEnableCookie will adds UA in all subsequent request +func WithUserAgent(userAgent string) ClientOption { + return func(client *Client) error { + client.Setting.UserAgent = userAgent + return nil + } +} + +// WithTLSClientConfig will adds tls config in all subsequent request +func WithTLSClientConfig(config *tls.Config) ClientOption { + return func(client *Client) error { + client.Setting.TLSClientConfig = config + return nil + } +} + +// WithTransport will set transport field in all subsequent request +func WithTransport(transport http.RoundTripper) ClientOption { + return func(client *Client) error { + client.Setting.Transport = transport + return nil + } +} + +// WithProxy will set http proxy field in all subsequent request +func WithProxy(proxy func(*http.Request) (*url.URL, error)) ClientOption { + return func(client *Client) error { + client.Setting.Proxy = proxy + return nil + } +} + +// WithCheckRedirect will specifies the policy for handling redirects in all subsequent request +func WithCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) ClientOption { + return func(client *Client) error { + client.Setting.CheckRedirect = redirect + return nil + } +} + +// WithHTTPSetting can replace beegoHTTPSeting +func WithHTTPSetting(setting BeegoHTTPSettings) ClientOption { + return func(client *Client) error { + client.Setting = &setting + return nil + } +} + +// WithEnableGzip will enable gzip in all subsequent request +func WithEnableGzip(enable bool) ClientOption { + return func(client *Client) error { + client.Setting.Gzip = enable + return nil + } +} + +// BeegoHttpRequestOption + +// WithTimeout sets connect time out and read-write time out for BeegoRequest. +func WithTimeout(connectTimeout, readWriteTimeout time.Duration) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.SetTimeout(connectTimeout, readWriteTimeout) + return nil + } +} + +// WithHeader adds header item string in request. +func WithHeader(key, value string) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.Header(key, value) + return nil + } +} + +// WithCookie adds a cookie to the request. +func WithCookie(cookie *http.Cookie) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.Header("Cookie", cookie.String()) + return nil + } +} + +// Withtokenfactory adds a custom function to set Authorization +func WithTokenFactory(tokenFactory func() (string, error)) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + t, err := tokenFactory() + if err != nil { + return err + } + request.Header("Authorization", t) + return nil + } +} + +// WithBasicAuth adds a custom function to set basic auth +func WithBasicAuth(basicAuth func() (string, string, error)) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + username, password, err := basicAuth() + if err != nil { + return err + } + request.SetBasicAuth(username, password) + return nil + } +} + +// WithFilters will use the filter as the invocation filters +func WithFilters(fcs ...FilterChain) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.SetFilters(fcs...) + return nil + } +} + +// WithContentType adds ContentType in header +func WithContentType(contentType string) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.Header("Content-Type", contentType) + return nil + } +} + +// WithParam adds query param in to request. +func WithParam(key, value string) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.Param(key, value) + return nil + } +} + +// WithRetry set retry times and delay for the request +// default is 0 (never retry) +// -1 retry indefinitely (forever) +// Other numbers specify the exact retry amount +func WithRetry(times int, delay time.Duration) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) error { + request.Retries(times) + request.RetryDelay(delay) + return nil + } +} diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go new file mode 100644 index 00000000..d989bd94 --- /dev/null +++ b/client/httplib/client_option_test.go @@ -0,0 +1,250 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httplib + +import ( + "errors" + "net" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestOption_WithEnableCookie(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/", + WithEnableCookie(true)) + if err != nil { + t.Fatal(err) + } + + v := "smallfish" + var str *string + err = client.Get(&str, "/cookies/set?k1="+v) + if err != nil { + t.Fatal(err) + } + t.Log(*str) + + err = client.Get(&str, "/cookies") + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, v) + if n == -1 { + t.Fatal(v + " not found in cookie") + } +} + +func TestOption_WithUserAgent(t *testing.T) { + v := "beego" + client, err := NewClient("test", "http://httpbin.org/", + WithUserAgent(v)) + if err != nil { + t.Fatal(err) + } + + var str *string + err = client.Get(&str, "/headers") + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, v) + if n == -1 { + t.Fatal(v + " not found in user-agent") + } +} + +func TestOption_WithCheckRedirect(t *testing.T) { + client, err := NewClient("test", "https://goolnk.com/33BD2j", + WithCheckRedirect(func(redirectReq *http.Request, redirectVia []*http.Request) error { + return errors.New("Redirect triggered") + })) + if err != nil { + t.Fatal(err) + } + err = client.Get(nil, "") + assert.NotNil(t, err) +} + +func TestOption_WithHTTPSetting(t *testing.T) { + v := "beego" + var setting BeegoHTTPSettings + setting.EnableCookie = true + setting.UserAgent = v + setting.Transport = &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 50, + IdleConnTimeout: 90 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + setting.ReadWriteTimeout = 5 * time.Second + + client, err := NewClient("test", "http://httpbin.org/", + WithHTTPSetting(setting)) + if err != nil { + t.Fatal(err) + } + + var str *string + err = client.Get(&str, "/get") + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, v) + if n == -1 { + t.Fatal(v + " not found in user-agent") + } +} + +func TestOption_WithHeader(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + client.CommonOpts = append(client.CommonOpts, WithHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")) + + var str *string + err = client.Get(&str, "/headers") + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, "Mozilla/5.0") + if n == -1 { + t.Fatal("Mozilla/5.0 not found in user-agent") + } +} + +func TestOption_WithTokenFactory(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + client.CommonOpts = append(client.CommonOpts, + WithTokenFactory(func() (string, error) { + return "testauth", nil + })) + + var str *string + err = client.Get(&str, "/headers") + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, "testauth") + if n == -1 { + t.Fatal("Auth is not set in request") + } +} + +func TestOption_WithBasicAuth(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var str *string + err = client.Get(&str, "/basic-auth/user/passwd", + WithBasicAuth(func() (string, string, error) { + return "user", "passwd", nil + })) + if err != nil { + t.Fatal(err) + } + t.Log(str) + n := strings.Index(*str, "authenticated") + if n == -1 { + t.Fatal("authenticated not found in response") + } +} + +func TestOption_WithContentType(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + v := "application/json" + var str *string + err = client.Get(&str, "/headers", WithContentType(v)) + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, v) + if n == -1 { + t.Fatal(v + " not found in header") + } +} + +func TestOption_WithParam(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + v := "smallfish" + var str *string + err = client.Get(&str, "/get", WithParam("username", v)) + if err != nil { + t.Fatal(err) + } + t.Log(str) + + n := strings.Index(*str, v) + if n == -1 { + t.Fatal(v + " not found in header") + } +} + +func TestOption_WithRetry(t *testing.T) { + client, err := NewClient("test", "https://goolnk.com/33BD2j", + WithCheckRedirect(func(redirectReq *http.Request, redirectVia []*http.Request) error { + return errors.New("Redirect triggered") + })) + if err != nil { + t.Fatal(err) + } + + retryAmount := 1 + retryDelay := 1400 * time.Millisecond + startTime := time.Now().UnixNano() / int64(time.Millisecond) + + err = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) + + assert.NotNil(t, err) + endTime := time.Now().UnixNano() / int64(time.Millisecond) + elapsedTime := endTime - startTime + delayedTime := int64(retryAmount) * retryDelay.Milliseconds() + if elapsedTime < delayedTime { + t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime) + } +} diff --git a/client/httplib/client/client.go b/client/httplib/httpclient.go similarity index 87% rename from client/httplib/client/client.go rename to client/httplib/httpclient.go index 2d0afa9c..ea6e4316 100644 --- a/client/httplib/client/client.go +++ b/client/httplib/httpclient.go @@ -12,26 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package client +package httplib import ( "net/http" "strings" - "github.com/beego/beego/v2/client/httplib" "github.com/beego/beego/v2/core/berror" ) +// Client provides an HTTP client supporting chain call type Client struct { Name string Endpoint string CommonOpts []BeegoHttpRequestOption - Setting *httplib.BeegoHTTPSettings - pointer *ResponsePointer + Setting *BeegoHTTPSettings + pointer *responsePointer } -type ResponsePointer struct { +type responsePointer struct { response **http.Response statusCode **int header **http.Header @@ -39,12 +39,14 @@ type ResponsePointer struct { contentLength **int64 } -// NewClient +// NewClient return a new http client func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, error) { res := &Client{ Name: name, Endpoint: endpoint, } + setting := GetDefaultSetting() + res.Setting = &setting for _, o := range opts { err := o(res) if err != nil { @@ -58,7 +60,7 @@ func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, err func (c *Client) Response(resp **http.Response) *Client { if c.pointer == nil { newC := *c - newC.pointer = &ResponsePointer{ + newC.pointer = &responsePointer{ response: resp, } return &newC @@ -71,7 +73,7 @@ func (c *Client) Response(resp **http.Response) *Client { func (c *Client) StatusCode(code **int) *Client { if c.pointer == nil { newC := *c - newC.pointer = &ResponsePointer{ + newC.pointer = &responsePointer{ statusCode: code, } return &newC @@ -84,7 +86,7 @@ func (c *Client) StatusCode(code **int) *Client { func (c *Client) Headers(headers **http.Header) *Client { if c.pointer == nil { newC := *c - newC.pointer = &ResponsePointer{ + newC.pointer = &responsePointer{ header: headers, } return &newC @@ -97,7 +99,7 @@ func (c *Client) Headers(headers **http.Header) *Client { func (c *Client) HeaderValue(key string, value **string) *Client { if c.pointer == nil { newC := *c - newC.pointer = &ResponsePointer{ + newC.pointer = &responsePointer{ headerValues: map[string]**string{ key: value, }, @@ -120,7 +122,7 @@ func (c *Client) ContentType(contentType **string) *Client { func (c *Client) ContentLength(contentLength **int64) *Client { if c.pointer == nil { newC := *c - newC.pointer = &ResponsePointer{ + newC.pointer = &responsePointer{ contentLength: contentLength, } return &newC @@ -155,19 +157,19 @@ func (c *Client) setPointers(resp *http.Response) { } // initRequest will apply all the client setting, common option and request option -func (c *Client) newRequest(method, path string, opts []BeegoHttpRequestOption) (*httplib.BeegoHTTPRequest, error) { - var req *httplib.BeegoHTTPRequest +func (c *Client) newRequest(method, path string, opts []BeegoHttpRequestOption) (*BeegoHTTPRequest, error) { + var req *BeegoHTTPRequest switch method { case http.MethodGet: - req = httplib.Get(c.Endpoint + path) + req = Get(c.Endpoint + path) case http.MethodPost: - req = httplib.Post(c.Endpoint + path) + req = Post(c.Endpoint + path) case http.MethodPut: - req = httplib.Put(c.Endpoint + path) + req = Put(c.Endpoint + path) case http.MethodDelete: - req = httplib.Delete(c.Endpoint + path) + req = Delete(c.Endpoint + path) case http.MethodHead: - req = httplib.Head(c.Endpoint + path) + req = Head(c.Endpoint + path) } req = req.Setting(*c.Setting) @@ -187,7 +189,7 @@ func (c *Client) newRequest(method, path string, opts []BeegoHttpRequestOption) } // handleResponse try to parse body to meaningful value -func (c *Client) handleResponse(value interface{}, req *httplib.BeegoHTTPRequest) error { +func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error { // send request resp, err := req.Response() if err != nil { @@ -195,6 +197,10 @@ func (c *Client) handleResponse(value interface{}, req *httplib.BeegoHTTPRequest } c.setPointers(resp) + if value == nil { + return nil + } + // handle basic type switch v := value.(type) { case **string: @@ -217,7 +223,7 @@ func (c *Client) handleResponse(value interface{}, req *httplib.BeegoHTTPRequest switch strings.Split(resp.Header.Get("Content-Type"), ";")[0] { case "application/json": return req.ToJSON(value) - case "text/xml": + case "text/xml", "application/xml": return req.ToXML(value) case "text/yaml", "application/x-yaml": return req.ToYAML(value) @@ -235,7 +241,7 @@ func (c *Client) handleResponse(value interface{}, req *httplib.BeegoHTTPRequest } // TODO add new error type about can't parse body - return berror.Error(httplib.UnsupportedBodyType, "unsupported body data") + return berror.Error(UnsupportedBodyType, "unsupported body data") } // Get Send a GET request and try to give its result value diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go new file mode 100644 index 00000000..0464c9e5 --- /dev/null +++ b/client/httplib/httpclient_test.go @@ -0,0 +1,258 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httplib + +import ( + "encoding/xml" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewClient(t *testing.T) { + client, err := NewClient("test1", "http://beego.me", WithEnableCookie(true)) + assert.NoError(t, err) + assert.NotNil(t, client) + assert.Equal(t, true, client.Setting.EnableCookie) +} + +func TestClient_Response(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var resp *http.Response + err = client.Response(&resp).Get(nil, "status/203") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 203, resp.StatusCode) +} + +func TestClient_StatusCode(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var statusCode *int + err = client.StatusCode(&statusCode).Get(nil, "status/203") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 203, *statusCode) +} + +func TestClient_Headers(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var header *http.Header + err = client.Headers(&header).Get(nil, "bytes/123") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "123", header.Get("Content-Length")) +} + +func TestClient_HeaderValue(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var val *string + err = client.Headers(nil).HeaderValue("Content-Length", &val).Get(nil, "bytes/123") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "123", *val) +} + +func TestClient_ContentType(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var contentType *string + err = client.ContentType(&contentType).Get(nil, "bytes/123") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "application/octet-stream", *contentType) +} + +func TestClient_ContentLength(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + var contentLength *int64 + err = client.ContentLength(&contentLength).Get(nil, "bytes/123") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, int64(123), *contentLength) +} + +type total struct { + Slideshow slideshow `json:"slideshow" yaml:"slideshow"` +} + +type slideshow struct { + XMLName xml.Name `xml:"slideshow"` + + Title string `json:"title" yaml:"title" xml:"title,attr"` + Author string `json:"author" yaml:"author" xml:"author,attr"` + Date string `json:"date" yaml:"date" xml:"date,attr"` + Slides []slide `json:"slides" yaml:"slides" xml:"slide"` +} + +type slide struct { + XMLName xml.Name `xml:"slide"` + + Title string `json:"title" yaml:"title" xml:"title"` +} + +func TestClient_Get(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org/") + if err != nil { + t.Fatal(err) + } + + // basic type + var s *string + err = client.Get(&s, "/base64/SFRUUEJJTiBpcyBhd2Vzb21l") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "HTTPBIN is awesome", *s) + + var bytes *[]byte + err = client.Get(&bytes, "/base64/SFRUUEJJTiBpcyBhd2Vzb21l") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, []byte("HTTPBIN is awesome"), *bytes) + + // json + var tp *total + err = client.Get(&tp, "/json") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "Sample Slide Show", tp.Slideshow.Title) + assert.Equal(t, 2, len(tp.Slideshow.Slides)) + assert.Equal(t, "Overview", tp.Slideshow.Slides[1].Title) + + // xml + var ssp *slideshow + err = client.Get(&ssp, "/base64/PD94bWwgPz48c2xpZGVzaG93CnRpdGxlPSJTYW1wbGUgU2xpZGUgU2hvdyIKZGF0ZT0iRGF0ZSBvZiBwdWJsaWNhdGlvbiIKYXV0aG9yPSJZb3VycyBUcnVseSI+PHNsaWRlIHR5cGU9ImFsbCI+PHRpdGxlPldha2UgdXAgdG8gV29uZGVyV2lkZ2V0cyE8L3RpdGxlPjwvc2xpZGU+PHNsaWRlIHR5cGU9ImFsbCI+PHRpdGxlPk92ZXJ2aWV3PC90aXRsZT48aXRlbT5XaHkgPGVtPldvbmRlcldpZGdldHM8L2VtPiBhcmUgZ3JlYXQ8L2l0ZW0+PGl0ZW0vPjxpdGVtPldobyA8ZW0+YnV5czwvZW0+IFdvbmRlcldpZGdldHM8L2l0ZW0+PC9zbGlkZT48L3NsaWRlc2hvdz4=") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "Sample Slide Show", ssp.Title) + assert.Equal(t, 2, len(ssp.Slides)) + assert.Equal(t, "Overview", ssp.Slides[1].Title) + + // yaml + tp = nil + err = client.Get(&tp, "/base64/c2xpZGVzaG93OgogIGF1dGhvcjogWW91cnMgVHJ1bHkKICBkYXRlOiBkYXRlIG9mIHB1YmxpY2F0aW9uCiAgc2xpZGVzOgogIC0gdGl0bGU6IFdha2UgdXAgdG8gV29uZGVyV2lkZ2V0cyEKICAgIHR5cGU6IGFsbAogIC0gaXRlbXM6CiAgICAtIFdoeSA8ZW0+V29uZGVyV2lkZ2V0czwvZW0+IGFyZSBncmVhdAogICAgLSBXaG8gPGVtPmJ1eXM8L2VtPiBXb25kZXJXaWRnZXRzCiAgICB0aXRsZTogT3ZlcnZpZXcKICAgIHR5cGU6IGFsbAogIHRpdGxlOiBTYW1wbGUgU2xpZGUgU2hvdw==") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "Sample Slide Show", tp.Slideshow.Title) + assert.Equal(t, 2, len(tp.Slideshow.Slides)) + assert.Equal(t, "Overview", tp.Slideshow.Slides[1].Title) + +} + +func TestClient_Post(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org") + if err != nil { + t.Fatal(err) + } + + var s *string + err = client.Get(&s, "/json") + if err != nil { + t.Fatal(err) + } + + var resp *http.Response + err = client.Response(&resp).Post(&s, "/post", *s) + if err != nil { + t.Fatal(err) + } + assert.NotNil(t, resp) + assert.Equal(t, http.MethodPost, resp.Request.Method) +} + +func TestClient_Put(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org") + if err != nil { + t.Fatal(err) + } + + var s *string + err = client.Get(&s, "/json") + if err != nil { + t.Fatal(err) + } + + var resp *http.Response + err = client.Response(&resp).Put(&s, "/put", *s) + if err != nil { + t.Fatal(err) + } + assert.NotNil(t, resp) + assert.Equal(t, http.MethodPut, resp.Request.Method) +} + +func TestClient_Delete(t *testing.T) { + client, err := NewClient("test", "http://httpbin.org") + if err != nil { + t.Fatal(err) + } + + var resp *http.Response + err = client.Response(&resp).Delete(nil, "/delete") + if err != nil { + t.Fatal(err) + } + assert.NotNil(t, resp) + assert.Equal(t, http.MethodDelete, resp.Request.Method) +} + +func TestClient_Head(t *testing.T) { + client, err := NewClient("test", "http://beego.me") + if err != nil { + t.Fatal(err) + } + + var resp *http.Response + err = client.Response(&resp).Head(nil, "") + if err != nil { + t.Fatal(err) + } + assert.NotNil(t, resp) + assert.Equal(t, http.MethodHead, resp.Request.Method) +} diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go index 9133ad5f..fc9f25a4 100644 --- a/client/httplib/httplib_test.go +++ b/client/httplib/httplib_test.go @@ -39,7 +39,7 @@ func TestResponse(t *testing.T) { } func TestDoRequest(t *testing.T) { - req := Get("https://goolnk.com/33BD2j") + req := Get("https://goolnk.com/") retryAmount := 1 req.Retries(1) req.RetryDelay(1400 * time.Millisecond) diff --git a/client/httplib/setting.go b/client/httplib/setting.go index df8eff4b..542c39be 100644 --- a/client/httplib/setting.go +++ b/client/httplib/setting.go @@ -55,6 +55,11 @@ func SetDefaultSetting(setting BeegoHTTPSettings) { defaultSetting = setting } +// SetDefaultSetting return current default setting +func GetDefaultSetting() BeegoHTTPSettings { + return defaultSetting +} + var defaultSetting = BeegoHTTPSettings{ UserAgent: "beegoServer", ConnectTimeout: 60 * time.Second, From 672a59d780cfe31e6264a2ec4a554212e45474dd Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 20 Apr 2021 17:57:58 +0800 Subject: [PATCH 03/29] fix: Restore a deleted field --- client/httplib/httplib_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go index fc9f25a4..9133ad5f 100644 --- a/client/httplib/httplib_test.go +++ b/client/httplib/httplib_test.go @@ -39,7 +39,7 @@ func TestResponse(t *testing.T) { } func TestDoRequest(t *testing.T) { - req := Get("https://goolnk.com/") + req := Get("https://goolnk.com/33BD2j") retryAmount := 1 req.Retries(1) req.RetryDelay(1400 * time.Millisecond) From 6f36998df8c60d3ebe7318208e98c0465e266d87 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 20 Apr 2021 18:09:11 +0800 Subject: [PATCH 04/29] add change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dbe15d0..cc190f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # developing +- Add http client and option func. [4455](https://github.com/beego/beego/issues/4455) - Web mock and test support. [4565](https://github.com/beego/beego/pull/4565) [4574](https://github.com/beego/beego/pull/4574) - Error codes definition of cache module. [4493](https://github.com/beego/beego/pull/4493) - Remove generateCommentRoute http hook. Using `bee generate routers` commands instead.[4486](https://github.com/beego/beego/pull/4486) [bee PR 762](https://github.com/beego/bee/pull/762) From 84946743d977a9d901f70c7c8290f308b348030d Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Thu, 22 Apr 2021 17:11:27 +0800 Subject: [PATCH 05/29] refactor: improve http client implement --- client/httplib/client_option.go | 74 ++++------- client/httplib/client_option_test.go | 12 +- client/httplib/httpclient.go | 192 +++++---------------------- client/httplib/httplib.go | 59 ++++++++ client/httplib/httplib_test.go | 4 + client/httplib/setting.go | 2 +- 6 files changed, 133 insertions(+), 210 deletions(-) diff --git a/client/httplib/client_option.go b/client/httplib/client_option.go index f38c50dc..e7402b8c 100644 --- a/client/httplib/client_option.go +++ b/client/httplib/client_option.go @@ -21,70 +21,62 @@ import ( "time" ) -type ClientOption func(client *Client) error -type BeegoHttpRequestOption func(request *BeegoHTTPRequest) error +type ClientOption func(client *Client) +type BeegoHttpRequestOption func(request *BeegoHTTPRequest) // WithEnableCookie will enable cookie in all subsequent request func WithEnableCookie(enable bool) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.EnableCookie = enable - return nil } } // WithEnableCookie will adds UA in all subsequent request func WithUserAgent(userAgent string) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.UserAgent = userAgent - return nil } } // WithTLSClientConfig will adds tls config in all subsequent request func WithTLSClientConfig(config *tls.Config) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.TLSClientConfig = config - return nil } } // WithTransport will set transport field in all subsequent request func WithTransport(transport http.RoundTripper) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.Transport = transport - return nil } } // WithProxy will set http proxy field in all subsequent request func WithProxy(proxy func(*http.Request) (*url.URL, error)) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.Proxy = proxy - return nil } } // WithCheckRedirect will specifies the policy for handling redirects in all subsequent request func WithCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.CheckRedirect = redirect - return nil } } // WithHTTPSetting can replace beegoHTTPSeting func WithHTTPSetting(setting BeegoHTTPSettings) ClientOption { - return func(client *Client) error { - client.Setting = &setting - return nil + return func(client *Client) { + client.Setting = setting } } // WithEnableGzip will enable gzip in all subsequent request func WithEnableGzip(enable bool) ClientOption { - return func(client *Client) error { + return func(client *Client) { client.Setting.Gzip = enable - return nil } } @@ -92,73 +84,60 @@ func WithEnableGzip(enable bool) ClientOption { // WithTimeout sets connect time out and read-write time out for BeegoRequest. func WithTimeout(connectTimeout, readWriteTimeout time.Duration) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.SetTimeout(connectTimeout, readWriteTimeout) - return nil } } // WithHeader adds header item string in request. func WithHeader(key, value string) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.Header(key, value) - return nil } } // WithCookie adds a cookie to the request. func WithCookie(cookie *http.Cookie) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.Header("Cookie", cookie.String()) - return nil } } // Withtokenfactory adds a custom function to set Authorization -func WithTokenFactory(tokenFactory func() (string, error)) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { - t, err := tokenFactory() - if err != nil { - return err - } +func WithTokenFactory(tokenFactory func() string) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) { + t := tokenFactory() + request.Header("Authorization", t) - return nil } } // WithBasicAuth adds a custom function to set basic auth -func WithBasicAuth(basicAuth func() (string, string, error)) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { - username, password, err := basicAuth() - if err != nil { - return err - } +func WithBasicAuth(basicAuth func() (string, string)) BeegoHttpRequestOption { + return func(request *BeegoHTTPRequest) { + username, password := basicAuth() request.SetBasicAuth(username, password) - return nil } } // WithFilters will use the filter as the invocation filters func WithFilters(fcs ...FilterChain) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.SetFilters(fcs...) - return nil } } // WithContentType adds ContentType in header func WithContentType(contentType string) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { - request.Header("Content-Type", contentType) - return nil + return func(request *BeegoHTTPRequest) { + request.Header(contentTypeKey, contentType) } } // WithParam adds query param in to request. func WithParam(key, value string) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.Param(key, value) - return nil } } @@ -167,9 +146,8 @@ func WithParam(key, value string) BeegoHttpRequestOption { // -1 retry indefinitely (forever) // Other numbers specify the exact retry amount func WithRetry(times int, delay time.Duration) BeegoHttpRequestOption { - return func(request *BeegoHTTPRequest) error { + return func(request *BeegoHTTPRequest) { request.Retries(times) request.RetryDelay(delay) - return nil } } diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index d989bd94..0598206c 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -147,8 +147,8 @@ func TestOption_WithTokenFactory(t *testing.T) { t.Fatal(err) } client.CommonOpts = append(client.CommonOpts, - WithTokenFactory(func() (string, error) { - return "testauth", nil + WithTokenFactory(func() string { + return "testauth" })) var str *string @@ -172,8 +172,8 @@ func TestOption_WithBasicAuth(t *testing.T) { var str *string err = client.Get(&str, "/basic-auth/user/passwd", - WithBasicAuth(func() (string, string, error) { - return "user", "passwd", nil + WithBasicAuth(func() (string, string) { + return "user", "passwd" })) if err != nil { t.Fatal(err) @@ -240,7 +240,9 @@ func TestOption_WithRetry(t *testing.T) { err = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) - assert.NotNil(t, err) + if err != nil { + t.Fatal(err) + } endTime := time.Now().UnixNano() / int64(time.Millisecond) elapsedTime := endTime - startTime delayedTime := int64(retryAmount) * retryDelay.Milliseconds() diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index ea6e4316..4cfadedf 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -16,9 +16,6 @@ package httplib import ( "net/http" - "strings" - - "github.com/beego/beego/v2/core/berror" ) // Client provides an HTTP client supporting chain call @@ -27,8 +24,8 @@ type Client struct { Endpoint string CommonOpts []BeegoHttpRequestOption - Setting *BeegoHTTPSettings - pointer *responsePointer + Setting BeegoHTTPSettings + pointer responsePointer } type responsePointer struct { @@ -46,71 +43,42 @@ func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, err Endpoint: endpoint, } setting := GetDefaultSetting() - res.Setting = &setting + res.Setting = setting for _, o := range opts { - err := o(res) - if err != nil { - return nil, err - } + o(res) } return res, nil } // Response will set response to the pointer func (c *Client) Response(resp **http.Response) *Client { - if c.pointer == nil { - newC := *c - newC.pointer = &responsePointer{ - response: resp, - } - return &newC - } - c.pointer.response = resp - return c + newC := *c + newC.pointer.response = resp + return &newC } // StatusCode will set response StatusCode to the pointer func (c *Client) StatusCode(code **int) *Client { - if c.pointer == nil { - newC := *c - newC.pointer = &responsePointer{ - statusCode: code, - } - return &newC - } - c.pointer.statusCode = code - return c + newC := *c + newC.pointer.statusCode = code + return &newC } // Headers will set response Headers to the pointer func (c *Client) Headers(headers **http.Header) *Client { - if c.pointer == nil { - newC := *c - newC.pointer = &responsePointer{ - header: headers, - } - return &newC - } - c.pointer.header = headers - return c + newC := *c + newC.pointer.header = headers + return &newC } // HeaderValue will set response HeaderValue to the pointer func (c *Client) HeaderValue(key string, value **string) *Client { - if c.pointer == nil { - newC := *c - newC.pointer = &responsePointer{ - headerValues: map[string]**string{ - key: value, - }, - } - return &newC + newC := *c + if newC.pointer.headerValues == nil { + newC.pointer.headerValues = make(map[string]**string) } - if c.pointer.headerValues == nil { - c.pointer.headerValues = map[string]**string{} - } - c.pointer.headerValues[key] = value - return c + newC.pointer.headerValues[key] = value + return &newC } // ContentType will set response ContentType to the pointer @@ -120,22 +88,13 @@ func (c *Client) ContentType(contentType **string) *Client { // ContentLength will set response ContentLength to the pointer func (c *Client) ContentLength(contentLength **int64) *Client { - if c.pointer == nil { - newC := *c - newC.pointer = &responsePointer{ - contentLength: contentLength, - } - return &newC - } - c.pointer.contentLength = contentLength - return c + newC := *c + newC.pointer.contentLength = contentLength + return &newC } // setPointers set the http response value to pointer func (c *Client) setPointers(resp *http.Response) { - if c.pointer == nil { - return - } if c.pointer.response != nil { *c.pointer.response = resp } @@ -156,36 +115,12 @@ func (c *Client) setPointers(resp *http.Response) { } } -// initRequest will apply all the client setting, common option and request option -func (c *Client) newRequest(method, path string, opts []BeegoHttpRequestOption) (*BeegoHTTPRequest, error) { - var req *BeegoHTTPRequest - switch method { - case http.MethodGet: - req = Get(c.Endpoint + path) - case http.MethodPost: - req = Post(c.Endpoint + path) - case http.MethodPut: - req = Put(c.Endpoint + path) - case http.MethodDelete: - req = Delete(c.Endpoint + path) - case http.MethodHead: - req = Head(c.Endpoint + path) - } - - req = req.Setting(*c.Setting) - for _, o := range c.CommonOpts { - err := o(req) - if err != nil { - return nil, err - } - } +func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHttpRequestOption) { + req.Setting(c.Setting) + opts = append(c.CommonOpts, opts...) for _, o := range opts { - err := o(req) - if err != nil { - return nil, err - } + o(req) } - return req, nil } // handleResponse try to parse body to meaningful value @@ -196,69 +131,20 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error return err } c.setPointers(resp) - - if value == nil { - return nil - } - - // handle basic type - switch v := value.(type) { - case **string: - s, err := req.String() - if err != nil { - return nil - } - *v = &s - return nil - case **[]byte: - bs, err := req.Bytes() - if err != nil { - return nil - } - *v = &bs - return nil - } - - // try to parse it as content type - switch strings.Split(resp.Header.Get("Content-Type"), ";")[0] { - case "application/json": - return req.ToJSON(value) - case "text/xml", "application/xml": - return req.ToXML(value) - case "text/yaml", "application/x-yaml": - return req.ToYAML(value) - } - - // try to parse it anyway - if err := req.ToJSON(value); err == nil { - return nil - } - if err := req.ToYAML(value); err == nil { - return nil - } - if err := req.ToXML(value); err == nil { - return nil - } - - // TODO add new error type about can't parse body - return berror.Error(UnsupportedBodyType, "unsupported body data") + return req.ResponseForValue(value) } // Get Send a GET request and try to give its result value func (c *Client) Get(value interface{}, path string, opts ...BeegoHttpRequestOption) error { - req, err := c.newRequest(http.MethodGet, path, opts) - if err != nil { - return err - } + req := Get(c.Endpoint + path) + c.customReq(req, opts) return c.handleResponse(value, req) } // Post Send a POST request and try to give its result value func (c *Client) Post(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { - req, err := c.newRequest(http.MethodPost, path, opts) - if err != nil { - return err - } + req := Post(c.Endpoint + path) + c.customReq(req, opts) if body != nil { req = req.Body(body) } @@ -267,10 +153,8 @@ func (c *Client) Post(value interface{}, path string, body interface{}, opts ... // Put Send a Put request and try to give its result value func (c *Client) Put(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { - req, err := c.newRequest(http.MethodPut, path, opts) - if err != nil { - return err - } + req := Put(c.Endpoint + path) + c.customReq(req, opts) if body != nil { req = req.Body(body) } @@ -279,18 +163,14 @@ func (c *Client) Put(value interface{}, path string, body interface{}, opts ...B // Delete Send a Delete request and try to give its result value func (c *Client) Delete(value interface{}, path string, opts ...BeegoHttpRequestOption) error { - req, err := c.newRequest(http.MethodDelete, path, opts) - if err != nil { - return err - } + req := Delete(c.Endpoint + path) + c.customReq(req, opts) return c.handleResponse(value, req) } // Head Send a Head request and try to give its result value func (c *Client) Head(value interface{}, path string, opts ...BeegoHttpRequestOption) error { - req, err := c.newRequest(http.MethodHead, path, opts) - if err != nil { - return err - } + req := Head(c.Endpoint + path) + c.customReq(req, opts) return c.handleResponse(value, req) } diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index cef2294c..317c462c 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -56,6 +56,7 @@ import ( ) const contentTypeKey = "Content-Type" + // it will be the last filter and execute request.Do var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { return req.doRequest(ctx) @@ -660,6 +661,64 @@ func (b *BeegoHTTPRequest) Response() (*http.Response, error) { return b.getResponse() } +// ResponseForValue attempts to resolve the response body to value using an existing method. +// Calls Response inner. +// If value type is **string or **[]byte, the func directly passes response body into the pointer. +// Else if response header contain Content-Type, func will call ToJSON\ToXML\ToYAML. +// Finally it will try to parse body as json\yaml\xml, If all attempts fail, an error will be returned +func (b *BeegoHTTPRequest) ResponseForValue(value interface{}) error { + if value == nil { + return nil + } + // handle basic type + switch v := value.(type) { + case **string: + s, err := b.String() + if err != nil { + return nil + } + *v = &s + return nil + case **[]byte: + bs, err := b.Bytes() + if err != nil { + return nil + } + *v = &bs + return nil + } + + resp, err := b.Response() + if err != nil { + return err + } + contentType := strings.Split(resp.Header.Get(contentTypeKey), ";")[0] + + // try to parse it as content type + switch contentType { + case "application/json": + return b.ToJSON(value) + case "text/xml", "application/xml": + return b.ToXML(value) + case "text/yaml", "application/x-yaml", "application/x+yaml": + return b.ToYAML(value) + } + + // try to parse it anyway + if err := b.ToJSON(value); err == nil { + return nil + } + if err := b.ToYAML(value); err == nil { + return nil + } + if err := b.ToXML(value); err == nil { + return nil + } + + // TODO add new error type about can't parse body + return berror.Error(UnsupportedBodyType, "unsupported body data") +} + // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. // Deprecated // we will move this at the end of 2021 diff --git a/client/httplib/httplib_test.go b/client/httplib/httplib_test.go index 9133ad5f..e7939a4e 100644 --- a/client/httplib/httplib_test.go +++ b/client/httplib/httplib_test.go @@ -433,3 +433,7 @@ func TestBeegoHTTPRequest_XMLBody(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, req.req.GetBody) } + +// TODO +func TestBeegoHTTPRequest_ResponseForValue(t *testing.T) { +} diff --git a/client/httplib/setting.go b/client/httplib/setting.go index 542c39be..2d7a0eed 100644 --- a/client/httplib/setting.go +++ b/client/httplib/setting.go @@ -55,7 +55,7 @@ func SetDefaultSetting(setting BeegoHTTPSettings) { defaultSetting = setting } -// SetDefaultSetting return current default setting +// GetDefaultSetting return current default setting func GetDefaultSetting() BeegoHTTPSettings { return defaultSetting } From 599e03b0cdb5bb28e6d3bb8131730282b14387a1 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Sun, 16 May 2021 14:18:34 +0800 Subject: [PATCH 06/29] Solution 3 --- client/httplib/client_option_test.go | 94 +++++++++------ client/httplib/httpclient.go | 93 ++++----------- client/httplib/httpclient_test.go | 170 +++++++-------------------- client/httplib/httplib.go | 22 +--- 4 files changed, 123 insertions(+), 256 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index 0598206c..9efeaa24 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -25,6 +25,27 @@ import ( "github.com/stretchr/testify/assert" ) +type respCarrier struct { + Resp *http.Response + bytes []byte +} + +func (r *respCarrier) SetHttpResponse(resp *http.Response) { + r.Resp = resp +} + +func (r *respCarrier) SetBytes(bytes []byte) { + r.bytes = bytes +} + +func (r *respCarrier) Bytes() []byte { + return r.bytes +} + +func (r *respCarrier) String() string { + return string(r.bytes) +} + func TestOption_WithEnableCookie(t *testing.T) { client, err := NewClient("test", "http://httpbin.org/", WithEnableCookie(true)) @@ -33,20 +54,20 @@ func TestOption_WithEnableCookie(t *testing.T) { } v := "smallfish" - var str *string - err = client.Get(&str, "/cookies/set?k1="+v) + var resp = &respCarrier{} + err = client.Get(resp, "/cookies/set?k1="+v) if err != nil { t.Fatal(err) } - t.Log(*str) + t.Log(resp.String()) - err = client.Get(&str, "/cookies") + err = client.Get(resp, "/cookies") if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, v) + n := strings.Index(resp.String(), v) if n == -1 { t.Fatal(v + " not found in cookie") } @@ -60,14 +81,14 @@ func TestOption_WithUserAgent(t *testing.T) { t.Fatal(err) } - var str *string - err = client.Get(&str, "/headers") + var resp = &respCarrier{} + err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, v) + n := strings.Index(resp.String(), v) if n == -1 { t.Fatal(v + " not found in user-agent") } @@ -108,14 +129,14 @@ func TestOption_WithHTTPSetting(t *testing.T) { t.Fatal(err) } - var str *string - err = client.Get(&str, "/get") + var resp = &respCarrier{} + err = client.Get(resp, "/get") if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, v) + n := strings.Index(resp.String(), v) if n == -1 { t.Fatal(v + " not found in user-agent") } @@ -128,14 +149,14 @@ func TestOption_WithHeader(t *testing.T) { } client.CommonOpts = append(client.CommonOpts, WithHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")) - var str *string - err = client.Get(&str, "/headers") + var resp = &respCarrier{} + err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, "Mozilla/5.0") + n := strings.Index(resp.String(), "Mozilla/5.0") if n == -1 { t.Fatal("Mozilla/5.0 not found in user-agent") } @@ -151,14 +172,14 @@ func TestOption_WithTokenFactory(t *testing.T) { return "testauth" })) - var str *string - err = client.Get(&str, "/headers") + var resp = &respCarrier{} + err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, "testauth") + n := strings.Index(resp.String(), "testauth") if n == -1 { t.Fatal("Auth is not set in request") } @@ -170,16 +191,16 @@ func TestOption_WithBasicAuth(t *testing.T) { t.Fatal(err) } - var str *string - err = client.Get(&str, "/basic-auth/user/passwd", + var resp = &respCarrier{} + err = client.Get(resp, "/basic-auth/user/passwd", WithBasicAuth(func() (string, string) { return "user", "passwd" })) if err != nil { t.Fatal(err) } - t.Log(str) - n := strings.Index(*str, "authenticated") + t.Log(resp.String()) + n := strings.Index(resp.String(), "authenticated") if n == -1 { t.Fatal("authenticated not found in response") } @@ -192,14 +213,14 @@ func TestOption_WithContentType(t *testing.T) { } v := "application/json" - var str *string - err = client.Get(&str, "/headers", WithContentType(v)) + var resp = &respCarrier{} + err = client.Get(resp, "/headers", WithContentType(v)) if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, v) + n := strings.Index(resp.String(), v) if n == -1 { t.Fatal(v + " not found in header") } @@ -212,14 +233,14 @@ func TestOption_WithParam(t *testing.T) { } v := "smallfish" - var str *string - err = client.Get(&str, "/get", WithParam("username", v)) + var resp = &respCarrier{} + err = client.Get(resp, "/get", WithParam("username", v)) if err != nil { t.Fatal(err) } - t.Log(str) + t.Log(resp.String()) - n := strings.Index(*str, v) + n := strings.Index(resp.String(), v) if n == -1 { t.Fatal(v + " not found in header") } @@ -238,11 +259,8 @@ func TestOption_WithRetry(t *testing.T) { retryDelay := 1400 * time.Millisecond startTime := time.Now().UnixNano() / int64(time.Millisecond) - err = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) + _ = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) - if err != nil { - t.Fatal(err) - } endTime := time.Now().UnixNano() / int64(time.Millisecond) elapsedTime := endTime - startTime delayedTime := int64(retryAmount) * retryDelay.Milliseconds() diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index 4cfadedf..69aaa286 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -25,15 +25,17 @@ type Client struct { CommonOpts []BeegoHttpRequestOption Setting BeegoHTTPSettings - pointer responsePointer } -type responsePointer struct { - response **http.Response - statusCode **int - header **http.Header - headerValues map[string]**string //用户传一个key,然后将key存在map的key里,header的value存在value里 - contentLength **int64 +// If value implement this interface. http.response will saved by SetHttpResponse +type HttpResponseCarrier interface { + SetHttpResponse(resp *http.Response) +} + +// If value implement this interface. bytes of http.response will saved by SetHttpResponse +type ResponseBytesCarrier interface { + // Cause of when user get http.response, the body stream is closed. So need to pass bytes by + SetBytes(bytes []byte) } // NewClient return a new http client @@ -50,71 +52,6 @@ func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, err return res, nil } -// Response will set response to the pointer -func (c *Client) Response(resp **http.Response) *Client { - newC := *c - newC.pointer.response = resp - return &newC -} - -// StatusCode will set response StatusCode to the pointer -func (c *Client) StatusCode(code **int) *Client { - newC := *c - newC.pointer.statusCode = code - return &newC -} - -// Headers will set response Headers to the pointer -func (c *Client) Headers(headers **http.Header) *Client { - newC := *c - newC.pointer.header = headers - return &newC -} - -// HeaderValue will set response HeaderValue to the pointer -func (c *Client) HeaderValue(key string, value **string) *Client { - newC := *c - if newC.pointer.headerValues == nil { - newC.pointer.headerValues = make(map[string]**string) - } - newC.pointer.headerValues[key] = value - return &newC -} - -// ContentType will set response ContentType to the pointer -func (c *Client) ContentType(contentType **string) *Client { - return c.HeaderValue("Content-Type", contentType) -} - -// ContentLength will set response ContentLength to the pointer -func (c *Client) ContentLength(contentLength **int64) *Client { - newC := *c - newC.pointer.contentLength = contentLength - return &newC -} - -// setPointers set the http response value to pointer -func (c *Client) setPointers(resp *http.Response) { - if c.pointer.response != nil { - *c.pointer.response = resp - } - if c.pointer.statusCode != nil { - *c.pointer.statusCode = &resp.StatusCode - } - if c.pointer.header != nil { - *c.pointer.header = &resp.Header - } - if c.pointer.headerValues != nil { - for k, v := range c.pointer.headerValues { - s := resp.Header.Get(k) - *v = &s - } - } - if c.pointer.contentLength != nil { - *c.pointer.contentLength = &resp.ContentLength - } -} - func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHttpRequestOption) { req.Setting(c.Setting) opts = append(c.CommonOpts, opts...) @@ -130,7 +67,17 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error if err != nil { return err } - c.setPointers(resp) + if carrier, ok := (value).(HttpResponseCarrier); ok { + (carrier).SetHttpResponse(resp) + } + if carrier, ok := (value).(ResponseBytesCarrier); ok { + bytes, err := req.Bytes() + if err != nil { + return err + } + (carrier).SetBytes(bytes) + } + return req.ResponseForValue(value) } diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index 0464c9e5..f006f18f 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -29,91 +29,10 @@ func TestNewClient(t *testing.T) { assert.Equal(t, true, client.Setting.EnableCookie) } -func TestClient_Response(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } +type slideSshowResponse struct { + Resp *http.Response + bytes []byte - var resp *http.Response - err = client.Response(&resp).Get(nil, "status/203") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 203, resp.StatusCode) -} - -func TestClient_StatusCode(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } - - var statusCode *int - err = client.StatusCode(&statusCode).Get(nil, "status/203") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 203, *statusCode) -} - -func TestClient_Headers(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } - - var header *http.Header - err = client.Headers(&header).Get(nil, "bytes/123") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "123", header.Get("Content-Length")) -} - -func TestClient_HeaderValue(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } - - var val *string - err = client.Headers(nil).HeaderValue("Content-Length", &val).Get(nil, "bytes/123") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "123", *val) -} - -func TestClient_ContentType(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } - - var contentType *string - err = client.ContentType(&contentType).Get(nil, "bytes/123") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "application/octet-stream", *contentType) -} - -func TestClient_ContentLength(t *testing.T) { - client, err := NewClient("test", "http://httpbin.org/") - if err != nil { - t.Fatal(err) - } - - var contentLength *int64 - err = client.ContentLength(&contentLength).Get(nil, "bytes/123") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, int64(123), *contentLength) -} - -type total struct { Slideshow slideshow `json:"slideshow" yaml:"slideshow"` } @@ -132,36 +51,37 @@ type slide struct { Title string `json:"title" yaml:"title" xml:"title"` } +func (s *slideSshowResponse) SetHttpResponse(resp *http.Response) { + s.Resp = resp +} + +func (s *slideSshowResponse) SetBytes(bytes []byte) { + s.bytes = bytes +} + +func (s *slideSshowResponse) Bytes() []byte { + return s.bytes +} + +func (s *slideSshowResponse) String() string { + return string(s.bytes) +} + func TestClient_Get(t *testing.T) { client, err := NewClient("test", "http://httpbin.org/") if err != nil { t.Fatal(err) } - // basic type - var s *string - err = client.Get(&s, "/base64/SFRUUEJJTiBpcyBhd2Vzb21l") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, "HTTPBIN is awesome", *s) - - var bytes *[]byte - err = client.Get(&bytes, "/base64/SFRUUEJJTiBpcyBhd2Vzb21l") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, []byte("HTTPBIN is awesome"), *bytes) - // json - var tp *total - err = client.Get(&tp, "/json") + var s *slideSshowResponse + err = client.Get(&s, "/json") if err != nil { t.Fatal(err) } - assert.Equal(t, "Sample Slide Show", tp.Slideshow.Title) - assert.Equal(t, 2, len(tp.Slideshow.Slides)) - assert.Equal(t, "Overview", tp.Slideshow.Slides[1].Title) + assert.Equal(t, "Sample Slide Show", s.Slideshow.Title) + assert.Equal(t, 2, len(s.Slideshow.Slides)) + assert.Equal(t, "Overview", s.Slideshow.Slides[1].Title) // xml var ssp *slideshow @@ -174,14 +94,14 @@ func TestClient_Get(t *testing.T) { assert.Equal(t, "Overview", ssp.Slides[1].Title) // yaml - tp = nil - err = client.Get(&tp, "/base64/c2xpZGVzaG93OgogIGF1dGhvcjogWW91cnMgVHJ1bHkKICBkYXRlOiBkYXRlIG9mIHB1YmxpY2F0aW9uCiAgc2xpZGVzOgogIC0gdGl0bGU6IFdha2UgdXAgdG8gV29uZGVyV2lkZ2V0cyEKICAgIHR5cGU6IGFsbAogIC0gaXRlbXM6CiAgICAtIFdoeSA8ZW0+V29uZGVyV2lkZ2V0czwvZW0+IGFyZSBncmVhdAogICAgLSBXaG8gPGVtPmJ1eXM8L2VtPiBXb25kZXJXaWRnZXRzCiAgICB0aXRsZTogT3ZlcnZpZXcKICAgIHR5cGU6IGFsbAogIHRpdGxlOiBTYW1wbGUgU2xpZGUgU2hvdw==") + s = nil + err = client.Get(&s, "/base64/c2xpZGVzaG93OgogIGF1dGhvcjogWW91cnMgVHJ1bHkKICBkYXRlOiBkYXRlIG9mIHB1YmxpY2F0aW9uCiAgc2xpZGVzOgogIC0gdGl0bGU6IFdha2UgdXAgdG8gV29uZGVyV2lkZ2V0cyEKICAgIHR5cGU6IGFsbAogIC0gaXRlbXM6CiAgICAtIFdoeSA8ZW0+V29uZGVyV2lkZ2V0czwvZW0+IGFyZSBncmVhdAogICAgLSBXaG8gPGVtPmJ1eXM8L2VtPiBXb25kZXJXaWRnZXRzCiAgICB0aXRsZTogT3ZlcnZpZXcKICAgIHR5cGU6IGFsbAogIHRpdGxlOiBTYW1wbGUgU2xpZGUgU2hvdw==") if err != nil { t.Fatal(err) } - assert.Equal(t, "Sample Slide Show", tp.Slideshow.Title) - assert.Equal(t, 2, len(tp.Slideshow.Slides)) - assert.Equal(t, "Overview", tp.Slideshow.Slides[1].Title) + assert.Equal(t, "Sample Slide Show", s.Slideshow.Title) + assert.Equal(t, 2, len(s.Slideshow.Slides)) + assert.Equal(t, "Overview", s.Slideshow.Slides[1].Title) } @@ -191,19 +111,19 @@ func TestClient_Post(t *testing.T) { t.Fatal(err) } - var s *string - err = client.Get(&s, "/json") + var resp = &slideSshowResponse{} + err = client.Get(resp, "/json") if err != nil { t.Fatal(err) } - var resp *http.Response - err = client.Response(&resp).Post(&s, "/post", *s) + jsonStr := resp.String() + err = client.Post(resp, "/post", jsonStr) if err != nil { t.Fatal(err) } assert.NotNil(t, resp) - assert.Equal(t, http.MethodPost, resp.Request.Method) + assert.Equal(t, http.MethodPost, resp.Resp.Request.Method) } func TestClient_Put(t *testing.T) { @@ -212,19 +132,19 @@ func TestClient_Put(t *testing.T) { t.Fatal(err) } - var s *string - err = client.Get(&s, "/json") + var resp = &slideSshowResponse{} + err = client.Get(resp, "/json") if err != nil { t.Fatal(err) } - var resp *http.Response - err = client.Response(&resp).Put(&s, "/put", *s) + jsonStr := resp.String() + err = client.Put(resp, "/put", jsonStr) if err != nil { t.Fatal(err) } assert.NotNil(t, resp) - assert.Equal(t, http.MethodPut, resp.Request.Method) + assert.Equal(t, http.MethodPut, resp.Resp.Request.Method) } func TestClient_Delete(t *testing.T) { @@ -233,13 +153,13 @@ func TestClient_Delete(t *testing.T) { t.Fatal(err) } - var resp *http.Response - err = client.Response(&resp).Delete(nil, "/delete") + var resp = &slideSshowResponse{} + err = client.Delete(resp, "/delete") if err != nil { t.Fatal(err) } assert.NotNil(t, resp) - assert.Equal(t, http.MethodDelete, resp.Request.Method) + assert.Equal(t, http.MethodDelete, resp.Resp.Request.Method) } func TestClient_Head(t *testing.T) { @@ -248,11 +168,11 @@ func TestClient_Head(t *testing.T) { t.Fatal(err) } - var resp *http.Response - err = client.Response(&resp).Head(nil, "") + var resp = &slideSshowResponse{} + err = client.Head(resp, "") if err != nil { t.Fatal(err) } assert.NotNil(t, resp) - assert.Equal(t, http.MethodHead, resp.Request.Method) + assert.Equal(t, http.MethodHead, resp.Resp.Request.Method) } diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index 317c462c..f032a294 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -663,30 +663,12 @@ func (b *BeegoHTTPRequest) Response() (*http.Response, error) { // ResponseForValue attempts to resolve the response body to value using an existing method. // Calls Response inner. -// If value type is **string or **[]byte, the func directly passes response body into the pointer. -// Else if response header contain Content-Type, func will call ToJSON\ToXML\ToYAML. -// Finally it will try to parse body as json\yaml\xml, If all attempts fail, an error will be returned +// If response header contain Content-Type, func will call ToJSON\ToXML\ToYAML. +// Else it will try to parse body as json\yaml\xml, If all attempts fail, an error will be returned func (b *BeegoHTTPRequest) ResponseForValue(value interface{}) error { if value == nil { return nil } - // handle basic type - switch v := value.(type) { - case **string: - s, err := b.String() - if err != nil { - return nil - } - *v = &s - return nil - case **[]byte: - bs, err := b.Bytes() - if err != nil { - return nil - } - *v = &bs - return nil - } resp, err := b.Response() if err != nil { From 765e96446b67ee64d7f54a6f87a6af03ac2da6ee Mon Sep 17 00:00:00 2001 From: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Tue, 18 May 2021 13:27:06 +0800 Subject: [PATCH 07/29] Test on Go v1.15.x & v1.16.x --- .travis.yml | 4 +++- CHANGELOG.md | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5ccd3645..9382e985 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: go go: - "1.14.x" + - "1.15.x" + - "1.16.x" services: - redis-server - mysql @@ -12,7 +14,7 @@ env: global: - GO_REPO_FULLNAME="github.com/beego/beego/v2" matrix: - - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db + - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - ORM_DRIVER=mysql export ORM_SOURCE="root:@/orm_test?charset=utf8" before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md index c45fece0..60346a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619) - Chore: format code. [4615](https://github.com/beego/beego/pull/4615) +- Test on Go v1.15.x & v1.16.x. [4614](https://github.com/beego/beego/pull/4614) - Env: non-empty GOBIN & GOPATH. [4613](https://github.com/beego/beego/pull/4613) - Chore: update dependencies. [4611](https://github.com/beego/beego/pull/4611) - Update orm_test.go/TestInsertOrUpdate with table-driven. [4609](https://github.com/beego/beego/pull/4609) From 8722e9889104b69907e9cc758d5397dbed3a7efb Mon Sep 17 00:00:00 2001 From: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Fri, 21 May 2021 02:33:56 +0800 Subject: [PATCH 08/29] Infra: use dependabot to update dependencies --- .github/dependabot.yml | 17 +++++++++++++++++ CHANGELOG.md | 1 + 2 files changed, 18 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..d85c6373 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/CHANGELOG.md b/CHANGELOG.md index 60346a21..e99bf9f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # developing +- Infra: use dependabot to update dependencies. [4623](https://github.com/beego/beego/pull/4623) - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619) - Chore: format code. [4615](https://github.com/beego/beego/pull/4615) - Test on Go v1.15.x & v1.16.x. [4614](https://github.com/beego/beego/pull/4614) From 819ec2164c39d232b7fcecad7a332722ebba984c Mon Sep 17 00:00:00 2001 From: t29kida Date: Fri, 21 May 2021 13:13:47 +0900 Subject: [PATCH 09/29] improve code quality * fill empty block of code --- CHANGELOG.md | 1 + server/web/controller.go | 3 ++- server/web/tree.go | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60346a21..71fbd0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ ## Fix Sonar +- [4624](https://github.com/beego/beego/pull/4624) - [4608](https://github.com/beego/beego/pull/4608) - [4473](https://github.com/beego/beego/pull/4473) - [4474](https://github.com/beego/beego/pull/4474) diff --git a/server/web/controller.go b/server/web/controller.go index a767fac4..c3a79ac8 100644 --- a/server/web/controller.go +++ b/server/web/controller.go @@ -250,7 +250,8 @@ func (c *Controller) Bind(obj interface{}) error { return c.BindJson(obj) } i, l := 0, len(ct[0]) - for ; i < l && ct[0][i] != ';'; i++ { + for i < l && ct[0][i] != ';' { + i++ } switch ct[0][0:i] { case "application/json": diff --git a/server/web/tree.go b/server/web/tree.go index 3716d4f4..fbe06f5e 100644 --- a/server/web/tree.go +++ b/server/web/tree.go @@ -294,7 +294,8 @@ func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string, ctx *context.Context) (runObject interface{}) { if len(pattern) > 0 { i, l := 0, len(pattern) - for ; i < l && pattern[i] == '/'; i++ { + for i < l && pattern[i] == '/' { + i++ } pattern = pattern[i:] } @@ -316,7 +317,8 @@ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string } var seg string i, l := 0, len(pattern) - for ; i < l && pattern[i] != '/'; i++ { + for i < l && pattern[i] != '/' { + i++ } if i == 0 { seg = pattern From 068573bfe354f99b16c7c4fc8783ed3fda5e00c5 Mon Sep 17 00:00:00 2001 From: t29kida Date: Fri, 21 May 2021 17:44:04 +0900 Subject: [PATCH 10/29] resolve code about deepsource test case * Empty string test can be improved CRT-A0004 --- server/web/tree.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/web/tree.go b/server/web/tree.go index fbe06f5e..4088e83e 100644 --- a/server/web/tree.go +++ b/server/web/tree.go @@ -284,7 +284,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, // Match router to runObject & params func (t *Tree) Match(pattern string, ctx *context.Context) (runObject interface{}) { - if len(pattern) == 0 || pattern[0] != '/' { + if pattern == "" || pattern[0] != '/' { return nil } w := make([]string, 0, 20) @@ -300,7 +300,7 @@ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string pattern = pattern[i:] } // Handle leaf nodes: - if len(pattern) == 0 { + if pattern == "" { for _, l := range t.leaves { if ok := l.match(treePattern, wildcardValues, ctx); ok { return l.runObject @@ -329,7 +329,7 @@ func (t *Tree) match(treePattern string, pattern string, wildcardValues []string } for _, subTree := range t.fixrouters { if subTree.prefix == seg { - if len(pattern) != 0 && pattern[0] == '/' { + if pattern != "" && pattern[0] == '/' { treePattern = pattern[1:] } else { treePattern = pattern From b0d6f3bd2fb7f033c570c0782259a642167fae65 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Fri, 21 May 2021 16:56:52 +0800 Subject: [PATCH 11/29] temp --- client/httplib/client_option_test.go | 12 ----- client/httplib/error_code.go | 8 ++++ client/httplib/httpclient.go | 69 +++++++++++++++++++++++----- client/httplib/httpclient_test.go | 12 ----- client/httplib/httplib.go | 19 ++++---- 5 files changed, 75 insertions(+), 45 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index 9efeaa24..3c9df88a 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -34,18 +34,6 @@ func (r *respCarrier) SetHttpResponse(resp *http.Response) { r.Resp = resp } -func (r *respCarrier) SetBytes(bytes []byte) { - r.bytes = bytes -} - -func (r *respCarrier) Bytes() []byte { - return r.bytes -} - -func (r *respCarrier) String() string { - return string(r.bytes) -} - func TestOption_WithEnableCookie(t *testing.T) { client, err := NewClient("test", "http://httpbin.org/", WithEnableCookie(true)) diff --git a/client/httplib/error_code.go b/client/httplib/error_code.go index bd349a34..177419ad 100644 --- a/client/httplib/error_code.go +++ b/client/httplib/error_code.go @@ -124,3 +124,11 @@ Make sure that: 1. You pass valid structure pointer to the function; 2. The body is valid YAML document `) + +var UnmarshalResponseToObjectFailed = berror.DefineCode(5001011, moduleName, + "UnmarshalResponseToObjectFailed", ` +Beego trying to unmarshal response's body to structure but failed. +There are several cases that cause this error: +1. You pass valid structure pointer to the function; +2. The body is valid json, Yaml or XML document +`) diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index 69aaa286..ecd557a4 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -15,6 +15,9 @@ package httplib import ( + "bytes" + "io" + "io/ioutil" "net/http" ) @@ -27,17 +30,32 @@ type Client struct { Setting BeegoHTTPSettings } -// If value implement this interface. http.response will saved by SetHttpResponse +// HttpResponseCarrier If value implement HttpResponseCarrier. http.Response will pass to SetHttpResponse type HttpResponseCarrier interface { SetHttpResponse(resp *http.Response) } -// If value implement this interface. bytes of http.response will saved by SetHttpResponse -type ResponseBytesCarrier interface { - // Cause of when user get http.response, the body stream is closed. So need to pass bytes by +// HttpBodyCarrier If value implement HttpBodyCarrier. http.Response.Body will pass to SetReader +type HttpBodyCarrier interface { + SetReader(r *io.ReadCloser) +} + +// HttpBytesCarrier If value implement HttpBytesCarrier. +// All the byte in http.Response.Body will pass to SetBytes +type HttpBytesCarrier interface { SetBytes(bytes []byte) } +// HttpStatusCarrier If value implement HttpStatusCarrier. http.Response.StatusCode will pass to SetStatusCode +type HttpStatusCarrier interface { + SetStatusCode(status int) +} + +// HttpHeaderCarrier If value implement HttpHeaderCarrier. http.Response.Header will pass to SetHeader +type HttpHeadersCarrier interface { + SetHeader(header map[string][]string) +} + // NewClient return a new http client func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, error) { res := &Client{ @@ -67,18 +85,47 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error if err != nil { return err } - if carrier, ok := (value).(HttpResponseCarrier); ok { - (carrier).SetHttpResponse(resp) - } - if carrier, ok := (value).(ResponseBytesCarrier); ok { - bytes, err := req.Bytes() + + switch carrier := value.(type) { + case HttpResponseCarrier: + b, err := req.Bytes() if err != nil { return err } - (carrier).SetBytes(bytes) + resp.Body = ioutil.NopCloser(bytes.NewReader(b)) + carrier.SetHttpResponse(resp) + fallthrough + case HttpBodyCarrier: + b, err := req.Bytes() + if err != nil { + return err + } + reader := ioutil.NopCloser(bytes.NewReader(b)) + carrier.SetReader(&reader) + fallthrough + case HttpBytesCarrier: + b, err := req.Bytes() + if err != nil { + return err + } + carrier.SetBytes(b) + fallthrough + case HttpStatusCarrier: + resp, err := req.Response() + if err != nil { + return err + } + carrier.SetStatusCode(resp.StatusCode) + fallthrough + case HttpHeadersCarrier: + resp, err := req.Response() + if err != nil { + return err + } + carrier.SetHeader(resp.Header) } - return req.ResponseForValue(value) + return req.ToValue(value) } // Get Send a GET request and try to give its result value diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index f006f18f..4a0ae843 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -55,18 +55,6 @@ func (s *slideSshowResponse) SetHttpResponse(resp *http.Response) { s.Resp = resp } -func (s *slideSshowResponse) SetBytes(bytes []byte) { - s.bytes = bytes -} - -func (s *slideSshowResponse) Bytes() []byte { - return s.bytes -} - -func (s *slideSshowResponse) String() string { - return string(s.bytes) -} - func TestClient_Get(t *testing.T) { client, err := NewClient("test", "http://httpbin.org/") if err != nil { diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index f032a294..eb51f3a5 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -656,16 +656,11 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.") } -// Response executes request client gets response manually. -func (b *BeegoHTTPRequest) Response() (*http.Response, error) { - return b.getResponse() -} - -// ResponseForValue attempts to resolve the response body to value using an existing method. +// ToValue attempts to resolve the response body to value using an existing method. // Calls Response inner. // If response header contain Content-Type, func will call ToJSON\ToXML\ToYAML. // Else it will try to parse body as json\yaml\xml, If all attempts fail, an error will be returned -func (b *BeegoHTTPRequest) ResponseForValue(value interface{}) error { +func (b *BeegoHTTPRequest) ToValue(value interface{}) error { if value == nil { return nil } @@ -674,8 +669,8 @@ func (b *BeegoHTTPRequest) ResponseForValue(value interface{}) error { if err != nil { return err } - contentType := strings.Split(resp.Header.Get(contentTypeKey), ";")[0] + contentType := strings.Split(resp.Header.Get(contentTypeKey), ";")[0] // try to parse it as content type switch contentType { case "application/json": @@ -697,8 +692,12 @@ func (b *BeegoHTTPRequest) ResponseForValue(value interface{}) error { return nil } - // TODO add new error type about can't parse body - return berror.Error(UnsupportedBodyType, "unsupported body data") + return berror.Error(UnmarshalResponseToObjectFailed, "unmarshal body to object failed.") +} + +// Response executes request client gets response manually. +func (b *BeegoHTTPRequest) Response() (*http.Response, error) { + return b.getResponse() } // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. From 44bbb3c3e9a363c6eacf5c378184dbc3c2a27595 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 22 May 2021 02:42:37 +0000 Subject: [PATCH 12/29] Bump actions/stale from 1 to 3.0.19 Bumps [actions/stale](https://github.com/actions/stale) from 1 to 3.0.19. - [Release notes](https://github.com/actions/stale/releases) - [Commits](https://github.com/actions/stale/compare/v1...v3.0.19) Signed-off-by: dependabot[bot] --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 412274a3..8142982a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v3.0.19 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is inactive for a long time.' From 4b1619b105a1d9c39c633c3a25feb1d0a227efde Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Sat, 22 May 2021 14:58:40 +0800 Subject: [PATCH 13/29] add more carrier --- client/httplib/client_option_test.go | 8 +-- client/httplib/httpclient.go | 50 ++++++++++--------- client/httplib/httpclient_test.go | 73 ++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 37 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index 3c9df88a..5b1420d7 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -26,12 +26,14 @@ import ( ) type respCarrier struct { - Resp *http.Response bytes []byte } -func (r *respCarrier) SetHttpResponse(resp *http.Response) { - r.Resp = resp +func (r *respCarrier) SetBytes(bytes []byte) { + r.bytes = bytes +} +func (r *respCarrier) String() string { + return string(r.bytes) } func TestOption_WithEnableCookie(t *testing.T) { diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index ecd557a4..70edbc1a 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -37,7 +37,7 @@ type HttpResponseCarrier interface { // HttpBodyCarrier If value implement HttpBodyCarrier. http.Response.Body will pass to SetReader type HttpBodyCarrier interface { - SetReader(r *io.ReadCloser) + SetReader(r io.ReadCloser) } // HttpBytesCarrier If value implement HttpBytesCarrier. @@ -80,52 +80,54 @@ func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHttpRequestOption) // handleResponse try to parse body to meaningful value func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error { - // send request - resp, err := req.Response() + err := c.handleCarrier(value, req) if err != nil { return err } - switch carrier := value.(type) { - case HttpResponseCarrier: + return req.ToValue(value) +} + +// handleCarrier set http data to value +func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { + resp, err := req.Response() + if err != nil { + return err + } + if value == nil { + return err + } + + if carrier, ok := value.(HttpResponseCarrier); ok { b, err := req.Bytes() if err != nil { return err } resp.Body = ioutil.NopCloser(bytes.NewReader(b)) carrier.SetHttpResponse(resp) - fallthrough - case HttpBodyCarrier: + } + if carrier, ok := value.(HttpBodyCarrier); ok { b, err := req.Bytes() if err != nil { return err } reader := ioutil.NopCloser(bytes.NewReader(b)) - carrier.SetReader(&reader) - fallthrough - case HttpBytesCarrier: + carrier.SetReader(reader) + } + if carrier, ok := value.(HttpBytesCarrier); ok { b, err := req.Bytes() if err != nil { return err } carrier.SetBytes(b) - fallthrough - case HttpStatusCarrier: - resp, err := req.Response() - if err != nil { - return err - } + } + if carrier, ok := value.(HttpStatusCarrier); ok { carrier.SetStatusCode(resp.StatusCode) - fallthrough - case HttpHeadersCarrier: - resp, err := req.Response() - if err != nil { - return err - } + } + if carrier, ok := value.(HttpHeadersCarrier); ok { carrier.SetHeader(resp.Header) } - - return req.ToValue(value) + return nil } // Get Send a GET request and try to give its result value diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index 4a0ae843..daedece3 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -16,6 +16,8 @@ package httplib import ( "encoding/xml" + "io" + "io/ioutil" "net/http" "testing" @@ -29,13 +31,40 @@ func TestNewClient(t *testing.T) { assert.Equal(t, true, client.Setting.EnableCookie) } -type slideSshowResponse struct { - Resp *http.Response - bytes []byte +type slideShowResponse struct { + Resp *http.Response + bytes []byte + StatusCode int + Body io.ReadCloser + Header map[string][]string Slideshow slideshow `json:"slideshow" yaml:"slideshow"` } +func (r *slideShowResponse) SetHttpResponse(resp *http.Response) { + r.Resp = resp +} + +func (r *slideShowResponse) SetBytes(bytes []byte) { + r.bytes = bytes +} + +func (r *slideShowResponse) SetReader(reader io.ReadCloser) { + r.Body = reader +} + +func (r *slideShowResponse) SetStatusCode(status int) { + r.StatusCode = status +} + +func (r *slideShowResponse) SetHeader(header map[string][]string) { + r.Header = header +} + +func (r *slideShowResponse) String() string { + return string(r.bytes) +} + type slideshow struct { XMLName xml.Name `xml:"slideshow"` @@ -51,8 +80,32 @@ type slide struct { Title string `json:"title" yaml:"title" xml:"title"` } -func (s *slideSshowResponse) SetHttpResponse(resp *http.Response) { - s.Resp = resp +func TestClient_handleCarrier(t *testing.T) { + v := "beego" + client, err := NewClient("test", "http://httpbin.org/", + WithUserAgent(v)) + if err != nil { + t.Fatal(err) + } + + var s = &slideShowResponse{} + err = client.Get(s, "/json") + if err != nil { + t.Fatal(err) + } + defer s.Body.Close() + + assert.NotNil(t, s.Resp) + assert.NotNil(t, s.Body) + assert.Equal(t, "429", s.Header["Content-Length"][0]) + assert.Equal(t, 200, s.StatusCode) + + b, err := ioutil.ReadAll(s.Body) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 429, len(b)) + assert.Equal(t, s.String(), string(b)) } func TestClient_Get(t *testing.T) { @@ -62,7 +115,7 @@ func TestClient_Get(t *testing.T) { } // json - var s *slideSshowResponse + var s *slideShowResponse err = client.Get(&s, "/json") if err != nil { t.Fatal(err) @@ -99,7 +152,7 @@ func TestClient_Post(t *testing.T) { t.Fatal(err) } - var resp = &slideSshowResponse{} + var resp = &slideShowResponse{} err = client.Get(resp, "/json") if err != nil { t.Fatal(err) @@ -120,7 +173,7 @@ func TestClient_Put(t *testing.T) { t.Fatal(err) } - var resp = &slideSshowResponse{} + var resp = &slideShowResponse{} err = client.Get(resp, "/json") if err != nil { t.Fatal(err) @@ -141,7 +194,7 @@ func TestClient_Delete(t *testing.T) { t.Fatal(err) } - var resp = &slideSshowResponse{} + var resp = &slideShowResponse{} err = client.Delete(resp, "/delete") if err != nil { t.Fatal(err) @@ -156,7 +209,7 @@ func TestClient_Head(t *testing.T) { t.Fatal(err) } - var resp = &slideSshowResponse{} + var resp = &slideShowResponse{} err = client.Head(resp, "") if err != nil { t.Fatal(err) From 4d6b22ef0859c3a0ffe6505d5314ca124b21cded Mon Sep 17 00:00:00 2001 From: ruancongyong Date: Thu, 20 May 2021 09:49:09 +0800 Subject: [PATCH 14/29] Propoal: Convenient way to generate mock object format code and add unit test --- core/bean/mock.go | 139 +++++++++++++++++++++++++++++++++++++++++ core/bean/mock_test.go | 74 ++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 core/bean/mock.go create mode 100644 core/bean/mock_test.go diff --git a/core/bean/mock.go b/core/bean/mock.go new file mode 100644 index 00000000..5b627f01 --- /dev/null +++ b/core/bean/mock.go @@ -0,0 +1,139 @@ +package bean + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// the mock object must be pointer of struct +// the element in mock object can be slices, structures, basic data types, pointers and interface +func Mock(v interface{}) (err error) { + + pv := reflect.ValueOf(v) + //the input must be pointer of struct + if pv.Kind() != reflect.Ptr || pv.IsNil() { + err = fmt.Errorf("not a pointer of struct") + return + } + err = mock(pv) + return +} + +func mock(pv reflect.Value) (err error) { + pt := pv.Type() + for i := 0; i < pt.Elem().NumField(); i++ { + ptt := pt.Elem().Field(i) + pvv := pv.Elem().FieldByName(ptt.Name) + if !pvv.CanSet() || !pvv.CanAddr() { + continue + } + kt := ptt.Type.Kind() + tagValue := ptt.Tag.Get("mock") + switch kt { + case reflect.Map: + continue + case reflect.Interface: + if pvv.IsNil() { // when interface is nil,can not sure the type + continue + } + pvv.Set(reflect.New(pvv.Elem().Type().Elem())) + err = mock(pvv.Elem()) + case reflect.Ptr: + err = mockPtr(pvv, ptt.Type.Elem()) + case reflect.Struct: + err = mock(pvv.Addr()) + case reflect.Array, reflect.Slice: + err = mockSlice(tagValue, pvv) + case reflect.String: + pvv.SetString(tagValue) + case reflect.Bool: + err = mockBool(tagValue, pvv) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + value, e := strconv.ParseInt(tagValue, 10, 64) + if e != nil || pvv.OverflowInt(value) { + err = fmt.Errorf("the value:%s is invalid", tagValue) + } + pvv.SetInt(value) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + value, e := strconv.ParseUint(tagValue, 10, 64) + if e != nil || pvv.OverflowUint(value) { + err = fmt.Errorf("the value:%s is invalid", tagValue) + } + pvv.SetUint(value) + case reflect.Float32, reflect.Float64: + value, e := strconv.ParseFloat(tagValue, pvv.Type().Bits()) + if e != nil || pvv.OverflowFloat(value) { + err = fmt.Errorf("the value:%s is invalid", tagValue) + } + pvv.SetFloat(value) + default: + } + if err != nil { + return + } + } + return +} + +// mock slice value +func mockSlice(tagValue string, pvv reflect.Value) (err error) { + sliceMetas := strings.Split(tagValue, ":") + if len(sliceMetas) != 2 || sliceMetas[0] != "length" { + return + } + length, e := strconv.Atoi(sliceMetas[1]) + if e != nil { + return e + } + + sliceType := reflect.SliceOf(pvv.Type().Elem()) //get slice type + itemType := sliceType.Elem() // get the type of item in slice + value := reflect.MakeSlice(sliceType, 0, length) + newSliceValue := make([]reflect.Value, 0, length) + for k := 0; k < length; k++ { + itemValue := reflect.New(itemType).Elem() + // if item in slice is struct or pointer,must set zero value + switch itemType.Kind() { + case reflect.Struct: + err = mock(itemValue.Addr()) + case reflect.Ptr: + if itemValue.IsNil() { + itemValue.Set(reflect.New(itemType.Elem())) + if e := mock(itemValue); e != nil { + return e + } + } + } + newSliceValue = append(newSliceValue, itemValue) + if err != nil { + return + } + } + value = reflect.Append(value, newSliceValue...) + pvv.Set(value) + return +} + +//mock bool value +func mockBool(tagValue string, pvv reflect.Value) (err error) { + switch tagValue { + case "true": + pvv.SetBool(true) + case "false": + pvv.SetBool(false) + default: + err = fmt.Errorf("the value:%s is invalid", tagValue) + } + return +} + +//mock pointer +func mockPtr(pvv reflect.Value, ptt reflect.Type) (err error) { + if pvv.IsNil() { + pvv.Set(reflect.New(ptt)) //must set nil value to zero value + } + err = mock(pvv) + return +} diff --git a/core/bean/mock_test.go b/core/bean/mock_test.go new file mode 100644 index 00000000..0fe81f43 --- /dev/null +++ b/core/bean/mock_test.go @@ -0,0 +1,74 @@ +package bean + +import ( + "fmt" + "testing" +) + +func TestMock(t *testing.T) { + type MockSubSubObject struct { + A int `mock:"20"` + } + type MockSubObjectAnoy struct { + Anoy int `mock:"20"` + } + type MockSubObject struct { + A bool `mock:"true"` + B MockSubSubObject + } + type MockObject struct { + A string `mock:"aaaaa"` + B int8 `mock:"10"` + C []*MockSubObject `mock:"length:2"` + D bool `mock:"true"` + E *MockSubObject + F []int `mock:"length:3"` + G InterfaceA + H InterfaceA + MockSubObjectAnoy + } + m := &MockObject{G: &ImplA{}} + err := Mock(m) + if err != nil { + t.Fatalf("mock failed: %v", err) + } + if m.A != "aaaaa" || m.B != 10 || m.C[1].B.A != 20 || + !m.E.A || m.E.B.A != 20 || !m.D || len(m.F) != 3 { + t.Fail() + } + _, ok := m.G.(*ImplA) + if !ok { + t.Fail() + } + _, ok = m.G.(*ImplB) + if ok { + t.Fail() + } + _, ok = m.H.(*ImplA) + if ok { + t.Fail() + } + if m.Anoy != 20 { + t.Fail() + } +} + +type InterfaceA interface { + Item() +} + +type ImplA struct { + A string `mock:"aaa"` +} + +func (i *ImplA) Item() { + fmt.Println("implA") +} + +type ImplB struct { + B string `mock:"bbb"` +} + +func (i *ImplB) Item() { + fmt.Println("implB") +} From 84fa2ccd9aa149d2eaa9e726b3e520839b4346bd Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Sun, 23 May 2021 23:01:01 +0800 Subject: [PATCH 15/29] fix lint temp --- client/httplib/README.md | 4 +-- client/httplib/client_option.go | 20 +++++++------- client/httplib/httpclient.go | 38 +++++++++++++-------------- client/httplib/httplib.go | 23 +++++++--------- client/httplib/mock/mock_condition.go | 2 +- go.mod | 1 + go.sum | 6 +++++ 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/client/httplib/README.md b/client/httplib/README.md index 1d22f341..a5723c6d 100644 --- a/client/httplib/README.md +++ b/client/httplib/README.md @@ -9,7 +9,7 @@ httplib is an libs help you to curl remote url. you can use Get to crawl data. import "github.com/beego/beego/v2/client/httplib" - + str, err := httplib.Get("http://beego.me/").String() if err != nil { // error @@ -39,7 +39,7 @@ Example: // GET httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) - + // POST httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) diff --git a/client/httplib/client_option.go b/client/httplib/client_option.go index e7402b8c..db81ec50 100644 --- a/client/httplib/client_option.go +++ b/client/httplib/client_option.go @@ -22,7 +22,7 @@ import ( ) type ClientOption func(client *Client) -type BeegoHttpRequestOption func(request *BeegoHTTPRequest) +type BeegoHTTPRequestOption func(request *BeegoHTTPRequest) // WithEnableCookie will enable cookie in all subsequent request func WithEnableCookie(enable bool) ClientOption { @@ -83,28 +83,28 @@ func WithEnableGzip(enable bool) ClientOption { // BeegoHttpRequestOption // WithTimeout sets connect time out and read-write time out for BeegoRequest. -func WithTimeout(connectTimeout, readWriteTimeout time.Duration) BeegoHttpRequestOption { +func WithTimeout(connectTimeout, readWriteTimeout time.Duration) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.SetTimeout(connectTimeout, readWriteTimeout) } } // WithHeader adds header item string in request. -func WithHeader(key, value string) BeegoHttpRequestOption { +func WithHeader(key, value string) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.Header(key, value) } } // WithCookie adds a cookie to the request. -func WithCookie(cookie *http.Cookie) BeegoHttpRequestOption { +func WithCookie(cookie *http.Cookie) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.Header("Cookie", cookie.String()) } } // Withtokenfactory adds a custom function to set Authorization -func WithTokenFactory(tokenFactory func() string) BeegoHttpRequestOption { +func WithTokenFactory(tokenFactory func() string) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { t := tokenFactory() @@ -113,7 +113,7 @@ func WithTokenFactory(tokenFactory func() string) BeegoHttpRequestOption { } // WithBasicAuth adds a custom function to set basic auth -func WithBasicAuth(basicAuth func() (string, string)) BeegoHttpRequestOption { +func WithBasicAuth(basicAuth func() (string, string)) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { username, password := basicAuth() request.SetBasicAuth(username, password) @@ -121,21 +121,21 @@ func WithBasicAuth(basicAuth func() (string, string)) BeegoHttpRequestOption { } // WithFilters will use the filter as the invocation filters -func WithFilters(fcs ...FilterChain) BeegoHttpRequestOption { +func WithFilters(fcs ...FilterChain) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.SetFilters(fcs...) } } // WithContentType adds ContentType in header -func WithContentType(contentType string) BeegoHttpRequestOption { +func WithContentType(contentType string) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.Header(contentTypeKey, contentType) } } // WithParam adds query param in to request. -func WithParam(key, value string) BeegoHttpRequestOption { +func WithParam(key, value string) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.Param(key, value) } @@ -145,7 +145,7 @@ func WithParam(key, value string) BeegoHttpRequestOption { // default is 0 (never retry) // -1 retry indefinitely (forever) // Other numbers specify the exact retry amount -func WithRetry(times int, delay time.Duration) BeegoHttpRequestOption { +func WithRetry(times int, delay time.Duration) BeegoHTTPRequestOption { return func(request *BeegoHTTPRequest) { request.Retries(times) request.RetryDelay(delay) diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index 70edbc1a..45916a40 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -25,29 +25,29 @@ import ( type Client struct { Name string Endpoint string - CommonOpts []BeegoHttpRequestOption + CommonOpts []BeegoHTTPRequestOption Setting BeegoHTTPSettings } -// HttpResponseCarrier If value implement HttpResponseCarrier. http.Response will pass to SetHttpResponse -type HttpResponseCarrier interface { +// HTTPResponseCarrier If value implement HTTPResponseCarrier. http.Response will pass to SetHttpResponse +type HTTPResponseCarrier interface { SetHttpResponse(resp *http.Response) } -// HttpBodyCarrier If value implement HttpBodyCarrier. http.Response.Body will pass to SetReader -type HttpBodyCarrier interface { +// HTTPBodyCarrier If value implement HTTPBodyCarrier. http.Response.Body will pass to SetReader +type HTTPBodyCarrier interface { SetReader(r io.ReadCloser) } -// HttpBytesCarrier If value implement HttpBytesCarrier. +// HTTPBytesCarrier If value implement HTTPBytesCarrier. // All the byte in http.Response.Body will pass to SetBytes -type HttpBytesCarrier interface { +type HTTPBytesCarrier interface { SetBytes(bytes []byte) } -// HttpStatusCarrier If value implement HttpStatusCarrier. http.Response.StatusCode will pass to SetStatusCode -type HttpStatusCarrier interface { +// HTTPStatusCarrier If value implement HTTPStatusCarrier. http.Response.StatusCode will pass to SetStatusCode +type HTTPStatusCarrier interface { SetStatusCode(status int) } @@ -70,7 +70,7 @@ func NewClient(name string, endpoint string, opts ...ClientOption) (*Client, err return res, nil } -func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHttpRequestOption) { +func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHTTPRequestOption) { req.Setting(c.Setting) opts = append(c.CommonOpts, opts...) for _, o := range opts { @@ -98,7 +98,7 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { return err } - if carrier, ok := value.(HttpResponseCarrier); ok { + if carrier, ok := value.(HTTPResponseCarrier); ok { b, err := req.Bytes() if err != nil { return err @@ -106,7 +106,7 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { resp.Body = ioutil.NopCloser(bytes.NewReader(b)) carrier.SetHttpResponse(resp) } - if carrier, ok := value.(HttpBodyCarrier); ok { + if carrier, ok := value.(HTTPBodyCarrier); ok { b, err := req.Bytes() if err != nil { return err @@ -114,14 +114,14 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { reader := ioutil.NopCloser(bytes.NewReader(b)) carrier.SetReader(reader) } - if carrier, ok := value.(HttpBytesCarrier); ok { + if carrier, ok := value.(HTTPBytesCarrier); ok { b, err := req.Bytes() if err != nil { return err } carrier.SetBytes(b) } - if carrier, ok := value.(HttpStatusCarrier); ok { + if carrier, ok := value.(HTTPStatusCarrier); ok { carrier.SetStatusCode(resp.StatusCode) } if carrier, ok := value.(HttpHeadersCarrier); ok { @@ -131,14 +131,14 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { } // Get Send a GET request and try to give its result value -func (c *Client) Get(value interface{}, path string, opts ...BeegoHttpRequestOption) error { +func (c *Client) Get(value interface{}, path string, opts ...BeegoHTTPRequestOption) error { req := Get(c.Endpoint + path) c.customReq(req, opts) return c.handleResponse(value, req) } // Post Send a POST request and try to give its result value -func (c *Client) Post(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { +func (c *Client) Post(value interface{}, path string, body interface{}, opts ...BeegoHTTPRequestOption) error { req := Post(c.Endpoint + path) c.customReq(req, opts) if body != nil { @@ -148,7 +148,7 @@ func (c *Client) Post(value interface{}, path string, body interface{}, opts ... } // Put Send a Put request and try to give its result value -func (c *Client) Put(value interface{}, path string, body interface{}, opts ...BeegoHttpRequestOption) error { +func (c *Client) Put(value interface{}, path string, body interface{}, opts ...BeegoHTTPRequestOption) error { req := Put(c.Endpoint + path) c.customReq(req, opts) if body != nil { @@ -158,14 +158,14 @@ func (c *Client) Put(value interface{}, path string, body interface{}, opts ...B } // Delete Send a Delete request and try to give its result value -func (c *Client) Delete(value interface{}, path string, opts ...BeegoHttpRequestOption) error { +func (c *Client) Delete(value interface{}, path string, opts ...BeegoHTTPRequestOption) error { req := Delete(c.Endpoint + path) c.customReq(req, opts) return c.handleResponse(value, req) } // Head Send a Head request and try to give its result value -func (c *Client) Head(value interface{}, path string, opts ...BeegoHttpRequestOption) error { +func (c *Client) Head(value interface{}, path string, opts ...BeegoHTTPRequestOption) error { req := Head(c.Endpoint + path) c.customReq(req, opts) return c.handleResponse(value, req) diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index eb51f3a5..df53bb9c 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -124,7 +124,6 @@ type BeegoHTTPRequest struct { setting BeegoHTTPSettings resp *http.Response body []byte - dump []byte } // GetRequest returns the request object @@ -199,7 +198,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { // SetProtocolVersion sets the protocol version for incoming requests. // Client requests always use HTTP/1.1 func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { - if len(vers) == 0 { + if vers == "" { vers = "HTTP/1.1" } @@ -511,18 +510,16 @@ func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper { DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), MaxIdleConnsPerHost: 100, } - } else { + } else if t, ok := trans.(*http.Transport); ok { // if b.transport is *http.Transport then set the settings. - if t, ok := trans.(*http.Transport); ok { - if t.TLSClientConfig == nil { - t.TLSClientConfig = b.setting.TLSClientConfig - } - if t.Proxy == nil { - t.Proxy = b.setting.Proxy - } - if t.DialContext == nil { - t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) - } + if t.TLSClientConfig == nil { + t.TLSClientConfig = b.setting.TLSClientConfig + } + if t.Proxy == nil { + t.Proxy = b.setting.Proxy + } + if t.DialContext == nil { + t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) } } return trans diff --git a/client/httplib/mock/mock_condition.go b/client/httplib/mock/mock_condition.go index 53d3d703..912699ff 100644 --- a/client/httplib/mock/mock_condition.go +++ b/client/httplib/mock/mock_condition.go @@ -57,7 +57,7 @@ func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondi } func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { - res := true + var res bool if len(sc.path) > 0 { res = sc.matchPath(ctx, req) } else if len(sc.pathReg) > 0 { diff --git a/go.mod b/go.mod index 0305be1e..99b24947 100644 --- a/go.mod +++ b/go.mod @@ -37,4 +37,5 @@ require ( golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a google.golang.org/grpc v1.37.1 gopkg.in/yaml.v2 v2.4.0 + mvdan.cc/gofumpt v0.1.1 // indirect ) diff --git a/go.sum b/go.sum index c144e80d..a20c509f 100644 --- a/go.sum +++ b/go.sum @@ -333,6 +333,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -427,6 +428,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -512,6 +515,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -589,6 +593,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mvdan.cc/gofumpt v0.1.1 h1:bi/1aS/5W00E2ny5q65w9SnKpWEF/UIOqDYBILpo9rA= +mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= From 162247b8a5268b3da0f871dd1ae918b2a660d460 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Mon, 24 May 2021 11:26:52 +0800 Subject: [PATCH 16/29] fix lint --- client/httplib/httpclient_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index daedece3..e13bdf74 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -199,6 +199,7 @@ func TestClient_Delete(t *testing.T) { if err != nil { t.Fatal(err) } + defer resp.Resp.Body.Close() assert.NotNil(t, resp) assert.Equal(t, http.MethodDelete, resp.Resp.Request.Method) } @@ -214,6 +215,7 @@ func TestClient_Head(t *testing.T) { if err != nil { t.Fatal(err) } + defer resp.Resp.Body.Close() assert.NotNil(t, resp) assert.Equal(t, http.MethodHead, resp.Resp.Request.Method) } From 5f5afc111a964760332e5d4abe6a660e6155e326 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Mon, 24 May 2021 14:45:44 +0800 Subject: [PATCH 17/29] add xml-roundtrip-validator --- client/httplib/httplib.go | 5 +++++ go.mod | 1 + go.sum | 3 +++ 3 files changed, 9 insertions(+) diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index df53bb9c..ec6c2013 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -49,6 +49,7 @@ import ( "strings" "time" + xrv "github.com/mattermost/xml-roundtrip-validator" "gopkg.in/yaml.v2" "github.com/beego/beego/v2/core/berror" @@ -638,6 +639,10 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { if err != nil { return err } + if err := xrv.Validate(bytes.NewReader(data)); err != nil { + panic(err) + } + return berror.Wrap(xml.Unmarshal(data, v), UnmarshalXMLResponseToObjectFailed, "unmarshal xml body to object failed.") } diff --git a/go.mod b/go.mod index 99b24947..abef3a2b 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 github.com/lib/pq v1.10.2 + github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/mattn/go-sqlite3 v1.14.7 github.com/mitchellh/mapstructure v1.4.1 github.com/opentracing/opentracing-go v1.2.0 diff --git a/go.sum b/go.sum index a20c509f..a60bd832 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,8 @@ github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -367,6 +369,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112 h1:NBrpnvz0pDPf3+HXZ1C9GcJd1DTpWDLcLWZhNq6uP7o= From 00b6c32dc2888b58dac4f730e2b19d8bd3ba5fc2 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Mon, 24 May 2021 15:17:02 +0800 Subject: [PATCH 18/29] remove panic --- client/httplib/client_option_test.go | 2 +- client/httplib/httplib.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index 5b1420d7..fd7ba7df 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -246,7 +246,7 @@ func TestOption_WithRetry(t *testing.T) { } retryAmount := 1 - retryDelay := 1400 * time.Millisecond + retryDelay := 200 * time.Millisecond startTime := time.Now().UnixNano() / int64(time.Millisecond) _ = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index ec6c2013..be4f388e 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -640,7 +640,7 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { return err } if err := xrv.Validate(bytes.NewReader(data)); err != nil { - panic(err) + return err } return berror.Wrap(xml.Unmarshal(data, v), From 580d694b0ecc012239f35aa9af22bc8823842a1e Mon Sep 17 00:00:00 2001 From: ruancongyong Date: Mon, 24 May 2021 21:26:46 +0800 Subject: [PATCH 19/29] fix deepsource bug --- CHANGELOG.md | 1 + core/bean/mock.go | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c45fece0..bb8dbcd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # developing +- Add: Convenient way to generate mock object [4620](https://github.com/beego/beego/issues/4397) - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619) - Chore: format code. [4615](https://github.com/beego/beego/pull/4615) - Env: non-empty GOBIN & GOPATH. [4613](https://github.com/beego/beego/pull/4613) diff --git a/core/bean/mock.go b/core/bean/mock.go index 5b627f01..707d875e 100644 --- a/core/bean/mock.go +++ b/core/bean/mock.go @@ -10,9 +10,8 @@ import ( // the mock object must be pointer of struct // the element in mock object can be slices, structures, basic data types, pointers and interface func Mock(v interface{}) (err error) { - pv := reflect.ValueOf(v) - //the input must be pointer of struct + // the input must be pointer of struct if pv.Kind() != reflect.Ptr || pv.IsNil() { err = fmt.Errorf("not a pointer of struct") return @@ -26,7 +25,7 @@ func mock(pv reflect.Value) (err error) { for i := 0; i < pt.Elem().NumField(); i++ { ptt := pt.Elem().Field(i) pvv := pv.Elem().FieldByName(ptt.Name) - if !pvv.CanSet() || !pvv.CanAddr() { + if !pvv.CanSet() { continue } kt := ptt.Type.Kind() @@ -79,8 +78,12 @@ func mock(pv reflect.Value) (err error) { // mock slice value func mockSlice(tagValue string, pvv reflect.Value) (err error) { + if len(tagValue) == 0 { + return + } sliceMetas := strings.Split(tagValue, ":") if len(sliceMetas) != 2 || sliceMetas[0] != "length" { + err = fmt.Errorf("the value:%s is invalid", tagValue) return } length, e := strconv.Atoi(sliceMetas[1]) @@ -88,7 +91,7 @@ func mockSlice(tagValue string, pvv reflect.Value) (err error) { return e } - sliceType := reflect.SliceOf(pvv.Type().Elem()) //get slice type + sliceType := reflect.SliceOf(pvv.Type().Elem()) // get slice type itemType := sliceType.Elem() // get the type of item in slice value := reflect.MakeSlice(sliceType, 0, length) newSliceValue := make([]reflect.Value, 0, length) @@ -116,7 +119,7 @@ func mockSlice(tagValue string, pvv reflect.Value) (err error) { return } -//mock bool value +// mock bool value func mockBool(tagValue string, pvv reflect.Value) (err error) { switch tagValue { case "true": @@ -129,10 +132,10 @@ func mockBool(tagValue string, pvv reflect.Value) (err error) { return } -//mock pointer +// mock pointer func mockPtr(pvv reflect.Value, ptt reflect.Type) (err error) { if pvv.IsNil() { - pvv.Set(reflect.New(ptt)) //must set nil value to zero value + pvv.Set(reflect.New(ptt)) // must set nil value to zero value } err = mock(pvv) return From b159750ef44991dab0f3937118518f6af37e1a1e Mon Sep 17 00:00:00 2001 From: ruancongyong Date: Mon, 24 May 2021 21:40:52 +0800 Subject: [PATCH 20/29] =?UTF-8?q?=E4=BF=AE=E6=94=B9changelog=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=92=8Cdeepsource=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- core/bean/mock.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d71682..97fa7111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # developing -- Add: Convenient way to generate mock object [4620](https://github.com/beego/beego/issues/4397) +- Add: Convenient way to generate mock object [4620](https://github.com/beego/beego/issues/4620) - Infra: use dependabot to update dependencies. [4623](https://github.com/beego/beego/pull/4623) - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619) - Chore: format code. [4615](https://github.com/beego/beego/pull/4615) diff --git a/core/bean/mock.go b/core/bean/mock.go index 707d875e..0a8d3e29 100644 --- a/core/bean/mock.go +++ b/core/bean/mock.go @@ -78,7 +78,7 @@ func mock(pv reflect.Value) (err error) { // mock slice value func mockSlice(tagValue string, pvv reflect.Value) (err error) { - if len(tagValue) == 0 { + if tagValue == "" { return } sliceMetas := strings.Split(tagValue, ":") From 8a241927629c65855a1dad0427c678b8d498b53a Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 25 May 2021 09:56:54 +0800 Subject: [PATCH 21/29] fix lint --- CHANGELOG.md | 1 + client/httplib/client_option.go | 6 ++++-- client/httplib/httpclient.go | 11 ++++++----- client/httplib/httpclient_test.go | 2 +- client/httplib/httplib.go | 6 +----- go.mod | 1 - go.sum | 2 -- 7 files changed, 13 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb3a4f7..87a34e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # developing - Add http client and option func. [4455](https://github.com/beego/beego/issues/4455) +- Add: Convenient way to generate mock object [4620](https://github.com/beego/beego/issues/4620) - Infra: use dependabot to update dependencies. [4623](https://github.com/beego/beego/pull/4623) - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619) - Chore: format code. [4615](https://github.com/beego/beego/pull/4615) diff --git a/client/httplib/client_option.go b/client/httplib/client_option.go index db81ec50..f970e67d 100644 --- a/client/httplib/client_option.go +++ b/client/httplib/client_option.go @@ -21,8 +21,10 @@ import ( "time" ) -type ClientOption func(client *Client) -type BeegoHTTPRequestOption func(request *BeegoHTTPRequest) +type ( + ClientOption func(client *Client) + BeegoHTTPRequestOption func(request *BeegoHTTPRequest) +) // WithEnableCookie will enable cookie in all subsequent request func WithEnableCookie(enable bool) ClientOption { diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index 45916a40..32ab4d6c 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -30,9 +30,9 @@ type Client struct { Setting BeegoHTTPSettings } -// HTTPResponseCarrier If value implement HTTPResponseCarrier. http.Response will pass to SetHttpResponse +// HTTPResponseCarrier If value implement HTTPResponseCarrier. http.Response will pass to SetHTTPResponse type HTTPResponseCarrier interface { - SetHttpResponse(resp *http.Response) + SetHTTPResponse(resp *http.Response) } // HTTPBodyCarrier If value implement HTTPBodyCarrier. http.Response.Body will pass to SetReader @@ -52,7 +52,7 @@ type HTTPStatusCarrier interface { } // HttpHeaderCarrier If value implement HttpHeaderCarrier. http.Response.Header will pass to SetHeader -type HttpHeadersCarrier interface { +type HTTPHeadersCarrier interface { SetHeader(header map[string][]string) } @@ -91,6 +91,7 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error // handleCarrier set http data to value func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { resp, err := req.Response() + defer resp.Body.Close() if err != nil { return err } @@ -104,7 +105,7 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { return err } resp.Body = ioutil.NopCloser(bytes.NewReader(b)) - carrier.SetHttpResponse(resp) + carrier.SetHTTPResponse(resp) } if carrier, ok := value.(HTTPBodyCarrier); ok { b, err := req.Bytes() @@ -124,7 +125,7 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { if carrier, ok := value.(HTTPStatusCarrier); ok { carrier.SetStatusCode(resp.StatusCode) } - if carrier, ok := value.(HttpHeadersCarrier); ok { + if carrier, ok := value.(HTTPHeadersCarrier); ok { carrier.SetHeader(resp.Header) } return nil diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index e13bdf74..6437e534 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -41,7 +41,7 @@ type slideShowResponse struct { Slideshow slideshow `json:"slideshow" yaml:"slideshow"` } -func (r *slideShowResponse) SetHttpResponse(resp *http.Response) { +func (r *slideShowResponse) SetHTTPResponse(resp *http.Response) { r.Resp = resp } diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index be4f388e..6a78e13b 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -49,7 +49,6 @@ import ( "strings" "time" - xrv "github.com/mattermost/xml-roundtrip-validator" "gopkg.in/yaml.v2" "github.com/beego/beego/v2/core/berror" @@ -639,10 +638,6 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { if err != nil { return err } - if err := xrv.Validate(bytes.NewReader(data)); err != nil { - return err - } - return berror.Wrap(xml.Unmarshal(data, v), UnmarshalXMLResponseToObjectFailed, "unmarshal xml body to object failed.") } @@ -668,6 +663,7 @@ func (b *BeegoHTTPRequest) ToValue(value interface{}) error { } resp, err := b.Response() + defer resp.Body.Close() if err != nil { return err } diff --git a/go.mod b/go.mod index abef3a2b..99b24947 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 github.com/lib/pq v1.10.2 - github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/mattn/go-sqlite3 v1.14.7 github.com/mitchellh/mapstructure v1.4.1 github.com/opentracing/opentracing-go v1.2.0 diff --git a/go.sum b/go.sum index a60bd832..91616ec7 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,6 @@ github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= -github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= From 2182081831bb8959e88cdd8e69743b3fba56ab03 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 25 May 2021 10:10:06 +0800 Subject: [PATCH 22/29] fix lint --- client/httplib/client_option_test.go | 17 +++++++++-------- client/httplib/httpclient.go | 4 ++-- client/httplib/httpclient_test.go | 11 +++++------ client/httplib/httplib.go | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index fd7ba7df..28cbc6fe 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -32,6 +32,7 @@ type respCarrier struct { func (r *respCarrier) SetBytes(bytes []byte) { r.bytes = bytes } + func (r *respCarrier) String() string { return string(r.bytes) } @@ -44,7 +45,7 @@ func TestOption_WithEnableCookie(t *testing.T) { } v := "smallfish" - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/cookies/set?k1="+v) if err != nil { t.Fatal(err) @@ -71,7 +72,7 @@ func TestOption_WithUserAgent(t *testing.T) { t.Fatal(err) } - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) @@ -119,7 +120,7 @@ func TestOption_WithHTTPSetting(t *testing.T) { t.Fatal(err) } - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/get") if err != nil { t.Fatal(err) @@ -139,7 +140,7 @@ func TestOption_WithHeader(t *testing.T) { } client.CommonOpts = append(client.CommonOpts, WithHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")) - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) @@ -162,7 +163,7 @@ func TestOption_WithTokenFactory(t *testing.T) { return "testauth" })) - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/headers") if err != nil { t.Fatal(err) @@ -181,7 +182,7 @@ func TestOption_WithBasicAuth(t *testing.T) { t.Fatal(err) } - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/basic-auth/user/passwd", WithBasicAuth(func() (string, string) { return "user", "passwd" @@ -203,7 +204,7 @@ func TestOption_WithContentType(t *testing.T) { } v := "application/json" - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/headers", WithContentType(v)) if err != nil { t.Fatal(err) @@ -223,7 +224,7 @@ func TestOption_WithParam(t *testing.T) { } v := "smallfish" - var resp = &respCarrier{} + resp := &respCarrier{} err = client.Get(resp, "/get", WithParam("username", v)) if err != nil { t.Fatal(err) diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index 32ab4d6c..ef2e27eb 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -91,14 +91,14 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error // handleCarrier set http data to value func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { resp, err := req.Response() - defer resp.Body.Close() if err != nil { return err } + defer resp.Body.Close() + if value == nil { return err } - if carrier, ok := value.(HTTPResponseCarrier); ok { b, err := req.Bytes() if err != nil { diff --git a/client/httplib/httpclient_test.go b/client/httplib/httpclient_test.go index 6437e534..6bb00258 100644 --- a/client/httplib/httpclient_test.go +++ b/client/httplib/httpclient_test.go @@ -88,7 +88,7 @@ func TestClient_handleCarrier(t *testing.T) { t.Fatal(err) } - var s = &slideShowResponse{} + s := &slideShowResponse{} err = client.Get(s, "/json") if err != nil { t.Fatal(err) @@ -143,7 +143,6 @@ func TestClient_Get(t *testing.T) { assert.Equal(t, "Sample Slide Show", s.Slideshow.Title) assert.Equal(t, 2, len(s.Slideshow.Slides)) assert.Equal(t, "Overview", s.Slideshow.Slides[1].Title) - } func TestClient_Post(t *testing.T) { @@ -152,7 +151,7 @@ func TestClient_Post(t *testing.T) { t.Fatal(err) } - var resp = &slideShowResponse{} + resp := &slideShowResponse{} err = client.Get(resp, "/json") if err != nil { t.Fatal(err) @@ -173,7 +172,7 @@ func TestClient_Put(t *testing.T) { t.Fatal(err) } - var resp = &slideShowResponse{} + resp := &slideShowResponse{} err = client.Get(resp, "/json") if err != nil { t.Fatal(err) @@ -194,7 +193,7 @@ func TestClient_Delete(t *testing.T) { t.Fatal(err) } - var resp = &slideShowResponse{} + resp := &slideShowResponse{} err = client.Delete(resp, "/delete") if err != nil { t.Fatal(err) @@ -210,7 +209,7 @@ func TestClient_Head(t *testing.T) { t.Fatal(err) } - var resp = &slideShowResponse{} + resp := &slideShowResponse{} err = client.Head(resp, "") if err != nil { t.Fatal(err) diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index 6a78e13b..76583712 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -663,10 +663,10 @@ func (b *BeegoHTTPRequest) ToValue(value interface{}) error { } resp, err := b.Response() - defer resp.Body.Close() if err != nil { return err } + defer resp.Body.Close() contentType := strings.Split(resp.Header.Get(contentTypeKey), ";")[0] // try to parse it as content type From f8df3d1bee5eb46a6e23a6c55bd184aa27cf3485 Mon Sep 17 00:00:00 2001 From: holooooo <844082183@qq.com> Date: Tue, 25 May 2021 15:15:43 +0800 Subject: [PATCH 23/29] fix read on close --- client/httplib/client_option_test.go | 2 +- client/httplib/httpclient.go | 25 +++++++++++++------------ client/httplib/httplib.go | 8 +------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/client/httplib/client_option_test.go b/client/httplib/client_option_test.go index 28cbc6fe..79e5103f 100644 --- a/client/httplib/client_option_test.go +++ b/client/httplib/client_option_test.go @@ -247,7 +247,7 @@ func TestOption_WithRetry(t *testing.T) { } retryAmount := 1 - retryDelay := 200 * time.Millisecond + retryDelay := 800 * time.Millisecond startTime := time.Now().UnixNano() / int64(time.Millisecond) _ = client.Get(nil, "", WithRetry(retryAmount, retryDelay)) diff --git a/client/httplib/httpclient.go b/client/httplib/httpclient.go index ef2e27eb..c2a61fcf 100644 --- a/client/httplib/httpclient.go +++ b/client/httplib/httpclient.go @@ -80,7 +80,13 @@ func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHTTPRequestOption) // handleResponse try to parse body to meaningful value func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error { - err := c.handleCarrier(value, req) + // make sure req.resp is not nil + _, err := req.Bytes() + if err != nil { + return err + } + + err = c.handleCarrier(value, req) if err != nil { return err } @@ -90,22 +96,17 @@ func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error // handleCarrier set http data to value func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { - resp, err := req.Response() - if err != nil { - return err - } - defer resp.Body.Close() - if value == nil { - return err + return nil } + if carrier, ok := value.(HTTPResponseCarrier); ok { b, err := req.Bytes() if err != nil { return err } - resp.Body = ioutil.NopCloser(bytes.NewReader(b)) - carrier.SetHTTPResponse(resp) + req.resp.Body = ioutil.NopCloser(bytes.NewReader(b)) + carrier.SetHTTPResponse(req.resp) } if carrier, ok := value.(HTTPBodyCarrier); ok { b, err := req.Bytes() @@ -123,10 +124,10 @@ func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error { carrier.SetBytes(b) } if carrier, ok := value.(HTTPStatusCarrier); ok { - carrier.SetStatusCode(resp.StatusCode) + carrier.SetStatusCode(req.resp.StatusCode) } if carrier, ok := value.(HTTPHeadersCarrier); ok { - carrier.SetHeader(resp.Header) + carrier.SetHeader(req.resp.Header) } return nil } diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index 76583712..b102f687 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -662,13 +662,7 @@ func (b *BeegoHTTPRequest) ToValue(value interface{}) error { return nil } - resp, err := b.Response() - if err != nil { - return err - } - defer resp.Body.Close() - - contentType := strings.Split(resp.Header.Get(contentTypeKey), ";")[0] + contentType := strings.Split(b.resp.Header.Get(contentTypeKey), ";")[0] // try to parse it as content type switch contentType { case "application/json": From 9327e2b026b85144e68727115db821df6276fd58 Mon Sep 17 00:00:00 2001 From: guoxingyong Date: Wed, 26 May 2021 09:56:44 +0800 Subject: [PATCH 24/29] task manager graceful shutdown support https://github.com/beego/beego/issues/4631 --- task/task.go | 20 ++++++++++++++++++-- task/task_test.go | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/task/task.go b/task/task.go index 00e67c4b..29b570e9 100644 --- a/task/task.go +++ b/task/task.go @@ -37,6 +37,7 @@ type taskManager struct { stop chan bool changed chan bool started bool + wait sync.WaitGroup } func newTaskManager() *taskManager { @@ -508,7 +509,7 @@ func (m *taskManager) run() { select { case now = <-time.After(effective.Sub(now)): // wait for effective time - runNextTasks(sortList, effective) + m.runNextTasks(sortList, effective) continue case <-m.changed: // tasks have been changed, set all tasks run again now now = time.Now().Local() @@ -540,7 +541,7 @@ func (m *taskManager) markManagerStop() { } // runNextTasks it runs next task which next run time is equal to effective -func runNextTasks(sortList *MapSorter, effective time.Time) { +func (m *taskManager) runNextTasks(sortList *MapSorter, effective time.Time) { // Run every entry whose next time was this effective time. var i = 0 for _, e := range sortList.Vals { @@ -553,19 +554,23 @@ func runNextTasks(sortList *MapSorter, effective time.Time) { ctx := context.Background() if duration := e.GetTimeout(ctx); duration != 0 { go func(e Tasker) { + m.wait.Add(1) ctx, cancelFunc := context.WithTimeout(ctx, duration) defer cancelFunc() err := e.Run(ctx) if err != nil { log.Printf("tasker.run err: %s\n", err.Error()) } + m.wait.Done() }(e) } else { go func(e Tasker) { + m.wait.Add(1) err := e.Run(ctx) if err != nil { log.Printf("tasker.run err: %s\n", err.Error()) } + m.wait.Done() }(e) } @@ -581,6 +586,17 @@ func (m *taskManager) StopTask() { }() } +// StopTask stop all tasks +func (m *taskManager) GracefulShutdown() <-chan struct{} { + done := make(chan struct{}, 0) + go func() { + m.stop <- true + m.wait.Wait() + close(done) + }() + return done +} + // AddTask add task with name func (m *taskManager) AddTask(taskname string, t Tasker) { isChanged := false diff --git a/task/task_test.go b/task/task_test.go index 1078aa01..8d274e8f 100644 --- a/task/task_test.go +++ b/task/task_test.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "testing" "time" @@ -177,6 +178,26 @@ func TestCrudTask(t *testing.T) { assert.Equal(t, 0, len(m.adminTaskList)) } +func TestGracefulShutdown(t *testing.T) { + m := newTaskManager() + defer m.ClearTask() + waitDone := atomic.Value{} + waitDone.Store(false) + tk := NewTask("everySecond", "* * * * * *", func(ctx context.Context) error { + fmt.Println("hello world") + time.Sleep(2 * time.Second) + waitDone.Store(true) + return nil + }) + m.AddTask("taska", tk) + m.StartTask() + time.Sleep(1 * time.Second) + shutdown := m.GracefulShutdown() + assert.False(t, waitDone.Load().(bool)) + <-shutdown + assert.True(t, waitDone.Load().(bool)) +} + func wait(wg *sync.WaitGroup) chan bool { ch := make(chan bool) go func() { From 93b73ddb34bdf9371b080f1649c5658aa5492bbf Mon Sep 17 00:00:00 2001 From: guoxingyong Date: Wed, 26 May 2021 10:19:03 +0800 Subject: [PATCH 25/29] task manager graceful shutdown support --- task/task.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/task/task.go b/task/task.go index 29b570e9..b4478cc4 100644 --- a/task/task.go +++ b/task/task.go @@ -472,6 +472,11 @@ func ClearTask() { globalTaskManager.ClearTask() } +// GracefulShutdown wait all task done +func GracefulShutdown() <-chan struct{} { + return globalTaskManager.GracefulShutdown() +} + // StartTask start all tasks func (m *taskManager) StartTask() { m.taskLock.Lock() @@ -586,7 +591,7 @@ func (m *taskManager) StopTask() { }() } -// StopTask stop all tasks +// GracefulShutdown wait all task done func (m *taskManager) GracefulShutdown() <-chan struct{} { done := make(chan struct{}, 0) go func() { From 8a193c5004475d7ea595120d5937ad4931ef2470 Mon Sep 17 00:00:00 2001 From: guoxingyong Date: Wed, 26 May 2021 10:34:11 +0800 Subject: [PATCH 26/29] task manager graceful shutdown support --- task/task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/task/task.go b/task/task.go index b4478cc4..fd3c6e28 100644 --- a/task/task.go +++ b/task/task.go @@ -560,22 +560,22 @@ func (m *taskManager) runNextTasks(sortList *MapSorter, effective time.Time) { if duration := e.GetTimeout(ctx); duration != 0 { go func(e Tasker) { m.wait.Add(1) + defer m.wait.Done() ctx, cancelFunc := context.WithTimeout(ctx, duration) defer cancelFunc() err := e.Run(ctx) if err != nil { log.Printf("tasker.run err: %s\n", err.Error()) } - m.wait.Done() }(e) } else { go func(e Tasker) { m.wait.Add(1) + defer m.wait.Done() err := e.Run(ctx) if err != nil { log.Printf("tasker.run err: %s\n", err.Error()) } - m.wait.Done() }(e) } From 7419ad952db027e4fa4d1449560ec3270b57bab6 Mon Sep 17 00:00:00 2001 From: guoxingyong Date: Wed, 26 May 2021 10:37:16 +0800 Subject: [PATCH 27/29] task manager graceful shutdown support --- task/task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task/task.go b/task/task.go index fd3c6e28..8576566c 100644 --- a/task/task.go +++ b/task/task.go @@ -593,7 +593,7 @@ func (m *taskManager) StopTask() { // GracefulShutdown wait all task done func (m *taskManager) GracefulShutdown() <-chan struct{} { - done := make(chan struct{}, 0) + done := make(chan struct{}) go func() { m.stop <- true m.wait.Wait() From 003434cad6f2c47e50eb791ce1e62c8bdac29905 Mon Sep 17 00:00:00 2001 From: guoxingyong Date: Wed, 26 May 2021 13:37:09 +0800 Subject: [PATCH 28/29] fix DeepSource and CHANGELOG.md --- CHANGELOG.md | 1 + task/task.go | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fa7111..94a511c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Optimize AddAutoPrefix: only register one router in case-insensitive mode. [4582](https://github.com/beego/beego/pull/4582) - Init exceptMethod by using reflection. [4583](https://github.com/beego/beego/pull/4583) - Deprecated BeeMap and replace all usage with `sync.map` [4616](https://github.com/beego/beego/pull/4616) +- TaskManager support graceful shutdown [4635](https://github.com/beego/beego/pull/4635) ## Fix Sonar diff --git a/task/task.go b/task/task.go index 8576566c..2bec0cc7 100644 --- a/task/task.go +++ b/task/task.go @@ -557,9 +557,9 @@ func (m *taskManager) runNextTasks(sortList *MapSorter, effective time.Time) { // check if timeout is on, if yes passing the timeout context ctx := context.Background() + m.wait.Add(1) if duration := e.GetTimeout(ctx); duration != 0 { go func(e Tasker) { - m.wait.Add(1) defer m.wait.Done() ctx, cancelFunc := context.WithTimeout(ctx, duration) defer cancelFunc() @@ -570,7 +570,6 @@ func (m *taskManager) runNextTasks(sortList *MapSorter, effective time.Time) { }(e) } else { go func(e Tasker) { - m.wait.Add(1) defer m.wait.Done() err := e.Run(ctx) if err != nil { From de65d8270165e5c06e3702dd0bdc00c5211ffa90 Mon Sep 17 00:00:00 2001 From: t29kida Date: Sat, 29 May 2021 23:48:17 +0900 Subject: [PATCH 29/29] fix sonar problem * replace min limit value with const values * remove duplicated testcase and add testcase * put together switch case statement * fill empty block of code --- CHANGELOG.md | 1 + client/cache/calc_utils.go | 9 ++++-- client/orm/clauses/order_clause/order_test.go | 30 ++++++++----------- client/orm/orm_test.go | 8 ++--- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8750999c..9eed8490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,3 +58,4 @@ - [4473](https://github.com/beego/beego/pull/4473) - [4474](https://github.com/beego/beego/pull/4474) - [4479](https://github.com/beego/beego/pull/4479) +- [4639](https://github.com/beego/beego/pull/4639) diff --git a/client/cache/calc_utils.go b/client/cache/calc_utils.go index 417f8337..f8b7f24a 100644 --- a/client/cache/calc_utils.go +++ b/client/cache/calc_utils.go @@ -12,6 +12,11 @@ var ( ErrNotIntegerType = berror.Error(NotIntegerType, "item val is not (u)int (u)int32 (u)int64") ) +const ( + MinUint32 uint32 = 0 + MinUint64 uint64 = 0 +) + func incr(originVal interface{}) (interface{}, error) { switch val := originVal.(type) { case int: @@ -75,12 +80,12 @@ func decr(originVal interface{}) (interface{}, error) { } return val - 1, nil case uint32: - if val == 0 { + if val == MinUint32 { return nil, ErrDecrementOverflow } return val - 1, nil case uint64: - if val == 0 { + if val == MinUint64 { return nil, ErrDecrementOverflow } return val - 1, nil diff --git a/client/orm/clauses/order_clause/order_test.go b/client/orm/clauses/order_clause/order_test.go index 172e7492..7854948e 100644 --- a/client/orm/clauses/order_clause/order_test.go +++ b/client/orm/clauses/order_clause/order_test.go @@ -120,25 +120,21 @@ func TestOrder_GetColumn(t *testing.T) { } } -func TestOrder_GetSort(t *testing.T) { - o := Clause( - SortDescending(), - ) - if o.GetSort() != Descending { - t.Error() - } -} +func TestSortString(t *testing.T) { + template := "got: %s, want: %s" -func TestOrder_IsRaw(t *testing.T) { - o1 := Clause() - if o1.IsRaw() { - t.Error() + o1 := Clause(sort(Sort(1))) + if o1.SortString() != "ASC" { + t.Errorf(template, o1.SortString(), "ASC") } - o2 := Clause( - Raw(), - ) - if !o2.IsRaw() { - t.Error() + o2 := Clause(sort(Sort(2))) + if o2.SortString() != "DESC" { + t.Errorf(template, o2.SortString(), "DESC") + } + + o3 := Clause(sort(Sort(3))) + if o3.SortString() != `` { + t.Errorf(template, o3.SortString(), ``) } } diff --git a/client/orm/orm_test.go b/client/orm/orm_test.go index 58f2a597..5ded367a 100644 --- a/client/orm/orm_test.go +++ b/client/orm/orm_test.go @@ -1845,17 +1845,12 @@ func TestRawQueryRow(t *testing.T) { case "id": throwFail(t, AssertIs(id, 1)) break - case "time": + case "time", "datetime": v = v.(time.Time).In(DefaultTimeLoc) value := dataValues[col].(time.Time).In(DefaultTimeLoc) assert.True(t, v.(time.Time).Sub(value) <= time.Second) break case "date": - case "datetime": - v = v.(time.Time).In(DefaultTimeLoc) - value := dataValues[col].(time.Time).In(DefaultTimeLoc) - assert.True(t, v.(time.Time).Sub(value) <= time.Second) - break default: throwFail(t, AssertIs(v, dataValues[col])) } @@ -2769,6 +2764,7 @@ func TestStrPkInsert(t *testing.T) { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { } else if err == ErrLastInsertIdUnavailable { + return } else { throwFailNow(t, err) }