diff --git a/expect_test.go b/expect_test.go index 50fb000df..2c201002f 100644 --- a/expect_test.go +++ b/expect_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "testing" + "github.com/valyala/fasthttp/fasthttpadaptor" "github.com/gavv/httpexpect/fasthttpexpect" ) @@ -295,3 +296,17 @@ func BenchmarkExpectBinderStandard(b *testing.B) { testHandler(e) } } + +func BenchmarkExpectBinderFast(b *testing.B) { + handler := fasthttpadaptor.NewFastHTTPHandler(createHandler()) + + e := WithConfig(Config{ + BaseURL: "http://example.com", + Client: fasthttpexpect.NewBinder(handler), + Reporter: NewRequireReporter(b), + }) + + for i := 0; i < b.N; i++ { + testHandler(e) + } +} diff --git a/fasthttpexpect/binder.go b/fasthttpexpect/binder.go new file mode 100644 index 000000000..87adedcae --- /dev/null +++ b/fasthttpexpect/binder.go @@ -0,0 +1,36 @@ +package fasthttpexpect + +import ( + "github.com/valyala/fasthttp" + "net/http" +) + +// Binder implements networkless httpexpect.Client attached directly to +// fasthttp.RequestHandler. +type Binder struct { + handler fasthttp.RequestHandler +} + +// NewBinder returns a new Binder given fasthttp.RequestHandler. +func NewBinder(handler fasthttp.RequestHandler) *Binder { + return &Binder{handler} +} + +// Do implements httpexpect.Client.Do. +func (binder *Binder) Do(stdreq *http.Request) (*http.Response, error) { + var fastreq fasthttp.Request + + convertRequest(stdreq, &fastreq) + + var ctx fasthttp.RequestCtx + + ctx.Init(&fastreq, nil, nil) + + if stdreq.Body != nil { + ctx.Request.SetBodyStream(stdreq.Body, -1) + } + + binder.handler(&ctx) + + return convertResponse(stdreq, &ctx.Response), nil +} diff --git a/fasthttpexpect/client.go b/fasthttpexpect/client.go index 5af61aa88..08fc7a0c5 100644 --- a/fasthttpexpect/client.go +++ b/fasthttpexpect/client.go @@ -1,10 +1,7 @@ -// Package fasthttpexpect provides fasthttp adapter for httpexpect. package fasthttpexpect import ( - "bytes" "github.com/valyala/fasthttp" - "io" "net/http" ) @@ -37,55 +34,15 @@ func WithClient(backend ClientBackend) ClientAdapter { // Do implements httpexpect.Client.Do. func (adapter ClientAdapter) Do(stdreq *http.Request) (stdresp *http.Response, err error) { - fastreq := fasthttp.AcquireRequest() + var fastreq fasthttp.Request - if stdreq.Body != nil { - fastreq.SetBodyStream(stdreq.Body, -1) - } - - fastreq.SetRequestURI(stdreq.URL.String()) - - fastreq.Header.SetMethod(stdreq.Method) - - for k, a := range stdreq.Header { - for _, v := range a { - fastreq.Header.Add(k, v) - } - } + convertRequest(stdreq, &fastreq) var fastresp fasthttp.Response - if err = adapter.backend.Do(fastreq, &fastresp); err == nil { - status := fastresp.Header.StatusCode() - body := fastresp.Body() - - stdresp = &http.Response{ - Request: stdreq, - StatusCode: status, - Status: http.StatusText(status), - } - - fastresp.Header.VisitAll(func(k, v []byte) { - if stdresp.Header == nil { - stdresp.Header = make(http.Header) - } - stdresp.Header.Add(string(k), string(v)) - }) - - if body != nil { - stdresp.Body = readCloserAdapter{bytes.NewReader(body)} - } + if err = adapter.backend.Do(&fastreq, &fastresp); err == nil { + stdresp = convertResponse(stdreq, &fastresp) } - fasthttp.ReleaseRequest(fastreq) - return } - -type readCloserAdapter struct { - io.Reader -} - -func (b readCloserAdapter) Close() error { - return nil -} diff --git a/fasthttpexpect/convert.go b/fasthttpexpect/convert.go new file mode 100644 index 000000000..d12c5e107 --- /dev/null +++ b/fasthttpexpect/convert.go @@ -0,0 +1,56 @@ +package fasthttpexpect + +import ( + "bytes" + "github.com/valyala/fasthttp" + "io" + "net/http" +) + +func convertRequest(stdreq *http.Request, fastreq *fasthttp.Request) { + if stdreq.Body != nil { + fastreq.SetBodyStream(stdreq.Body, -1) + } + + fastreq.SetRequestURI(stdreq.URL.String()) + + fastreq.Header.SetMethod(stdreq.Method) + + for k, a := range stdreq.Header { + for _, v := range a { + fastreq.Header.Add(k, v) + } + } +} + +func convertResponse(stdreq *http.Request, fastresp *fasthttp.Response) *http.Response { + status := fastresp.Header.StatusCode() + body := fastresp.Body() + + stdresp := &http.Response{ + Request: stdreq, + StatusCode: status, + Status: http.StatusText(status), + } + + fastresp.Header.VisitAll(func(k, v []byte) { + if stdresp.Header == nil { + stdresp.Header = make(http.Header) + } + stdresp.Header.Add(string(k), string(v)) + }) + + if body != nil { + stdresp.Body = readCloserAdapter{bytes.NewReader(body)} + } + + return stdresp +} + +type readCloserAdapter struct { + io.Reader +} + +func (b readCloserAdapter) Close() error { + return nil +} diff --git a/fasthttpexpect/fasthttpexpect.go b/fasthttpexpect/fasthttpexpect.go new file mode 100644 index 000000000..2c2e1b017 --- /dev/null +++ b/fasthttpexpect/fasthttpexpect.go @@ -0,0 +1,2 @@ +// Package fasthttpexpect provides fasthttp adapter for httpexpect. +package fasthttpexpect diff --git a/fasthttpexpect/client_test.go b/fasthttpexpect/fasthttpexpect_test.go similarity index 74% rename from fasthttpexpect/client_test.go rename to fasthttpexpect/fasthttpexpect_test.go index ddefdd336..0bc3a562e 100644 --- a/fasthttpexpect/client_test.go +++ b/fasthttpexpect/fasthttpexpect_test.go @@ -9,6 +9,10 @@ import ( "testing" ) +type testClient interface { + Do(*http.Request) (*http.Response, error) +} + type mockBackend struct { t *testing.T } @@ -24,9 +28,7 @@ func (c mockBackend) Do(req *fasthttp.Request, resp *fasthttp.Response) error { return nil } -func TestClientAdapter(t *testing.T) { - adapter := WithClient(mockBackend{t}) - +func runTest(t *testing.T, client testClient) { req, err := http.NewRequest( "GET", "http://example.com", bytes.NewReader([]byte("body"))) @@ -34,7 +36,7 @@ func TestClientAdapter(t *testing.T) { t.Fatal(err) } - resp, err := adapter.Do(req) + resp, err := client.Do(req) if err != nil { t.Fatal(err) } @@ -51,3 +53,19 @@ func TestClientAdapter(t *testing.T) { assert.Equal(t, header, resp.Header) assert.Equal(t, `{"hello":"world"}`, string(b)) } + +func TestClientAdapter(t *testing.T) { + adapter := WithClient(mockBackend{t}) + + runTest(t, adapter) +} + +func TestBinder(t *testing.T) { + backend := mockBackend{t} + + binder := NewBinder(func(ctx *fasthttp.RequestCtx) { + backend.Do(&ctx.Request, &ctx.Response) + }) + + runTest(t, binder) +}