diff --git a/httplib/httplib.go b/httplib/httplib.go index 60aa4e8b..8ae95641 100644 --- a/httplib/httplib.go +++ b/httplib/httplib.go @@ -74,6 +74,7 @@ func createDefaultCookie() { } // SetDefaultSetting Overwrite default settings +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func SetDefaultSetting(setting BeegoHTTPSettings) { settingMutex.Lock() defer settingMutex.Unlock() @@ -81,6 +82,7 @@ func SetDefaultSetting(setting BeegoHTTPSettings) { } // NewBeegoRequest return *BeegoHttpRequest with specific method +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { var resp http.Response u, err := url.Parse(rawurl) @@ -106,31 +108,37 @@ func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { } // Get returns *BeegoHttpRequest with GET method. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func Get(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "GET") } // Post returns *BeegoHttpRequest with POST method. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func Post(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "POST") } // Put returns *BeegoHttpRequest with PUT method. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func Put(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "PUT") } // Delete returns *BeegoHttpRequest DELETE method. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func Delete(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "DELETE") } // Head returns *BeegoHttpRequest with HEAD method. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func Head(url string) *BeegoHTTPRequest { return NewBeegoRequest(url, "HEAD") } // BeegoHTTPSettings is the http.Client setting +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 type BeegoHTTPSettings struct { ShowDebug bool UserAgent string @@ -148,6 +156,7 @@ type BeegoHTTPSettings struct { } // BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 type BeegoHTTPRequest struct { url string req *http.Request @@ -160,35 +169,41 @@ type BeegoHTTPRequest struct { } // GetRequest return the request object +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) GetRequest() *http.Request { return b.req } // Setting Change request settings +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { b.setting = setting return b } // SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { b.req.SetBasicAuth(username, password) return b } // SetEnableCookie sets enable/disable cookiejar +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { b.setting.EnableCookie = enable return b } // SetUserAgent sets User-Agent header field +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { b.setting.UserAgent = useragent return b } // Debug sets show debug or not when executing request. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { b.setting.ShowDebug = isdebug return b @@ -198,28 +213,33 @@ func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { // default is 0 means no retried. // -1 means retried forever. // others means retried times. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest { b.setting.Retries = times return b } +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest { b.setting.RetryDelay = delay return b } // DumpBody setting whether need to Dump the Body. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { b.setting.DumpBody = isdump return b } // DumpRequest return the DumpRequest +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) DumpRequest() []byte { return b.dump } // SetTimeout sets connect time out and read-write time out for BeegoRequest. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { b.setting.ConnectTimeout = connectTimeout b.setting.ReadWriteTimeout = readWriteTimeout @@ -227,18 +247,21 @@ func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Dura } // SetTLSClientConfig sets tls connection configurations if visiting https url. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { b.setting.TLSClientConfig = config return b } // Header add header item string in request. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { b.req.Header.Set(key, value) return b } // SetHost set the request host +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { b.req.Host = host return b @@ -246,6 +269,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { // SetProtocolVersion Set the protocol version for incoming requests. // Client requests always use HTTP/1.1. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { if len(vers) == 0 { vers = "HTTP/1.1" @@ -262,12 +286,14 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { } // SetCookie add cookie into request. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { b.req.Header.Add("Cookie", cookie.String()) return b } // SetTransport set the setting transport +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { b.setting.Transport = transport return b @@ -280,6 +306,7 @@ func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPR // u, _ := url.ParseRequestURI("http://127.0.0.1:8118") // return u, nil // } +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { b.setting.Proxy = proxy return b @@ -289,6 +316,7 @@ func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) // // If CheckRedirect is nil, the Client uses its default policy, // which is to stop after 10 consecutive requests. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest { b.setting.CheckRedirect = redirect return b @@ -296,6 +324,7 @@ func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via // Param adds query param in to request. // params build query string as ?key1=value1&key2=value2... +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { if param, ok := b.params[key]; ok { b.params[key] = append(param, value) @@ -306,6 +335,7 @@ func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { } // PostFile add a post file to the request +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { b.files[formname] = filename return b @@ -313,6 +343,7 @@ func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest // Body adds request raw body. // it supports string and []byte. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { switch t := data.(type) { case string: @@ -328,6 +359,7 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { } // XMLBody adds request raw body encoding by XML. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := xml.Marshal(obj) @@ -342,6 +374,7 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { } // YAMLBody adds request raw body encoding by YAML. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := yaml.Marshal(obj) @@ -356,6 +389,7 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) } // JSONBody adds request raw body encoding by JSON. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { if b.req.Body == nil && obj != nil { byts, err := json.Marshal(obj) @@ -438,6 +472,7 @@ func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { } // DoRequest will do the client.Do +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { var paramBody string if len(b.params) > 0 { @@ -531,6 +566,7 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { // String returns the body string in response. // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) String() (string, error) { data, err := b.Bytes() if err != nil { @@ -542,6 +578,7 @@ func (b *BeegoHTTPRequest) String() (string, error) { // Bytes returns the body []byte in response. // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { if b.body != nil { return b.body, nil @@ -568,6 +605,7 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { // ToFile saves the body data in response to one file. // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) ToFile(filename string) error { resp, err := b.getResponse() if err != nil { @@ -608,6 +646,7 @@ func pathExistAndMkdir(filename string) (err error) { // ToJSON returns the map that marshals from the body bytes as json in response . // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -618,6 +657,7 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { // ToXML returns the map that marshals from the body bytes as xml in response . // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) ToXML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -628,6 +668,7 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error { // ToYAML returns the map that marshals from the body bytes as yaml in response . // it calls Response inner. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { data, err := b.Bytes() if err != nil { @@ -637,11 +678,13 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error { } // Response executes request client gets response mannually. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func (b *BeegoHTTPRequest) Response() (*http.Response, error) { return b.getResponse() } // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. +// Deprecated: using pkg/httplib, we will delete this in v2.1.0 func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { return func(netw, addr string) (net.Conn, error) { conn, err := net.DialTimeout(netw, addr, cTimeout) diff --git a/pkg/httplib/filter.go b/pkg/httplib/filter.go new file mode 100644 index 00000000..72a497d0 --- /dev/null +++ b/pkg/httplib/filter.go @@ -0,0 +1,24 @@ +// 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 ( + "context" + "net/http" +) + +type FilterChain func(next Filter) Filter + +type Filter func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) diff --git a/pkg/httplib/filter/opentracing/filter.go b/pkg/httplib/filter/opentracing/filter.go new file mode 100644 index 00000000..5f409c63 --- /dev/null +++ b/pkg/httplib/filter/opentracing/filter.go @@ -0,0 +1,77 @@ +// 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 opentracing + +import ( + "context" + "net/http" + "strconv" + + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" + + "github.com/astaxie/beego/pkg/httplib" +) + +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, + req *httplib.BeegoHTTPRequest, resp *http.Response, err error) +} + +func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { + + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + + method := req.GetRequest().Method + host := req.GetRequest().URL.Host + path := req.GetRequest().URL.Path + + proto := req.GetRequest().Proto + + scheme := req.GetRequest().URL.Scheme + + operationName := host + path + "#" + method + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + + inject := opentracingKit.ContextToHTTP(opentracing.GlobalTracer(), logKit.NewNopLogger()) + inject(spanCtx, req.GetRequest()) + resp, err := next(spanCtx, req) + + if resp != nil { + span.SetTag("status", strconv.Itoa(resp.StatusCode)) + } + + span.SetTag("method", method) + span.SetTag("host", host) + span.SetTag("path", path) + span.SetTag("proto", proto) + span.SetTag("scheme", scheme) + + span.LogFields(log.String("url", req.GetRequest().URL.String())) + + if err != nil { + span.LogFields(log.String("error", err.Error())) + } + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, ctx, req, resp, err) + } + return resp, err + } +} diff --git a/pkg/httplib/filter/opentracing/filter_test.go b/pkg/httplib/filter/opentracing/filter_test.go new file mode 100644 index 00000000..aa687541 --- /dev/null +++ b/pkg/httplib/filter/opentracing/filter_test.go @@ -0,0 +1,42 @@ +// 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 opentracing + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/pkg/httplib" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + time.Sleep(100 * time.Millisecond) + return &http.Response{ + StatusCode: 404, + }, errors.New("hello") + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") + resp, err := filter(context.Background(), req) + assert.NotNil(t, resp) + assert.NotNil(t, err) +} diff --git a/pkg/httplib/filter/prometheus/filter.go b/pkg/httplib/filter/prometheus/filter.go new file mode 100644 index 00000000..a0b24d67 --- /dev/null +++ b/pkg/httplib/filter/prometheus/filter.go @@ -0,0 +1,73 @@ +// 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 prometheus + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + + beego "github.com/astaxie/beego/pkg" + "github.com/astaxie/beego/pkg/httplib" +) + +type FilterChainBuilder struct { + summaryVec prometheus.ObserverVec +} + +func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { + + builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "remote_http_request", + ConstLabels: map[string]string{ + "server": beego.BConfig.ServerName, + "env": beego.BConfig.RunMode, + "appname": beego.BConfig.AppName, + }, + Help: "The statics info for remote http requests", + }, []string{"proto", "scheme", "method", "host", "path", "status", "duration", "isError"}) + + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + startTime := time.Now() + resp, err := next(ctx, req) + endTime := time.Now() + go builder.report(startTime, endTime, ctx, req, resp, err) + return resp, err + } +} + +func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time, + ctx context.Context, req *httplib.BeegoHTTPRequest, resp *http.Response, err error) { + + proto := req.GetRequest().Proto + + scheme := req.GetRequest().URL.Scheme + method := req.GetRequest().Method + + host := req.GetRequest().URL.Host + path := req.GetRequest().URL.Path + + status := resp.StatusCode + + dur := int(endTime.Sub(startTime) / time.Millisecond) + + + builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, + strconv.Itoa(status), strconv.Itoa(dur), strconv.FormatBool(err == nil)) +} diff --git a/pkg/httplib/filter/prometheus/filter_test.go b/pkg/httplib/filter/prometheus/filter_test.go new file mode 100644 index 00000000..e15d82e5 --- /dev/null +++ b/pkg/httplib/filter/prometheus/filter_test.go @@ -0,0 +1,41 @@ +// 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 prometheus + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/pkg/httplib" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + time.Sleep(100 * time.Millisecond) + return &http.Response{ + StatusCode: 404, + }, nil + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") + resp, err := filter(context.Background(), req) + assert.NotNil(t, resp) + assert.Nil(t, err) +} diff --git a/pkg/httplib/httplib.go b/pkg/httplib/httplib.go index 7255b2ca..f8ab80a1 100644 --- a/pkg/httplib/httplib.go +++ b/pkg/httplib/httplib.go @@ -34,6 +34,7 @@ package httplib import ( "bytes" "compress/gzip" + "context" "crypto/tls" "encoding/json" "encoding/xml" @@ -66,6 +67,11 @@ var defaultSetting = BeegoHTTPSettings{ 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() @@ -145,6 +151,7 @@ type BeegoHTTPSettings struct { 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. @@ -295,6 +302,18 @@ func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via return b } +// SetFilters will use the filter as the invocation filters +func (b *BeegoHTTPRequest) SetFilters(fcs ...FilterChain) *BeegoHTTPRequest { + b.setting.FilterChains = fcs + return b +} + +// AddFilters adds filter +func (b *BeegoHTTPRequest) AddFilters(fcs ...FilterChain) *BeegoHTTPRequest { + b.setting.FilterChains = append(b.setting.FilterChains, fcs...) + return b +} + // Param adds query param in to request. // params build query string as ?key1=value1&key2=value2... func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { @@ -397,7 +416,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) { if err != nil { log.Println("Httplib:", err) } - //iocopy + // iocopy _, err = io.Copy(fileWriter, fh) fh.Close() if err != nil { @@ -440,6 +459,21 @@ func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { // DoRequest executes client.Do func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) { + return b.DoRequestWithCtx(context.Background()) +} + +func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) { + + root := doRequestFilter + if len(b.setting.FilterChains) > 0 { + for i := len(b.setting.FilterChains) - 1; i >= 0; i-- { + root = b.setting.FilterChains[i](root) + } + } + return root(ctx, b) +} + +func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) { var paramBody string if len(b.params) > 0 { var buf bytes.Buffer diff --git a/pkg/orm/filter.go b/pkg/orm/filter.go index 9676e4af..d04b8c42 100644 --- a/pkg/orm/filter.go +++ b/pkg/orm/filter.go @@ -18,8 +18,12 @@ import ( "context" ) +// FilterChain is used to build a Filter +// don't forget to call next(...) inside your Filter type FilterChain func(next Filter) Filter +// Filter's behavior is a little big strang. +// it's only be called when users call methods of Ormer type Filter func(ctx context.Context, inv *Invocation) var globalFilterChains = make([]FilterChain, 0, 4) diff --git a/pkg/orm/filter/opentracing/filter.go b/pkg/orm/filter/opentracing/filter.go new file mode 100644 index 00000000..a55ae6d2 --- /dev/null +++ b/pkg/orm/filter/opentracing/filter.go @@ -0,0 +1,59 @@ +// 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 opentracing + +import ( + "context" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/pkg/orm" +) + +// FilterChainBuilder provides an extension point +// this Filter's behavior looks a little bit strange +// for example: +// if we want to trace QuerySetter +// actually we trace invoking "QueryTable" and "QueryTableWithCtx" +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) +} + +func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { + return func(ctx context.Context, inv *orm.Invocation) { + operationName := builder.operationName(ctx, inv) + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + + next(spanCtx, inv) + span.SetTag("Method", inv.Method) + span.SetTag("Table", inv.GetTableName()) + span.SetTag("InsideTx", inv.InsideTx) + span.SetTag("TxName", spanCtx.Value(orm.TxNameKey)) + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, spanCtx, inv) + } + + } +} + +func (builder *FilterChainBuilder) operationName(ctx context.Context, inv *orm.Invocation) string { + if n, ok := ctx.Value(orm.TxNameKey).(string); ok { + return inv.Method + "#" + n + } + return inv.Method + "#" + inv.GetTableName() +} diff --git a/pkg/orm/filter/opentracing/filter_test.go b/pkg/orm/filter/opentracing/filter_test.go new file mode 100644 index 00000000..1428df8a --- /dev/null +++ b/pkg/orm/filter/opentracing/filter_test.go @@ -0,0 +1,43 @@ +// 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 opentracing + +import ( + "context" + "testing" + "time" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/pkg/orm" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, inv *orm.Invocation) { + inv.TxName = "Hello" + } + + builder := &FilterChainBuilder{ + CustomSpanFunc: func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { + span.SetTag("hello", "hell") + }, + } + + inv := &orm.Invocation{ + Method: "Hello", + TxStartTime: time.Now(), + } + builder.FilterChain(next)(context.Background(), inv) +} \ No newline at end of file diff --git a/pkg/orm/filter/prometheus/filter.go b/pkg/orm/filter/prometheus/filter.go index 9f177deb..33fdf78f 100644 --- a/pkg/orm/filter/prometheus/filter.go +++ b/pkg/orm/filter/prometheus/filter.go @@ -29,6 +29,10 @@ import ( // FilterChainBuilder is an extension point, // when we want to support some configuration, // please use this structure +// this Filter's behavior looks a little bit strange +// for example: +// if we want to records the metrics of QuerySetter +// actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx" type FilterChainBuilder struct { summaryVec prometheus.ObserverVec } diff --git a/pkg/web/filter/opentracing/filter.go b/pkg/web/filter/opentracing/filter.go index 8e332c7d..822d5e4d 100644 --- a/pkg/web/filter/opentracing/filter.go +++ b/pkg/web/filter/opentracing/filter.go @@ -15,37 +15,44 @@ package opentracing import ( + "context" + + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" "github.com/opentracing/opentracing-go" beego "github.com/astaxie/beego/pkg" - "github.com/astaxie/beego/pkg/context" + beegoCtx "github.com/astaxie/beego/pkg/context" ) // FilterChainBuilder provides an extension point that we can support more configurations if necessary type FilterChainBuilder struct { // CustomSpanFunc makes users to custom the span. - CustomSpanFunc func(span opentracing.Span, ctx *context.Context) + CustomSpanFunc func(span opentracing.Span, ctx *beegoCtx.Context) } func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.FilterFunc { - return func(ctx *context.Context) { - span := opentracing.SpanFromContext(ctx.Request.Context()) - spanCtx := ctx.Request.Context() - if span == nil { - operationName := ctx.Input.URL() - // it means that there is not any span, so we create a span as the root span. - // TODO, if we support multiple servers, this need to be changed - route, found := beego.BeeApp.Handlers.FindRouter(ctx) - if found { - operationName = route.GetPattern() - } - span, spanCtx = opentracing.StartSpanFromContext(spanCtx, operationName) - newReq := ctx.Request.Clone(spanCtx) - ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) + return func(ctx *beegoCtx.Context) { + var ( + spanCtx context.Context + span opentracing.Span + ) + operationName := builder.operationName(ctx) + + if preSpan := opentracing.SpanFromContext(ctx.Request.Context()); preSpan == nil { + inject := opentracingKit.HTTPToContext(opentracing.GlobalTracer(), operationName, logKit.NewNopLogger()) + spanCtx = inject(ctx.Request.Context(), ctx.Request) + span = opentracing.SpanFromContext(spanCtx) + } else { + span, spanCtx = opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) } defer span.Finish() + + newReq := ctx.Request.Clone(spanCtx) + ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) + next(ctx) // if you think we need to do more things, feel free to create an issue to tell us span.SetTag("status", ctx.Output.Status) @@ -56,3 +63,14 @@ func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.Filt } } } + +func (builder *FilterChainBuilder) operationName(ctx *beegoCtx.Context) string { + operationName := ctx.Input.URL() + // it means that there is not any span, so we create a span as the root span. + // TODO, if we support multiple servers, this need to be changed + route, found := beego.BeeApp.Handlers.FindRouter(ctx) + if found { + operationName = route.GetPattern() + } + return operationName +}