mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
improve middleware feature for ghttp.Client
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user