diff --git a/client/httplib/httplib.go b/client/httplib/httplib.go index 9402eca6..c9aadfd5 100644 --- a/client/httplib/httplib.go +++ b/client/httplib/httplib.go @@ -44,49 +44,22 @@ import ( "mime/multipart" "net" "net/http" - "net/http/cookiejar" "net/http/httputil" "net/url" "os" "path" "strings" - "sync" "time" "gopkg.in/yaml.v2" ) -var defaultSetting = BeegoHTTPSettings{ - UserAgent: "beegoServer", - ConnectTimeout: 60 * time.Second, - ReadWriteTimeout: 60 * time.Second, - Gzip: true, - DumpBody: true, - FilterChains: []FilterChain{mockFilter.FilterChain}, -} - -var defaultCookieJar http.CookieJar -var settingMutex sync.Mutex // 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) } -// createDefaultCookie creates a global cookiejar to store cookies. -func createDefaultCookie() { - settingMutex.Lock() - defer settingMutex.Unlock() - defaultCookieJar, _ = cookiejar.New(nil) -} - -// SetDefaultSetting overwrites default settings -func SetDefaultSetting(setting BeegoHTTPSettings) { - settingMutex.Lock() - defer settingMutex.Unlock() - defaultSetting = setting -} - // NewBeegoRequest returns *BeegoHttpRequest with specific method func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { var resp http.Response @@ -137,23 +110,7 @@ func Head(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "HEAD") } -// BeegoHTTPSettings is the http.Client setting -type BeegoHTTPSettings struct { - ShowDebug bool - UserAgent string - ConnectTimeout time.Duration - ReadWriteTimeout time.Duration - TLSClientConfig *tls.Config - Proxy func(*http.Request) (*url.URL, error) - Transport http.RoundTripper - CheckRedirect func(req *http.Request, via []*http.Request) error - EnableCookie bool - Gzip bool - DumpBody bool - Retries int // if set to -1 means will retry forever - RetryDelay time.Duration - FilterChains []FilterChain -} + // BeegoHTTPRequest provides more useful methods than http.Request for requesting a url. type BeegoHTTPRequest struct { diff --git a/client/httplib/mock.go b/client/httplib/mock/mock.go similarity index 90% rename from client/httplib/mock.go rename to client/httplib/mock/mock.go index 691f03d2..7640e454 100644 --- a/client/httplib/mock.go +++ b/client/httplib/mock/mock.go @@ -12,17 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "context" "net/http" + "github.com/beego/beego/v2/client/httplib" "github.com/beego/beego/v2/core/logs" ) const mockCtxKey = "beego-httplib-mock" +func init() { + InitMockSetting() +} + type Stub interface { Mock(cond RequestCondition, resp *http.Response, err error) Clear() @@ -31,6 +36,10 @@ type Stub interface { var mockFilter = &MockResponseFilter{} +func InitMockSetting() { + httplib.AddDefaultFilter(mockFilter.FilterChain) +} + func StartMock() Stub { return mockFilter } diff --git a/client/httplib/mock_condition.go b/client/httplib/mock/mock_condition.go similarity index 88% rename from client/httplib/mock_condition.go rename to client/httplib/mock/mock_condition.go index 5e6ff455..639b45a3 100644 --- a/client/httplib/mock_condition.go +++ b/client/httplib/mock/mock_condition.go @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "context" "encoding/json" "net/textproto" "regexp" + + "github.com/beego/beego/v2/client/httplib" ) type RequestCondition interface { - Match(ctx context.Context, req *BeegoHTTPRequest) bool + Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool } // reqCondition create condition @@ -54,7 +56,7 @@ func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondi return sc } -func (sc *SimpleCondition) Match(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { res := true if len(sc.path) > 0 { res = sc.matchPath(ctx, req) @@ -70,12 +72,12 @@ func (sc *SimpleCondition) Match(ctx context.Context, req *BeegoHTTPRequest) boo sc.matchBodyFields(ctx, req) } -func (sc *SimpleCondition) matchPath(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchPath(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { path := req.GetRequest().URL.Path return path == sc.path } -func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { path := req.GetRequest().URL.Path if b, err := regexp.Match(sc.pathReg, []byte(path)); err == nil { return b @@ -83,7 +85,7 @@ func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *BeegoHTTPReque return false } -func (sc *SimpleCondition) matchQuery(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchQuery(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { qs := req.GetRequest().URL.Query() for k, v := range sc.query { if uv, ok := qs[k]; !ok || uv[0] != v { @@ -93,7 +95,7 @@ func (sc *SimpleCondition) matchQuery(ctx context.Context, req *BeegoHTTPRequest return true } -func (sc *SimpleCondition) matchHeader(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchHeader(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { headers := req.GetRequest().Header for k, v := range sc.header { if uv, ok := headers[k]; !ok || uv[0] != v { @@ -103,7 +105,7 @@ func (sc *SimpleCondition) matchHeader(ctx context.Context, req *BeegoHTTPReques return true } -func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { if len(sc.body) == 0 { return true } @@ -135,7 +137,7 @@ func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *BeegoHTTPRe return true } -func (sc *SimpleCondition) matchMethod(ctx context.Context, req *BeegoHTTPRequest) bool { +func (sc *SimpleCondition) matchMethod(ctx context.Context, req *httplib.BeegoHTTPRequest) bool { if len(sc.method) > 0 { return sc.method == req.GetRequest().Method } diff --git a/client/httplib/mock_condition_test.go b/client/httplib/mock/mock_condition_test.go similarity index 74% rename from client/httplib/mock_condition_test.go rename to client/httplib/mock/mock_condition_test.go index 643dc353..4fc6d377 100644 --- a/client/httplib/mock_condition_test.go +++ b/client/httplib/mock/mock_condition_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "context" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/beego/beego/v2/client/httplib" ) func init() { @@ -28,37 +29,37 @@ func init() { func TestSimpleCondition_MatchPath(t *testing.T) { sc := NewSimpleCondition("/abc/s") - res := sc.Match(context.Background(), Get("http://localhost:8080/abc/s")) + res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s")) assert.True(t, res) } func TestSimpleCondition_MatchQuery(t *testing.T) { k, v := "my-key", "my-value" sc := NewSimpleCondition("/abc/s") - res := sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value")) + res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value")) assert.True(t, res) sc = NewSimpleCondition("/abc/s", WithQuery(k, v)) - res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value")) + res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value")) assert.True(t, res) - res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-valuesss")) + res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-valuesss")) assert.False(t, res) - res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key-a=my-value")) + res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key-a=my-value")) assert.False(t, res) - res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello")) + res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello")) assert.True(t, res) } func TestSimpleCondition_MatchHeader(t *testing.T) { k, v := "my-header", "my-header-value" sc := NewSimpleCondition("/abc/s") - req := Get("http://localhost:8080/abc/s") + req := httplib.Get("http://localhost:8080/abc/s") assert.True(t, sc.Match(context.Background(), req)) - req = Get("http://localhost:8080/abc/s") + req = httplib.Get("http://localhost:8080/abc/s") req.Header(k, v) assert.True(t, sc.Match(context.Background(), req)) @@ -73,7 +74,7 @@ func TestSimpleCondition_MatchHeader(t *testing.T) { func TestSimpleCondition_MatchBodyField(t *testing.T) { sc := NewSimpleCondition("/abc/s") - req := Post("http://localhost:8080/abc/s") + req := httplib.Post("http://localhost:8080/abc/s") assert.True(t, sc.Match(context.Background(), req)) @@ -102,7 +103,7 @@ func TestSimpleCondition_MatchBodyField(t *testing.T) { func TestSimpleCondition_Match(t *testing.T) { sc := NewSimpleCondition("/abc/s") - req := Post("http://localhost:8080/abc/s") + req := httplib.Post("http://localhost:8080/abc/s") assert.True(t, sc.Match(context.Background(), req)) @@ -115,9 +116,9 @@ func TestSimpleCondition_Match(t *testing.T) { func TestSimpleCondition_MatchPathReg(t *testing.T) { sc := NewSimpleCondition("", WithPathReg(`\/abc\/.*`)) - req := Post("http://localhost:8080/abc/s") + req := httplib.Post("http://localhost:8080/abc/s") assert.True(t, sc.Match(context.Background(), req)) - req = Post("http://localhost:8080/abcd/s") + req = httplib.Post("http://localhost:8080/abcd/s") assert.False(t, sc.Match(context.Background(), req)) } diff --git a/client/httplib/mock_filter.go b/client/httplib/mock/mock_filter.go similarity index 87% rename from client/httplib/mock_filter.go rename to client/httplib/mock/mock_filter.go index 83a7b71b..225d65f3 100644 --- a/client/httplib/mock_filter.go +++ b/client/httplib/mock/mock_filter.go @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "context" - "fmt" "net/http" + + "github.com/beego/beego/v2/client/httplib" ) // MockResponse will return mock response if find any suitable mock data @@ -32,13 +33,10 @@ func NewMockResponseFilter() *MockResponseFilter { } } -func (m *MockResponseFilter) FilterChain(next Filter) Filter { - return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) { - +func (m *MockResponseFilter) FilterChain(next httplib.Filter) httplib.Filter { + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { ms := mockFromCtx(ctx) ms = append(ms, m.ms...) - - fmt.Printf("url: %s, mock: %d \n", req.url, len(ms)) for _, mock := range ms { if mock.cond.Match(ctx, req) { return mock.resp, mock.err diff --git a/client/httplib/mock_filter_test.go b/client/httplib/mock/mock_filter_test.go similarity index 79% rename from client/httplib/mock_filter_test.go rename to client/httplib/mock/mock_filter_test.go index 40a2185e..b27e772e 100644 --- a/client/httplib/mock_filter_test.go +++ b/client/httplib/mock/mock_filter_test.go @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "errors" "testing" "github.com/stretchr/testify/assert" + + "github.com/beego/beego/v2/client/httplib" ) func TestMockResponseFilter_FilterChain(t *testing.T) { - req := Get("http://localhost:8080/abc/s") + req := httplib.Get("http://localhost:8080/abc/s") ft := NewMockResponseFilter() - expectedResp := NewHttpResponseWithJsonBody(`{}`) + expectedResp := httplib.NewHttpResponseWithJsonBody(`{}`) expectedErr := errors.New("expected error") ft.Mock(NewSimpleCondition("/abc/s"), expectedResp, expectedErr) @@ -35,16 +37,16 @@ func TestMockResponseFilter_FilterChain(t *testing.T) { assert.Equal(t, expectedErr, err) assert.Equal(t, expectedResp, resp) - req = Get("http://localhost:8080/abcd/s") + req = httplib.Get("http://localhost:8080/abcd/s") req.AddFilters(ft.FilterChain) resp, err = req.DoRequest() assert.NotEqual(t, expectedErr, err) assert.NotEqual(t, expectedResp, resp) - req = Get("http://localhost:8080/abc/s") + req = httplib.Get("http://localhost:8080/abc/s") req.AddFilters(ft.FilterChain) - expectedResp1 := NewHttpResponseWithJsonBody(map[string]string{}) + expectedResp1 := httplib.NewHttpResponseWithJsonBody(map[string]string{}) expectedErr1 := errors.New("expected error") ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1) @@ -52,7 +54,7 @@ func TestMockResponseFilter_FilterChain(t *testing.T) { assert.Equal(t, expectedErr, err) assert.Equal(t, expectedResp, resp) - req = Get("http://localhost:8080/abc/abs/bbc") + req = httplib.Get("http://localhost:8080/abc/abs/bbc") req.AddFilters(ft.FilterChain) ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1) resp, err = req.DoRequest() diff --git a/client/httplib/mock_test.go b/client/httplib/mock/mock_test.go similarity index 75% rename from client/httplib/mock_test.go rename to client/httplib/mock/mock_test.go index 1d913b29..e73e8a6a 100644 --- a/client/httplib/mock_test.go +++ b/client/httplib/mock/mock_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package httplib +package mock import ( "context" @@ -21,16 +21,18 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/beego/beego/v2/client/httplib" ) func TestStartMock(t *testing.T) { - defaultSetting.FilterChains = []FilterChain{mockFilter.FilterChain} + // httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain} stub := StartMock() // defer stub.Clear() - expectedResp := NewHttpResponseWithJsonBody([]byte(`{}`)) + expectedResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`)) expectedErr := errors.New("expected err") stub.Mock(NewSimpleCondition("/abc"), expectedResp, expectedErr) @@ -45,14 +47,14 @@ func TestStartMock(t *testing.T) { // TestStartMock_Isolation Test StartMock that // mock only work for this request func TestStartMock_Isolation(t *testing.T) { - defaultSetting.FilterChains = []FilterChain{mockFilter.FilterChain} + // httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain} // setup global stub stub := StartMock() - globalMockResp := NewHttpResponseWithJsonBody([]byte(`{}`)) + globalMockResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`)) globalMockErr := errors.New("expected err") stub.Mock(NewSimpleCondition("/abc"), globalMockResp, globalMockErr) - expectedResp := NewHttpResponseWithJsonBody(struct { + expectedResp := httplib.NewHttpResponseWithJsonBody(struct { A string `json:"a"` }{ A: "aaa", @@ -67,9 +69,9 @@ func TestStartMock_Isolation(t *testing.T) { } func OriginnalCodeUsingHttplibPassCtx(ctx context.Context) (*http.Response, error) { - return Get("http://localhost:7777/abc").DoRequestWithCtx(ctx) + return httplib.Get("http://localhost:7777/abc").DoRequestWithCtx(ctx) } func OriginalCodeUsingHttplib() (*http.Response, error){ - return Get("http://localhost:7777/abc").DoRequest() + return httplib.Get("http://localhost:7777/abc").DoRequest() } diff --git a/client/httplib/setting.go b/client/httplib/setting.go new file mode 100644 index 00000000..c8d049e0 --- /dev/null +++ b/client/httplib/setting.go @@ -0,0 +1,81 @@ +// 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/http/cookiejar" + "net/url" + "sync" + "time" +) + +// BeegoHTTPSettings is the http.Client setting +type BeegoHTTPSettings struct { + ShowDebug bool + UserAgent string + ConnectTimeout time.Duration + ReadWriteTimeout time.Duration + TLSClientConfig *tls.Config + Proxy func(*http.Request) (*url.URL, error) + Transport http.RoundTripper + CheckRedirect func(req *http.Request, via []*http.Request) error + EnableCookie bool + Gzip bool + DumpBody bool + Retries int // if set to -1 means will retry forever + RetryDelay time.Duration + FilterChains []FilterChain +} + +// createDefaultCookie creates a global cookiejar to store cookies. +func createDefaultCookie() { + settingMutex.Lock() + defer settingMutex.Unlock() + defaultCookieJar, _ = cookiejar.New(nil) +} + +// SetDefaultSetting overwrites default settings +// Keep in mind that when you invoke the SetDefaultSetting +// some methods invoked before SetDefaultSetting +func SetDefaultSetting(setting BeegoHTTPSettings) { + settingMutex.Lock() + defer settingMutex.Unlock() + defaultSetting = setting +} + +var defaultSetting = BeegoHTTPSettings{ + UserAgent: "beegoServer", + ConnectTimeout: 60 * time.Second, + ReadWriteTimeout: 60 * time.Second, + Gzip: true, + DumpBody: true, + FilterChains: make([]FilterChain, 0, 4), +} + +var defaultCookieJar http.CookieJar +var settingMutex sync.Mutex + +// AddDefaultFilter add a new filter into defaultSetting +// Be careful about using this method if you invoke SetDefaultSetting somewhere +func AddDefaultFilter(fc FilterChain) { + settingMutex.Lock() + defer settingMutex.Unlock() + if defaultSetting.FilterChains == nil { + defaultSetting.FilterChains = make([]FilterChain, 0, 4) + } + defaultSetting.FilterChains = append(defaultSetting.FilterChains, fc) +} \ No newline at end of file