Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare fasthttp fork #78

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 30 additions & 35 deletions _examples/advanced-generic/gzip_pass_through_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,57 @@ package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

func Test_directGzip(t *testing.T) {
r := NewRouter()

req, err := http.NewRequest(http.MethodGet, "/gzip-pass-through", nil)
require.NoError(t, err)
rc := &fasthttp.RequestCtx{}
rc.Request.SetRequestURI("/gzip-pass-through")
rc.Request.Header.Set("Accept-Encoding", "gzip")

req.Header.Set("Accept-Encoding", "gzip")
rw := httptest.NewRecorder()

r.ServeHTTP(rw, req)
assert.Equal(t, http.StatusOK, rw.Code)
assert.Equal(t, "330epditz19z", rw.Header().Get("Etag"))
assert.Equal(t, "gzip", rw.Header().Get("Content-Encoding"))
assert.Equal(t, "abc", rw.Header().Get("X-Header"))
assert.Less(t, len(rw.Body.Bytes()), 500)
r.ServeHTTP(rc, rc)
assert.Equal(t, http.StatusOK, rc.Response.StatusCode())
assert.Equal(t, "330epditz19z", string(rc.Response.Header.Peek("Etag")))
assert.Equal(t, "gzip", string(rc.Response.Header.Peek("Content-Encoding")))
assert.Equal(t, "abc", string(rc.Response.Header.Peek("X-Header")))
assert.Less(t, len(rc.Response.Body()), 500)
}

func Test_noDirectGzip(t *testing.T) {
r := NewRouter()

req, err := http.NewRequest(http.MethodGet, "/gzip-pass-through?plainStruct=1", nil)
require.NoError(t, err)

req.Header.Set("Accept-Encoding", "gzip")
rw := httptest.NewRecorder()
rc := &fasthttp.RequestCtx{}
rc.Request.SetRequestURI("/gzip-pass-through?plainStruct=1")
rc.Request.Header.Set("Accept-Encoding", "gzip")

r.ServeHTTP(rw, req)
assert.Equal(t, http.StatusOK, rw.Code)
assert.Equal(t, "", rw.Header().Get("Etag")) // No ETag for dynamic compression.
assert.Equal(t, "gzip", rw.Header().Get("Content-Encoding"))
assert.Equal(t, "cba", rw.Header().Get("X-Header"))
assert.Less(t, len(rw.Body.Bytes()), 1000) // Worse compression for better speed.
r.ServeHTTP(rc, rc)
assert.Equal(t, http.StatusOK, rc.Response.StatusCode())
assert.Equal(t, "", string(rc.Response.Header.Peek("Etag"))) // No ETag for dynamic compression.
assert.Equal(t, "gzip", string(rc.Response.Header.Peek("Content-Encoding")))
assert.Equal(t, "cba", string(rc.Response.Header.Peek("X-Header")))
assert.Less(t, len(rc.Response.Body()), 1000) // Worse compression for better speed.
}

func Test_directGzip_perf(t *testing.T) {
res := testing.Benchmark(Benchmark_directGzip)

if httptestbench.RaceDetectorEnabled {
assert.Less(t, res.Extra["B:rcvd/op"], 700.0)
assert.Less(t, res.Extra["B:sent/op"], 104.0)
assert.Less(t, res.AllocsPerOp(), int64(60))
assert.Less(t, res.AllocedBytesPerOp(), int64(8500))
assert.Less(t, res.Extra["B:sent/op"], 110.0)
assert.Less(t, res.AllocsPerOp(), int64(30))
assert.Less(t, res.AllocedBytesPerOp(), int64(4500))
} else {
assert.Less(t, res.Extra["B:rcvd/op"], 700.0)
assert.Less(t, res.Extra["B:sent/op"], 104.0)
assert.Less(t, res.AllocsPerOp(), int64(45))
assert.Less(t, res.AllocedBytesPerOp(), int64(4100))
assert.Less(t, res.Extra["B:sent/op"], 105.0)
assert.Less(t, res.AllocsPerOp(), int64(17))
assert.Less(t, res.AllocedBytesPerOp(), int64(1100))
}
}

Expand All @@ -69,7 +64,7 @@ func Test_directGzip_perf(t *testing.T) {
func Benchmark_directGzip(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand All @@ -86,7 +81,7 @@ func Benchmark_directGzip(b *testing.B) {
func Benchmark_directGzipHead(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand All @@ -105,7 +100,7 @@ func Benchmark_directGzipHead(b *testing.B) {
func Benchmark_noDirectGzip(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand All @@ -123,7 +118,7 @@ func Benchmark_noDirectGzip(b *testing.B) {
func Benchmark_directGzip_decode(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand All @@ -140,7 +135,7 @@ func Benchmark_directGzip_decode(b *testing.B) {
func Benchmark_noDirectGzip_decode(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand Down
37 changes: 16 additions & 21 deletions _examples/advanced-generic/json_body_manual.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"

"github.com/go-chi/chi/v5"
"github.com/swaggest/fchi"
"github.com/swaggest/jsonschema-go"
"github.com/swaggest/rest/request"
"github.com/swaggest/usecase"
"github.com/valyala/fasthttp"
)

func jsonBodyManual() usecase.Interactor {
Expand Down Expand Up @@ -55,28 +53,25 @@ type inputWithJSON struct {

var _ request.Loader = &inputWithJSON{}

func (i *inputWithJSON) LoadFromHTTPRequest(r *http.Request) (err error) {
defer func() {
if err := r.Body.Close(); err != nil {
log.Printf("failed to close request body: %s", err.Error())
}
}()

b, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("failed to read request body: %w", err)
func (i *inputWithJSON) LoadFromFastHTTPRequest(rc *fasthttp.RequestCtx) (err error) {
if err = json.Unmarshal(rc.Request.Body(), i); err != nil {
return fmt.Errorf("failed to unmarshal request body: %w", err)
}

if err = json.Unmarshal(b, i); err != nil {
return fmt.Errorf("failsed to unmarshal request body: %w", err)
}
i.Header = string(rc.Request.Header.Peek("X-Header"))

i.Header = r.Header.Get("X-Header")
if err := i.Query.UnmarshalText([]byte(r.URL.Query().Get("in_query"))); err != nil {
return fmt.Errorf("failed to decode in_query %q: %w", r.URL.Query().Get("in_query"), err)
rc.Request.URI().QueryArgs().VisitAll(func(key, value []byte) {
if string(key) == "in_query" {
if err = i.Query.UnmarshalText(value); err != nil {
err = fmt.Errorf("failed to decode in_query %q: %w", string(value), err)
}
}
})
if err != nil {
return err
}

if routeCtx := chi.RouteContext(r.Context()); routeCtx != nil {
if routeCtx := fchi.RouteContext(rc); routeCtx != nil {
i.Path = routeCtx.URLParam("in-path")
} else {
return errors.New("missing path params in context")
Expand Down
6 changes: 3 additions & 3 deletions _examples/advanced-generic/json_body_manual_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

// Benchmark_jsonBodyManual-12 125672 8542 ns/op 208.0 B:rcvd/op 195.0 B:sent/op 117048 rps 4523 B/op 49 allocs/op.
// Benchmark_jsonBodyManual-12 147058 8812 ns/op 226.0 B:rcvd/op 195.0 B:sent/op 113469 rps 728 B/op 18 allocs/op.
func Benchmark_jsonBodyManual(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand Down
6 changes: 3 additions & 3 deletions _examples/advanced-generic/json_body_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

// Benchmark_jsonBody-12 96762 12042 ns/op 208.0 B:rcvd/op 188.0 B:sent/op 83033 rps 10312 B/op 100 allocs/op.
// Benchmark_jsonBody-12 68124 17828 ns/op 226.0 B:rcvd/op 188.0 B:sent/op 56083 rps 6864 B/op 85 allocs/op.
func Benchmark_jsonBody(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand Down
4 changes: 2 additions & 2 deletions _examples/advanced-generic/json_body_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

// Benchmark_jsonBodyValidation-4 19126 60170 ns/op 194 B:rcvd/op 192 B:sent/op 16620 rps 18363 B/op 144 allocs/op.
func Benchmark_jsonBodyValidation(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand Down
6 changes: 4 additions & 2 deletions _examples/advanced-generic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ package main

import (
"log"
"net/http"

"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

func main() {
log.Println("http://localhost:8011/docs")
if err := http.ListenAndServe(":8011", NewRouter()); err != nil {
if err := fasthttp.ListenAndServe(":8011", fchi.RequestHandler(NewRouter())); err != nil {
log.Fatal(err)
}
}
6 changes: 3 additions & 3 deletions _examples/advanced-generic/output_headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggest/assertjson"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

// Benchmark_outputHeaders-4 41424 27054 ns/op 154 B:rcvd/op 77.0 B:sent/op 36963 rps 3641 B/op 35 allocs/op.
func Benchmark_outputHeaders(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand All @@ -33,7 +33,7 @@ func Benchmark_outputHeaders(b *testing.B) {
func Test_outputHeaders(t *testing.T) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

resp, err := http.Get(srv.URL + "/output-headers")
Expand Down
6 changes: 3 additions & 3 deletions _examples/advanced-generic/request_response_mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/bool64/httptestbench"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggest/fchi"
"github.com/valyala/fasthttp"
)

func Test_requestResponseMapping(t *testing.T) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

req, err := http.NewRequest(http.MethodPost, srv.URL+"/req-resp-mapping",
Expand All @@ -44,7 +44,7 @@ func Test_requestResponseMapping(t *testing.T) {
func Benchmark_requestResponseMapping(b *testing.B) {
r := NewRouter()

srv := httptest.NewServer(r)
srv := fchi.NewTestServer(r)
defer srv.Close()

httptestbench.RoundTrip(b, 50, func(i int, req *fasthttp.Request) {
Expand Down
12 changes: 4 additions & 8 deletions _examples/advanced-generic/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"reflect"
"strings"

"github.com/rs/cors"
"github.com/swaggest/fchi"
"github.com/swaggest/jsonschema-go"
"github.com/swaggest/openapi-go/openapi3"
"github.com/swaggest/rest"
Expand All @@ -20,7 +20,7 @@ import (
swgui "github.com/swaggest/swgui/v4emb"
)

func NewRouter() http.Handler {
func NewRouter() fchi.Handler {
s := web.DefaultService()

s.OpenAPI.Info.Title = "Advanced Example"
Expand Down Expand Up @@ -56,8 +56,8 @@ func NewRouter() http.Handler {
s.OpenAPICollector.CombineErrors = "anyOf"

s.Wrap(
// Example middleware to set up custom error responses and disable response validation for particular handlers.
func(handler http.Handler) http.Handler {
// Example middleware to set up custom error responses.
func(handler fchi.Handler) fchi.Handler {
var h *nethttp.Handler
if nethttp.HandlerAs(handler, &h) {
h.MakeErrResp = func(ctx context.Context, err error) (int, interface{}) {
Expand Down Expand Up @@ -87,10 +87,6 @@ func NewRouter() http.Handler {
return handler
},

// Example middleware to set up CORS headers.
// See https://pkg.go.dev/github.com/rs/cors for more details.
cors.AllowAll().Handler,

// Response validator setup.
//
// It might be a good idea to disable this middleware in production to save performance,
Expand Down
Loading