Skip to content

Commit

Permalink
feat: fasthttp is back
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam committed Jul 1, 2024
1 parent afaef54 commit df2163e
Show file tree
Hide file tree
Showing 21 changed files with 432 additions and 315 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ require (
)

require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.55.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
Expand All @@ -26,6 +30,10 @@ github.com/urfave/cli-docs/v3 v3.0.0-alpha5 h1:H1oWnR2/GN0dNm2PVylws+GxSOD6YOwW/
github.com/urfave/cli-docs/v3 v3.0.0-alpha5/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/perftest/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit
Aliases: []string{"perf", "test"},
Hidden: true,
Usage: "Simple performance (load) test for the HTTP server",
Action: func(ctx context.Context, c *cli.Command) error {
Action: func(ctx context.Context, c *cli.Command) error { // TODO: use fasthttp.Client
var (
perfCtx, cancel = context.WithTimeout(ctx, c.Duration(durationFlag.Name))
startedAt = time.Now()
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/serve/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy

// Run current command.
func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Config) error { //nolint:funlen
var srv = appHttp.NewServer(ctx, log)
var srv = appHttp.NewServer(log)

if err := srv.Register(cfg); err != nil {
return err
Expand Down
15 changes: 10 additions & 5 deletions internal/http/handlers/error_page/code.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package error_page

import (
"net/http"
"path/filepath"
"strconv"
"strings"

"github.com/valyala/fasthttp"
)

// extractCodeFromURL extracts the error code from the given URL.
Expand Down Expand Up @@ -37,11 +38,15 @@ func extractCodeFromURL(url string) (uint16, bool) {
func URLContainsCode(url string) (ok bool) { _, ok = extractCodeFromURL(url); return } //nolint:nlreturn

// extractCodeFromHeaders extracts the error code from the given headers.
func extractCodeFromHeaders(headers http.Header) (uint16, bool) {
func extractCodeFromHeaders(headers *fasthttp.RequestHeader) (uint16, bool) {
if headers == nil {
return 0, false
}

// https://kubernetes.github.io/ingress-nginx/user-guide/custom-errors/
// HTTP status code returned by the request
if value := headers.Get("X-Code"); len(value) > 0 && len(value) <= 3 {
if code, err := strconv.ParseUint(value, 10, 16); err == nil && code > 0 && code < 999 {
if value := headers.Peek("X-Code"); len(value) > 0 && len(value) <= 3 {
if code, err := strconv.ParseUint(string(value), 10, 16); err == nil && code > 0 && code < 999 {
return uint16(code), true
}
}
Expand All @@ -50,7 +55,7 @@ func extractCodeFromHeaders(headers http.Header) (uint16, bool) {
}

// HeadersContainCode checks if the given headers contain an error code.
func HeadersContainCode(headers http.Header) (ok bool) {
func HeadersContainCode(headers *fasthttp.RequestHeader) (ok bool) {
_, ok = extractCodeFromHeaders(headers)

return
Expand Down
24 changes: 16 additions & 8 deletions internal/http/handlers/error_page/code_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package error_page_test

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"

"gh.tarampamp.am/error-pages/internal/http/handlers/error_page"
)
Expand Down Expand Up @@ -36,18 +36,26 @@ func TestURLContainsCode(t *testing.T) {
func TestHeadersContainCode(t *testing.T) {
t.Parallel()

var mkHeaders = func(key, value string) *fasthttp.RequestHeader {
var out = new(fasthttp.RequestHeader)

out.Set(key, value)

return out
}

for name, _tt := range map[string]struct {
giveHeaders http.Header
giveHeaders *fasthttp.RequestHeader
wantOk bool
}{
"with code": {giveHeaders: http.Header{"X-Code": {"404"}}, wantOk: true},
"with code": {giveHeaders: mkHeaders("X-Code", "404"), wantOk: true},

"empty": {giveHeaders: nil},
"no code": {giveHeaders: http.Header{"X-Code": {""}}},
"wrong": {giveHeaders: http.Header{"X-Code": {"foo"}}},
"too big": {giveHeaders: http.Header{"X-Code": {"1000"}}},
"too small": {giveHeaders: http.Header{"X-Code": {"0"}}},
"negative": {giveHeaders: http.Header{"X-Code": {"-1"}}},
"no code": {giveHeaders: mkHeaders("X-Code", "")},
"wrong": {giveHeaders: mkHeaders("X-Code", "foo")},
"too big": {giveHeaders: mkHeaders("X-Code", "1000")},
"too small": {giveHeaders: mkHeaders("X-Code", "0")},
"negative": {giveHeaders: mkHeaders("X-Code", "-1")},
} {
tt := _tt

Expand Down
11 changes: 6 additions & 5 deletions internal/http/handlers/error_page/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package error_page

import (
"math"
"net/http"
"slices"
"strconv"
"strings"

"github.com/valyala/fasthttp"
)

type preferredFormat = byte
Expand All @@ -21,10 +22,10 @@ const (
// detectPreferredFormatForClient detects the preferred format for the client based on the headers.
// It supports the following headers: Content-Type, Accept, X-Format.
// If the headers are not set or the format is not recognized, it returns unknownFormat.
func detectPreferredFormatForClient(headers http.Header) preferredFormat { //nolint:funlen,gocognit
func detectPreferredFormatForClient(headers *fasthttp.RequestHeader) preferredFormat { //nolint:funlen,gocognit
var contentType, accept string

if contentTypeHeader := strings.TrimSpace(headers.Get("Content-Type")); contentTypeHeader != "" { //nolint:nestif
if contentTypeHeader := strings.TrimSpace(string(headers.Peek("Content-Type"))); contentTypeHeader != "" { //nolint:nestif,lll
// https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type
// text/html; charset=utf-8
// multipart/form-data; boundary=something
Expand All @@ -38,11 +39,11 @@ func detectPreferredFormatForClient(headers http.Header) preferredFormat { //nol
// take the whole value
contentType = contentTypeHeader
}
} else if xFormatHeader := strings.TrimSpace(headers.Get("X-Format")); xFormatHeader != "" {
} else if xFormatHeader := strings.TrimSpace(string(headers.Peek("X-Format"))); xFormatHeader != "" {
// https://kubernetes.github.io/ingress-nginx/user-guide/custom-errors/
// Value of the `Accept` header sent by the client
accept = xFormatHeader
} else if acceptHeader := strings.TrimSpace(headers.Get("Accept")); acceptHeader != "" {
} else if acceptHeader := strings.TrimSpace(string(headers.Peek("Accept"))); acceptHeader != "" {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
// text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8
// text/html
Expand Down
54 changes: 31 additions & 23 deletions internal/http/handlers/error_page/format_test.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,114 @@
package error_page

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"
)

func Test_detectPreferredFormatForClient(t *testing.T) {
t.Parallel()

for name, _tt := range map[string]struct {
giveHeaders http.Header
giveHeaders map[string][]string
wantFormat preferredFormat
}{
"content type json": {
giveHeaders: http.Header{"Content-Type": {"application/jSoN"}},
giveHeaders: map[string][]string{"Content-Type": {"application/jSoN"}},
wantFormat: jsonFormat,
},
"content type xml": {
giveHeaders: http.Header{"Content-Type": {"application/xml; charset=UTF-8"}},
giveHeaders: map[string][]string{"Content-Type": {"application/xml; charset=UTF-8"}},
wantFormat: xmlFormat,
},
"content type html": {
giveHeaders: http.Header{"Content-Type": {"text/hTmL; charset=utf-8"}},
giveHeaders: map[string][]string{"Content-Type": {"text/hTmL; charset=utf-8"}},
wantFormat: htmlFormat,
},
"content type plain": {
giveHeaders: http.Header{"Content-Type": {"text/plaIN"}},
giveHeaders: map[string][]string{"Content-Type": {"text/plaIN"}},
wantFormat: plainTextFormat,
},

"accept json": {
giveHeaders: http.Header{"Accept": {"application/jsoN,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"application/jsoN,*/*;q=0.8"}},
wantFormat: jsonFormat,
},
"accept xml, depends on weight": {
giveHeaders: http.Header{"Accept": {"text/html;q=0.5,application/xhtml+xml;q=0.9,application/xml;q=1,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"text/html;q=0.5,application/xhtml+xml;q=0.9,application/xml;q=1,*/*;q=0.8"}},
wantFormat: xmlFormat,
},
"accept json, depends on weight": {
giveHeaders: http.Header{"Accept": {"application/jsoN,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"application/jsoN,*/*;q=0.8"}},
wantFormat: jsonFormat,
},
"accept xml": {
giveHeaders: http.Header{"Accept": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
wantFormat: xmlFormat,
},
"accept html": {
giveHeaders: http.Header{"Accept": {"text/html, application/xhtml+xml, application/xml;q=0.9, image/avif, image/webp, */*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"text/html, application/xhtml+xml, application/xml;q=0.9, image/avif, image/webp, */*;q=0.8"}},
wantFormat: htmlFormat,
},
"accept plain": {
giveHeaders: http.Header{"Accept": {"text/plaiN,text/html,application/xml;q=0.9,,,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"text/plaiN,text/html,application/xml;q=0.9,,,*/*;q=0.8"}},
wantFormat: plainTextFormat,
},
"accept json, weighted values only": {
giveHeaders: http.Header{"Accept": {"application/jsoN;Q=0.1,text/html;q=1.1,application/xml;q=-1,*/*;q=0.8"}},
giveHeaders: map[string][]string{"Accept": {"application/jsoN;Q=0.1,text/html;q=1.1,application/xml;q=-1,*/*;q=0.8"}},
wantFormat: jsonFormat,
},

"x-format json, depends on weight": {
giveHeaders: http.Header{"X-Format": {"application/jsoN,*/*;q=0.8"}},
giveHeaders: map[string][]string{"X-Format": {"application/jsoN,*/*;q=0.8"}},
wantFormat: jsonFormat,
},
"x-format xml": {
giveHeaders: http.Header{"X-Format": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
giveHeaders: map[string][]string{"X-Format": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
wantFormat: xmlFormat,
},

"content type has priority over accept": {
giveHeaders: http.Header{"Content-Type": {"text/plain"}, "Accept": {"application/xml"}},
giveHeaders: map[string][]string{"Content-Type": {"text/plain"}, "Accept": {"application/xml"}},
wantFormat: plainTextFormat,
},
"accept has priority over x-format": {
giveHeaders: http.Header{"Accept": {"application/xml"}, "X-Format": {"text/plain"}},
giveHeaders: map[string][]string{"Accept": {"application/xml"}, "X-Format": {"text/plain"}},
wantFormat: plainTextFormat,
},

"empty headers": {
giveHeaders: nil,
},
"empty content type": {
giveHeaders: http.Header{"Content-Type": {" "}},
giveHeaders: map[string][]string{"Content-Type": {" "}},
},
"wrong content type": {
giveHeaders: http.Header{"Content-Type": {"multipart/form-data; boundary=something"}},
giveHeaders: map[string][]string{"Content-Type": {"multipart/form-data; boundary=something"}},
},
"wrong accept": {
giveHeaders: http.Header{"Accept": {";q=foobar,bar/baz;;;;;application/xml"}},
giveHeaders: map[string][]string{"Accept": {";q=foobar,bar/baz;;;;;application/xml"}},
},
"none on invalid input": {
giveHeaders: http.Header{"Content-Type": {"foo/bar; charset=utf-8"}, "Accept": {"foo/bar; charset=utf-8"}},
giveHeaders: map[string][]string{"Content-Type": {"foo/bar; charset=utf-8"}, "Accept": {"foo/bar; charset=utf-8"}},
},
"completely unknown": {
giveHeaders: http.Header{"Content-Type": {"😀"}, "Accept": {"😄"}, "X-Format": {"😍"}},
giveHeaders: map[string][]string{"Content-Type": {"😀"}, "Accept": {"😄"}, "X-Format": {"😍"}},
},
} {
tt := _tt

t.Run(name, func(t *testing.T) {
assert.Equal(t, tt.wantFormat, detectPreferredFormatForClient(tt.giveHeaders))
var headers = new(fasthttp.RequestHeader)

for key, values := range tt.giveHeaders {
for _, value := range values {
headers.Add(key, value)
}
}

assert.Equal(t, tt.wantFormat, detectPreferredFormatForClient(headers))
})
}
}
Loading

0 comments on commit df2163e

Please sign in to comment.