diff --git a/net/ghttp/ghttp_client_middleware.go b/net/ghttp/ghttp_client_middleware.go index c121ef66b..505d85bf5 100644 --- a/net/ghttp/ghttp_client_middleware.go +++ b/net/ghttp/ghttp_client_middleware.go @@ -4,52 +4,45 @@ import ( "net/http" ) -const gfHTTPClientMiddlewareKey = "__gfHttpClientMiddlewareKey" - -// Use Add middleware to client -func (c *Client) Use(handlers ...ClientHandlerFunc) *Client { - newClient := c - if c.parent == nil { - newClient = c.Clone() - } - - newClient.middlewareHandler = append(newClient.middlewareHandler, handlers...) - return newClient -} - -// MiddlewareNext call next middleware -// this is should only be call in ClientHandlerFunc -func (c *Client) MiddlewareNext(req *http.Request) (*ClientResponse, error) { - m, ok := req.Context().Value(gfHTTPClientMiddlewareKey).(*clientMiddleware) - if ok { - resp, err := m.Next(req) - return resp, err - } - return c.callRequest(req) -} - // ClientHandlerFunc middleware handler func type ClientHandlerFunc = func(c *Client, r *http.Request) (*ClientResponse, error) // clientMiddleware is the plugin for http client request workflow management. type clientMiddleware struct { - client *Client // http client - handlers []ClientHandlerFunc // mdl handlers - handlerIndex int // current handler index - resp *ClientResponse // save resp - err error // save err + client *Client // http client. + handlers []ClientHandlerFunc // mdl handlers. + handlerIndex int // current handler index. + resp *ClientResponse // save resp. + err error // save err. } -// Next call next middleware handler, if abort, +const clientMiddlewareKey = "__clientMiddlewareKey" + +// Use adds one or more middleware handlers to client. +func (c *Client) Use(handlers ...ClientHandlerFunc) *Client { + c.middlewareHandler = append(c.middlewareHandler, handlers...) + return c +} + +// MiddlewareNext calls next middleware. +// This is should only be call in ClientHandlerFunc. +func (c *Client) MiddlewareNext(req *http.Request) (*ClientResponse, error) { + if v := req.Context().Value(clientMiddlewareKey); v != nil { + if m, ok := v.(*clientMiddleware); ok { + return m.Next(req) + } + } + return c.callRequest(req) +} + +// Next calls next middleware handler. func (m *clientMiddleware) Next(req *http.Request) (resp *ClientResponse, err error) { if m.err != nil { return m.resp, m.err } if m.handlerIndex < len(m.handlers) { m.handlerIndex++ - resp, err = m.handlers[m.handlerIndex](m.client, req) - m.resp = resp - m.err = err + m.resp, m.err = m.handlers[m.handlerIndex](m.client, req) } - return + return m.resp, m.err } diff --git a/net/ghttp/ghttp_client_request.go b/net/ghttp/ghttp_client_request.go index 0962c9080..f407c59e6 100644 --- a/net/ghttp/ghttp_client_request.go +++ b/net/ghttp/ghttp_client_request.go @@ -11,6 +11,7 @@ import ( "context" "errors" "fmt" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" "github.com/gogf/gf/internal/utils" "io" @@ -83,7 +84,52 @@ func (c *Client) Trace(url string, data ...interface{}) (*ClientResponse, error) return c.DoRequest("TRACE", url, data...) } -// prepareRequest verify params and return http request +// DoRequest sends request with given HTTP method and data and returns the response object. +// Note that the response object MUST be closed if it'll be never used. +// +// Note that it uses "multipart/form-data" as its Content-Type if it contains file uploading, +// else it uses "application/x-www-form-urlencoded". It also automatically detects the post +// content for JSON format, and for that it automatically sets the Content-Type as +// "application/json". +func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *ClientResponse, err error) { + req, err := c.prepareRequest(method, url, data...) + if err != nil { + return nil, err + } + + // Client middleware. + if len(c.middlewareHandler) > 0 { + mdlHandlers := make([]ClientHandlerFunc, 0, len(c.middlewareHandler)+1) + mdlHandlers = append(mdlHandlers, c.middlewareHandler...) + mdlHandlers = append(mdlHandlers, func(cli *Client, r *http.Request) (*ClientResponse, error) { + return cli.callRequest(r) + }) + ctx := context.WithValue(req.Context(), clientMiddlewareKey, &clientMiddleware{ + client: c, + handlers: mdlHandlers, + handlerIndex: -1, + }) + req = req.WithContext(ctx) + resp, err = c.MiddlewareNext(req) + } else { + resp, err = c.callRequest(req) + } + + // Auto saving cookie content. + if c.browserMode && resp != nil { + now := time.Now() + for _, v := range resp.Response.Cookies() { + if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() { + delete(c.cookies, v.Name) + } else { + c.cookies[v.Name] = v.Value + } + } + } + return resp, err +} + +// prepareRequest verifies request parameters, builds and returns http request. func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *http.Request, err error) { method = strings.ToUpper(method) if len(c.prefix) > 0 { @@ -145,10 +191,14 @@ func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *h if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil { if f, err := os.Open(path); err == nil { if _, err = io.Copy(file, f); err != nil { - f.Close() + if err := f.Close(); err != nil { + intlog.Errorf(`%+v`, err) + } return nil, err } - f.Close() + if err := f.Close(); err != nil { + intlog.Errorf(`%+v`, err) + } } else { return nil, err } @@ -246,7 +296,9 @@ func (c *Client) callRequest(req *http.Request) (resp *ClientResponse, err error if resp.Response, err = c.Do(req); err != nil { // The response might not be nil when err != nil. if resp.Response != nil { - resp.Response.Body.Close() + if err := resp.Response.Body.Close(); err != nil { + intlog.Errorf(`%+v`, err) + } } if c.retryCount > 0 { c.retryCount-- @@ -261,51 +313,3 @@ func (c *Client) callRequest(req *http.Request) (resp *ClientResponse, err error } return resp, err } - -// DoRequest sends request with given HTTP method and data and returns the response object. -// Note that the response object MUST be closed if it'll be never used. -// -// Note that it uses "multipart/form-data" as its Content-Type if it contains file uploading, -// else it uses "application/x-www-form-urlencoded". It also automatically detects the post -// content for JSON format, and for that it automatically sets the Content-Type as -// "application/json". -func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *ClientResponse, err error) { - req, err := c.prepareRequest(method, url, data...) - if err != nil { - return nil, err - } - - if len(c.middlewareHandler) > 0 { - mdlHandlers := make([]ClientHandlerFunc, 0, len(c.middlewareHandler)+1) - mdlHandlers = append(mdlHandlers, c.middlewareHandler...) - - // last call internal handler - mdlHandlers = append(mdlHandlers, func(cli *Client, r *http.Request) (*ClientResponse, error) { - return cli.callRequest(r) - }) - - // call middleware - ctx := context.WithValue(req.Context(), gfHTTPClientMiddlewareKey, &clientMiddleware{ - client: c, - handlers: mdlHandlers, - handlerIndex: -1, - }) - req = req.WithContext(ctx) - resp, err = c.MiddlewareNext(req) - } else { - resp, err = c.callRequest(req) - } - - // Auto saving cookie content. - if c.browserMode { - now := time.Now() - for _, v := range resp.Response.Cookies() { - if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() { - delete(c.cookies, v.Name) - } else { - c.cookies[v.Name] = v.Value - } - } - } - return resp, err -} diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index f2425c665..af9edfd5c 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -64,8 +64,8 @@ func serverProcessInit() { } } - // Register signal handler. - registerSignalHandler() + // Signal handler. + go handleProcessSignal() // Process message handler. // It's enabled only graceful feature is enabled. diff --git a/net/ghttp/ghttp_unit_client_test.go b/net/ghttp/ghttp_unit_client_test.go index bead02afb..468483b2a 100644 --- a/net/ghttp/ghttp_unit_client_test.go +++ b/net/ghttp/ghttp_unit_client_test.go @@ -353,52 +353,71 @@ func Test_Client_Middleware(t *testing.T) { time.Sleep(100 * time.Millisecond) gtest.C(t, func(t *gtest.T) { - str := "" - str2 := "resp body" - c := ghttp.NewClient().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)).Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { - str += "a" + var ( + str1 = "" + str2 = "resp body" + ) + c := g.Client().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "a" resp, err = c.MiddlewareNext(r) - str += "b" - return - }).Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { - str += "c" - resp, err = c.MiddlewareNext(r) - str += "d" - return - }).Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { - str += "e" - resp, err = c.MiddlewareNext(r) - resp.Response.Body = ioutil.NopCloser(bytes.NewBufferString(str2)) - str += "f" + if err != nil { + return nil, err + } + str1 += "b" + return + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "c" + resp, err = c.MiddlewareNext(r) + if err != nil { + return nil, err + } + str1 += "d" + return + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + str1 += "e" + resp, err = c.MiddlewareNext(r) + if err != nil { + return nil, err + } + resp.Response.Body = ioutil.NopCloser(bytes.NewBufferString(str2)) + str1 += "f" return }) - resp, err := c.Get("/") - t.Assert(str, "acefdb") + t.Assert(str1, "acefdb") t.Assert(err, nil) t.Assert(resp.ReadAllString(), str2) t.Assert(isServerHandler, true) // test abort, abort will not send - str3 := "" - abortStr := "abort request" - c = ghttp.NewClient().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)).Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + var ( + str3 = "" + abortStr = "abort request" + ) + + c = g.Client().SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { str3 += "a" resp, err = c.MiddlewareNext(r) str3 += "b" return - }).Use(func(c *ghttp.Client, r *http.Request) (*ghttp.ClientResponse, error) { + }) + c.Use(func(c *ghttp.Client, r *http.Request) (*ghttp.ClientResponse, error) { str3 += "c" return nil, gerror.New(abortStr) - }).Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { + }) + c.Use(func(c *ghttp.Client, r *http.Request) (resp *ghttp.ClientResponse, err error) { str3 += "f" resp, err = c.MiddlewareNext(r) str3 += "g" return }) resp, err = c.Get("/") + t.Assert(err, abortStr) t.Assert(str3, "acb") - t.Assert(err.Error(), abortStr) t.Assert(resp, nil) }) }