Skip to content
This repository was archived by the owner on Apr 13, 2024. It is now read-only.

Commit d163dde

Browse files
authored
feat: ability to use middlewares for clients (#5)
1 parent 1cf4ec1 commit d163dde

File tree

7 files changed

+139
-9
lines changed

7 files changed

+139
-9
lines changed

example_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package requests
33
import (
44
"context"
55
"fmt"
6+
"net/http"
67

78
"github.com/frankli0324/go-requests/request"
89
)
@@ -17,3 +18,28 @@ func ExampleCtxDo() {
1718
c, err := resp.Body()
1819
fmt.Println(string(c))
1920
}
21+
22+
func ExampleMiddleware() {
23+
cli, _ := NewClient()
24+
cli.Use(func(next Handler) Handler {
25+
return func(rc *RequestCtx) error {
26+
if rc.Request.Headers == nil {
27+
rc.Request.Headers = make(http.Header)
28+
}
29+
rc.Request.Headers.Add("handler-ok", "1")
30+
err := next(rc)
31+
if err == nil {
32+
fmt.Println(rc.Response.Header.Get("Date"))
33+
}
34+
return err
35+
}
36+
})
37+
done, resp, err := cli.CtxDo(context.Background(), request.Get("https://www.google.com", nil))
38+
if err != nil {
39+
panic(err)
40+
}
41+
defer done()
42+
43+
c, err := resp.Body()
44+
fmt.Println(string(c))
45+
}

internal/client/client.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@ import (
44
"context"
55
"net"
66
"net/http"
7+
"sync"
78
"time"
89

910
"github.com/frankli0324/go-requests/internal/request"
1011
"github.com/frankli0324/go-requests/internal/response"
1112
)
1213

14+
type Handler = func(*RequestCtx) error
15+
type Middleware = func(next Handler) Handler
16+
1317
type Client struct {
1418
Client http.Client
19+
20+
// I miss partial classes in C#
21+
middlewares []Middleware
22+
chainedHandler Handler
23+
mwLock sync.RWMutex
1524
}
1625

1726
func New(opts ...Option) (*Client, error) {
@@ -39,24 +48,31 @@ func New(opts ...Option) (*Client, error) {
3948
},
4049
},
4150
}
51+
cli.chainedHandler = cli.request
4252
return cli, cli.Configure(opts...)
4353
}
4454

4555
func (c *Client) CtxDo(
4656
ctx context.Context, req *request.Request,
4757
) (func(), *response.Response, error) {
48-
r, err := req.Build(ctx)
58+
rctx := getRequestCtx(ctx)
59+
rctx.Request = req
60+
c.mwLock.RLock()
61+
call := c.chainedHandler
62+
c.mwLock.RUnlock()
63+
return rctx.Done, rctx.Response, call(rctx)
64+
}
65+
66+
func (c *Client) request(rc *RequestCtx) error {
67+
r, err := rc.Request.Build(rc.Context)
4968
if err != nil {
50-
return func() {}, nil, err
69+
return err
5170
}
5271
resp, err := c.Client.Do(r)
5372
if err != nil {
5473
// on error, return a Response that won't be recovered
55-
return func() {}, response.Wrap(resp), err
74+
return err
5675
}
57-
ret := response.Wrap(resp)
58-
return func() {
59-
ret.Done()
60-
request.PutRequest(r)
61-
}, ret, nil
76+
rc.Response = response.Wrap(resp)
77+
return nil
6278
}

internal/client/middleware.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package client
2+
3+
// Use appends the passed middleware to the end of the list
4+
func (c *Client) Use(mw Middleware) {
5+
c.mwLock.Lock()
6+
defer c.mwLock.Unlock()
7+
c.middlewares = append(c.middlewares, mw)
8+
c.buildHandler()
9+
}
10+
11+
func (c *Client) UseIndex(mw Middleware, idx int) {
12+
c.mwLock.Lock()
13+
defer c.mwLock.Unlock()
14+
if len(c.middlewares) <= idx {
15+
c.middlewares = append(c.middlewares, mw)
16+
} else {
17+
c.middlewares = append(c.middlewares[:idx+1], c.middlewares[idx:]...)
18+
c.middlewares[idx] = mw
19+
}
20+
c.buildHandler()
21+
}
22+
23+
func (c *Client) buildHandler() {
24+
// rebuilds the entire middleware chain
25+
chain := c.request
26+
for _, mw := range c.middlewares {
27+
chain = mw(chain)
28+
}
29+
c.chainedHandler = chain
30+
}

internal/client/requestctx.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"sync"
6+
7+
"github.com/frankli0324/go-requests/internal/request"
8+
"github.com/frankli0324/go-requests/internal/response"
9+
)
10+
11+
type RequestCtx struct {
12+
context.Context
13+
Error error
14+
Request *request.Request
15+
Response *response.Response
16+
}
17+
18+
// Done recycles the resources used by request context,
19+
// after calling this function, the request context
20+
// MUST NOT be used again.
21+
func (rc *RequestCtx) Done() {
22+
if rc.Request != nil {
23+
rc.Request.Done()
24+
}
25+
if rc.Response != nil {
26+
rc.Response.Done()
27+
}
28+
putRequestCtx(rc)
29+
}
30+
31+
var p = sync.Pool{New: func() interface{} {
32+
return &RequestCtx{}
33+
}}
34+
35+
func getRequestCtx(ctx context.Context) *RequestCtx {
36+
c := p.Get().(*RequestCtx)
37+
c.Context = ctx
38+
return c
39+
}
40+
41+
func putRequestCtx(ctx *RequestCtx) {
42+
ctx.Context = nil
43+
ctx.Request = nil
44+
ctx.Response = nil
45+
ctx.Error = nil
46+
p.Put(ctx)
47+
}

internal/request/pool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ func getRequest() *http.Request {
1313
return p.Get().(*http.Request)
1414
}
1515

16-
func PutRequest(req *http.Request) {
16+
func putRequest(req *http.Request) {
1717
p.Put(req)
1818
}

internal/request/request.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type Request struct {
1919
Encoders []func(io.ReadCloser) (string, io.ReadCloser)
2020

2121
NoRedirect bool
22+
23+
innerReq *http.Request
2224
}
2325

2426
func (r *Request) Build(ctx context.Context) (*http.Request, error) {
@@ -36,8 +38,13 @@ func (r *Request) Build(ctx context.Context) (*http.Request, error) {
3638
ctx = context.WithValue(ctx, KRedirect, false)
3739
}
3840
req = req.WithContext(ctx) // copy occurred here
41+
r.innerReq = req
3942

4043
r.buildContent(req)
4144
r.buildEncoding(req)
4245
return req, nil
4346
}
47+
48+
func (r *Request) Done() {
49+
putRequest(r.innerReq)
50+
}

requests.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type ClientOption = client.Option
1515
type Request = request.Request
1616
type Response = response.Response
1717

18+
type RequestCtx = client.RequestCtx
19+
type Handler = client.Handler
20+
type Middleware = client.Middleware
21+
1822
var defaultClient = Client{}
1923

2024
func Configure(baseProfile string, opts ...client.Option) error {

0 commit comments

Comments
 (0)