├── testdata └── test.png ├── examples ├── fileserver │ ├── .gitignore │ ├── Makefile │ ├── ssl-cert-snakeoil.pem │ ├── ssl-cert-snakeoil.key │ ├── README.md │ └── fileserver.go ├── helloworldserver │ ├── .gitignore │ ├── Makefile │ ├── README.md │ └── helloworldserver.go ├── README.md └── multidomain │ ├── Makefile │ ├── README.md │ └── multidomain.go ├── .gitignore ├── fasthttputil ├── doc.go ├── ecdsa.key ├── ecdsa.pem ├── rsa.pem ├── rsa.key ├── inmemory_listener.go ├── inmemory_listener_test.go ├── inmemory_listener_timing_test.go ├── pipeconns.go └── pipeconns_test.go ├── bytesconv_32.go ├── bytesconv_64.go ├── stackless ├── doc.go ├── func_timing_test.go ├── func.go ├── func_test.go ├── writer.go └── writer_test.go ├── TODO ├── .github └── workflows │ └── lint.yml ├── uri_unix.go ├── uri_windows.go ├── reuseport ├── reuseport_windows.go ├── reuseport_error.go ├── reuseport_windows_test.go ├── reuseport_example_test.go ├── LICENSE ├── reuseport.go └── reuseport_test.go ├── uri_windows_test.go ├── go.mod ├── coarseTime.go ├── fuzzit ├── url │ └── url_fuzz.go ├── cookie │ └── cookie_fuzz.go ├── request │ └── request_fuzz.go └── response │ └── response_fuzz.go ├── nocopy.go ├── methods.go ├── args_timing_test.go ├── fs_example_test.go ├── fuzzit.sh ├── coarseTime_test.go ├── requestctx_setbodystreamwriter_example_test.go ├── fasthttpproxy └── socks5.go ├── cookie_timing_test.go ├── ssl-cert-snakeoil.pem ├── lbclient_example_test.go ├── bytesconv_32_test.go ├── LICENSE ├── userdata_timing_test.go ├── client_example_test.go ├── go.sum ├── bytesconv_64_test.go ├── fs_handler_example_test.go ├── uri_timing_test.go ├── stream.go ├── peripconn_test.go ├── userdata.go ├── timer.go ├── doc.go ├── pprofhandler └── pprof.go ├── stream_timing_test.go ├── expvarhandler ├── expvar.go └── expvar_test.go ├── userdata_test.go ├── ssl-cert-snakeoil.key ├── .travis.yml ├── allocation_test.go ├── peripconn.go ├── prefork ├── README.md ├── prefork.go └── prefork_test.go ├── stream_test.go ├── header_regression_test.go ├── bytesconv_table_gen.go ├── strings.go ├── header_timing_test.go ├── bytesconv_timing_test.go ├── workerpool_test.go ├── fasthttpadaptor ├── adaptor.go └── adaptor_test.go ├── bytesconv_table.go ├── lbclient.go ├── server_example_test.go ├── workerpool.go ├── headers.go ├── compress_test.go ├── SECURITY.md └── status.go /testdata/test.png: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/fileserver/.gitignore: -------------------------------------------------------------------------------- 1 | fileserver 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | *.pprof 3 | *.fasthttp.gz 4 | -------------------------------------------------------------------------------- /examples/helloworldserver/.gitignore: -------------------------------------------------------------------------------- 1 | helloworldserver 2 | -------------------------------------------------------------------------------- /fasthttputil/doc.go: -------------------------------------------------------------------------------- 1 | // Package fasthttputil provides utility functions for fasthttp. 2 | package fasthttputil 3 | -------------------------------------------------------------------------------- /bytesconv_32.go: -------------------------------------------------------------------------------- 1 | // +build !amd64,!arm64,!ppc64 2 | 3 | package fasthttp 4 | 5 | const ( 6 | maxHexIntChars = 7 7 | ) 8 | -------------------------------------------------------------------------------- /bytesconv_64.go: -------------------------------------------------------------------------------- 1 | // +build amd64 arm64 ppc64 2 | 3 | package fasthttp 4 | 5 | const ( 6 | maxHexIntChars = 15 7 | ) 8 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Code examples 2 | 3 | * [HelloWorld server](helloworldserver) 4 | * [Static file server](fileserver) 5 | -------------------------------------------------------------------------------- /examples/multidomain/Makefile: -------------------------------------------------------------------------------- 1 | writer: clean 2 | go get -u github.com/valyala/fasthttp 3 | go build 4 | 5 | clean: 6 | rm -f multidomain 7 | -------------------------------------------------------------------------------- /examples/helloworldserver/Makefile: -------------------------------------------------------------------------------- 1 | helloworldserver: clean 2 | go get -u github.com/valyala/fasthttp 3 | go build 4 | 5 | clean: 6 | rm -f helloworldserver 7 | -------------------------------------------------------------------------------- /stackless/doc.go: -------------------------------------------------------------------------------- 1 | // Package stackless provides functionality that may save stack space 2 | // for high number of concurrently running goroutines. 3 | package stackless 4 | -------------------------------------------------------------------------------- /examples/fileserver/Makefile: -------------------------------------------------------------------------------- 1 | fileserver: clean 2 | go get -u github.com/valyala/fasthttp 3 | go get -u github.com/valyala/fasthttp/expvarhandler 4 | go build 5 | 6 | clean: 7 | rm -f fileserver 8 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - SessionClient with referer and cookies support. 2 | - ProxyHandler similar to FSHandler. 3 | - WebSockets. See https://tools.ietf.org/html/rfc6455 . 4 | - HTTP/2.0. See https://tools.ietf.org/html/rfc7540 . 5 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: GolangCI-Lint Action 10 | uses: actions-contrib/golangci-lint@v0.1.0 11 | -------------------------------------------------------------------------------- /examples/multidomain/README.md: -------------------------------------------------------------------------------- 1 | # Multidomain using SSL certs example 2 | 3 | * Prints two messages depending on visited host. 4 | 5 | # How to build 6 | 7 | ``` 8 | make 9 | ``` 10 | 11 | # How to run 12 | 13 | ``` 14 | ./multidomain 15 | ``` 16 | -------------------------------------------------------------------------------- /fasthttputil/ecdsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIBpQbZ6a5jL1Yh4wdP6yZk4MKjYWArD/QOLENFw8vbELoAoGCCqGSM49 3 | AwEHoUQDQgAEKQCZWgE2IBhb47ot8MIs1D4KSisHYlZ41IWyeutpjb0fjwwIhimh 4 | pl1Qld1/d2j3Z3vVyfa5yD+ncV7qCFZuSg== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /uri_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package fasthttp 4 | 5 | func addLeadingSlash(dst, src []byte) []byte { 6 | // add leading slash for unix paths 7 | if len(src) == 0 || src[0] != '/' { 8 | dst = append(dst, '/') 9 | } 10 | 11 | return dst 12 | } 13 | -------------------------------------------------------------------------------- /uri_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package fasthttp 4 | 5 | func addLeadingSlash(dst, src []byte) []byte { 6 | // zero length and "C:/" case 7 | if len(src) == 0 || (len(src) > 2 && src[1] != ':') { 8 | dst = append(dst, '/') 9 | } 10 | 11 | return dst 12 | } 13 | -------------------------------------------------------------------------------- /reuseport/reuseport_windows.go: -------------------------------------------------------------------------------- 1 | package reuseport 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // Listen always returns ErrNoReusePort on Windows 9 | func Listen(network, addr string) (net.Listener, error) { 10 | return nil, &ErrNoReusePort{fmt.Errorf("Not supported on Windows")} 11 | } 12 | -------------------------------------------------------------------------------- /uri_windows_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package fasthttp 4 | 5 | import "testing" 6 | 7 | func TestURIPathNormalizeIssue86(t *testing.T) { 8 | t.Parallel() 9 | 10 | // see https://github.com/valyala/fasthttp/issues/86 11 | var u URI 12 | 13 | testURIPathNormalize(t, &u, `C:\a\b\c\fs.go`, `C:\a\b\c\fs.go`) 14 | } 15 | -------------------------------------------------------------------------------- /examples/helloworldserver/README.md: -------------------------------------------------------------------------------- 1 | # HelloWorld server example 2 | 3 | * Displays various request info. 4 | * Sets response headers and cookies. 5 | * Supports transparent compression. 6 | 7 | # How to build 8 | 9 | ``` 10 | make 11 | ``` 12 | 13 | # How to run 14 | 15 | ``` 16 | ./helloworldserver -addr=tcp.addr.to.listen:to 17 | ``` 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/valyala/fasthttp 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/klauspost/compress v1.8.2 7 | github.com/klauspost/cpuid v1.2.1 // indirect 8 | github.com/valyala/bytebufferpool v1.0.0 9 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a 10 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 11 | ) 12 | -------------------------------------------------------------------------------- /coarseTime.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // CoarseTimeNow returns the current time truncated to the nearest second. 8 | // 9 | // Deprecated: This is slower than calling time.Now() directly. 10 | // This is now time.Now().Truncate(time.Second) shortcut. 11 | func CoarseTimeNow() time.Time { 12 | return time.Now().Truncate(time.Second) 13 | } 14 | -------------------------------------------------------------------------------- /reuseport/reuseport_error.go: -------------------------------------------------------------------------------- 1 | package reuseport 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ErrNoReusePort is returned if the OS doesn't support SO_REUSEPORT. 8 | type ErrNoReusePort struct { 9 | err error 10 | } 11 | 12 | // Error implements error interface. 13 | func (e *ErrNoReusePort) Error() string { 14 | return fmt.Sprintf("The OS doesn't support SO_REUSEPORT: %s", e.err) 15 | } 16 | -------------------------------------------------------------------------------- /fuzzit/url/url_fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package fuzzit 4 | 5 | import ( 6 | "bytes" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | func Fuzz(data []byte) int { 12 | u := fasthttp.AcquireURI() 13 | defer fasthttp.ReleaseURI(u) 14 | 15 | u.UpdateBytes(data) 16 | 17 | w := bytes.Buffer{} 18 | if _, err := u.WriteTo(&w); err != nil { 19 | return 0 20 | } 21 | 22 | return 1 23 | } 24 | -------------------------------------------------------------------------------- /nocopy.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | // Embed this type into a struct, which mustn't be copied, 4 | // so `go vet` gives a warning if this struct is copied. 5 | // 6 | // See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details. 7 | // and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example 8 | type noCopy struct{} //nolint:unused 9 | 10 | func (*noCopy) Lock() {} 11 | func (*noCopy) Unlock() {} 12 | -------------------------------------------------------------------------------- /reuseport/reuseport_windows_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package reuseport 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestListen(t *testing.T) { 10 | _, err := Listen("tcp6", "[::1]:10082") 11 | if err == nil { 12 | t.Fatalf("unexpected non-error creating listener") 13 | } 14 | 15 | if _, errnoreuseport := err.(*ErrNoReusePort); !errnoreuseport { 16 | t.Fatalf("unexpected error creating listener: %s", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fuzzit/cookie/cookie_fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package fuzzit 4 | 5 | import ( 6 | "bytes" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | func Fuzz(data []byte) int { 12 | c := fasthttp.AcquireCookie() 13 | defer fasthttp.ReleaseCookie(c) 14 | 15 | if err := c.ParseBytes(data); err != nil { 16 | return 0 17 | } 18 | 19 | w := bytes.Buffer{} 20 | if _, err := c.WriteTo(&w); err != nil { 21 | return 0 22 | } 23 | 24 | return 1 25 | } 26 | -------------------------------------------------------------------------------- /fuzzit/request/request_fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package fasthttp 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func Fuzz(data []byte) int { 13 | req := fasthttp.AcquireRequest() 14 | defer fasthttp.ReleaseRequest(req) 15 | 16 | if err := req.ReadLimitBody(bufio.NewReader(bytes.NewReader(data)), 1024*1024); err != nil { 17 | return 0 18 | } 19 | 20 | w := bytes.Buffer{} 21 | if _, err := req.WriteTo(&w); err != nil { 22 | return 0 23 | } 24 | 25 | return 1 26 | } 27 | -------------------------------------------------------------------------------- /fuzzit/response/response_fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package fuzzit 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func Fuzz(data []byte) int { 13 | res := fasthttp.AcquireResponse() 14 | defer fasthttp.ReleaseResponse(res) 15 | 16 | if err := res.ReadLimitBody(bufio.NewReader(bytes.NewReader(data)), 1024*1024); err != nil { 17 | return 0 18 | } 19 | 20 | w := bytes.Buffer{} 21 | if _, err := res.WriteTo(&w); err != nil { 22 | return 0 23 | } 24 | 25 | return 1 26 | } 27 | -------------------------------------------------------------------------------- /methods.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | // HTTP methods were copied from net/http. 4 | const ( 5 | MethodGet = "GET" // RFC 7231, 4.3.1 6 | MethodHead = "HEAD" // RFC 7231, 4.3.2 7 | MethodPost = "POST" // RFC 7231, 4.3.3 8 | MethodPut = "PUT" // RFC 7231, 4.3.4 9 | MethodPatch = "PATCH" // RFC 5789 10 | MethodDelete = "DELETE" // RFC 7231, 4.3.5 11 | MethodConnect = "CONNECT" // RFC 7231, 4.3.6 12 | MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 13 | MethodTrace = "TRACE" // RFC 7231, 4.3.8 14 | ) 15 | -------------------------------------------------------------------------------- /fasthttputil/ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBbTCCAROgAwIBAgIQPo718S+K+G7hc1SgTEU4QDAKBggqhkjOPQQDAjASMRAw 3 | DgYDVQQKEwdBY21lIENvMB4XDTE3MDQyMDIxMDExNFoXDTE4MDQyMDIxMDExNFow 4 | EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCkA 5 | mVoBNiAYW+O6LfDCLNQ+CkorB2JWeNSFsnrraY29H48MCIYpoaZdUJXdf3do92d7 6 | 1cn2ucg/p3Fe6ghWbkqjSzBJMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr 7 | BgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAKBggq 8 | hkjOPQQDAgNIADBFAiEAoLAIQkvSuIcHUqyWroA6yWYw2fznlRH/uO9/hMCxUCEC 9 | IClRYb/5O9eD/Eq/ozPnwNpsQHOeYefEhadJ/P82y0lG 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /reuseport/reuseport_example_test.go: -------------------------------------------------------------------------------- 1 | package reuseport_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/valyala/fasthttp" 8 | "github.com/valyala/fasthttp/reuseport" 9 | ) 10 | 11 | func ExampleListen() { 12 | ln, err := reuseport.Listen("tcp4", "localhost:12345") 13 | if err != nil { 14 | log.Fatalf("error in reuseport listener: %s", err) 15 | } 16 | 17 | if err = fasthttp.Serve(ln, requestHandler); err != nil { 18 | log.Fatalf("error in fasthttp Server: %s", err) 19 | } 20 | } 21 | 22 | func requestHandler(ctx *fasthttp.RequestCtx) { 23 | fmt.Fprintf(ctx, "Hello, world!") 24 | } 25 | -------------------------------------------------------------------------------- /args_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkArgsParse(b *testing.B) { 9 | s := []byte("foo=bar&baz=qqq&aaaaa=bbbb") 10 | b.RunParallel(func(pb *testing.PB) { 11 | var a Args 12 | for pb.Next() { 13 | a.ParseBytes(s) 14 | } 15 | }) 16 | } 17 | 18 | func BenchmarkArgsPeek(b *testing.B) { 19 | value := []byte("foobarbaz1234") 20 | key := "foobarbaz" 21 | b.RunParallel(func(pb *testing.PB) { 22 | var a Args 23 | a.SetBytesV(key, value) 24 | for pb.Next() { 25 | if !bytes.Equal(a.Peek(key), value) { 26 | b.Fatalf("unexpected arg value %q. Expecting %q", a.Peek(key), value) 27 | } 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /fs_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/valyala/fasthttp" 7 | ) 8 | 9 | func ExampleFS() { 10 | fs := &fasthttp.FS{ 11 | // Path to directory to serve. 12 | Root: "/var/www/static-site", 13 | 14 | // Generate index pages if client requests directory contents. 15 | GenerateIndexPages: true, 16 | 17 | // Enable transparent compression to save network traffic. 18 | Compress: true, 19 | } 20 | 21 | // Create request handler for serving static files. 22 | h := fs.NewRequestHandler() 23 | 24 | // Start the server. 25 | if err := fasthttp.ListenAndServe(":8080", h); err != nil { 26 | log.Fatalf("error in ListenAndServe: %s", err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /fuzzit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | ## go-fuzz doesn't support modules for now, so ensure we do everything 6 | ## in the old style GOPATH way 7 | export GO111MODULE="off" 8 | 9 | # We need to download these dependencies again after we set GO111MODULE="off" 10 | go get -t -v ./... 11 | 12 | go get github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build 13 | 14 | wget -q -O fuzzitbin https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.52/fuzzit_Linux_x86_64 15 | chmod a+x fuzzitbin 16 | 17 | for w in request response cookie url; do 18 | go-fuzz-build -libfuzzer -o fasthttp_$w.a ./fuzzit/$w/ 19 | clang -fsanitize=fuzzer fasthttp_$w.a -o fasthttp_$w 20 | 21 | ./fuzzitbin create job --type $1 fasthttp/$w fasthttp_$w 22 | done 23 | -------------------------------------------------------------------------------- /coarseTime_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func BenchmarkCoarseTimeNow(b *testing.B) { 10 | var zeroTimeCount uint64 11 | b.RunParallel(func(pb *testing.PB) { 12 | for pb.Next() { 13 | t := CoarseTimeNow() 14 | if t.IsZero() { 15 | atomic.AddUint64(&zeroTimeCount, 1) 16 | } 17 | } 18 | }) 19 | if zeroTimeCount > 0 { 20 | b.Fatalf("zeroTimeCount must be zero") 21 | } 22 | } 23 | 24 | func BenchmarkTimeNow(b *testing.B) { 25 | var zeroTimeCount uint64 26 | b.RunParallel(func(pb *testing.PB) { 27 | for pb.Next() { 28 | t := time.Now() 29 | if t.IsZero() { 30 | atomic.AddUint64(&zeroTimeCount, 1) 31 | } 32 | } 33 | }) 34 | if zeroTimeCount > 0 { 35 | b.Fatalf("zeroTimeCount must be zero") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /stackless/func_timing_test.go: -------------------------------------------------------------------------------- 1 | package stackless 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkFuncOverhead(b *testing.B) { 9 | var n uint64 10 | f := NewFunc(func(ctx interface{}) { 11 | atomic.AddUint64(&n, *(ctx.(*uint64))) 12 | }) 13 | b.RunParallel(func(pb *testing.PB) { 14 | x := uint64(1) 15 | for pb.Next() { 16 | if !f(&x) { 17 | b.Fatalf("f mustn't return false") 18 | } 19 | } 20 | }) 21 | if n != uint64(b.N) { 22 | b.Fatalf("unexected n: %d. Expecting %d", n, b.N) 23 | } 24 | } 25 | 26 | func BenchmarkFuncPure(b *testing.B) { 27 | var n uint64 28 | f := func(x *uint64) { 29 | atomic.AddUint64(&n, *x) 30 | } 31 | b.RunParallel(func(pb *testing.PB) { 32 | x := uint64(1) 33 | for pb.Next() { 34 | f(&x) 35 | } 36 | }) 37 | if n != uint64(b.N) { 38 | b.Fatalf("unexected n: %d. Expecting %d", n, b.N) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /requestctx_setbodystreamwriter_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func ExampleRequestCtx_SetBodyStreamWriter() { 13 | // Start fasthttp server for streaming responses. 14 | if err := fasthttp.ListenAndServe(":8080", responseStreamHandler); err != nil { 15 | log.Fatalf("unexpected error in server: %s", err) 16 | } 17 | } 18 | 19 | func responseStreamHandler(ctx *fasthttp.RequestCtx) { 20 | // Send the response in chunks and wait for a second between each chunk. 21 | ctx.SetBodyStreamWriter(func(w *bufio.Writer) { 22 | for i := 0; i < 10; i++ { 23 | fmt.Fprintf(w, "this is a message number %d", i) 24 | 25 | // Do not forget flushing streamed data to the client. 26 | if err := w.Flush(); err != nil { 27 | return 28 | } 29 | time.Sleep(time.Second) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /fasthttpproxy/socks5.go: -------------------------------------------------------------------------------- 1 | package fasthttpproxy 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/valyala/fasthttp" 7 | "golang.org/x/net/proxy" 8 | ) 9 | 10 | // FasthttpSocksDialer returns a fasthttp.DialFunc that dials using 11 | // the provided SOCKS5 proxy. 12 | // 13 | // Example usage: 14 | // c := &fasthttp.Client{ 15 | // Dial: fasthttpproxy.FasthttpSocksDialer("localhost:9050"), 16 | // } 17 | func FasthttpSocksDialer(proxyAddr string) fasthttp.DialFunc { 18 | dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct) 19 | // It would be nice if we could return the error here. But we can't 20 | // change our API so just keep returning it in the returned Dial function. 21 | // Besides the implementation of proxy.SOCKS5() at the time of writing this 22 | // will always return nil as error. 23 | 24 | return func(addr string) (net.Conn, error) { 25 | if err != nil { 26 | return nil, err 27 | } 28 | return dialer.Dial("tcp", addr) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cookie_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkCookieParseMin(b *testing.B) { 8 | var c Cookie 9 | s := []byte("xxx=yyy") 10 | for i := 0; i < b.N; i++ { 11 | if err := c.ParseBytes(s); err != nil { 12 | b.Fatalf("unexpected error when parsing cookies: %s", err) 13 | } 14 | } 15 | } 16 | 17 | func BenchmarkCookieParseNoExpires(b *testing.B) { 18 | var c Cookie 19 | s := []byte("xxx=yyy; domain=foobar.com; path=/a/b") 20 | for i := 0; i < b.N; i++ { 21 | if err := c.ParseBytes(s); err != nil { 22 | b.Fatalf("unexpected error when parsing cookies: %s", err) 23 | } 24 | } 25 | } 26 | 27 | func BenchmarkCookieParseFull(b *testing.B) { 28 | var c Cookie 29 | s := []byte("xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b") 30 | for i := 0; i < b.N; i++ { 31 | if err := c.ParseBytes(s); err != nil { 32 | b.Fatalf("unexpected error when parsing cookies: %s", err) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fasthttputil/rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV 4 | MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D 6 | K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te 7 | +z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij 8 | L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1 9 | xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY 10 | 6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG 11 | SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98 12 | L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2 13 | 45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li 14 | K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6 15 | X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI 16 | whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /ssl-cert-snakeoil.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV 4 | MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D 6 | K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te 7 | +z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij 8 | L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1 9 | xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY 10 | 6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG 11 | SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98 12 | L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2 13 | 45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li 14 | K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6 15 | X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI 16 | whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /examples/fileserver/ssl-cert-snakeoil.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV 4 | MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D 6 | K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te 7 | +z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij 8 | L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1 9 | xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY 10 | 6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG 11 | SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98 12 | L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2 13 | 45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li 14 | K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6 15 | X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI 16 | whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /lbclient_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/valyala/fasthttp" 8 | ) 9 | 10 | func ExampleLBClient() { 11 | // Requests will be spread among these servers. 12 | servers := []string{ 13 | "google.com:80", 14 | "foobar.com:8080", 15 | "127.0.0.1:123", 16 | } 17 | 18 | // Prepare clients for each server 19 | var lbc fasthttp.LBClient 20 | for _, addr := range servers { 21 | c := &fasthttp.HostClient{ 22 | Addr: addr, 23 | } 24 | lbc.Clients = append(lbc.Clients, c) 25 | } 26 | 27 | // Send requests to load-balanced servers 28 | var req fasthttp.Request 29 | var resp fasthttp.Response 30 | for i := 0; i < 10; i++ { 31 | url := fmt.Sprintf("http://abcedfg/foo/bar/%d", i) 32 | req.SetRequestURI(url) 33 | if err := lbc.Do(&req, &resp); err != nil { 34 | log.Fatalf("Error when sending request: %s", err) 35 | } 36 | if resp.StatusCode() != fasthttp.StatusOK { 37 | log.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), fasthttp.StatusOK) 38 | } 39 | 40 | useResponseBody(resp.Body()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bytesconv_32_test.go: -------------------------------------------------------------------------------- 1 | // +build !amd64,!arm64,!ppc64 2 | 3 | package fasthttp 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestWriteHexInt(t *testing.T) { 10 | t.Parallel() 11 | 12 | testWriteHexInt(t, 0, "0") 13 | testWriteHexInt(t, 1, "1") 14 | testWriteHexInt(t, 0x123, "123") 15 | testWriteHexInt(t, 0x7fffffff, "7fffffff") 16 | } 17 | 18 | func TestAppendUint(t *testing.T) { 19 | t.Parallel() 20 | 21 | testAppendUint(t, 0) 22 | testAppendUint(t, 123) 23 | testAppendUint(t, 0x7fffffff) 24 | 25 | for i := 0; i < 2345; i++ { 26 | testAppendUint(t, i) 27 | } 28 | } 29 | 30 | func TestReadHexIntSuccess(t *testing.T) { 31 | t.Parallel() 32 | 33 | testReadHexIntSuccess(t, "0", 0) 34 | testReadHexIntSuccess(t, "fF", 0xff) 35 | testReadHexIntSuccess(t, "00abc", 0xabc) 36 | testReadHexIntSuccess(t, "7ffffff", 0x7ffffff) 37 | testReadHexIntSuccess(t, "000", 0) 38 | testReadHexIntSuccess(t, "1234ZZZ", 0x1234) 39 | } 40 | 41 | func TestParseUintSuccess(t *testing.T) { 42 | t.Parallel() 43 | 44 | testParseUintSuccess(t, "0", 0) 45 | testParseUintSuccess(t, "123", 123) 46 | testParseUintSuccess(t, "123456789", 123456789) 47 | } 48 | -------------------------------------------------------------------------------- /reuseport/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Max Riveiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /userdata_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkUserDataCustom(b *testing.B) { 8 | keys := []string{"foobar", "baz", "aaa", "bsdfs"} 9 | b.RunParallel(func(pb *testing.PB) { 10 | var u userData 11 | var v interface{} = u 12 | for pb.Next() { 13 | for _, key := range keys { 14 | u.Set(key, v) 15 | } 16 | for _, key := range keys { 17 | vv := u.Get(key) 18 | if _, ok := vv.(userData); !ok { 19 | b.Fatalf("unexpected value %v for key %q", vv, key) 20 | } 21 | } 22 | u.Reset() 23 | } 24 | }) 25 | } 26 | 27 | func BenchmarkUserDataStdMap(b *testing.B) { 28 | keys := []string{"foobar", "baz", "aaa", "bsdfs"} 29 | b.RunParallel(func(pb *testing.PB) { 30 | u := make(map[string]interface{}) 31 | var v interface{} = u 32 | for pb.Next() { 33 | for _, key := range keys { 34 | u[key] = v 35 | } 36 | for _, key := range keys { 37 | vv := u[key] 38 | if _, ok := vv.(map[string]interface{}); !ok { 39 | b.Fatalf("unexpected value %v for key %q", vv, key) 40 | } 41 | } 42 | 43 | for k := range u { 44 | delete(u, k) 45 | } 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /client_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/valyala/fasthttp" 7 | ) 8 | 9 | func ExampleHostClient() { 10 | // Perpare a client, which fetches webpages via HTTP proxy listening 11 | // on the localhost:8080. 12 | c := &fasthttp.HostClient{ 13 | Addr: "localhost:8080", 14 | } 15 | 16 | // Fetch google page via local proxy. 17 | statusCode, body, err := c.Get(nil, "http://google.com/foo/bar") 18 | if err != nil { 19 | log.Fatalf("Error when loading google page through local proxy: %s", err) 20 | } 21 | if statusCode != fasthttp.StatusOK { 22 | log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK) 23 | } 24 | useResponseBody(body) 25 | 26 | // Fetch foobar page via local proxy. Reuse body buffer. 27 | statusCode, body, err = c.Get(body, "http://foobar.com/google/com") 28 | if err != nil { 29 | log.Fatalf("Error when loading foobar page through local proxy: %s", err) 30 | } 31 | if statusCode != fasthttp.StatusOK { 32 | log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK) 33 | } 34 | useResponseBody(body) 35 | } 36 | 37 | func useResponseBody(body []byte) { 38 | // Do something with body :) 39 | } 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs= 2 | github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 3 | github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= 4 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 5 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 6 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 7 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc= 8 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 9 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 10 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 11 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 12 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 14 | -------------------------------------------------------------------------------- /bytesconv_64_test.go: -------------------------------------------------------------------------------- 1 | // +build amd64 arm64 ppc64 2 | 3 | package fasthttp 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestWriteHexInt(t *testing.T) { 10 | t.Parallel() 11 | 12 | testWriteHexInt(t, 0, "0") 13 | testWriteHexInt(t, 1, "1") 14 | testWriteHexInt(t, 0x123, "123") 15 | testWriteHexInt(t, 0x7fffffffffffffff, "7fffffffffffffff") 16 | } 17 | 18 | func TestAppendUint(t *testing.T) { 19 | t.Parallel() 20 | 21 | testAppendUint(t, 0) 22 | testAppendUint(t, 123) 23 | testAppendUint(t, 0x7fffffffffffffff) 24 | 25 | for i := 0; i < 2345; i++ { 26 | testAppendUint(t, i) 27 | } 28 | } 29 | 30 | func TestReadHexIntSuccess(t *testing.T) { 31 | t.Parallel() 32 | 33 | testReadHexIntSuccess(t, "0", 0) 34 | testReadHexIntSuccess(t, "fF", 0xff) 35 | testReadHexIntSuccess(t, "00abc", 0xabc) 36 | testReadHexIntSuccess(t, "7fffffff", 0x7fffffff) 37 | testReadHexIntSuccess(t, "000", 0) 38 | testReadHexIntSuccess(t, "1234ZZZ", 0x1234) 39 | testReadHexIntSuccess(t, "7ffffffffffffff", 0x7ffffffffffffff) 40 | } 41 | 42 | func TestParseUintSuccess(t *testing.T) { 43 | t.Parallel() 44 | 45 | testParseUintSuccess(t, "0", 0) 46 | testParseUintSuccess(t, "123", 123) 47 | testParseUintSuccess(t, "1234567890", 1234567890) 48 | testParseUintSuccess(t, "123456789012345678", 123456789012345678) 49 | testParseUintSuccess(t, "9223372036854775807", 9223372036854775807) 50 | } 51 | -------------------------------------------------------------------------------- /fs_handler_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | 7 | "github.com/valyala/fasthttp" 8 | ) 9 | 10 | // Setup file handlers (aka 'file server config') 11 | var ( 12 | // Handler for serving images from /img/ path, 13 | // i.e. /img/foo/bar.jpg will be served from 14 | // /var/www/images/foo/bar.jpb . 15 | imgPrefix = []byte("/img/") 16 | imgHandler = fasthttp.FSHandler("/var/www/images", 1) 17 | 18 | // Handler for serving css from /static/css/ path, 19 | // i.e. /static/css/foo/bar.css will be served from 20 | // /home/dev/css/foo/bar.css . 21 | cssPrefix = []byte("/static/css/") 22 | cssHandler = fasthttp.FSHandler("/home/dev/css", 2) 23 | 24 | // Handler for serving the rest of requests, 25 | // i.e. /foo/bar/baz.html will be served from 26 | // /var/www/files/foo/bar/baz.html . 27 | filesHandler = fasthttp.FSHandler("/var/www/files", 0) 28 | ) 29 | 30 | // Main request handler 31 | func requestHandler(ctx *fasthttp.RequestCtx) { 32 | path := ctx.Path() 33 | switch { 34 | case bytes.HasPrefix(path, imgPrefix): 35 | imgHandler(ctx) 36 | case bytes.HasPrefix(path, cssPrefix): 37 | cssHandler(ctx) 38 | default: 39 | filesHandler(ctx) 40 | } 41 | } 42 | 43 | func ExampleFSHandler() { 44 | if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil { 45 | log.Fatalf("Error in server: %s", err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /uri_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkURIParsePath(b *testing.B) { 8 | benchmarkURIParse(b, "google.com", "/foo/bar") 9 | } 10 | 11 | func BenchmarkURIParsePathQueryString(b *testing.B) { 12 | benchmarkURIParse(b, "google.com", "/foo/bar?query=string&other=value") 13 | } 14 | 15 | func BenchmarkURIParsePathQueryStringHash(b *testing.B) { 16 | benchmarkURIParse(b, "google.com", "/foo/bar?query=string&other=value#hashstring") 17 | } 18 | 19 | func BenchmarkURIParseHostname(b *testing.B) { 20 | benchmarkURIParse(b, "google.com", "http://foobar.com/foo/bar?query=string&other=value#hashstring") 21 | } 22 | 23 | func BenchmarkURIFullURI(b *testing.B) { 24 | host := []byte("foobar.com") 25 | requestURI := []byte("/foobar/baz?aaa=bbb&ccc=ddd") 26 | uriLen := len(host) + len(requestURI) + 7 27 | 28 | b.RunParallel(func(pb *testing.PB) { 29 | var u URI 30 | u.Parse(host, requestURI) 31 | for pb.Next() { 32 | uri := u.FullURI() 33 | if len(uri) != uriLen { 34 | b.Fatalf("unexpected uri len %d. Expecting %d", len(uri), uriLen) 35 | } 36 | } 37 | }) 38 | } 39 | 40 | func benchmarkURIParse(b *testing.B, host, uri string) { 41 | strHost, strURI := []byte(host), []byte(uri) 42 | 43 | b.RunParallel(func(pb *testing.PB) { 44 | var u URI 45 | for pb.Next() { 46 | u.Parse(strHost, strURI) 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "sync" 7 | 8 | "github.com/valyala/fasthttp/fasthttputil" 9 | ) 10 | 11 | // StreamWriter must write data to w. 12 | // 13 | // Usually StreamWriter writes data to w in a loop (aka 'data streaming'). 14 | // 15 | // StreamWriter must return immediately if w returns error. 16 | // 17 | // Since the written data is buffered, do not forget calling w.Flush 18 | // when the data must be propagated to reader. 19 | type StreamWriter func(w *bufio.Writer) 20 | 21 | // NewStreamReader returns a reader, which replays all the data generated by sw. 22 | // 23 | // The returned reader may be passed to Response.SetBodyStream. 24 | // 25 | // Close must be called on the returned reader after all the required data 26 | // has been read. Otherwise goroutine leak may occur. 27 | // 28 | // See also Response.SetBodyStreamWriter. 29 | func NewStreamReader(sw StreamWriter) io.ReadCloser { 30 | pc := fasthttputil.NewPipeConns() 31 | pw := pc.Conn1() 32 | pr := pc.Conn2() 33 | 34 | var bw *bufio.Writer 35 | v := streamWriterBufPool.Get() 36 | if v == nil { 37 | bw = bufio.NewWriter(pw) 38 | } else { 39 | bw = v.(*bufio.Writer) 40 | bw.Reset(pw) 41 | } 42 | 43 | go func() { 44 | sw(bw) 45 | bw.Flush() 46 | pw.Close() 47 | 48 | streamWriterBufPool.Put(bw) 49 | }() 50 | 51 | return pr 52 | } 53 | 54 | var streamWriterBufPool sync.Pool 55 | -------------------------------------------------------------------------------- /peripconn_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIPxUint32(t *testing.T) { 8 | t.Parallel() 9 | 10 | testIPxUint32(t, 0) 11 | testIPxUint32(t, 10) 12 | testIPxUint32(t, 0x12892392) 13 | } 14 | 15 | func testIPxUint32(t *testing.T, n uint32) { 16 | ip := uint322ip(n) 17 | nn := ip2uint32(ip) 18 | if n != nn { 19 | t.Fatalf("Unexpected value=%d for ip=%s. Expected %d", nn, ip, n) 20 | } 21 | } 22 | 23 | func TestPerIPConnCounter(t *testing.T) { 24 | t.Parallel() 25 | 26 | var cc perIPConnCounter 27 | 28 | expectPanic(t, func() { cc.Unregister(123) }) 29 | 30 | for i := 1; i < 100; i++ { 31 | if n := cc.Register(123); n != i { 32 | t.Fatalf("Unexpected counter value=%d. Expected %d", n, i) 33 | } 34 | } 35 | 36 | n := cc.Register(456) 37 | if n != 1 { 38 | t.Fatalf("Unexpected counter value=%d. Expected 1", n) 39 | } 40 | 41 | for i := 1; i < 100; i++ { 42 | cc.Unregister(123) 43 | } 44 | cc.Unregister(456) 45 | 46 | expectPanic(t, func() { cc.Unregister(123) }) 47 | expectPanic(t, func() { cc.Unregister(456) }) 48 | 49 | n = cc.Register(123) 50 | if n != 1 { 51 | t.Fatalf("Unexpected counter value=%d. Expected 1", n) 52 | } 53 | cc.Unregister(123) 54 | } 55 | 56 | func expectPanic(t *testing.T, f func()) { 57 | defer func() { 58 | if r := recover(); r == nil { 59 | t.Fatalf("Expecting panic") 60 | } 61 | }() 62 | f() 63 | } 64 | -------------------------------------------------------------------------------- /userdata.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type userDataKV struct { 8 | key []byte 9 | value interface{} 10 | } 11 | 12 | type userData []userDataKV 13 | 14 | func (d *userData) Set(key string, value interface{}) { 15 | args := *d 16 | n := len(args) 17 | for i := 0; i < n; i++ { 18 | kv := &args[i] 19 | if string(kv.key) == key { 20 | kv.value = value 21 | return 22 | } 23 | } 24 | 25 | c := cap(args) 26 | if c > n { 27 | args = args[:n+1] 28 | kv := &args[n] 29 | kv.key = append(kv.key[:0], key...) 30 | kv.value = value 31 | *d = args 32 | return 33 | } 34 | 35 | kv := userDataKV{} 36 | kv.key = append(kv.key[:0], key...) 37 | kv.value = value 38 | *d = append(args, kv) 39 | } 40 | 41 | func (d *userData) SetBytes(key []byte, value interface{}) { 42 | d.Set(b2s(key), value) 43 | } 44 | 45 | func (d *userData) Get(key string) interface{} { 46 | args := *d 47 | n := len(args) 48 | for i := 0; i < n; i++ { 49 | kv := &args[i] 50 | if string(kv.key) == key { 51 | return kv.value 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func (d *userData) GetBytes(key []byte) interface{} { 58 | return d.Get(b2s(key)) 59 | } 60 | 61 | func (d *userData) Reset() { 62 | args := *d 63 | n := len(args) 64 | for i := 0; i < n; i++ { 65 | v := args[i].value 66 | if vc, ok := v.(io.Closer); ok { 67 | vc.Close() 68 | } 69 | } 70 | *d = (*d)[:0] 71 | } 72 | -------------------------------------------------------------------------------- /timer.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func initTimer(t *time.Timer, timeout time.Duration) *time.Timer { 9 | if t == nil { 10 | return time.NewTimer(timeout) 11 | } 12 | if t.Reset(timeout) { 13 | panic("BUG: active timer trapped into initTimer()") 14 | } 15 | return t 16 | } 17 | 18 | func stopTimer(t *time.Timer) { 19 | if !t.Stop() { 20 | // Collect possibly added time from the channel 21 | // if timer has been stopped and nobody collected its' value. 22 | select { 23 | case <-t.C: 24 | default: 25 | } 26 | } 27 | } 28 | 29 | // AcquireTimer returns a time.Timer from the pool and updates it to 30 | // send the current time on its channel after at least timeout. 31 | // 32 | // The returned Timer may be returned to the pool with ReleaseTimer 33 | // when no longer needed. This allows reducing GC load. 34 | func AcquireTimer(timeout time.Duration) *time.Timer { 35 | v := timerPool.Get() 36 | if v == nil { 37 | return time.NewTimer(timeout) 38 | } 39 | t := v.(*time.Timer) 40 | initTimer(t, timeout) 41 | return t 42 | } 43 | 44 | // ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool 45 | // and prevents the Timer from firing. 46 | // 47 | // Do not access the released time.Timer or read from it's channel otherwise 48 | // data races may occur. 49 | func ReleaseTimer(t *time.Timer) { 50 | stopTimer(t) 51 | timerPool.Put(t) 52 | } 53 | 54 | var timerPool sync.Pool 55 | -------------------------------------------------------------------------------- /reuseport/reuseport.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | // Package reuseport provides TCP net.Listener with SO_REUSEPORT support. 4 | // 5 | // SO_REUSEPORT allows linear scaling server performance on multi-CPU servers. 6 | // See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for more details :) 7 | // 8 | // The package is based on https://github.com/kavu/go_reuseport . 9 | package reuseport 10 | 11 | import ( 12 | "net" 13 | "strings" 14 | 15 | "github.com/valyala/tcplisten" 16 | ) 17 | 18 | // Listen returns TCP listener with SO_REUSEPORT option set. 19 | // 20 | // The returned listener tries enabling the following TCP options, which usually 21 | // have positive impact on performance: 22 | // 23 | // - TCP_DEFER_ACCEPT. This option expects that the server reads from accepted 24 | // connections before writing to them. 25 | // 26 | // - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details. 27 | // 28 | // Use https://github.com/valyala/tcplisten if you want customizing 29 | // these options. 30 | // 31 | // Only tcp4 and tcp6 networks are supported. 32 | // 33 | // ErrNoReusePort error is returned if the system doesn't support SO_REUSEPORT. 34 | func Listen(network, addr string) (net.Listener, error) { 35 | ln, err := cfg.NewListener(network, addr) 36 | if err != nil && strings.Contains(err.Error(), "SO_REUSEPORT") { 37 | return nil, &ErrNoReusePort{err} 38 | } 39 | return ln, err 40 | } 41 | 42 | var cfg = &tcplisten.Config{ 43 | ReusePort: true, 44 | DeferAccept: true, 45 | FastOpen: true, 46 | } 47 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package fasthttp provides fast HTTP server and client API. 3 | 4 | Fasthttp provides the following features: 5 | 6 | * Optimized for speed. Easily handles more than 100K qps and more than 1M 7 | concurrent keep-alive connections on modern hardware. 8 | * Optimized for low memory usage. 9 | * Easy 'Connection: Upgrade' support via RequestCtx.Hijack. 10 | * Server provides the following anti-DoS limits: 11 | 12 | * The number of concurrent connections. 13 | * The number of concurrent connections per client IP. 14 | * The number of requests per connection. 15 | * Request read timeout. 16 | * Response write timeout. 17 | * Maximum request header size. 18 | * Maximum request body size. 19 | * Maximum request execution time. 20 | * Maximum keep-alive connection lifetime. 21 | * Early filtering out non-GET requests. 22 | 23 | * A lot of additional useful info is exposed to request handler: 24 | 25 | * Server and client address. 26 | * Per-request logger. 27 | * Unique request id. 28 | * Request start time. 29 | * Connection start time. 30 | * Request sequence number for the current connection. 31 | 32 | * Client supports automatic retry on idempotent requests' failure. 33 | * Fasthttp API is designed with the ability to extend existing client 34 | and server implementations or to write custom client and server 35 | implementations from scratch. 36 | */ 37 | package fasthttp 38 | -------------------------------------------------------------------------------- /pprofhandler/pprof.go: -------------------------------------------------------------------------------- 1 | package pprofhandler 2 | 3 | import ( 4 | "net/http/pprof" 5 | rtp "runtime/pprof" 6 | "strings" 7 | 8 | "github.com/valyala/fasthttp" 9 | "github.com/valyala/fasthttp/fasthttpadaptor" 10 | ) 11 | 12 | var ( 13 | cmdline = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Cmdline) 14 | profile = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Profile) 15 | symbol = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Symbol) 16 | trace = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Trace) 17 | index = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Index) 18 | ) 19 | 20 | // PprofHandler serves server runtime profiling data in the format expected by the pprof visualization tool. 21 | // 22 | // See https://golang.org/pkg/net/http/pprof/ for details. 23 | func PprofHandler(ctx *fasthttp.RequestCtx) { 24 | ctx.Response.Header.Set("Content-Type", "text/html") 25 | if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/cmdline") { 26 | cmdline(ctx) 27 | } else if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/profile") { 28 | profile(ctx) 29 | } else if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/symbol") { 30 | symbol(ctx) 31 | } else if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/trace") { 32 | trace(ctx) 33 | } else { 34 | for _, v := range rtp.Profiles() { 35 | ppName := v.Name() 36 | if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/"+ppName) { 37 | namedHandler := fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler(ppName).ServeHTTP) 38 | namedHandler(ctx) 39 | return 40 | } 41 | } 42 | index(ctx) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stream_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func BenchmarkStreamReader1(b *testing.B) { 11 | benchmarkStreamReader(b, 1) 12 | } 13 | 14 | func BenchmarkStreamReader10(b *testing.B) { 15 | benchmarkStreamReader(b, 10) 16 | } 17 | 18 | func BenchmarkStreamReader100(b *testing.B) { 19 | benchmarkStreamReader(b, 100) 20 | } 21 | 22 | func BenchmarkStreamReader1K(b *testing.B) { 23 | benchmarkStreamReader(b, 1000) 24 | } 25 | 26 | func BenchmarkStreamReader10K(b *testing.B) { 27 | benchmarkStreamReader(b, 10000) 28 | } 29 | 30 | func benchmarkStreamReader(b *testing.B, size int) { 31 | src := createFixedBody(size) 32 | b.SetBytes(int64(size)) 33 | 34 | b.RunParallel(func(pb *testing.PB) { 35 | dst := make([]byte, size) 36 | ch := make(chan error, 1) 37 | sr := NewStreamReader(func(w *bufio.Writer) { 38 | for pb.Next() { 39 | if _, err := w.Write(src); err != nil { 40 | ch <- err 41 | return 42 | } 43 | if err := w.Flush(); err != nil { 44 | ch <- err 45 | return 46 | } 47 | } 48 | ch <- nil 49 | }) 50 | for { 51 | if _, err := sr.Read(dst); err != nil { 52 | if err == io.EOF { 53 | break 54 | } 55 | b.Fatalf("unexpected error when reading from stream reader: %s", err) 56 | } 57 | } 58 | if err := sr.Close(); err != nil { 59 | b.Fatalf("unexpected error when closing stream reader: %s", err) 60 | } 61 | select { 62 | case err := <-ch: 63 | if err != nil { 64 | b.Fatalf("unexpected error from stream reader: %s", err) 65 | } 66 | case <-time.After(time.Second): 67 | b.Fatalf("timeout") 68 | } 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /expvarhandler/expvar.go: -------------------------------------------------------------------------------- 1 | // Package expvarhandler provides fasthttp-compatible request handler 2 | // serving expvars. 3 | package expvarhandler 4 | 5 | import ( 6 | "expvar" 7 | "fmt" 8 | "regexp" 9 | 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | var ( 14 | expvarHandlerCalls = expvar.NewInt("expvarHandlerCalls") 15 | expvarRegexpErrors = expvar.NewInt("expvarRegexpErrors") 16 | 17 | defaultRE = regexp.MustCompile(".") 18 | ) 19 | 20 | // ExpvarHandler dumps json representation of expvars to http response. 21 | // 22 | // Expvars may be filtered by regexp provided via 'r' query argument. 23 | // 24 | // See https://golang.org/pkg/expvar/ for details. 25 | func ExpvarHandler(ctx *fasthttp.RequestCtx) { 26 | expvarHandlerCalls.Add(1) 27 | 28 | ctx.Response.Reset() 29 | 30 | r, err := getExpvarRegexp(ctx) 31 | if err != nil { 32 | expvarRegexpErrors.Add(1) 33 | fmt.Fprintf(ctx, "Error when obtaining expvar regexp: %s", err) 34 | ctx.SetStatusCode(fasthttp.StatusBadRequest) 35 | return 36 | } 37 | 38 | fmt.Fprintf(ctx, "{\n") 39 | first := true 40 | expvar.Do(func(kv expvar.KeyValue) { 41 | if r.MatchString(kv.Key) { 42 | if !first { 43 | fmt.Fprintf(ctx, ",\n") 44 | } 45 | first = false 46 | fmt.Fprintf(ctx, "\t%q: %s", kv.Key, kv.Value) 47 | } 48 | }) 49 | fmt.Fprintf(ctx, "\n}\n") 50 | 51 | ctx.SetContentType("application/json; charset=utf-8") 52 | } 53 | 54 | func getExpvarRegexp(ctx *fasthttp.RequestCtx) (*regexp.Regexp, error) { 55 | r := string(ctx.QueryArgs().Peek("r")) 56 | if len(r) == 0 { 57 | return defaultRE, nil 58 | } 59 | rr, err := regexp.Compile(r) 60 | if err != nil { 61 | return nil, fmt.Errorf("cannot parse r=%q: %s", r, err) 62 | } 63 | return rr, nil 64 | } 65 | -------------------------------------------------------------------------------- /expvarhandler/expvar_test.go: -------------------------------------------------------------------------------- 1 | package expvarhandler 2 | 3 | import ( 4 | "encoding/json" 5 | "expvar" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/valyala/fasthttp" 10 | ) 11 | 12 | func TestExpvarHandlerBasic(t *testing.T) { 13 | expvar.Publish("customVar", expvar.Func(func() interface{} { 14 | return "foobar" 15 | })) 16 | 17 | var ctx fasthttp.RequestCtx 18 | 19 | expvarHandlerCalls.Set(0) 20 | 21 | ExpvarHandler(&ctx) 22 | 23 | body := ctx.Response.Body() 24 | 25 | var m map[string]interface{} 26 | if err := json.Unmarshal(body, &m); err != nil { 27 | t.Fatalf("unexpected error: %s", err) 28 | } 29 | 30 | if _, ok := m["cmdline"]; !ok { 31 | t.Fatalf("cannot locate cmdline expvar") 32 | } 33 | if _, ok := m["memstats"]; !ok { 34 | t.Fatalf("cannot locate memstats expvar") 35 | } 36 | 37 | v := m["customVar"] 38 | sv, ok := v.(string) 39 | if !ok { 40 | t.Fatalf("unexpected custom var type %T. Expecting string", v) 41 | } 42 | if sv != "foobar" { 43 | t.Fatalf("unexpected custom var value: %q. Expecting %q", v, "foobar") 44 | } 45 | 46 | v = m["expvarHandlerCalls"] 47 | fv, ok := v.(float64) 48 | if !ok { 49 | t.Fatalf("unexpected expvarHandlerCalls type %T. Expecting float64", v) 50 | } 51 | if int(fv) != 1 { 52 | t.Fatalf("unexpected value for expvarHandlerCalls: %v. Expecting %v", fv, 1) 53 | } 54 | } 55 | 56 | func TestExpvarHandlerRegexp(t *testing.T) { 57 | var ctx fasthttp.RequestCtx 58 | ctx.QueryArgs().Set("r", "cmd") 59 | ExpvarHandler(&ctx) 60 | body := string(ctx.Response.Body()) 61 | if !strings.Contains(body, `"cmdline"`) { 62 | t.Fatalf("missing 'cmdline' expvar") 63 | } 64 | if strings.Contains(body, `"memstats"`) { 65 | t.Fatalf("unexpected memstats expvar found") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /userdata_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestUserData(t *testing.T) { 10 | t.Parallel() 11 | 12 | var u userData 13 | 14 | for i := 0; i < 10; i++ { 15 | key := []byte(fmt.Sprintf("key_%d", i)) 16 | u.SetBytes(key, i+5) 17 | testUserDataGet(t, &u, key, i+5) 18 | u.SetBytes(key, i) 19 | testUserDataGet(t, &u, key, i) 20 | } 21 | 22 | for i := 0; i < 10; i++ { 23 | key := []byte(fmt.Sprintf("key_%d", i)) 24 | testUserDataGet(t, &u, key, i) 25 | } 26 | 27 | u.Reset() 28 | 29 | for i := 0; i < 10; i++ { 30 | key := []byte(fmt.Sprintf("key_%d", i)) 31 | testUserDataGet(t, &u, key, nil) 32 | } 33 | } 34 | 35 | func testUserDataGet(t *testing.T, u *userData, key []byte, value interface{}) { 36 | v := u.GetBytes(key) 37 | if v == nil && value != nil { 38 | t.Fatalf("cannot obtain value for key=%q", key) 39 | } 40 | if !reflect.DeepEqual(v, value) { 41 | t.Fatalf("unexpected value for key=%q: %d. Expecting %d", key, v, value) 42 | } 43 | } 44 | 45 | func TestUserDataValueClose(t *testing.T) { 46 | t.Parallel() 47 | 48 | var u userData 49 | 50 | closeCalls := 0 51 | 52 | // store values implementing io.Closer 53 | for i := 0; i < 5; i++ { 54 | key := fmt.Sprintf("key_%d", i) 55 | u.Set(key, &closerValue{&closeCalls}) 56 | } 57 | 58 | // store values without io.Closer 59 | for i := 0; i < 10; i++ { 60 | key := fmt.Sprintf("key_noclose_%d", i) 61 | u.Set(key, i) 62 | } 63 | 64 | u.Reset() 65 | 66 | if closeCalls != 5 { 67 | t.Fatalf("unexpected number of Close calls: %d. Expecting 10", closeCalls) 68 | } 69 | } 70 | 71 | type closerValue struct { 72 | closeCalls *int 73 | } 74 | 75 | func (cv *closerValue) Close() error { 76 | (*cv.closeCalls)++ 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /examples/helloworldserver/helloworldserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | var ( 12 | addr = flag.String("addr", ":8080", "TCP address to listen to") 13 | compress = flag.Bool("compress", false, "Whether to enable transparent response compression") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | 19 | h := requestHandler 20 | if *compress { 21 | h = fasthttp.CompressHandler(h) 22 | } 23 | 24 | if err := fasthttp.ListenAndServe(*addr, h); err != nil { 25 | log.Fatalf("Error in ListenAndServe: %s", err) 26 | } 27 | } 28 | 29 | func requestHandler(ctx *fasthttp.RequestCtx) { 30 | fmt.Fprintf(ctx, "Hello, world!\n\n") 31 | 32 | fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method()) 33 | fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI()) 34 | fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path()) 35 | fmt.Fprintf(ctx, "Host is %q\n", ctx.Host()) 36 | fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs()) 37 | fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent()) 38 | fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime()) 39 | fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time()) 40 | fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum()) 41 | fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP()) 42 | 43 | fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request) 44 | 45 | ctx.SetContentType("text/plain; charset=utf8") 46 | 47 | // Set arbitrary headers 48 | ctx.Response.Header.Set("X-My-Header", "my-header-value") 49 | 50 | // Set cookies 51 | var c fasthttp.Cookie 52 | c.SetKey("cookie-name") 53 | c.SetValue("cookie-value") 54 | ctx.Response.Header.SetCookie(&c) 55 | } 56 | -------------------------------------------------------------------------------- /fasthttputil/rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG 3 | 3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U 4 | wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0 5 | FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf 6 | IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg 7 | GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF 8 | sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2 9 | sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D 10 | uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb 11 | K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3 12 | YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+ 13 | DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk 14 | B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV 15 | Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x 16 | IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY 17 | wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj 18 | wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D 19 | FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m 20 | tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX 21 | fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU 22 | ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk 23 | K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT 24 | 6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt 25 | 9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN 26 | Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV 27 | c257YgaWmjK9uB0Y2r2VxS0G 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ssl-cert-snakeoil.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG 3 | 3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U 4 | wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0 5 | FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf 6 | IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg 7 | GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF 8 | sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2 9 | sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D 10 | uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb 11 | K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3 12 | YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+ 13 | DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk 14 | B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV 15 | Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x 16 | IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY 17 | wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj 18 | wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D 19 | FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m 20 | tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX 21 | fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU 22 | ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk 23 | K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT 24 | 6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt 25 | 9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN 26 | Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV 27 | c257YgaWmjK9uB0Y2r2VxS0G 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/fileserver/ssl-cert-snakeoil.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG 3 | 3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U 4 | wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0 5 | FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf 6 | IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg 7 | GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF 8 | sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2 9 | sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D 10 | uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb 11 | K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3 12 | YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+ 13 | DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk 14 | B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV 15 | Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x 16 | IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY 17 | wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj 18 | wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D 19 | FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m 20 | tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX 21 | fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU 22 | ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk 23 | K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT 24 | 6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt 25 | 9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN 26 | Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV 27 | c257YgaWmjK9uB0Y2r2VxS0G 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | # Docker is required for fuzzit regression tests 4 | services: 5 | - docker 6 | 7 | dist: bionic 8 | 9 | os: 10 | - linux 11 | - osx 12 | go: 13 | - tip 14 | - 1.13.x 15 | - 1.12.x 16 | - 1.11.x 17 | - 1.10.x 18 | - 1.9.x 19 | 20 | matrix: 21 | allow_failures: 22 | - tip 23 | fast_finish: true 24 | 25 | env: 26 | global: 27 | secure: "v/F0oI9zE9mcpEp4AVdHzSSHbe5ZFtH6B0i/BiUXKdQRQ10+JMPDOFRJQti7yxjMwltyd/QSFmR50Fl108sQYpo4xdlEXMHp2Y6OAN6crrp6PuHbLYgDWu3df/cH7/BqDyIq1uX8KZEeQssnygYN8hN4tpJCUg+NIb40Lm57Zsodt8DVjjyDWQQFDL7soNyAwGwQIqEyJsn+NUieXWEB1Qnt0xUtPIReuLlrwXR8wC1nLEjG9yz4ftDHHQdhVbO2b+xGWyaJ7QB5ixztaQP8Jnny6kSW9j6zEhJVuzdZ6d3xz23ibCbzSXBHdIUEI9u6ifQj8BYXr8fFS0FB3++IxgAYSs3ybZ+qEwuAxSBBm6YNW+3FrfDknVwTQscjKqnXPisjUqaRC9b31hke0tXzBq1488hE+wxMXeDM4LwWT5IMEO2gz0WGQXxmdVit72DIjCZxJkf1TvZZ0YH7Y//6wJTYYP9xulsy4gqu8CuFdWiF3fiGc3p5DTIS75nJ/Yy76Sa1pRPASKCujfLxtHE6Mt0XKvSolIXklYIzBkjN6vn80N6JIrqtqlimBGPW/Ec6+dwbmRe2AcOKRl4y7pZsGYhJhqdue1mucUYO/e2QeBZJGkqqG+zF5AW0v8x29BHvMwViAonc8o9eelkJ8khYzc/Qeq05pZnR/N/Pqfc+68k=" 28 | 29 | before_install: 30 | - go get -t -v ./... 31 | 32 | jobs: 33 | include: 34 | - stage: test 35 | script: 36 | # build test for supported platforms 37 | - GOOS=linux go build 38 | - GOOS=darwin go build 39 | - GOOS=freebsd go build 40 | - GOOS=windows go build 41 | - GOARCH=386 go build 42 | 43 | # run tests on a standard platform 44 | - go test -v ./... 45 | 46 | # run tests with the race detector as well 47 | - go test -race -v ./... 48 | - stage: fuzzit.dev 49 | os: 50 | - linux 51 | go: 52 | - 1.13 53 | script: 54 | - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./fuzzit.sh fuzzing; fi 55 | - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then ./fuzzit.sh local-regression; fi 56 | -------------------------------------------------------------------------------- /stackless/func.go: -------------------------------------------------------------------------------- 1 | package stackless 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | ) 7 | 8 | // NewFunc returns stackless wrapper for the function f. 9 | // 10 | // Unlike f, the returned stackless wrapper doesn't use stack space 11 | // on the goroutine that calls it. 12 | // The wrapper may save a lot of stack space if the following conditions 13 | // are met: 14 | // 15 | // - f doesn't contain blocking calls on network, I/O or channels; 16 | // - f uses a lot of stack space; 17 | // - the wrapper is called from high number of concurrent goroutines. 18 | // 19 | // The stackless wrapper returns false if the call cannot be processed 20 | // at the moment due to high load. 21 | func NewFunc(f func(ctx interface{})) func(ctx interface{}) bool { 22 | if f == nil { 23 | panic("BUG: f cannot be nil") 24 | } 25 | 26 | funcWorkCh := make(chan *funcWork, runtime.GOMAXPROCS(-1)*2048) 27 | onceInit := func() { 28 | n := runtime.GOMAXPROCS(-1) 29 | for i := 0; i < n; i++ { 30 | go funcWorker(funcWorkCh, f) 31 | } 32 | } 33 | var once sync.Once 34 | 35 | return func(ctx interface{}) bool { 36 | once.Do(onceInit) 37 | fw := getFuncWork() 38 | fw.ctx = ctx 39 | 40 | select { 41 | case funcWorkCh <- fw: 42 | default: 43 | putFuncWork(fw) 44 | return false 45 | } 46 | <-fw.done 47 | putFuncWork(fw) 48 | return true 49 | } 50 | } 51 | 52 | func funcWorker(funcWorkCh <-chan *funcWork, f func(ctx interface{})) { 53 | for fw := range funcWorkCh { 54 | f(fw.ctx) 55 | fw.done <- struct{}{} 56 | } 57 | } 58 | 59 | func getFuncWork() *funcWork { 60 | v := funcWorkPool.Get() 61 | if v == nil { 62 | v = &funcWork{ 63 | done: make(chan struct{}, 1), 64 | } 65 | } 66 | return v.(*funcWork) 67 | } 68 | 69 | func putFuncWork(fw *funcWork) { 70 | fw.ctx = nil 71 | funcWorkPool.Put(fw) 72 | } 73 | 74 | var funcWorkPool sync.Pool 75 | 76 | type funcWork struct { 77 | ctx interface{} 78 | done chan struct{} 79 | } 80 | -------------------------------------------------------------------------------- /stackless/func_test.go: -------------------------------------------------------------------------------- 1 | package stackless 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestNewFuncSimple(t *testing.T) { 11 | var n uint64 12 | f := NewFunc(func(ctx interface{}) { 13 | atomic.AddUint64(&n, uint64(ctx.(int))) 14 | }) 15 | 16 | iterations := 4 * 1024 17 | for i := 0; i < iterations; i++ { 18 | if !f(2) { 19 | t.Fatalf("f mustn't return false") 20 | } 21 | } 22 | if n != uint64(2*iterations) { 23 | t.Fatalf("Unexpected n: %d. Expecting %d", n, 2*iterations) 24 | } 25 | } 26 | 27 | func TestNewFuncMulti(t *testing.T) { 28 | var n1, n2 uint64 29 | f1 := NewFunc(func(ctx interface{}) { 30 | atomic.AddUint64(&n1, uint64(ctx.(int))) 31 | }) 32 | f2 := NewFunc(func(ctx interface{}) { 33 | atomic.AddUint64(&n2, uint64(ctx.(int))) 34 | }) 35 | 36 | iterations := 4 * 1024 37 | 38 | f1Done := make(chan error, 1) 39 | go func() { 40 | var err error 41 | for i := 0; i < iterations; i++ { 42 | if !f1(3) { 43 | err = fmt.Errorf("f1 mustn't return false") 44 | break 45 | } 46 | } 47 | f1Done <- err 48 | }() 49 | 50 | f2Done := make(chan error, 1) 51 | go func() { 52 | var err error 53 | for i := 0; i < iterations; i++ { 54 | if !f2(5) { 55 | err = fmt.Errorf("f2 mustn't return false") 56 | break 57 | } 58 | } 59 | f2Done <- err 60 | }() 61 | 62 | select { 63 | case err := <-f1Done: 64 | if err != nil { 65 | t.Fatalf("unexpected error: %s", err) 66 | } 67 | case <-time.After(time.Second): 68 | t.Fatalf("timeout") 69 | } 70 | 71 | select { 72 | case err := <-f2Done: 73 | if err != nil { 74 | t.Fatalf("unexpected error: %s", err) 75 | } 76 | case <-time.After(time.Second): 77 | t.Fatalf("timeout") 78 | } 79 | 80 | if n1 != uint64(3*iterations) { 81 | t.Fatalf("unexpected n1: %d. Expecting %d", n1, 3*iterations) 82 | } 83 | if n2 != uint64(5*iterations) { 84 | t.Fatalf("unexpected n2: %d. Expecting %d", n2, 5*iterations) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /allocation_test.go: -------------------------------------------------------------------------------- 1 | // +build !race 2 | 3 | package fasthttp 4 | 5 | import ( 6 | "net" 7 | 8 | "testing" 9 | ) 10 | 11 | func TestAllocationServeConn(t *testing.T) { 12 | t.Parallel() 13 | 14 | s := &Server{ 15 | Handler: func(ctx *RequestCtx) { 16 | }, 17 | } 18 | 19 | rw := &readWriter{} 20 | // Make space for the request and response here so it 21 | // doesn't allocate within the test. 22 | rw.r.Grow(1024) 23 | rw.w.Grow(1024) 24 | 25 | n := testing.AllocsPerRun(100, func() { 26 | rw.r.WriteString("GET / HTTP/1.1\r\nHost: google.com\r\nCookie: foo=bar\r\n\r\n") 27 | if err := s.ServeConn(rw); err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | // Reset the write buffer to make space for the next response. 32 | rw.w.Reset() 33 | }) 34 | 35 | if n != 0 { 36 | t.Fatalf("expected 0 allocations, got %f", n) 37 | } 38 | } 39 | 40 | func TestAllocationClient(t *testing.T) { 41 | ln, err := net.Listen("tcp4", "127.0.0.1:0") 42 | if err != nil { 43 | t.Fatalf("cannot listen: %s", err) 44 | } 45 | defer ln.Close() 46 | 47 | s := &Server{ 48 | Handler: func(ctx *RequestCtx) { 49 | }, 50 | } 51 | go s.Serve(ln) //nolint:errcheck 52 | 53 | c := &Client{} 54 | url := "http://test:test@" + ln.Addr().String() + "/foo?bar=baz" 55 | 56 | n := testing.AllocsPerRun(100, func() { 57 | req := AcquireRequest() 58 | res := AcquireResponse() 59 | 60 | req.SetRequestURI(url) 61 | if err := c.Do(req, res); err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | ReleaseRequest(req) 66 | ReleaseResponse(res) 67 | }) 68 | 69 | if n != 0 { 70 | t.Fatalf("expected 0 allocations, got %f", n) 71 | } 72 | } 73 | 74 | func TestAllocationURI(t *testing.T) { 75 | t.Parallel() 76 | 77 | uri := []byte("http://username:password@example.com/some/path?foo=bar#test") 78 | 79 | n := testing.AllocsPerRun(100, func() { 80 | u := AcquireURI() 81 | u.Parse(nil, uri) 82 | ReleaseURI(u) 83 | }) 84 | 85 | if n != 0 { 86 | t.Fatalf("expected 0 allocations, got %f", n) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /peripconn.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | type perIPConnCounter struct { 10 | pool sync.Pool 11 | lock sync.Mutex 12 | m map[uint32]int 13 | } 14 | 15 | func (cc *perIPConnCounter) Register(ip uint32) int { 16 | cc.lock.Lock() 17 | if cc.m == nil { 18 | cc.m = make(map[uint32]int) 19 | } 20 | n := cc.m[ip] + 1 21 | cc.m[ip] = n 22 | cc.lock.Unlock() 23 | return n 24 | } 25 | 26 | func (cc *perIPConnCounter) Unregister(ip uint32) { 27 | cc.lock.Lock() 28 | if cc.m == nil { 29 | cc.lock.Unlock() 30 | panic("BUG: perIPConnCounter.Register() wasn't called") 31 | } 32 | n := cc.m[ip] - 1 33 | if n < 0 { 34 | cc.lock.Unlock() 35 | panic(fmt.Sprintf("BUG: negative per-ip counter=%d for ip=%d", n, ip)) 36 | } 37 | cc.m[ip] = n 38 | cc.lock.Unlock() 39 | } 40 | 41 | type perIPConn struct { 42 | net.Conn 43 | 44 | ip uint32 45 | perIPConnCounter *perIPConnCounter 46 | } 47 | 48 | func acquirePerIPConn(conn net.Conn, ip uint32, counter *perIPConnCounter) *perIPConn { 49 | v := counter.pool.Get() 50 | if v == nil { 51 | v = &perIPConn{ 52 | perIPConnCounter: counter, 53 | } 54 | } 55 | c := v.(*perIPConn) 56 | c.Conn = conn 57 | c.ip = ip 58 | return c 59 | } 60 | 61 | func releasePerIPConn(c *perIPConn) { 62 | c.Conn = nil 63 | c.perIPConnCounter.pool.Put(c) 64 | } 65 | 66 | func (c *perIPConn) Close() error { 67 | err := c.Conn.Close() 68 | c.perIPConnCounter.Unregister(c.ip) 69 | releasePerIPConn(c) 70 | return err 71 | } 72 | 73 | func getUint32IP(c net.Conn) uint32 { 74 | return ip2uint32(getConnIP4(c)) 75 | } 76 | 77 | func getConnIP4(c net.Conn) net.IP { 78 | addr := c.RemoteAddr() 79 | ipAddr, ok := addr.(*net.TCPAddr) 80 | if !ok { 81 | return net.IPv4zero 82 | } 83 | return ipAddr.IP.To4() 84 | } 85 | 86 | func ip2uint32(ip net.IP) uint32 { 87 | if len(ip) != 4 { 88 | return 0 89 | } 90 | return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3]) 91 | } 92 | 93 | func uint322ip(ip uint32) net.IP { 94 | b := make([]byte, 4) 95 | b[0] = byte(ip >> 24) 96 | b[1] = byte(ip >> 16) 97 | b[2] = byte(ip >> 8) 98 | b[3] = byte(ip) 99 | return b 100 | } 101 | -------------------------------------------------------------------------------- /examples/fileserver/README.md: -------------------------------------------------------------------------------- 1 | # Static file server example 2 | 3 | * Serves files from the given directory. 4 | * Supports transparent response compression. 5 | * Supports byte range responses. 6 | * Generates directory index pages. 7 | * Supports TLS (aka SSL or HTTPS). 8 | * Supports virtual hosts. 9 | * Exports various stats on /stats path. 10 | 11 | # How to build 12 | 13 | ``` 14 | make 15 | ``` 16 | 17 | # How to run 18 | 19 | ``` 20 | ./fileserver -h 21 | ./fileserver -addr=tcp.addr.to.listen:to -dir=/path/to/directory/to/serve 22 | ``` 23 | 24 | # fileserver vs nginx performance comparison 25 | 26 | Serving default nginx path (`/usr/share/nginx/html` on ubuntu). 27 | 28 | * nginx 29 | 30 | ``` 31 | $ ./wrk -t 4 -c 16 -d 10 http://localhost:80 32 | Running 10s test @ http://localhost:80 33 | 4 threads and 16 connections 34 | Thread Stats Avg Stdev Max +/- Stdev 35 | Latency 397.76us 1.08ms 20.23ms 95.19% 36 | Req/Sec 21.20k 2.49k 31.34k 79.65% 37 | 850220 requests in 10.10s, 695.65MB read 38 | Requests/sec: 84182.71 39 | Transfer/sec: 68.88MB 40 | ``` 41 | 42 | * fileserver 43 | 44 | ``` 45 | $ ./wrk -t 4 -c 16 -d 10 http://localhost:8080 46 | Running 10s test @ http://localhost:8080 47 | 4 threads and 16 connections 48 | Thread Stats Avg Stdev Max +/- Stdev 49 | Latency 447.99us 1.59ms 27.20ms 94.79% 50 | Req/Sec 37.13k 3.99k 47.86k 76.00% 51 | 1478457 requests in 10.02s, 1.03GB read 52 | Requests/sec: 147597.06 53 | Transfer/sec: 105.15MB 54 | ``` 55 | 56 | 8 pipelined requests 57 | 58 | * nginx 59 | 60 | ``` 61 | $ ./wrk -s pipeline.lua -t 4 -c 16 -d 10 http://localhost:80 -- 8 62 | Running 10s test @ http://localhost:80 63 | 4 threads and 16 connections 64 | Thread Stats Avg Stdev Max +/- Stdev 65 | Latency 1.34ms 2.15ms 30.91ms 92.16% 66 | Req/Sec 33.54k 7.36k 108.12k 76.81% 67 | 1339908 requests in 10.10s, 1.07GB read 68 | Requests/sec: 132705.81 69 | Transfer/sec: 108.58MB 70 | ``` 71 | 72 | * fileserver 73 | 74 | ``` 75 | $ ./wrk -s pipeline.lua -t 4 -c 16 -d 10 http://localhost:8080 -- 8 76 | Running 10s test @ http://localhost:8080 77 | 4 threads and 16 connections 78 | Thread Stats Avg Stdev Max +/- Stdev 79 | Latency 2.08ms 6.33ms 88.26ms 92.83% 80 | Req/Sec 116.54k 14.66k 167.98k 69.00% 81 | 4642226 requests in 10.03s, 3.23GB read 82 | Requests/sec: 462769.41 83 | Transfer/sec: 329.67MB 84 | ``` 85 | -------------------------------------------------------------------------------- /prefork/README.md: -------------------------------------------------------------------------------- 1 | # Prefork 2 | 3 | Server prefork implementation. 4 | 5 | Preforks master process between several child processes increases performance, because Go doesn't have to share and manage memory between cores. 6 | 7 | **WARNING: using prefork prevents the use of any global state!. Things like in-memory caches won't work.** 8 | 9 | - How it works: 10 | 11 | ```go 12 | import ( 13 | "github.com/valyala/fasthttp" 14 | "github.com/valyala/fasthttp/prefork" 15 | ) 16 | 17 | server := &fasthttp.Server{ 18 | // Your configuration 19 | } 20 | 21 | // Wraps the server with prefork 22 | preforkServer := prefork.New(server) 23 | 24 | if err := preforkServer.ListenAndServe(":8080"); err != nil { 25 | panic(err) 26 | } 27 | ``` 28 | 29 | ## Benchmarks 30 | 31 | Environment: 32 | 33 | - Machine: MacBook Pro 13-inch, 2017 34 | - OS: MacOS 10.15.3 35 | - Go: go1.13.6 darwin/amd64 36 | 37 | Handler code: 38 | 39 | ```go 40 | func requestHandler(ctx *fasthttp.RequestCtx) { 41 | // Simulates some hard work 42 | time.Sleep(100 * time.Millisecond) 43 | } 44 | ``` 45 | 46 | Test command: 47 | 48 | ```bash 49 | $ wrk -H 'Host: localhost' -H 'Accept: text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7' -H 'Connection: keep-alive' --latency -d 15 -c 512 --timeout 8 -t 4 http://localhost:8080 50 | ``` 51 | 52 | Results: 53 | 54 | - prefork 55 | 56 | ```bash 57 | Running 15s test @ http://localhost:8080 58 | 4 threads and 512 connections 59 | Thread Stats Avg Stdev Max +/- Stdev 60 | Latency 4.75ms 4.27ms 126.24ms 97.45% 61 | Req/Sec 26.46k 4.16k 71.18k 88.72% 62 | Latency Distribution 63 | 50% 4.55ms 64 | 75% 4.82ms 65 | 90% 5.46ms 66 | 99% 15.49ms 67 | 1581916 requests in 15.09s, 140.30MB read 68 | Socket errors: connect 0, read 318, write 0, timeout 0 69 | Requests/sec: 104861.58 70 | Transfer/sec: 9.30MB 71 | ``` 72 | 73 | - **non**-prefork 74 | 75 | ```bash 76 | Running 15s test @ http://localhost:8080 77 | 4 threads and 512 connections 78 | Thread Stats Avg Stdev Max +/- Stdev 79 | Latency 6.42ms 11.83ms 177.19ms 96.42% 80 | Req/Sec 24.96k 5.83k 56.83k 82.93% 81 | Latency Distribution 82 | 50% 4.53ms 83 | 75% 4.93ms 84 | 90% 6.94ms 85 | 99% 74.54ms 86 | 1472441 requests in 15.09s, 130.59MB read 87 | Socket errors: connect 0, read 265, write 0, timeout 0 88 | Requests/sec: 97553.34 89 | Transfer/sec: 8.65MB 90 | ``` 91 | -------------------------------------------------------------------------------- /fasthttputil/inmemory_listener.go: -------------------------------------------------------------------------------- 1 | package fasthttputil 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | // ErrInmemoryListenerClosed indicates that the InmemoryListener is already closed. 10 | var ErrInmemoryListenerClosed = errors.New("InmemoryListener is already closed: use of closed network connection") 11 | 12 | // InmemoryListener provides in-memory dialer<->net.Listener implementation. 13 | // 14 | // It may be used either for fast in-process client<->server communications 15 | // without network stack overhead or for client<->server tests. 16 | type InmemoryListener struct { 17 | lock sync.Mutex 18 | closed bool 19 | conns chan acceptConn 20 | } 21 | 22 | type acceptConn struct { 23 | conn net.Conn 24 | accepted chan struct{} 25 | } 26 | 27 | // NewInmemoryListener returns new in-memory dialer<->net.Listener. 28 | func NewInmemoryListener() *InmemoryListener { 29 | return &InmemoryListener{ 30 | conns: make(chan acceptConn, 1024), 31 | } 32 | } 33 | 34 | // Accept implements net.Listener's Accept. 35 | // 36 | // It is safe calling Accept from concurrently running goroutines. 37 | // 38 | // Accept returns new connection per each Dial call. 39 | func (ln *InmemoryListener) Accept() (net.Conn, error) { 40 | c, ok := <-ln.conns 41 | if !ok { 42 | return nil, ErrInmemoryListenerClosed 43 | } 44 | close(c.accepted) 45 | return c.conn, nil 46 | } 47 | 48 | // Close implements net.Listener's Close. 49 | func (ln *InmemoryListener) Close() error { 50 | var err error 51 | 52 | ln.lock.Lock() 53 | if !ln.closed { 54 | close(ln.conns) 55 | ln.closed = true 56 | } else { 57 | err = ErrInmemoryListenerClosed 58 | } 59 | ln.lock.Unlock() 60 | return err 61 | } 62 | 63 | // Addr implements net.Listener's Addr. 64 | func (ln *InmemoryListener) Addr() net.Addr { 65 | return &net.UnixAddr{ 66 | Name: "InmemoryListener", 67 | Net: "memory", 68 | } 69 | } 70 | 71 | // Dial creates new client<->server connection. 72 | // Just like a real Dial it only returns once the server 73 | // has accepted the connection. 74 | // 75 | // It is safe calling Dial from concurrently running goroutines. 76 | func (ln *InmemoryListener) Dial() (net.Conn, error) { 77 | pc := NewPipeConns() 78 | cConn := pc.Conn1() 79 | sConn := pc.Conn2() 80 | ln.lock.Lock() 81 | accepted := make(chan struct{}) 82 | if !ln.closed { 83 | ln.conns <- acceptConn{sConn, accepted} 84 | // Wait until the connection has been accepted. 85 | <-accepted 86 | } else { 87 | sConn.Close() 88 | cConn.Close() 89 | cConn = nil 90 | } 91 | ln.lock.Unlock() 92 | 93 | if cConn == nil { 94 | return nil, ErrInmemoryListenerClosed 95 | } 96 | return cConn, nil 97 | } 98 | -------------------------------------------------------------------------------- /stream_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestNewStreamReader(t *testing.T) { 13 | t.Parallel() 14 | 15 | ch := make(chan struct{}) 16 | r := NewStreamReader(func(w *bufio.Writer) { 17 | fmt.Fprintf(w, "Hello, world\n") 18 | fmt.Fprintf(w, "Line #2\n") 19 | close(ch) 20 | }) 21 | 22 | data, err := ioutil.ReadAll(r) 23 | if err != nil { 24 | t.Fatalf("unexpected error: %s", err) 25 | } 26 | expectedData := "Hello, world\nLine #2\n" 27 | if string(data) != expectedData { 28 | t.Fatalf("unexpected data %q. Expecting %q", data, expectedData) 29 | } 30 | 31 | if err = r.Close(); err != nil { 32 | t.Fatalf("unexpected error") 33 | } 34 | 35 | select { 36 | case <-ch: 37 | case <-time.After(time.Second): 38 | t.Fatalf("timeout") 39 | } 40 | } 41 | 42 | func TestStreamReaderClose(t *testing.T) { 43 | t.Parallel() 44 | 45 | firstLine := "the first line must pass" 46 | ch := make(chan error, 1) 47 | r := NewStreamReader(func(w *bufio.Writer) { 48 | fmt.Fprintf(w, "%s", firstLine) 49 | if err := w.Flush(); err != nil { 50 | ch <- fmt.Errorf("unexpected error on first flush: %s", err) 51 | return 52 | } 53 | 54 | data := createFixedBody(4000) 55 | for i := 0; i < 100; i++ { 56 | w.Write(data) //nolint:errcheck 57 | } 58 | if err := w.Flush(); err == nil { 59 | ch <- fmt.Errorf("expecting error on the second flush") 60 | } 61 | ch <- nil 62 | }) 63 | 64 | buf := make([]byte, len(firstLine)) 65 | n, err := io.ReadFull(r, buf) 66 | if err != nil { 67 | t.Fatalf("unexpected error: %s", err) 68 | } 69 | if n != len(buf) { 70 | t.Fatalf("unexpected number of bytes read: %d. Expecting %d", n, len(buf)) 71 | } 72 | if string(buf) != firstLine { 73 | t.Fatalf("unexpected result: %q. Expecting %q", buf, firstLine) 74 | } 75 | 76 | if err := r.Close(); err != nil { 77 | t.Fatalf("unexpected error: %s", err) 78 | } 79 | 80 | select { 81 | case err := <-ch: 82 | if err != nil { 83 | t.Fatalf("error returned from stream reader: %s", err) 84 | } 85 | case <-time.After(time.Second): 86 | t.Fatalf("timeout when waiting for stream reader") 87 | } 88 | 89 | // read trailing data 90 | go func() { 91 | if _, err := ioutil.ReadAll(r); err != nil { 92 | ch <- fmt.Errorf("unexpected error when reading trailing data: %s", err) 93 | return 94 | } 95 | ch <- nil 96 | }() 97 | 98 | select { 99 | case err := <-ch: 100 | if err != nil { 101 | t.Fatalf("error returned when reading tail data: %s", err) 102 | } 103 | case <-time.After(time.Second): 104 | t.Fatalf("timeout when reading tail data") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /header_regression_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestIssue28ResponseWithoutBodyNoContentType(t *testing.T) { 12 | t.Parallel() 13 | 14 | var r Response 15 | 16 | // Empty response without content-type 17 | s := r.String() 18 | if strings.Contains(s, "Content-Type") { 19 | t.Fatalf("unexpected Content-Type found in response header with empty body: %q", s) 20 | } 21 | 22 | // Explicitly set content-type 23 | r.Header.SetContentType("foo/bar") 24 | s = r.String() 25 | if !strings.Contains(s, "Content-Type: foo/bar\r\n") { 26 | t.Fatalf("missing explicitly set content-type for empty response: %q", s) 27 | } 28 | 29 | // Non-empty response. 30 | r.Reset() 31 | r.SetBodyString("foobar") 32 | s = r.String() 33 | if !strings.Contains(s, fmt.Sprintf("Content-Type: %s\r\n", defaultContentType)) { 34 | t.Fatalf("missing default content-type for non-empty response: %q", s) 35 | } 36 | 37 | // Non-empty response with custom content-type. 38 | r.Header.SetContentType("aaa/bbb") 39 | s = r.String() 40 | if !strings.Contains(s, "Content-Type: aaa/bbb\r\n") { 41 | t.Fatalf("missing custom content-type: %q", s) 42 | } 43 | } 44 | 45 | func TestIssue6RequestHeaderSetContentType(t *testing.T) { 46 | t.Parallel() 47 | 48 | testIssue6RequestHeaderSetContentType(t, MethodGet) 49 | testIssue6RequestHeaderSetContentType(t, MethodPost) 50 | testIssue6RequestHeaderSetContentType(t, MethodPut) 51 | testIssue6RequestHeaderSetContentType(t, MethodPatch) 52 | } 53 | 54 | func testIssue6RequestHeaderSetContentType(t *testing.T, method string) { 55 | contentType := "application/json" 56 | contentLength := 123 57 | 58 | var h RequestHeader 59 | h.SetMethod(method) 60 | h.SetRequestURI("http://localhost/test") 61 | h.SetContentType(contentType) 62 | h.SetContentLength(contentLength) 63 | 64 | issue6VerifyRequestHeader(t, &h, contentType, contentLength, method) 65 | 66 | s := h.String() 67 | 68 | var h1 RequestHeader 69 | 70 | br := bufio.NewReader(bytes.NewBufferString(s)) 71 | if err := h1.Read(br); err != nil { 72 | t.Fatalf("unexpected error: %s", err) 73 | } 74 | issue6VerifyRequestHeader(t, &h1, contentType, contentLength, method) 75 | } 76 | 77 | func issue6VerifyRequestHeader(t *testing.T, h *RequestHeader, contentType string, contentLength int, method string) { 78 | if string(h.ContentType()) != contentType { 79 | t.Fatalf("unexpected content-type: %q. Expecting %q. method=%q", h.ContentType(), contentType, method) 80 | } 81 | if string(h.Method()) != method { 82 | t.Fatalf("unexpected method: %q. Expecting %q", h.Method(), method) 83 | } 84 | if h.ContentLength() != contentLength { 85 | t.Fatalf("unexpected content-length: %d. Expecting %d. method=%q", h.ContentLength(), contentLength, method) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /bytesconv_table_gen.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | ) 11 | 12 | const ( 13 | toLower = 'a' - 'A' 14 | ) 15 | 16 | func main() { 17 | hex2intTable := func() [256]byte { 18 | var b [256]byte 19 | for i := 0; i < 256; i++ { 20 | c := byte(16) 21 | if i >= '0' && i <= '9' { 22 | c = byte(i) - '0' 23 | } else if i >= 'a' && i <= 'f' { 24 | c = byte(i) - 'a' + 10 25 | } else if i >= 'A' && i <= 'F' { 26 | c = byte(i) - 'A' + 10 27 | } 28 | b[i] = c 29 | } 30 | return b 31 | }() 32 | 33 | toLowerTable := func() [256]byte { 34 | var a [256]byte 35 | for i := 0; i < 256; i++ { 36 | c := byte(i) 37 | if c >= 'A' && c <= 'Z' { 38 | c += toLower 39 | } 40 | a[i] = c 41 | } 42 | return a 43 | }() 44 | 45 | toUpperTable := func() [256]byte { 46 | var a [256]byte 47 | for i := 0; i < 256; i++ { 48 | c := byte(i) 49 | if c >= 'a' && c <= 'z' { 50 | c -= toLower 51 | } 52 | a[i] = c 53 | } 54 | return a 55 | }() 56 | 57 | quotedArgShouldEscapeTable := func() [256]byte { 58 | // According to RFC 3986 §2.3 59 | var a [256]byte 60 | for i := 0; i < 256; i++ { 61 | a[i] = 1 62 | } 63 | 64 | // ALPHA 65 | for i := int('a'); i <= int('z'); i++ { 66 | a[i] = 0 67 | } 68 | for i := int('A'); i <= int('Z'); i++ { 69 | a[i] = 0 70 | } 71 | 72 | // DIGIT 73 | for i := int('0'); i <= int('9'); i++ { 74 | a[i] = 0 75 | } 76 | 77 | // Unreserved characters 78 | for _, v := range `-_.~` { 79 | a[v] = 0 80 | } 81 | 82 | return a 83 | }() 84 | 85 | quotedPathShouldEscapeTable := func() [256]byte { 86 | // The implementation here equal to net/url shouldEscape(s, encodePath) 87 | // 88 | // The RFC allows : @ & = + $ but saves / ; , for assigning 89 | // meaning to individual path segments. This package 90 | // only manipulates the path as a whole, so we allow those 91 | // last three as well. That leaves only ? to escape. 92 | var a = quotedArgShouldEscapeTable 93 | 94 | for _, v := range `$&+,/:;=@` { 95 | a[v] = 0 96 | } 97 | 98 | return a 99 | }() 100 | 101 | w := new(bytes.Buffer) 102 | w.WriteString(pre) 103 | fmt.Fprintf(w, "const hex2intTable = %q\n", hex2intTable) 104 | fmt.Fprintf(w, "const toLowerTable = %q\n", toLowerTable) 105 | fmt.Fprintf(w, "const toUpperTable = %q\n", toUpperTable) 106 | fmt.Fprintf(w, "const quotedArgShouldEscapeTable = %q\n", quotedArgShouldEscapeTable) 107 | fmt.Fprintf(w, "const quotedPathShouldEscapeTable = %q\n", quotedPathShouldEscapeTable) 108 | 109 | if err := ioutil.WriteFile("bytesconv_table.go", w.Bytes(), 0660); err != nil { 110 | log.Fatal(err) 111 | } 112 | } 113 | 114 | const pre = `package fasthttp 115 | 116 | // Code generated by go run bytesconv_table_gen.go; DO NOT EDIT. 117 | // See bytesconv_table_gen.go for more information about these tables. 118 | 119 | ` 120 | -------------------------------------------------------------------------------- /stackless/writer.go: -------------------------------------------------------------------------------- 1 | package stackless 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/valyala/bytebufferpool" 9 | ) 10 | 11 | // Writer is an interface stackless writer must conform to. 12 | // 13 | // The interface contains common subset for Writers from compress/* packages. 14 | type Writer interface { 15 | Write(p []byte) (int, error) 16 | Flush() error 17 | Close() error 18 | Reset(w io.Writer) 19 | } 20 | 21 | // NewWriterFunc must return new writer that will be wrapped into 22 | // stackless writer. 23 | type NewWriterFunc func(w io.Writer) Writer 24 | 25 | // NewWriter creates a stackless writer around a writer returned 26 | // from newWriter. 27 | // 28 | // The returned writer writes data to dstW. 29 | // 30 | // Writers that use a lot of stack space may be wrapped into stackless writer, 31 | // thus saving stack space for high number of concurrently running goroutines. 32 | func NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer { 33 | w := &writer{ 34 | dstW: dstW, 35 | } 36 | w.zw = newWriter(&w.xw) 37 | return w 38 | } 39 | 40 | type writer struct { 41 | dstW io.Writer 42 | zw Writer 43 | xw xWriter 44 | 45 | err error 46 | n int 47 | 48 | p []byte 49 | op op 50 | } 51 | 52 | type op int 53 | 54 | const ( 55 | opWrite op = iota 56 | opFlush 57 | opClose 58 | opReset 59 | ) 60 | 61 | func (w *writer) Write(p []byte) (int, error) { 62 | w.p = p 63 | err := w.do(opWrite) 64 | w.p = nil 65 | return w.n, err 66 | } 67 | 68 | func (w *writer) Flush() error { 69 | return w.do(opFlush) 70 | } 71 | 72 | func (w *writer) Close() error { 73 | return w.do(opClose) 74 | } 75 | 76 | func (w *writer) Reset(dstW io.Writer) { 77 | w.xw.Reset() 78 | w.do(opReset) //nolint:errcheck 79 | w.dstW = dstW 80 | } 81 | 82 | func (w *writer) do(op op) error { 83 | w.op = op 84 | if !stacklessWriterFunc(w) { 85 | return errHighLoad 86 | } 87 | err := w.err 88 | if err != nil { 89 | return err 90 | } 91 | if w.xw.bb != nil && len(w.xw.bb.B) > 0 { 92 | _, err = w.dstW.Write(w.xw.bb.B) 93 | } 94 | w.xw.Reset() 95 | 96 | return err 97 | } 98 | 99 | var errHighLoad = errors.New("cannot compress data due to high load") 100 | 101 | var stacklessWriterFunc = NewFunc(writerFunc) 102 | 103 | func writerFunc(ctx interface{}) { 104 | w := ctx.(*writer) 105 | switch w.op { 106 | case opWrite: 107 | w.n, w.err = w.zw.Write(w.p) 108 | case opFlush: 109 | w.err = w.zw.Flush() 110 | case opClose: 111 | w.err = w.zw.Close() 112 | case opReset: 113 | w.zw.Reset(&w.xw) 114 | w.err = nil 115 | default: 116 | panic(fmt.Sprintf("BUG: unexpected op: %d", w.op)) 117 | } 118 | } 119 | 120 | type xWriter struct { 121 | bb *bytebufferpool.ByteBuffer 122 | } 123 | 124 | func (w *xWriter) Write(p []byte) (int, error) { 125 | if w.bb == nil { 126 | w.bb = bufferPool.Get() 127 | } 128 | return w.bb.Write(p) 129 | } 130 | 131 | func (w *xWriter) Reset() { 132 | if w.bb != nil { 133 | bufferPool.Put(w.bb) 134 | w.bb = nil 135 | } 136 | } 137 | 138 | var bufferPool bytebufferpool.Pool 139 | -------------------------------------------------------------------------------- /examples/multidomain/multidomain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "fmt" 10 | "math/big" 11 | "time" 12 | 13 | "github.com/valyala/fasthttp" 14 | ) 15 | 16 | var domains = make(map[string]fasthttp.RequestHandler) 17 | 18 | func main() { 19 | server := &fasthttp.Server{ 20 | // You can check the access using openssl command: 21 | // $ openssl s_client -connect localhost:8080 << EOF 22 | // > GET / 23 | // > Host: localhost 24 | // > EOF 25 | // 26 | // $ openssl s_client -connect localhost:8080 << EOF 27 | // > GET / 28 | // > Host: 127.0.0.1:8080 29 | // > EOF 30 | // 31 | Handler: func(ctx *fasthttp.RequestCtx) { 32 | h, ok := domains[string(ctx.Host())] 33 | if !ok { 34 | ctx.NotFound() 35 | return 36 | } 37 | h(ctx) 38 | }, 39 | } 40 | 41 | // preparing first host 42 | cert, priv, err := GenerateCert("localhost:8080") 43 | if err != nil { 44 | panic(err) 45 | } 46 | domains["localhost:8080"] = func(ctx *fasthttp.RequestCtx) { 47 | ctx.Write([]byte("You are accessing to localhost:8080\n")) 48 | } 49 | 50 | err = server.AppendCertEmbed(cert, priv) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | // preparing second host 56 | cert, priv, err = GenerateCert("127.0.0.1") 57 | if err != nil { 58 | panic(err) 59 | } 60 | domains["127.0.0.1:8080"] = func(ctx *fasthttp.RequestCtx) { 61 | ctx.Write([]byte("You are accessing to 127.0.0.1:8080\n")) 62 | } 63 | 64 | err = server.AppendCertEmbed(cert, priv) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | fmt.Println(server.ListenAndServeTLS(":8080", "", "")) 70 | } 71 | 72 | // GenerateCert generates certificate and private key based on the given host. 73 | func GenerateCert(host string) ([]byte, []byte, error) { 74 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 75 | if err != nil { 76 | return nil, nil, err 77 | } 78 | 79 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 80 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 81 | if err != nil { 82 | return nil, nil, err 83 | } 84 | 85 | cert := &x509.Certificate{ 86 | SerialNumber: serialNumber, 87 | Subject: pkix.Name{ 88 | Organization: []string{"I have your data"}, 89 | }, 90 | NotBefore: time.Now(), 91 | NotAfter: time.Now().Add(365 * 24 * time.Hour), 92 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, 93 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 94 | SignatureAlgorithm: x509.SHA256WithRSA, 95 | DNSNames: []string{host}, 96 | BasicConstraintsValid: true, 97 | IsCA: true, 98 | } 99 | 100 | certBytes, err := x509.CreateCertificate( 101 | rand.Reader, cert, cert, &priv.PublicKey, priv, 102 | ) 103 | 104 | p := pem.EncodeToMemory( 105 | &pem.Block{ 106 | Type: "PRIVATE KEY", 107 | Bytes: x509.MarshalPKCS1PrivateKey(priv), 108 | }, 109 | ) 110 | 111 | b := pem.EncodeToMemory( 112 | &pem.Block{ 113 | Type: "CERTIFICATE", 114 | Bytes: certBytes, 115 | }, 116 | ) 117 | 118 | return b, p, err 119 | } 120 | -------------------------------------------------------------------------------- /stackless/writer_test.go: -------------------------------------------------------------------------------- 1 | package stackless 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "compress/gzip" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestCompressFlateSerial(t *testing.T) { 15 | if err := testCompressFlate(); err != nil { 16 | t.Fatalf("unexpected error: %s", err) 17 | } 18 | } 19 | 20 | func TestCompressFlateConcurrent(t *testing.T) { 21 | if err := testConcurrent(testCompressFlate, 10); err != nil { 22 | t.Fatalf("unexpected error: %s", err) 23 | } 24 | } 25 | 26 | func testCompressFlate() error { 27 | return testWriter(func(w io.Writer) Writer { 28 | zw, err := flate.NewWriter(w, flate.DefaultCompression) 29 | if err != nil { 30 | panic(fmt.Sprintf("BUG: unexpected error: %s", err)) 31 | } 32 | return zw 33 | }, func(r io.Reader) io.Reader { 34 | return flate.NewReader(r) 35 | }) 36 | } 37 | 38 | func TestCompressGzipSerial(t *testing.T) { 39 | if err := testCompressGzip(); err != nil { 40 | t.Fatalf("unexpected error: %s", err) 41 | } 42 | } 43 | 44 | func TestCompressGzipConcurrent(t *testing.T) { 45 | if err := testConcurrent(testCompressGzip, 10); err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | } 49 | 50 | func testCompressGzip() error { 51 | return testWriter(func(w io.Writer) Writer { 52 | return gzip.NewWriter(w) 53 | }, func(r io.Reader) io.Reader { 54 | zr, err := gzip.NewReader(r) 55 | if err != nil { 56 | panic(fmt.Sprintf("BUG: cannot create gzip reader: %s", err)) 57 | } 58 | return zr 59 | }) 60 | } 61 | 62 | func testWriter(newWriter NewWriterFunc, newReader func(io.Reader) io.Reader) error { 63 | dstW := &bytes.Buffer{} 64 | w := NewWriter(dstW, newWriter) 65 | 66 | for i := 0; i < 5; i++ { 67 | if err := testWriterReuse(w, dstW, newReader); err != nil { 68 | return fmt.Errorf("unexpected error when re-using writer on iteration %d: %s", i, err) 69 | } 70 | dstW = &bytes.Buffer{} 71 | w.Reset(dstW) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader) error { 78 | wantW := &bytes.Buffer{} 79 | mw := io.MultiWriter(w, wantW) 80 | for i := 0; i < 30; i++ { 81 | fmt.Fprintf(mw, "foobar %d\n", i) 82 | if i%13 == 0 { 83 | if err := w.Flush(); err != nil { 84 | return fmt.Errorf("error on flush: %s", err) 85 | } 86 | } 87 | } 88 | w.Close() 89 | 90 | zr := newReader(r) 91 | data, err := ioutil.ReadAll(zr) 92 | if err != nil { 93 | return fmt.Errorf("unexpected error: %s, data=%q", err, data) 94 | } 95 | 96 | wantData := wantW.Bytes() 97 | if !bytes.Equal(data, wantData) { 98 | return fmt.Errorf("unexpected data: %q. Expecting %q", data, wantData) 99 | } 100 | 101 | return nil 102 | } 103 | 104 | func testConcurrent(testFunc func() error, concurrency int) error { 105 | ch := make(chan error, concurrency) 106 | for i := 0; i < concurrency; i++ { 107 | go func() { 108 | ch <- testFunc() 109 | }() 110 | } 111 | for i := 0; i < concurrency; i++ { 112 | select { 113 | case err := <-ch: 114 | if err != nil { 115 | return fmt.Errorf("unexpected error on goroutine %d: %s", i, err) 116 | } 117 | case <-time.After(time.Second): 118 | return fmt.Errorf("timeout on goroutine %d", i) 119 | } 120 | } 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /reuseport/reuseport_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package reuseport 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestTCP4(t *testing.T) { 14 | testNewListener(t, "tcp4", "localhost:10081", 20, 1000) 15 | } 16 | 17 | func TestTCP6(t *testing.T) { 18 | // Run this test only if tcp6 interface exists. 19 | if hasLocalIPv6(t) { 20 | testNewListener(t, "tcp6", "[::1]:10082", 20, 1000) 21 | } 22 | } 23 | 24 | func hasLocalIPv6(t *testing.T) bool { 25 | addrs, err := net.InterfaceAddrs() 26 | if err != nil { 27 | t.Fatalf("cannot obtain local interfaces: %s", err) 28 | } 29 | for _, a := range addrs { 30 | if a.String() == "::1/128" { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | func testNewListener(t *testing.T, network, addr string, serversCount, requestsCount int) { 38 | 39 | var lns []net.Listener 40 | doneCh := make(chan struct{}, serversCount) 41 | 42 | for i := 0; i < serversCount; i++ { 43 | ln, err := Listen(network, addr) 44 | if err != nil { 45 | t.Fatalf("cannot create listener %d: %s", i, err) 46 | } 47 | go func() { 48 | serveEcho(t, ln) 49 | doneCh <- struct{}{} 50 | }() 51 | lns = append(lns, ln) 52 | } 53 | 54 | for i := 0; i < requestsCount; i++ { 55 | c, err := net.Dial(network, addr) 56 | if err != nil { 57 | t.Fatalf("%d. unexpected error when dialing: %s", i, err) 58 | } 59 | req := fmt.Sprintf("request number %d", i) 60 | if _, err = c.Write([]byte(req)); err != nil { 61 | t.Fatalf("%d. unexpected error when writing request: %s", i, err) 62 | } 63 | if err = c.(*net.TCPConn).CloseWrite(); err != nil { 64 | t.Fatalf("%d. unexpected error when closing write end of the connection: %s", i, err) 65 | } 66 | 67 | var resp []byte 68 | ch := make(chan struct{}) 69 | go func() { 70 | if resp, err = ioutil.ReadAll(c); err != nil { 71 | t.Fatalf("%d. unexpected error when reading response: %s", i, err) 72 | } 73 | close(ch) 74 | }() 75 | select { 76 | case <-ch: 77 | case <-time.After(200 * time.Millisecond): 78 | t.Fatalf("%d. timeout when waiting for response", i) 79 | } 80 | 81 | if string(resp) != req { 82 | t.Fatalf("%d. unexpected response %q. Expecting %q", i, resp, req) 83 | } 84 | if err = c.Close(); err != nil { 85 | t.Fatalf("%d. unexpected error when closing connection: %s", i, err) 86 | } 87 | } 88 | 89 | for _, ln := range lns { 90 | if err := ln.Close(); err != nil { 91 | t.Fatalf("unexpected error when closing listener: %s", err) 92 | } 93 | } 94 | 95 | for i := 0; i < serversCount; i++ { 96 | select { 97 | case <-doneCh: 98 | case <-time.After(200 * time.Millisecond): 99 | t.Fatalf("timeout when waiting for servers to be closed") 100 | } 101 | } 102 | } 103 | 104 | func serveEcho(t *testing.T, ln net.Listener) { 105 | for { 106 | c, err := ln.Accept() 107 | if err != nil { 108 | break 109 | } 110 | req, err := ioutil.ReadAll(c) 111 | if err != nil { 112 | t.Fatalf("unexpected error when reading request: %s", err) 113 | } 114 | if _, err = c.Write(req); err != nil { 115 | t.Fatalf("unexpected error when writing response: %s", err) 116 | } 117 | if err = c.Close(); err != nil { 118 | t.Fatalf("unexpected error when closing connection: %s", err) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | var ( 4 | defaultServerName = []byte("fasthttp") 5 | defaultUserAgent = []byte("fasthttp") 6 | defaultContentType = []byte("text/plain; charset=utf-8") 7 | ) 8 | 9 | var ( 10 | strSlash = []byte("/") 11 | strSlashSlash = []byte("//") 12 | strSlashDotDot = []byte("/..") 13 | strSlashDotSlash = []byte("/./") 14 | strSlashDotDotSlash = []byte("/../") 15 | strCRLF = []byte("\r\n") 16 | strHTTP = []byte("http") 17 | strHTTPS = []byte("https") 18 | strHTTP11 = []byte("HTTP/1.1") 19 | strColon = []byte(":") 20 | strColonSlashSlash = []byte("://") 21 | strColonSpace = []byte(": ") 22 | strGMT = []byte("GMT") 23 | strAt = []byte("@") 24 | 25 | strResponseContinue = []byte("HTTP/1.1 100 Continue\r\n\r\n") 26 | 27 | strGet = []byte(MethodGet) 28 | strHead = []byte(MethodHead) 29 | strPost = []byte(MethodPost) 30 | strPut = []byte(MethodPut) 31 | strDelete = []byte(MethodDelete) 32 | strConnect = []byte(MethodConnect) 33 | strOptions = []byte(MethodOptions) 34 | strTrace = []byte(MethodTrace) 35 | strPatch = []byte(MethodPatch) 36 | 37 | strExpect = []byte(HeaderExpect) 38 | strConnection = []byte(HeaderConnection) 39 | strContentLength = []byte(HeaderContentLength) 40 | strContentType = []byte(HeaderContentType) 41 | strDate = []byte(HeaderDate) 42 | strHost = []byte(HeaderHost) 43 | strReferer = []byte(HeaderReferer) 44 | strServer = []byte(HeaderServer) 45 | strTransferEncoding = []byte(HeaderTransferEncoding) 46 | strContentEncoding = []byte(HeaderContentEncoding) 47 | strAcceptEncoding = []byte(HeaderAcceptEncoding) 48 | strUserAgent = []byte(HeaderUserAgent) 49 | strCookie = []byte(HeaderCookie) 50 | strSetCookie = []byte(HeaderSetCookie) 51 | strLocation = []byte(HeaderLocation) 52 | strIfModifiedSince = []byte(HeaderIfModifiedSince) 53 | strLastModified = []byte(HeaderLastModified) 54 | strAcceptRanges = []byte(HeaderAcceptRanges) 55 | strRange = []byte(HeaderRange) 56 | strContentRange = []byte(HeaderContentRange) 57 | strAuthorization = []byte(HeaderAuthorization) 58 | 59 | strCookieExpires = []byte("expires") 60 | strCookieDomain = []byte("domain") 61 | strCookiePath = []byte("path") 62 | strCookieHTTPOnly = []byte("HttpOnly") 63 | strCookieSecure = []byte("secure") 64 | strCookieMaxAge = []byte("max-age") 65 | strCookieSameSite = []byte("SameSite") 66 | strCookieSameSiteLax = []byte("Lax") 67 | strCookieSameSiteStrict = []byte("Strict") 68 | strCookieSameSiteNone = []byte("None") 69 | 70 | strClose = []byte("close") 71 | strGzip = []byte("gzip") 72 | strDeflate = []byte("deflate") 73 | strKeepAlive = []byte("keep-alive") 74 | strUpgrade = []byte("Upgrade") 75 | strChunked = []byte("chunked") 76 | strIdentity = []byte("identity") 77 | str100Continue = []byte("100-continue") 78 | strPostArgsContentType = []byte("application/x-www-form-urlencoded") 79 | strMultipartFormData = []byte("multipart/form-data") 80 | strBoundary = []byte("boundary") 81 | strBytes = []byte("bytes") 82 | strTextSlash = []byte("text/") 83 | strApplicationSlash = []byte("application/") 84 | strBasicSpace = []byte("Basic ") 85 | ) 86 | -------------------------------------------------------------------------------- /header_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "testing" 8 | 9 | "github.com/valyala/bytebufferpool" 10 | ) 11 | 12 | var strFoobar = []byte("foobar.com") 13 | 14 | type benchReadBuf struct { 15 | s []byte 16 | n int 17 | } 18 | 19 | func (r *benchReadBuf) Read(p []byte) (int, error) { 20 | if r.n == len(r.s) { 21 | return 0, io.EOF 22 | } 23 | 24 | n := copy(p, r.s[r.n:]) 25 | r.n += n 26 | return n, nil 27 | } 28 | 29 | func BenchmarkRequestHeaderRead(b *testing.B) { 30 | b.RunParallel(func(pb *testing.PB) { 31 | var h RequestHeader 32 | buf := &benchReadBuf{ 33 | s: []byte("GET /foo/bar HTTP/1.1\r\nHost: foobar.com\r\nUser-Agent: aaa.bbb\r\nReferer: http://google.com/aaa/bbb\r\n\r\n"), 34 | } 35 | br := bufio.NewReader(buf) 36 | for pb.Next() { 37 | buf.n = 0 38 | br.Reset(buf) 39 | if err := h.Read(br); err != nil { 40 | b.Fatalf("unexpected error when reading header: %s", err) 41 | } 42 | } 43 | }) 44 | } 45 | 46 | func BenchmarkResponseHeaderRead(b *testing.B) { 47 | b.RunParallel(func(pb *testing.PB) { 48 | var h ResponseHeader 49 | buf := &benchReadBuf{ 50 | s: []byte("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 1256\r\nServer: aaa 1/2.3\r\nTest: 1.2.3\r\n\r\n"), 51 | } 52 | br := bufio.NewReader(buf) 53 | for pb.Next() { 54 | buf.n = 0 55 | br.Reset(buf) 56 | if err := h.Read(br); err != nil { 57 | b.Fatalf("unexpected error when reading header: %s", err) 58 | } 59 | } 60 | }) 61 | } 62 | 63 | func BenchmarkRequestHeaderWrite(b *testing.B) { 64 | b.RunParallel(func(pb *testing.PB) { 65 | var h RequestHeader 66 | h.SetRequestURI("/foo/bar") 67 | h.SetHost("foobar.com") 68 | h.SetUserAgent("aaa.bbb") 69 | h.SetReferer("http://google.com/aaa/bbb") 70 | var w bytebufferpool.ByteBuffer 71 | for pb.Next() { 72 | if _, err := h.WriteTo(&w); err != nil { 73 | b.Fatalf("unexpected error when writing header: %s", err) 74 | } 75 | w.Reset() 76 | } 77 | }) 78 | } 79 | 80 | func BenchmarkResponseHeaderWrite(b *testing.B) { 81 | b.RunParallel(func(pb *testing.PB) { 82 | var h ResponseHeader 83 | h.SetStatusCode(200) 84 | h.SetContentType("text/html") 85 | h.SetContentLength(1256) 86 | h.SetServer("aaa 1/2.3") 87 | h.Set("Test", "1.2.3") 88 | var w bytebufferpool.ByteBuffer 89 | for pb.Next() { 90 | if _, err := h.WriteTo(&w); err != nil { 91 | b.Fatalf("unexpected error when writing header: %s", err) 92 | } 93 | w.Reset() 94 | } 95 | }) 96 | } 97 | 98 | func BenchmarkRequestHeaderPeekBytesCanonical(b *testing.B) { 99 | b.RunParallel(func(pb *testing.PB) { 100 | var h RequestHeader 101 | h.SetBytesV("Host", strFoobar) 102 | for pb.Next() { 103 | v := h.PeekBytes(strHost) 104 | if !bytes.Equal(v, strFoobar) { 105 | b.Fatalf("unexpected result: %q. Expected %q", v, strFoobar) 106 | } 107 | } 108 | }) 109 | } 110 | 111 | func BenchmarkRequestHeaderPeekBytesNonCanonical(b *testing.B) { 112 | b.RunParallel(func(pb *testing.PB) { 113 | var h RequestHeader 114 | h.SetBytesV("Host", strFoobar) 115 | hostBytes := []byte("HOST") 116 | for pb.Next() { 117 | v := h.PeekBytes(hostBytes) 118 | if !bytes.Equal(v, strFoobar) { 119 | b.Fatalf("unexpected result: %q. Expected %q", v, strFoobar) 120 | } 121 | } 122 | }) 123 | } 124 | 125 | func BenchmarkNormalizeHeaderKeyCommonCase(b *testing.B) { 126 | src := []byte("User-Agent-Host-Content-Type-Content-Length-Server") 127 | benchmarkNormalizeHeaderKey(b, src) 128 | } 129 | 130 | func BenchmarkNormalizeHeaderKeyLowercase(b *testing.B) { 131 | src := []byte("user-agent-host-content-type-content-length-server") 132 | benchmarkNormalizeHeaderKey(b, src) 133 | } 134 | 135 | func BenchmarkNormalizeHeaderKeyUppercase(b *testing.B) { 136 | src := []byte("USER-AGENT-HOST-CONTENT-TYPE-CONTENT-LENGTH-SERVER") 137 | benchmarkNormalizeHeaderKey(b, src) 138 | } 139 | 140 | func benchmarkNormalizeHeaderKey(b *testing.B, src []byte) { 141 | b.RunParallel(func(pb *testing.PB) { 142 | buf := make([]byte, len(src)) 143 | for pb.Next() { 144 | copy(buf, src) 145 | normalizeHeaderKey(buf, false) 146 | } 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /bytesconv_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bufio" 5 | "html" 6 | "net" 7 | "testing" 8 | 9 | "github.com/valyala/bytebufferpool" 10 | ) 11 | 12 | func BenchmarkAppendHTMLEscape(b *testing.B) { 13 | sOrig := "foobarbazxxxyyyzzz" 14 | sExpected := string(AppendHTMLEscape(nil, sOrig)) 15 | b.RunParallel(func(pb *testing.PB) { 16 | var buf []byte 17 | for pb.Next() { 18 | for i := 0; i < 10; i++ { 19 | buf = AppendHTMLEscape(buf[:0], sOrig) 20 | if string(buf) != sExpected { 21 | b.Fatalf("unexpected escaped string: %s. Expecting %s", buf, sExpected) 22 | } 23 | } 24 | } 25 | }) 26 | } 27 | 28 | func BenchmarkHTMLEscapeString(b *testing.B) { 29 | sOrig := "foobarbazxxxyyyzzz" 30 | sExpected := html.EscapeString(sOrig) 31 | b.RunParallel(func(pb *testing.PB) { 32 | var s string 33 | for pb.Next() { 34 | for i := 0; i < 10; i++ { 35 | s = html.EscapeString(sOrig) 36 | if s != sExpected { 37 | b.Fatalf("unexpected escaped string: %s. Expecting %s", s, sExpected) 38 | } 39 | } 40 | } 41 | }) 42 | } 43 | 44 | func BenchmarkParseIPv4(b *testing.B) { 45 | ipStr := []byte("123.145.167.189") 46 | b.RunParallel(func(pb *testing.PB) { 47 | var ip net.IP 48 | var err error 49 | for pb.Next() { 50 | ip, err = ParseIPv4(ip, ipStr) 51 | if err != nil { 52 | b.Fatalf("unexpected error: %s", err) 53 | } 54 | } 55 | }) 56 | } 57 | 58 | func BenchmarkAppendIPv4(b *testing.B) { 59 | ip := net.ParseIP("123.145.167.189") 60 | b.RunParallel(func(pb *testing.PB) { 61 | var buf []byte 62 | for pb.Next() { 63 | buf = AppendIPv4(buf[:0], ip) 64 | } 65 | }) 66 | } 67 | 68 | func BenchmarkWriteHexInt(b *testing.B) { 69 | b.RunParallel(func(pb *testing.PB) { 70 | var w bytebufferpool.ByteBuffer 71 | bw := bufio.NewWriter(&w) 72 | i := 0 73 | for pb.Next() { 74 | writeHexInt(bw, i) //nolint:errcheck 75 | i++ 76 | if i > 0x7fffffff { 77 | i = 0 78 | } 79 | w.Reset() 80 | bw.Reset(&w) 81 | } 82 | }) 83 | } 84 | 85 | func BenchmarkParseUint(b *testing.B) { 86 | b.RunParallel(func(pb *testing.PB) { 87 | buf := []byte("1234567") 88 | for pb.Next() { 89 | n, err := ParseUint(buf) 90 | if err != nil { 91 | b.Fatalf("unexpected error: %s", err) 92 | } 93 | if n != 1234567 { 94 | b.Fatalf("unexpected result: %d. Expecting %s", n, buf) 95 | } 96 | } 97 | }) 98 | } 99 | 100 | func BenchmarkAppendUint(b *testing.B) { 101 | b.RunParallel(func(pb *testing.PB) { 102 | var buf []byte 103 | i := 0 104 | for pb.Next() { 105 | buf = AppendUint(buf[:0], i) 106 | i++ 107 | if i > 0x7fffffff { 108 | i = 0 109 | } 110 | } 111 | }) 112 | } 113 | 114 | func BenchmarkLowercaseBytesNoop(b *testing.B) { 115 | src := []byte("foobarbaz_lowercased_all") 116 | b.RunParallel(func(pb *testing.PB) { 117 | s := make([]byte, len(src)) 118 | for pb.Next() { 119 | copy(s, src) 120 | lowercaseBytes(s) 121 | } 122 | }) 123 | } 124 | 125 | func BenchmarkLowercaseBytesAll(b *testing.B) { 126 | src := []byte("FOOBARBAZ_UPPERCASED_ALL") 127 | b.RunParallel(func(pb *testing.PB) { 128 | s := make([]byte, len(src)) 129 | for pb.Next() { 130 | copy(s, src) 131 | lowercaseBytes(s) 132 | } 133 | }) 134 | } 135 | 136 | func BenchmarkLowercaseBytesMixed(b *testing.B) { 137 | src := []byte("Foobarbaz_Uppercased_Mix") 138 | b.RunParallel(func(pb *testing.PB) { 139 | s := make([]byte, len(src)) 140 | for pb.Next() { 141 | copy(s, src) 142 | lowercaseBytes(s) 143 | } 144 | }) 145 | } 146 | 147 | func BenchmarkAppendUnquotedArgFastPath(b *testing.B) { 148 | src := []byte("foobarbaz no quoted chars fdskjsdf jklsdfdfskljd;aflskjdsaf fdsklj fsdkj fsdl kfjsdlk jfsdklj fsdfsdf sdfkflsd") 149 | b.RunParallel(func(pb *testing.PB) { 150 | var dst []byte 151 | for pb.Next() { 152 | dst = AppendUnquotedArg(dst[:0], src) 153 | } 154 | }) 155 | } 156 | 157 | func BenchmarkAppendUnquotedArgSlowPath(b *testing.B) { 158 | src := []byte("D0%B4%20%D0%B0%D0%B2%D0%BB%D0%B4%D1%84%D1%8B%D0%B0%D0%BE%20%D1%84%D0%B2%D0%B6%D0%BB%D0%B4%D1%8B%20%D0%B0%D0%BE") 159 | b.RunParallel(func(pb *testing.PB) { 160 | var dst []byte 161 | for pb.Next() { 162 | dst = AppendUnquotedArg(dst[:0], src) 163 | } 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /examples/fileserver/fileserver.go: -------------------------------------------------------------------------------- 1 | // Example static file server. 2 | // 3 | // Serves static files from the given directory. 4 | // Exports various stats at /stats . 5 | package main 6 | 7 | import ( 8 | "expvar" 9 | "flag" 10 | "log" 11 | 12 | "github.com/valyala/fasthttp" 13 | "github.com/valyala/fasthttp/expvarhandler" 14 | ) 15 | 16 | var ( 17 | addr = flag.String("addr", "localhost:8080", "TCP address to listen to") 18 | addrTLS = flag.String("addrTLS", "", "TCP address to listen to TLS (aka SSL or HTTPS) requests. Leave empty for disabling TLS") 19 | byteRange = flag.Bool("byteRange", false, "Enables byte range requests if set to true") 20 | certFile = flag.String("certFile", "./ssl-cert-snakeoil.pem", "Path to TLS certificate file") 21 | compress = flag.Bool("compress", false, "Enables transparent response compression if set to true") 22 | dir = flag.String("dir", "/usr/share/nginx/html", "Directory to serve static files from") 23 | generateIndexPages = flag.Bool("generateIndexPages", true, "Whether to generate directory index pages") 24 | keyFile = flag.String("keyFile", "./ssl-cert-snakeoil.key", "Path to TLS key file") 25 | vhost = flag.Bool("vhost", false, "Enables virtual hosting by prepending the requested path with the requested hostname") 26 | ) 27 | 28 | func main() { 29 | // Parse command-line flags. 30 | flag.Parse() 31 | 32 | // Setup FS handler 33 | fs := &fasthttp.FS{ 34 | Root: *dir, 35 | IndexNames: []string{"index.html"}, 36 | GenerateIndexPages: *generateIndexPages, 37 | Compress: *compress, 38 | AcceptByteRange: *byteRange, 39 | } 40 | if *vhost { 41 | fs.PathRewrite = fasthttp.NewVHostPathRewriter(0) 42 | } 43 | fsHandler := fs.NewRequestHandler() 44 | 45 | // Create RequestHandler serving server stats on /stats and files 46 | // on other requested paths. 47 | // /stats output may be filtered using regexps. For example: 48 | // 49 | // * /stats?r=fs will show only stats (expvars) containing 'fs' 50 | // in their names. 51 | requestHandler := func(ctx *fasthttp.RequestCtx) { 52 | switch string(ctx.Path()) { 53 | case "/stats": 54 | expvarhandler.ExpvarHandler(ctx) 55 | default: 56 | fsHandler(ctx) 57 | updateFSCounters(ctx) 58 | } 59 | } 60 | 61 | // Start HTTP server. 62 | if len(*addr) > 0 { 63 | log.Printf("Starting HTTP server on %q", *addr) 64 | go func() { 65 | if err := fasthttp.ListenAndServe(*addr, requestHandler); err != nil { 66 | log.Fatalf("error in ListenAndServe: %s", err) 67 | } 68 | }() 69 | } 70 | 71 | // Start HTTPS server. 72 | if len(*addrTLS) > 0 { 73 | log.Printf("Starting HTTPS server on %q", *addrTLS) 74 | go func() { 75 | if err := fasthttp.ListenAndServeTLS(*addrTLS, *certFile, *keyFile, requestHandler); err != nil { 76 | log.Fatalf("error in ListenAndServeTLS: %s", err) 77 | } 78 | }() 79 | } 80 | 81 | log.Printf("Serving files from directory %q", *dir) 82 | log.Printf("See stats at http://%s/stats", *addr) 83 | 84 | // Wait forever. 85 | select {} 86 | } 87 | 88 | func updateFSCounters(ctx *fasthttp.RequestCtx) { 89 | // Increment the number of fsHandler calls. 90 | fsCalls.Add(1) 91 | 92 | // Update other stats counters 93 | resp := &ctx.Response 94 | switch resp.StatusCode() { 95 | case fasthttp.StatusOK: 96 | fsOKResponses.Add(1) 97 | fsResponseBodyBytes.Add(int64(resp.Header.ContentLength())) 98 | case fasthttp.StatusNotModified: 99 | fsNotModifiedResponses.Add(1) 100 | case fasthttp.StatusNotFound: 101 | fsNotFoundResponses.Add(1) 102 | default: 103 | fsOtherResponses.Add(1) 104 | } 105 | } 106 | 107 | // Various counters - see https://golang.org/pkg/expvar/ for details. 108 | var ( 109 | // Counter for total number of fs calls 110 | fsCalls = expvar.NewInt("fsCalls") 111 | 112 | // Counters for various response status codes 113 | fsOKResponses = expvar.NewInt("fsOKResponses") 114 | fsNotModifiedResponses = expvar.NewInt("fsNotModifiedResponses") 115 | fsNotFoundResponses = expvar.NewInt("fsNotFoundResponses") 116 | fsOtherResponses = expvar.NewInt("fsOtherResponses") 117 | 118 | // Total size in bytes for OK response bodies served. 119 | fsResponseBodyBytes = expvar.NewInt("fsResponseBodyBytes") 120 | ) 121 | -------------------------------------------------------------------------------- /workerpool_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/valyala/fasthttp/fasthttputil" 10 | ) 11 | 12 | func TestWorkerPoolStartStopSerial(t *testing.T) { 13 | t.Parallel() 14 | 15 | testWorkerPoolStartStop(t) 16 | } 17 | 18 | func TestWorkerPoolStartStopConcurrent(t *testing.T) { 19 | t.Parallel() 20 | 21 | concurrency := 10 22 | ch := make(chan struct{}, concurrency) 23 | for i := 0; i < concurrency; i++ { 24 | go func() { 25 | testWorkerPoolStartStop(t) 26 | ch <- struct{}{} 27 | }() 28 | } 29 | for i := 0; i < concurrency; i++ { 30 | select { 31 | case <-ch: 32 | case <-time.After(time.Second): 33 | t.Fatalf("timeout") 34 | } 35 | } 36 | } 37 | 38 | func testWorkerPoolStartStop(t *testing.T) { 39 | wp := &workerPool{ 40 | WorkerFunc: func(conn net.Conn) error { return nil }, 41 | MaxWorkersCount: 10, 42 | Logger: defaultLogger, 43 | } 44 | for i := 0; i < 10; i++ { 45 | wp.Start() 46 | wp.Stop() 47 | } 48 | } 49 | 50 | func TestWorkerPoolMaxWorkersCountSerial(t *testing.T) { 51 | t.Parallel() 52 | 53 | testWorkerPoolMaxWorkersCountMulti(t) 54 | } 55 | 56 | func TestWorkerPoolMaxWorkersCountConcurrent(t *testing.T) { 57 | t.Parallel() 58 | 59 | concurrency := 4 60 | ch := make(chan struct{}, concurrency) 61 | for i := 0; i < concurrency; i++ { 62 | go func() { 63 | testWorkerPoolMaxWorkersCountMulti(t) 64 | ch <- struct{}{} 65 | }() 66 | } 67 | for i := 0; i < concurrency; i++ { 68 | select { 69 | case <-ch: 70 | case <-time.After(time.Second): 71 | t.Fatalf("timeout") 72 | } 73 | } 74 | } 75 | 76 | func testWorkerPoolMaxWorkersCountMulti(t *testing.T) { 77 | for i := 0; i < 5; i++ { 78 | testWorkerPoolMaxWorkersCount(t) 79 | } 80 | } 81 | 82 | func testWorkerPoolMaxWorkersCount(t *testing.T) { 83 | ready := make(chan struct{}) 84 | wp := &workerPool{ 85 | WorkerFunc: func(conn net.Conn) error { 86 | buf := make([]byte, 100) 87 | n, err := conn.Read(buf) 88 | if err != nil { 89 | t.Errorf("unexpected error: %s", err) 90 | } 91 | buf = buf[:n] 92 | if string(buf) != "foobar" { 93 | t.Errorf("unexpected data read: %q. Expecting %q", buf, "foobar") 94 | } 95 | if _, err = conn.Write([]byte("baz")); err != nil { 96 | t.Errorf("unexpected error: %s", err) 97 | } 98 | 99 | <-ready 100 | 101 | return nil 102 | }, 103 | MaxWorkersCount: 10, 104 | Logger: defaultLogger, 105 | connState: func(net.Conn, ConnState) {}, 106 | } 107 | wp.Start() 108 | 109 | ln := fasthttputil.NewInmemoryListener() 110 | 111 | clientCh := make(chan struct{}, wp.MaxWorkersCount) 112 | for i := 0; i < wp.MaxWorkersCount; i++ { 113 | go func() { 114 | conn, err := ln.Dial() 115 | if err != nil { 116 | t.Errorf("unexpected error: %s", err) 117 | } 118 | if _, err = conn.Write([]byte("foobar")); err != nil { 119 | t.Errorf("unexpected error: %s", err) 120 | } 121 | data, err := ioutil.ReadAll(conn) 122 | if err != nil { 123 | t.Errorf("unexpected error: %s", err) 124 | } 125 | if string(data) != "baz" { 126 | t.Errorf("unexpected value read: %q. Expecting %q", data, "baz") 127 | } 128 | if err = conn.Close(); err != nil { 129 | t.Errorf("unexpected error: %s", err) 130 | } 131 | clientCh <- struct{}{} 132 | }() 133 | } 134 | 135 | for i := 0; i < wp.MaxWorkersCount; i++ { 136 | conn, err := ln.Accept() 137 | if err != nil { 138 | t.Fatalf("unexpected error: %s", err) 139 | } 140 | if !wp.Serve(conn) { 141 | t.Fatalf("worker pool must have enough workers to serve the conn") 142 | } 143 | } 144 | 145 | go func() { 146 | if _, err := ln.Dial(); err != nil { 147 | t.Errorf("unexpected error: %s", err) 148 | } 149 | }() 150 | conn, err := ln.Accept() 151 | if err != nil { 152 | t.Fatalf("unexpected error: %s", err) 153 | } 154 | for i := 0; i < 5; i++ { 155 | if wp.Serve(conn) { 156 | t.Fatalf("worker pool must be full") 157 | } 158 | } 159 | if err = conn.Close(); err != nil { 160 | t.Fatalf("unexpected error: %s", err) 161 | } 162 | 163 | close(ready) 164 | 165 | for i := 0; i < wp.MaxWorkersCount; i++ { 166 | select { 167 | case <-clientCh: 168 | case <-time.After(time.Second): 169 | t.Fatalf("timeout") 170 | } 171 | } 172 | 173 | if err := ln.Close(); err != nil { 174 | t.Fatalf("unexpected error: %s", err) 175 | } 176 | wp.Stop() 177 | } 178 | -------------------------------------------------------------------------------- /fasthttpadaptor/adaptor.go: -------------------------------------------------------------------------------- 1 | // Package fasthttpadaptor provides helper functions for converting net/http 2 | // request handlers to fasthttp request handlers. 3 | package fasthttpadaptor 4 | 5 | import ( 6 | "io" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | // NewFastHTTPHandlerFunc wraps net/http handler func to fasthttp 14 | // request handler, so it can be passed to fasthttp server. 15 | // 16 | // While this function may be used for easy switching from net/http to fasthttp, 17 | // it has the following drawbacks comparing to using manually written fasthttp 18 | // request handler: 19 | // 20 | // * A lot of useful functionality provided by fasthttp is missing 21 | // from net/http handler. 22 | // * net/http -> fasthttp handler conversion has some overhead, 23 | // so the returned handler will be always slower than manually written 24 | // fasthttp handler. 25 | // 26 | // So it is advisable using this function only for quick net/http -> fasthttp 27 | // switching. Then manually convert net/http handlers to fasthttp handlers 28 | // according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp . 29 | func NewFastHTTPHandlerFunc(h http.HandlerFunc) fasthttp.RequestHandler { 30 | return NewFastHTTPHandler(h) 31 | } 32 | 33 | // NewFastHTTPHandler wraps net/http handler to fasthttp request handler, 34 | // so it can be passed to fasthttp server. 35 | // 36 | // While this function may be used for easy switching from net/http to fasthttp, 37 | // it has the following drawbacks comparing to using manually written fasthttp 38 | // request handler: 39 | // 40 | // * A lot of useful functionality provided by fasthttp is missing 41 | // from net/http handler. 42 | // * net/http -> fasthttp handler conversion has some overhead, 43 | // so the returned handler will be always slower than manually written 44 | // fasthttp handler. 45 | // 46 | // So it is advisable using this function only for quick net/http -> fasthttp 47 | // switching. Then manually convert net/http handlers to fasthttp handlers 48 | // according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp . 49 | func NewFastHTTPHandler(h http.Handler) fasthttp.RequestHandler { 50 | return func(ctx *fasthttp.RequestCtx) { 51 | var r http.Request 52 | 53 | body := ctx.PostBody() 54 | r.Method = string(ctx.Method()) 55 | r.Proto = "HTTP/1.1" 56 | r.ProtoMajor = 1 57 | r.ProtoMinor = 1 58 | r.RequestURI = string(ctx.RequestURI()) 59 | r.ContentLength = int64(len(body)) 60 | r.Host = string(ctx.Host()) 61 | r.RemoteAddr = ctx.RemoteAddr().String() 62 | 63 | hdr := make(http.Header) 64 | ctx.Request.Header.VisitAll(func(k, v []byte) { 65 | sk := string(k) 66 | sv := string(v) 67 | switch sk { 68 | case "Transfer-Encoding": 69 | r.TransferEncoding = append(r.TransferEncoding, sv) 70 | default: 71 | hdr.Set(sk, sv) 72 | } 73 | }) 74 | r.Header = hdr 75 | r.Body = &netHTTPBody{body} 76 | rURL, err := url.ParseRequestURI(r.RequestURI) 77 | if err != nil { 78 | ctx.Logger().Printf("cannot parse requestURI %q: %s", r.RequestURI, err) 79 | ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) 80 | return 81 | } 82 | r.URL = rURL 83 | 84 | var w netHTTPResponseWriter 85 | h.ServeHTTP(&w, r.WithContext(ctx)) 86 | 87 | ctx.SetStatusCode(w.StatusCode()) 88 | for k, vv := range w.Header() { 89 | for _, v := range vv { 90 | ctx.Response.Header.Set(k, v) 91 | } 92 | } 93 | ctx.Write(w.body) //nolint:errcheck 94 | } 95 | } 96 | 97 | type netHTTPBody struct { 98 | b []byte 99 | } 100 | 101 | func (r *netHTTPBody) Read(p []byte) (int, error) { 102 | if len(r.b) == 0 { 103 | return 0, io.EOF 104 | } 105 | n := copy(p, r.b) 106 | r.b = r.b[n:] 107 | return n, nil 108 | } 109 | 110 | func (r *netHTTPBody) Close() error { 111 | r.b = r.b[:0] 112 | return nil 113 | } 114 | 115 | type netHTTPResponseWriter struct { 116 | statusCode int 117 | h http.Header 118 | body []byte 119 | } 120 | 121 | func (w *netHTTPResponseWriter) StatusCode() int { 122 | if w.statusCode == 0 { 123 | return http.StatusOK 124 | } 125 | return w.statusCode 126 | } 127 | 128 | func (w *netHTTPResponseWriter) Header() http.Header { 129 | if w.h == nil { 130 | w.h = make(http.Header) 131 | } 132 | return w.h 133 | } 134 | 135 | func (w *netHTTPResponseWriter) WriteHeader(statusCode int) { 136 | w.statusCode = statusCode 137 | } 138 | 139 | func (w *netHTTPResponseWriter) Write(p []byte) (int, error) { 140 | w.body = append(w.body, p...) 141 | return len(p), nil 142 | } 143 | -------------------------------------------------------------------------------- /bytesconv_table.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | // Code generated by go run bytesconv_table_gen.go; DO NOT EDIT. 4 | // See bytesconv_table_gen.go for more information about these tables. 5 | 6 | const hex2intTable = "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x00\x01\x02\x03\x04\x05\x06\a\b\t\x10\x10\x10\x10\x10\x10\x10\n\v\f\r\x0e\x0f\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\n\v\f\r\x0e\x0f\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" 7 | const toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" 8 | const toUpperTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" 9 | const quotedArgShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" 10 | const quotedPathShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" 11 | -------------------------------------------------------------------------------- /prefork/prefork.go: -------------------------------------------------------------------------------- 1 | package prefork 2 | 3 | import ( 4 | "flag" 5 | "net" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | 10 | "github.com/valyala/fasthttp" 11 | "github.com/valyala/fasthttp/reuseport" 12 | ) 13 | 14 | const preforkChildFlag = "-prefork-child" 15 | const defaultNetwork = "tcp4" 16 | 17 | // Prefork implements fasthttp server prefork 18 | // 19 | // Preforks master process (with all cores) between several child processes 20 | // increases performance significantly, because Go doesn't have to share 21 | // and manage memory between cores 22 | // 23 | // WARNING: using prefork prevents the use of any global state! 24 | // Things like in-memory caches won't work. 25 | type Prefork struct { 26 | // The network must be "tcp", "tcp4" or "tcp6". 27 | // 28 | // By default is "tcp4" 29 | Network string 30 | 31 | // Flag to use a listener with reuseport, if not a file Listener will be used 32 | // See: https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ 33 | // 34 | // It's disabled by default 35 | Reuseport bool 36 | 37 | ServeFunc func(ln net.Listener) error 38 | ServeTLSFunc func(ln net.Listener, certFile, keyFile string) error 39 | ServeTLSEmbedFunc func(ln net.Listener, certData, keyData []byte) error 40 | 41 | ln net.Listener 42 | files []*os.File 43 | } 44 | 45 | func init() { //nolint:gochecknoinits 46 | // Definition flag to not break the program when the user adds their own flags 47 | // and runs `flag.Parse()` 48 | flag.Bool(preforkChildFlag[1:], false, "Is a child process") 49 | } 50 | 51 | // IsChild checks if the current thread/process is a child 52 | func IsChild() bool { 53 | for _, arg := range os.Args[1:] { 54 | if arg == preforkChildFlag { 55 | return true 56 | } 57 | } 58 | 59 | return false 60 | } 61 | 62 | // New wraps the fasthttp server to run with preforked processes 63 | func New(s *fasthttp.Server) *Prefork { 64 | return &Prefork{ 65 | Network: defaultNetwork, 66 | ServeFunc: s.Serve, 67 | ServeTLSFunc: s.ServeTLS, 68 | ServeTLSEmbedFunc: s.ServeTLSEmbed, 69 | } 70 | } 71 | 72 | func (p *Prefork) listen(addr string) (net.Listener, error) { 73 | runtime.GOMAXPROCS(1) 74 | 75 | if p.Network == "" { 76 | p.Network = defaultNetwork 77 | } 78 | 79 | if p.Reuseport { 80 | return reuseport.Listen(p.Network, addr) 81 | } 82 | 83 | return net.FileListener(os.NewFile(3, "")) 84 | } 85 | 86 | func (p *Prefork) setTCPListenerFiles(addr string) error { 87 | if p.Network == "" { 88 | p.Network = defaultNetwork 89 | } 90 | 91 | tcpAddr, err := net.ResolveTCPAddr(p.Network, addr) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | tcplistener, err := net.ListenTCP(p.Network, tcpAddr) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | p.ln = tcplistener 102 | 103 | fl, err := tcplistener.File() 104 | if err != nil { 105 | return err 106 | } 107 | 108 | p.files = []*os.File{fl} 109 | 110 | return nil 111 | } 112 | 113 | func (p *Prefork) prefork(addr string) error { 114 | chErr := make(chan error, 1) 115 | 116 | if !p.Reuseport { 117 | if err := p.setTCPListenerFiles(addr); err != nil { 118 | return err 119 | } 120 | 121 | defer p.ln.Close() 122 | } 123 | 124 | for i := 0; i < runtime.GOMAXPROCS(0); i++ { 125 | cmd := exec.Command(os.Args[0], append(os.Args[1:], preforkChildFlag)...) 126 | cmd.Stdout = os.Stdout 127 | cmd.Stderr = os.Stderr 128 | cmd.ExtraFiles = p.files 129 | 130 | go func() { 131 | chErr <- cmd.Run() 132 | }() 133 | } 134 | 135 | return <-chErr 136 | } 137 | 138 | // ListenAndServe serves HTTP requests from the given TCP addr 139 | func (p *Prefork) ListenAndServe(addr string) error { 140 | if IsChild() { 141 | ln, err := p.listen(addr) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | p.ln = ln 147 | 148 | return p.ServeFunc(ln) 149 | } 150 | 151 | return p.prefork(addr) 152 | } 153 | 154 | // ListenAndServeTLS serves HTTPS requests from the given TCP addr 155 | // 156 | // certFile and keyFile are paths to TLS certificate and key files. 157 | func (p *Prefork) ListenAndServeTLS(addr, certKey, certFile string) error { 158 | if IsChild() { 159 | ln, err := p.listen(addr) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | p.ln = ln 165 | 166 | return p.ServeTLSFunc(ln, certFile, certKey) 167 | } 168 | 169 | return p.prefork(addr) 170 | } 171 | 172 | // ListenAndServeTLSEmbed serves HTTPS requests from the given TCP addr 173 | // 174 | // certData and keyData must contain valid TLS certificate and key data. 175 | func (p *Prefork) ListenAndServeTLSEmbed(addr string, certData, keyData []byte) error { 176 | if IsChild() { 177 | ln, err := p.listen(addr) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | p.ln = ln 183 | 184 | return p.ServeTLSEmbedFunc(ln, certData, keyData) 185 | } 186 | 187 | return p.prefork(addr) 188 | } 189 | -------------------------------------------------------------------------------- /prefork/prefork_test.go: -------------------------------------------------------------------------------- 1 | package prefork 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net" 7 | "os" 8 | "reflect" 9 | "runtime" 10 | "testing" 11 | 12 | "github.com/valyala/fasthttp" 13 | ) 14 | 15 | func setUp() { 16 | os.Args = append(os.Args, preforkChildFlag) 17 | } 18 | 19 | func tearDown() { 20 | os.Args = os.Args[:len(os.Args)-1] 21 | } 22 | 23 | func getAddr() string { 24 | return fmt.Sprintf("0.0.0.0:%d", rand.Intn(9000-3000)+3000) 25 | } 26 | 27 | func Test_IsChild(t *testing.T) { 28 | v := IsChild() 29 | if v { 30 | t.Errorf("IsChild() == %v, want %v", v, false) 31 | } 32 | 33 | setUp() 34 | 35 | v = IsChild() 36 | if !v { 37 | t.Errorf("IsChild() == %v, want %v", v, true) 38 | } 39 | 40 | tearDown() 41 | } 42 | 43 | func Test_New(t *testing.T) { 44 | s := &fasthttp.Server{} 45 | p := New(s) 46 | 47 | if p.Network != defaultNetwork { 48 | t.Errorf("Prefork.Netork == %s, want %s", p.Network, defaultNetwork) 49 | } 50 | 51 | if reflect.ValueOf(p.ServeFunc).Pointer() != reflect.ValueOf(s.Serve).Pointer() { 52 | t.Errorf("Prefork.ServeFunc == %p, want %p", p.ServeFunc, s.Serve) 53 | } 54 | 55 | if reflect.ValueOf(p.ServeTLSFunc).Pointer() != reflect.ValueOf(s.ServeTLS).Pointer() { 56 | t.Errorf("Prefork.ServeTLSFunc == %p, want %p", p.ServeTLSFunc, s.ServeTLS) 57 | } 58 | 59 | if reflect.ValueOf(p.ServeTLSEmbedFunc).Pointer() != reflect.ValueOf(s.ServeTLSEmbed).Pointer() { 60 | t.Errorf("Prefork.ServeTLSFunc == %p, want %p", p.ServeTLSEmbedFunc, s.ServeTLSEmbed) 61 | } 62 | } 63 | 64 | func Test_listen(t *testing.T) { 65 | p := &Prefork{ 66 | Reuseport: true, 67 | } 68 | addr := getAddr() 69 | 70 | ln, err := p.listen(addr) 71 | 72 | if err != nil { 73 | t.Fatalf("Unexpected error: %v", err) 74 | } 75 | 76 | ln.Close() 77 | 78 | lnAddr := ln.Addr().String() 79 | if lnAddr != addr { 80 | t.Errorf("Prefork.Addr == %s, want %s", lnAddr, addr) 81 | } 82 | 83 | if p.Network != defaultNetwork { 84 | t.Errorf("Prefork.Network == %s, want %s", p.Network, defaultNetwork) 85 | } 86 | 87 | procs := runtime.GOMAXPROCS(0) 88 | if procs != 1 { 89 | t.Errorf("GOMAXPROCS == %d, want %d", procs, 1) 90 | } 91 | } 92 | 93 | func Test_setTCPListenerFiles(t *testing.T) { 94 | p := &Prefork{} 95 | addr := getAddr() 96 | 97 | err := p.setTCPListenerFiles(addr) 98 | 99 | if err != nil { 100 | t.Fatalf("Unexpected error: %v", err) 101 | } 102 | 103 | if p.ln == nil { 104 | t.Fatal("Prefork.ln is nil") 105 | } 106 | 107 | p.ln.Close() 108 | 109 | lnAddr := p.ln.Addr().String() 110 | if lnAddr != addr { 111 | t.Errorf("Prefork.Addr == %s, want %s", lnAddr, addr) 112 | } 113 | 114 | if p.Network != defaultNetwork { 115 | t.Errorf("Prefork.Network == %s, want %s", p.Network, defaultNetwork) 116 | } 117 | 118 | if len(p.files) != 1 { 119 | t.Errorf("Prefork.files == %d, want %d", len(p.files), 1) 120 | } 121 | } 122 | 123 | func Test_ListenAndServe(t *testing.T) { 124 | setUp() 125 | 126 | s := &fasthttp.Server{} 127 | p := New(s) 128 | p.Reuseport = true 129 | p.ServeFunc = func(ln net.Listener) error { 130 | return nil 131 | } 132 | 133 | addr := getAddr() 134 | 135 | err := p.ListenAndServe(addr) 136 | if err != nil { 137 | t.Errorf("Unexpected error: %v", err) 138 | } 139 | 140 | p.ln.Close() 141 | 142 | lnAddr := p.ln.Addr().String() 143 | if lnAddr != addr { 144 | t.Errorf("Prefork.Addr == %s, want %s", lnAddr, addr) 145 | } 146 | 147 | if p.ln == nil { 148 | t.Error("Prefork.ln is nil") 149 | } 150 | 151 | tearDown() 152 | } 153 | 154 | func Test_ListenAndServeTLS(t *testing.T) { 155 | setUp() 156 | 157 | s := &fasthttp.Server{} 158 | p := New(s) 159 | p.Reuseport = true 160 | p.ServeTLSFunc = func(ln net.Listener, certFile, keyFile string) error { 161 | return nil 162 | } 163 | 164 | addr := getAddr() 165 | 166 | err := p.ListenAndServeTLS(addr, "./key", "./cert") 167 | if err != nil { 168 | t.Errorf("Unexpected error: %v", err) 169 | } 170 | 171 | p.ln.Close() 172 | 173 | lnAddr := p.ln.Addr().String() 174 | if lnAddr != addr { 175 | t.Errorf("Prefork.Addr == %s, want %s", lnAddr, addr) 176 | } 177 | 178 | if p.ln == nil { 179 | t.Error("Prefork.ln is nil") 180 | } 181 | 182 | tearDown() 183 | } 184 | 185 | func Test_ListenAndServeTLSEmbed(t *testing.T) { 186 | setUp() 187 | 188 | s := &fasthttp.Server{} 189 | p := New(s) 190 | p.Reuseport = true 191 | p.ServeTLSEmbedFunc = func(ln net.Listener, certData, keyData []byte) error { 192 | return nil 193 | } 194 | 195 | addr := getAddr() 196 | 197 | err := p.ListenAndServeTLSEmbed(addr, []byte("key"), []byte("cert")) 198 | if err != nil { 199 | t.Errorf("Unexpected error: %v", err) 200 | } 201 | 202 | p.ln.Close() 203 | 204 | lnAddr := p.ln.Addr().String() 205 | if lnAddr != addr { 206 | t.Errorf("Prefork.Addr == %s, want %s", lnAddr, addr) 207 | } 208 | 209 | if p.ln == nil { 210 | t.Error("Prefork.ln is nil") 211 | } 212 | 213 | tearDown() 214 | } 215 | -------------------------------------------------------------------------------- /lbclient.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | // BalancingClient is the interface for clients, which may be passed 10 | // to LBClient.Clients. 11 | type BalancingClient interface { 12 | DoDeadline(req *Request, resp *Response, deadline time.Time) error 13 | PendingRequests() int 14 | } 15 | 16 | // LBClient balances requests among available LBClient.Clients. 17 | // 18 | // It has the following features: 19 | // 20 | // - Balances load among available clients using 'least loaded' + 'least total' 21 | // hybrid technique. 22 | // - Dynamically decreases load on unhealthy clients. 23 | // 24 | // It is forbidden copying LBClient instances. Create new instances instead. 25 | // 26 | // It is safe calling LBClient methods from concurrently running goroutines. 27 | type LBClient struct { 28 | noCopy noCopy //nolint:unused,structcheck 29 | 30 | // Clients must contain non-zero clients list. 31 | // Incoming requests are balanced among these clients. 32 | Clients []BalancingClient 33 | 34 | // HealthCheck is a callback called after each request. 35 | // 36 | // The request, response and the error returned by the client 37 | // is passed to HealthCheck, so the callback may determine whether 38 | // the client is healthy. 39 | // 40 | // Load on the current client is decreased if HealthCheck returns false. 41 | // 42 | // By default HealthCheck returns false if err != nil. 43 | HealthCheck func(req *Request, resp *Response, err error) bool 44 | 45 | // Timeout is the request timeout used when calling LBClient.Do. 46 | // 47 | // DefaultLBClientTimeout is used by default. 48 | Timeout time.Duration 49 | 50 | cs []*lbClient 51 | 52 | once sync.Once 53 | } 54 | 55 | // DefaultLBClientTimeout is the default request timeout used by LBClient 56 | // when calling LBClient.Do. 57 | // 58 | // The timeout may be overridden via LBClient.Timeout. 59 | const DefaultLBClientTimeout = time.Second 60 | 61 | // DoDeadline calls DoDeadline on the least loaded client 62 | func (cc *LBClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error { 63 | return cc.get().DoDeadline(req, resp, deadline) 64 | } 65 | 66 | // DoTimeout calculates deadline and calls DoDeadline on the least loaded client 67 | func (cc *LBClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error { 68 | deadline := time.Now().Add(timeout) 69 | return cc.get().DoDeadline(req, resp, deadline) 70 | } 71 | 72 | // Do calls calculates deadline using LBClient.Timeout and calls DoDeadline 73 | // on the least loaded client. 74 | func (cc *LBClient) Do(req *Request, resp *Response) error { 75 | timeout := cc.Timeout 76 | if timeout <= 0 { 77 | timeout = DefaultLBClientTimeout 78 | } 79 | return cc.DoTimeout(req, resp, timeout) 80 | } 81 | 82 | func (cc *LBClient) init() { 83 | if len(cc.Clients) == 0 { 84 | panic("BUG: LBClient.Clients cannot be empty") 85 | } 86 | for _, c := range cc.Clients { 87 | cc.cs = append(cc.cs, &lbClient{ 88 | c: c, 89 | healthCheck: cc.HealthCheck, 90 | }) 91 | } 92 | } 93 | 94 | func (cc *LBClient) get() *lbClient { 95 | cc.once.Do(cc.init) 96 | 97 | cs := cc.cs 98 | 99 | minC := cs[0] 100 | minN := minC.PendingRequests() 101 | minT := atomic.LoadUint64(&minC.total) 102 | for _, c := range cs[1:] { 103 | n := c.PendingRequests() 104 | t := atomic.LoadUint64(&c.total) 105 | if n < minN || (n == minN && t < minT) { 106 | minC = c 107 | minN = n 108 | minT = t 109 | } 110 | } 111 | return minC 112 | } 113 | 114 | type lbClient struct { 115 | c BalancingClient 116 | healthCheck func(req *Request, resp *Response, err error) bool 117 | penalty uint32 118 | 119 | // total amount of requests handled. 120 | total uint64 121 | } 122 | 123 | func (c *lbClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error { 124 | err := c.c.DoDeadline(req, resp, deadline) 125 | if !c.isHealthy(req, resp, err) && c.incPenalty() { 126 | // Penalize the client returning error, so the next requests 127 | // are routed to another clients. 128 | time.AfterFunc(penaltyDuration, c.decPenalty) 129 | } else { 130 | atomic.AddUint64(&c.total, 1) 131 | } 132 | return err 133 | } 134 | 135 | func (c *lbClient) PendingRequests() int { 136 | n := c.c.PendingRequests() 137 | m := atomic.LoadUint32(&c.penalty) 138 | return n + int(m) 139 | } 140 | 141 | func (c *lbClient) isHealthy(req *Request, resp *Response, err error) bool { 142 | if c.healthCheck == nil { 143 | return err == nil 144 | } 145 | return c.healthCheck(req, resp, err) 146 | } 147 | 148 | func (c *lbClient) incPenalty() bool { 149 | m := atomic.AddUint32(&c.penalty, 1) 150 | if m > maxPenalty { 151 | c.decPenalty() 152 | return false 153 | } 154 | return true 155 | } 156 | 157 | func (c *lbClient) decPenalty() { 158 | atomic.AddUint32(&c.penalty, ^uint32(0)) 159 | } 160 | 161 | const ( 162 | maxPenalty = 300 163 | 164 | penaltyDuration = 3 * time.Second 165 | ) 166 | -------------------------------------------------------------------------------- /fasthttputil/inmemory_listener_test.go: -------------------------------------------------------------------------------- 1 | package fasthttputil 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | "sync" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func TestInmemoryListener(t *testing.T) { 17 | ln := NewInmemoryListener() 18 | 19 | ch := make(chan struct{}) 20 | for i := 0; i < 10; i++ { 21 | go func(n int) { 22 | conn, err := ln.Dial() 23 | if err != nil { 24 | t.Fatalf("unexpected error: %s", err) 25 | } 26 | defer conn.Close() 27 | req := fmt.Sprintf("request_%d", n) 28 | nn, err := conn.Write([]byte(req)) 29 | if err != nil { 30 | t.Fatalf("unexpected error: %s", err) 31 | } 32 | if nn != len(req) { 33 | t.Fatalf("unexpected number of bytes written: %d. Expecting %d", nn, len(req)) 34 | } 35 | buf := make([]byte, 30) 36 | nn, err = conn.Read(buf) 37 | if err != nil { 38 | t.Fatalf("unexpected error: %s", err) 39 | } 40 | buf = buf[:nn] 41 | resp := fmt.Sprintf("response_%d", n) 42 | if nn != len(resp) { 43 | t.Fatalf("unexpected number of bytes read: %d. Expecting %d", nn, len(resp)) 44 | } 45 | if string(buf) != resp { 46 | t.Fatalf("unexpected response %q. Expecting %q", buf, resp) 47 | } 48 | ch <- struct{}{} 49 | }(i) 50 | } 51 | 52 | serverCh := make(chan struct{}) 53 | go func() { 54 | for { 55 | conn, err := ln.Accept() 56 | if err != nil { 57 | close(serverCh) 58 | return 59 | } 60 | defer conn.Close() 61 | buf := make([]byte, 30) 62 | n, err := conn.Read(buf) 63 | if err != nil { 64 | t.Fatalf("unexpected error: %s", err) 65 | } 66 | buf = buf[:n] 67 | if !bytes.HasPrefix(buf, []byte("request_")) { 68 | t.Fatalf("unexpected request prefix %q. Expecting %q", buf, "request_") 69 | } 70 | resp := fmt.Sprintf("response_%s", buf[len("request_"):]) 71 | n, err = conn.Write([]byte(resp)) 72 | if err != nil { 73 | t.Fatalf("unexpected error: %s", err) 74 | } 75 | if n != len(resp) { 76 | t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(resp)) 77 | } 78 | } 79 | }() 80 | 81 | for i := 0; i < 10; i++ { 82 | select { 83 | case <-ch: 84 | case <-time.After(time.Second): 85 | t.Fatalf("timeout") 86 | } 87 | } 88 | 89 | if err := ln.Close(); err != nil { 90 | t.Fatalf("unexpected error: %s", err) 91 | } 92 | 93 | select { 94 | case <-serverCh: 95 | case <-time.After(time.Second): 96 | t.Fatalf("timeout") 97 | } 98 | } 99 | 100 | // echoServerHandler implements http.Handler. 101 | type echoServerHandler struct { 102 | t *testing.T 103 | } 104 | 105 | func (s *echoServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 106 | w.WriteHeader(200) 107 | time.Sleep(time.Millisecond * 100) 108 | if _, err := io.Copy(w, r.Body); err != nil { 109 | s.t.Fatalf("unexpected error: %s", err) 110 | } 111 | } 112 | 113 | func testInmemoryListenerHTTP(t *testing.T, f func(t *testing.T, client *http.Client)) { 114 | ln := NewInmemoryListener() 115 | defer ln.Close() 116 | 117 | client := &http.Client{ 118 | Transport: &http.Transport{ 119 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 120 | return ln.Dial() 121 | }, 122 | }, 123 | Timeout: time.Second, 124 | } 125 | 126 | server := &http.Server{ 127 | Handler: &echoServerHandler{t}, 128 | } 129 | 130 | go func() { 131 | if err := server.Serve(ln); err != nil && err != http.ErrServerClosed { 132 | t.Fatalf("unexpected error: %s", err) 133 | } 134 | }() 135 | 136 | f(t, client) 137 | 138 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) 139 | defer cancel() 140 | server.Shutdown(ctx) //nolint:errcheck 141 | } 142 | 143 | func testInmemoryListenerHTTPSingle(t *testing.T, client *http.Client, content string) { 144 | res, err := client.Post("http://...", "text/plain", bytes.NewBufferString(content)) 145 | if err != nil { 146 | t.Fatalf("unexpected error: %s", err) 147 | } 148 | b, err := ioutil.ReadAll(res.Body) 149 | if err != nil { 150 | t.Fatalf("unexpected error: %s", err) 151 | } 152 | s := string(b) 153 | if string(b) != content { 154 | t.Fatalf("unexpected response %s, expecting %s", s, content) 155 | } 156 | } 157 | 158 | func TestInmemoryListenerHTTPSingle(t *testing.T) { 159 | testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) { 160 | testInmemoryListenerHTTPSingle(t, client, "request") 161 | }) 162 | } 163 | 164 | func TestInmemoryListenerHTTPSerial(t *testing.T) { 165 | testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) { 166 | for i := 0; i < 10; i++ { 167 | testInmemoryListenerHTTPSingle(t, client, fmt.Sprintf("request_%d", i)) 168 | } 169 | }) 170 | } 171 | 172 | func TestInmemoryListenerHTTPConcurrent(t *testing.T) { 173 | testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) { 174 | var wg sync.WaitGroup 175 | for i := 0; i < 10; i++ { 176 | wg.Add(1) 177 | go func(i int) { 178 | defer wg.Done() 179 | testInmemoryListenerHTTPSingle(t, client, fmt.Sprintf("request_%d", i)) 180 | }(i) 181 | } 182 | wg.Wait() 183 | }) 184 | } 185 | -------------------------------------------------------------------------------- /fasthttpadaptor/adaptor_test.go: -------------------------------------------------------------------------------- 1 | package fasthttpadaptor 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/valyala/fasthttp" 13 | ) 14 | 15 | func TestNewFastHTTPHandler(t *testing.T) { 16 | expectedMethod := fasthttp.MethodPost 17 | expectedProto := "HTTP/1.1" 18 | expectedProtoMajor := 1 19 | expectedProtoMinor := 1 20 | expectedRequestURI := "/foo/bar?baz=123" 21 | expectedBody := "body 123 foo bar baz" 22 | expectedContentLength := len(expectedBody) 23 | expectedTransferEncoding := "encoding" 24 | expectedHost := "foobar.com" 25 | expectedRemoteAddr := "1.2.3.4:6789" 26 | expectedHeader := map[string]string{ 27 | "Foo-Bar": "baz", 28 | "Abc": "defg", 29 | "XXX-Remote-Addr": "123.43.4543.345", 30 | } 31 | expectedURL, err := url.ParseRequestURI(expectedRequestURI) 32 | if err != nil { 33 | t.Fatalf("unexpected error: %s", err) 34 | } 35 | expectedContextKey := "contextKey" 36 | expectedContextValue := "contextValue" 37 | 38 | callsCount := 0 39 | nethttpH := func(w http.ResponseWriter, r *http.Request) { 40 | callsCount++ 41 | if r.Method != expectedMethod { 42 | t.Fatalf("unexpected method %q. Expecting %q", r.Method, expectedMethod) 43 | } 44 | if r.Proto != expectedProto { 45 | t.Fatalf("unexpected proto %q. Expecting %q", r.Proto, expectedProto) 46 | } 47 | if r.ProtoMajor != expectedProtoMajor { 48 | t.Fatalf("unexpected protoMajor %d. Expecting %d", r.ProtoMajor, expectedProtoMajor) 49 | } 50 | if r.ProtoMinor != expectedProtoMinor { 51 | t.Fatalf("unexpected protoMinor %d. Expecting %d", r.ProtoMinor, expectedProtoMinor) 52 | } 53 | if r.RequestURI != expectedRequestURI { 54 | t.Fatalf("unexpected requestURI %q. Expecting %q", r.RequestURI, expectedRequestURI) 55 | } 56 | if r.ContentLength != int64(expectedContentLength) { 57 | t.Fatalf("unexpected contentLength %d. Expecting %d", r.ContentLength, expectedContentLength) 58 | } 59 | if len(r.TransferEncoding) != 1 || r.TransferEncoding[0] != expectedTransferEncoding { 60 | t.Fatalf("unexpected transferEncoding %q. Expecting %q", r.TransferEncoding, expectedTransferEncoding) 61 | } 62 | if r.Host != expectedHost { 63 | t.Fatalf("unexpected host %q. Expecting %q", r.Host, expectedHost) 64 | } 65 | if r.RemoteAddr != expectedRemoteAddr { 66 | t.Fatalf("unexpected remoteAddr %q. Expecting %q", r.RemoteAddr, expectedRemoteAddr) 67 | } 68 | body, err := ioutil.ReadAll(r.Body) 69 | r.Body.Close() 70 | if err != nil { 71 | t.Fatalf("unexpected error when reading request body: %s", err) 72 | } 73 | if string(body) != expectedBody { 74 | t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody) 75 | } 76 | if !reflect.DeepEqual(r.URL, expectedURL) { 77 | t.Fatalf("unexpected URL: %#v. Expecting %#v", r.URL, expectedURL) 78 | } 79 | if r.Context().Value(expectedContextKey) != expectedContextValue { 80 | t.Fatalf("unexpected context value for key %q. Expecting %q", expectedContextKey, expectedContextValue) 81 | } 82 | 83 | for k, expectedV := range expectedHeader { 84 | v := r.Header.Get(k) 85 | if v != expectedV { 86 | t.Fatalf("unexpected header value %q for key %q. Expecting %q", v, k, expectedV) 87 | } 88 | } 89 | 90 | w.Header().Set("Header1", "value1") 91 | w.Header().Set("Header2", "value2") 92 | w.WriteHeader(http.StatusBadRequest) 93 | fmt.Fprintf(w, "request body is %q", body) 94 | } 95 | fasthttpH := NewFastHTTPHandler(http.HandlerFunc(nethttpH)) 96 | fasthttpH = setContextValueMiddleware(fasthttpH, expectedContextKey, expectedContextValue) 97 | 98 | var ctx fasthttp.RequestCtx 99 | var req fasthttp.Request 100 | 101 | req.Header.SetMethod(expectedMethod) 102 | req.SetRequestURI(expectedRequestURI) 103 | req.Header.SetHost(expectedHost) 104 | req.Header.Add(fasthttp.HeaderTransferEncoding, expectedTransferEncoding) 105 | req.BodyWriter().Write([]byte(expectedBody)) // nolint:errcheck 106 | for k, v := range expectedHeader { 107 | req.Header.Set(k, v) 108 | } 109 | 110 | remoteAddr, err := net.ResolveTCPAddr("tcp", expectedRemoteAddr) 111 | if err != nil { 112 | t.Fatalf("unexpected error: %s", err) 113 | } 114 | ctx.Init(&req, remoteAddr, nil) 115 | 116 | fasthttpH(&ctx) 117 | 118 | if callsCount != 1 { 119 | t.Fatalf("unexpected callsCount: %d. Expecting 1", callsCount) 120 | } 121 | 122 | resp := &ctx.Response 123 | if resp.StatusCode() != fasthttp.StatusBadRequest { 124 | t.Fatalf("unexpected statusCode: %d. Expecting %d", resp.StatusCode(), fasthttp.StatusBadRequest) 125 | } 126 | if string(resp.Header.Peek("Header1")) != "value1" { 127 | t.Fatalf("unexpected header value: %q. Expecting %q", resp.Header.Peek("Header1"), "value1") 128 | } 129 | if string(resp.Header.Peek("Header2")) != "value2" { 130 | t.Fatalf("unexpected header value: %q. Expecting %q", resp.Header.Peek("Header2"), "value2") 131 | } 132 | expectedResponseBody := fmt.Sprintf("request body is %q", expectedBody) 133 | if string(resp.Body()) != expectedResponseBody { 134 | t.Fatalf("unexpected response body %q. Expecting %q", resp.Body(), expectedResponseBody) 135 | } 136 | } 137 | 138 | func setContextValueMiddleware(next fasthttp.RequestHandler, key string, value interface{}) fasthttp.RequestHandler { 139 | return func(ctx *fasthttp.RequestCtx) { 140 | ctx.SetUserValue(key, value) 141 | next(ctx) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /fasthttputil/inmemory_listener_timing_test.go: -------------------------------------------------------------------------------- 1 | package fasthttputil_test 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "testing" 7 | 8 | "github.com/valyala/fasthttp" 9 | "github.com/valyala/fasthttp/fasthttputil" 10 | ) 11 | 12 | // BenchmarkPlainStreaming measures end-to-end plaintext streaming performance 13 | // for fasthttp client and server. 14 | // 15 | // It issues http requests over a small number of keep-alive connections. 16 | func BenchmarkPlainStreaming(b *testing.B) { 17 | benchmark(b, streamingHandler, false) 18 | } 19 | 20 | // BenchmarkPlainHandshake measures end-to-end plaintext handshake performance 21 | // for fasthttp client and server. 22 | // 23 | // It re-establishes new connection per each http request. 24 | func BenchmarkPlainHandshake(b *testing.B) { 25 | benchmark(b, handshakeHandler, false) 26 | } 27 | 28 | // BenchmarkTLSStreaming measures end-to-end TLS streaming performance 29 | // for fasthttp client and server. 30 | // 31 | // It issues http requests over a small number of TLS keep-alive connections. 32 | func BenchmarkTLSStreaming(b *testing.B) { 33 | benchmark(b, streamingHandler, true) 34 | } 35 | 36 | // BenchmarkTLSHandshake measures end-to-end TLS handshake performance 37 | // for fasthttp client and server. 38 | // 39 | // It re-establishes new TLS connection per each http request. 40 | func BenchmarkTLSHandshakeRSAWithClientSessionCache(b *testing.B) { 41 | bc := &benchConfig{ 42 | IsTLS: true, 43 | DisableClientSessionCache: false, 44 | } 45 | benchmarkExt(b, handshakeHandler, bc) 46 | } 47 | 48 | func BenchmarkTLSHandshakeRSAWithoutClientSessionCache(b *testing.B) { 49 | bc := &benchConfig{ 50 | IsTLS: true, 51 | DisableClientSessionCache: true, 52 | } 53 | benchmarkExt(b, handshakeHandler, bc) 54 | } 55 | 56 | func BenchmarkTLSHandshakeECDSAWithClientSessionCache(b *testing.B) { 57 | bc := &benchConfig{ 58 | IsTLS: true, 59 | DisableClientSessionCache: false, 60 | UseECDSA: true, 61 | } 62 | benchmarkExt(b, handshakeHandler, bc) 63 | } 64 | 65 | func BenchmarkTLSHandshakeECDSAWithoutClientSessionCache(b *testing.B) { 66 | bc := &benchConfig{ 67 | IsTLS: true, 68 | DisableClientSessionCache: true, 69 | UseECDSA: true, 70 | } 71 | benchmarkExt(b, handshakeHandler, bc) 72 | } 73 | 74 | func BenchmarkTLSHandshakeECDSAWithCurvesWithClientSessionCache(b *testing.B) { 75 | bc := &benchConfig{ 76 | IsTLS: true, 77 | DisableClientSessionCache: false, 78 | UseCurves: true, 79 | UseECDSA: true, 80 | } 81 | benchmarkExt(b, handshakeHandler, bc) 82 | } 83 | 84 | func BenchmarkTLSHandshakeECDSAWithCurvesWithoutClientSessionCache(b *testing.B) { 85 | bc := &benchConfig{ 86 | IsTLS: true, 87 | DisableClientSessionCache: true, 88 | UseCurves: true, 89 | UseECDSA: true, 90 | } 91 | benchmarkExt(b, handshakeHandler, bc) 92 | } 93 | 94 | func benchmark(b *testing.B, h fasthttp.RequestHandler, isTLS bool) { 95 | bc := &benchConfig{ 96 | IsTLS: isTLS, 97 | } 98 | benchmarkExt(b, h, bc) 99 | } 100 | 101 | type benchConfig struct { 102 | IsTLS bool 103 | DisableClientSessionCache bool 104 | UseCurves bool 105 | UseECDSA bool 106 | } 107 | 108 | func benchmarkExt(b *testing.B, h fasthttp.RequestHandler, bc *benchConfig) { 109 | var serverTLSConfig, clientTLSConfig *tls.Config 110 | if bc.IsTLS { 111 | certFile := "rsa.pem" 112 | keyFile := "rsa.key" 113 | if bc.UseECDSA { 114 | certFile = "ecdsa.pem" 115 | keyFile = "ecdsa.key" 116 | } 117 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 118 | if err != nil { 119 | b.Fatalf("cannot load TLS certificate from certFile=%q, keyFile=%q: %s", certFile, keyFile, err) 120 | } 121 | serverTLSConfig = &tls.Config{ 122 | Certificates: []tls.Certificate{cert}, 123 | PreferServerCipherSuites: true, 124 | } 125 | serverTLSConfig.CurvePreferences = []tls.CurveID{} 126 | if bc.UseCurves { 127 | serverTLSConfig.CurvePreferences = []tls.CurveID{ 128 | tls.CurveP256, 129 | } 130 | } 131 | clientTLSConfig = &tls.Config{ 132 | InsecureSkipVerify: true, 133 | } 134 | if bc.DisableClientSessionCache { 135 | clientTLSConfig.ClientSessionCache = fakeSessionCache{} 136 | } 137 | } 138 | ln := fasthttputil.NewInmemoryListener() 139 | serverStopCh := make(chan struct{}) 140 | go func() { 141 | serverLn := net.Listener(ln) 142 | if serverTLSConfig != nil { 143 | serverLn = tls.NewListener(serverLn, serverTLSConfig) 144 | } 145 | if err := fasthttp.Serve(serverLn, h); err != nil { 146 | b.Fatalf("unexpected error in server: %s", err) 147 | } 148 | close(serverStopCh) 149 | }() 150 | c := &fasthttp.HostClient{ 151 | Dial: func(addr string) (net.Conn, error) { 152 | return ln.Dial() 153 | }, 154 | IsTLS: clientTLSConfig != nil, 155 | TLSConfig: clientTLSConfig, 156 | } 157 | 158 | b.RunParallel(func(pb *testing.PB) { 159 | runRequests(b, pb, c) 160 | }) 161 | ln.Close() 162 | <-serverStopCh 163 | } 164 | 165 | func streamingHandler(ctx *fasthttp.RequestCtx) { 166 | ctx.WriteString("foobar") //nolint:errcheck 167 | } 168 | 169 | func handshakeHandler(ctx *fasthttp.RequestCtx) { 170 | streamingHandler(ctx) 171 | 172 | // Explicitly close connection after each response. 173 | ctx.SetConnectionClose() 174 | } 175 | 176 | func runRequests(b *testing.B, pb *testing.PB, c *fasthttp.HostClient) { 177 | var req fasthttp.Request 178 | req.SetRequestURI("http://foo.bar/baz") 179 | var resp fasthttp.Response 180 | for pb.Next() { 181 | if err := c.Do(&req, &resp); err != nil { 182 | b.Fatalf("unexpected error: %s", err) 183 | } 184 | if resp.StatusCode() != fasthttp.StatusOK { 185 | b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), fasthttp.StatusOK) 186 | } 187 | } 188 | } 189 | 190 | type fakeSessionCache struct{} 191 | 192 | func (fakeSessionCache) Get(sessionKey string) (*tls.ClientSessionState, bool) { 193 | return nil, false 194 | } 195 | 196 | func (fakeSessionCache) Put(sessionKey string, cs *tls.ClientSessionState) { 197 | // no-op 198 | } 199 | -------------------------------------------------------------------------------- /server_example_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net" 8 | "time" 9 | 10 | "github.com/valyala/fasthttp" 11 | ) 12 | 13 | func ExampleListenAndServe() { 14 | // The server will listen for incoming requests on this address. 15 | listenAddr := "127.0.0.1:80" 16 | 17 | // This function will be called by the server for each incoming request. 18 | // 19 | // RequestCtx provides a lot of functionality related to http request 20 | // processing. See RequestCtx docs for details. 21 | requestHandler := func(ctx *fasthttp.RequestCtx) { 22 | fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path()) 23 | } 24 | 25 | // Start the server with default settings. 26 | // Create Server instance for adjusting server settings. 27 | // 28 | // ListenAndServe returns only on error, so usually it blocks forever. 29 | if err := fasthttp.ListenAndServe(listenAddr, requestHandler); err != nil { 30 | log.Fatalf("error in ListenAndServe: %s", err) 31 | } 32 | } 33 | 34 | func ExampleServe() { 35 | // Create network listener for accepting incoming requests. 36 | // 37 | // Note that you are not limited by TCP listener - arbitrary 38 | // net.Listener may be used by the server. 39 | // For example, unix socket listener or TLS listener. 40 | ln, err := net.Listen("tcp4", "127.0.0.1:8080") 41 | if err != nil { 42 | log.Fatalf("error in net.Listen: %s", err) 43 | } 44 | 45 | // This function will be called by the server for each incoming request. 46 | // 47 | // RequestCtx provides a lot of functionality related to http request 48 | // processing. See RequestCtx docs for details. 49 | requestHandler := func(ctx *fasthttp.RequestCtx) { 50 | fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path()) 51 | } 52 | 53 | // Start the server with default settings. 54 | // Create Server instance for adjusting server settings. 55 | // 56 | // Serve returns on ln.Close() or error, so usually it blocks forever. 57 | if err := fasthttp.Serve(ln, requestHandler); err != nil { 58 | log.Fatalf("error in Serve: %s", err) 59 | } 60 | } 61 | 62 | func ExampleServer() { 63 | // This function will be called by the server for each incoming request. 64 | // 65 | // RequestCtx provides a lot of functionality related to http request 66 | // processing. See RequestCtx docs for details. 67 | requestHandler := func(ctx *fasthttp.RequestCtx) { 68 | fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path()) 69 | } 70 | 71 | // Create custom server. 72 | s := &fasthttp.Server{ 73 | Handler: requestHandler, 74 | 75 | // Every response will contain 'Server: My super server' header. 76 | Name: "My super server", 77 | 78 | // Other Server settings may be set here. 79 | } 80 | 81 | // Start the server listening for incoming requests on the given address. 82 | // 83 | // ListenAndServe returns only on error, so usually it blocks forever. 84 | if err := s.ListenAndServe("127.0.0.1:80"); err != nil { 85 | log.Fatalf("error in ListenAndServe: %s", err) 86 | } 87 | } 88 | 89 | func ExampleRequestCtx_Hijack() { 90 | // hijackHandler is called on hijacked connection. 91 | hijackHandler := func(c net.Conn) { 92 | fmt.Fprintf(c, "This message is sent over a hijacked connection to the client %s\n", c.RemoteAddr()) 93 | fmt.Fprintf(c, "Send me something and I'll echo it to you\n") 94 | var buf [1]byte 95 | for { 96 | if _, err := c.Read(buf[:]); err != nil { 97 | log.Printf("error when reading from hijacked connection: %s", err) 98 | return 99 | } 100 | fmt.Fprintf(c, "You sent me %q. Waiting for new data\n", buf[:]) 101 | } 102 | } 103 | 104 | // requestHandler is called for each incoming request. 105 | requestHandler := func(ctx *fasthttp.RequestCtx) { 106 | path := ctx.Path() 107 | switch { 108 | case string(path) == "/hijack": 109 | // Note that the connection is hijacked only after 110 | // returning from requestHandler and sending http response. 111 | ctx.Hijack(hijackHandler) 112 | 113 | // The connection will be hijacked after sending this response. 114 | fmt.Fprintf(ctx, "Hijacked the connection!") 115 | case string(path) == "/": 116 | fmt.Fprintf(ctx, "Root directory requested") 117 | default: 118 | fmt.Fprintf(ctx, "Requested path is %q", path) 119 | } 120 | } 121 | 122 | if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil { 123 | log.Fatalf("error in ListenAndServe: %s", err) 124 | } 125 | } 126 | 127 | func ExampleRequestCtx_TimeoutError() { 128 | requestHandler := func(ctx *fasthttp.RequestCtx) { 129 | // Emulate long-running task, which touches ctx. 130 | doneCh := make(chan struct{}) 131 | go func() { 132 | workDuration := time.Millisecond * time.Duration(rand.Intn(2000)) 133 | time.Sleep(workDuration) 134 | 135 | fmt.Fprintf(ctx, "ctx has been accessed by long-running task\n") 136 | fmt.Fprintf(ctx, "The reuqestHandler may be finished by this time.\n") 137 | 138 | close(doneCh) 139 | }() 140 | 141 | select { 142 | case <-doneCh: 143 | fmt.Fprintf(ctx, "The task has been finished in less than a second") 144 | case <-time.After(time.Second): 145 | // Since the long-running task is still running and may access ctx, 146 | // we must call TimeoutError before returning from requestHandler. 147 | // 148 | // Otherwise the program will suffer from data races. 149 | ctx.TimeoutError("Timeout!") 150 | } 151 | } 152 | 153 | if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil { 154 | log.Fatalf("error in ListenAndServe: %s", err) 155 | } 156 | } 157 | 158 | func ExampleRequestCtx_Logger() { 159 | requestHandler := func(ctx *fasthttp.RequestCtx) { 160 | if string(ctx.Path()) == "/top-secret" { 161 | ctx.Logger().Printf("Alarm! Alien intrusion detected!") 162 | ctx.Error("Access denied!", fasthttp.StatusForbidden) 163 | return 164 | } 165 | 166 | // Logger may be cached in local variables. 167 | logger := ctx.Logger() 168 | 169 | logger.Printf("Good request from User-Agent %q", ctx.Request.Header.UserAgent()) 170 | fmt.Fprintf(ctx, "Good request to %q", ctx.Path()) 171 | logger.Printf("Multiple log messages may be written during a single request") 172 | } 173 | 174 | if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil { 175 | log.Fatalf("error in ListenAndServe: %s", err) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /workerpool.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "net" 5 | "runtime" 6 | "strings" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // workerPool serves incoming connections via a pool of workers 12 | // in FILO order, i.e. the most recently stopped worker will serve the next 13 | // incoming connection. 14 | // 15 | // Such a scheme keeps CPU caches hot (in theory). 16 | type workerPool struct { 17 | // Function for serving server connections. 18 | // It must leave c unclosed. 19 | WorkerFunc ServeHandler 20 | 21 | MaxWorkersCount int 22 | 23 | LogAllErrors bool 24 | 25 | MaxIdleWorkerDuration time.Duration 26 | 27 | Logger Logger 28 | 29 | lock sync.Mutex 30 | workersCount int 31 | mustStop bool 32 | 33 | ready []*workerChan 34 | 35 | stopCh chan struct{} 36 | 37 | workerChanPool sync.Pool 38 | 39 | connState func(net.Conn, ConnState) 40 | } 41 | 42 | type workerChan struct { 43 | lastUseTime time.Time 44 | ch chan net.Conn 45 | } 46 | 47 | func (wp *workerPool) Start() { 48 | if wp.stopCh != nil { 49 | panic("BUG: workerPool already started") 50 | } 51 | wp.stopCh = make(chan struct{}) 52 | stopCh := wp.stopCh 53 | wp.workerChanPool.New = func() interface{} { 54 | return &workerChan{ 55 | ch: make(chan net.Conn, workerChanCap), 56 | } 57 | } 58 | go func() { 59 | var scratch []*workerChan 60 | for { 61 | wp.clean(&scratch) 62 | select { 63 | case <-stopCh: 64 | return 65 | default: 66 | time.Sleep(wp.getMaxIdleWorkerDuration()) 67 | } 68 | } 69 | }() 70 | } 71 | 72 | func (wp *workerPool) Stop() { 73 | if wp.stopCh == nil { 74 | panic("BUG: workerPool wasn't started") 75 | } 76 | close(wp.stopCh) 77 | wp.stopCh = nil 78 | 79 | // Stop all the workers waiting for incoming connections. 80 | // Do not wait for busy workers - they will stop after 81 | // serving the connection and noticing wp.mustStop = true. 82 | wp.lock.Lock() 83 | ready := wp.ready 84 | for i := range ready { 85 | ready[i].ch <- nil 86 | ready[i] = nil 87 | } 88 | wp.ready = ready[:0] 89 | wp.mustStop = true 90 | wp.lock.Unlock() 91 | } 92 | 93 | func (wp *workerPool) getMaxIdleWorkerDuration() time.Duration { 94 | if wp.MaxIdleWorkerDuration <= 0 { 95 | return 10 * time.Second 96 | } 97 | return wp.MaxIdleWorkerDuration 98 | } 99 | 100 | func (wp *workerPool) clean(scratch *[]*workerChan) { 101 | maxIdleWorkerDuration := wp.getMaxIdleWorkerDuration() 102 | 103 | // Clean least recently used workers if they didn't serve connections 104 | // for more than maxIdleWorkerDuration. 105 | criticalTime := time.Now().Add(-maxIdleWorkerDuration) 106 | 107 | wp.lock.Lock() 108 | ready := wp.ready 109 | n := len(ready) 110 | 111 | // Use binary-search algorithm to find out the index of the least recently worker which can be cleaned up. 112 | l, r, mid := 0, n-1, 0 113 | for l <= r { 114 | mid = (l + r) / 2 115 | if criticalTime.After(wp.ready[mid].lastUseTime) { 116 | l = mid + 1 117 | } else { 118 | r = mid - 1 119 | } 120 | } 121 | i := r 122 | if i == -1 { 123 | wp.lock.Unlock() 124 | return 125 | } 126 | 127 | *scratch = append((*scratch)[:0], ready[:i+1]...) 128 | m := copy(ready, ready[i+1:]) 129 | for i = m; i < n; i++ { 130 | ready[i] = nil 131 | } 132 | wp.ready = ready[:m] 133 | wp.lock.Unlock() 134 | 135 | // Notify obsolete workers to stop. 136 | // This notification must be outside the wp.lock, since ch.ch 137 | // may be blocking and may consume a lot of time if many workers 138 | // are located on non-local CPUs. 139 | tmp := *scratch 140 | for i := range tmp { 141 | tmp[i].ch <- nil 142 | tmp[i] = nil 143 | } 144 | } 145 | 146 | func (wp *workerPool) Serve(c net.Conn) bool { 147 | ch := wp.getCh() 148 | if ch == nil { 149 | return false 150 | } 151 | ch.ch <- c 152 | return true 153 | } 154 | 155 | var workerChanCap = func() int { 156 | // Use blocking workerChan if GOMAXPROCS=1. 157 | // This immediately switches Serve to WorkerFunc, which results 158 | // in higher performance (under go1.5 at least). 159 | if runtime.GOMAXPROCS(0) == 1 { 160 | return 0 161 | } 162 | 163 | // Use non-blocking workerChan if GOMAXPROCS>1, 164 | // since otherwise the Serve caller (Acceptor) may lag accepting 165 | // new connections if WorkerFunc is CPU-bound. 166 | return 1 167 | }() 168 | 169 | func (wp *workerPool) getCh() *workerChan { 170 | var ch *workerChan 171 | createWorker := false 172 | 173 | wp.lock.Lock() 174 | ready := wp.ready 175 | n := len(ready) - 1 176 | if n < 0 { 177 | if wp.workersCount < wp.MaxWorkersCount { 178 | createWorker = true 179 | wp.workersCount++ 180 | } 181 | } else { 182 | ch = ready[n] 183 | ready[n] = nil 184 | wp.ready = ready[:n] 185 | } 186 | wp.lock.Unlock() 187 | 188 | if ch == nil { 189 | if !createWorker { 190 | return nil 191 | } 192 | vch := wp.workerChanPool.Get() 193 | ch = vch.(*workerChan) 194 | go func() { 195 | wp.workerFunc(ch) 196 | wp.workerChanPool.Put(vch) 197 | }() 198 | } 199 | return ch 200 | } 201 | 202 | func (wp *workerPool) release(ch *workerChan) bool { 203 | ch.lastUseTime = time.Now() 204 | wp.lock.Lock() 205 | if wp.mustStop { 206 | wp.lock.Unlock() 207 | return false 208 | } 209 | wp.ready = append(wp.ready, ch) 210 | wp.lock.Unlock() 211 | return true 212 | } 213 | 214 | func (wp *workerPool) workerFunc(ch *workerChan) { 215 | var c net.Conn 216 | 217 | var err error 218 | for c = range ch.ch { 219 | if c == nil { 220 | break 221 | } 222 | 223 | if err = wp.WorkerFunc(c); err != nil && err != errHijacked { 224 | errStr := err.Error() 225 | if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") || 226 | strings.Contains(errStr, "reset by peer") || 227 | strings.Contains(errStr, "request headers: small read buffer") || 228 | strings.Contains(errStr, "unexpected EOF") || 229 | strings.Contains(errStr, "i/o timeout")) { 230 | wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err) 231 | } 232 | } 233 | if err == errHijacked { 234 | wp.connState(c, StateHijacked) 235 | } else { 236 | _ = c.Close() 237 | wp.connState(c, StateClosed) 238 | } 239 | c = nil 240 | 241 | if !wp.release(ch) { 242 | break 243 | } 244 | } 245 | 246 | wp.lock.Lock() 247 | wp.workersCount-- 248 | wp.lock.Unlock() 249 | } 250 | -------------------------------------------------------------------------------- /headers.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | // Headers 4 | const ( 5 | // Authentication 6 | HeaderAuthorization = "Authorization" 7 | HeaderProxyAuthenticate = "Proxy-Authenticate" 8 | HeaderProxyAuthorization = "Proxy-Authorization" 9 | HeaderWWWAuthenticate = "WWW-Authenticate" 10 | 11 | // Caching 12 | HeaderAge = "Age" 13 | HeaderCacheControl = "Cache-Control" 14 | HeaderClearSiteData = "Clear-Site-Data" 15 | HeaderExpires = "Expires" 16 | HeaderPragma = "Pragma" 17 | HeaderWarning = "Warning" 18 | 19 | // Client hints 20 | HeaderAcceptCH = "Accept-CH" 21 | HeaderAcceptCHLifetime = "Accept-CH-Lifetime" 22 | HeaderContentDPR = "Content-DPR" 23 | HeaderDPR = "DPR" 24 | HeaderEarlyData = "Early-Data" 25 | HeaderSaveData = "Save-Data" 26 | HeaderViewportWidth = "Viewport-Width" 27 | HeaderWidth = "Width" 28 | 29 | // Conditionals 30 | HeaderETag = "ETag" 31 | HeaderIfMatch = "If-Match" 32 | HeaderIfModifiedSince = "If-Modified-Since" 33 | HeaderIfNoneMatch = "If-None-Match" 34 | HeaderIfUnmodifiedSince = "If-Unmodified-Since" 35 | HeaderLastModified = "Last-Modified" 36 | HeaderVary = "Vary" 37 | 38 | // Connection management 39 | HeaderConnection = "Connection" 40 | HeaderKeepAlive = "Keep-Alive" 41 | 42 | // Content negotiation 43 | HeaderAccept = "Accept" 44 | HeaderAcceptCharset = "Accept-Charset" 45 | HeaderAcceptEncoding = "Accept-Encoding" 46 | HeaderAcceptLanguage = "Accept-Language" 47 | 48 | // Controls 49 | HeaderCookie = "Cookie" 50 | HeaderExpect = "Expect" 51 | HeaderMaxForwards = "Max-Forwards" 52 | HeaderSetCookie = "Set-Cookie" 53 | 54 | // CORS 55 | HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" 56 | HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" 57 | HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" 58 | HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" 59 | HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" 60 | HeaderAccessControlMaxAge = "Access-Control-Max-Age" 61 | HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" 62 | HeaderAccessControlRequestMethod = "Access-Control-Request-Method" 63 | HeaderOrigin = "Origin" 64 | HeaderTimingAllowOrigin = "Timing-Allow-Origin" 65 | HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" 66 | 67 | // Do Not Track 68 | HeaderDNT = "DNT" 69 | HeaderTk = "Tk" 70 | 71 | // Downloads 72 | HeaderContentDisposition = "Content-Disposition" 73 | 74 | // Message body information 75 | HeaderContentEncoding = "Content-Encoding" 76 | HeaderContentLanguage = "Content-Language" 77 | HeaderContentLength = "Content-Length" 78 | HeaderContentLocation = "Content-Location" 79 | HeaderContentType = "Content-Type" 80 | 81 | // Proxies 82 | HeaderForwarded = "Forwarded" 83 | HeaderVia = "Via" 84 | HeaderXForwardedFor = "X-Forwarded-For" 85 | HeaderXForwardedHost = "X-Forwarded-Host" 86 | HeaderXForwardedProto = "X-Forwarded-Proto" 87 | 88 | // Redirects 89 | HeaderLocation = "Location" 90 | 91 | // Request context 92 | HeaderFrom = "From" 93 | HeaderHost = "Host" 94 | HeaderReferer = "Referer" 95 | HeaderReferrerPolicy = "Referrer-Policy" 96 | HeaderUserAgent = "User-Agent" 97 | 98 | // Response context 99 | HeaderAllow = "Allow" 100 | HeaderServer = "Server" 101 | 102 | // Range requests 103 | HeaderAcceptRanges = "Accept-Ranges" 104 | HeaderContentRange = "Content-Range" 105 | HeaderIfRange = "If-Range" 106 | HeaderRange = "Range" 107 | 108 | // Security 109 | HeaderContentSecurityPolicy = "Content-Security-Policy" 110 | HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" 111 | HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" 112 | HeaderExpectCT = "Expect-CT" 113 | HeaderFeaturePolicy = "Feature-Policy" 114 | HeaderPublicKeyPins = "Public-Key-Pins" 115 | HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" 116 | HeaderStrictTransportSecurity = "Strict-Transport-Security" 117 | HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" 118 | HeaderXContentTypeOptions = "X-Content-Type-Options" 119 | HeaderXDownloadOptions = "X-Download-Options" 120 | HeaderXFrameOptions = "X-Frame-Options" 121 | HeaderXPoweredBy = "X-Powered-By" 122 | HeaderXXSSProtection = "X-XSS-Protection" 123 | 124 | // Server-sent event 125 | HeaderLastEventID = "Last-Event-ID" 126 | HeaderNEL = "NEL" 127 | HeaderPingFrom = "Ping-From" 128 | HeaderPingTo = "Ping-To" 129 | HeaderReportTo = "Report-To" 130 | 131 | // Transfer coding 132 | HeaderTE = "TE" 133 | HeaderTrailer = "Trailer" 134 | HeaderTransferEncoding = "Transfer-Encoding" 135 | 136 | // WebSockets 137 | HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" 138 | HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" 139 | HeaderSecWebSocketKey = "Sec-WebSocket-Key" 140 | HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" 141 | HeaderSecWebSocketVersion = "Sec-WebSocket-Version" 142 | 143 | // Other 144 | HeaderAcceptPatch = "Accept-Patch" 145 | HeaderAcceptPushPolicy = "Accept-Push-Policy" 146 | HeaderAcceptSignature = "Accept-Signature" 147 | HeaderAltSvc = "Alt-Svc" 148 | HeaderDate = "Date" 149 | HeaderIndex = "Index" 150 | HeaderLargeAllocation = "Large-Allocation" 151 | HeaderLink = "Link" 152 | HeaderPushPolicy = "Push-Policy" 153 | HeaderRetryAfter = "Retry-After" 154 | HeaderServerTiming = "Server-Timing" 155 | HeaderSignature = "Signature" 156 | HeaderSignedHeaders = "Signed-Headers" 157 | HeaderSourceMap = "SourceMap" 158 | HeaderUpgrade = "Upgrade" 159 | HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" 160 | HeaderXPingback = "X-Pingback" 161 | HeaderXRequestedWith = "X-Requested-With" 162 | HeaderXRobotsTag = "X-Robots-Tag" 163 | HeaderXUACompatible = "X-UA-Compatible" 164 | ) 165 | -------------------------------------------------------------------------------- /compress_test.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var compressTestcases = func() []string { 12 | a := []string{ 13 | "", 14 | "foobar", 15 | "выфаодлодл одлфываыв sd2 k34", 16 | } 17 | bigS := createFixedBody(1e4) 18 | a = append(a, string(bigS)) 19 | return a 20 | }() 21 | 22 | func TestGzipBytesSerial(t *testing.T) { 23 | t.Parallel() 24 | 25 | if err := testGzipBytes(); err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | 30 | func TestGzipBytesConcurrent(t *testing.T) { 31 | t.Parallel() 32 | 33 | if err := testConcurrent(10, testGzipBytes); err != nil { 34 | t.Fatal(err) 35 | } 36 | } 37 | 38 | func TestDeflateBytesSerial(t *testing.T) { 39 | t.Parallel() 40 | 41 | if err := testDeflateBytes(); err != nil { 42 | t.Fatal(err) 43 | } 44 | } 45 | 46 | func TestDeflateBytesConcurrent(t *testing.T) { 47 | t.Parallel() 48 | 49 | if err := testConcurrent(10, testDeflateBytes); err != nil { 50 | t.Fatal(err) 51 | } 52 | } 53 | 54 | func testGzipBytes() error { 55 | for _, s := range compressTestcases { 56 | if err := testGzipBytesSingleCase(s); err != nil { 57 | return err 58 | } 59 | } 60 | return nil 61 | } 62 | 63 | func testDeflateBytes() error { 64 | for _, s := range compressTestcases { 65 | if err := testDeflateBytesSingleCase(s); err != nil { 66 | return err 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | func testGzipBytesSingleCase(s string) error { 73 | prefix := []byte("foobar") 74 | gzippedS := AppendGzipBytes(prefix, []byte(s)) 75 | if !bytes.Equal(gzippedS[:len(prefix)], prefix) { 76 | return fmt.Errorf("unexpected prefix when compressing %q: %q. Expecting %q", s, gzippedS[:len(prefix)], prefix) 77 | } 78 | 79 | gunzippedS, err := AppendGunzipBytes(prefix, gzippedS[len(prefix):]) 80 | if err != nil { 81 | return fmt.Errorf("unexpected error when uncompressing %q: %s", s, err) 82 | } 83 | if !bytes.Equal(gunzippedS[:len(prefix)], prefix) { 84 | return fmt.Errorf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, gunzippedS[:len(prefix)], prefix) 85 | } 86 | gunzippedS = gunzippedS[len(prefix):] 87 | if string(gunzippedS) != s { 88 | return fmt.Errorf("unexpected uncompressed string %q. Expecting %q", gunzippedS, s) 89 | } 90 | return nil 91 | } 92 | 93 | func testDeflateBytesSingleCase(s string) error { 94 | prefix := []byte("foobar") 95 | deflatedS := AppendDeflateBytes(prefix, []byte(s)) 96 | if !bytes.Equal(deflatedS[:len(prefix)], prefix) { 97 | return fmt.Errorf("unexpected prefix when compressing %q: %q. Expecting %q", s, deflatedS[:len(prefix)], prefix) 98 | } 99 | 100 | inflatedS, err := AppendInflateBytes(prefix, deflatedS[len(prefix):]) 101 | if err != nil { 102 | return fmt.Errorf("unexpected error when uncompressing %q: %s", s, err) 103 | } 104 | if !bytes.Equal(inflatedS[:len(prefix)], prefix) { 105 | return fmt.Errorf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, inflatedS[:len(prefix)], prefix) 106 | } 107 | inflatedS = inflatedS[len(prefix):] 108 | if string(inflatedS) != s { 109 | return fmt.Errorf("unexpected uncompressed string %q. Expecting %q", inflatedS, s) 110 | } 111 | return nil 112 | } 113 | 114 | func TestGzipCompressSerial(t *testing.T) { 115 | t.Parallel() 116 | 117 | if err := testGzipCompress(); err != nil { 118 | t.Fatal(err) 119 | } 120 | } 121 | 122 | func TestGzipCompressConcurrent(t *testing.T) { 123 | t.Parallel() 124 | 125 | if err := testConcurrent(10, testGzipCompress); err != nil { 126 | t.Fatal(err) 127 | } 128 | } 129 | 130 | func TestFlateCompressSerial(t *testing.T) { 131 | t.Parallel() 132 | 133 | if err := testFlateCompress(); err != nil { 134 | t.Fatal(err) 135 | } 136 | } 137 | 138 | func TestFlateCompressConcurrent(t *testing.T) { 139 | t.Parallel() 140 | 141 | if err := testConcurrent(10, testFlateCompress); err != nil { 142 | t.Fatal(err) 143 | } 144 | } 145 | 146 | func testGzipCompress() error { 147 | for _, s := range compressTestcases { 148 | if err := testGzipCompressSingleCase(s); err != nil { 149 | return err 150 | } 151 | } 152 | return nil 153 | } 154 | 155 | func testFlateCompress() error { 156 | for _, s := range compressTestcases { 157 | if err := testFlateCompressSingleCase(s); err != nil { 158 | return err 159 | } 160 | } 161 | return nil 162 | } 163 | 164 | func testGzipCompressSingleCase(s string) error { 165 | var buf bytes.Buffer 166 | zw := acquireStacklessGzipWriter(&buf, CompressDefaultCompression) 167 | if _, err := zw.Write([]byte(s)); err != nil { 168 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 169 | } 170 | releaseStacklessGzipWriter(zw, CompressDefaultCompression) 171 | 172 | zr, err := acquireGzipReader(&buf) 173 | if err != nil { 174 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 175 | } 176 | body, err := ioutil.ReadAll(zr) 177 | if err != nil { 178 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 179 | } 180 | if string(body) != s { 181 | return fmt.Errorf("unexpected string after decompression: %q. Expecting %q", body, s) 182 | } 183 | releaseGzipReader(zr) 184 | return nil 185 | } 186 | 187 | func testFlateCompressSingleCase(s string) error { 188 | var buf bytes.Buffer 189 | zw := acquireStacklessDeflateWriter(&buf, CompressDefaultCompression) 190 | if _, err := zw.Write([]byte(s)); err != nil { 191 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 192 | } 193 | releaseStacklessDeflateWriter(zw, CompressDefaultCompression) 194 | 195 | zr, err := acquireFlateReader(&buf) 196 | if err != nil { 197 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 198 | } 199 | body, err := ioutil.ReadAll(zr) 200 | if err != nil { 201 | return fmt.Errorf("unexpected error: %s. s=%q", err, s) 202 | } 203 | if string(body) != s { 204 | return fmt.Errorf("unexpected string after decompression: %q. Expecting %q", body, s) 205 | } 206 | releaseFlateReader(zr) 207 | return nil 208 | } 209 | 210 | func testConcurrent(concurrency int, f func() error) error { 211 | ch := make(chan error, concurrency) 212 | for i := 0; i < concurrency; i++ { 213 | go func(idx int) { 214 | err := f() 215 | if err != nil { 216 | ch <- fmt.Errorf("error in goroutine %d: %s", idx, err) 217 | } 218 | ch <- nil 219 | }(i) 220 | } 221 | for i := 0; i < concurrency; i++ { 222 | select { 223 | case err := <-ch: 224 | if err != nil { 225 | return err 226 | } 227 | case <-time.After(time.Second): 228 | return fmt.Errorf("timeout") 229 | } 230 | } 231 | return nil 232 | } 233 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ### TL;DR 2 | 3 | We use a simplified version of [Golang Security Policy](https://golang.org/security). 4 | For example, for now we skip CVE assignment. 5 | 6 | ### Reporting a Security Bug 7 | 8 | Please report to us any issues you find. This document explains how to do that and what to expect in return. 9 | 10 | All security bugs in our releases should be reported by email to oss-security@highload.solutions. 11 | This mail is delivered to a small security team. 12 | Your email will be acknowledged within 24 hours, and you'll receive a more detailed response 13 | to your email within 72 hours indicating the next steps in handling your report. 14 | For critical problems, you can encrypt your report using our PGP key (listed below). 15 | 16 | Please use a descriptive subject line for your report email. 17 | After the initial reply to your report, the security team will 18 | endeavor to keep you informed of the progress being made towards a fix and full announcement. 19 | These updates will be sent at least every five days. 20 | In reality, this is more likely to be every 24-48 hours. 21 | 22 | If you have not received a reply to your email within 48 hours or you have not heard from the security 23 | team for the past five days please contact us by email to developers@highload.solutions or by Telegram message 24 | to [our support](https://t.me/highload_support). 25 | Please note that developers@highload.solutions list includes all developers, who may be outside our opensource security team. 26 | When escalating on this list, please do not disclose the details of the issue. 27 | Simply state that you're trying to reach a member of the security team. 28 | 29 | ### Flagging Existing Issues as Security-related 30 | 31 | If you believe that an existing issue is security-related, we ask that you send an email to oss-security@highload.solutions. 32 | The email should include the issue ID and a short description of why it should be handled according to this security policy. 33 | 34 | ### Disclosure Process 35 | 36 | Our project uses the following disclosure process: 37 | 38 | - Once the security report is received it is assigned a primary handler. This person coordinates the fix and release process. 39 | - The issue is confirmed and a list of affected software is determined. 40 | - Code is audited to find any potential similar problems. 41 | - Fixes are prepared for the two most recent major releases and the head/master revision. These fixes are not yet committed to the public repository. 42 | - To notify users, a new issue without security details is submitted to our GitHub repository. 43 | - Three working days following this notification, the fixes are applied to the public repository and a new release is issued. 44 | - On the date that the fixes are applied, announcement is published in the issue. 45 | 46 | This process can take some time, especially when coordination is required with maintainers of other projects. 47 | Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow 48 | the process described above to ensure that disclosures are handled consistently. 49 | 50 | ### Receiving Security Updates 51 | The best way to receive security announcements is to subscribe ("Watch") to our repository. 52 | Any GitHub issues pertaining to a security issue will be prefixed with [security]. 53 | 54 | ### Comments on This Policy 55 | If you have any suggestions to improve this policy, please send an email to oss-security@highload.solutions for discussion. 56 | 57 | ### PGP Key for oss-security@highload.solutions 58 | 59 | We accept PGP-encrypted email, but the majority of the security team are not regular PGP users 60 | so it's somewhat inconvenient. Please only use PGP for critical security reports. 61 | 62 | ``` 63 | -----BEGIN PGP PUBLIC KEY BLOCK----- 64 | 65 | mQINBFzdjYUBEACa3YN+QVSlnXofUjxr+YrmIaF+da0IUq+TRM4aqUXALsemEdGh 66 | yIl7Z6qOOy1d2kPe6t//H9l/92lJ1X7i6aEBK4n/pnPZkwbpy9gGpebgvTZFvcbe 67 | mFhF6k1FM35D8TxneJSjizPyGhJPqcr5qccqf8R64TlQx5Ud1JqT2l8P1C5N7gNS 68 | lEYXq1h4zBCvTWk1wdeLRRPx7Bn6xrgmyu/k61dLoJDvpvWNATVFDA67oTrPgzTW 69 | xtLbbk/xm0mK4a8zMzIpNyz1WkaJW9+4HFXaL+yKlsx7iHe2O7VlGoqS0kdeQup4 70 | 1HIw/P7yc0jBlNMLUzpuA6ElYUwESWsnCI71YY1x4rKgI+GqH1mWwgn7tteuXQtb 71 | Zj0vEdjK3IKIOSbzbzAvSbDt8F1+o7EMtdy1eUysjKSQgFkDlT6JRmYvEup5/IoG 72 | iknh/InQq9RmGFKii6pXWWoltC0ebfCwYOXvymyDdr/hYDqJeHS9Tenpy86Doaaf 73 | HGf5nIFAMB2G5ctNpBwzNXR2MAWkeHQgdr5a1xmog0hS125usjnUTet3QeCyo4kd 74 | gVouoOroMcqFFUXdYaMH4c3KWz0afhTmIaAsFFOv/eMdadVA4QyExTJf3TAoQ+kH 75 | lKDlbOAIxEZWRPDFxMRixaVPQC+VxhBcaQ+yNoaUkM0V2m8u8sDBpzi1OQARAQAB 76 | tDxPU1MgU2VjdXJpdHksIEhpZ2hsb2FkIExURCA8b3NzLXNlY3VyaXR5QGhpZ2hs 77 | b2FkLnNvbHV0aW9ucz6JAlQEEwEIAD4WIQRljYp380uKq2g8TeqsQcvu+Qp2TAUC 78 | XN2NhQIbAwUJB4YfgAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCsQcvu+Qp2 79 | TKmED/96YoQoOjD28blFFrigvAsiNcNNZoX9I0dX1lNpD83fBJf+/9i+x4jqUnI5 80 | 5XK/DFTDbhpw8kQBpxS9eEuIYnuo0RdLLp1ctNWTlpwfyHn92mGddl/uBdYHUuUk 81 | cjhIQcFaCcWRY+EpamDlv1wmZ83IwBr8Hu5FS+/Msyw1TBvtTRVKW1KoGYMYoXLk 82 | BzIglRPwn821B6s4BvK/RJnZkrmHMBZBfYMf+iSMSYd2yPmfT8wbcAjgjLfQa28U 83 | gbt4u9xslgKjuM83IqwFfEXBnm7su3OouGWqc+62mQTsbnK65zRFnx6GXRXC1BAi 84 | 6m9Tm1PU0IiINz66ainquspkXYeHjd9hTwfR3BdFnzBTRRM01cKMFabWbLj8j0p8 85 | fF4g9cxEdiLrzEF7Yz4WY0mI4Cpw4eJZfsHMc07Jn7QxfJhIoq+rqBOtEmTjnxMh 86 | aWeykoXMHlZN4K0ZrAytozVH1D4bugWA9Zuzi9U3F9hrVVABm11yyhd2iSqI6/FR 87 | GcCFOCBW1kEJbzoEguub+BV8LDi8ldljHalvur5k/VFhoDBxniYNsKmiCLVCmDWs 88 | /nF84hCReAOJt0vDGwqHe3E2BFFPbKwdJLRNkjxBY0c/pvaV+JxbWQmaxDZNeIFV 89 | hFcVGp48HNY3qLWZdsQIfT9m1masJFLVuq8Wx7bYs8Et5eFnH7kCDQRc3Y2FARAA 90 | 2DJWAxABydyIdCxgFNdqnYyWS46vh2DmLmRMqgasNlD0ozG4S9bszBsgnUI2Xs06 91 | J76kFRh8MMHcu9I4lUKCQzfrA4uHkiOK5wvNCaWP+C6JUYNHsqPwk/ILO3gtQ/Ws 92 | LLf/PW3rJZVOZB+WY8iaYc20l5vukTaVw4qbEi9dtLkJvVpNHt//+jayXU6s3ew1 93 | 2X5xdwyAZxaxlnzFaY/Xo/qR+bZhVFC0T9pAECnHv9TVhFGp0JE9ipPGnro5xTIS 94 | LttdAkzv4AuSVTIgWgTkh8nN8t7STJqfPEv0I12nmmYHMUyTYOurkfskF3jY2x6x 95 | 8l02NQ4d5KdC3ReV1j51swrGcZCwsWNp51jnEXKwo+B0NM5OmoRrNJgF2iDgLehs 96 | hP00ljU7cB8/1/7kdHZStYaUHICFOFqHzg415FlYm+jpY0nJp/b9BAO0d0/WYnEe 97 | Xjihw8EVBAqzEt4kay1BQonZAypeYnGBJr7vNvdiP+mnRwly5qZSGiInxGvtZZFt 98 | zL1E3osiF+muQxFcM63BeGdJeYXy+MoczkWa4WNggfcHlGAZkMYiv28zpr4PfrK9 99 | mvj4Nu8s71PE9pPpBoZcNDf9v1sHuu96jDSITsPx5YMvvKZWhzJXFKzk6YgAsNH/ 100 | MF0G+/qmKJZpCdvtHKpYM1uHX85H81CwWJFfBPthyD8AEQEAAYkCPAQYAQgAJhYh 101 | BGWNinfzS4qraDxN6qxBy+75CnZMBQJc3Y2FAhsMBQkHhh+AAAoJEKxBy+75CnZM 102 | Rn8P/RyL1bhU4Q4WpvmlkepCAwNA0G3QvnKcSZNHEPE5h7H3IyrA/qy16A9eOsgm 103 | sthsHYlo5A5lRIy4wPHkFCClMrMHdKuoS72//qgw+oOrBcwb7Te+Nas+ewhaJ7N9 104 | vAX06vDH9bLl52CPbtats5+eBpePgP3HDPxd7CWHxq9bzJTbzqsTkN7JvoovR2dP 105 | itPJDij7QYLYVEM1t7QxUVpVwAjDi/kCtC9ts5L+V0snF2n3bHZvu04EXdpvxOQI 106 | pG/7Q+/WoI8NU6Bb/FA3tJGYIhSwI3SY+5XV/TAZttZaYSh2SD8vhc+eo+gW9sAN 107 | xa+VESBQCht9+tKIwEwHs1efoRgFdbwwJ2c+33+XydQ6yjdXoX1mn2uyCr82jorZ 108 | xTzbkY04zr7oZ+0fLpouOFg/mrSL4w2bWEhdHuyoVthLBjnRme0wXCaS3g3mYdLG 109 | nSUkogOGOOvvvBtoq/vfx0Eu79piUtw5D8yQSrxLDuz8GxCrVRZ0tYIHb26aTE9G 110 | cDsW/Lg5PjcY/LgVNEWOxDQDFVurlImnlVJFb3q+NrWvPbgeIEWwJDCay/z25SEH 111 | k3bSOXLp8YGRnlkWUmoeL4g/CCK52iAAlfscZNoKMILhBnbCoD657jpa5GQKJj/U 112 | Q8kjgr7kwV/RSosNV9HCPj30mVyiCQ1xg+ZLzMKXVCuBWd+G 113 | =lnt2 114 | -----END PGP PUBLIC KEY BLOCK----- 115 | ``` 116 | -------------------------------------------------------------------------------- /fasthttputil/pipeconns.go: -------------------------------------------------------------------------------- 1 | package fasthttputil 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // NewPipeConns returns new bi-directional connection pipe. 12 | // 13 | // PipeConns is NOT safe for concurrent use by multiple goroutines! 14 | func NewPipeConns() *PipeConns { 15 | ch1 := make(chan *byteBuffer, 4) 16 | ch2 := make(chan *byteBuffer, 4) 17 | 18 | pc := &PipeConns{ 19 | stopCh: make(chan struct{}), 20 | } 21 | pc.c1.rCh = ch1 22 | pc.c1.wCh = ch2 23 | pc.c2.rCh = ch2 24 | pc.c2.wCh = ch1 25 | pc.c1.pc = pc 26 | pc.c2.pc = pc 27 | return pc 28 | } 29 | 30 | // PipeConns provides bi-directional connection pipe, 31 | // which use in-process memory as a transport. 32 | // 33 | // PipeConns must be created by calling NewPipeConns. 34 | // 35 | // PipeConns has the following additional features comparing to connections 36 | // returned from net.Pipe(): 37 | // 38 | // * It is faster. 39 | // * It buffers Write calls, so there is no need to have concurrent goroutine 40 | // calling Read in order to unblock each Write call. 41 | // * It supports read and write deadlines. 42 | // 43 | // PipeConns is NOT safe for concurrent use by multiple goroutines! 44 | type PipeConns struct { 45 | c1 pipeConn 46 | c2 pipeConn 47 | stopCh chan struct{} 48 | stopChLock sync.Mutex 49 | } 50 | 51 | // Conn1 returns the first end of bi-directional pipe. 52 | // 53 | // Data written to Conn1 may be read from Conn2. 54 | // Data written to Conn2 may be read from Conn1. 55 | func (pc *PipeConns) Conn1() net.Conn { 56 | return &pc.c1 57 | } 58 | 59 | // Conn2 returns the second end of bi-directional pipe. 60 | // 61 | // Data written to Conn2 may be read from Conn1. 62 | // Data written to Conn1 may be read from Conn2. 63 | func (pc *PipeConns) Conn2() net.Conn { 64 | return &pc.c2 65 | } 66 | 67 | // Close closes pipe connections. 68 | func (pc *PipeConns) Close() error { 69 | pc.stopChLock.Lock() 70 | select { 71 | case <-pc.stopCh: 72 | default: 73 | close(pc.stopCh) 74 | } 75 | pc.stopChLock.Unlock() 76 | 77 | return nil 78 | } 79 | 80 | type pipeConn struct { 81 | b *byteBuffer 82 | bb []byte 83 | 84 | rCh chan *byteBuffer 85 | wCh chan *byteBuffer 86 | pc *PipeConns 87 | 88 | readDeadlineTimer *time.Timer 89 | writeDeadlineTimer *time.Timer 90 | 91 | readDeadlineCh <-chan time.Time 92 | writeDeadlineCh <-chan time.Time 93 | 94 | readDeadlineChLock sync.Mutex 95 | } 96 | 97 | func (c *pipeConn) Write(p []byte) (int, error) { 98 | b := acquireByteBuffer() 99 | b.b = append(b.b[:0], p...) 100 | 101 | select { 102 | case <-c.pc.stopCh: 103 | releaseByteBuffer(b) 104 | return 0, errConnectionClosed 105 | default: 106 | } 107 | 108 | select { 109 | case c.wCh <- b: 110 | default: 111 | select { 112 | case c.wCh <- b: 113 | case <-c.writeDeadlineCh: 114 | c.writeDeadlineCh = closedDeadlineCh 115 | return 0, ErrTimeout 116 | case <-c.pc.stopCh: 117 | releaseByteBuffer(b) 118 | return 0, errConnectionClosed 119 | } 120 | } 121 | 122 | return len(p), nil 123 | } 124 | 125 | func (c *pipeConn) Read(p []byte) (int, error) { 126 | mayBlock := true 127 | nn := 0 128 | for len(p) > 0 { 129 | n, err := c.read(p, mayBlock) 130 | nn += n 131 | if err != nil { 132 | if !mayBlock && err == errWouldBlock { 133 | err = nil 134 | } 135 | return nn, err 136 | } 137 | p = p[n:] 138 | mayBlock = false 139 | } 140 | 141 | return nn, nil 142 | } 143 | 144 | func (c *pipeConn) read(p []byte, mayBlock bool) (int, error) { 145 | if len(c.bb) == 0 { 146 | if err := c.readNextByteBuffer(mayBlock); err != nil { 147 | return 0, err 148 | } 149 | } 150 | n := copy(p, c.bb) 151 | c.bb = c.bb[n:] 152 | 153 | return n, nil 154 | } 155 | 156 | func (c *pipeConn) readNextByteBuffer(mayBlock bool) error { 157 | releaseByteBuffer(c.b) 158 | c.b = nil 159 | 160 | select { 161 | case c.b = <-c.rCh: 162 | default: 163 | if !mayBlock { 164 | return errWouldBlock 165 | } 166 | c.readDeadlineChLock.Lock() 167 | readDeadlineCh := c.readDeadlineCh 168 | c.readDeadlineChLock.Unlock() 169 | select { 170 | case c.b = <-c.rCh: 171 | case <-readDeadlineCh: 172 | c.readDeadlineChLock.Lock() 173 | c.readDeadlineCh = closedDeadlineCh 174 | c.readDeadlineChLock.Unlock() 175 | // rCh may contain data when deadline is reached. 176 | // Read the data before returning ErrTimeout. 177 | select { 178 | case c.b = <-c.rCh: 179 | default: 180 | return ErrTimeout 181 | } 182 | case <-c.pc.stopCh: 183 | // rCh may contain data when stopCh is closed. 184 | // Read the data before returning EOF. 185 | select { 186 | case c.b = <-c.rCh: 187 | default: 188 | return io.EOF 189 | } 190 | } 191 | } 192 | 193 | c.bb = c.b.b 194 | return nil 195 | } 196 | 197 | var ( 198 | errWouldBlock = errors.New("would block") 199 | errConnectionClosed = errors.New("connection closed") 200 | ) 201 | 202 | type timeoutError struct { 203 | } 204 | 205 | func (e *timeoutError) Error() string { 206 | return "timeout" 207 | } 208 | 209 | // Only implement the Timeout() function of the net.Error interface. 210 | // This allows for checks like: 211 | // 212 | // if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() { 213 | func (e *timeoutError) Timeout() bool { 214 | return true 215 | } 216 | 217 | var ( 218 | // ErrTimeout is returned from Read() or Write() on timeout. 219 | ErrTimeout = &timeoutError{} 220 | ) 221 | 222 | func (c *pipeConn) Close() error { 223 | return c.pc.Close() 224 | } 225 | 226 | func (c *pipeConn) LocalAddr() net.Addr { 227 | return pipeAddr(0) 228 | } 229 | 230 | func (c *pipeConn) RemoteAddr() net.Addr { 231 | return pipeAddr(0) 232 | } 233 | 234 | func (c *pipeConn) SetDeadline(deadline time.Time) error { 235 | c.SetReadDeadline(deadline) //nolint:errcheck 236 | c.SetWriteDeadline(deadline) //nolint:errcheck 237 | return nil 238 | } 239 | 240 | func (c *pipeConn) SetReadDeadline(deadline time.Time) error { 241 | if c.readDeadlineTimer == nil { 242 | c.readDeadlineTimer = time.NewTimer(time.Hour) 243 | } 244 | readDeadlineCh := updateTimer(c.readDeadlineTimer, deadline) 245 | c.readDeadlineChLock.Lock() 246 | c.readDeadlineCh = readDeadlineCh 247 | c.readDeadlineChLock.Unlock() 248 | return nil 249 | } 250 | 251 | func (c *pipeConn) SetWriteDeadline(deadline time.Time) error { 252 | if c.writeDeadlineTimer == nil { 253 | c.writeDeadlineTimer = time.NewTimer(time.Hour) 254 | } 255 | c.writeDeadlineCh = updateTimer(c.writeDeadlineTimer, deadline) 256 | return nil 257 | } 258 | 259 | func updateTimer(t *time.Timer, deadline time.Time) <-chan time.Time { 260 | if !t.Stop() { 261 | select { 262 | case <-t.C: 263 | default: 264 | } 265 | } 266 | if deadline.IsZero() { 267 | return nil 268 | } 269 | d := -time.Since(deadline) 270 | if d <= 0 { 271 | return closedDeadlineCh 272 | } 273 | t.Reset(d) 274 | return t.C 275 | } 276 | 277 | var closedDeadlineCh = func() <-chan time.Time { 278 | ch := make(chan time.Time) 279 | close(ch) 280 | return ch 281 | }() 282 | 283 | type pipeAddr int 284 | 285 | func (pipeAddr) Network() string { 286 | return "pipe" 287 | } 288 | 289 | func (pipeAddr) String() string { 290 | return "pipe" 291 | } 292 | 293 | type byteBuffer struct { 294 | b []byte 295 | } 296 | 297 | func acquireByteBuffer() *byteBuffer { 298 | return byteBufferPool.Get().(*byteBuffer) 299 | } 300 | 301 | func releaseByteBuffer(b *byteBuffer) { 302 | if b != nil { 303 | byteBufferPool.Put(b) 304 | } 305 | } 306 | 307 | var byteBufferPool = &sync.Pool{ 308 | New: func() interface{} { 309 | return &byteBuffer{ 310 | b: make([]byte, 1024), 311 | } 312 | }, 313 | } 314 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package fasthttp 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | // HTTP status codes were stolen from net/http. 9 | const ( 10 | StatusContinue = 100 // RFC 7231, 6.2.1 11 | StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2 12 | StatusProcessing = 102 // RFC 2518, 10.1 13 | 14 | StatusOK = 200 // RFC 7231, 6.3.1 15 | StatusCreated = 201 // RFC 7231, 6.3.2 16 | StatusAccepted = 202 // RFC 7231, 6.3.3 17 | StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4 18 | StatusNoContent = 204 // RFC 7231, 6.3.5 19 | StatusResetContent = 205 // RFC 7231, 6.3.6 20 | StatusPartialContent = 206 // RFC 7233, 4.1 21 | StatusMultiStatus = 207 // RFC 4918, 11.1 22 | StatusAlreadyReported = 208 // RFC 5842, 7.1 23 | StatusIMUsed = 226 // RFC 3229, 10.4.1 24 | 25 | StatusMultipleChoices = 300 // RFC 7231, 6.4.1 26 | StatusMovedPermanently = 301 // RFC 7231, 6.4.2 27 | StatusFound = 302 // RFC 7231, 6.4.3 28 | StatusSeeOther = 303 // RFC 7231, 6.4.4 29 | StatusNotModified = 304 // RFC 7232, 4.1 30 | StatusUseProxy = 305 // RFC 7231, 6.4.5 31 | _ = 306 // RFC 7231, 6.4.6 (Unused) 32 | StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 33 | StatusPermanentRedirect = 308 // RFC 7538, 3 34 | 35 | StatusBadRequest = 400 // RFC 7231, 6.5.1 36 | StatusUnauthorized = 401 // RFC 7235, 3.1 37 | StatusPaymentRequired = 402 // RFC 7231, 6.5.2 38 | StatusForbidden = 403 // RFC 7231, 6.5.3 39 | StatusNotFound = 404 // RFC 7231, 6.5.4 40 | StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5 41 | StatusNotAcceptable = 406 // RFC 7231, 6.5.6 42 | StatusProxyAuthRequired = 407 // RFC 7235, 3.2 43 | StatusRequestTimeout = 408 // RFC 7231, 6.5.7 44 | StatusConflict = 409 // RFC 7231, 6.5.8 45 | StatusGone = 410 // RFC 7231, 6.5.9 46 | StatusLengthRequired = 411 // RFC 7231, 6.5.10 47 | StatusPreconditionFailed = 412 // RFC 7232, 4.2 48 | StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11 49 | StatusRequestURITooLong = 414 // RFC 7231, 6.5.12 50 | StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13 51 | StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 52 | StatusExpectationFailed = 417 // RFC 7231, 6.5.14 53 | StatusTeapot = 418 // RFC 7168, 2.3.3 54 | StatusUnprocessableEntity = 422 // RFC 4918, 11.2 55 | StatusLocked = 423 // RFC 4918, 11.3 56 | StatusFailedDependency = 424 // RFC 4918, 11.4 57 | StatusUpgradeRequired = 426 // RFC 7231, 6.5.15 58 | StatusPreconditionRequired = 428 // RFC 6585, 3 59 | StatusTooManyRequests = 429 // RFC 6585, 4 60 | StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 61 | StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 62 | 63 | StatusInternalServerError = 500 // RFC 7231, 6.6.1 64 | StatusNotImplemented = 501 // RFC 7231, 6.6.2 65 | StatusBadGateway = 502 // RFC 7231, 6.6.3 66 | StatusServiceUnavailable = 503 // RFC 7231, 6.6.4 67 | StatusGatewayTimeout = 504 // RFC 7231, 6.6.5 68 | StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6 69 | StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 70 | StatusInsufficientStorage = 507 // RFC 4918, 11.5 71 | StatusLoopDetected = 508 // RFC 5842, 7.2 72 | StatusNotExtended = 510 // RFC 2774, 7 73 | StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 74 | ) 75 | 76 | var ( 77 | statusLines atomic.Value 78 | 79 | statusMessages = map[int]string{ 80 | StatusContinue: "Continue", 81 | StatusSwitchingProtocols: "Switching Protocols", 82 | StatusProcessing: "Processing", 83 | 84 | StatusOK: "OK", 85 | StatusCreated: "Created", 86 | StatusAccepted: "Accepted", 87 | StatusNonAuthoritativeInfo: "Non-Authoritative Information", 88 | StatusNoContent: "No Content", 89 | StatusResetContent: "Reset Content", 90 | StatusPartialContent: "Partial Content", 91 | StatusMultiStatus: "Multi-Status", 92 | StatusAlreadyReported: "Already Reported", 93 | StatusIMUsed: "IM Used", 94 | 95 | StatusMultipleChoices: "Multiple Choices", 96 | StatusMovedPermanently: "Moved Permanently", 97 | StatusFound: "Found", 98 | StatusSeeOther: "See Other", 99 | StatusNotModified: "Not Modified", 100 | StatusUseProxy: "Use Proxy", 101 | StatusTemporaryRedirect: "Temporary Redirect", 102 | StatusPermanentRedirect: "Permanent Redirect", 103 | 104 | StatusBadRequest: "Bad Request", 105 | StatusUnauthorized: "Unauthorized", 106 | StatusPaymentRequired: "Payment Required", 107 | StatusForbidden: "Forbidden", 108 | StatusNotFound: "Not Found", 109 | StatusMethodNotAllowed: "Method Not Allowed", 110 | StatusNotAcceptable: "Not Acceptable", 111 | StatusProxyAuthRequired: "Proxy Authentication Required", 112 | StatusRequestTimeout: "Request Timeout", 113 | StatusConflict: "Conflict", 114 | StatusGone: "Gone", 115 | StatusLengthRequired: "Length Required", 116 | StatusPreconditionFailed: "Precondition Failed", 117 | StatusRequestEntityTooLarge: "Request Entity Too Large", 118 | StatusRequestURITooLong: "Request URI Too Long", 119 | StatusUnsupportedMediaType: "Unsupported Media Type", 120 | StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", 121 | StatusExpectationFailed: "Expectation Failed", 122 | StatusTeapot: "I'm a teapot", 123 | StatusUnprocessableEntity: "Unprocessable Entity", 124 | StatusLocked: "Locked", 125 | StatusFailedDependency: "Failed Dependency", 126 | StatusUpgradeRequired: "Upgrade Required", 127 | StatusPreconditionRequired: "Precondition Required", 128 | StatusTooManyRequests: "Too Many Requests", 129 | StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large", 130 | StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons", 131 | 132 | StatusInternalServerError: "Internal Server Error", 133 | StatusNotImplemented: "Not Implemented", 134 | StatusBadGateway: "Bad Gateway", 135 | StatusServiceUnavailable: "Service Unavailable", 136 | StatusGatewayTimeout: "Gateway Timeout", 137 | StatusHTTPVersionNotSupported: "HTTP Version Not Supported", 138 | StatusVariantAlsoNegotiates: "Variant Also Negotiates", 139 | StatusInsufficientStorage: "Insufficient Storage", 140 | StatusLoopDetected: "Loop Detected", 141 | StatusNotExtended: "Not Extended", 142 | StatusNetworkAuthenticationRequired: "Network Authentication Required", 143 | } 144 | ) 145 | 146 | // StatusMessage returns HTTP status message for the given status code. 147 | func StatusMessage(statusCode int) string { 148 | s := statusMessages[statusCode] 149 | if s == "" { 150 | s = "Unknown Status Code" 151 | } 152 | return s 153 | } 154 | 155 | func init() { 156 | statusLines.Store(make(map[int][]byte)) 157 | } 158 | 159 | func statusLine(statusCode int) []byte { 160 | m := statusLines.Load().(map[int][]byte) 161 | h := m[statusCode] 162 | if h != nil { 163 | return h 164 | } 165 | 166 | statusText := StatusMessage(statusCode) 167 | 168 | h = []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", statusCode, statusText)) 169 | newM := make(map[int][]byte, len(m)+1) 170 | for k, v := range m { 171 | newM[k] = v 172 | } 173 | newM[statusCode] = h 174 | statusLines.Store(newM) 175 | return h 176 | } 177 | -------------------------------------------------------------------------------- /fasthttputil/pipeconns_test.go: -------------------------------------------------------------------------------- 1 | package fasthttputil 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestPipeConnsWriteTimeout(t *testing.T) { 14 | pc := NewPipeConns() 15 | c1 := pc.Conn1() 16 | 17 | deadline := time.Now().Add(time.Millisecond) 18 | if err := c1.SetWriteDeadline(deadline); err != nil { 19 | t.Fatalf("unexpected error: %s", err) 20 | } 21 | 22 | data := []byte("foobar") 23 | for { 24 | _, err := c1.Write(data) 25 | if err != nil { 26 | if err == ErrTimeout { 27 | break 28 | } 29 | t.Fatalf("unexpected error: %s", err) 30 | } 31 | } 32 | 33 | for i := 0; i < 10; i++ { 34 | _, err := c1.Write(data) 35 | if err == nil { 36 | t.Fatalf("expecting error") 37 | } 38 | if err != ErrTimeout { 39 | t.Fatalf("unexpected error: %s. Expecting %s", err, ErrTimeout) 40 | } 41 | } 42 | 43 | // read the written data 44 | c2 := pc.Conn2() 45 | if err := c2.SetReadDeadline(time.Now().Add(10 * time.Millisecond)); err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | for { 49 | _, err := c2.Read(data) 50 | if err != nil { 51 | if err == ErrTimeout { 52 | break 53 | } 54 | t.Fatalf("unexpected error: %s", err) 55 | } 56 | } 57 | 58 | for i := 0; i < 10; i++ { 59 | _, err := c2.Read(data) 60 | if err == nil { 61 | t.Fatalf("expecting error") 62 | } 63 | if err != ErrTimeout { 64 | t.Fatalf("unexpected error: %s. Expecting %s", err, ErrTimeout) 65 | } 66 | } 67 | } 68 | 69 | func TestPipeConnsPositiveReadTimeout(t *testing.T) { 70 | testPipeConnsReadTimeout(t, time.Millisecond) 71 | } 72 | 73 | func TestPipeConnsNegativeReadTimeout(t *testing.T) { 74 | testPipeConnsReadTimeout(t, -time.Second) 75 | } 76 | 77 | var zeroTime time.Time 78 | 79 | func testPipeConnsReadTimeout(t *testing.T, timeout time.Duration) { 80 | pc := NewPipeConns() 81 | c1 := pc.Conn1() 82 | 83 | deadline := time.Now().Add(timeout) 84 | if err := c1.SetReadDeadline(deadline); err != nil { 85 | t.Fatalf("unexpected error: %s", err) 86 | } 87 | 88 | var buf [1]byte 89 | for i := 0; i < 10; i++ { 90 | _, err := c1.Read(buf[:]) 91 | if err == nil { 92 | t.Fatalf("expecting error on iteration %d", i) 93 | } 94 | if err != ErrTimeout { 95 | t.Fatalf("unexpected error on iteration %d: %s. Expecting %s", i, err, ErrTimeout) 96 | } 97 | } 98 | 99 | // disable deadline and send data from c2 to c1 100 | if err := c1.SetReadDeadline(zeroTime); err != nil { 101 | t.Fatalf("unexpected error: %s", err) 102 | } 103 | 104 | data := []byte("foobar") 105 | c2 := pc.Conn2() 106 | if _, err := c2.Write(data); err != nil { 107 | t.Fatalf("unexpected error: %s", err) 108 | } 109 | dataBuf := make([]byte, len(data)) 110 | if _, err := io.ReadFull(c1, dataBuf); err != nil { 111 | t.Fatalf("unexpected error: %s", err) 112 | } 113 | if !bytes.Equal(data, dataBuf) { 114 | t.Fatalf("unexpected data received: %q. Expecting %q", dataBuf, data) 115 | } 116 | } 117 | 118 | func TestPipeConnsCloseWhileReadWriteConcurrent(t *testing.T) { 119 | concurrency := 4 120 | ch := make(chan struct{}, concurrency) 121 | for i := 0; i < concurrency; i++ { 122 | go func() { 123 | testPipeConnsCloseWhileReadWriteSerial(t) 124 | ch <- struct{}{} 125 | }() 126 | } 127 | 128 | for i := 0; i < concurrency; i++ { 129 | select { 130 | case <-ch: 131 | case <-time.After(3 * time.Second): 132 | t.Fatalf("timeout") 133 | } 134 | } 135 | } 136 | 137 | func TestPipeConnsCloseWhileReadWriteSerial(t *testing.T) { 138 | testPipeConnsCloseWhileReadWriteSerial(t) 139 | } 140 | 141 | func testPipeConnsCloseWhileReadWriteSerial(t *testing.T) { 142 | for i := 0; i < 10; i++ { 143 | testPipeConnsCloseWhileReadWrite(t) 144 | } 145 | } 146 | 147 | func testPipeConnsCloseWhileReadWrite(t *testing.T) { 148 | pc := NewPipeConns() 149 | c1 := pc.Conn1() 150 | c2 := pc.Conn2() 151 | 152 | readCh := make(chan error) 153 | go func() { 154 | var err error 155 | if _, err = io.Copy(ioutil.Discard, c1); err != nil { 156 | if err != errConnectionClosed { 157 | err = fmt.Errorf("unexpected error: %s", err) 158 | } else { 159 | err = nil 160 | } 161 | } 162 | readCh <- err 163 | }() 164 | 165 | writeCh := make(chan error) 166 | go func() { 167 | var err error 168 | for { 169 | if _, err = c2.Write([]byte("foobar")); err != nil { 170 | if err != errConnectionClosed { 171 | err = fmt.Errorf("unexpected error: %s", err) 172 | } else { 173 | err = nil 174 | } 175 | break 176 | } 177 | } 178 | writeCh <- err 179 | }() 180 | 181 | time.Sleep(10 * time.Millisecond) 182 | if err := c1.Close(); err != nil { 183 | t.Fatalf("unexpected error: %s", err) 184 | } 185 | if err := c2.Close(); err != nil { 186 | t.Fatalf("unexpected error: %s", err) 187 | } 188 | 189 | select { 190 | case err := <-readCh: 191 | if err != nil { 192 | t.Fatalf("unexpected error in reader: %s", err) 193 | } 194 | case <-time.After(time.Second): 195 | t.Fatalf("timeout") 196 | } 197 | select { 198 | case err := <-writeCh: 199 | if err != nil { 200 | t.Fatalf("unexpected error in writer: %s", err) 201 | } 202 | case <-time.After(time.Second): 203 | t.Fatalf("timeout") 204 | } 205 | } 206 | 207 | func TestPipeConnsReadWriteSerial(t *testing.T) { 208 | testPipeConnsReadWriteSerial(t) 209 | } 210 | 211 | func TestPipeConnsReadWriteConcurrent(t *testing.T) { 212 | testConcurrency(t, 10, testPipeConnsReadWriteSerial) 213 | } 214 | 215 | func testPipeConnsReadWriteSerial(t *testing.T) { 216 | pc := NewPipeConns() 217 | testPipeConnsReadWrite(t, pc.Conn1(), pc.Conn2()) 218 | 219 | pc = NewPipeConns() 220 | testPipeConnsReadWrite(t, pc.Conn2(), pc.Conn1()) 221 | } 222 | 223 | func testPipeConnsReadWrite(t *testing.T, c1, c2 net.Conn) { 224 | defer c1.Close() 225 | defer c2.Close() 226 | 227 | var buf [32]byte 228 | for i := 0; i < 10; i++ { 229 | // The first write 230 | s1 := fmt.Sprintf("foo_%d", i) 231 | n, err := c1.Write([]byte(s1)) 232 | if err != nil { 233 | t.Fatalf("unexpected error: %s", err) 234 | } 235 | if n != len(s1) { 236 | t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(s1)) 237 | } 238 | 239 | // The second write 240 | s2 := fmt.Sprintf("bar_%d", i) 241 | n, err = c1.Write([]byte(s2)) 242 | if err != nil { 243 | t.Fatalf("unexpected error: %s", err) 244 | } 245 | if n != len(s2) { 246 | t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(s2)) 247 | } 248 | 249 | // Read data written above in two writes 250 | s := s1 + s2 251 | n, err = c2.Read(buf[:]) 252 | if err != nil { 253 | t.Fatalf("unexpected error: %s", err) 254 | } 255 | if n != len(s) { 256 | t.Fatalf("unexpected number of bytes read: %d. Expecting %d", n, len(s)) 257 | } 258 | if string(buf[:n]) != s { 259 | t.Fatalf("unexpected string read: %q. Expecting %q", buf[:n], s) 260 | } 261 | } 262 | } 263 | 264 | func TestPipeConnsCloseSerial(t *testing.T) { 265 | testPipeConnsCloseSerial(t) 266 | } 267 | 268 | func TestPipeConnsCloseConcurrent(t *testing.T) { 269 | testConcurrency(t, 10, testPipeConnsCloseSerial) 270 | } 271 | 272 | func testPipeConnsCloseSerial(t *testing.T) { 273 | pc := NewPipeConns() 274 | testPipeConnsClose(t, pc.Conn1(), pc.Conn2()) 275 | 276 | pc = NewPipeConns() 277 | testPipeConnsClose(t, pc.Conn2(), pc.Conn1()) 278 | } 279 | 280 | func testPipeConnsClose(t *testing.T, c1, c2 net.Conn) { 281 | if err := c1.Close(); err != nil { 282 | t.Fatalf("unexpected error: %s", err) 283 | } 284 | var buf [10]byte 285 | 286 | // attempt writing to closed conn 287 | for i := 0; i < 10; i++ { 288 | n, err := c1.Write(buf[:]) 289 | if err == nil { 290 | t.Fatalf("expecting error") 291 | } 292 | if n != 0 { 293 | t.Fatalf("unexpected number of bytes written: %d. Expecting 0", n) 294 | } 295 | } 296 | 297 | // attempt reading from closed conn 298 | for i := 0; i < 10; i++ { 299 | n, err := c2.Read(buf[:]) 300 | if err == nil { 301 | t.Fatalf("expecting error") 302 | } 303 | if err != io.EOF { 304 | t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF) 305 | } 306 | if n != 0 { 307 | t.Fatalf("unexpected number of bytes read: %d. Expecting 0", n) 308 | } 309 | } 310 | 311 | if err := c2.Close(); err != nil { 312 | t.Fatalf("unexpected error: %s", err) 313 | } 314 | 315 | // attempt closing already closed conns 316 | for i := 0; i < 10; i++ { 317 | if err := c1.Close(); err != nil { 318 | t.Fatalf("unexpected error: %s", err) 319 | } 320 | if err := c2.Close(); err != nil { 321 | t.Fatalf("unexpected error: %s", err) 322 | } 323 | } 324 | } 325 | 326 | func testConcurrency(t *testing.T, concurrency int, f func(*testing.T)) { 327 | ch := make(chan struct{}, concurrency) 328 | for i := 0; i < concurrency; i++ { 329 | go func() { 330 | f(t) 331 | ch <- struct{}{} 332 | }() 333 | } 334 | 335 | for i := 0; i < concurrency; i++ { 336 | select { 337 | case <-ch: 338 | case <-time.After(time.Second): 339 | t.Fatalf("timeout") 340 | } 341 | } 342 | } 343 | --------------------------------------------------------------------------------