Merge pull request #4584 from holooooo/develop

add http client and option func
This commit is contained in:
Ming Deng 2021-05-25 16:34:05 +08:00 committed by GitHub
commit cc58dffaa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 883 additions and 16 deletions

View File

@ -1,5 +1,6 @@
# developing # 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) - 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) - 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) - Lint: use golangci-lint. [4619](https://github.com/beego/beego/pull/4619)

View File

@ -0,0 +1,155 @@
// 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)
BeegoHTTPRequestOption func(request *BeegoHTTPRequest)
)
// WithEnableCookie will enable cookie in all subsequent request
func WithEnableCookie(enable bool) ClientOption {
return func(client *Client) {
client.Setting.EnableCookie = enable
}
}
// WithEnableCookie will adds UA in all subsequent request
func WithUserAgent(userAgent string) ClientOption {
return func(client *Client) {
client.Setting.UserAgent = userAgent
}
}
// WithTLSClientConfig will adds tls config in all subsequent request
func WithTLSClientConfig(config *tls.Config) ClientOption {
return func(client *Client) {
client.Setting.TLSClientConfig = config
}
}
// WithTransport will set transport field in all subsequent request
func WithTransport(transport http.RoundTripper) ClientOption {
return func(client *Client) {
client.Setting.Transport = transport
}
}
// WithProxy will set http proxy field in all subsequent request
func WithProxy(proxy func(*http.Request) (*url.URL, error)) ClientOption {
return func(client *Client) {
client.Setting.Proxy = proxy
}
}
// 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) {
client.Setting.CheckRedirect = redirect
}
}
// WithHTTPSetting can replace beegoHTTPSeting
func WithHTTPSetting(setting BeegoHTTPSettings) ClientOption {
return func(client *Client) {
client.Setting = setting
}
}
// WithEnableGzip will enable gzip in all subsequent request
func WithEnableGzip(enable bool) ClientOption {
return func(client *Client) {
client.Setting.Gzip = enable
}
}
// BeegoHttpRequestOption
// WithTimeout sets connect time out and read-write time out for BeegoRequest.
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 {
return func(request *BeegoHTTPRequest) {
request.Header(key, value)
}
}
// WithCookie adds a cookie to the request.
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 {
return func(request *BeegoHTTPRequest) {
t := tokenFactory()
request.Header("Authorization", t)
}
}
// WithBasicAuth adds a custom function to set basic auth
func WithBasicAuth(basicAuth func() (string, string)) BeegoHTTPRequestOption {
return func(request *BeegoHTTPRequest) {
username, password := basicAuth()
request.SetBasicAuth(username, password)
}
}
// WithFilters will use the filter as the invocation filters
func WithFilters(fcs ...FilterChain) BeegoHTTPRequestOption {
return func(request *BeegoHTTPRequest) {
request.SetFilters(fcs...)
}
}
// WithContentType adds ContentType in header
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 {
return func(request *BeegoHTTPRequest) {
request.Param(key, value)
}
}
// 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) {
request.Retries(times)
request.RetryDelay(delay)
}
}

View File

@ -0,0 +1,261 @@
// 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"
)
type respCarrier struct {
bytes []byte
}
func (r *respCarrier) SetBytes(bytes []byte) {
r.bytes = 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))
if err != nil {
t.Fatal(err)
}
v := "smallfish"
resp := &respCarrier{}
err = client.Get(resp, "/cookies/set?k1="+v)
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
err = client.Get(resp, "/cookies")
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), 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)
}
resp := &respCarrier{}
err = client.Get(resp, "/headers")
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), 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)
}
resp := &respCarrier{}
err = client.Get(resp, "/get")
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), 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"))
resp := &respCarrier{}
err = client.Get(resp, "/headers")
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), "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 {
return "testauth"
}))
resp := &respCarrier{}
err = client.Get(resp, "/headers")
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), "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)
}
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(resp.String())
n := strings.Index(resp.String(), "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"
resp := &respCarrier{}
err = client.Get(resp, "/headers", WithContentType(v))
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), 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"
resp := &respCarrier{}
err = client.Get(resp, "/get", WithParam("username", v))
if err != nil {
t.Fatal(err)
}
t.Log(resp.String())
n := strings.Index(resp.String(), 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 := 800 * time.Millisecond
startTime := time.Now().UnixNano() / int64(time.Millisecond)
_ = client.Get(nil, "", WithRetry(retryAmount, retryDelay))
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)
}
}

View File

@ -124,3 +124,11 @@ Make sure that:
1. You pass valid structure pointer to the function; 1. You pass valid structure pointer to the function;
2. The body is valid YAML document 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
`)

View File

@ -0,0 +1,174 @@
// 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 (
"bytes"
"io"
"io/ioutil"
"net/http"
)
// Client provides an HTTP client supporting chain call
type Client struct {
Name string
Endpoint string
CommonOpts []BeegoHTTPRequestOption
Setting BeegoHTTPSettings
}
// 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 {
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{
Name: name,
Endpoint: endpoint,
}
setting := GetDefaultSetting()
res.Setting = setting
for _, o := range opts {
o(res)
}
return res, nil
}
func (c *Client) customReq(req *BeegoHTTPRequest, opts []BeegoHTTPRequestOption) {
req.Setting(c.Setting)
opts = append(c.CommonOpts, opts...)
for _, o := range opts {
o(req)
}
}
// handleResponse try to parse body to meaningful value
func (c *Client) handleResponse(value interface{}, req *BeegoHTTPRequest) error {
// 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
}
return req.ToValue(value)
}
// handleCarrier set http data to value
func (c *Client) handleCarrier(value interface{}, req *BeegoHTTPRequest) error {
if value == nil {
return nil
}
if carrier, ok := value.(HTTPResponseCarrier); ok {
b, err := req.Bytes()
if err != nil {
return err
}
req.resp.Body = ioutil.NopCloser(bytes.NewReader(b))
carrier.SetHTTPResponse(req.resp)
}
if carrier, ok := value.(HTTPBodyCarrier); ok {
b, err := req.Bytes()
if err != nil {
return err
}
reader := ioutil.NopCloser(bytes.NewReader(b))
carrier.SetReader(reader)
}
if carrier, ok := value.(HTTPBytesCarrier); ok {
b, err := req.Bytes()
if err != nil {
return err
}
carrier.SetBytes(b)
}
if carrier, ok := value.(HTTPStatusCarrier); ok {
carrier.SetStatusCode(req.resp.StatusCode)
}
if carrier, ok := value.(HTTPHeadersCarrier); ok {
carrier.SetHeader(req.resp.Header)
}
return nil
}
// Get Send a GET request and try to give its result value
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 {
req := Post(c.Endpoint + path)
c.customReq(req, opts)
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 := Put(c.Endpoint + path)
c.customReq(req, opts)
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 := 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 := Head(c.Endpoint + path)
c.customReq(req, opts)
return c.handleResponse(value, req)
}

View File

@ -0,0 +1,220 @@
// 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"
"io"
"io/ioutil"
"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)
}
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"`
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_handleCarrier(t *testing.T) {
v := "beego"
client, err := NewClient("test", "http://httpbin.org/",
WithUserAgent(v))
if err != nil {
t.Fatal(err)
}
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) {
client, err := NewClient("test", "http://httpbin.org/")
if err != nil {
t.Fatal(err)
}
// json
var s *slideShowResponse
err = client.Get(&s, "/json")
if err != nil {
t.Fatal(err)
}
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
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
s = nil
err = client.Get(&s, "/base64/c2xpZGVzaG93OgogIGF1dGhvcjogWW91cnMgVHJ1bHkKICBkYXRlOiBkYXRlIG9mIHB1YmxpY2F0aW9uCiAgc2xpZGVzOgogIC0gdGl0bGU6IFdha2UgdXAgdG8gV29uZGVyV2lkZ2V0cyEKICAgIHR5cGU6IGFsbAogIC0gaXRlbXM6CiAgICAtIFdoeSA8ZW0+V29uZGVyV2lkZ2V0czwvZW0+IGFyZSBncmVhdAogICAgLSBXaG8gPGVtPmJ1eXM8L2VtPiBXb25kZXJXaWRnZXRzCiAgICB0aXRsZTogT3ZlcnZpZXcKICAgIHR5cGU6IGFsbAogIHRpdGxlOiBTYW1wbGUgU2xpZGUgU2hvdw==")
if err != nil {
t.Fatal(err)
}
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) {
client, err := NewClient("test", "http://httpbin.org")
if err != nil {
t.Fatal(err)
}
resp := &slideShowResponse{}
err = client.Get(resp, "/json")
if err != nil {
t.Fatal(err)
}
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.Resp.Request.Method)
}
func TestClient_Put(t *testing.T) {
client, err := NewClient("test", "http://httpbin.org")
if err != nil {
t.Fatal(err)
}
resp := &slideShowResponse{}
err = client.Get(resp, "/json")
if err != nil {
t.Fatal(err)
}
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.Resp.Request.Method)
}
func TestClient_Delete(t *testing.T) {
client, err := NewClient("test", "http://httpbin.org")
if err != nil {
t.Fatal(err)
}
resp := &slideShowResponse{}
err = client.Delete(resp, "/delete")
if err != nil {
t.Fatal(err)
}
defer resp.Resp.Body.Close()
assert.NotNil(t, resp)
assert.Equal(t, http.MethodDelete, resp.Resp.Request.Method)
}
func TestClient_Head(t *testing.T) {
client, err := NewClient("test", "http://beego.me")
if err != nil {
t.Fatal(err)
}
resp := &slideShowResponse{}
err = client.Head(resp, "")
if err != nil {
t.Fatal(err)
}
defer resp.Resp.Body.Close()
assert.NotNil(t, resp)
assert.Equal(t, http.MethodHead, resp.Resp.Request.Method)
}

View File

@ -124,7 +124,6 @@ type BeegoHTTPRequest struct {
setting BeegoHTTPSettings setting BeegoHTTPSettings
resp *http.Response resp *http.Response
body []byte body []byte
dump []byte
} }
// GetRequest returns the request object // GetRequest returns the request object
@ -199,7 +198,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
// SetProtocolVersion sets the protocol version for incoming requests. // SetProtocolVersion sets the protocol version for incoming requests.
// Client requests always use HTTP/1.1 // Client requests always use HTTP/1.1
func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
if len(vers) == 0 { if vers == "" {
vers = "HTTP/1.1" vers = "HTTP/1.1"
} }
@ -511,9 +510,8 @@ func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper {
DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: 100, MaxIdleConnsPerHost: 100,
} }
} else { } else if t, ok := trans.(*http.Transport); ok {
// if b.transport is *http.Transport then set the settings. // if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil { if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TLSClientConfig t.TLSClientConfig = b.setting.TLSClientConfig
} }
@ -524,7 +522,6 @@ func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper {
t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
} }
} }
}
return trans return trans
} }
@ -656,6 +653,40 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.") UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.")
} }
// 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) ToValue(value interface{}) error {
if value == nil {
return nil
}
contentType := strings.Split(b.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
}
return berror.Error(UnmarshalResponseToObjectFailed, "unmarshal body to object failed.")
}
// Response executes request client gets response manually. // Response executes request client gets response manually.
func (b *BeegoHTTPRequest) Response() (*http.Response, error) { func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
return b.getResponse() return b.getResponse()

View File

@ -433,3 +433,7 @@ func TestBeegoHTTPRequestXMLBody(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, req.req.GetBody) assert.NotNil(t, req.req.GetBody)
} }
// TODO
func TestBeegoHTTPRequest_ResponseForValue(t *testing.T) {
}

View File

@ -57,7 +57,7 @@ func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondi
} }
func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
res := true var res bool
if len(sc.path) > 0 { if len(sc.path) > 0 {
res = sc.matchPath(ctx, req) res = sc.matchPath(ctx, req)
} else if len(sc.pathReg) > 0 { } else if len(sc.pathReg) > 0 {

View File

@ -55,6 +55,11 @@ func SetDefaultSetting(setting BeegoHTTPSettings) {
defaultSetting = setting defaultSetting = setting
} }
// GetDefaultSetting return current default setting
func GetDefaultSetting() BeegoHTTPSettings {
return defaultSetting
}
var defaultSetting = BeegoHTTPSettings{ var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer", UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second, ConnectTimeout: 60 * time.Second,

1
go.mod
View File

@ -37,4 +37,5 @@ require (
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
google.golang.org/grpc v1.37.1 google.golang.org/grpc v1.37.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
mvdan.cc/gofumpt v0.1.1 // indirect
) )

7
go.sum
View File

@ -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 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/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.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/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/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= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
@ -366,6 +367,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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112 h1:NBrpnvz0pDPf3+HXZ1C9GcJd1DTpWDLcLWZhNq6uP7o=
@ -427,6 +429,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.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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -512,6 +516,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-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-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-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 h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -589,6 +594,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.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 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 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.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 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= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=