├── 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 |
--------------------------------------------------------------------------------