diff --git a/.github/README.md b/.github/README.md index 1d1563a563..cbcfd15eca 100644 --- a/.github/README.md +++ b/.github/README.md @@ -88,7 +88,7 @@ First of all, [download](https://golang.org/dl/) and install Go. `1.11` or highe Installation is done using the [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 Benchmarks @@ -235,7 +235,7 @@ func main() { "year": 1999, }) }) - + // ... } ``` @@ -245,20 +245,20 @@ func main() { ```go func main() { app := fiber.New() - + // Root API route api := app.Group("/api", cors()) // /api - + // API v1 routes v1 := api.Group("/v1", mysql()) // /api/v1 v1.Get("/list", handler) // /api/v1/list v1.Get("/user", handler) // /api/v1/user - + // API v2 routes v2 := api.Group("/v2", mongodb()) // /api/v2 v2.Get("/list", handler) // /api/v2/list v2.Get("/user", handler) // /api/v2/user - + // ... } ``` diff --git a/.github/README_de.md b/.github/README_de.md index 704939faeb..bd5c6c7d3d 100644 --- a/.github/README_de.md +++ b/.github/README_de.md @@ -88,7 +88,7 @@ Als erstes, [downloade](https://golang.org/dl/) und installiere Go. `1.11` oder Die Installation wird durch das [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) Kommando gestartet: ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 Benchmarks diff --git a/.github/README_fr.md b/.github/README_fr.md index 73bb21d2a2..9659f5b338 100644 --- a/.github/README_fr.md +++ b/.github/README_fr.md @@ -88,7 +88,7 @@ Premièrement, [téléchargez](https://golang.org/dl/) et installez Go. Version L'installation est ensuite lancée via la commande [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them): ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 Benchmarks diff --git a/.github/README_ja.md b/.github/README_ja.md index 540967c76f..46edc0cb7b 100644 --- a/.github/README_ja.md +++ b/.github/README_ja.md @@ -89,7 +89,7 @@ func main() { そして、[`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them)コマンドを使用してインストールしてください。 ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 ベンチマーク diff --git a/.github/README_ko.md b/.github/README_ko.md index 672b429e78..e0b595c5a9 100644 --- a/.github/README_ko.md +++ b/.github/README_ko.md @@ -88,7 +88,7 @@ func main() { [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) 명령어를 이용해 설치가 완료됩니다: ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 벤치마크 diff --git a/.github/README_pt.md b/.github/README_pt.md index fa8c5f179a..1a1a79dbb9 100644 --- a/.github/README_pt.md +++ b/.github/README_pt.md @@ -88,7 +88,7 @@ Primeiro de tudo, faça o [download](https://golang.org/dl/) e instale o Go. É A instalação é feita usando o comando [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) : ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 Benchmarks diff --git a/.github/README_tr.md b/.github/README_tr.md index 455af4d6e6..2999607c26 100644 --- a/.github/README_tr.md +++ b/.github/README_tr.md @@ -85,7 +85,7 @@ func main() { [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) komutunu kullanarak kurulumu tamamlıyoruz: ```bash -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 Performans Ölçümleri diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md index ea7ccf497b..33292c094a 100644 --- a/.github/README_zh-CN.md +++ b/.github/README_zh-CN.md @@ -84,7 +84,7 @@ func main() { ```bash export GO111MODULE=on export GOPROXY=https://goproxy.cn -go get github.com/gofiber/fiber +go get -u github.com/gofiber/fiber/... ``` ## 🤖 性能 diff --git a/app.go b/app.go index fd368dcdde..1f38994446 100644 --- a/app.go +++ b/app.go @@ -50,24 +50,27 @@ type ( ServerHeader string `default:""` // Enables handler values to be immutable even if you return from handler Immutable bool `default:"false"` + // Enables GZip / Deflate compression for all responses + Compression bool `default:"false"` + // CompressionLevel int `default:"1"` // fasthttp settings - GETOnly bool `default:"false"` - IdleTimeout time.Duration `default:"0"` - Concurrency int `default:"256 * 1024"` - ReadTimeout time.Duration `default:"0"` - WriteTimeout time.Duration `default:"0"` - TCPKeepalive bool `default:"false"` - MaxConnsPerIP int `default:"0"` - ReadBufferSize int `default:"4096"` - WriteBufferSize int `default:"4096"` - ConcurrencySleep time.Duration `default:"0"` - DisableKeepAlive bool `default:"false"` - ReduceMemoryUsage bool `default:"false"` - MaxRequestsPerConn int `default:"0"` - TCPKeepalivePeriod time.Duration `default:"0"` - MaxRequestBodySize int `default:"4 * 1024 * 1024"` - NoHeaderNormalizing bool `default:"false"` - NoDefaultContentType bool `default:"false"` + // GETOnly bool `default:"false"` + // IdleTimeout time.Duration `default:"0"` + // Concurrency int `default:"256 * 1024"` + // ReadTimeout time.Duration `default:"0"` + // WriteTimeout time.Duration `default:"0"` + // TCPKeepalive bool `default:"false"` + // MaxConnsPerIP int `default:"0"` + // ReadBufferSize int `default:"4096"` + // WriteBufferSize int `default:"4096"` + // ConcurrencySleep time.Duration `default:"0"` + // DisableKeepAlive bool `default:"false"` + // ReduceMemoryUsage bool `default:"false"` + // MaxRequestsPerConn int `default:"0"` + // TCPKeepalivePeriod time.Duration `default:"0"` + BodyLimit int `default:"4 * 1024 * 1024"` + // NoHeaderNormalizing bool `default:"false"` + // NoDefaultContentType bool `default:"false"` // template settings TemplateFolder string `default:""` TemplateEngine string `default:""` @@ -104,27 +107,30 @@ func New(settings ...*Settings) (app *App) { return string(b) } } - if opt.Concurrency == 0 { - opt.Concurrency = 256 * 1024 - } - if opt.ReadBufferSize == 0 { - opt.ReadBufferSize = 4096 - } - if opt.WriteBufferSize == 0 { - opt.WriteBufferSize = 4096 - } - if opt.MaxRequestBodySize == 0 { - opt.MaxRequestBodySize = 4 * 1024 * 1024 + // if opt.Concurrency == 0 { + // opt.Concurrency = 256 * 1024 + // } + // if opt.ReadBufferSize == 0 { + // opt.ReadBufferSize = 4096 + // } + // if opt.WriteBufferSize == 0 { + // opt.WriteBufferSize = 4096 + // } + if opt.BodyLimit == 0 { + opt.BodyLimit = 4 * 1024 * 1024 } + // if opt.CompressionLevel == 0 { + // opt.CompressionLevel = 1 + // } app.Settings = opt return } app.Settings = &Settings{ - Prefork: prefork, - Concurrency: 256 * 1024, - ReadBufferSize: 4096, - WriteBufferSize: 4096, - MaxRequestBodySize: 4 * 1024 * 1024, + Prefork: prefork, + // Concurrency: 256 * 1024, + // ReadBufferSize: 4096, + // WriteBufferSize: 4096, + BodyLimit: 4 * 1024 * 1024, } return } @@ -335,11 +341,11 @@ func (app *App) Test(request *http.Request) (*http.Response, error) { func (app *App) prefork(address string) (ln net.Listener, err error) { // Master proc if !app.child { - addr, err := net.ResolveTCPAddr("tcp4", address) + addr, err := net.ResolveTCPAddr("tcp", address) if err != nil { return ln, err } - tcplistener, err := net.ListenTCP("tcp4", addr) + tcplistener, err := net.ListenTCP("tcp", addr) if err != nil { return ln, err } @@ -373,31 +379,44 @@ func (app *App) prefork(address string) (ln net.Listener, err error) { return ln, err } +type disableLogger struct{} + +func (dl *disableLogger) Printf(format string, args ...interface{}) { + // fmt.Println(fmt.Sprintf(format, args...)) +} + func (app *App) newServer() *fasthttp.Server { return &fasthttp.Server{ Handler: app.handler, + Name: app.Settings.ServerHeader, + // Concurrency: app.Settings.Concurrency, + // SleepWhenConcurrencyLimitsExceeded: app.Settings.ConcurrencySleep, + // DisableKeepalive: app.Settings.DisableKeepAlive, + // ReadBufferSize: app.Settings.ReadBufferSize, + // WriteBufferSize: app.Settings.WriteBufferSize, + // ReadTimeout: app.Settings.ReadTimeout, + // WriteTimeout: app.Settings.WriteTimeout, + // IdleTimeout: app.Settings.IdleTimeout, + // MaxConnsPerIP: app.Settings.MaxConnsPerIP, + // MaxRequestsPerConn: app.Settings.MaxRequestsPerConn, + // TCPKeepalive: app.Settings.TCPKeepalive, + // TCPKeepalivePeriod: app.Settings.TCPKeepalivePeriod, + MaxRequestBodySize: app.Settings.BodyLimit, + // ReduceMemoryUsage: app.Settings.ReduceMemoryUsage, + // GetOnly: app.Settings.GETOnly, + // DisableHeaderNamesNormalizing: app.Settings.NoHeaderNormalizing, + NoDefaultServerHeader: app.Settings.ServerHeader == "", + // NoDefaultContentType: app.Settings.NoDefaultContentType, + Logger: &disableLogger{}, + LogAllErrors: false, ErrorHandler: func(ctx *fasthttp.RequestCtx, err error) { - ctx.Response.SetStatusCode(400) - ctx.Response.SetBodyString("Bad Request") + if err.Error() == "body size exceeds the given limit" { + ctx.Response.SetStatusCode(413) + ctx.Response.SetBodyString("Request Entity Too Large") + } else { + ctx.Response.SetStatusCode(400) + ctx.Response.SetBodyString("Bad Request") + } }, - Name: app.Settings.ServerHeader, - Concurrency: app.Settings.Concurrency, - SleepWhenConcurrencyLimitsExceeded: app.Settings.ConcurrencySleep, - DisableKeepalive: app.Settings.DisableKeepAlive, - ReadBufferSize: app.Settings.ReadBufferSize, - WriteBufferSize: app.Settings.WriteBufferSize, - ReadTimeout: app.Settings.ReadTimeout, - WriteTimeout: app.Settings.WriteTimeout, - IdleTimeout: app.Settings.IdleTimeout, - MaxConnsPerIP: app.Settings.MaxConnsPerIP, - MaxRequestsPerConn: app.Settings.MaxRequestsPerConn, - TCPKeepalive: app.Settings.TCPKeepalive, - TCPKeepalivePeriod: app.Settings.TCPKeepalivePeriod, - MaxRequestBodySize: app.Settings.MaxRequestBodySize, - ReduceMemoryUsage: app.Settings.ReduceMemoryUsage, - GetOnly: app.Settings.GETOnly, - DisableHeaderNamesNormalizing: app.Settings.NoHeaderNormalizing, - NoDefaultServerHeader: app.Settings.ServerHeader == "", - NoDefaultContentType: app.Settings.NoDefaultContentType, } } diff --git a/context.go b/context.go index ab05336ec1..676a69bfd9 100644 --- a/context.go +++ b/context.go @@ -34,15 +34,16 @@ type Ctx struct { error error params *[]string values []string + compress bool Fasthttp *fasthttp.RequestCtx } -// RangeInfo info of range header -type RangeInfo struct { +// Range info of range header +type Range struct { Type string Ranges []struct { - Start int64 - End int64 + Start int + End int } } @@ -54,9 +55,8 @@ var poolCtx = sync.Pool{ } // Acquire Ctx from pool -func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx { +func acquireCtx() *Ctx { ctx := poolCtx.Get().(*Ctx) - ctx.Fasthttp = fctx return ctx } @@ -67,6 +67,7 @@ func releaseCtx(ctx *Ctx) { ctx.error = nil ctx.params = nil ctx.values = nil + ctx.compress = false ctx.Fasthttp = nil poolCtx.Put(ctx) } @@ -112,7 +113,7 @@ func (ctx *Ctx) Accepts(offers ...string) (offer string) { if len(offers) == 0 { return "" } - h := ctx.Get(fasthttp.HeaderAccept) + h := ctx.Get(HeaderAccept) if h == "" { return offers[0] } @@ -151,7 +152,7 @@ func (ctx *Ctx) AcceptsCharsets(offers ...string) (offer string) { return "" } - h := ctx.Get(fasthttp.HeaderAcceptCharset) + h := ctx.Get(HeaderAcceptCharset) if h == "" { return offers[0] } @@ -177,7 +178,7 @@ func (ctx *Ctx) AcceptsEncodings(offers ...string) (offer string) { return "" } - h := ctx.Get(fasthttp.HeaderAcceptEncoding) + h := ctx.Get(HeaderAcceptEncoding) if h == "" { return offers[0] } @@ -202,7 +203,7 @@ func (ctx *Ctx) AcceptsLanguages(offers ...string) (offer string) { if len(offers) == 0 { return "" } - h := ctx.Get(fasthttp.HeaderAcceptLanguage) + h := ctx.Get(HeaderAcceptLanguage) if h == "" { return offers[0] } @@ -243,10 +244,10 @@ func (ctx *Ctx) Attachment(name ...string) { if len(name) > 0 { filename := filepath.Base(name[0]) ctx.Type(filepath.Ext(filename)) - ctx.Set(fasthttp.HeaderContentDisposition, `attachment; filename="`+filename+`"`) + ctx.Set(HeaderContentDisposition, `attachment; filename="`+filename+`"`) return } - ctx.Set(fasthttp.HeaderContentDisposition, "attachment") + ctx.Set(HeaderContentDisposition, "attachment") } // BaseURL : https://fiber.wiki/context#baseurl @@ -314,6 +315,14 @@ func (ctx *Ctx) ClearCookie(key ...string) { }) } +// Compress : https://fiber.wiki/context#compress +func (ctx *Ctx) Compress(enable ...bool) { + ctx.compress = true + if len(enable) > 0 { + ctx.compress = enable[0] + } +} + // Cookie : https://fiber.wiki/context#cookie func (ctx *Ctx) Cookie(cookie *Cookie) { fcookie := &fasthttp.Cookie{} @@ -330,7 +339,7 @@ func (ctx *Ctx) Cookie(cookie *Cookie) { // Cookies : https://fiber.wiki/context#cookies func (ctx *Ctx) Cookies(key ...string) (value string) { if len(key) == 0 { - return ctx.Get(fasthttp.HeaderCookie) + return ctx.Get(HeaderCookie) } return getString(ctx.Fasthttp.Request.Header.Cookie(key[0])) } @@ -343,7 +352,7 @@ func (ctx *Ctx) Download(file string, name ...string) { filename = name[0] } - ctx.Set(fasthttp.HeaderContentDisposition, "attachment; filename="+filename) + ctx.Set(HeaderContentDisposition, "attachment; filename="+filename) ctx.SendFile(file) } @@ -412,7 +421,7 @@ func (ctx *Ctx) IP() string { // IPs : https://fiber.wiki/context#ips func (ctx *Ctx) IPs() []string { - ips := strings.Split(ctx.Get(fasthttp.HeaderXForwardedFor), ",") + ips := strings.Split(ctx.Get(HeaderXForwardedFor), ",") for i := range ips { ips[i] = strings.TrimSpace(ips[i]) } @@ -425,7 +434,7 @@ func (ctx *Ctx) Is(extension string) (match bool) { extension = "." + extension } - exts, _ := mime.ExtensionsByType(ctx.Get(fasthttp.HeaderContentType)) + exts, _ := mime.ExtensionsByType(ctx.Get(HeaderContentType)) if len(exts) > 0 { for _, item := range exts { if item == extension { @@ -462,7 +471,7 @@ func (ctx *Ctx) JSONP(json interface{}, callback ...string) error { } str += getString(raw) + ");" - ctx.Set(fasthttp.HeaderXContentTypeOptions, "nosniff") + ctx.Set(HeaderXContentTypeOptions, "nosniff") ctx.Fasthttp.Response.Header.SetContentType(MIMEApplicationJavaScript) ctx.Fasthttp.Response.SetBodyString(str) @@ -482,7 +491,7 @@ func (ctx *Ctx) Links(link ...string) { if len(link) > 0 { h = strings.TrimSuffix(h, ",") - ctx.Set(fasthttp.HeaderLink, h) + ctx.Set(HeaderLink, h) } } @@ -497,7 +506,7 @@ func (ctx *Ctx) Locals(key string, value ...interface{}) (val interface{}) { // Location : https://fiber.wiki/context#location func (ctx *Ctx) Location(path string) { - ctx.Set(fasthttp.HeaderLocation, path) + ctx.Set(HeaderLocation, path) } // Method : https://fiber.wiki/context#method @@ -558,21 +567,21 @@ func (ctx *Ctx) Query(key string) (value string) { } // Range : https://fiber.wiki/context#range -func (ctx *Ctx) Range(size int64) (rangeInfo RangeInfo, err error) { +func (ctx *Ctx) Range(size int) (rangeData Range, err error) { rangeStr := string(ctx.Fasthttp.Request.Header.Peek("range")) if rangeStr == "" || !strings.Contains(rangeStr, "=") { - return rangeInfo, fmt.Errorf("malformed range header string") + return rangeData, fmt.Errorf("malformed range header string") } data := strings.Split(rangeStr, "=") - rangeInfo.Type = data[0] + rangeData.Type = data[0] arr := strings.Split(data[1], ",") for i := 0; i < len(arr); i++ { item := strings.Split(arr[i], "-") if len(item) == 1 { - return rangeInfo, fmt.Errorf("malformed range header string") + return rangeData, fmt.Errorf("malformed range header string") } - start, startErr := strconv.ParseInt(item[0], 10, 64) - end, endErr := strconv.ParseInt(item[1], 10, 64) + start, startErr := strconv.Atoi(item[0]) + end, endErr := strconv.Atoi(item[1]) if startErr != nil { // -nnn start = size - end end = size - 1 @@ -585,18 +594,18 @@ func (ctx *Ctx) Range(size int64) (rangeInfo RangeInfo, err error) { if start > end || start < 0 { continue } - rangeInfo.Ranges = append(rangeInfo.Ranges, struct { - Start int64 - End int64 + rangeData.Ranges = append(rangeData.Ranges, struct { + Start int + End int }{ start, end, }) } - if len(rangeInfo.Ranges) < 1 { - return rangeInfo, fmt.Errorf("unsatisfiable range") + if len(rangeData.Ranges) < 1 { + return rangeData, fmt.Errorf("unsatisfiable range") } - return rangeInfo, nil + return rangeData, nil } // Redirect : https://fiber.wiki/context#redirect @@ -606,7 +615,7 @@ func (ctx *Ctx) Redirect(path string, status ...int) { code = status[0] } - ctx.Set(fasthttp.HeaderLocation, path) + ctx.Set(HeaderLocation, path) ctx.Fasthttp.Response.SetStatusCode(code) } @@ -764,7 +773,7 @@ func (ctx *Ctx) Vary(fields ...string) { return } - h := getString(ctx.Fasthttp.Response.Header.Peek(fasthttp.HeaderVary)) + h := getString(ctx.Fasthttp.Response.Header.Peek(HeaderVary)) for i := range fields { if h == "" { h += fields[i] @@ -773,7 +782,7 @@ func (ctx *Ctx) Vary(fields ...string) { } } - ctx.Set(fasthttp.HeaderVary, h) + ctx.Set(HeaderVary, h) } // Write : https://fiber.wiki/context#write @@ -792,5 +801,5 @@ func (ctx *Ctx) Write(bodies ...interface{}) { // XHR : https://fiber.wiki/context#xhr func (ctx *Ctx) XHR() bool { - return ctx.Get(fasthttp.HeaderXRequestedWith) == "XMLHttpRequest" + return ctx.Get(HeaderXRequestedWith) == "XMLHttpRequest" } diff --git a/context_test.go b/context_test.go index 7be2327844..08c2af3412 100644 --- a/context_test.go +++ b/context_test.go @@ -552,11 +552,11 @@ func Test_Range(t *testing.T) { if result.Type != expect { t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result.Type) } - expectNum := int64(500) + expectNum := 500 if result.Ranges[0].Start != expectNum { t.Fatalf(`%s: Expecting %v, got %v`, t.Name(), expectNum, result.Ranges[0].Start) } - expectNum = int64(700) + expectNum = 700 if result.Ranges[0].End != expectNum { t.Fatalf(`%s: Expecting %v, got %v`, t.Name(), expectNum, result.Ranges[0].End) } diff --git a/go.mod b/go.mod index 61666ddaae..7c2bf8f258 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.11 require ( github.com/fasthttp/websocket v1.4.2 github.com/gofiber/template v1.0.0 + github.com/google/uuid v1.1.1 github.com/gorilla/schema v1.1.0 github.com/json-iterator/go v1.1.9 github.com/valyala/fasthttp v1.9.0 + github.com/valyala/fasttemplate v1.1.0 ) diff --git a/go.sum b/go.sum index ae4af7745f..425a50ac69 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/fasthttp/websocket v1.4.2/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0Ff github.com/gofiber/template v1.0.0 h1:Vf4Fby9zUWVQyY2y69KKyRHsEYlIE+Pxb25M+jiaEL0= github.com/gofiber/template v1.0.0/go.mod h1:+bij+R0NI6urTg2jtQvPj5wb2uWMxW9eYGsAN3QhnP0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= @@ -35,6 +37,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw= github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go new file mode 100644 index 0000000000..c0db0b07ab --- /dev/null +++ b/middleware/basic_auth.go @@ -0,0 +1,78 @@ +package middleware + +import ( + "encoding/base64" + "strings" + + "github.com/gofiber/fiber" +) + +// BasicAuthConfig defines the config for BasicAuth middleware +type BasicAuthConfig struct { + // Skip defines a function to skip middleware. + // Optional. Default: nil + Skip func(*fiber.Ctx) bool + // Users defines the allowed credentials + // Required. Default: map[string]string{} + Users map[string]string + // Realm is a string to define realm attribute of BasicAuth. + // Optional. Default: "Restricted". + Realm string +} + +// BasicAuthConfigDefault is the default BasicAuth middleware config. +var BasicAuthConfigDefault = BasicAuthConfig{ + Skip: nil, + Users: map[string]string{}, + Realm: "Restricted", +} + +// BasicAuth ... +func BasicAuth(config ...BasicAuthConfig) func(*fiber.Ctx) { + // Init config + var cfg BasicAuthConfig + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if cfg.Users == nil { + cfg.Users = BasicAuthConfigDefault.Users + } + if cfg.Realm == "" { + cfg.Realm = BasicAuthConfigDefault.Realm + } + // Return middleware handler + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + // Get authorization header + auth := c.Get(fiber.HeaderAuthorization) + // Check if characters are provided + if len(auth) > 6 && strings.ToLower(auth[:5]) == "basic" { + // Try to decode + if raw, err := base64.StdEncoding.DecodeString(auth[6:]); err == nil { + // Convert to string + cred := string(raw) + // Find semicolumn + for i := 0; i < len(cred); i++ { + if cred[i] == ':' { + // Split into user & pass + user := cred[:i] + pass := cred[i+1:] + // If exist & match in Users, we let him pass + if cfg.Users[user] == pass { + c.Next() + return + } + } + } + } + } + // Authentication required + c.Set(fiber.HeaderWWWAuthenticate, "basic realm="+cfg.Realm) + c.SendStatus(401) + } +} diff --git a/middleware/cors.go b/middleware/cors.go new file mode 100644 index 0000000000..a10cbc1ba0 --- /dev/null +++ b/middleware/cors.go @@ -0,0 +1,172 @@ +package middleware + +import ( + "net/http" + "strconv" + "strings" + + "github.com/gofiber/fiber" +) + +// CORSConfig ... +type CORSConfig struct { + Skip func(*fiber.Ctx) bool + // Optional. Default value []string{"*"}. + AllowOrigins []string + // Optional. Default value []string{"GET","POST","HEAD","PUT","DELETE","PATCH"} + AllowMethods []string + // Optional. Default value []string{}. + AllowHeaders []string + // Optional. Default value false. + AllowCredentials bool + // Optional. Default value []string{}. + ExposeHeaders []string + // Optional. Default value 0. + MaxAge int +} + +// CorsConfigDefault is the defaul Cors middleware config. +var CorsConfigDefault = CORSConfig{ + Skip: nil, + AllowOrigins: []string{"*"}, + AllowMethods: []string{ + http.MethodGet, + http.MethodPost, + http.MethodHead, + http.MethodPut, + http.MethodDelete, + http.MethodPatch, + }, +} + +// Cors ... +func Cors(config ...CORSConfig) func(*fiber.Ctx) { + // Init config + var cfg CORSConfig + // Set config if provided + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if len(cfg.AllowOrigins) == 0 { + cfg.AllowOrigins = CorsConfigDefault.AllowOrigins + } + if len(cfg.AllowMethods) == 0 { + cfg.AllowMethods = CorsConfigDefault.AllowMethods + } + // Middleware settings + allowMethods := strings.Join(cfg.AllowMethods, ",") + allowHeaders := strings.Join(cfg.AllowHeaders, ",") + exposeHeaders := strings.Join(cfg.ExposeHeaders, ",") + maxAge := strconv.Itoa(cfg.MaxAge) + // Middleware function + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + origin := c.Get(fiber.HeaderOrigin) + allowOrigin := "" + // Check allowed origins + for _, o := range cfg.AllowOrigins { + if o == "*" && cfg.AllowCredentials { + allowOrigin = origin + break + } + if o == "*" || o == origin { + allowOrigin = o + break + } + if matchSubdomain(origin, o) { + allowOrigin = origin + break + } + } + // Simple request + if c.Method() != http.MethodOptions { + c.Vary(fiber.HeaderOrigin) + c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) + + if cfg.AllowCredentials { + c.Set(fiber.HeaderAccessControlAllowCredentials, "true") + } + if exposeHeaders != "" { + c.Set(fiber.HeaderAccessControlExposeHeaders, exposeHeaders) + } + c.Next() + return + } + // Preflight request + c.Vary(fiber.HeaderOrigin) + c.Vary(fiber.HeaderAccessControlRequestMethod) + c.Vary(fiber.HeaderAccessControlRequestHeaders) + c.Set(fiber.HeaderAccessControlAllowOrigin, allowOrigin) + c.Set(fiber.HeaderAccessControlAllowMethods, allowMethods) + + if cfg.AllowCredentials { + c.Set(fiber.HeaderAccessControlAllowCredentials, "true") + } + if allowHeaders != "" { + c.Set(fiber.HeaderAccessControlAllowHeaders, allowHeaders) + } else { + h := c.Get(fiber.HeaderAccessControlRequestHeaders) + if h != "" { + c.Set(fiber.HeaderAccessControlAllowHeaders, h) + } + } + if cfg.MaxAge > 0 { + c.Set(fiber.HeaderAccessControlMaxAge, maxAge) + } + c.SendStatus(204) // No Content + } +} + +func matchScheme(domain, pattern string) bool { + didx := strings.Index(domain, ":") + pidx := strings.Index(pattern, ":") + return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx] +} + +// matchSubdomain compares authority with wildcard +func matchSubdomain(domain, pattern string) bool { + if !matchScheme(domain, pattern) { + return false + } + didx := strings.Index(domain, "://") + pidx := strings.Index(pattern, "://") + if didx == -1 || pidx == -1 { + return false + } + domAuth := domain[didx+3:] + // to avoid long loop by invalid long domain + if len(domAuth) > 253 { + return false + } + patAuth := pattern[pidx+3:] + + domComp := strings.Split(domAuth, ".") + patComp := strings.Split(patAuth, ".") + for i := len(domComp)/2 - 1; i >= 0; i-- { + opp := len(domComp) - 1 - i + domComp[i], domComp[opp] = domComp[opp], domComp[i] + } + for i := len(patComp)/2 - 1; i >= 0; i-- { + opp := len(patComp) - 1 - i + patComp[i], patComp[opp] = patComp[opp], patComp[i] + } + + for i, v := range domComp { + if len(patComp) <= i { + return false + } + p := patComp[i] + if p == "*" { + return true + } + if p != v { + return false + } + } + return false +} diff --git a/middleware/helmet.go b/middleware/helmet.go new file mode 100644 index 0000000000..d92d1b89ce --- /dev/null +++ b/middleware/helmet.go @@ -0,0 +1,107 @@ +package middleware + +import ( + "fmt" + + "github.com/gofiber/fiber" +) + +// HelmetConfig ... +type HelmetConfig struct { + // Skip defines a function to skip middleware. + // Optional. Default: nil + Skip func(*fiber.Ctx) bool + // XSSProtection + // Optional. Default value "1; mode=block". + XSSProtection string + // ContentTypeNosniff + // Optional. Default value "nosniff". + ContentTypeNosniff string + // XFrameOptions + // Optional. Default value "SAMEORIGIN". + // Possible values: "SAMEORIGIN", "DENY", "ALLOW-FROM uri" + XFrameOptions string + // HSTSMaxAge + // Optional. Default value 0. + HSTSMaxAge int + // HSTSExcludeSubdomains + // Optional. Default value false. + HSTSExcludeSubdomains bool + // ContentSecurityPolicy + // Optional. Default value "". + ContentSecurityPolicy string + // CSPReportOnly + // Optional. Default value false. + CSPReportOnly bool + // HSTSPreloadEnabled + // Optional. Default value false. + HSTSPreloadEnabled bool + // ReferrerPolicy + // Optional. Default value "". + ReferrerPolicy string +} + +// HelmetConfigDefault is the defaul Helmet middleware config. +var HelmetConfigDefault = HelmetConfig{ + Skip: nil, + XSSProtection: "1; mode=block", + ContentTypeNosniff: "nosniff", + XFrameOptions: "SAMEORIGIN", +} + +// Secure ... +func Secure(config ...HelmetConfig) func(*fiber.Ctx) { + // Init config + var cfg HelmetConfig + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if cfg.XSSProtection == "" { + cfg.XSSProtection = HelmetConfigDefault.XSSProtection + } + if cfg.ContentTypeNosniff == "" { + cfg.ContentTypeNosniff = HelmetConfigDefault.ContentTypeNosniff + } + if cfg.XFrameOptions == "" { + cfg.XFrameOptions = HelmetConfigDefault.XFrameOptions + } + // Return middleware handler + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + if cfg.XSSProtection != "" { + c.Set(fiber.HeaderXXSSProtection, cfg.XSSProtection) + } + if cfg.ContentTypeNosniff != "" { + c.Set(fiber.HeaderXContentTypeOptions, cfg.ContentTypeNosniff) + } + if cfg.XFrameOptions != "" { + c.Set(fiber.HeaderXFrameOptions, cfg.XFrameOptions) + } + if (c.Secure() || (c.Get(fiber.HeaderXForwardedProto) == "https")) && cfg.HSTSMaxAge != 0 { + subdomains := "" + if !cfg.HSTSExcludeSubdomains { + subdomains = "; includeSubdomains" + } + if cfg.HSTSPreloadEnabled { + subdomains = fmt.Sprintf("%s; preload", subdomains) + } + c.Set(fiber.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", cfg.HSTSMaxAge, subdomains)) + } + if cfg.ContentSecurityPolicy != "" { + if cfg.CSPReportOnly { + c.Set(fiber.HeaderContentSecurityPolicyReportOnly, cfg.ContentSecurityPolicy) + } else { + c.Set(fiber.HeaderContentSecurityPolicy, cfg.ContentSecurityPolicy) + } + } + if cfg.ReferrerPolicy != "" { + c.Set(fiber.HeaderReferrerPolicy, cfg.ReferrerPolicy) + } + c.Next() + } +} diff --git a/middleware/limiter.go b/middleware/limiter.go new file mode 100644 index 0000000000..1e3c8873c6 --- /dev/null +++ b/middleware/limiter.go @@ -0,0 +1,141 @@ +package middleware + +import ( + "strconv" + "time" + + "github.com/gofiber/fiber" +) + +// LimiterConfig ... +type LimiterConfig struct { + Skip func(*fiber.Ctx) bool + // Timeout in seconds on how long to keep records of requests in memory + // Default: 60 + Timeout int + // Max number of recent connections during `Timeout` seconds before sending a 429 response + // Default: 10 + Max int + // Message + // default: "Too many requests, please try again later." + Message string + // StatusCode + // Default: 429 Too Many Requests + StatusCode int + // Key allows to use a custom handler to create custom keys + // Default: func(c *fiber.Ctx) string { + // return c.IP() + // } + Key func(*fiber.Ctx) string + // Handler is called when a request hits the limit + // Default: func(c *fiber.Ctx) { + // c.Status(cfg.StatusCode).SendString(cfg.Message) + // } + Handler func(*fiber.Ctx) +} + +// LimiterConfigDefault is the defaul Limiter middleware config. +var LimiterConfigDefault = LimiterConfig{ + Skip: nil, + Timeout: 60, + Max: 10, + Message: "Too many requests, please try again later.", + StatusCode: 429, + Key: func(c *fiber.Ctx) string { + return c.IP() + }, +} + +// Limiter ... +func Limiter(config ...LimiterConfig) func(*fiber.Ctx) { + // Init config + var cfg LimiterConfig + // Set config if provided + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if cfg.Timeout == 0 { + cfg.Timeout = LimiterConfigDefault.Timeout + } + if cfg.Max == 0 { + cfg.Max = LimiterConfigDefault.Max + } + if cfg.Message == "" { + cfg.Message = LimiterConfigDefault.Message + } + if cfg.StatusCode == 0 { + cfg.StatusCode = LimiterConfigDefault.StatusCode + } + if cfg.Key == nil { + cfg.Key = LimiterConfigDefault.Key + } + if cfg.Handler == nil { + cfg.Handler = func(c *fiber.Ctx) { + c.Status(cfg.StatusCode).SendString(cfg.Message) + } + } + // Limiter settings + var hits = map[string]int{} + var reset = map[string]int{} + var timestamp = int(time.Now().Unix()) + // Update timestamp every second + go func() { + for { + timestamp = int(time.Now().Unix()) + time.Sleep(1 * time.Second) + } + }() + // Reset hits every cfg.Timeout + go func() { + for { + // For every key in reset + for key := range reset { + // If resetTime exist and current time is equal or bigger + if reset[key] != 0 && timestamp >= reset[key] { + // Reset hits and resetTime + hits[key] = 0 + reset[key] = 0 + } + } + // Wait cfg.Timeout + time.Sleep(time.Duration(cfg.Timeout) * time.Second) + } + }() + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + // Get key (default is the remote IP) + key := cfg.Key(c) + // Increment key hits + hits[key]++ + // Set unix timestamp if not exist + if reset[key] == 0 { + reset[key] = timestamp + cfg.Timeout + } + // Get current hits + hitCount := hits[key] + // Set how many hits we have left + remaining := cfg.Max - hitCount + // Calculate when it resets in seconds + resetTime := reset[key] - timestamp + // Check if hits exceed the cfg.Max + if remaining < 1 { + // Call Handler func + cfg.Handler(c) + // Return response with Retry-After header + // https://tools.ietf.org/html/rfc6584 + c.Set("Retry-After", strconv.Itoa(resetTime)) + return + } + // We can continue, update RateLimit headers + c.Set("X-RateLimit-Limit", strconv.Itoa(cfg.Max)) + c.Set("X-RateLimit-Remaining", strconv.Itoa(remaining)) + c.Set("X-RateLimit-Reset", strconv.Itoa(resetTime)) + // Bye! + c.Next() + } +} diff --git a/middleware/logger.go b/middleware/logger.go new file mode 100644 index 0000000000..4f2aca7099 --- /dev/null +++ b/middleware/logger.go @@ -0,0 +1,129 @@ +package middleware + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/gofiber/fiber" + "github.com/valyala/fasttemplate" +) + +// LoggerConfig ... +type LoggerConfig struct { + // Skip defines a function to skip middleware. + // Optional. Default: nil + Skip func(*fiber.Ctx) bool + // Format defines the logging format with defined variables + // Optional. Default: "${time} - ${ip} - ${method} ${path}\t${ua}\n" + // Possible values: time, ip, url, host, method, path, protocol + // referer, ua, header:, query:, formform:, cookie: + Format string + // TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html + // Optional. Default: 15:04:05 + TimeFormat string + // Output is a writter where logs are written + // Default: os.Stderr + Output io.Writer +} + +// LoggerConfigDefault is the defaul Logger middleware config. +var LoggerConfigDefault = LoggerConfig{ + Skip: nil, + Format: "${time} - ${ip} - ${method} ${path}\t${ua}\n", + TimeFormat: "15:04:05", + Output: os.Stderr, +} + +// Logger ... +func Logger(config ...LoggerConfig) func(*fiber.Ctx) { + // Init config + var cfg LoggerConfig + // Set config if provided + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if cfg.Format == "" { + cfg.Format = LoggerConfigDefault.Format + } + if cfg.TimeFormat == "" { + cfg.TimeFormat = LoggerConfigDefault.TimeFormat + } + if cfg.Output == nil { + cfg.Output = LoggerConfigDefault.Output + } + // Middleware settings + tmpl := fasttemplate.New(cfg.Format, "${", "}") + pool := &sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 256)) + }, + } + timestamp := time.Now().Format(cfg.TimeFormat) + // Update date/time every second in a seperate go routine + if strings.Contains(cfg.Format, "${time}") { + go func() { + for { + timestamp = time.Now().Format(cfg.TimeFormat) + time.Sleep(1 * time.Second) + } + }() + } + // Middleware function + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + buf := pool.Get().(*bytes.Buffer) + buf.Reset() + defer pool.Put(buf) + _, err := tmpl.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { + switch tag { + case "time": + return buf.WriteString(timestamp) + case "referer": + return buf.WriteString(c.Get(fiber.HeaderReferer)) + case "protocol": + return buf.WriteString(c.Protocol()) + case "ip": + return buf.WriteString(c.IP()) + case "host": + return buf.WriteString(c.Hostname()) + case "method": + return buf.WriteString(c.Method()) + case "path": + return buf.WriteString(c.Path()) + case "url": + return buf.WriteString(c.OriginalURL()) + case "ua": + return buf.WriteString(c.Get(fiber.HeaderUserAgent)) + default: + switch { + case strings.HasPrefix(tag, "header:"): + return buf.WriteString(c.Get(tag[7:])) + case strings.HasPrefix(tag, "query:"): + return buf.WriteString(c.Query(tag[6:])) + case strings.HasPrefix(tag, "form:"): + return buf.WriteString(c.FormValue(tag[5:])) + case strings.HasPrefix(tag, "cookie:"): + return buf.WriteString(c.Cookies(tag[7:])) + } + } + return 0, nil + }) + if err != nil { + buf.WriteString(err.Error()) + } + if _, err := cfg.Output.Write(buf.Bytes()); err != nil { + fmt.Println(err) + } + c.Next() + } +} diff --git a/middleware/request_id.go b/middleware/request_id.go new file mode 100644 index 0000000000..481e32f27a --- /dev/null +++ b/middleware/request_id.go @@ -0,0 +1,63 @@ +package middleware + +import ( + "fmt" + + "github.com/gofiber/fiber" + "github.com/google/uuid" +) + +// RequestIDConfig defines the config for RequestID middleware +type RequestIDConfig struct { + // Skip defines a function to skip middleware. + // Optional. Default: nil + Skip func(*fiber.Ctx) bool + // Generator defines a function to generate an ID. + // Optional. Default: func() string { + // return uuid.New().String() + // } + Generator func() string +} + +// RequestIDConfigDefault is the default RequestID middleware config. +var RequestIDConfigDefault = RequestIDConfig{ + Skip: nil, + Generator: func() string { + return uuid.New().String() + }, +} + +// RequestID adds an indentifier to the request using the `X-Request-ID` header +func RequestID(config ...RequestIDConfig) func(*fiber.Ctx) { + // Init config + var cfg RequestIDConfig + if len(config) > 0 { + cfg = config[0] + } + // Set config default values + if cfg.Generator == nil { + cfg.Skip = RequestIDConfigDefault.Skip + } + if cfg.Generator == nil { + cfg.Generator = RequestIDConfigDefault.Generator + } + // Return middleware handler + return func(c *fiber.Ctx) { + // Skip middleware if Skip returns true + if cfg.Skip != nil && cfg.Skip(c) { + c.Next() + return + } + // Get value from RequestID + rid := c.Get(fiber.HeaderXRequestID) + fmt.Println(rid) + // Create new ID + if rid == "" { + rid = cfg.Generator() + } + // Set X-Request-ID + c.Set(fiber.HeaderXRequestID, rid) + // Bye! + c.Next() + } +} diff --git a/router.go b/router.go index 2a9656dfc7..2cd26692d0 100644 --- a/router.go +++ b/router.go @@ -257,11 +257,11 @@ func (app *App) handler(fctx *fasthttp.RequestCtx) { // Use this boolean to perform 404 not found at the end var match = false // get custom context from sync pool - ctx := acquireCtx(fctx) + ctx := acquireCtx() defer releaseCtx(ctx) - if ctx.app == nil { - ctx.app = app - } + ctx.app = app + ctx.compress = app.Settings.Compression + ctx.Fasthttp = fctx // get path and method path := ctx.Path() if !app.Settings.CaseSensitive { @@ -398,7 +398,21 @@ func (app *App) handler(fctx *fasthttp.RequestCtx) { // set next to false for next iteration ctx.next = false } - // No match, send default 404 + // Do we need to compress? + if ctx.compress { + compressDefaultCompression(fctx) + // switch app.Settings.CompressionLevel { + // case 2: + // compressBestSpeed(fctx) + // case 3: + // compressBestCompression(fctx) + // case 4: + // compressHuffmanOnly(fctx) + // default: // 1 + // compressDefaultCompression(fctx) + // } + } + // No match, send default 404 Not Found if !match { ctx.SendStatus(404) } diff --git a/utils.go b/utils.go index 4d8a641b3c..ba980e06c5 100644 --- a/utils.go +++ b/utils.go @@ -20,6 +20,11 @@ import ( fasthttp "github.com/valyala/fasthttp" ) +var compressDefaultCompression = fasthttp.CompressHandlerLevel(func(c *fasthttp.RequestCtx) {}, fasthttp.CompressDefaultCompression) + +// var compressBestSpeed = fasthttp.CompressHandlerLevel(func(c *fasthttp.RequestCtx) {}, fasthttp.CompressBestSpeed) +// var compressBestCompression = fasthttp.CompressHandlerLevel(func(c *fasthttp.RequestCtx) {}, fasthttp.CompressBestCompression) +// var compressHuffmanOnly = fasthttp.CompressHandlerLevel(func(c *fasthttp.RequestCtx) {}, fasthttp.CompressHuffmanOnly) var schemaDecoder = schema.NewDecoder() var socketUpgrade = websocket.FastHTTPUpgrader{ ReadBufferSize: 1024, @@ -329,3 +334,167 @@ var extensionMIME = map[string]string{ "wmv": "video/x-ms-wmv", "avi": "video/x-msvideo", } + +// HTTP Headers +const ( + // Authentication + HeaderAuthorization = "Authorization" + HeaderProxyAuthenticate = "Proxy-Authenticate" + HeaderProxyAuthorization = "Proxy-Authorization" + HeaderWWWAuthenticate = "WWW-Authenticate" + + // Caching + HeaderAge = "Age" + HeaderCacheControl = "Cache-Control" + HeaderClearSiteData = "Clear-Site-Data" + HeaderExpires = "Expires" + HeaderPragma = "Pragma" + HeaderWarning = "Warning" + + // Client hints + HeaderAcceptCH = "Accept-CH" + HeaderAcceptCHLifetime = "Accept-CH-Lifetime" + HeaderContentDPR = "Content-DPR" + HeaderDPR = "DPR" + HeaderEarlyData = "Early-Data" + HeaderSaveData = "Save-Data" + HeaderViewportWidth = "Viewport-Width" + HeaderWidth = "Width" + + // Conditionals + HeaderETag = "ETag" + HeaderIfMatch = "If-Match" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderIfNoneMatch = "If-None-Match" + HeaderIfUnmodifiedSince = "If-Unmodified-Since" + HeaderLastModified = "Last-Modified" + HeaderVary = "Vary" + + // Connection management + HeaderConnection = "Connection" + HeaderKeepAlive = "Keep-Alive" + + // Content negotiation + HeaderAccept = "Accept" + HeaderAcceptCharset = "Accept-Charset" + HeaderAcceptEncoding = "Accept-Encoding" + HeaderAcceptLanguage = "Accept-Language" + + // Controls + HeaderCookie = "Cookie" + HeaderExpect = "Expect" + HeaderMaxForwards = "Max-Forwards" + HeaderSetCookie = "Set-Cookie" + + // CORS + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge = "Access-Control-Max-Age" + HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" + HeaderAccessControlRequestMethod = "Access-Control-Request-Method" + HeaderOrigin = "Origin" + HeaderTimingAllowOrigin = "Timing-Allow-Origin" + HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" + + // Do Not Track + HeaderDNT = "DNT" + HeaderTk = "Tk" + + // Downloads + HeaderContentDisposition = "Content-Disposition" + + // Message body information + HeaderContentEncoding = "Content-Encoding" + HeaderContentLanguage = "Content-Language" + HeaderContentLength = "Content-Length" + HeaderContentLocation = "Content-Location" + HeaderContentType = "Content-Type" + + // Proxies + HeaderForwarded = "Forwarded" + HeaderVia = "Via" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedHost = "X-Forwarded-Host" + HeaderXForwardedProto = "X-Forwarded-Proto" + + // Redirects + HeaderLocation = "Location" + + // Request context + HeaderFrom = "From" + HeaderHost = "Host" + HeaderReferer = "Referer" + HeaderReferrerPolicy = "Referrer-Policy" + HeaderUserAgent = "User-Agent" + + // Response context + HeaderAllow = "Allow" + HeaderServer = "Server" + + // Range requests + HeaderAcceptRanges = "Accept-Ranges" + HeaderContentRange = "Content-Range" + HeaderIfRange = "If-Range" + HeaderRange = "Range" + + // Security + HeaderContentSecurityPolicy = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" + HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" + HeaderExpectCT = "Expect-CT" + HeaderFeaturePolicy = "Feature-Policy" + HeaderPublicKeyPins = "Public-Key-Pins" + HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" + HeaderStrictTransportSecurity = "Strict-Transport-Security" + HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" + HeaderXContentTypeOptions = "X-Content-Type-Options" + HeaderXDownloadOptions = "X-Download-Options" + HeaderXFrameOptions = "X-Frame-Options" + HeaderXPoweredBy = "X-Powered-By" + HeaderXXSSProtection = "X-XSS-Protection" + + // Server-sent event + HeaderLastEventID = "Last-Event-ID" + HeaderNEL = "NEL" + HeaderPingFrom = "Ping-From" + HeaderPingTo = "Ping-To" + HeaderReportTo = "Report-To" + + // Transfer coding + HeaderTE = "TE" + HeaderTrailer = "Trailer" + HeaderTransferEncoding = "Transfer-Encoding" + + // WebSockets + HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" + HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" + HeaderSecWebSocketKey = "Sec-WebSocket-Key" + HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" + HeaderSecWebSocketVersion = "Sec-WebSocket-Version" + + // Other + HeaderAcceptPatch = "Accept-Patch" + HeaderAcceptPushPolicy = "Accept-Push-Policy" + HeaderAcceptSignature = "Accept-Signature" + HeaderAltSvc = "Alt-Svc" + HeaderDate = "Date" + HeaderIndex = "Index" + HeaderLargeAllocation = "Large-Allocation" + HeaderLink = "Link" + HeaderPushPolicy = "Push-Policy" + HeaderRetryAfter = "Retry-After" + HeaderServerTiming = "Server-Timing" + HeaderSignature = "Signature" + HeaderSignedHeaders = "Signed-Headers" + HeaderSourceMap = "SourceMap" + HeaderUpgrade = "Upgrade" + HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" + HeaderXPingback = "X-Pingback" + HeaderXRequestID = "X-Request-ID" + HeaderXRequestedWith = "X-Requested-With" + HeaderXRobotsTag = "X-Robots-Tag" + HeaderXUACompatible = "X-UA-Compatible" +)