[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\r\nupdates:\r\n  - package-ecosystem: \"github-actions\"\r\n    directory: \"/\"\r\n    schedule:\r\n      interval: \"daily\"\r\n  - package-ecosystem: \"gomod\"\r\n    directory: \"/\"\r\n    schedule:\r\n      interval: \"daily\""
  },
  {
    "path": ".github/workflows/cifuzz.yml",
    "content": "name: CIFuzz\non: [pull_request]\njobs:\n  Fuzzing:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Build Fuzzers\n      id: build\n      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master\n      with:\n        oss-fuzz-project-name: 'fasthttp'\n        dry-run: false\n        language: go\n    - name: Run Fuzzers\n      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master\n      with:\n        oss-fuzz-project-name: 'fasthttp'\n        fuzz-seconds: 300\n        dry-run: false\n        language: go\n    - name: Upload Crash\n      uses: actions/upload-artifact@v7\n      if: failure() && steps.build.outcome == 'success'\n      with:\n        name: artifacts\n        path: ./out/artifacts\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\npermissions:\n  # Required: allow read access to the content for analysis.\n  contents: read\n  # Optional: allow read access to pull request. Use with `only-new-issues` option.\n  pull-requests: read\n  # Optional: Allow write access to checks to allow the action to annotate code in the PR.\n  checks: write\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-go@v6\n      with:\n        go-version: 1.26.x\n    - run: go version\n    - name: Run golangci-lint\n      uses: golangci/golangci-lint-action@v9.2.0\n      with:\n        version: v2.10.1\n        args: --verbose\n"
  },
  {
    "path": ".github/workflows/security.yml",
    "content": "name: Security\non:\n  push:\n    branches:\n      - master\n  pull_request:\njobs:\n  test:\n    strategy:\n      matrix:\n        go-version: [1.20.x]\n        platform: [ubuntu-latest]\n    runs-on: ${{ matrix.platform }}\n    env:\n      GO111MODULE: on\n    steps:\n      - uses: actions/checkout@v6\n      - name: Run Gosec Security Scanner\n        uses: securego/gosec@v2.25.0\n        with:\n          args: '-exclude=G103,G104,G304,G402 ./...'\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  push:\n    branches:\n      - master\n  pull_request:\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        go-version: [1.24.x, 1.26.x]\n        os: [ubuntu-latest, macos-latest, windows-latest, macos-14]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go-version }}\n\n      - run: go version\n      - run: go test -shuffle=on ./...\n      - run: go test -race -shuffle=on ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "tags\n*.pprof\n*.fasthttp.br\n*.fasthttp.zst\n*.fasthttp.gz\n.idea\n.vscode\n.DS_Store\nvendor/\ntestdata/fuzz\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nlinters:\n  default: all\n  disable:\n    - copyloopvar\n    - cyclop\n    - depguard\n    - dupl\n    - err113\n    - errname\n    - errorlint\n    - exhaustive\n    - exhaustruct\n    - forbidigo\n    - forcetypeassert\n    - funcorder\n    - funlen\n    - gochecknoglobals\n    - gocognit\n    - goconst\n    - gocyclo\n    - godox\n    - gosec\n    - gosmopolitan\n    - inamedparam\n    - intrange\n    - ireturn\n    - maintidx\n    - mnd\n    - nakedret\n    - nestif\n    - nlreturn\n    - noctx\n    - noinlineerr\n    - nonamedreturns\n    - paralleltest\n    - testableexamples\n    - testpackage\n    - thelper\n    - tparallel\n    - unparam\n    - usestdlibvars\n    - varnamelen\n    - wrapcheck\n    - wsl\n    - wsl_v5\n  settings:\n    gocritic:\n      disabled-checks:\n        - deferInLoop\n        - importShadow\n        - sloppyReassign\n        - unnamedResult\n        - whyNoLint\n      enabled-tags:\n        - diagnostic\n        - experimental\n        - opinionated\n        - performance\n        - style\n    govet:\n      disable:\n        - fieldalignment\n        - shadow\n      enable-all: true\n    lll:\n      line-length: 130\n    revive:\n      rules:\n        - name: indent-error-flow\n        - name: use-any\n    staticcheck:\n      checks:\n        - -ST1000\n        - all\n  exclusions:\n    generated: lax\n    presets:\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    rules:\n      - linters:\n          - lll\n        path: _test\\.go\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - gci\n    - gofmt\n    - gofumpt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-present Aliaksandr Valialkin, VertaMedia, Kirill Danshin, Erik Dubbelboer, FastHTTP Authors\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# fasthttp\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/valyala/fasthttp)](https://pkg.go.dev/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)\n\n![FastHTTP – Fastest and reliable HTTP implementation in Go](https://github.com/fasthttp/docs-assets/raw/master/banner@0.5.png)\n\nFast HTTP implementation for Go.\n\n## fasthttp might not be for you!\n\nfasthttp was designed for some high performance edge cases. **Unless** your server/client needs to handle **thousands of small to medium requests per second** and needs a consistent low millisecond response time fasthttp might not be for you. **For most cases `net/http` is much better** as it's easier to use and can handle more cases. For most cases you won't even notice the performance difference.\n\n## General info and links\n\nCurrently fasthttp is successfully used by [VertaMedia](https://vertamedia.com/)\nin a production serving up to 200K rps from more than 1.5M concurrent keep-alive\nconnections per physical server.\n\n[TechEmpower Benchmark round 23 results](https://www.techempower.com/benchmarks/#section=data-r23&hw=ph&test=plaintext)\n\n[Server Benchmarks](#http-server-performance-comparison-with-nethttp)\n\n[Client Benchmarks](#http-client-comparison-with-nethttp)\n\n[Install](#install)\n\n[Documentation](https://pkg.go.dev/github.com/valyala/fasthttp)\n\n[Examples from docs](https://pkg.go.dev/github.com/valyala/fasthttp#pkg-examples)\n\n[Code examples](examples)\n\n[Awesome fasthttp tools](https://github.com/fasthttp)\n\n[Switching from net/http to fasthttp](#switching-from-nethttp-to-fasthttp)\n\n[Fasthttp best practices](#fasthttp-best-practices)\n\n[Related projects](#related-projects)\n\n[FAQ](#faq)\n\n## HTTP server performance comparison with [net/http](https://pkg.go.dev/net/http)\n\nIn short, fasthttp server is up to 6 times faster than net/http.\nBelow are benchmark results.\n\n_GOMAXPROCS=1_\n\nnet/http server:\n\n```\n$ GOMAXPROCS=1 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkNetHTTPServerGet1ReqPerConn                      722565             15327 ns/op            3258 B/op         36 allocs/op\nBenchmarkNetHTTPServerGet2ReqPerConn                      990067             11533 ns/op            2817 B/op         28 allocs/op\nBenchmarkNetHTTPServerGet10ReqPerConn                    1376821              8734 ns/op            2483 B/op         23 allocs/op\nBenchmarkNetHTTPServerGet10KReqPerConn                   1691265              7151 ns/op            2385 B/op         21 allocs/op\nBenchmarkNetHTTPServerGet1ReqPerConn10KClients            643940             17152 ns/op            3529 B/op         36 allocs/op\nBenchmarkNetHTTPServerGet2ReqPerConn10KClients            868576             14010 ns/op            2826 B/op         28 allocs/op\nBenchmarkNetHTTPServerGet10ReqPerConn10KClients          1297398              9329 ns/op            2611 B/op         23 allocs/op\nBenchmarkNetHTTPServerGet100ReqPerConn10KClients         1467963              7902 ns/op            2450 B/op         21 allocs/op\n```\n\nfasthttp server:\n\n```\n$ GOMAXPROCS=1 go test -bench=kServerGet -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkServerGet1ReqPerConn                    4304683              2733 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet2ReqPerConn                    5685157              2140 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet10ReqPerConn                   7659729              1550 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet10KReqPerConn                  8580660              1422 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet1ReqPerConn10KClients          4092148              3009 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet2ReqPerConn10KClients          5272755              2208 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet10ReqPerConn10KClients         7566351              1546 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet100ReqPerConn10KClients        8369295              1418 ns/op               0 B/op          0 allocs/op\n```\n\n_GOMAXPROCS=4_\n\nnet/http server:\n\n```\n$ GOMAXPROCS=4 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkNetHTTPServerGet1ReqPerConn-4                   2670654              4542 ns/op            3263 B/op         36 allocs/op\nBenchmarkNetHTTPServerGet2ReqPerConn-4                   3376021              3559 ns/op            2823 B/op         28 allocs/op\nBenchmarkNetHTTPServerGet10ReqPerConn-4                  4387959              2707 ns/op            2489 B/op         23 allocs/op\nBenchmarkNetHTTPServerGet10KReqPerConn-4                 5412049              2179 ns/op            2386 B/op         21 allocs/op\nBenchmarkNetHTTPServerGet1ReqPerConn10KClients-4         2226048              5216 ns/op            3289 B/op         36 allocs/op\nBenchmarkNetHTTPServerGet2ReqPerConn10KClients-4         2989957              3982 ns/op            2839 B/op         28 allocs/op\nBenchmarkNetHTTPServerGet10ReqPerConn10KClients-4        4383570              2834 ns/op            2514 B/op         23 allocs/op\nBenchmarkNetHTTPServerGet100ReqPerConn10KClients-4       5315100              2394 ns/op            2419 B/op         21 allocs/op\n```\n\nfasthttp server:\n\n```\n$ GOMAXPROCS=4 go test -bench=kServerGet -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkServerGet1ReqPerConn-4                  7797037              1494 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet2ReqPerConn-4                 13004892               963.7 ns/op             0 B/op          0 allocs/op\nBenchmarkServerGet10ReqPerConn-4                22479348               522.6 ns/op             0 B/op          0 allocs/op\nBenchmarkServerGet10KReqPerConn-4               25899390               451.4 ns/op             0 B/op          0 allocs/op\nBenchmarkServerGet1ReqPerConn10KClients-4        8421531              1469 ns/op               0 B/op          0 allocs/op\nBenchmarkServerGet2ReqPerConn10KClients-4       13426772               903.7 ns/op             0 B/op          0 allocs/op\nBenchmarkServerGet10ReqPerConn10KClients-4      21899584               513.5 ns/op             0 B/op          0 allocs/op\nBenchmarkServerGet100ReqPerConn10KClients-4     25291686               439.4 ns/op             0 B/op          0 allocs/op\n```\n\n## HTTP client comparison with net/http\n\nIn short, fasthttp client is up to 4 times faster than net/http.\nBelow are benchmark results.\n\n_GOMAXPROCS=1_\n\nnet/http client:\n\n```\n$ GOMAXPROCS=1 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkNetHTTPClientDoFastServer                        885637             13883 ns/op            3384 B/op         44 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd1TCP                     203875             55619 ns/op            6296 B/op         70 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd10TCP                    231290             54618 ns/op            6299 B/op         70 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd100TCP                   202879             58278 ns/op            6304 B/op         69 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd1Inmemory                396764             26878 ns/op            6216 B/op         69 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd10Inmemory               396422             28373 ns/op            6209 B/op         68 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd100Inmemory              363976             33101 ns/op            6326 B/op         68 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd1000Inmemory             208881             51725 ns/op            8298 B/op         84 allocs/op\nBenchmarkNetHTTPClientGetEndToEndWaitConn1Inmemory           237          50451765 ns/op            7474 B/op         79 allocs/op\nBenchmarkNetHTTPClientGetEndToEndWaitConn10Inmemory          237          50447244 ns/op            7434 B/op         77 allocs/op\nBenchmarkNetHTTPClientGetEndToEndWaitConn100Inmemory         238          50067993 ns/op            8639 B/op         82 allocs/op\nBenchmarkNetHTTPClientGetEndToEndWaitConn1000Inmemory       1366           7324990 ns/op            4064 B/op         44 allocs/op\n```\n\nfasthttp client:\n\n```\n$ GOMAXPROCS=1 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkClientGetEndToEnd1TCP                    406376             26558 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10TCP                   517425             23595 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd100TCP                  474800             25153 ns/op               3 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd1Inmemory              2563800              4827 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10Inmemory             2460135              4805 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd100Inmemory            2520543              4846 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd1000Inmemory           2437015              4914 ns/op               2 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10KInmemory            2481050              5049 ns/op               9 B/op          0 allocs/op\n```\n\n_GOMAXPROCS=4_\n\nnet/http client:\n\n```\n$ GOMAXPROCS=4 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkNetHTTPClientGetEndToEnd1TCP-4                           767133             16175 ns/op            6304 B/op         69 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd10TCP-4                          785198             15276 ns/op            6295 B/op         69 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd100TCP-4                         780464             15605 ns/op            6305 B/op         69 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd1Inmemory-4                     1356932              8772 ns/op            6220 B/op         68 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd10Inmemory-4                    1379245              8726 ns/op            6213 B/op         68 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd100Inmemory-4                   1119213             10294 ns/op            6418 B/op         68 allocs/op\nBenchmarkNetHTTPClientGetEndToEnd1000Inmemory-4                   504194             31010 ns/op           17668 B/op        102 allocs/op\n```\n\nfasthttp client:\n\n```\n$ GOMAXPROCS=4 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s\ncpu: Intel(R) Xeon(R) CPU @ 2.20GHz\nBenchmarkClientGetEndToEnd1TCP-4                         1474552              8143 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10TCP-4                        1710270              7186 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd100TCP-4                       1701672              6892 ns/op               4 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd1Inmemory-4                    6797713              1590 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10Inmemory-4                   6663642              1782 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd100Inmemory-4                  6608209              1867 ns/op               0 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd1000Inmemory-4                 6254452              2645 ns/op               8 B/op          0 allocs/op\nBenchmarkClientGetEndToEnd10KInmemory-4                  6944584              1966 ns/op              17 B/op          0 allocs/op\n```\n\n## Install\n\n```\ngo get -u github.com/valyala/fasthttp\n```\n\n## Switching from net/http to fasthttp\n\nUnfortunately, fasthttp doesn't provide API identical to net/http.\nSee the [FAQ](#faq) for details.\nThere is [net/http -> fasthttp handler converter](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor),\nbut it is better to write fasthttp request handlers by hand in order to use\nall of the fasthttp advantages (especially high performance :) ).\n\nImportant points:\n\n- Fasthttp works with [RequestHandler functions](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)\n  instead of objects implementing [Handler interface](https://pkg.go.dev/net/http#Handler).\n  Fortunately, it is easy to pass bound struct methods to fasthttp:\n\n  ```go\n  type MyHandler struct {\n  \tfoobar string\n  }\n\n  // request handler in net/http style, i.e. method bound to MyHandler struct.\n  func (h *MyHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {\n  \t// notice that we may access MyHandler properties here - see h.foobar.\n  \tfmt.Fprintf(ctx, \"Hello, world! Requested path is %q. Foobar is %q\",\n  \t\tctx.Path(), h.foobar)\n  }\n\n  // request handler in fasthttp style, i.e. just plain function.\n  func fastHTTPHandler(ctx *fasthttp.RequestCtx) {\n  \tfmt.Fprintf(ctx, \"Hi there! RequestURI is %q\", ctx.RequestURI())\n  }\n\n  // pass bound struct method to fasthttp\n  myHandler := &MyHandler{\n  \tfoobar: \"foobar\",\n  }\n  fasthttp.ListenAndServe(\":8080\", myHandler.HandleFastHTTP)\n\n  // pass plain function to fasthttp\n  fasthttp.ListenAndServe(\":8081\", fastHTTPHandler)\n  ```\n\n- The [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)\n  accepts only one argument - [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx).\n  It contains all the functionality required for http request processing\n  and response writing. Below is an example of a simple request handler conversion\n  from net/http to fasthttp.\n\n  ```go\n  // net/http request handler\n  requestHandler := func(w http.ResponseWriter, r *http.Request) {\n  \tswitch r.URL.Path {\n  \tcase \"/foo\":\n  \t\tfooHandler(w, r)\n  \tcase \"/bar\":\n  \t\tbarHandler(w, r)\n  \tdefault:\n  \t\thttp.Error(w, \"Unsupported path\", http.StatusNotFound)\n  \t}\n  }\n  ```\n\n  ```go\n  // the corresponding fasthttp request handler\n  requestHandler := func(ctx *fasthttp.RequestCtx) {\n  \tswitch string(ctx.Path()) {\n  \tcase \"/foo\":\n  \t\tfooHandler(ctx)\n  \tcase \"/bar\":\n  \t\tbarHandler(ctx)\n  \tdefault:\n  \t\tctx.Error(\"Unsupported path\", fasthttp.StatusNotFound)\n  \t}\n  }\n  ```\n\n- Fasthttp allows setting response headers and writing response body\n  in an arbitrary order. There is no 'headers first, then body' restriction\n  like in net/http. The following code is valid for fasthttp:\n\n  ```go\n  requestHandler := func(ctx *fasthttp.RequestCtx) {\n  \t// set some headers and status code first\n  \tctx.SetContentType(\"foo/bar\")\n  \tctx.SetStatusCode(fasthttp.StatusOK)\n\n  \t// then write the first part of body\n  \tfmt.Fprintf(ctx, \"this is the first part of body\\n\")\n\n  \t// then set more headers\n  \tctx.Response.Header.Set(\"Foo-Bar\", \"baz\")\n\n  \t// then write more body\n  \tfmt.Fprintf(ctx, \"this is the second part of body\\n\")\n\n  \t// then override already written body\n  \tctx.SetBody([]byte(\"this is completely new body contents\"))\n\n  \t// then update status code\n  \tctx.SetStatusCode(fasthttp.StatusNotFound)\n\n  \t// basically, anything may be updated many times before\n  \t// returning from RequestHandler.\n  \t//\n  \t// Unlike net/http fasthttp doesn't put response to the wire until\n  \t// returning from RequestHandler.\n  }\n  ```\n\n- Fasthttp doesn't provide [ServeMux](https://pkg.go.dev/net/http#ServeMux),\n  but there are more powerful third-party routers and web frameworks\n  with fasthttp support:\n\n  - [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)\n  - [router](https://github.com/fasthttp/router)\n  - [lu](https://github.com/vincentLiuxiang/lu)\n  - [atreugo](https://github.com/savsgio/atreugo)\n  - [Fiber](https://github.com/gofiber/fiber)\n  - [Gearbox](https://github.com/gogearbox/gearbox)\n\n  Net/http code with simple ServeMux is trivially converted to fasthttp code:\n\n  ```go\n  // net/http code\n\n  m := &http.ServeMux{}\n  m.HandleFunc(\"/foo\", fooHandlerFunc)\n  m.HandleFunc(\"/bar\", barHandlerFunc)\n  m.Handle(\"/baz\", bazHandler)\n\n  http.ListenAndServe(\":80\", m)\n  ```\n\n  ```go\n  // the corresponding fasthttp code\n  m := func(ctx *fasthttp.RequestCtx) {\n  \tswitch string(ctx.Path()) {\n  \tcase \"/foo\":\n  \t\tfooHandlerFunc(ctx)\n  \tcase \"/bar\":\n  \t\tbarHandlerFunc(ctx)\n  \tcase \"/baz\":\n  \t\tbazHandler.HandlerFunc(ctx)\n  \tdefault:\n  \t\tctx.Error(\"not found\", fasthttp.StatusNotFound)\n  \t}\n  }\n\n  fasthttp.ListenAndServe(\":80\", m)\n  ```\n\n- Because creating a new channel for every request is just too expensive, so the channel returned by RequestCtx.Done() is only closed when the server is shutting down.\n\n  ```go\n  func main() {\n  fasthttp.ListenAndServe(\":8080\", fasthttp.TimeoutHandler(func(ctx *fasthttp.RequestCtx) {\n  \tselect {\n  \tcase <-ctx.Done():\n  \t\t// ctx.Done() is only closed when the server is shutting down.\n  \t\tlog.Println(\"context cancelled\")\n  \t\treturn\n  \tcase <-time.After(10 * time.Second):\n  \t\tlog.Println(\"process finished ok\")\n  \t}\n  }, time.Second*2, \"timeout\"))\n  }\n  ```\n\n- net/http -> fasthttp conversion table:\n\n  - All the pseudocode below assumes w, r and ctx have these types:\n\n  ```go\n  var (\n  \tw http.ResponseWriter\n  \tr *http.Request\n  \tctx *fasthttp.RequestCtx\n  )\n  ```\n\n  - [r.Body](https://pkg.go.dev/net/http#Request) **➜** [ctx.PostBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody)\n  - [r.URL.Path](https://pkg.go.dev/net/url#URL) **➜** [ctx.Path()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Path)\n  - [r.URL](https://pkg.go.dev/net/http#Request) **➜** [ctx.URI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.URI)\n  - [r.Method](https://pkg.go.dev/net/http#Request) **➜** [ctx.Method()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Method)\n  - [r.Header](https://pkg.go.dev/net/http#Request) **➜** [ctx.Request.Header](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader)\n  - [r.Header.Get()](https://pkg.go.dev/net/http#Header.Get) **➜** [ctx.Request.Header.Peek()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Peek)\n  - [r.Host](https://pkg.go.dev/net/http#Request) **➜** [ctx.Host()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Host)\n  - [r.Form](https://pkg.go.dev/net/http#Request) **➜** [ctx.QueryArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +\n    [ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)\n  - [r.PostForm](https://pkg.go.dev/net/http#Request) **➜** [ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)\n  - [r.FormValue()](https://pkg.go.dev/net/http#Request.FormValue) **➜** [ctx.FormValue()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormValue)\n  - [r.FormFile()](https://pkg.go.dev/net/http#Request.FormFile) **➜** [ctx.FormFile()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormFile)\n  - [r.MultipartForm](https://pkg.go.dev/net/http#Request) **➜** [ctx.MultipartForm()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartForm)\n    For untrusted multipart input, use [ctx.MultipartFormWithLimit()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartFormWithLimit) (or a custom [Server.FormValueFunc](https://pkg.go.dev/github.com/valyala/fasthttp#Server)) to enforce a parsing size limit.\n  - [r.RemoteAddr](https://pkg.go.dev/net/http#Request) **➜** [ctx.RemoteAddr()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)\n  - [r.RequestURI](https://pkg.go.dev/net/http#Request) **➜** [ctx.RequestURI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RequestURI)\n  - [r.TLS](https://pkg.go.dev/net/http#Request) **➜** [ctx.IsTLS()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.IsTLS)\n  - [r.Cookie()](https://pkg.go.dev/net/http#Request.Cookie) **➜** [ctx.Request.Header.Cookie()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Cookie)\n  - [r.Referer()](https://pkg.go.dev/net/http#Request.Referer) **➜** [ctx.Referer()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Referer)\n  - [r.UserAgent()](https://pkg.go.dev/net/http#Request.UserAgent) **➜** [ctx.UserAgent()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.UserAgent)\n  - [w.Header()](https://pkg.go.dev/net/http#ResponseWriter) **➜** [ctx.Response.Header](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader)\n  - [w.Header().Set()](https://pkg.go.dev/net/http#Header.Set) **➜** [ctx.Response.Header.Set()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.Set)\n  - [w.Header().Set(\"Content-Type\")](https://pkg.go.dev/net/http#Header.Set) **➜** [ctx.SetContentType()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetContentType)\n  - [w.Header().Set(\"Set-Cookie\")](https://pkg.go.dev/net/http#Header.Set) **➜** [ctx.Response.Header.SetCookie()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.SetCookie)\n  - [w.Write()](https://pkg.go.dev/net/http#ResponseWriter) **➜** [ctx.Write()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Write),\n    [ctx.SetBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBody),\n    [ctx.SetBodyStream()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),\n    [ctx.SetBodyStreamWriter()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)\n  - [w.WriteHeader()](https://pkg.go.dev/net/http#ResponseWriter) **➜** [ctx.SetStatusCode()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)\n  - [w.(http.Hijacker).Hijack()](https://pkg.go.dev/net/http#Hijacker) **➜** [ctx.Hijack()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)\n  - [http.Error()](https://pkg.go.dev/net/http#Error) **➜** [ctx.Error()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Error)\n  - [http.FileServer()](https://pkg.go.dev/net/http#FileServer) **➜** [fasthttp.FSHandler()](https://pkg.go.dev/github.com/valyala/fasthttp#FSHandler),\n    [fasthttp.FS](https://pkg.go.dev/github.com/valyala/fasthttp#FS)\n  - [http.ServeFile()](https://pkg.go.dev/net/http#ServeFile) **➜** [fasthttp.ServeFile()](https://pkg.go.dev/github.com/valyala/fasthttp#ServeFile)\n  - [http.Redirect()](https://pkg.go.dev/net/http#Redirect) **➜** [ctx.Redirect()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Redirect)\n  - [http.NotFound()](https://pkg.go.dev/net/http#NotFound) **➜** [ctx.NotFound()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.NotFound)\n  - [http.StripPrefix()](https://pkg.go.dev/net/http#StripPrefix) **➜** [fasthttp.PathRewriteFunc](https://pkg.go.dev/github.com/valyala/fasthttp#PathRewriteFunc)\n\n- _VERY IMPORTANT!_ Fasthttp disallows holding references\n  to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) or to its'\n  members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).\n  Otherwise [data races](http://go.dev/blog/race-detector) are inevitable.\n  Carefully inspect all the net/http request handlers converted to fasthttp whether\n  they retain references to RequestCtx or to its' members after returning.\n  RequestCtx provides the following _band aids_ for this case:\n\n  - Wrap RequestHandler into [TimeoutHandler](https://pkg.go.dev/github.com/valyala/fasthttp#TimeoutHandler).\n  - Call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)\n    before returning from RequestHandler if there are references to RequestCtx or to its' members.\n    See [the example](https://pkg.go.dev/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError)\n    for more details.\n\nUse this brilliant tool - [race detector](http://go.dev/blog/race-detector) -\nfor detecting and eliminating data races in your program. If you detected\ndata race related to fasthttp in your program, then there is high probability\nyou forgot calling [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)\nbefore returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).\n\n- Blind switching from net/http to fasthttp won't give you performance boost.\n  While fasthttp is optimized for speed, its' performance may be easily saturated\n  by slow [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).\n  So [profile](http://go.dev/blog/pprof) and optimize your\n  code after switching to fasthttp. For instance, use [quicktemplate](https://github.com/valyala/quicktemplate)\n  instead of [html/template](https://pkg.go.dev/html/template).\n\n- See also [fasthttputil](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttputil),\n  [fasthttpadaptor](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor) and\n  [expvarhandler](https://pkg.go.dev/github.com/valyala/fasthttp/expvarhandler).\n\n## Performance optimization tips for multi-core systems\n\n- Use [reuseport](https://pkg.go.dev/github.com/valyala/fasthttp/reuseport) listener.\n- Run a separate server instance per CPU core with GOMAXPROCS=1.\n- Pin each server instance to a separate CPU core using [taskset](http://linux.die.net/man/1/taskset).\n- Ensure the interrupts of multiqueue network card are evenly distributed between CPU cores.\n  See [this article](https://blog.cloudflare.com/how-to-achieve-low-latency/) for details.\n- Use the latest version of Go as each version contains performance improvements.\n\n## Fasthttp best practices\n\n- Do not allocate objects and `[]byte` buffers - just reuse them as much\n  as possible. Fasthttp API design encourages this.\n- [sync.Pool](https://pkg.go.dev/sync#Pool) is your best friend.\n- [Profile your program](http://go.dev/blog/pprof)\n  in production.\n  `go tool pprof --alloc_objects your-program mem.pprof` usually gives better\n  insights for optimization opportunities than `go tool pprof your-program cpu.pprof`.\n- Write [tests and benchmarks](https://pkg.go.dev/testing) for hot paths.\n- Avoid conversion between `[]byte` and `string`, since this may result in memory\n  allocation+copy - see [this wiki page](https://github.com/golang/go/wiki/CompilerOptimizations#string-and-byte)\n  for more details.\n- Verify your tests and production code under\n  [race detector](https://go.dev/doc/articles/race_detector.html) on a regular basis.\n- Prefer [quicktemplate](https://github.com/valyala/quicktemplate) instead of\n  [html/template](https://pkg.go.dev/html/template) in your webserver.\n\n## Unsafe Zero-Allocation Conversions\n\nIn performance-critical code, converting between `[]byte` and `string` using standard Go allocations can be inefficient. To address this, `fasthttp` uses **unsafe**, zero-allocation helpers:\n\n> ⚠️ **Warning:** These conversions break Go's type safety. Use only when you're certain the converted value will not be mutated, as violating immutability can cause undefined behavior.\n\n### `UnsafeString(b []byte) string`\n\nConverts a `[]byte` to a `string` **without memory allocation**.\n\n```go\n// UnsafeString returns a string pointer without allocation\nfunc UnsafeString(b []byte) string {\n    // #nosec G103\n    return *(*string)(unsafe.Pointer(&b))\n}\n```\n\n### `UnsafeBytes(s string) []byte`\n\nConverts a `string` to a `[]byte` **without memory allocation**.\n\n```go\n// UnsafeBytes returns a byte pointer without allocation.\nfunc UnsafeBytes(s string) []byte {\n    // #nosec G103\n    return unsafe.Slice(unsafe.StringData(s), len(s))\n}\n```\n\n### Use Cases & Caveats\n\n- These functions are ideal for performance-sensitive scenarios where allocations must be avoided (e.g., request/response processing loops).\n- **Do not** mutate the `[]byte` returned from `UnsafeBytes(s string)` if the original string is still in use, as strings are immutable in Go and may be shared across the runtime.\n- Use samples guarded with `#nosec G103` comments to suppress static analysis warnings about unsafe operations.\n\n## Tricks with `[]byte` buffers\n\nThe following tricks are used by fasthttp. Use them in your code too.\n\n- Standard Go functions accept nil buffers\n\n```go\nvar (\n\t// both buffers are uninitialized\n\tdst []byte\n\tsrc []byte\n)\ndst = append(dst, src...)  // is legal if dst is nil and/or src is nil\ncopy(dst, src)  // is legal if dst is nil and/or src is nil\n(string(src) == \"\")  // is true if src is nil\n(len(src) == 0)  // is true if src is nil\nsrc = src[:0]  // works like a charm with nil src\n\n// this for loop doesn't panic if src is nil\nfor i, ch := range src {\n\tdoSomething(i, ch)\n}\n```\n\nSo throw away nil checks for `[]byte` buffers from you code. For example,\n\n```go\nsrcLen := 0\nif src != nil {\n\tsrcLen = len(src)\n}\n```\n\nbecomes\n\n```go\nsrcLen := len(src)\n```\n\n- String may be appended to `[]byte` buffer with `append`\n\n```go\ndst = append(dst, \"foobar\"...)\n```\n\n- `[]byte` buffer may be extended to its' capacity.\n\n```go\nbuf := make([]byte, 100)\na := buf[:10]  // len(a) == 10, cap(a) == 100.\nb := a[:100]  // is valid, since cap(a) == 100.\n```\n\n- All fasthttp functions accept nil `[]byte` buffer\n\n```go\nstatusCode, body, err := fasthttp.Get(nil, \"http://google.com/\")\nuintBuf := fasthttp.AppendUint(nil, 1234)\n```\n\n- String and `[]byte` buffers may converted without memory allocations\n\n```go\nfunc b2s(b []byte) string {\n    return *(*string)(unsafe.Pointer(&b))\n}\n\nfunc s2b(s string) (b []byte) {\n    bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))\n    sh := (*reflect.StringHeader)(unsafe.Pointer(&s))\n    bh.Data = sh.Data\n    bh.Cap = sh.Len\n    bh.Len = sh.Len\n    return b\n}\n```\n\n### Warning:\n\nThis is an **unsafe** way, the result string and `[]byte` buffer share the same bytes.\n\n**Please make sure not to modify the bytes in the `[]byte` buffer if the string still survives!**\n\n## Related projects\n\n- [fasthttp](https://github.com/fasthttp) - various useful\n  helpers for projects based on fasthttp.\n- [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing) - fast and\n  powerful routing package for fasthttp servers.\n- [http2](https://github.com/dgrr/http2) - HTTP/2 implementation for fasthttp.\n- [router](https://github.com/fasthttp/router) - a high\n  performance fasthttp request router that scales well.\n- [fasthttp-auth](https://github.com/casbin/fasthttp-auth) - Authorization middleware for fasthttp using Casbin.\n- [fastws](https://github.com/fasthttp/fastws) - Bloatless WebSocket package made for fasthttp\n  to handle Read/Write operations concurrently.\n- [gramework](https://github.com/gramework/gramework) - a web framework made by one of fasthttp maintainers.\n- [lu](https://github.com/vincentLiuxiang/lu) - a high performance\n  go middleware web framework which is based on fasthttp.\n- [websocket](https://github.com/fasthttp/websocket) - Gorilla-based\n  websocket implementation for fasthttp.\n- [websocket](https://github.com/dgrr/websocket) - Event-based high-performance WebSocket library for zero-allocation\n  websocket servers and clients.\n- [fasthttpsession](https://github.com/phachon/fasthttpsession) - a fast and powerful session package for fasthttp servers.\n- [atreugo](https://github.com/savsgio/atreugo) - High performance and extensible micro web framework with zero memory allocations in hot paths.\n- [kratgo](https://github.com/savsgio/kratgo) - Simple, lightweight and ultra-fast HTTP Cache to speed up your websites.\n- [kit-plugins](https://github.com/wencan/kit-plugins/tree/master/transport/fasthttp) - go-kit transport implementation for fasthttp.\n- [Fiber](https://github.com/gofiber/fiber) - An Expressjs inspired web framework running on Fasthttp.\n- [Gearbox](https://github.com/gogearbox/gearbox) - :gear: gearbox is a web framework written in Go with a focus on high performance and memory optimization.\n- [http2curl](https://github.com/li-jin-gou/http2curl) - A tool to convert fasthttp requests to curl command line.\n- [OpenTelemetry Golang Compile Time Instrumentation](https://github.com/alibaba/opentelemetry-go-auto-instrumentation) - A tool to monitor fasthttp application without changing any code with OpenTelemetry APIs.\n\n## FAQ\n\n- _Why creating yet another http package instead of optimizing net/http?_\n\n  Because net/http API limits many optimization opportunities.\n  For example:\n\n  - net/http Request object lifetime isn't limited by request handler execution\n    time. So the server must create a new request object per each request instead\n    of reusing existing objects like fasthttp does.\n  - net/http headers are stored in a `map[string][]string`. So the server\n    must parse all the headers, convert them from `[]byte` to `string` and put\n    them into the map before calling user-provided request handler.\n    This all requires unnecessary memory allocations avoided by fasthttp.\n  - net/http client API requires creating a new response object per each request.\n\n- _Why fasthttp API is incompatible with net/http?_\n\n  Because net/http API limits many optimization opportunities. See the answer\n  above for more details. Also certain net/http API parts are suboptimal\n  for use:\n\n  - Compare [net/http connection hijacking](https://pkg.go.dev/net/http#Hijacker)\n    to [fasthttp connection hijacking](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack).\n  - Compare [net/http Request.Body reading](https://pkg.go.dev/net/http#Request)\n    to [fasthttp request body reading](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody).\n\n- _Why fasthttp doesn't support HTTP/2.0 and WebSockets?_\n\n  [HTTP/2.0 support](https://github.com/fasthttp/http2) is in progress. [WebSockets](https://github.com/fasthttp/websockets) has been done already.\n  Third parties also may use [RequestCtx.Hijack](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)\n  for implementing these goodies.\n\n- _Are there known net/http advantages comparing to fasthttp?_\n\n  Yes:\n\n  - net/http supports [HTTP/2.0 starting from go1.6](https://pkg.go.dev/golang.org/x/net/http2).\n  - net/http API is stable, while fasthttp API constantly evolves.\n  - net/http handles more HTTP corner cases.\n  - net/http can stream both request and response bodies\n  - net/http can handle bigger bodies as it doesn't read the whole body into memory\n  - net/http should contain less bugs, since it is used and tested by much\n    wider audience.\n\n- _Why fasthttp API prefers returning `[]byte` instead of `string`?_\n\n  Because `[]byte` to `string` conversion isn't free - it requires memory\n  allocation and copy. Feel free wrapping returned `[]byte` result into\n  `string()` if you prefer working with strings instead of byte slices.\n  But be aware that this has non-zero overhead.\n\n- _Which GO versions are supported by fasthttp?_\n\n  We support the same versions the Go team supports.\n  Currently that is Go 1.24.x and newer.\n  Older versions might work, but won't officially be supported.\n\n- _Please provide real benchmark data and server information_\n\n  See [this issue](https://github.com/valyala/fasthttp/issues/4).\n\n- _Are there plans to add request routing to fasthttp?_\n\n  There are no plans to add request routing into fasthttp.\n  Use third-party routers and web frameworks with fasthttp support:\n\n  - [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)\n  - [router](https://github.com/fasthttp/router)\n  - [gramework](https://github.com/gramework/gramework)\n  - [lu](https://github.com/vincentLiuxiang/lu)\n  - [atreugo](https://github.com/savsgio/atreugo)\n  - [Fiber](https://github.com/gofiber/fiber)\n  - [Gearbox](https://github.com/gogearbox/gearbox)\n\n- _I detected data race in fasthttp!_\n\n  Cool! [File a bug](https://github.com/valyala/fasthttp/issues/new). But before\n  doing this check the following in your code:\n\n  - Make sure there are no references to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx)\n    or to its' members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).\n  - Make sure you call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)\n    before returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)\n    if there are references to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx)\n    or to its' members, which may be accessed by other goroutines.\n\n- _I didn't find an answer for my question here_\n\n  Try exploring [these questions](https://github.com/valyala/fasthttp/issues?q=label%3Aquestion).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "### TL;DR\n\nWe use a simplified version of [Golang Security Policy](https://go.dev/security).\nFor example, for now we skip CVE assignment.\n\n### Reporting a Security Bug\n\nPlease report to us any issues you find. This document explains how to do that and what to expect in return.\n\nAll security bugs in our releases should be reported by email to erik@dubbelboer.com\nYour email will be acknowledged within 24 hours, and you'll receive a more detailed response\nto your email within 72 hours indicating the next steps in handling your report.\nPlease use a descriptive subject line for your report email.\n\n### Flagging Existing Issues as Security-related\n\nIf you believe that an existing issue is security-related, we ask that you send an email to erik@dubbelboer.com\nThe email should include the issue ID and a short description of why it should be handled according to this security policy.\n\n### Disclosure Process\n\nOur project uses the following disclosure process:\n\n- Once the security report is received it is assigned a primary handler. This person coordinates the fix and release process.\n- The issue is confirmed and a list of affected software is determined.\n- Code is audited to find any potential similar problems.\n- 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.\n- To notify users, a new issue without security details is submitted to our GitHub repository.\n- Three working days following this notification, the fixes are applied to the public repository and a new release is issued.\n- On the date that the fixes are applied, announcement is published in the issue.\n\nThis process can take some time, especially when coordination is required with maintainers of other projects.\nEvery effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow\nthe process described above to ensure that disclosures are handled consistently.\n\n### Receiving Security Updates\nThe best way to receive security announcements is to subscribe (\"Watch\") to our repository.\nAny GitHub issues pertaining to a security issue will be prefixed with [security].\n\n### Comments on This Policy\nIf you have any suggestions to improve this policy, please send an email to erik@dubbelboer.com for discussion.\n"
  },
  {
    "path": "TODO",
    "content": "- SessionClient with referer and cookies support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n"
  },
  {
    "path": "allocation_test.go",
    "content": "//go:build !race\n\npackage fasthttp\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestAllocationServeConn(t *testing.T) {\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.SetStatusCode(StatusOK)\n\t\t\tctx.SetBodyString(\"Hello, World!\")\n\t\t\tctx.Response.Header.Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\t// Make space for the request and response here so it\n\t// doesn't allocate within the test.\n\trw.r.Grow(1024)\n\trw.w.Grow(1024)\n\n\tn := testing.AllocsPerRun(100, func() {\n\t\trw.r.WriteString(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\nCookie: foo=bar\\r\\n\\r\\n\")\n\t\tif err := s.ServeConn(rw); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Reset the write buffer to make space for the next response.\n\t\trw.w.Reset()\n\t})\n\n\tif n != 0 {\n\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t}\n}\n\nfunc TestAllocationClient(t *testing.T) {\n\tln, err := net.Listen(\"tcp4\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatalf(\"cannot listen: %v\", err)\n\t}\n\tdefer ln.Close()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.SetStatusCode(StatusOK)\n\t\t\tctx.SetBodyString(\"Hello, World!\")\n\t\t\tctx.Response.Header.Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tc := &Client{}\n\turl := \"http://test:test@\" + ln.Addr().String() + \"/foo?bar=baz\"\n\n\tn := testing.AllocsPerRun(100, func() {\n\t\treq := AcquireRequest()\n\t\tres := AcquireResponse()\n\n\t\treq.SetRequestURI(url)\n\t\treq.Header.Add(\"Foo\", \"bar\")\n\t\tif err := c.Do(req, res); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t})\n\n\tif n != 0 {\n\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t}\n}\n\nfunc TestAllocationURI(t *testing.T) {\n\turi := []byte(\"http://username:password@hello.%e4%b8%96%e7%95%8c.com/some/path?foo=bar#test\")\n\n\tn := testing.AllocsPerRun(100, func() {\n\t\tu := AcquireURI()\n\t\tu.Parse(nil, uri) //nolint:errcheck\n\t\tReleaseURI(u)\n\t})\n\n\tif n != 0 {\n\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t}\n}\n\nfunc TestAllocationFS(t *testing.T) {\n\t// Create a simple test filesystem handler\n\tfs := &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: false,\n\t\tCompress:           false,\n\t\tAcceptByteRange:    false,\n\t}\n\th := fs.NewRequestHandler()\n\n\tctx := &RequestCtx{}\n\n\tn := testing.AllocsPerRun(100, func() {\n\t\tctx.Request.Reset()\n\t\tctx.Response.Reset()\n\t\tctx.Request.SetRequestURI(\"/allocation_test.go\")\n\t\tctx.Request.Header.Set(\"Host\", \"localhost\")\n\n\t\th(ctx)\n\t})\n\n\tt.Logf(\"FS operations allocate %f times per request\", n)\n\n\tif n != 0 {\n\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t}\n}\n\nfunc TestAllocationsHeaderScanner(t *testing.T) {\n\tbody := []byte(\"Host: a.com\\r\\nCookie: foo=bar\\r\\nWithTabs: \\t v1 \\t\\r\\nWithTabs-Start: \\t \\t v1 \\r\\nWithTabs-End: v1 \\t \\t\\t\\t\\r\\nWithTabs-Multi-Line: \\t v1 \\t;\\r\\n \\t v2 \\t;\\r\\n\\t v3\\r\\n\\r\\n\")\n\n\tn := testing.AllocsPerRun(100, func() {\n\t\tvar s headerScanner\n\t\ts.b = body\n\n\t\tfor s.next() {\n\t\t}\n\n\t\tif s.err != nil {\n\t\t\tt.Fatal(s.err)\n\t\t}\n\t})\n\n\tif n != 0 {\n\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t}\n}\n"
  },
  {
    "path": "args.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"iter\"\n\t\"sort\"\n\t\"sync\"\n)\n\nconst (\n\targsNoValue  = true\n\targsHasValue = false\n)\n\n// AcquireArgs returns an empty Args object from the pool.\n//\n// The returned Args may be returned to the pool with ReleaseArgs\n// when no longer needed. This allows reducing GC load.\nfunc AcquireArgs() *Args {\n\treturn argsPool.Get().(*Args)\n}\n\n// ReleaseArgs returns the object acquired via AcquireArgs to the pool.\n//\n// Do not access the released Args object, otherwise data races may occur.\nfunc ReleaseArgs(a *Args) {\n\ta.Reset()\n\targsPool.Put(a)\n}\n\nvar argsPool = &sync.Pool{\n\tNew: func() any {\n\t\treturn &Args{}\n\t},\n}\n\n// Args represents query arguments.\n//\n// It is forbidden copying Args instances. Create new instances instead\n// and use CopyTo().\n//\n// Args instance MUST NOT be used from concurrently running goroutines.\ntype Args struct {\n\tnoCopy noCopy\n\n\targs []argsKV\n\tbuf  []byte\n}\n\ntype argsKV struct {\n\tkey     []byte\n\tvalue   []byte\n\tnoValue bool\n}\n\n// Reset clears query args.\nfunc (a *Args) Reset() {\n\ta.args = a.args[:0]\n}\n\n// CopyTo copies all args to dst.\nfunc (a *Args) CopyTo(dst *Args) {\n\tdst.args = copyArgs(dst.args, a.args)\n}\n\n// All returns an iterator over key-value pairs from args.\n//\n// The key and value may invalid outside the iteration loop.\n// Make copies if you need to use them after the loop ends.\n//\n// Making modifications to the Args during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (a *Args) All() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\tfor i := range a.args {\n\t\t\tif !yield(a.args[i].key, a.args[i].value) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VisitAll calls f for each existing arg.\n//\n// f must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\n//\n// Deprecated: Use All instead.\nfunc (a *Args) VisitAll(f func(key, value []byte)) {\n\ta.All()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// Len returns the number of query args.\nfunc (a *Args) Len() int {\n\treturn len(a.args)\n}\n\n// Parse parses the given string containing query args.\nfunc (a *Args) Parse(s string) {\n\ta.buf = append(a.buf[:0], s...)\n\ta.ParseBytes(a.buf)\n}\n\n// ParseBytes parses the given b containing query args.\nfunc (a *Args) ParseBytes(b []byte) {\n\ta.Reset()\n\n\tvar s argsScanner\n\ts.b = b\n\n\tvar kv *argsKV\n\ta.args, kv = allocArg(a.args)\n\tfor s.next(kv) {\n\t\tif len(kv.key) > 0 || len(kv.value) > 0 {\n\t\t\ta.args, kv = allocArg(a.args)\n\t\t}\n\t}\n\ta.args = releaseArg(a.args)\n}\n\n// String returns string representation of query args.\nfunc (a *Args) String() string {\n\treturn string(a.QueryString())\n}\n\n// QueryString returns query string for the args.\n//\n// The returned value is valid until the Args is reused or released (ReleaseArgs).\n// Do not store references to the returned value. Make copies instead.\nfunc (a *Args) QueryString() []byte {\n\ta.buf = a.AppendBytes(a.buf[:0])\n\treturn a.buf\n}\n\n// Sort sorts Args by key and then value using 'f' as comparison function.\n//\n// For example args.Sort(bytes.Compare).\nfunc (a *Args) Sort(f func(x, y []byte) int) {\n\tsort.SliceStable(a.args, func(i, j int) bool {\n\t\tn := f(a.args[i].key, a.args[j].key)\n\t\tif n == 0 {\n\t\t\treturn f(a.args[i].value, a.args[j].value) == -1\n\t\t}\n\t\treturn n == -1\n\t})\n}\n\n// SortKeys sorts Args by key only using 'f' as comparison function.\n//\n// For example args.SortKeys(bytes.Compare).\nfunc (a *Args) SortKeys(f func(x, y []byte) int) {\n\tsort.SliceStable(a.args, func(i, j int) bool {\n\t\treturn f(a.args[i].key, a.args[j].key) == -1\n\t})\n}\n\n// AppendBytes appends query string to dst and returns the extended dst.\nfunc (a *Args) AppendBytes(dst []byte) []byte {\n\tfor i, n := 0, len(a.args); i < n; i++ {\n\t\tkv := &a.args[i]\n\t\tdst = AppendQuotedArg(dst, kv.key)\n\t\tif !kv.noValue {\n\t\t\tdst = append(dst, '=')\n\t\t\tif len(kv.value) > 0 {\n\t\t\t\tdst = AppendQuotedArg(dst, kv.value)\n\t\t\t}\n\t\t}\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, '&')\n\t\t}\n\t}\n\treturn dst\n}\n\n// WriteTo writes query string to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (a *Args) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(a.QueryString())\n\treturn int64(n), err\n}\n\n// Del deletes argument with the given key from query args.\nfunc (a *Args) Del(key string) {\n\ta.args = delAllArgsStable(a.args, key)\n}\n\n// DelBytes deletes argument with the given key from query args.\nfunc (a *Args) DelBytes(key []byte) {\n\ta.args = delAllArgsStable(a.args, b2s(key))\n}\n\n// Add adds 'key=value' argument.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) Add(key, value string) {\n\ta.args = appendArg(a.args, key, value, argsHasValue)\n}\n\n// AddBytesK adds 'key=value' argument.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) AddBytesK(key []byte, value string) {\n\ta.args = appendArg(a.args, b2s(key), value, argsHasValue)\n}\n\n// AddBytesV adds 'key=value' argument.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) AddBytesV(key string, value []byte) {\n\ta.args = appendArg(a.args, key, b2s(value), argsHasValue)\n}\n\n// AddBytesKV adds 'key=value' argument.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) AddBytesKV(key, value []byte) {\n\ta.args = appendArg(a.args, b2s(key), b2s(value), argsHasValue)\n}\n\n// AddNoValue adds only 'key' as argument without the '='.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) AddNoValue(key string) {\n\ta.args = appendArg(a.args, key, \"\", argsNoValue)\n}\n\n// AddBytesKNoValue adds only 'key' as argument without the '='.\n//\n// Multiple values for the same key may be added.\nfunc (a *Args) AddBytesKNoValue(key []byte) {\n\ta.args = appendArg(a.args, b2s(key), \"\", argsNoValue)\n}\n\n// Set sets 'key=value' argument.\nfunc (a *Args) Set(key, value string) {\n\ta.args = setArg(a.args, key, value, argsHasValue)\n}\n\n// SetBytesK sets 'key=value' argument.\nfunc (a *Args) SetBytesK(key []byte, value string) {\n\ta.args = setArg(a.args, b2s(key), value, argsHasValue)\n}\n\n// SetBytesV sets 'key=value' argument.\nfunc (a *Args) SetBytesV(key string, value []byte) {\n\ta.args = setArg(a.args, key, b2s(value), argsHasValue)\n}\n\n// SetBytesKV sets 'key=value' argument.\nfunc (a *Args) SetBytesKV(key, value []byte) {\n\ta.args = setArgBytes(a.args, key, value, argsHasValue)\n}\n\n// SetNoValue sets only 'key' as argument without the '='.\n//\n// Only key in argument, like key1&key2.\nfunc (a *Args) SetNoValue(key string) {\n\ta.args = setArg(a.args, key, \"\", argsNoValue)\n}\n\n// SetBytesKNoValue sets 'key' argument.\nfunc (a *Args) SetBytesKNoValue(key []byte) {\n\ta.args = setArg(a.args, b2s(key), \"\", argsNoValue)\n}\n\n// Peek returns query arg value for the given key.\n//\n// The returned value is valid until the Args is reused or released (ReleaseArgs).\n// Do not store references to the returned value. Make copies instead.\nfunc (a *Args) Peek(key string) []byte {\n\treturn peekArgStr(a.args, key)\n}\n\n// PeekBytes returns query arg value for the given key.\n//\n// The returned value is valid until the Args is reused or released (ReleaseArgs).\n// Do not store references to the returned value. Make copies instead.\nfunc (a *Args) PeekBytes(key []byte) []byte {\n\treturn peekArgBytes(a.args, key)\n}\n\n// PeekMulti returns all the arg values for the given key.\nfunc (a *Args) PeekMulti(key string) [][]byte {\n\tvar values [][]byte\n\tfor k, v := range a.All() {\n\t\tif string(k) == key {\n\t\t\tvalues = append(values, v)\n\t\t}\n\t}\n\treturn values\n}\n\n// PeekMultiBytes returns all the arg values for the given key.\nfunc (a *Args) PeekMultiBytes(key []byte) [][]byte {\n\treturn a.PeekMulti(b2s(key))\n}\n\n// Has returns true if the given key exists in Args.\nfunc (a *Args) Has(key string) bool {\n\treturn hasArg(a.args, key)\n}\n\n// HasBytes returns true if the given key exists in Args.\nfunc (a *Args) HasBytes(key []byte) bool {\n\treturn hasArg(a.args, b2s(key))\n}\n\n// ErrNoArgValue is returned when Args value with the given key is missing.\nvar ErrNoArgValue = errors.New(\"no Args value for the given key\")\n\n// GetUint returns uint value for the given key.\nfunc (a *Args) GetUint(key string) (int, error) {\n\tvalue := a.Peek(key)\n\tif len(value) == 0 {\n\t\treturn -1, ErrNoArgValue\n\t}\n\treturn ParseUint(value)\n}\n\n// SetUint sets uint value for the given key.\nfunc (a *Args) SetUint(key string, value int) {\n\ta.buf = AppendUint(a.buf[:0], value)\n\ta.SetBytesV(key, a.buf)\n}\n\n// SetUintBytes sets uint value for the given key.\nfunc (a *Args) SetUintBytes(key []byte, value int) {\n\ta.SetUint(b2s(key), value)\n}\n\n// GetUintOrZero returns uint value for the given key.\n//\n// Zero (0) is returned on error.\nfunc (a *Args) GetUintOrZero(key string) int {\n\tn, err := a.GetUint(key)\n\tif err != nil {\n\t\tn = 0\n\t}\n\treturn n\n}\n\n// GetUfloat returns ufloat value for the given key.\nfunc (a *Args) GetUfloat(key string) (float64, error) {\n\tvalue := a.Peek(key)\n\tif len(value) == 0 {\n\t\treturn -1, ErrNoArgValue\n\t}\n\treturn ParseUfloat(value)\n}\n\n// GetUfloatOrZero returns ufloat value for the given key.\n//\n// Zero (0) is returned on error.\nfunc (a *Args) GetUfloatOrZero(key string) float64 {\n\tf, err := a.GetUfloat(key)\n\tif err != nil {\n\t\tf = 0\n\t}\n\treturn f\n}\n\n// GetBool returns boolean value for the given key.\n//\n// true is returned for \"1\", \"t\", \"T\", \"true\", \"TRUE\", \"True\", \"y\", \"yes\", \"Y\", \"YES\", \"Yes\",\n// otherwise false is returned.\nfunc (a *Args) GetBool(key string) bool {\n\tswitch string(a.Peek(key)) {\n\t// Support the same true cases as strconv.ParseBool\n\t// See: https://github.com/golang/go/blob/4e1b11e2c9bdb0ddea1141eed487be1a626ff5be/src/strconv/atob.go#L12\n\t// and Y and Yes versions.\n\tcase \"1\", \"t\", \"T\", \"true\", \"TRUE\", \"True\", \"y\", \"yes\", \"Y\", \"YES\", \"Yes\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc copyArgs(dst, src []argsKV) []argsKV {\n\tif cap(dst) < len(src) {\n\t\ttmp := make([]argsKV, len(src))\n\t\tdstLen := len(dst)\n\t\tdst = dst[:cap(dst)] // copy all of dst.\n\t\tcopy(tmp, dst)\n\t\tfor i := dstLen; i < len(tmp); i++ {\n\t\t\t// Make sure nothing is nil.\n\t\t\ttmp[i].key = []byte{}\n\t\t\ttmp[i].value = []byte{}\n\t\t}\n\t\tdst = tmp\n\t}\n\tn := len(src)\n\tdst = dst[:n]\n\tfor i := range n {\n\t\tdstKV := &dst[i]\n\t\tsrcKV := &src[i]\n\t\tdstKV.key = append(dstKV.key[:0], srcKV.key...)\n\t\tif srcKV.noValue {\n\t\t\tdstKV.value = dstKV.value[:0]\n\t\t} else {\n\t\t\tdstKV.value = append(dstKV.value[:0], srcKV.value...)\n\t\t}\n\t\tdstKV.noValue = srcKV.noValue\n\t}\n\treturn dst\n}\n\nfunc delAllArgsStable(args []argsKV, key string) []argsKV {\n\tfor i, n := 0, len(args); i < n; i++ {\n\t\tkv := &args[i]\n\t\tif key == string(kv.key) {\n\t\t\ttmp := *kv\n\t\t\tcopy(args[i:], args[i+1:])\n\t\t\tn--\n\t\t\ti--\n\t\t\targs[n] = tmp\n\t\t\targs = args[:n]\n\t\t}\n\t}\n\treturn args\n}\n\nfunc delAllArgs(args []argsKV, key string) []argsKV {\n\tn := len(args)\n\tfor i := 0; i < n; i++ {\n\t\tif key == string(args[i].key) {\n\t\t\targs[i], args[n-1] = args[n-1], args[i]\n\t\t\tn--\n\t\t\ti--\n\t\t}\n\t}\n\treturn args[:n]\n}\n\nfunc setArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {\n\treturn setArg(h, b2s(key), b2s(value), noValue)\n}\n\nfunc setArg(h []argsKV, key, value string, noValue bool) []argsKV {\n\tn := len(h)\n\tfor i := range n {\n\t\tkv := &h[i]\n\t\tif key == string(kv.key) {\n\t\t\tif noValue {\n\t\t\t\tkv.value = kv.value[:0]\n\t\t\t} else {\n\t\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\t}\n\t\t\tkv.noValue = noValue\n\t\t\treturn h\n\t\t}\n\t}\n\treturn appendArg(h, key, value, noValue)\n}\n\nfunc appendArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {\n\treturn appendArg(h, b2s(key), b2s(value), noValue)\n}\n\nfunc appendArg(args []argsKV, key, value string, noValue bool) []argsKV {\n\tvar kv *argsKV\n\targs, kv = allocArg(args)\n\tkv.key = append(kv.key[:0], key...)\n\tif noValue {\n\t\tkv.value = kv.value[:0]\n\t} else {\n\t\tkv.value = append(kv.value[:0], value...)\n\t}\n\tkv.noValue = noValue\n\treturn args\n}\n\nfunc allocArg(h []argsKV) ([]argsKV, *argsKV) {\n\tn := len(h)\n\tif cap(h) > n {\n\t\th = h[:n+1]\n\t} else {\n\t\th = append(h, argsKV{\n\t\t\tvalue: []byte{},\n\t\t})\n\t}\n\treturn h, &h[n]\n}\n\nfunc releaseArg(h []argsKV) []argsKV {\n\treturn h[:len(h)-1]\n}\n\nfunc hasArg(h []argsKV, key string) bool {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif key == string(kv.key) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc peekArgBytes(h []argsKV, k []byte) []byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif bytes.Equal(kv.key, k) {\n\t\t\treturn kv.value\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc peekArgStr(h []argsKV, k string) []byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif string(kv.key) == k {\n\t\t\treturn kv.value\n\t\t}\n\t}\n\treturn nil\n}\n\ntype argsScanner struct {\n\tb []byte\n}\n\nfunc (s *argsScanner) next(kv *argsKV) bool {\n\tif len(s.b) == 0 {\n\t\treturn false\n\t}\n\tkv.noValue = argsHasValue\n\n\tisKey := true\n\tk := 0\n\tfor i, c := range s.b {\n\t\tswitch c {\n\t\tcase '=':\n\t\t\tif isKey {\n\t\t\t\tisKey = false\n\t\t\t\tkv.key = decodeArgAppend(kv.key[:0], s.b[:i])\n\t\t\t\tk = i + 1\n\t\t\t}\n\t\tcase '&':\n\t\t\tif isKey {\n\t\t\t\tkv.key = decodeArgAppend(kv.key[:0], s.b[:i])\n\t\t\t\tkv.value = kv.value[:0]\n\t\t\t\tkv.noValue = argsNoValue\n\t\t\t} else {\n\t\t\t\tkv.value = decodeArgAppend(kv.value[:0], s.b[k:i])\n\t\t\t}\n\t\t\ts.b = s.b[i+1:]\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif isKey {\n\t\tkv.key = decodeArgAppend(kv.key[:0], s.b)\n\t\tkv.value = kv.value[:0]\n\t\tkv.noValue = argsNoValue\n\t} else {\n\t\tkv.value = decodeArgAppend(kv.value[:0], s.b[k:])\n\t}\n\ts.b = s.b[len(s.b):]\n\treturn true\n}\n\nfunc decodeArgAppend(dst, src []byte) []byte {\n\tidxPercent := bytes.IndexByte(src, '%')\n\tidxPlus := bytes.IndexByte(src, '+')\n\tif idxPercent == -1 && idxPlus == -1 {\n\t\t// fast path: src doesn't contain encoded chars\n\t\treturn append(dst, src...)\n\t}\n\n\tvar idx int\n\tswitch {\n\tcase idxPercent == -1:\n\t\tidx = idxPlus\n\tcase idxPlus == -1:\n\t\tidx = idxPercent\n\tcase idxPercent > idxPlus:\n\t\tidx = idxPlus\n\tdefault:\n\t\tidx = idxPercent\n\t}\n\n\tdst = append(dst, src[:idx]...)\n\n\t// slow path\n\tfor i := idx; i < len(src); i++ {\n\t\tc := src[i]\n\t\tswitch c {\n\t\tcase '%':\n\t\t\tif i+2 >= len(src) {\n\t\t\t\treturn append(dst, src[i:]...)\n\t\t\t}\n\t\t\tx2 := hex2intTable[src[i+2]]\n\t\t\tx1 := hex2intTable[src[i+1]]\n\t\t\tif x1 == 16 || x2 == 16 {\n\t\t\t\tdst = append(dst, '%')\n\t\t\t} else {\n\t\t\t\tdst = append(dst, x1<<4|x2)\n\t\t\t\ti += 2\n\t\t\t}\n\t\tcase '+':\n\t\t\tdst = append(dst, ' ')\n\t\tdefault:\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\n// decodeArgAppendNoPlus is almost identical to decodeArgAppend, but it doesn't\n// substitute '+' with ' '.\n//\n// The function is copy-pasted from decodeArgAppend due to the performance\n// reasons only.\nfunc decodeArgAppendNoPlus(dst, src []byte) []byte {\n\tidx := bytes.IndexByte(src, '%')\n\tif idx < 0 {\n\t\t// fast path: src doesn't contain encoded chars\n\t\treturn append(dst, src...)\n\t}\n\tdst = append(dst, src[:idx]...)\n\n\t// slow path\n\tfor i := idx; i < len(src); i++ {\n\t\tc := src[i]\n\t\tif c == '%' {\n\t\t\tif i+2 >= len(src) {\n\t\t\t\treturn append(dst, src[i:]...)\n\t\t\t}\n\t\t\tx2 := hex2intTable[src[i+2]]\n\t\t\tx1 := hex2intTable[src[i+1]]\n\t\t\tif x1 == 16 || x2 == 16 {\n\t\t\t\tdst = append(dst, '%')\n\t\t\t} else {\n\t\t\t\tdst = append(dst, x1<<4|x2)\n\t\t\t\ti += 2\n\t\t\t}\n\t\t} else {\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc peekAllArgBytesToDst(dst [][]byte, h []argsKV, k []byte) [][]byte {\n\tfor i, n := 0, len(h); i < n; i++ {\n\t\tkv := &h[i]\n\t\tif bytes.Equal(kv.key, k) {\n\t\t\tdst = append(dst, kv.value)\n\t\t}\n\t}\n\treturn dst\n}\n"
  },
  {
    "path": "args_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nfunc TestDecodeArgAppend(t *testing.T) {\n\tt.Parallel()\n\n\ttestDecodeArgAppend(t, \"\", \"\")\n\ttestDecodeArgAppend(t, \"foobar\", \"foobar\")\n\ttestDecodeArgAppend(t, \"тест\", \"тест\")\n\ttestDecodeArgAppend(t, \"a%\", \"a%\")\n\ttestDecodeArgAppend(t, \"%a%21\", \"%a!\")\n\ttestDecodeArgAppend(t, \"ab%test\", \"ab%test\")\n\ttestDecodeArgAppend(t, \"d%тестF\", \"d%тестF\")\n\ttestDecodeArgAppend(t, \"a%\\xffb%20c\", \"a%\\xffb c\")\n\ttestDecodeArgAppend(t, \"foo%20bar\", \"foo bar\")\n\ttestDecodeArgAppend(t, \"f.o%2C1%3A2%2F4=%7E%60%21%40%23%24%25%5E%26*%28%29_-%3D%2B%5C%7C%2F%5B%5D%7B%7D%3B%3A%27%22%3C%3E%2C.%2F%3F\",\n\t\t\"f.o,1:2/4=~`!@#$%^&*()_-=+\\\\|/[]{};:'\\\"<>,./?\")\n}\n\nfunc testDecodeArgAppend(t *testing.T, s, expectedResult string) {\n\tresult := decodeArgAppend(nil, []byte(s))\n\tif string(result) != expectedResult {\n\t\tt.Fatalf(\"unexpected decodeArgAppend(%q)=%q; expecting %q\", s, result, expectedResult)\n\t}\n}\n\nfunc TestArgsAdd(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.Add(\"foo\", \"bar\")\n\ta.Add(\"foo\", \"baz\")\n\ta.Add(\"foo\", \"1\")\n\ta.Add(\"ba\", \"23\")\n\ta.Add(\"foo\", \"\")\n\ta.AddNoValue(\"foo\")\n\tif a.Len() != 6 {\n\t\tt.Fatalf(\"unexpected number of elements: %d. Expecting 6\", a.Len())\n\t}\n\ts := a.String()\n\texpectedS := \"foo=bar&foo=baz&foo=1&ba=23&foo=&foo\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", s, expectedS)\n\t}\n\n\ta.Sort(bytes.Compare)\n\tss := a.String()\n\texpectedSS := \"ba=23&foo=&foo&foo=1&foo=bar&foo=baz\"\n\tif ss != expectedSS {\n\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", ss, expectedSS)\n\t}\n\n\tvar a1 Args\n\ta1.Parse(s)\n\tif a1.Len() != 6 {\n\t\tt.Fatalf(\"unexpected number of elements: %d. Expecting 6\", a.Len())\n\t}\n\n\tvar barFound, bazFound, oneFound, emptyFound1, emptyFound2, baFound bool\n\tfor k, v := range a1.All() {\n\t\tswitch string(k) {\n\t\tcase \"foo\":\n\t\t\tswitch string(v) {\n\t\t\tcase \"bar\":\n\t\t\t\tbarFound = true\n\t\t\tcase \"baz\":\n\t\t\t\tbazFound = true\n\t\t\tcase \"1\":\n\t\t\t\toneFound = true\n\t\t\tcase \"\":\n\t\t\t\tif emptyFound1 {\n\t\t\t\t\temptyFound2 = true\n\t\t\t\t} else {\n\t\t\t\t\temptyFound1 = true\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unexpected value %q\", v)\n\t\t\t}\n\t\tcase \"ba\":\n\t\t\tif string(v) != \"23\" {\n\t\t\t\tt.Fatalf(\"unexpected value: %q. Expecting %q\", v, \"23\")\n\t\t\t}\n\t\t\tbaFound = true\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found %q\", k)\n\t\t}\n\t}\n\tif !barFound || !bazFound || !oneFound || !emptyFound1 || !emptyFound2 || !baFound {\n\t\tt.Fatalf(\"something is missing: %v, %v, %v, %v, %v, %v\", barFound, bazFound, oneFound, emptyFound1, emptyFound2, baFound)\n\t}\n}\n\nfunc TestArgsSortKeys(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.Add(\"a\", \"789\")\n\ta.Add(\"b\", \"456\")\n\ta.Add(\"a\", \"123\")\n\ta.SortKeys(bytes.Compare)\n\n\ts := a.String()\n\texpectedS := \"a=789&a=123&b=456\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", s, expectedS)\n\t}\n}\n\nfunc TestArgsAcquireReleaseSequential(t *testing.T) {\n\ttestArgsAcquireRelease(t)\n}\n\nfunc TestArgsAcquireReleaseConcurrent(t *testing.T) {\n\tch := make(chan struct{}, 10)\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestArgsAcquireRelease(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testArgsAcquireRelease(t *testing.T) {\n\ta := AcquireArgs()\n\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tv := fmt.Sprintf(\"value_%d\", i*3+123)\n\t\ta.Set(k, v)\n\t}\n\n\ts := a.String()\n\ta.Reset()\n\ta.Parse(s)\n\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\texpectedV := fmt.Sprintf(\"value_%d\", i*3+123)\n\t\tv := a.Peek(k)\n\t\tif string(v) != expectedV {\n\t\t\tt.Fatalf(\"unexpected value %q for key %q. Expecting %q\", v, k, expectedV)\n\t\t}\n\t}\n\n\tReleaseArgs(a)\n}\n\nfunc TestArgsPeekMulti(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.Parse(\"foo=123&bar=121&foo=321&foo=&barz=sdf\")\n\n\tvv := a.PeekMulti(\"foo\")\n\texpectedVV := [][]byte{\n\t\t[]byte(\"123\"),\n\t\t[]byte(\"321\"),\n\t\t[]byte(nil),\n\t}\n\tif !reflect.DeepEqual(vv, expectedVV) {\n\t\tt.Fatalf(\"unexpected vv\\n%#v\\nExpecting\\n%#v\\n\", vv, expectedVV)\n\t}\n\n\tvv = a.PeekMulti(\"aaaa\")\n\tif len(vv) > 0 {\n\t\tt.Fatalf(\"expecting empty result for non-existing key. Got %#v\", vv)\n\t}\n\n\tvv = a.PeekMulti(\"bar\")\n\texpectedVV = [][]byte{[]byte(\"121\")}\n\tif !reflect.DeepEqual(vv, expectedVV) {\n\t\tt.Fatalf(\"unexpected vv\\n%#v\\nExpecting\\n%#v\\n\", vv, expectedVV)\n\t}\n}\n\nfunc TestArgsEscape(t *testing.T) {\n\tt.Parallel()\n\n\ttestArgsEscape(t, \"foo\", \"bar\", \"foo=bar\")\n\n\t// Test all characters\n\tk := \"f.o,1:2/4\"\n\tv := make([]byte, 256)\n\tfor i := range 256 {\n\t\tv[i] = byte(i)\n\t}\n\tu := url.Values{}\n\tu.Add(k, string(v))\n\ttestArgsEscape(t, k, string(v), u.Encode())\n}\n\nfunc testArgsEscape(t *testing.T, k, v, expectedS string) {\n\tvar a Args\n\ta.Set(k, v)\n\ts := a.String()\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected args %q. Expecting %q. k=%q, v=%q\", s, expectedS, k, v)\n\t}\n}\n\nfunc TestPathEscape(t *testing.T) {\n\tt.Parallel()\n\n\ttestPathEscape(t, \"/foo/bar\")\n\ttestPathEscape(t, \"\")\n\ttestPathEscape(t, \"/\")\n\ttestPathEscape(t, \"//\")\n\ttestPathEscape(t, \"*\") // See https://github.com/golang/go/issues/11202\n\n\t// Test all characters\n\tpathSegment := make([]byte, 256)\n\tfor i := range 256 {\n\t\tpathSegment[i] = byte(i)\n\t}\n\ttestPathEscape(t, \"/foo/\"+string(pathSegment))\n}\n\nfunc testPathEscape(t *testing.T, s string) {\n\tu := url.URL{Path: s}\n\texpectedS := u.EscapedPath()\n\tres := string(appendQuotedPath(nil, []byte(s)))\n\tif res != expectedS {\n\t\tt.Fatalf(\"unexpected args %q. Expecting %q.\", res, expectedS)\n\t}\n}\n\nfunc TestArgsWriteTo(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"foo=bar&baz=123&aaa=bbb\"\n\n\tvar a Args\n\ta.Parse(s)\n\n\tvar w bytebufferpool.ByteBuffer\n\tn, err := a.WriteTo(&w)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != int64(len(s)) {\n\t\tt.Fatalf(\"unexpected n: %d. Expecting %d\", n, len(s))\n\t}\n\tresult := string(w.B)\n\tif result != s {\n\t\tt.Fatalf(\"unexpected result %q. Expecting %q\", result, s)\n\t}\n}\n\nfunc TestArgsGetBool(t *testing.T) {\n\tt.Parallel()\n\n\ttestArgsGetBool(t, \"\", false)\n\ttestArgsGetBool(t, \"0\", false)\n\ttestArgsGetBool(t, \"n\", false)\n\ttestArgsGetBool(t, \"no\", false)\n\ttestArgsGetBool(t, \"1\", true)\n\ttestArgsGetBool(t, \"y\", true)\n\ttestArgsGetBool(t, \"yes\", true)\n\n\ttestArgsGetBool(t, \"123\", false)\n\ttestArgsGetBool(t, \"foobar\", false)\n}\n\nfunc testArgsGetBool(t *testing.T, value string, expectedResult bool) {\n\tvar a Args\n\ta.Parse(\"v=\" + value)\n\n\tresult := a.GetBool(\"v\")\n\tif result != expectedResult {\n\t\tt.Fatalf(\"unexpected result %v. Expecting %v for value %q\", result, expectedResult, value)\n\t}\n}\n\nfunc TestArgsUint(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.SetUint(\"foo\", 123)\n\ta.SetUint(\"bar\", 0)\n\ta.SetUint(\"aaaa\", 34566)\n\n\texpectedS := \"foo=123&bar=0&aaaa=34566\"\n\ts := string(a.QueryString())\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected args %q. Expecting %q\", s, expectedS)\n\t}\n\n\tif a.GetUintOrZero(\"foo\") != 123 {\n\t\tt.Fatalf(\"unexpected arg value %d. Expecting %d\", a.GetUintOrZero(\"foo\"), 123)\n\t}\n\tif a.GetUintOrZero(\"bar\") != 0 {\n\t\tt.Fatalf(\"unexpected arg value %d. Expecting %d\", a.GetUintOrZero(\"bar\"), 0)\n\t}\n\tif a.GetUintOrZero(\"aaaa\") != 34566 {\n\t\tt.Fatalf(\"unexpected arg value %d. Expecting %d\", a.GetUintOrZero(\"aaaa\"), 34566)\n\t}\n\n\tif string(a.Peek(\"foo\")) != \"123\" {\n\t\tt.Fatalf(\"unexpected arg value %q. Expecting %q\", a.Peek(\"foo\"), \"123\")\n\t}\n\tif string(a.Peek(\"bar\")) != \"0\" {\n\t\tt.Fatalf(\"unexpected arg value %q. Expecting %q\", a.Peek(\"bar\"), \"0\")\n\t}\n\tif string(a.Peek(\"aaaa\")) != \"34566\" {\n\t\tt.Fatalf(\"unexpected arg value %q. Expecting %q\", a.Peek(\"aaaa\"), \"34566\")\n\t}\n}\n\nfunc TestArgsCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\n\t// empty args\n\ttestCopyTo(t, &a)\n\n\ta.Set(\"foo\", \"bar\")\n\ttestCopyTo(t, &a)\n\n\ta.Set(\"xxx\", \"yyy\")\n\ta.AddNoValue(\"ba\")\n\ttestCopyTo(t, &a)\n\n\ta.Del(\"foo\")\n\ttestCopyTo(t, &a)\n}\n\nfunc testCopyTo(t *testing.T, a *Args) {\n\tkeys := make(map[string]struct{})\n\tfor k := range a.All() {\n\t\tkeys[string(k)] = struct{}{}\n\t}\n\n\tvar b Args\n\ta.CopyTo(&b)\n\n\tif !reflect.DeepEqual(a, &b) {\n\t\tt.Fatalf(\"ArgsCopyTo fail, a: \\n%+v\\nb: \\n%+v\\n\", a, &b)\n\t}\n\n\tfor k := range b.All() {\n\t\tif _, ok := keys[string(k)]; !ok {\n\t\t\tt.Fatalf(\"unexpected key %q after copying from %q\", k, a.String())\n\t\t}\n\t\tdelete(keys, string(k))\n\t}\n\tif len(keys) > 0 {\n\t\tt.Fatalf(\"missing keys %#v after copying from %q\", keys, a.String())\n\t}\n}\n\nfunc TestArgsVisitAll(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.Set(\"foo\", \"bar\")\n\n\ti := 0\n\ta.VisitAll(func(k, v []byte) {\n\t\tif string(k) != \"foo\" {\n\t\t\tt.Fatalf(\"unexpected key %q. Expected %q\", k, \"foo\")\n\t\t}\n\t\tif string(v) != \"bar\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expected %q\", v, \"bar\")\n\t\t}\n\t\ti++\n\t})\n\tif i != 1 {\n\t\tt.Fatalf(\"unexpected number of VisitAll calls: %d. Expected %d\", i, 1)\n\t}\n}\n\nfunc TestArgsStringCompose(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\ta.Set(\"foo\", \"bar\")\n\ta.Set(\"aa\", \"bbb\")\n\ta.Set(\"привет\", \"мир\")\n\ta.SetNoValue(\"bb\")\n\ta.Set(\"\", \"xxxx\")\n\ta.Set(\"cvx\", \"\")\n\ta.SetNoValue(\"novalue\")\n\n\texpectedS := \"foo=bar&aa=bbb&%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80&bb&=xxxx&cvx=&novalue\"\n\ts := a.String()\n\tif s != expectedS {\n\t\tt.Fatalf(\"Unexpected string %q. Expected %q\", s, expectedS)\n\t}\n}\n\nfunc TestArgsString(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\n\ttestArgsString(t, &a, \"\")\n\ttestArgsString(t, &a, \"foobar\")\n\ttestArgsString(t, &a, \"foo=bar\")\n\ttestArgsString(t, &a, \"foo=bar&baz=sss\")\n\ttestArgsString(t, &a, \"\")\n\ttestArgsString(t, &a, \"f+o=x.x%2A-_8x%D0%BF%D1%80%D0%B8%D0%B2%D0%B5aaa&sdf=ss\")\n\ttestArgsString(t, &a, \"=asdfsdf\")\n}\n\nfunc testArgsString(t *testing.T, a *Args, s string) {\n\ta.Parse(s)\n\ts1 := a.String()\n\tif s != s1 {\n\t\tt.Fatalf(\"Unexpected args %q. Expected %q\", s1, s)\n\t}\n}\n\nfunc TestArgsSetGetDel(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\n\tif len(a.Peek(\"foo\")) > 0 {\n\t\tt.Fatalf(\"Unexpected value: %q\", a.Peek(\"foo\"))\n\t}\n\tif len(a.Peek(\"\")) > 0 {\n\t\tt.Fatalf(\"Unexpected value: %q\", a.Peek(\"\"))\n\t}\n\ta.Del(\"xxx\")\n\n\tfor range 3 {\n\t\tfor i := range 10 {\n\t\t\tk := fmt.Sprintf(\"foo%d\", i)\n\t\t\tv := fmt.Sprintf(\"bar_%d\", i)\n\t\t\ta.Set(k, v)\n\t\t\tif string(a.Peek(k)) != v {\n\t\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), v)\n\t\t\t}\n\t\t}\n\t}\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"foo%d\", i)\n\t\tv := fmt.Sprintf(\"bar_%d\", i)\n\t\tif string(a.Peek(k)) != v {\n\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), v)\n\t\t}\n\t\ta.Del(k)\n\t\tif len(a.Peek(k)) != 0 {\n\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), \"\")\n\t\t}\n\t}\n\n\ta.Parse(\"aaa=xxx&bb=aa\")\n\tif len(a.Peek(\"foo0\")) != 0 {\n\t\tt.Fatalf(\"Unexpected value %q\", a.Peek(\"foo0\"))\n\t}\n\tif string(a.Peek(\"aaa\")) != \"xxx\" {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", a.Peek(\"aaa\"), \"xxx\")\n\t}\n\tif string(a.Peek(\"bb\")) != \"aa\" {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", a.Peek(\"bb\"), \"aa\")\n\t}\n\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"xx%d\", i)\n\t\tv := fmt.Sprintf(\"yy%d\", i)\n\t\ta.Set(k, v)\n\t\tif string(a.Peek(k)) != v {\n\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), v)\n\t\t}\n\t}\n\tfor i := 5; i < 10; i++ {\n\t\tk := fmt.Sprintf(\"xx%d\", i)\n\t\tv := fmt.Sprintf(\"yy%d\", i)\n\t\tif string(a.Peek(k)) != v {\n\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), v)\n\t\t}\n\t\ta.Del(k)\n\t\tif len(a.Peek(k)) != 0 {\n\t\t\tt.Fatalf(\"Unexpected value: %q. Expected %q\", a.Peek(k), \"\")\n\t\t}\n\t}\n}\n\nfunc TestArgsParse(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\n\t// empty args\n\ttestArgsParse(t, &a, \"\", 0, \"foo=\", \"bar=\", \"=\")\n\n\t// arg without value\n\ttestArgsParse(t, &a, \"foo1\", 1, \"foo=\", \"bar=\", \"=\")\n\n\t// arg without value, but with equal sign\n\ttestArgsParse(t, &a, \"foo2=\", 1, \"foo=\", \"bar=\", \"=\")\n\n\t// arg with value\n\ttestArgsParse(t, &a, \"foo3=bar1\", 1, \"foo3=bar1\", \"bar=\", \"=\")\n\n\t// empty key\n\ttestArgsParse(t, &a, \"=bar2\", 1, \"foo=\", \"=bar2\", \"bar2=\")\n\n\t// missing kv\n\ttestArgsParse(t, &a, \"&&&&\", 0, \"foo=\", \"bar=\", \"=\")\n\n\t// multiple values with the same key\n\ttestArgsParse(t, &a, \"x=1&x=2&x=3\", 3, \"x=1\")\n\n\t// multiple args\n\ttestArgsParse(t, &a, \"&&&qw=er&tyx=124&&&zxc_ss=2234&&\", 3, \"qw=er\", \"tyx=124\", \"zxc_ss=2234\")\n\n\t// multiple args without values\n\ttestArgsParse(t, &a, \"&&a&&b&&bar&baz\", 4, \"a=\", \"b=\", \"bar=\", \"baz=\")\n\n\t// values with '='\n\ttestArgsParse(t, &a, \"zz=1&k=v=v=a=a=s\", 2, \"k=v=v=a=a=s\", \"zz=1\")\n\n\t// mixed '=' and '&'\n\ttestArgsParse(t, &a, \"sss&z=dsf=&df\", 3, \"sss=\", \"z=dsf=\", \"df=\")\n\n\t// encoded args\n\ttestArgsParse(t, &a, \"f+o%20o=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82+test\", 1, \"f o o=привет test\")\n\n\t// invalid percent encoding\n\ttestArgsParse(t, &a, \"f%=x&qw%z=d%0k%20p&%%20=%%%20x\", 3, \"f%=x\", \"qw%z=d%0k p\", \"% =%% x\")\n\n\t// special chars\n\ttestArgsParse(t, &a, \"a.b,c:d/e=f.g,h:i/q\", 1, \"a.b,c:d/e=f.g,h:i/q\")\n}\n\nfunc TestArgsHas(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Args\n\n\t// single arg\n\ttestArgsHas(t, &a, \"foo\", \"foo\")\n\ttestArgsHasNot(t, &a, \"foo\", \"bar\", \"baz\", \"\")\n\n\t// multi args without values\n\ttestArgsHas(t, &a, \"foo&bar\", \"foo\", \"bar\")\n\ttestArgsHasNot(t, &a, \"foo&bar\", \"\", \"aaaa\")\n\n\t// multi args\n\ttestArgsHas(t, &a, \"b=xx&=aaa&c=\", \"b\", \"\", \"c\")\n\ttestArgsHasNot(t, &a, \"b=xx&=aaa&c=\", \"xx\", \"aaa\", \"foo\")\n\n\t// encoded args\n\ttestArgsHas(t, &a, \"a+b=c+d%20%20e\", \"a b\")\n\ttestArgsHasNot(t, &a, \"a+b=c+d\", \"a+b\", \"c+d\")\n}\n\nfunc testArgsHas(t *testing.T, a *Args, s string, expectedKeys ...string) {\n\ta.Parse(s)\n\tfor _, key := range expectedKeys {\n\t\tif !a.Has(key) {\n\t\t\tt.Fatalf(\"Missing key %q in %q\", key, s)\n\t\t}\n\t}\n}\n\nfunc testArgsHasNot(t *testing.T, a *Args, s string, unexpectedKeys ...string) {\n\ta.Parse(s)\n\tfor _, key := range unexpectedKeys {\n\t\tif a.Has(key) {\n\t\t\tt.Fatalf(\"Unexpected key %q in %q\", key, s)\n\t\t}\n\t}\n}\n\nfunc testArgsParse(t *testing.T, a *Args, s string, expectedLen int, expectedArgs ...string) {\n\ta.Parse(s)\n\tif a.Len() != expectedLen {\n\t\tt.Fatalf(\"Unexpected args len %d. Expected %d. s=%q\", a.Len(), expectedLen, s)\n\t}\n\tfor _, xx := range expectedArgs {\n\t\ttmp := strings.SplitN(xx, \"=\", 2)\n\t\tk := tmp[0]\n\t\tv := tmp[1]\n\t\tbuf := a.Peek(k)\n\t\tif string(buf) != v {\n\t\t\tt.Fatalf(\"Unexpected value for key=%q: %q. Expected %q. s=%q\", k, buf, v, s)\n\t\t}\n\t}\n}\n\nfunc TestArgsDeleteAll(t *testing.T) {\n\tt.Parallel()\n\tvar a Args\n\ta.Add(\"q1\", \"foo\")\n\ta.Add(\"q1\", \"bar\")\n\ta.Add(\"q1\", \"baz\")\n\ta.Add(\"q1\", \"quux\")\n\ta.Add(\"q2\", \"1234\")\n\ta.Del(\"q1\")\n\tif a.Len() != 1 || a.Has(\"q1\") {\n\t\tt.Fatalf(\"Expected q1 arg to be completely deleted. Current Args: %q\", a.String())\n\t}\n}\n\nfunc TestIssue932(t *testing.T) {\n\tt.Parallel()\n\tvar a []argsKV\n\n\ta = setArg(a, \"t1\", \"ok\", argsHasValue)\n\ta = setArg(a, \"t2\", \"\", argsHasValue)\n\ta = setArg(a, \"t1\", \"\", argsHasValue)\n\ta = setArgBytes(a, s2b(\"t3\"), []byte{}, argsHasValue)\n\ta = setArgBytes(a, s2b(\"t4\"), nil, argsHasValue)\n\n\tif peekArgStr(a, \"t1\") == nil {\n\t\tt.Error(\"nil not expected for t1\")\n\t}\n\tif peekArgStr(a, \"t2\") == nil {\n\t\tt.Error(\"nil not expected for t2\")\n\t}\n\tif peekArgStr(a, \"t3\") == nil {\n\t\tt.Error(\"nil not expected for t3\")\n\t}\n\tif peekArgStr(a, \"t4\") != nil {\n\t\tt.Error(\"nil expected for t4\")\n\t}\n}\n"
  },
  {
    "path": "args_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc BenchmarkArgsParse(b *testing.B) {\n\ts := []byte(\"foo=bar&baz=qqq&aaaaa=bbbb\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar a Args\n\t\tfor pb.Next() {\n\t\t\ta.ParseBytes(s)\n\t\t}\n\t})\n}\n\nfunc BenchmarkArgsPeek(b *testing.B) {\n\tvalue := []byte(\"foobarbaz1234\")\n\tkey := \"foobarbaz\"\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar a Args\n\t\ta.SetBytesV(key, value)\n\t\tfor pb.Next() {\n\t\t\tif !bytes.Equal(a.Peek(key), value) {\n\t\t\t\tb.Fatalf(\"unexpected arg value %q. Expecting %q\", a.Peek(key), value)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "b2s.go",
    "content": "package fasthttp\n\nimport \"unsafe\"\n\n// b2s converts byte slice to a string without memory allocation.\n// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .\nfunc b2s(b []byte) string {\n\treturn unsafe.String(unsafe.SliceData(b), len(b))\n}\n"
  },
  {
    "path": "brotli.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/andybalholm/brotli\"\n\t\"github.com/valyala/bytebufferpool\"\n\t\"github.com/valyala/fasthttp/stackless\"\n)\n\n// Supported compression levels.\nconst (\n\tCompressBrotliNoCompression   = 0\n\tCompressBrotliBestSpeed       = brotli.BestSpeed\n\tCompressBrotliBestCompression = brotli.BestCompression\n\n\t// CompressBrotliDefaultCompression chooses a default brotli compression level comparable to\n\t// CompressDefaultCompression (gzip 6).\n\t// See: https://github.com/valyala/fasthttp/issues/798#issuecomment-626293806\n\tCompressBrotliDefaultCompression = 4\n)\n\nfunc acquireBrotliReader(r io.Reader) (*brotli.Reader, error) {\n\tv := brotliReaderPool.Get()\n\tif v == nil {\n\t\treturn brotli.NewReader(r), nil\n\t}\n\tzr := v.(*brotli.Reader)\n\tif err := zr.Reset(r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn zr, nil\n}\n\nfunc releaseBrotliReader(zr *brotli.Reader) {\n\tbrotliReaderPool.Put(zr)\n}\n\nvar brotliReaderPool sync.Pool\n\nfunc acquireStacklessBrotliWriter(w io.Writer, level int) stackless.Writer {\n\tnLevel := normalizeBrotliCompressLevel(level)\n\tp := stacklessBrotliWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\treturn stackless.NewWriter(w, func(w io.Writer) stackless.Writer {\n\t\t\treturn acquireRealBrotliWriter(w, level)\n\t\t})\n\t}\n\tsw := v.(stackless.Writer)\n\tsw.Reset(w)\n\treturn sw\n}\n\nfunc releaseStacklessBrotliWriter(sw stackless.Writer, level int) {\n\tsw.Close()\n\tnLevel := normalizeBrotliCompressLevel(level)\n\tp := stacklessBrotliWriterPoolMap[nLevel]\n\tp.Put(sw)\n}\n\nfunc acquireRealBrotliWriter(w io.Writer, level int) *brotli.Writer {\n\tnLevel := normalizeBrotliCompressLevel(level)\n\tp := realBrotliWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\tzw := brotli.NewWriterLevel(w, level)\n\t\treturn zw\n\t}\n\tzw := v.(*brotli.Writer)\n\tzw.Reset(w)\n\treturn zw\n}\n\nfunc releaseRealBrotliWriter(zw *brotli.Writer, level int) {\n\tzw.Close()\n\tnLevel := normalizeBrotliCompressLevel(level)\n\tp := realBrotliWriterPoolMap[nLevel]\n\tp.Put(zw)\n}\n\nvar (\n\tstacklessBrotliWriterPoolMap = newCompressWriterPoolMap()\n\trealBrotliWriterPoolMap      = newCompressWriterPoolMap()\n)\n\n// AppendBrotliBytesLevel appends brotlied src to dst using the given\n// compression level and returns the resulting dst.\n//\n// Supported compression levels are:\n//\n//   - CompressBrotliNoCompression\n//   - CompressBrotliBestSpeed\n//   - CompressBrotliBestCompression\n//   - CompressBrotliDefaultCompression\nfunc AppendBrotliBytesLevel(dst, src []byte, level int) []byte {\n\tw := &byteSliceWriter{b: dst}\n\tWriteBrotliLevel(w, src, level) //nolint:errcheck\n\treturn w.b\n}\n\n// WriteBrotliLevel writes brotlied p to w using the given compression level\n// and returns the number of compressed bytes written to w.\n//\n// Supported compression levels are:\n//\n//   - CompressBrotliNoCompression\n//   - CompressBrotliBestSpeed\n//   - CompressBrotliBestCompression\n//   - CompressBrotliDefaultCompression\nfunc WriteBrotliLevel(w io.Writer, p []byte, level int) (int, error) {\n\tswitch w.(type) {\n\tcase *byteSliceWriter,\n\t\t*bytes.Buffer,\n\t\t*bytebufferpool.ByteBuffer:\n\t\t// These writers don't block, so we can just use stacklessWriteBrotli\n\t\tctx := &compressCtx{\n\t\t\tw:     w,\n\t\t\tp:     p,\n\t\t\tlevel: level,\n\t\t}\n\t\tstacklessWriteBrotli(ctx)\n\t\treturn len(p), nil\n\tdefault:\n\t\tzw := acquireStacklessBrotliWriter(w, level)\n\t\tn, err := zw.Write(p)\n\t\treleaseStacklessBrotliWriter(zw, level)\n\t\treturn n, err\n\t}\n}\n\nvar (\n\tstacklessWriteBrotliOnce sync.Once\n\tstacklessWriteBrotliFunc func(ctx any) bool\n)\n\nfunc stacklessWriteBrotli(ctx any) {\n\tstacklessWriteBrotliOnce.Do(func() {\n\t\tstacklessWriteBrotliFunc = stackless.NewFunc(nonblockingWriteBrotli)\n\t})\n\tstacklessWriteBrotliFunc(ctx)\n}\n\nfunc nonblockingWriteBrotli(ctxv any) {\n\tctx := ctxv.(*compressCtx)\n\tzw := acquireRealBrotliWriter(ctx.w, ctx.level)\n\n\tzw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway\n\n\treleaseRealBrotliWriter(zw, ctx.level)\n}\n\n// WriteBrotli writes brotlied p to w and returns the number of compressed\n// bytes written to w.\nfunc WriteBrotli(w io.Writer, p []byte) (int, error) {\n\treturn WriteBrotliLevel(w, p, CompressBrotliDefaultCompression)\n}\n\n// AppendBrotliBytes appends brotlied src to dst and returns the resulting dst.\nfunc AppendBrotliBytes(dst, src []byte) []byte {\n\treturn AppendBrotliBytesLevel(dst, src, CompressBrotliDefaultCompression)\n}\n\n// WriteUnbrotli writes unbrotlied p to w and returns the number of uncompressed\n// bytes written to w.\nfunc WriteUnbrotli(w io.Writer, p []byte) (int, error) {\n\treturn writeUnbrotli(w, p, 0)\n}\n\nfunc writeUnbrotli(w io.Writer, p []byte, maxBodySize int) (int, error) {\n\tr := &byteSliceReader{b: p}\n\tzr, err := acquireBrotliReader(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := copyZeroAllocWithLimit(w, zr, maxBodySize)\n\treleaseBrotliReader(zr)\n\tnn := int(n)\n\tif int64(nn) != n {\n\t\treturn 0, fmt.Errorf(\"too much data unbrotlied: %d\", n)\n\t}\n\treturn nn, err\n}\n\n// AppendUnbrotliBytes appends unbrotlied src to dst and returns the resulting dst.\nfunc AppendUnbrotliBytes(dst, src []byte) ([]byte, error) {\n\tw := &byteSliceWriter{b: dst}\n\t_, err := WriteUnbrotli(w, src)\n\treturn w.b, err\n}\n\n// normalizes compression level into [0..11], so it could be used as an index\n// in *PoolMap.\nfunc normalizeBrotliCompressLevel(level int) int {\n\t// -2 is the lowest compression level - CompressHuffmanOnly\n\t// 9 is the highest compression level - CompressBestCompression\n\tif level < 0 || level > 11 {\n\t\tlevel = CompressBrotliDefaultCompression\n\t}\n\treturn level\n}\n"
  },
  {
    "path": "brotli_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestBrotliBytesSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testBrotliBytes(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestBrotliBytesConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testBrotliBytes); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testBrotliBytes() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testBrotliBytesSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testBrotliBytesSingleCase(s string) error {\n\tprefix := []byte(\"foobar\")\n\tbrotlipedS := AppendBrotliBytes(prefix, []byte(s))\n\tif !bytes.Equal(brotlipedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when compressing %q: %q. Expecting %q\", s, brotlipedS[:len(prefix)], prefix)\n\t}\n\n\tunbrotliedS, err := AppendUnbrotliBytes(prefix, brotlipedS[len(prefix):])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error when uncompressing %q: %w\", s, err)\n\t}\n\tif !bytes.Equal(unbrotliedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when uncompressing %q: %q. Expecting %q\", s, unbrotliedS[:len(prefix)], prefix)\n\t}\n\tunbrotliedS = unbrotliedS[len(prefix):]\n\tif string(unbrotliedS) != s {\n\t\treturn fmt.Errorf(\"unexpected uncompressed string %q. Expecting %q\", unbrotliedS, s)\n\t}\n\treturn nil\n}\n\nfunc TestBrotliCompressSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testBrotliCompress(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestBrotliCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testBrotliCompress); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testBrotliCompress() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testBrotliCompressSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testBrotliCompressSingleCase(s string) error {\n\tvar buf bytes.Buffer\n\tzw := acquireStacklessBrotliWriter(&buf, CompressDefaultCompression)\n\tif _, err := zw.Write([]byte(s)); err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\treleaseStacklessBrotliWriter(zw, CompressDefaultCompression)\n\n\tzr, err := acquireBrotliReader(&buf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tbody, err := io.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tif string(body) != s {\n\t\treturn fmt.Errorf(\"unexpected string after decompression: %q. Expecting %q\", body, s)\n\t}\n\treleaseBrotliReader(zr)\n\treturn nil\n}\n\nfunc TestCompressHandlerBrotliLevel(t *testing.T) {\n\tt.Parallel()\n\n\texpectedBody := createFixedBody(2e4)\n\th := CompressHandlerBrotliLevel(func(ctx *RequestCtx) {\n\t\tctx.Write(expectedBody) //nolint:errcheck\n\t}, CompressBrotliDefaultCompression, CompressDefaultCompression)\n\n\tvar ctx RequestCtx\n\tvar resp Response\n\n\t// verify uncompressed response\n\th(&ctx)\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) != 0 {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"\")\n\t}\n\tbody := resp.Body()\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify gzip-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"gzip\")\n\t}\n\tbody, err := resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify brotli-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc, br\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"br\")\n\t}\n\tbody, err = resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n}\n"
  },
  {
    "path": "bytesconv.go",
    "content": "//go:generate go run bytesconv_table_gen.go\n\npackage fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\n// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.\nfunc AppendHTMLEscape(dst []byte, s string) []byte {\n\tvar (\n\t\tprev int\n\t\tsub  string\n\t)\n\n\tfor i, n := 0, len(s); i < n; i++ {\n\t\tsub = \"\"\n\t\tswitch s[i] {\n\t\tcase '&':\n\t\t\tsub = \"&amp;\"\n\t\tcase '<':\n\t\t\tsub = \"&lt;\"\n\t\tcase '>':\n\t\t\tsub = \"&gt;\"\n\t\tcase '\"':\n\t\t\tsub = \"&#34;\" // \"&#34;\" is shorter than \"&quot;\".\n\t\tcase '\\'':\n\t\t\tsub = \"&#39;\" // \"&#39;\" is shorter than \"&apos;\" and apos was not in HTML until HTML5.\n\t\t}\n\t\tif sub != \"\" {\n\t\t\tdst = append(dst, s[prev:i]...)\n\t\t\tdst = append(dst, sub...)\n\t\t\tprev = i + 1\n\t\t}\n\t}\n\treturn append(dst, s[prev:]...)\n}\n\n// AppendHTMLEscapeBytes appends html-escaped s to dst and returns\n// the extended dst.\nfunc AppendHTMLEscapeBytes(dst, s []byte) []byte {\n\treturn AppendHTMLEscape(dst, b2s(s))\n}\n\n// AppendIPv4 appends string representation of the given ip v4 to dst\n// and returns the extended dst.\nfunc AppendIPv4(dst []byte, ip net.IP) []byte {\n\tip = ip.To4()\n\tif ip == nil {\n\t\treturn append(dst, \"non-v4 ip passed to AppendIPv4\"...)\n\t}\n\n\tdst = AppendUint(dst, int(ip[0]))\n\tfor i := 1; i < 4; i++ {\n\t\tdst = append(dst, '.')\n\t\tdst = AppendUint(dst, int(ip[i]))\n\t}\n\treturn dst\n}\n\nvar errEmptyIPStr = errors.New(\"empty ip address string\")\n\n// ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.\nfunc ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {\n\tif len(ipStr) == 0 {\n\t\treturn dst, errEmptyIPStr\n\t}\n\tif len(dst) < net.IPv4len || len(dst) > net.IPv4len {\n\t\tdst = make([]byte, net.IPv4len)\n\t}\n\tcopy(dst, net.IPv4zero)\n\tdst = dst.To4() // dst is always non-nil here\n\n\tb := ipStr\n\tfor i := range 3 {\n\t\tn := bytes.IndexByte(b, '.')\n\t\tif n < 0 {\n\t\t\treturn dst, fmt.Errorf(\"cannot find dot in ipStr %q\", ipStr)\n\t\t}\n\t\toctet, parsed, err := parseIPv4Octet(b[:n])\n\t\tif err != nil {\n\t\t\tif errors.Is(err, errIPv4PartTooLarge) {\n\t\t\t\treturn dst, fmt.Errorf(\"cannot parse ipStr %q: ip part cannot exceed 255: parsed %d\", ipStr, parsed)\n\t\t\t}\n\t\t\treturn dst, fmt.Errorf(\"cannot parse ipStr %q: %w\", ipStr, err)\n\t\t}\n\t\tdst[i] = octet\n\t\tb = b[n+1:]\n\t}\n\toctet, parsed, err := parseIPv4Octet(b)\n\tif err != nil {\n\t\tif errors.Is(err, errIPv4PartTooLarge) {\n\t\t\treturn dst, fmt.Errorf(\"cannot parse ipStr %q: ip part cannot exceed 255: parsed %d\", ipStr, parsed)\n\t\t}\n\t\treturn dst, fmt.Errorf(\"cannot parse ipStr %q: %w\", ipStr, err)\n\t}\n\tdst[3] = octet\n\n\treturn dst, nil\n}\n\n// AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date\n// to dst and returns the extended dst.\nfunc AppendHTTPDate(dst []byte, date time.Time) []byte {\n\tdst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)\n\tcopy(dst[len(dst)-3:], strGMT)\n\treturn dst\n}\n\n// ParseHTTPDate parses HTTP-compliant (RFC1123) date.\nfunc ParseHTTPDate(date []byte) (time.Time, error) {\n\treturn time.Parse(time.RFC1123, b2s(date))\n}\n\n// AppendUint appends n to dst and returns the extended dst.\nfunc AppendUint(dst []byte, n int) []byte {\n\tif n < 0 {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: int must be positive\")\n\t}\n\n\treturn strconv.AppendUint(dst, uint64(n), 10)\n}\n\n// ParseUint parses uint from buf.\nfunc ParseUint(buf []byte) (int, error) {\n\tv, n, err := parseUintBuf(buf)\n\tif n != len(buf) {\n\t\treturn -1, errUnexpectedTrailingChar\n\t}\n\treturn v, err\n}\n\nvar (\n\terrEmptyInt               = errors.New(\"empty integer\")\n\terrIPv4PartTooLarge       = errors.New(\"ip part cannot exceed 255\")\n\terrUnexpectedFirstChar    = errors.New(\"unexpected first char found. Expecting 0-9\")\n\terrUnexpectedTrailingChar = errors.New(\"unexpected trailing char found. Expecting 0-9\")\n\terrTooLongInt             = errors.New(\"too long int\")\n)\n\nfunc parseUintBuf(b []byte) (int, int, error) {\n\tn := len(b)\n\tif n == 0 {\n\t\treturn -1, 0, errEmptyInt\n\t}\n\tv := 0\n\tfor i := range n {\n\t\tc := b[i]\n\t\tk := c - '0'\n\t\tif k > 9 {\n\t\t\tif i == 0 {\n\t\t\t\treturn -1, i, errUnexpectedFirstChar\n\t\t\t}\n\t\t\treturn v, i, nil\n\t\t}\n\t\tvNew := 10*v + int(k)\n\t\t// Test for overflow.\n\t\tif vNew < v {\n\t\t\treturn -1, i, errTooLongInt\n\t\t}\n\t\tv = vNew\n\t}\n\treturn v, n, nil\n}\n\nfunc parseIPv4Octet(b []byte) (byte, int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, 0, errEmptyInt\n\t}\n\n\tvar (\n\t\toctet  byte\n\t\tparsed int\n\t)\n\tfor i := range len(b) {\n\t\tc := b[i]\n\t\tk := c - '0'\n\t\tif k > 9 {\n\t\t\tif i == 0 {\n\t\t\t\treturn 0, parsed, errUnexpectedFirstChar\n\t\t\t}\n\t\t\treturn 0, parsed, errUnexpectedTrailingChar\n\t\t}\n\t\tparsed = parsed*10 + int(k)\n\t\tif octet > 25 || (octet == 25 && k > 5) {\n\t\t\treturn 0, parsed, errIPv4PartTooLarge\n\t\t}\n\t\toctet = octet*10 + k\n\t}\n\treturn octet, parsed, nil\n}\n\n// ParseUfloat parses unsigned float from buf.\nfunc ParseUfloat(buf []byte) (float64, error) {\n\t// The implementation of parsing a float string is not easy.\n\t// We believe that the conservative approach is to call strconv.ParseFloat.\n\t// https://github.com/valyala/fasthttp/pull/1865\n\tres, err := strconv.ParseFloat(b2s(buf), 64)\n\tif res < 0 {\n\t\treturn -1, errors.New(\"negative input is invalid\")\n\t}\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\treturn res, err\n}\n\nvar (\n\terrEmptyHexNum    = errors.New(\"empty hex number\")\n\terrTooLargeHexNum = errors.New(\"too large hex number\")\n)\n\nfunc readHexInt(r *bufio.Reader) (int, error) {\n\tvar k, i, n int\n\tfor {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\tif err == io.EOF && i > 0 {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\treturn -1, err\n\t\t}\n\t\tk = int(hex2intTable[c])\n\t\tif k == 16 {\n\t\t\tif i == 0 {\n\t\t\t\treturn -1, errEmptyHexNum\n\t\t\t}\n\t\t\tif err := r.UnreadByte(); err != nil {\n\t\t\t\treturn -1, err\n\t\t\t}\n\t\t\treturn n, nil\n\t\t}\n\t\tif i >= maxHexIntChars {\n\t\t\treturn -1, errTooLargeHexNum\n\t\t}\n\t\tn = (n << 4) | k\n\t\ti++\n\t}\n}\n\nvar hexIntBufPool sync.Pool\n\nfunc writeHexInt(w *bufio.Writer, n int) error {\n\tif n < 0 {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: int must be positive\")\n\t}\n\n\tv := hexIntBufPool.Get()\n\tif v == nil {\n\t\tv = make([]byte, maxHexIntChars+1)\n\t}\n\tbuf := v.([]byte)\n\ti := len(buf) - 1\n\tfor {\n\t\tbuf[i] = lowerhex[n&0xf]\n\t\tn >>= 4\n\t\tif n == 0 {\n\t\t\tbreak\n\t\t}\n\t\ti--\n\t}\n\t_, err := w.Write(buf[i:])\n\thexIntBufPool.Put(v)\n\treturn err\n}\n\nconst (\n\tupperhex = \"0123456789ABCDEF\"\n\tlowerhex = \"0123456789abcdef\"\n)\n\nfunc lowercaseBytes(b []byte) {\n\tfor i := range b {\n\t\tp := &b[i]\n\t\t*p = toLowerTable[*p]\n\t}\n}\n\n// AppendUnquotedArg appends url-decoded src to dst and returns appended dst.\n//\n// dst may point to src. In this case src will be overwritten.\nfunc AppendUnquotedArg(dst, src []byte) []byte {\n\treturn decodeArgAppend(dst, src)\n}\n\n// AppendQuotedArg appends url-encoded src to dst and returns appended dst.\nfunc AppendQuotedArg(dst, src []byte) []byte {\n\tfor _, c := range src {\n\t\tswitch {\n\t\tcase c == ' ':\n\t\t\tdst = append(dst, '+')\n\t\tcase quotedArgShouldEscapeTable[int(c)] != 0:\n\t\t\tdst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])\n\t\tdefault:\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc appendQuotedPath(dst, src []byte) []byte {\n\t// Fix issue in https://github.com/golang/go/issues/11202\n\tif len(src) == 1 && src[0] == '*' {\n\t\treturn append(dst, '*')\n\t}\n\n\tfor _, c := range src {\n\t\tif quotedPathShouldEscapeTable[int(c)] != 0 {\n\t\t\tdst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])\n\t\t} else {\n\t\t\tdst = append(dst, c)\n\t\t}\n\t}\n\treturn dst\n}\n"
  },
  {
    "path": "bytesconv_32.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64 && !ppc64le && !riscv64 && !s390x\n\npackage fasthttp\n\nconst (\n\tmaxHexIntChars = 7\n)\n"
  },
  {
    "path": "bytesconv_32_test.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64 && !ppc64le && !riscv64 && !s390x\n\npackage fasthttp\n\nimport (\n\t\"testing\"\n)\n\nfunc TestWriteHexInt(t *testing.T) {\n\tt.Parallel()\n\n\ttestWriteHexInt(t, 0, \"0\")\n\ttestWriteHexInt(t, 1, \"1\")\n\ttestWriteHexInt(t, 0x123, \"123\")\n\ttestWriteHexInt(t, 0x7fffffff, \"7fffffff\")\n}\n\nfunc TestAppendUint(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendUint(t, 0)\n\ttestAppendUint(t, 123)\n\ttestAppendUint(t, 0x7fffffff)\n\n\tfor i := 0; i < 2345; i++ {\n\t\ttestAppendUint(t, i)\n\t}\n}\n\nfunc TestReadHexIntSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestReadHexIntSuccess(t, \"0\", 0)\n\ttestReadHexIntSuccess(t, \"fF\", 0xff)\n\ttestReadHexIntSuccess(t, \"00abc\", 0xabc)\n\ttestReadHexIntSuccess(t, \"7ffffff\", 0x7ffffff)\n\ttestReadHexIntSuccess(t, \"000\", 0)\n\ttestReadHexIntSuccess(t, \"1234ZZZ\", 0x1234)\n}\n\nfunc TestParseUintError32(t *testing.T) {\n\tt.Parallel()\n\n\t// Overflow by last digit: 2 ** 32 / 2 * 10 ** n\n\ttestParseUintError(t, \"2147483648\")\n\ttestParseUintError(t, \"21474836480\")\n\ttestParseUintError(t, \"214748364800\")\n}\n\nfunc TestParseUintSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseUintSuccess(t, \"0\", 0)\n\ttestParseUintSuccess(t, \"123\", 123)\n\ttestParseUintSuccess(t, \"123456789\", 123456789)\n\n\t// Max supported value: 2 ** 32 / 2 - 1\n\ttestParseUintSuccess(t, \"2147483647\", 2147483647)\n}\n"
  },
  {
    "path": "bytesconv_64.go",
    "content": "//go:build amd64 || arm64 || ppc64 || ppc64le || riscv64 || s390x\n\npackage fasthttp\n\nconst (\n\tmaxHexIntChars = 15\n)\n"
  },
  {
    "path": "bytesconv_64_test.go",
    "content": "//go:build amd64 || arm64 || ppc64 || ppc64le || riscv64 || s390x\n\npackage fasthttp\n\nimport (\n\t\"testing\"\n)\n\nfunc TestWriteHexInt(t *testing.T) {\n\tt.Parallel()\n\n\ttestWriteHexInt(t, 0, \"0\")\n\ttestWriteHexInt(t, 1, \"1\")\n\ttestWriteHexInt(t, 0x123, \"123\")\n\ttestWriteHexInt(t, 0x7fffffffffffffff, \"7fffffffffffffff\")\n}\n\nfunc TestAppendUint(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendUint(t, 0)\n\ttestAppendUint(t, 123)\n\ttestAppendUint(t, 0x7fffffffffffffff)\n\n\tfor i := range 2345 {\n\t\ttestAppendUint(t, i)\n\t}\n}\n\nfunc TestReadHexIntSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestReadHexIntSuccess(t, \"0\", 0)\n\ttestReadHexIntSuccess(t, \"fF\", 0xff)\n\ttestReadHexIntSuccess(t, \"00abc\", 0xabc)\n\ttestReadHexIntSuccess(t, \"7fffffff\", 0x7fffffff)\n\ttestReadHexIntSuccess(t, \"000\", 0)\n\ttestReadHexIntSuccess(t, \"1234ZZZ\", 0x1234)\n\ttestReadHexIntSuccess(t, \"7ffffffffffffff\", 0x7ffffffffffffff)\n}\n\nfunc TestParseUintError64(t *testing.T) {\n\tt.Parallel()\n\n\t// Overflow by last digit: 2 ** 64 / 2 * 10 ** n\n\ttestParseUintError(t, \"9223372036854775808\")\n\ttestParseUintError(t, \"92233720368547758080\")\n\ttestParseUintError(t, \"922337203685477580800\")\n}\n\nfunc TestParseUintSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseUintSuccess(t, \"0\", 0)\n\ttestParseUintSuccess(t, \"123\", 123)\n\ttestParseUintSuccess(t, \"1234567890\", 1234567890)\n\ttestParseUintSuccess(t, \"123456789012345678\", 123456789012345678)\n\n\t// Max supported value: 2 ** 64 / 2 - 1\n\ttestParseUintSuccess(t, \"9223372036854775807\", 9223372036854775807)\n}\n"
  },
  {
    "path": "bytesconv_table.go",
    "content": "package fasthttp\n\n// Code generated by go run bytesconv_table_gen.go; DO NOT EDIT.\n// See bytesconv_table_gen.go for more information about these tables.\n\nconst 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\"\nconst 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{|}~\\x7f\\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\"\nconst 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{|}~\\x7f\\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\"\nconst 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\"\nconst 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\"\nconst validHeaderFieldByteTable = \"\\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\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x01\\x01\\x00\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\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\\x00\\x00\\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\\x00\\x01\\x00\\x01\\x00\"\nconst validHeaderValueByteTable = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\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\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\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\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\"\nconst validMethodValueByteTable = \"\\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\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x01\\x01\\x00\\x01\\x01\\x00\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\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\\x00\\x00\\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\\x00\\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\\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\\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\\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\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"
  },
  {
    "path": "bytesconv_table_gen.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n)\n\nconst (\n\ttoLower = 'a' - 'A'\n)\n\nfunc main() {\n\thex2intTable := func() [256]byte {\n\t\tvar b [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(16)\n\t\t\tif i >= '0' && i <= '9' {\n\t\t\t\tc = byte(i) - '0'\n\t\t\t} else if i >= 'a' && i <= 'f' {\n\t\t\t\tc = byte(i) - 'a' + 10\n\t\t\t} else if i >= 'A' && i <= 'F' {\n\t\t\t\tc = byte(i) - 'A' + 10\n\t\t\t}\n\t\t\tb[i] = c\n\t\t}\n\t\treturn b\n\t}()\n\n\ttoLowerTable := func() [256]byte {\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(i)\n\t\t\tif c >= 'A' && c <= 'Z' {\n\t\t\t\tc += toLower\n\t\t\t}\n\t\t\ta[i] = c\n\t\t}\n\t\treturn a\n\t}()\n\n\ttoUpperTable := func() [256]byte {\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\tc := byte(i)\n\t\t\tif c >= 'a' && c <= 'z' {\n\t\t\t\tc -= toLower\n\t\t\t}\n\t\t\ta[i] = c\n\t\t}\n\t\treturn a\n\t}()\n\n\tquotedArgShouldEscapeTable := func() [256]byte {\n\t\t// According to RFC 3986 §2.3\n\t\tvar a [256]byte\n\t\tfor i := 0; i < 256; i++ {\n\t\t\ta[i] = 1\n\t\t}\n\n\t\t// ALPHA\n\t\tfor i := int('a'); i <= int('z'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\t\tfor i := int('A'); i <= int('Z'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\t// DIGIT\n\t\tfor i := int('0'); i <= int('9'); i++ {\n\t\t\ta[i] = 0\n\t\t}\n\n\t\t// Unreserved characters\n\t\tfor _, v := range `-_.~` {\n\t\t\ta[v] = 0\n\t\t}\n\n\t\treturn a\n\t}()\n\n\tquotedPathShouldEscapeTable := func() [256]byte {\n\t\t// The implementation here equal to net/url shouldEscape(s, encodePath)\n\t\t//\n\t\t// The RFC allows : @ & = + $ but saves / ; , for assigning\n\t\t// meaning to individual path segments. This package\n\t\t// only manipulates the path as a whole, so we allow those\n\t\t// last three as well. That leaves only ? to escape.\n\t\ta := quotedArgShouldEscapeTable\n\n\t\tfor _, v := range `$&+,/:;=@` {\n\t\t\ta[v] = 0\n\t\t}\n\n\t\treturn a\n\t}()\n\n\tvalidHeaderFieldByteTable := func() [128]byte {\n\t\t// Should match net/textproto's validHeaderFieldByte(c byte) bool\n\t\t// Defined by RFC 7230 and 9110:\n\t\t//\n\t\t//\theader-field   = field-name \":\" OWS field-value OWS\n\t\t//\tfield-name     = token\n\t\t//\ttchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" /\n\t\t//\t        \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n\t\t//\ttoken = 1*tchar\n\t\tvar table [128]byte\n\t\tfor c := 0; c < 128; c++ {\n\t\t\tif (c >= '0' && c <= '9') ||\n\t\t\t\t(c >= 'a' && c <= 'z') ||\n\t\t\t\t(c >= 'A' && c <= 'Z') ||\n\t\t\t\tc == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||\n\t\t\t\tc == '\\'' || c == '*' || c == '+' || c == '-' || c == '.' ||\n\t\t\t\tc == '^' || c == '_' || c == '`' || c == '|' || c == '~' {\n\t\t\t\ttable[c] = 1\n\t\t\t}\n\t\t}\n\t\treturn table\n\t}()\n\n\tvalidHeaderValueByteTable := func() [256]byte {\n\t\t// Should match net/textproto's validHeaderValueByte(c byte) bool\n\t\t// Defined by RFC 7230 and 9110:\n\t\t//\n\t\t//\tfield-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n\t\t//\tfield-vchar    = VCHAR / obs-text\n\t\t//\tobs-text       = %x80-FF\n\t\t//\n\t\t// RFC 5234:\n\t\t//\n\t\t//\tHTAB           =  %x09\n\t\t//\tSP             =  %x20\n\t\t//\tVCHAR          =  %x21-7E\n\t\tvar table [256]byte\n\t\tfor c := 0; c < 256; c++ {\n\t\t\tif (c >= 0x21 && c <= 0x7E) || // VCHAR\n\t\t\t\tc == 0x20 || // SP\n\t\t\t\tc == 0x09 || // HTAB\n\t\t\t\tc >= 0x80 { // obs-text\n\t\t\t\ttable[c] = 1\n\t\t\t}\n\t\t}\n\t\treturn table\n\t}()\n\n\tvalidMethodValueByteTable := [256]byte{\n\t\t/*\n\t\t\t\tSame as net/http\n\n\t\t\t     Method         = \"OPTIONS\"                ; Section 9.2\n\t\t\t                    | \"GET\"                    ; Section 9.3\n\t\t\t                    | \"HEAD\"                   ; Section 9.4\n\t\t\t                    | \"POST\"                   ; Section 9.5\n\t\t\t                    | \"PUT\"                    ; Section 9.6\n\t\t\t                    | \"DELETE\"                 ; Section 9.7\n\t\t\t                    | \"TRACE\"                  ; Section 9.8\n\t\t\t                    | \"CONNECT\"                ; Section 9.9\n\t\t\t                    | extension-method\n\t\t\t   extension-method = token\n\t\t\t     token          = 1*<any CHAR except CTLs or separators>\n\t\t*/\n\t\t'!':  1,\n\t\t'#':  1,\n\t\t'$':  1,\n\t\t'%':  1,\n\t\t'&':  1,\n\t\t'\\'': 1,\n\t\t'*':  1,\n\t\t'+':  1,\n\t\t'-':  1,\n\t\t'.':  1,\n\t\t'0':  1,\n\t\t'1':  1,\n\t\t'2':  1,\n\t\t'3':  1,\n\t\t'4':  1,\n\t\t'5':  1,\n\t\t'6':  1,\n\t\t'7':  1,\n\t\t'8':  1,\n\t\t'9':  1,\n\t\t'A':  1,\n\t\t'B':  1,\n\t\t'C':  1,\n\t\t'D':  1,\n\t\t'E':  1,\n\t\t'F':  1,\n\t\t'G':  1,\n\t\t'H':  1,\n\t\t'I':  1,\n\t\t'J':  1,\n\t\t'K':  1,\n\t\t'L':  1,\n\t\t'M':  1,\n\t\t'N':  1,\n\t\t'O':  1,\n\t\t'P':  1,\n\t\t'Q':  1,\n\t\t'R':  1,\n\t\t'S':  1,\n\t\t'T':  1,\n\t\t'U':  1,\n\t\t'W':  1,\n\t\t'V':  1,\n\t\t'X':  1,\n\t\t'Y':  1,\n\t\t'Z':  1,\n\t\t'^':  1,\n\t\t'_':  1,\n\t\t'`':  1,\n\t\t'a':  1,\n\t\t'b':  1,\n\t\t'c':  1,\n\t\t'd':  1,\n\t\t'e':  1,\n\t\t'f':  1,\n\t\t'g':  1,\n\t\t'h':  1,\n\t\t'i':  1,\n\t\t'j':  1,\n\t\t'k':  1,\n\t\t'l':  1,\n\t\t'm':  1,\n\t\t'n':  1,\n\t\t'o':  1,\n\t\t'p':  1,\n\t\t'q':  1,\n\t\t'r':  1,\n\t\t's':  1,\n\t\t't':  1,\n\t\t'u':  1,\n\t\t'v':  1,\n\t\t'w':  1,\n\t\t'x':  1,\n\t\t'y':  1,\n\t\t'z':  1,\n\t\t'|':  1,\n\t\t'~':  1,\n\t}\n\n\tw := bytes.NewBufferString(pre)\n\tfmt.Fprintf(w, \"const hex2intTable = %q\\n\", hex2intTable)\n\tfmt.Fprintf(w, \"const toLowerTable = %q\\n\", toLowerTable)\n\tfmt.Fprintf(w, \"const toUpperTable = %q\\n\", toUpperTable)\n\tfmt.Fprintf(w, \"const quotedArgShouldEscapeTable = %q\\n\", quotedArgShouldEscapeTable)\n\tfmt.Fprintf(w, \"const quotedPathShouldEscapeTable = %q\\n\", quotedPathShouldEscapeTable)\n\tfmt.Fprintf(w, \"const validHeaderFieldByteTable = %q\\n\", validHeaderFieldByteTable)\n\tfmt.Fprintf(w, \"const validHeaderValueByteTable = %q\\n\", validHeaderValueByteTable)\n\tfmt.Fprintf(w, \"const validMethodValueByteTable = %q\\n\", validMethodValueByteTable)\n\n\tif err := os.WriteFile(\"bytesconv_table.go\", w.Bytes(), 0o660); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nconst pre = `package fasthttp\n\n// Code generated by go run bytesconv_table_gen.go; DO NOT EDIT.\n// See bytesconv_table_gen.go for more information about these tables.\n\n`\n"
  },
  {
    "path": "bytesconv_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"html\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nfunc TestAppendQuotedArg(t *testing.T) {\n\tt.Parallel()\n\n\t// Sync with url.QueryEscape\n\tallcases := make([]byte, 256)\n\tfor i := range 256 {\n\t\tallcases[i] = byte(i)\n\t}\n\tres := string(AppendQuotedArg(nil, allcases))\n\texpect := url.QueryEscape(string(allcases))\n\tif res != expect {\n\t\tt.Fatalf(\"unexpected string %q. Expecting %q.\", res, expect)\n\t}\n}\n\nfunc TestAppendHTMLEscape(t *testing.T) {\n\tt.Parallel()\n\n\t// Sync with html.EscapeString\n\tallcases := make([]byte, 256)\n\tfor i := range 256 {\n\t\tallcases[i] = byte(i)\n\t}\n\tres := string(AppendHTMLEscape(nil, string(allcases)))\n\texpect := html.EscapeString(string(allcases))\n\tif res != expect {\n\t\tt.Fatalf(\"unexpected string %q. Expecting %q.\", res, expect)\n\t}\n\n\ttestAppendHTMLEscape(t, \"\", \"\")\n\ttestAppendHTMLEscape(t, \"<\", \"&lt;\")\n\ttestAppendHTMLEscape(t, \"a\", \"a\")\n\ttestAppendHTMLEscape(t, `><\"''`, \"&gt;&lt;&#34;&#39;&#39;\")\n\ttestAppendHTMLEscape(t, \"fo<b x='ss'>a</b>xxx\", \"fo&lt;b x=&#39;ss&#39;&gt;a&lt;/b&gt;xxx\")\n}\n\nfunc testAppendHTMLEscape(t *testing.T, s, expectedS string) {\n\tbuf := AppendHTMLEscapeBytes(nil, []byte(s))\n\tif string(buf) != expectedS {\n\t\tt.Fatalf(\"unexpected html-escaped string %q. Expecting %q. Original string %q\", buf, expectedS, s)\n\t}\n}\n\nfunc TestParseIPv4(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseIPv4(t, net.IP{0}, \"0.0.0.0\", true)\n\ttestParseIPv4(t, nil, \"0.0.0.0\", true)\n\ttestParseIPv4(t, net.IP{0, 0, 0, 0, 0}, \"0.0.0.0\", true)\n\ttestParseIPv4(t, nil, \"255.255.255.255\", true)\n\ttestParseIPv4(t, nil, \"123.45.67.89\", true)\n\n\t// ipv6 shouldn't work\n\ttestParseIPv4(t, nil, \"2001:4860:0:2001::68\", false)\n\n\t// invalid ip\n\ttestParseIPv4(t, nil, \"\", false)\n\ttestParseIPv4(t, nil, \"foobar\", false)\n\ttestParseIPv4(t, nil, \"1.2.3\", false)\n\ttestParseIPv4(t, nil, \"123.456.789.11\", false)\n\ttestParseIPv4(t, nil, \"b.1.2.3\", false)\n\ttestParseIPv4(t, nil, \"1.2.3.b\", false)\n\ttestParseIPv4(t, nil, \"1.2.3.456\", false)\n}\n\nfunc testParseIPv4(t *testing.T, dst net.IP, ipStr string, isValid bool) {\n\tip, err := ParseIPv4(dst, []byte(ipStr))\n\tif isValid {\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when parsing ip %q: %v\", ipStr, err)\n\t\t}\n\t\ts := string(AppendIPv4(nil, ip))\n\t\tif s != ipStr {\n\t\t\tt.Fatalf(\"unexpected ip parsed %q. Expecting %q\", s, ipStr)\n\t\t}\n\t} else if err == nil {\n\t\tt.Fatalf(\"expecting error when parsing ip %q\", ipStr)\n\t}\n}\n\nfunc TestAppendIPv4(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendIPv4(t, \"0.0.0.0\", true)\n\ttestAppendIPv4(t, \"127.0.0.1\", true)\n\ttestAppendIPv4(t, \"8.8.8.8\", true)\n\ttestAppendIPv4(t, \"123.45.67.89\", true)\n\n\t// ipv6 shouldn't work\n\ttestAppendIPv4(t, \"2001:4860:0:2001::68\", false)\n}\n\nfunc testAppendIPv4(t *testing.T, ipStr string, isValid bool) {\n\tip := net.ParseIP(ipStr)\n\tif ip == nil {\n\t\tt.Fatalf(\"cannot parse ip %q\", ipStr)\n\t}\n\ts := string(AppendIPv4(nil, ip))\n\tif isValid {\n\t\tif s != ipStr {\n\t\t\tt.Fatalf(\"unexpected ip %q. Expecting %q\", s, ipStr)\n\t\t}\n\t} else {\n\t\tipStr = \"non-v4 ip passed to AppendIPv4\"\n\t\tif s != ipStr {\n\t\t\tt.Fatalf(\"unexpected ip %q. Expecting %q\", s, ipStr)\n\t\t}\n\t}\n}\n\nfunc testAppendUint(t *testing.T, n int) {\n\texpectedS := strconv.Itoa(n)\n\ts := AppendUint(nil, n)\n\tif string(s) != expectedS {\n\t\tt.Fatalf(\"unexpected uint %q. Expecting %q. n=%d\", s, expectedS, n)\n\t}\n}\n\nfunc testWriteHexInt(t *testing.T, n int, expectedS string) {\n\tvar w bytebufferpool.ByteBuffer\n\tbw := bufio.NewWriter(&w)\n\tif err := writeHexInt(bw, n); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing hex %x: %v\", n, err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing hex %x: %v\", n, err)\n\t}\n\ts := string(w.B)\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected hex after writing %q. Expected %q\", s, expectedS)\n\t}\n}\n\nfunc TestReadHexIntError(t *testing.T) {\n\tt.Parallel()\n\n\ttestReadHexIntError(t, \"\")\n\ttestReadHexIntError(t, \"ZZZ\")\n\ttestReadHexIntError(t, \"-123\")\n\ttestReadHexIntError(t, \"+434\")\n}\n\nfunc testReadHexIntError(t *testing.T, s string) {\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tn, err := readHexInt(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error when reading hex int %q\", s)\n\t}\n\tif n >= 0 {\n\t\tt.Fatalf(\"unexpected hex value read %d for hex int %q. must be negative\", n, s)\n\t}\n}\n\nfunc testReadHexIntSuccess(t *testing.T, s string, expectedN int) {\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tn, err := readHexInt(br)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. s=%q\", err, s)\n\t}\n\tif n != expectedN {\n\t\tt.Fatalf(\"unexpected hex int %d. Expected %d. s=%q\", n, expectedN, s)\n\t}\n}\n\nfunc TestAppendHTTPDate(t *testing.T) {\n\tt.Parallel()\n\n\td := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)\n\ts := string(AppendHTTPDate(nil, d))\n\texpectedS := \"Tue, 10 Nov 2009 23:00:00 GMT\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected date %q. Expecting %q\", s, expectedS)\n\t}\n\n\tb := []byte(\"prefix\")\n\ts = string(AppendHTTPDate(b, d))\n\tif s[:len(b)] != string(b) {\n\t\tt.Fatalf(\"unexpected prefix %q. Expecting %q\", s[:len(b)], b)\n\t}\n\ts = s[len(b):]\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected date %q. Expecting %q\", s, expectedS)\n\t}\n}\n\nfunc TestParseUintError(t *testing.T) {\n\tt.Parallel()\n\n\t// empty string\n\ttestParseUintError(t, \"\")\n\n\t// negative value\n\ttestParseUintError(t, \"-123\")\n\n\t// non-num\n\ttestParseUintError(t, \"foobar234\")\n\n\t// non-num chars at the end\n\ttestParseUintError(t, \"123w\")\n\n\t// floating point num\n\ttestParseUintError(t, \"1234.545\")\n\n\t// too big num\n\ttestParseUintError(t, \"12345678901234567890\")\n\ttestParseUintError(t, \"1234567890123456789012\")\n}\n\nfunc TestParseUfloatSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseUfloatSuccess(t, \"0\", 0)\n\ttestParseUfloatSuccess(t, \"1.\", 1.)\n\ttestParseUfloatSuccess(t, \".1\", 0.1)\n\ttestParseUfloatSuccess(t, \"123.456\", 123.456)\n\ttestParseUfloatSuccess(t, \"123\", 123)\n\ttestParseUfloatSuccess(t, \"1234e2\", 1234e2)\n\ttestParseUfloatSuccess(t, \"1234E-5\", 1234e-5)\n\ttestParseUfloatSuccess(t, \"1.234e+3\", 1.234e+3)\n\ttestParseUfloatSuccess(t, \"1234e23\", 1234e23)\n\ttestParseUfloatSuccess(t, \"1.234e+32\", 1.234e+32)\n\ttestParseUfloatSuccess(t, \"123456789123456789.987654321\", 123456789123456789.987654321)\n\ttestParseUfloatSuccess(t, \"1.23456789123456789987654321\", 1.23456789123456789987654321)\n\ttestParseUfloatSuccess(t, \"340282346638528859811704183484516925440\", 340282346638528859811704183484516925440)\n\ttestParseUfloatSuccess(t, \"00000000000000000001\", 1)\n}\n\nfunc TestParseUfloatError(t *testing.T) {\n\tt.Parallel()\n\n\t// empty num\n\ttestParseUfloatError(t, \"\")\n\n\t// negative num\n\ttestParseUfloatError(t, \"-123.53\")\n\n\t// non-num chars\n\ttestParseUfloatError(t, \"123sdfsd\")\n\ttestParseUfloatError(t, \"sdsf234\")\n\ttestParseUfloatError(t, \"sdfdf\")\n\n\t// non-num chars in exponent\n\ttestParseUfloatError(t, \"123e3s\")\n\ttestParseUfloatError(t, \"12.3e-op\")\n\ttestParseUfloatError(t, \"123E+SS5\")\n\n\t// duplicate point\n\ttestParseUfloatError(t, \"1.3.4\")\n\n\t// duplicate exponent\n\ttestParseUfloatError(t, \"123e5e6\")\n\n\t// missing exponent\n\ttestParseUfloatError(t, \"123534e\")\n\n\t// negative number\n\ttestParseUfloatError(t, \"-1\")\n\ttestParseUfloatError(t, \"-Inf\")\n}\n\nfunc testParseUfloatError(t *testing.T, s string) {\n\tn, err := ParseUfloat([]byte(s))\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when parsing %q. obtained %f\", s, n)\n\t}\n\tif n >= 0 {\n\t\tt.Fatalf(\"Expecting negative num instead of %f when parsing %q\", n, s)\n\t}\n}\n\nfunc testParseUfloatSuccess(t *testing.T, s string, expectedF float64) {\n\tf, err := ParseUfloat([]byte(s))\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing %q: %v\", s, err)\n\t}\n\tdelta := f - expectedF\n\tif delta < 0 {\n\t\tdelta = -delta\n\t}\n\tif delta > expectedF*1e-10 {\n\t\tt.Fatalf(\"Unexpected value when parsing %q: %f. Expected %f\", s, f, expectedF)\n\t}\n}\n\nfunc testParseUintError(t *testing.T, s string) {\n\tn, err := ParseUint([]byte(s))\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when parsing %q. obtained %d\", s, n)\n\t}\n\tif n >= 0 {\n\t\tt.Fatalf(\"Unexpected n=%d when parsing %q. Expected negative num\", n, s)\n\t}\n}\n\nfunc testParseUintSuccess(t *testing.T, s string, expectedN int) {\n\tn, err := ParseUint([]byte(s))\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing %q: %v\", s, err)\n\t}\n\tif n != expectedN {\n\t\tt.Fatalf(\"Unexpected value %d. Expected %d. num=%q\", n, expectedN, s)\n\t}\n}\n\nfunc TestAppendUnquotedArg(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendUnquotedArg(t, \"\", \"\")\n\ttestAppendUnquotedArg(t, \"abc\", \"abc\")\n\ttestAppendUnquotedArg(t, \"тест.abc\", \"тест.abc\")\n\ttestAppendUnquotedArg(t, \"%D1%82%D0%B5%D1%81%D1%82%20%=&;:\", \"тест %=&;:\")\n}\n\nfunc testAppendUnquotedArg(t *testing.T, s, expectedS string) {\n\t// test appending to nil\n\tresult := AppendUnquotedArg(nil, []byte(s))\n\tif string(result) != expectedS {\n\t\tt.Fatalf(\"Unexpected AppendUnquotedArg(%q)=%q, want %q\", s, result, expectedS)\n\t}\n\n\t// test appending to prefix\n\tprefix := \"prefix\"\n\tdst := []byte(prefix)\n\tdst = AppendUnquotedArg(dst, []byte(s))\n\tif !bytes.HasPrefix(dst, []byte(prefix)) {\n\t\tt.Fatalf(\"Unexpected prefix for AppendUnquotedArg(%q)=%q, want %q\", s, dst, prefix)\n\t}\n\tresult = dst[len(prefix):]\n\tif string(result) != expectedS {\n\t\tt.Fatalf(\"Unexpected AppendUnquotedArg(%q)=%q, want %q\", s, result, expectedS)\n\t}\n\n\t// test in-place appending\n\tresult = []byte(s)\n\tresult = AppendUnquotedArg(result[:0], result)\n\tif string(result) != expectedS {\n\t\tt.Fatalf(\"Unexpected AppendUnquotedArg(%q)=%q, want %q\", s, result, expectedS)\n\t}\n\n\t// verify AppendQuotedArg <-> AppendUnquotedArg conversion\n\tquotedS := AppendQuotedArg(nil, []byte(s))\n\tunquotedS := AppendUnquotedArg(nil, quotedS)\n\tif s != string(unquotedS) {\n\t\tt.Fatalf(\"Unexpected AppendUnquotedArg(AppendQuotedArg(%q))=%q, want %q\", s, unquotedS, s)\n\t}\n}\n"
  },
  {
    "path": "bytesconv_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"html\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nfunc BenchmarkAppendHTMLEscape(b *testing.B) {\n\tsOrig := \"<b>foobarbazxxxyyyzzz</b>\"\n\tsExpected := string(AppendHTMLEscape(nil, sOrig))\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar buf []byte\n\t\tfor pb.Next() {\n\t\t\tfor range 10 {\n\t\t\t\tbuf = AppendHTMLEscape(buf[:0], sOrig)\n\t\t\t\tif string(buf) != sExpected {\n\t\t\t\t\tb.Fatalf(\"unexpected escaped string: %q. Expecting %q\", buf, sExpected)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkHTMLEscapeString(b *testing.B) {\n\tsOrig := \"<b>foobarbazxxxyyyzzz</b>\"\n\tsExpected := html.EscapeString(sOrig)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar s string\n\t\tfor pb.Next() {\n\t\t\tfor range 10 {\n\t\t\t\ts = html.EscapeString(sOrig)\n\t\t\t\tif s != sExpected {\n\t\t\t\t\tb.Fatalf(\"unexpected escaped string: %q. Expecting %q\", s, sExpected)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkParseIPv4(b *testing.B) {\n\tipStr := []byte(\"123.145.167.189\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar ip net.IP\n\t\tvar err error\n\t\tfor pb.Next() {\n\t\t\tip, err = ParseIPv4(ip, ipStr)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkAppendIPv4(b *testing.B) {\n\tip := net.ParseIP(\"123.145.167.189\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar buf []byte\n\t\tfor pb.Next() {\n\t\t\tbuf = AppendIPv4(buf[:0], ip)\n\t\t}\n\t})\n}\n\nfunc BenchmarkWriteHexInt(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar w bytebufferpool.ByteBuffer\n\t\tbw := bufio.NewWriter(&w)\n\t\ti := 0\n\t\tfor pb.Next() {\n\t\t\twriteHexInt(bw, i) //nolint:errcheck\n\t\t\ti++\n\t\t\tif i > 0x7fffffff {\n\t\t\t\ti = 0\n\t\t\t}\n\t\t\tw.Reset()\n\t\t\tbw.Reset(&w)\n\t\t}\n\t})\n}\n\nfunc BenchmarkParseUint(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tbuf := []byte(\"1234567\")\n\t\tfor pb.Next() {\n\t\t\tn, err := ParseUint(buf)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif n != 1234567 {\n\t\t\t\tb.Fatalf(\"unexpected result: %d. Expecting %q\", n, buf)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkAppendUint(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar buf []byte\n\t\ti := 0\n\t\tfor pb.Next() {\n\t\t\tbuf = AppendUint(buf[:0], i)\n\t\t\ti++\n\t\t\tif i > 0x7fffffff {\n\t\t\t\ti = 0\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkLowercaseBytesNoop(b *testing.B) {\n\tsrc := []byte(\"foobarbaz_lowercased_all\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\ts := make([]byte, len(src))\n\t\tfor pb.Next() {\n\t\t\tcopy(s, src)\n\t\t\tlowercaseBytes(s)\n\t\t}\n\t})\n}\n\nfunc BenchmarkLowercaseBytesAll(b *testing.B) {\n\tsrc := []byte(\"FOOBARBAZ_UPPERCASED_ALL\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\ts := make([]byte, len(src))\n\t\tfor pb.Next() {\n\t\t\tcopy(s, src)\n\t\t\tlowercaseBytes(s)\n\t\t}\n\t})\n}\n\nfunc BenchmarkLowercaseBytesMixed(b *testing.B) {\n\tsrc := []byte(\"Foobarbaz_Uppercased_Mix\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\ts := make([]byte, len(src))\n\t\tfor pb.Next() {\n\t\t\tcopy(s, src)\n\t\t\tlowercaseBytes(s)\n\t\t}\n\t})\n}\n\nfunc BenchmarkAppendUnquotedArgFastPath(b *testing.B) {\n\tsrc := []byte(\"foobarbaz no quoted chars fdskjsdf jklsdfdfskljd;aflskjdsaf fdsklj fsdkj fsdl kfjsdlk jfsdklj fsdfsdf sdfkflsd\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar dst []byte\n\t\tfor pb.Next() {\n\t\t\tdst = AppendUnquotedArg(dst[:0], src)\n\t\t}\n\t})\n}\n\nfunc BenchmarkAppendUnquotedArgSlowPath(b *testing.B) {\n\tsrc := []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\")\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar dst []byte\n\t\tfor pb.Next() {\n\t\t\tdst = AppendUnquotedArg(dst[:0], src)\n\t\t}\n\t})\n}\n\nfunc BenchmarkParseUfloat(b *testing.B) {\n\tsrc := [][]byte{\n\t\t[]byte(\"0\"),\n\t\t[]byte(\"1234566789.\"),\n\t\t[]byte(\".1234556778\"),\n\t\t[]byte(\"123.456\"),\n\t\t[]byte(\"123456789\"),\n\t\t[]byte(\"1234e23\"),\n\t\t[]byte(\"1234E-51\"),\n\t\t[]byte(\"1.234e+32\"),\n\t\t[]byte(\"123456789123456789.987654321\"),\n\t}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tfor i := range src {\n\t\t\t\t_, err := ParseUfloat(src[i])\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "client.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n// Do performs the given http request and fills the given http response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc Do(req *Request, resp *Response) error {\n\treturn defaultClient.Do(req, resp)\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned during\n// the given timeout.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc DoTimeout(req *Request, resp *Response, timeout time.Duration) error {\n\treturn defaultClient.DoTimeout(req, resp, timeout)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned until\n// the given deadline.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\treturn defaultClient.DoDeadline(req, resp, deadline)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc DoRedirects(req *Request, resp *Response, maxRedirectsCount int) error {\n\tif defaultClient.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\t_, _, err := doRequestFollowRedirects(req, resp, req.URI().String(), maxRedirectsCount, &defaultClient)\n\treturn err\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc Get(dst []byte, url string) (statusCode int, body []byte, err error) {\n\treturn defaultClient.Get(dst, url)\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\nfunc GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {\n\treturn defaultClient.GetTimeout(dst, url, timeout)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\nfunc GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {\n\treturn defaultClient.GetDeadline(dst, url, deadline)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {\n\treturn defaultClient.Post(dst, url, postArgs)\n}\n\nvar defaultClient Client\n\n// Client implements http client.\n//\n// Copying Client by value is prohibited. Create new instance instead.\n//\n// It is safe calling Client methods from concurrently running goroutines.\n//\n// The fields of a Client should not be changed while it is in use.\ntype Client struct {\n\tnoCopy noCopy\n\n\treaderPool sync.Pool\n\twriterPool sync.Pool\n\n\t// Transport defines a transport-like mechanism that wraps every request/response.\n\tTransport RoundTripper\n\n\t// Callback for establishing new connections to hosts.\n\t//\n\t// Default DialTimeout is used if not set.\n\tDialTimeout DialFuncWithTimeout\n\n\t// Callback for establishing new connections to hosts.\n\t//\n\t// Note that if Dial is set instead of DialTimeout, Dial will ignore Request timeout.\n\t// If you want the tcp dial process to account for request timeouts, use DialTimeout instead.\n\t//\n\t// If not set, DialTimeout is used.\n\tDial DialFunc\n\n\t// TLS config for https connections.\n\t//\n\t// Default TLS config is used if not set.\n\tTLSConfig *tls.Config\n\n\t// RetryIf controls whether a retry should be attempted after an error.\n\t//\n\t// By default will use isIdempotent function.\n\t//\n\t// Deprecated: Use RetryIfErr instead.\n\t// This field is only effective when the `RetryIfErr` field is not set.\n\tRetryIf RetryIfFunc\n\n\t// When the client encounters an error during a request, the behavior—whether to retry\n\t// and whether to reset the request timeout—should be determined\n\t// based on the return value of this field.\n\t// This field is only effective within the range of MaxIdemponentCallAttempts.\n\tRetryIfErr RetryIfErrFunc\n\n\t// ConfigureClient configures the fasthttp.HostClient.\n\tConfigureClient func(hc *HostClient) error\n\n\tm  map[string]*HostClient\n\tms map[string]*HostClient\n\n\t// Client name. Used in User-Agent request header.\n\t//\n\t// Default client name is used if not set.\n\tName string\n\n\t// Maximum number of connections per each host which may be established.\n\t//\n\t// DefaultMaxConnsPerHost is used if not set.\n\tMaxConnsPerHost int\n\n\t// Idle keep-alive connections are closed after this duration.\n\t//\n\t// By default idle connections are closed\n\t// after DefaultMaxIdleConnDuration.\n\tMaxIdleConnDuration time.Duration\n\n\t// Keep-alive connections are closed after this duration.\n\t//\n\t// By default connection duration is unlimited.\n\tMaxConnDuration time.Duration\n\n\t// Maximum number of attempts for idempotent calls.\n\t//\n\t// DefaultMaxIdemponentCallAttempts is used if not set.\n\tMaxIdemponentCallAttempts int\n\n\t// Per-connection buffer size for responses' reading.\n\t// This also limits the maximum header size.\n\t//\n\t// Default buffer size is used if 0.\n\tReadBufferSize int\n\n\t// Per-connection buffer size for requests' writing.\n\t//\n\t// Default buffer size is used if 0.\n\tWriteBufferSize int\n\n\t// Maximum duration for full response reading (including body).\n\t//\n\t// By default response read timeout is unlimited.\n\tReadTimeout time.Duration\n\n\t// Maximum duration for full request writing (including body).\n\t//\n\t// By default request write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\t// Maximum response body size.\n\t//\n\t// The client returns ErrBodyTooLarge if this limit is greater than 0\n\t// and response body is greater than the limit.\n\t//\n\t// By default response body size is unlimited.\n\tMaxResponseBodySize int\n\n\t// Maximum duration for waiting for a free connection.\n\t//\n\t// By default will not waiting, return ErrNoFreeConns immediately.\n\tMaxConnWaitTimeout time.Duration\n\n\t// Connection pool strategy. Can be either LIFO or FIFO (default).\n\tConnPoolStrategy ConnPoolStrategyType\n\n\tmLock sync.RWMutex\n\tmOnce sync.Once\n\n\t// NoDefaultUserAgentHeader when set to true, causes the default\n\t// User-Agent header to be excluded from the Request.\n\tNoDefaultUserAgentHeader bool\n\n\t// Attempt to connect to both ipv4 and ipv6 addresses if set to true.\n\t//\n\t// This option is used only if default TCP dialer is used,\n\t// i.e. if Dial is blank.\n\t//\n\t// By default client connects only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\tDialDualStack bool\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive\n\t// header names. See https://github.com/valyala/fasthttp/issues/57\n\t// for details.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// Path values are sent as-is without normalization.\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n\n\t// StreamResponseBody enables response body streaming.\n\tStreamResponseBody bool\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc (c *Client) Get(dst []byte, url string) (statusCode int, body []byte, err error) {\n\treturn clientGetURL(dst, url, c)\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\nfunc (c *Client) GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {\n\treturn clientGetURLTimeout(dst, url, timeout, c)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\nfunc (c *Client) GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {\n\treturn clientGetURLDeadline(dst, url, deadline, c)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {\n\treturn clientPostURL(dst, url, postArgs, c)\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned during\n// the given timeout.\n// Immediately returns ErrTimeout if timeout value is negative.\n//\n// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {\n\treq.timeout = timeout\n\tif req.timeout <= 0 {\n\t\treturn ErrTimeout\n\t}\n\treturn c.Do(req, resp)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned until\n// the given deadline.\n// Immediately returns ErrTimeout if the deadline has already been reached.\n//\n// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\treq.timeout = time.Until(deadline)\n\tif req.timeout <= 0 {\n\t\treturn ErrTimeout\n\t}\n\treturn c.Do(req, resp)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) DoRedirects(req *Request, resp *Response, maxRedirectsCount int) error {\n\tif c.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\t_, _, err := doRequestFollowRedirects(req, resp, req.URI().String(), maxRedirectsCount, c)\n\treturn err\n}\n\n// Do performs the given http request and fills the given http response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *Client) Do(req *Request, resp *Response) error {\n\turi := req.URI()\n\tif uri == nil {\n\t\treturn ErrorInvalidURI\n\t}\n\n\thost := uri.Host()\n\n\tif bytes.ContainsRune(host, ',') {\n\t\treturn fmt.Errorf(\"invalid host %q. Use HostClient for multiple hosts\", host)\n\t}\n\n\tisTLS := false\n\tif uri.isHTTPS() {\n\t\tisTLS = true\n\t} else if !uri.isHTTP() {\n\t\treturn fmt.Errorf(\"unsupported protocol %q. http and https are supported\", uri.Scheme())\n\t}\n\n\tc.mOnce.Do(func() {\n\t\tc.m = make(map[string]*HostClient)\n\t\tc.ms = make(map[string]*HostClient)\n\t})\n\thc, err := c.hostClient(host, isTLS)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tatomic.AddInt32(&hc.pendingClientRequests, 1)\n\tdefer atomic.AddInt32(&hc.pendingClientRequests, -1)\n\treturn hc.Do(req, resp)\n}\n\nfunc (c *Client) hostClient(host []byte, isTLS bool) (*HostClient, error) {\n\tm := c.m\n\tif isTLS {\n\t\tm = c.ms\n\t}\n\n\tc.mLock.RLock()\n\thc, exist := m[string(host)]\n\tc.mLock.RUnlock()\n\tif exist {\n\t\treturn hc, nil\n\t}\n\tc.mLock.Lock()\n\tdefer c.mLock.Unlock()\n\thc, exist = m[string(host)]\n\tif exist {\n\t\treturn hc, nil\n\t}\n\thc = &HostClient{\n\t\tAddr:                          AddMissingPort(string(host), isTLS),\n\t\tTransport:                     c.Transport,\n\t\tName:                          c.Name,\n\t\tNoDefaultUserAgentHeader:      c.NoDefaultUserAgentHeader,\n\t\tDial:                          c.Dial,\n\t\tDialTimeout:                   c.DialTimeout,\n\t\tDialDualStack:                 c.DialDualStack,\n\t\tIsTLS:                         isTLS,\n\t\tTLSConfig:                     c.TLSConfig,\n\t\tMaxConns:                      c.MaxConnsPerHost,\n\t\tMaxIdleConnDuration:           c.MaxIdleConnDuration,\n\t\tMaxConnDuration:               c.MaxConnDuration,\n\t\tMaxIdemponentCallAttempts:     c.MaxIdemponentCallAttempts,\n\t\tReadBufferSize:                c.ReadBufferSize,\n\t\tWriteBufferSize:               c.WriteBufferSize,\n\t\tReadTimeout:                   c.ReadTimeout,\n\t\tWriteTimeout:                  c.WriteTimeout,\n\t\tMaxResponseBodySize:           c.MaxResponseBodySize,\n\t\tDisableHeaderNamesNormalizing: c.DisableHeaderNamesNormalizing,\n\t\tDisablePathNormalizing:        c.DisablePathNormalizing,\n\t\tMaxConnWaitTimeout:            c.MaxConnWaitTimeout,\n\t\tRetryIf:                       c.RetryIf,\n\t\tRetryIfErr:                    c.RetryIfErr,\n\t\tConnPoolStrategy:              c.ConnPoolStrategy,\n\t\tStreamResponseBody:            c.StreamResponseBody,\n\t\tclientReaderPool:              &c.readerPool,\n\t\tclientWriterPool:              &c.writerPool,\n\t}\n\n\tif c.ConfigureClient != nil {\n\t\tif err := c.ConfigureClient(hc); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tm[string(host)] = hc\n\tif len(m) == 1 {\n\t\tgo c.mCleaner(m)\n\t}\n\treturn hc, nil\n}\n\n// CloseIdleConnections closes any connections which were previously\n// connected from previous requests but are now sitting idle in a\n// \"keep-alive\" state. It does not interrupt any connections currently\n// in use.\nfunc (c *Client) CloseIdleConnections() {\n\tc.mLock.RLock()\n\tfor _, v := range c.m {\n\t\tv.CloseIdleConnections()\n\t}\n\tfor _, v := range c.ms {\n\t\tv.CloseIdleConnections()\n\t}\n\tc.mLock.RUnlock()\n}\n\nfunc (c *Client) mCleaner(m map[string]*HostClient) {\n\tmustStop := false\n\n\tsleep := c.MaxIdleConnDuration\n\tif sleep < time.Second {\n\t\tsleep = time.Second\n\t} else if sleep > 10*time.Second {\n\t\tsleep = 10 * time.Second\n\t}\n\n\tfor {\n\t\ttime.Sleep(sleep)\n\t\tc.mLock.Lock()\n\t\tfor k, v := range m {\n\t\t\tv.connsLock.Lock()\n\t\t\tif v.connsCount == 0 && atomic.LoadInt32(&v.pendingClientRequests) == 0 {\n\t\t\t\tdelete(m, k)\n\t\t\t}\n\t\t\tv.connsLock.Unlock()\n\t\t}\n\t\tif len(m) == 0 {\n\t\t\tmustStop = true\n\t\t}\n\t\tc.mLock.Unlock()\n\n\t\tif mustStop {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// DefaultMaxConnsPerHost is the maximum number of concurrent connections\n// http client may establish per host by default (i.e. if\n// Client.MaxConnsPerHost isn't set).\nconst DefaultMaxConnsPerHost = 512\n\n// DefaultMaxIdleConnDuration is the default duration before idle keep-alive\n// connection is closed.\nconst DefaultMaxIdleConnDuration = 10 * time.Second\n\n// DefaultMaxIdemponentCallAttempts is the default idempotent calls attempts count.\nconst DefaultMaxIdemponentCallAttempts = 5\n\n// DialFunc must establish connection to addr.\n//\n// There is no need in establishing TLS (SSL) connection for https.\n// The client automatically converts connection to TLS\n// if HostClient.IsTLS is set.\n//\n// TCP address passed to DialFunc always contains host and port.\n// Example TCP addr values:\n//\n//   - foobar.com:80\n//   - foobar.com:443\n//   - foobar.com:8080\ntype DialFunc func(addr string) (net.Conn, error)\n\n// DialFuncWithTimeout must establish connection to addr.\n// Unlike DialFunc, it also accepts a timeout.\n//\n// There is no need in establishing TLS (SSL) connection for https.\n// The client automatically converts connection to TLS\n// if HostClient.IsTLS is set.\n//\n// TCP address passed to DialFuncWithTimeout always contains host and port.\n// Example TCP addr values:\n//\n//   - foobar.com:80\n//   - foobar.com:443\n//   - foobar.com:8080\ntype DialFuncWithTimeout func(addr string, timeout time.Duration) (net.Conn, error)\n\n// RetryIfFunc defines the signature of the retry if function.\n// Request argument passed to RetryIfFunc, if there are any request errors.\ntype RetryIfFunc func(request *Request) bool\n\n// RetryIfErrFunc defines an interface used for implementing the following functionality:\n// When the client encounters an error during a request, the behavior—whether to retry\n// and whether to reset the request timeout—should be determined\n// based on the return value of this interface.\n//\n// attempt indicates which attempt the current retry is due to a failure of.\n// The first request counts as the first attempt.\n//\n// err represents the error encountered while attempting the `attempts`-th request.\n//\n// resetTimeout indicates whether to reuse the `Request`'s timeout as the timeout interval,\n// rather than using the timeout after subtracting the time spent on previous failed requests.\n// This return value is meaningful only when you use `Request.SetTimeout`, `DoTimeout`, or `DoDeadline`.\n//\n// retry indicates whether to retry the current request. If it is false,\n// the request function will immediately return with the `err`.\ntype RetryIfErrFunc func(request *Request, attempts int, err error) (resetTimeout bool, retry bool)\n\n// RoundTripper wraps every request/response.\ntype RoundTripper interface {\n\tRoundTrip(hc *HostClient, req *Request, resp *Response) (retry bool, err error)\n}\n\n// ConnPoolStrategyType define strategy of connection pool enqueue/dequeue.\ntype ConnPoolStrategyType int\n\nconst (\n\tFIFO ConnPoolStrategyType = iota\n\tLIFO\n)\n\n// HostClient balances http requests among hosts listed in Addr.\n//\n// HostClient may be used for balancing load among multiple upstream hosts.\n// While multiple addresses passed to HostClient.Addr may be used for balancing\n// load among them, it would be better using LBClient instead, since HostClient\n// may unevenly balance load among upstream hosts.\n//\n// It is forbidden copying HostClient instances. Create new instances instead.\n//\n// It is safe calling HostClient methods from concurrently running goroutines.\ntype HostClient struct {\n\tnoCopy noCopy\n\n\treaderPool sync.Pool\n\twriterPool sync.Pool\n\n\t// Transport defines a transport-like mechanism that wraps every request/response.\n\tTransport RoundTripper\n\n\t// Callback for establishing new connections to hosts.\n\t//\n\t// Default DialTimeout is used if not set.\n\tDialTimeout DialFuncWithTimeout\n\n\t// Callback for establishing new connections to hosts.\n\t//\n\t// Note that if Dial is set instead of DialTimeout, Dial will ignore Request timeout.\n\t// If you want the tcp dial process to account for request timeouts, use DialTimeout instead.\n\t//\n\t// If not set, DialTimeout is used.\n\tDial DialFunc\n\n\t// Optional TLS config.\n\tTLSConfig *tls.Config\n\n\t// RetryIf controls whether a retry should be attempted after an error.\n\t// By default, it uses the isIdempotent function.\n\t//\n\t// Deprecated: Use RetryIfErr instead.\n\t// This field is only effective when the `RetryIfErr` field is not set.\n\tRetryIf RetryIfFunc\n\n\t// When the client encounters an error during a request, the behavior—whether to retry\n\t// and whether to reset the request timeout—should be determined\n\t// based on the return value of this field.\n\t// This field is only effective within the range of MaxIdemponentCallAttempts.\n\tRetryIfErr RetryIfErrFunc\n\n\tconnsWait *wantConnQueue\n\n\ttlsConfigMap map[string]*tls.Config\n\n\tclientReaderPool *sync.Pool\n\tclientWriterPool *sync.Pool\n\n\t// Comma-separated list of upstream HTTP server host addresses,\n\t// which are passed to Dial or DialTimeout in a round-robin manner.\n\t//\n\t// Each address may contain port if default dialer is used.\n\t// For example,\n\t//\n\t//    - foobar.com:80\n\t//    - foobar.com:443\n\t//    - foobar.com:8080\n\tAddr string\n\n\t// Client name. Used in User-Agent request header.\n\tName string\n\n\tconns []*clientConn\n\taddrs []string\n\n\t// Maximum number of connections which may be established to all hosts\n\t// listed in Addr.\n\t//\n\t// You can change this value while the HostClient is being used\n\t// with HostClient.SetMaxConns(value)\n\t//\n\t// DefaultMaxConnsPerHost is used if not set.\n\tMaxConns int\n\n\t// Keep-alive connections are closed after this duration.\n\t//\n\t// By default connection duration is unlimited.\n\tMaxConnDuration time.Duration\n\n\t// Idle keep-alive connections are closed after this duration.\n\t//\n\t// By default idle connections are closed\n\t// after DefaultMaxIdleConnDuration.\n\tMaxIdleConnDuration time.Duration\n\n\t// Maximum number of attempts for idempotent calls.\n\t//\n\t// A value of 0 or a negative value represents using DefaultMaxIdemponentCallAttempts.\n\t// For example, a value of 1 means the request will be executed only once,\n\t// while 2 means the request will be executed at most twice.\n\t// The RetryIfErr and RetryIf fields can invalidate remaining attempts.\n\tMaxIdemponentCallAttempts int\n\n\t// Per-connection buffer size for responses' reading.\n\t// This also limits the maximum header size.\n\t//\n\t// Default buffer size is used if 0.\n\tReadBufferSize int\n\n\t// Per-connection buffer size for requests' writing.\n\t//\n\t// Default buffer size is used if 0.\n\tWriteBufferSize int\n\n\t// Maximum duration for full response reading (including body).\n\t//\n\t// By default response read timeout is unlimited.\n\tReadTimeout time.Duration\n\n\t// Maximum duration for full request writing (including body).\n\t//\n\t// By default request write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\t// Maximum response body size.\n\t//\n\t// The client returns ErrBodyTooLarge if this limit is greater than 0\n\t// and response body is greater than the limit.\n\t//\n\t// By default response body size is unlimited.\n\tMaxResponseBodySize int\n\n\t// Maximum duration for waiting for a free connection.\n\t//\n\t// By default will not waiting, return ErrNoFreeConns immediately\n\tMaxConnWaitTimeout time.Duration\n\n\t// Connection pool strategy. Can be either LIFO or FIFO (default).\n\tConnPoolStrategy ConnPoolStrategyType\n\n\tconnsCount int\n\n\tconnsLock sync.Mutex\n\n\taddrsLock        sync.Mutex\n\ttlsConfigMapLock sync.Mutex\n\n\taddrIdx     uint32\n\tlastUseTime uint32\n\n\tpendingRequests int32\n\n\t// pendingClientRequests counts the number of requests that a Client is currently running using this HostClient.\n\t// It will be incremented earlier than pendingRequests and will be used by Client to see if the HostClient is still in use.\n\tpendingClientRequests int32\n\n\t// NoDefaultUserAgentHeader when set to true, causes the default\n\t// User-Agent header to be excluded from the Request.\n\tNoDefaultUserAgentHeader bool\n\n\t// Attempt to connect to both ipv4 and ipv6 host addresses\n\t// if set to true.\n\t//\n\t// This option is used only if default TCP dialer is used,\n\t// i.e. if Dial and DialTimeout are blank.\n\t//\n\t// By default client connects only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\tDialDualStack bool\n\n\t// Whether to use TLS (aka SSL or HTTPS) for host connections.\n\tIsTLS bool\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive\n\t// header names. See https://github.com/valyala/fasthttp/issues/57\n\t// for details.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// Path values are sent as-is without normalization.\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n\n\t// Will not log potentially sensitive content in error logs.\n\t//\n\t// This option is useful for servers that handle sensitive data\n\t// in the request/response.\n\t//\n\t// Client logs full errors by default.\n\tSecureErrorLogMessage bool\n\n\t// StreamResponseBody enables response body streaming.\n\tStreamResponseBody bool\n\n\tconnsCleanerRun bool\n}\n\ntype clientConn struct {\n\tc net.Conn\n\n\tcreatedTime time.Time\n\tlastUseTime time.Time\n}\n\n// Conn returns the underlying net.Conn associated with the client connection.\nfunc (cc *clientConn) Conn() net.Conn {\n\treturn cc.c\n}\n\n// CreatedTime returns time the client was created.\nfunc (cc *clientConn) CreatedTime() time.Time {\n\treturn cc.createdTime\n}\n\n// LastUseTime returns time the client was last used.\nfunc (cc *clientConn) LastUseTime() time.Time {\n\treturn cc.lastUseTime\n}\n\nvar startTimeUnix = time.Now().Unix()\n\n// LastUseTime returns time the client was last used.\nfunc (c *HostClient) LastUseTime() time.Time {\n\tn := atomic.LoadUint32(&c.lastUseTime)\n\treturn time.Unix(startTimeUnix+int64(n), 0)\n}\n\n// Get returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\nfunc (c *HostClient) Get(dst []byte, url string) (statusCode int, body []byte, err error) {\n\treturn clientGetURL(dst, url, c)\n}\n\n// GetTimeout returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// during the given timeout.\nfunc (c *HostClient) GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {\n\treturn clientGetURLTimeout(dst, url, timeout, c)\n}\n\n// GetDeadline returns the status code and body of url.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// ErrTimeout error is returned if url contents couldn't be fetched\n// until the given deadline.\nfunc (c *HostClient) GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {\n\treturn clientGetURLDeadline(dst, url, deadline, c)\n}\n\n// Post sends POST request to the given url with the given POST arguments.\n//\n// The contents of dst will be replaced by the body and returned, if the dst\n// is too small a new slice will be allocated.\n//\n// The function follows redirects. Use Do* for manually handling redirects.\n//\n// Empty POST body is sent if postArgs is nil.\nfunc (c *HostClient) Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {\n\treturn clientPostURL(dst, url, postArgs, c)\n}\n\ntype clientDoer interface {\n\tDo(req *Request, resp *Response) error\n}\n\nfunc clientGetURL(dst []byte, url string, c clientDoer) (statusCode int, body []byte, err error) {\n\treq := AcquireRequest()\n\n\tstatusCode, body, err = doRequestFollowRedirectsBuffer(req, dst, url, c)\n\n\tReleaseRequest(req)\n\treturn statusCode, body, err\n}\n\nfunc clientGetURLTimeout(dst []byte, url string, timeout time.Duration, c clientDoer) (statusCode int, body []byte, err error) {\n\tdeadline := time.Now().Add(timeout)\n\treturn clientGetURLDeadline(dst, url, deadline, c)\n}\n\ntype clientURLResponse struct {\n\terr        error\n\tbody       []byte\n\tstatusCode int\n}\n\nfunc clientGetURLDeadline(dst []byte, url string, deadline time.Time, c clientDoer) (statusCode int, body []byte, err error) {\n\ttimeout := time.Until(deadline)\n\tif timeout <= 0 {\n\t\treturn 0, dst, ErrTimeout\n\t}\n\n\tvar ch chan clientURLResponse\n\tchv := clientURLResponseChPool.Get()\n\tif chv == nil {\n\t\tchv = make(chan clientURLResponse, 1)\n\t}\n\tch = chv.(chan clientURLResponse)\n\n\t// Note that the request continues execution on ErrTimeout until\n\t// client-specific ReadTimeout exceeds. This helps limiting load\n\t// on slow hosts by MaxConns* concurrent requests.\n\t//\n\t// Without this 'hack' the load on slow host could exceed MaxConns*\n\t// concurrent requests, since timed out requests on client side\n\t// usually continue execution on the host.\n\n\tvar mu sync.Mutex\n\tvar timedout, responded bool\n\n\tgo func() {\n\t\treq := AcquireRequest()\n\n\t\tstatusCodeCopy, bodyCopy, errCopy := doRequestFollowRedirectsBuffer(req, dst, url, c)\n\t\tmu.Lock()\n\t\tif !timedout {\n\t\t\tch <- clientURLResponse{\n\t\t\t\tstatusCode: statusCodeCopy,\n\t\t\t\tbody:       bodyCopy,\n\t\t\t\terr:        errCopy,\n\t\t\t}\n\t\t\tresponded = true\n\t\t}\n\t\tmu.Unlock()\n\n\t\tReleaseRequest(req)\n\t}()\n\n\ttc := AcquireTimer(timeout)\n\tselect {\n\tcase resp := <-ch:\n\t\tstatusCode = resp.statusCode\n\t\tbody = resp.body\n\t\terr = resp.err\n\tcase <-tc.C:\n\t\tmu.Lock()\n\t\tif responded {\n\t\t\tresp := <-ch\n\t\t\tstatusCode = resp.statusCode\n\t\t\tbody = resp.body\n\t\t\terr = resp.err\n\t\t} else {\n\t\t\ttimedout = true\n\t\t\terr = ErrTimeout\n\t\t\tbody = dst\n\t\t}\n\t\tmu.Unlock()\n\t}\n\tReleaseTimer(tc)\n\n\tclientURLResponseChPool.Put(chv)\n\n\treturn statusCode, body, err\n}\n\nvar clientURLResponseChPool sync.Pool\n\nfunc clientPostURL(dst []byte, url string, postArgs *Args, c clientDoer) (statusCode int, body []byte, err error) {\n\treq := AcquireRequest()\n\tdefer ReleaseRequest(req)\n\n\treq.Header.SetMethod(MethodPost)\n\treq.Header.SetContentTypeBytes(strPostArgsContentType)\n\tif postArgs != nil {\n\t\tif _, err := postArgs.WriteTo(req.BodyWriter()); err != nil {\n\t\t\treturn 0, nil, err\n\t\t}\n\t}\n\n\tstatusCode, body, err = doRequestFollowRedirectsBuffer(req, dst, url, c)\n\n\treturn statusCode, body, err\n}\n\nvar (\n\t// ErrMissingLocation is returned by clients when the Location header is missing on\n\t// an HTTP response with a redirect status code.\n\tErrMissingLocation = errors.New(\"missing Location header for http redirect\")\n\t// ErrTooManyRedirects is returned by clients when the number of redirects followed\n\t// exceed the max count.\n\tErrTooManyRedirects = errors.New(\"too many redirects detected when doing the request\")\n\n\t// ErrHostClientRedirectToDifferentScheme is returned when a HostClient follows a redirect to a different protocol.\n\tErrHostClientRedirectToDifferentScheme = errors.New(\"HostClient can't follow redirects to a different protocol,\" +\n\t\t\" please use Client instead\")\n)\n\nconst defaultMaxRedirectsCount = 16\n\nfunc doRequestFollowRedirectsBuffer(req *Request, dst []byte, url string, c clientDoer) (statusCode int, body []byte, err error) {\n\tresp := AcquireResponse()\n\tbodyBuf := resp.bodyBuffer()\n\tresp.keepBodyBuffer = true\n\toldBody := bodyBuf.B\n\tbodyBuf.B = dst\n\n\tstatusCode, _, err = doRequestFollowRedirects(req, resp, url, defaultMaxRedirectsCount, c)\n\n\tbody = bodyBuf.B\n\tbodyBuf.B = oldBody\n\tresp.keepBodyBuffer = false\n\tReleaseResponse(resp)\n\n\treturn statusCode, body, err\n}\n\nfunc doRequestFollowRedirects(\n\treq *Request, resp *Response, url string, maxRedirectsCount int, c clientDoer,\n) (statusCode int, body []byte, err error) {\n\tredirectsCount := 0\n\n\tfor {\n\t\treq.SetRequestURI(url)\n\t\tif err := req.parseURI(); err != nil {\n\t\t\treturn 0, nil, err\n\t\t}\n\n\t\tif err = c.Do(req, resp); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tstatusCode = resp.Header.StatusCode()\n\t\tif !StatusCodeIsRedirect(statusCode) {\n\t\t\tbreak\n\t\t}\n\n\t\tredirectsCount++\n\t\tif redirectsCount > maxRedirectsCount {\n\t\t\terr = ErrTooManyRedirects\n\t\t\tbreak\n\t\t}\n\t\tlocation := resp.Header.peek(strLocation)\n\t\tif len(location) == 0 {\n\t\t\terr = ErrMissingLocation\n\t\t\tbreak\n\t\t}\n\t\turl = getRedirectURL(url, location, req.DisableRedirectPathNormalizing)\n\n\t\tif string(req.Header.Method()) == \"POST\" && (statusCode == 301 || statusCode == 302) {\n\t\t\treq.Header.SetMethod(MethodGet)\n\t\t}\n\t}\n\n\treturn statusCode, body, err\n}\n\nfunc getRedirectURL(baseURL string, location []byte, disablePathNormalizing bool) string {\n\tu := AcquireURI()\n\tu.Update(baseURL)\n\tu.UpdateBytes(location)\n\tu.DisablePathNormalizing = disablePathNormalizing\n\tredirectURL := u.String()\n\tReleaseURI(u)\n\treturn redirectURL\n}\n\n// StatusCodeIsRedirect returns true if the status code indicates a redirect.\nfunc StatusCodeIsRedirect(statusCode int) bool {\n\treturn statusCode == StatusMovedPermanently ||\n\t\tstatusCode == StatusFound ||\n\t\tstatusCode == StatusSeeOther ||\n\t\tstatusCode == StatusTemporaryRedirect ||\n\t\tstatusCode == StatusPermanentRedirect\n}\n\nvar (\n\trequestPool  sync.Pool\n\tresponsePool sync.Pool\n)\n\n// AcquireRequest returns an empty Request instance from request pool.\n//\n// The returned Request instance may be passed to ReleaseRequest when it is\n// no longer needed. This allows Request recycling, reduces GC pressure\n// and usually improves performance.\nfunc AcquireRequest() *Request {\n\tv := requestPool.Get()\n\tif v == nil {\n\t\treturn &Request{}\n\t}\n\treturn v.(*Request)\n}\n\n// ReleaseRequest returns req acquired via AcquireRequest to request pool.\n//\n// It is forbidden accessing req and/or its' members after returning\n// it to request pool.\nfunc ReleaseRequest(req *Request) {\n\treq.Reset()\n\trequestPool.Put(req)\n}\n\n// AcquireResponse returns an empty Response instance from response pool.\n//\n// The returned Response instance may be passed to ReleaseResponse when it is\n// no longer needed. This allows Response recycling, reduces GC pressure\n// and usually improves performance.\nfunc AcquireResponse() *Response {\n\tv := responsePool.Get()\n\tif v == nil {\n\t\treturn &Response{}\n\t}\n\treturn v.(*Response)\n}\n\n// ReleaseResponse return resp acquired via AcquireResponse to response pool.\n//\n// It is forbidden accessing resp and/or its' members after returning\n// it to response pool.\nfunc ReleaseResponse(resp *Response) {\n\tresp.Reset()\n\tresponsePool.Put(resp)\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned during\n// the given timeout.\n// Immediately returns ErrTimeout if timeout value is negative.\n//\n// ErrNoFreeConns is returned if all HostClient.MaxConns connections\n// to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {\n\treq.timeout = timeout\n\tif req.timeout <= 0 {\n\t\treturn ErrTimeout\n\t}\n\treturn c.Do(req, resp)\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned until\n// the given deadline.\n// Immediately returns ErrTimeout if the deadline has already been reached.\n//\n// ErrNoFreeConns is returned if all HostClient.MaxConns connections\n// to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\treq.timeout = time.Until(deadline)\n\tif req.timeout <= 0 {\n\t\treturn ErrTimeout\n\t}\n\treturn c.Do(req, resp)\n}\n\n// DoRedirects performs the given http request and fills the given http response,\n// following up to maxRedirectsCount redirects. When the redirect count exceeds\n// maxRedirectsCount, ErrTooManyRedirects is returned.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// Client determines the server to be requested in the following order:\n//\n//   - from RequestURI if it contains full url with scheme and host;\n//   - from Host header otherwise.\n//\n// Response is ignored if resp is nil.\n//\n// ErrNoFreeConns is returned if all DefaultMaxConnsPerHost connections\n// to the requested host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) DoRedirects(req *Request, resp *Response, maxRedirectsCount int) error {\n\tif c.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\t_, _, err := doRequestFollowRedirects(req, resp, req.URI().String(), maxRedirectsCount, c)\n\treturn err\n}\n\n// Do performs the given http request and sets the corresponding response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrNoFreeConns is returned if all HostClient.MaxConns connections\n// to the host are busy.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *HostClient) Do(req *Request, resp *Response) error {\n\tvar (\n\t\terr          error\n\t\tretry        bool\n\t\tresetTimeout bool\n\t)\n\tmaxAttempts := c.MaxIdemponentCallAttempts\n\tif maxAttempts <= 0 {\n\t\tmaxAttempts = DefaultMaxIdemponentCallAttempts\n\t}\n\tattempts := 0\n\thasBodyStream := req.IsBodyStream()\n\n\t// If a request has a timeout we store the timeout\n\t// and calculate a deadline so we can keep updating the\n\t// timeout on each retry.\n\tdeadline := time.Time{}\n\ttimeout := req.timeout\n\tif timeout > 0 {\n\t\tdeadline = time.Now().Add(timeout)\n\t}\n\tretryFunc := c.RetryIf\n\tif retryFunc == nil {\n\t\tretryFunc = isIdempotent\n\t}\n\n\tatomic.AddInt32(&c.pendingRequests, 1)\n\tfor {\n\t\t// If the original timeout was set, we need to update\n\t\t// the one set on the request to reflect the remaining time.\n\t\tif timeout > 0 {\n\t\t\treq.timeout = time.Until(deadline)\n\t\t\tif req.timeout <= 0 {\n\t\t\t\terr = ErrTimeout\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tretry, err = c.do(req, resp)\n\t\tif err == nil || !retry {\n\t\t\tbreak\n\t\t}\n\n\t\tif hasBodyStream {\n\t\t\tbreak\n\t\t}\n\t\t// Path prioritization based on ease of computation\n\t\tattempts++\n\n\t\tif attempts >= maxAttempts {\n\t\t\tbreak\n\t\t}\n\t\tif c.RetryIfErr != nil {\n\t\t\tresetTimeout, retry = c.RetryIfErr(req, attempts, err)\n\t\t} else {\n\t\t\tretry = retryFunc(req)\n\t\t}\n\t\tif !retry {\n\t\t\tbreak\n\t\t}\n\t\tif timeout > 0 && resetTimeout {\n\t\t\tdeadline = time.Now().Add(timeout)\n\t\t}\n\t}\n\tatomic.AddInt32(&c.pendingRequests, -1)\n\n\t// Restore the original timeout.\n\treq.timeout = timeout\n\n\tif err == io.EOF {\n\t\terr = ErrConnectionClosed\n\t}\n\treturn err\n}\n\n// PendingRequests returns the current number of requests the client\n// is executing.\n//\n// This function may be used for balancing load among multiple HostClient\n// instances.\nfunc (c *HostClient) PendingRequests() int {\n\treturn int(atomic.LoadInt32(&c.pendingRequests))\n}\n\nfunc isIdempotent(req *Request) bool {\n\treturn req.Header.IsGet() || req.Header.IsHead() || req.Header.IsPut()\n}\n\nfunc (c *HostClient) do(req *Request, resp *Response) (bool, error) {\n\tif resp == nil {\n\t\tresp = AcquireResponse()\n\t\tdefer ReleaseResponse(resp)\n\t}\n\n\treturn c.doNonNilReqResp(req, resp)\n}\n\nfunc (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) {\n\tif req == nil {\n\t\t// for debugging purposes\n\t\tpanic(\"BUG: req cannot be nil\")\n\t}\n\tif resp == nil {\n\t\t// for debugging purposes\n\t\tpanic(\"BUG: resp cannot be nil\")\n\t}\n\n\t// Secure header error logs configuration\n\tresp.secureErrorLogMessage = c.SecureErrorLogMessage\n\tresp.Header.secureErrorLogMessage = c.SecureErrorLogMessage\n\treq.secureErrorLogMessage = c.SecureErrorLogMessage\n\treq.Header.secureErrorLogMessage = c.SecureErrorLogMessage\n\n\tif c.IsTLS != req.URI().isHTTPS() {\n\t\treturn false, ErrHostClientRedirectToDifferentScheme\n\t}\n\n\tatomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix)) // #nosec G115\n\n\t// Free up resources occupied by response before sending the request,\n\t// so the GC may reclaim these resources (e.g. response body).\n\n\t// backing up SkipBody in case it was set explicitly\n\tcustomSkipBody := resp.SkipBody\n\tcustomStreamBody := resp.StreamBody || c.StreamResponseBody\n\tresp.Reset()\n\tresp.SkipBody = customSkipBody\n\tresp.StreamBody = customStreamBody\n\n\treq.URI().DisablePathNormalizing = c.DisablePathNormalizing\n\n\tuserAgentOld := req.Header.UserAgent()\n\tif len(userAgentOld) == 0 {\n\t\tuserAgent := c.Name\n\t\tif userAgent == \"\" && !c.NoDefaultUserAgentHeader {\n\t\t\tuserAgent = defaultUserAgent\n\t\t}\n\t\tif userAgent != \"\" {\n\t\t\treq.Header.userAgent = append(req.Header.userAgent[:0], userAgent...)\n\t\t}\n\t}\n\n\treturn c.transport().RoundTrip(c, req, resp)\n}\n\nfunc (c *HostClient) transport() RoundTripper {\n\tif c.Transport == nil {\n\t\treturn DefaultTransport\n\t}\n\treturn c.Transport\n}\n\nvar (\n\t// ErrNoFreeConns is returned when no free connections available\n\t// to the given host.\n\t//\n\t// Increase the allowed number of connections per host if you\n\t// see this error.\n\tErrNoFreeConns = errors.New(\"no free connections available to host\")\n\n\t// ErrConnectionClosed may be returned from client methods if the server\n\t// closes connection before returning the first response byte.\n\t//\n\t// If you see this error, then either fix the server by returning\n\t// 'Connection: close' response header before closing the connection\n\t// or add 'Connection: close' request header before sending requests\n\t// to broken server.\n\tErrConnectionClosed = errors.New(\"the server closed connection before returning the first response byte. \" +\n\t\t\"Make sure the server returns 'Connection: close' response header before closing the connection\")\n\n\t// ErrConnPoolStrategyNotImpl is returned when HostClient.ConnPoolStrategy is not implement yet.\n\t// If you see this error, then you need to check your HostClient configuration.\n\tErrConnPoolStrategyNotImpl = errors.New(\"connection pool strategy is not implement\")\n)\n\ntype timeoutError struct{}\n\nfunc (e *timeoutError) Error() string {\n\treturn \"timeout\"\n}\n\n// Timeout implements the Timeout behavior of the net.Error interface.\n// This allows for checks like:\n//\n//\tif x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {\nfunc (e *timeoutError) Timeout() bool {\n\treturn true\n}\n\n// ErrTimeout is returned from timed out calls.\nvar ErrTimeout = &timeoutError{}\n\n// SetMaxConns sets up the maximum number of connections which may be established to all hosts listed in Addr.\nfunc (c *HostClient) SetMaxConns(newMaxConns int) {\n\tc.connsLock.Lock()\n\tc.MaxConns = newMaxConns\n\tc.connsLock.Unlock()\n}\n\nfunc (c *HostClient) AcquireConn(reqTimeout time.Duration, connectionClose bool) (cc *clientConn, err error) {\n\tcreateConn := false\n\tstartCleaner := false\n\n\tvar n int\n\tc.connsLock.Lock()\n\tn = len(c.conns)\n\tif n == 0 {\n\t\tmaxConns := c.MaxConns\n\t\tif maxConns <= 0 {\n\t\t\tmaxConns = DefaultMaxConnsPerHost\n\t\t}\n\t\tif c.connsCount < maxConns {\n\t\t\tc.connsCount++\n\t\t\tcreateConn = true\n\t\t\tif !c.connsCleanerRun && !connectionClose {\n\t\t\t\tstartCleaner = true\n\t\t\t\tc.connsCleanerRun = true\n\t\t\t}\n\t\t}\n\t} else {\n\t\tswitch c.ConnPoolStrategy {\n\t\tcase LIFO:\n\t\t\tn--\n\t\t\tcc = c.conns[n]\n\t\t\tc.conns[n] = nil\n\t\t\tc.conns = c.conns[:n]\n\t\tcase FIFO:\n\t\t\tcc = c.conns[0]\n\t\t\tcopy(c.conns, c.conns[1:])\n\t\t\tc.conns[n-1] = nil\n\t\t\tc.conns = c.conns[:n-1]\n\t\tdefault:\n\t\t\tc.connsLock.Unlock()\n\t\t\treturn nil, ErrConnPoolStrategyNotImpl\n\t\t}\n\t}\n\tc.connsLock.Unlock()\n\n\tif cc != nil {\n\t\treturn cc, nil\n\t}\n\tif !createConn {\n\t\tif c.MaxConnWaitTimeout <= 0 {\n\t\t\treturn nil, ErrNoFreeConns\n\t\t}\n\n\t\t//nolint:dupword\n\t\t// reqTimeout    c.MaxConnWaitTimeout   wait duration\n\t\t//     d1                 d2            min(d1, d2)\n\t\t//  0(not set)            d2            d2\n\t\t//     d1            0(don't wait)      0(don't wait)\n\t\t//  0(not set)            d2            d2\n\t\ttimeout := c.MaxConnWaitTimeout\n\t\ttimeoutOverridden := false\n\t\t// reqTimeout == 0 means not set\n\t\tif reqTimeout > 0 && reqTimeout < timeout {\n\t\t\ttimeout = reqTimeout\n\t\t\ttimeoutOverridden = true\n\t\t}\n\n\t\t// wait for a free connection\n\t\ttc := AcquireTimer(timeout)\n\t\tdefer ReleaseTimer(tc)\n\n\t\tw := &wantConn{\n\t\t\tready: make(chan struct{}, 1),\n\t\t}\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tw.cancel(c, err)\n\t\t\t}\n\t\t}()\n\n\t\tc.queueForIdle(w)\n\n\t\tselect {\n\t\tcase <-w.ready:\n\t\t\treturn w.conn, w.err\n\t\tcase <-tc.C:\n\t\t\tc.connsWait.failedWaiters.Add(1)\n\t\t\tif timeoutOverridden {\n\t\t\t\treturn nil, ErrTimeout\n\t\t\t}\n\t\t\treturn nil, ErrNoFreeConns\n\t\t}\n\t}\n\n\tif startCleaner {\n\t\tgo c.connsCleaner()\n\t}\n\n\tconn, err := c.dialHostHard(reqTimeout)\n\tif err != nil {\n\t\tc.decConnsCount()\n\t\treturn nil, err\n\t}\n\tcc = acquireClientConn(conn)\n\n\treturn cc, nil\n}\n\nfunc (c *HostClient) queueForIdle(w *wantConn) {\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tif c.connsWait == nil {\n\t\tc.connsWait = &wantConnQueue{}\n\t}\n\tc.connsWait.clearFront()\n\tc.connsWait.pushBack(w)\n}\n\nfunc (c *HostClient) dialConnFor(w *wantConn) {\n\tconn, err := c.dialHostHard(0)\n\tif err != nil {\n\t\tw.tryDeliver(nil, err)\n\t\tc.decConnsCount()\n\t\treturn\n\t}\n\n\tcc := acquireClientConn(conn)\n\tif !w.tryDeliver(cc, nil) {\n\t\t// not delivered, return idle connection\n\t\tc.ReleaseConn(cc)\n\t}\n}\n\n// CloseIdleConnections closes any connections which were previously\n// connected from previous requests but are now sitting idle in a\n// \"keep-alive\" state. It does not interrupt any connections currently\n// in use.\nfunc (c *HostClient) CloseIdleConnections() {\n\tc.connsLock.Lock()\n\tscratch := append([]*clientConn{}, c.conns...)\n\tfor i := range c.conns {\n\t\tc.conns[i] = nil\n\t}\n\tc.conns = c.conns[:0]\n\tc.connsLock.Unlock()\n\n\tfor _, cc := range scratch {\n\t\tc.CloseConn(cc)\n\t}\n}\n\nfunc (c *HostClient) connsCleaner() {\n\tvar (\n\t\tscratch             []*clientConn\n\t\tmaxIdleConnDuration = c.MaxIdleConnDuration\n\t)\n\tif maxIdleConnDuration <= 0 {\n\t\tmaxIdleConnDuration = DefaultMaxIdleConnDuration\n\t}\n\tfor {\n\t\tcurrentTime := time.Now()\n\n\t\t// Determine idle connections to be closed.\n\t\tc.connsLock.Lock()\n\t\tconns := c.conns\n\t\tn := len(conns)\n\t\ti := 0\n\t\tfor i < n && currentTime.Sub(conns[i].lastUseTime) > maxIdleConnDuration {\n\t\t\ti++\n\t\t}\n\t\tsleepFor := maxIdleConnDuration\n\t\tif i < n {\n\t\t\t// + 1 so we actually sleep past the expiration time and not up to it.\n\t\t\t// Otherwise the > check above would still fail.\n\t\t\tsleepFor = maxIdleConnDuration - currentTime.Sub(conns[i].lastUseTime) + 1\n\t\t}\n\t\tscratch = append(scratch[:0], conns[:i]...)\n\t\tif i > 0 {\n\t\t\tm := copy(conns, conns[i:])\n\t\t\tfor i = m; i < n; i++ {\n\t\t\t\tconns[i] = nil\n\t\t\t}\n\t\t\tc.conns = conns[:m]\n\t\t}\n\t\tc.connsLock.Unlock()\n\n\t\t// Close idle connections.\n\t\tfor i, cc := range scratch {\n\t\t\tc.CloseConn(cc)\n\t\t\tscratch[i] = nil\n\t\t}\n\n\t\t// Determine whether to stop the connsCleaner.\n\t\tc.connsLock.Lock()\n\t\tmustStop := c.connsCount == 0\n\t\tif mustStop {\n\t\t\tc.connsCleanerRun = false\n\t\t}\n\t\tc.connsLock.Unlock()\n\t\tif mustStop {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(sleepFor)\n\t}\n}\n\nfunc (c *HostClient) CloseConn(cc *clientConn) {\n\tc.decConnsCount()\n\tcc.c.Close()\n\treleaseClientConn(cc)\n}\n\nfunc (c *HostClient) decConnsCount() {\n\tif c.MaxConnWaitTimeout <= 0 {\n\t\tc.connsLock.Lock()\n\t\tc.connsCount--\n\t\tc.connsLock.Unlock()\n\t\treturn\n\t}\n\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tdialed := false\n\tif q := c.connsWait; q != nil && q.len() > 0 {\n\t\tfor q.len() > 0 {\n\t\t\tw := q.popFront()\n\t\t\tif w.waiting() {\n\t\t\t\tgo c.dialConnFor(w)\n\t\t\t\tdialed = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tc.connsWait.failedWaiters.Add(-1)\n\t\t}\n\t}\n\tif !dialed {\n\t\tc.connsCount--\n\t}\n}\n\n// ConnsCount returns connection count of HostClient.\nfunc (c *HostClient) ConnsCount() int {\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\n\treturn c.connsCount\n}\n\nfunc acquireClientConn(conn net.Conn) *clientConn {\n\tv := clientConnPool.Get()\n\tif v == nil {\n\t\tv = &clientConn{}\n\t}\n\tcc := v.(*clientConn)\n\tcc.c = conn\n\tcc.createdTime = time.Now()\n\treturn cc\n}\n\nfunc releaseClientConn(cc *clientConn) {\n\t// Reset all fields.\n\t*cc = clientConn{}\n\tclientConnPool.Put(cc)\n}\n\nvar clientConnPool sync.Pool\n\nfunc (c *HostClient) ReleaseConn(cc *clientConn) {\n\tcc.lastUseTime = time.Now()\n\tif c.MaxConnWaitTimeout <= 0 {\n\t\tc.connsLock.Lock()\n\t\tc.conns = append(c.conns, cc)\n\t\tc.connsLock.Unlock()\n\t\treturn\n\t}\n\n\t// try to deliver an idle connection to a *wantConn\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\tdelivered := false\n\tif q := c.connsWait; q != nil && q.len() > 0 {\n\t\tfor q.len() > 0 {\n\t\t\tw := q.popFront()\n\t\t\tif w.waiting() {\n\t\t\t\tdelivered = w.tryDeliver(cc, nil)\n\t\t\t\t// This is the last resort to hand over conCount sema.\n\t\t\t\t// We must ensure that there are no valid waiters in connsWait\n\t\t\t\t// when we exit this loop.\n\t\t\t\t//\n\t\t\t\t// We did not apply the same looping pattern in the decConnsCount\n\t\t\t\t// method because it needs to create a new time-spent connection,\n\t\t\t\t// and the decConnsCount call chain will inevitably reach this point.\n\t\t\t\t// When MaxConnWaitTimeout>0.\n\t\t\t\tif delivered {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tc.connsWait.failedWaiters.Add(-1)\n\t\t}\n\t}\n\tif !delivered {\n\t\tc.conns = append(c.conns, cc)\n\t}\n}\n\nfunc (c *HostClient) AcquireWriter(conn net.Conn) *bufio.Writer {\n\tvar v any\n\tif c.clientWriterPool != nil {\n\t\tv = c.clientWriterPool.Get()\n\t} else {\n\t\tv = c.writerPool.Get()\n\t}\n\tif v == nil {\n\t\tn := c.WriteBufferSize\n\t\tif n <= 0 {\n\t\t\tn = defaultWriteBufferSize\n\t\t}\n\t\treturn bufio.NewWriterSize(conn, n)\n\t}\n\n\tbw := v.(*bufio.Writer)\n\tbw.Reset(conn)\n\treturn bw\n}\n\nfunc (c *HostClient) ReleaseWriter(bw *bufio.Writer) {\n\tif c.clientWriterPool != nil {\n\t\tc.clientWriterPool.Put(bw)\n\t} else {\n\t\tc.writerPool.Put(bw)\n\t}\n}\n\nfunc (c *HostClient) AcquireReader(conn net.Conn) *bufio.Reader {\n\tvar v any\n\tif c.clientReaderPool != nil {\n\t\tv = c.clientReaderPool.Get()\n\t} else {\n\t\tv = c.readerPool.Get()\n\t}\n\tif v == nil {\n\t\tn := c.ReadBufferSize\n\t\tif n <= 0 {\n\t\t\tn = defaultReadBufferSize\n\t\t}\n\t\treturn bufio.NewReaderSize(conn, n)\n\t}\n\n\tbr := v.(*bufio.Reader)\n\tbr.Reset(conn)\n\treturn br\n}\n\nfunc (c *HostClient) ReleaseReader(br *bufio.Reader) {\n\tif c.clientReaderPool != nil {\n\t\tc.clientReaderPool.Put(br)\n\t} else {\n\t\tc.readerPool.Put(br)\n\t}\n}\n\nfunc newClientTLSConfig(c *tls.Config, addr string) *tls.Config {\n\tif c == nil {\n\t\tc = &tls.Config{}\n\t} else {\n\t\tc = c.Clone()\n\t}\n\n\tif c.ServerName == \"\" {\n\t\tserverName := tlsServerName(addr)\n\t\tif serverName == \"*\" {\n\t\t\tc.InsecureSkipVerify = true\n\t\t} else {\n\t\t\tc.ServerName = serverName\n\t\t}\n\t}\n\treturn c\n}\n\nfunc tlsServerName(addr string) string {\n\tif !strings.Contains(addr, \":\") {\n\t\treturn addr\n\t}\n\thost, _, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn \"*\"\n\t}\n\treturn host\n}\n\nfunc (c *HostClient) nextAddr() string {\n\tc.addrsLock.Lock()\n\tif c.addrs == nil {\n\t\tc.addrs = strings.Split(c.Addr, \",\")\n\t}\n\taddr := c.addrs[0]\n\tif len(c.addrs) > 1 {\n\t\taddr = c.addrs[c.addrIdx%uint32(len(c.addrs))] // #nosec G115\n\t\tc.addrIdx++\n\t}\n\tc.addrsLock.Unlock()\n\treturn addr\n}\n\nfunc (c *HostClient) dialHostHard(dialTimeout time.Duration) (conn net.Conn, err error) {\n\t// use dialTimeout to control the timeout of each dial. It does not work if dialTimeout is 0 or if\n\t// c.DialTimeout has not been set and c.Dial has been set.\n\t// attempt to dial all the available hosts before giving up.\n\n\tc.addrsLock.Lock()\n\tn := len(c.addrs)\n\tc.addrsLock.Unlock()\n\n\tif n == 0 {\n\t\t// It looks like c.addrs isn't initialized yet.\n\t\tn = 1\n\t}\n\n\ttimeout := c.ReadTimeout + c.WriteTimeout\n\tif timeout <= 0 {\n\t\ttimeout = DefaultDialTimeout\n\t}\n\tdeadline := time.Now().Add(timeout)\n\tfor n > 0 {\n\t\taddr := c.nextAddr()\n\t\ttlsConfig := c.cachedTLSConfig(addr)\n\t\tconn, err = dialAddr(addr, c.Dial, c.DialTimeout, c.DialDualStack, c.IsTLS, tlsConfig, dialTimeout, c.WriteTimeout)\n\t\tif err == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\tif time.Since(deadline) >= 0 {\n\t\t\tbreak\n\t\t}\n\t\tn--\n\t}\n\treturn nil, err\n}\n\nfunc (c *HostClient) cachedTLSConfig(addr string) *tls.Config {\n\tif !c.IsTLS {\n\t\treturn nil\n\t}\n\n\tc.tlsConfigMapLock.Lock()\n\tif c.tlsConfigMap == nil {\n\t\tc.tlsConfigMap = make(map[string]*tls.Config)\n\t}\n\tcfg := c.tlsConfigMap[addr]\n\tif cfg == nil {\n\t\tcfg = newClientTLSConfig(c.TLSConfig, addr)\n\t\tc.tlsConfigMap[addr] = cfg\n\t}\n\tc.tlsConfigMapLock.Unlock()\n\n\treturn cfg\n}\n\n// ErrTLSHandshakeTimeout indicates there is a timeout from tls handshake.\nvar ErrTLSHandshakeTimeout = errors.New(\"tls handshake timed out\")\n\nfunc tlsClientHandshake(rawConn net.Conn, tlsConfig *tls.Config, deadline time.Time) (_ net.Conn, retErr error) {\n\tdefer func() {\n\t\tif retErr != nil {\n\t\t\trawConn.Close()\n\t\t}\n\t}()\n\tconn := tls.Client(rawConn, tlsConfig)\n\terr := conn.SetDeadline(deadline)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = conn.Handshake()\n\tif netErr, ok := err.(net.Error); ok && netErr.Timeout() {\n\t\treturn nil, ErrTLSHandshakeTimeout\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = conn.SetDeadline(time.Time{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc dialAddr(\n\taddr string, dial DialFunc, dialWithTimeout DialFuncWithTimeout, dialDualStack, isTLS bool,\n\ttlsConfig *tls.Config, dialTimeout, writeTimeout time.Duration,\n) (net.Conn, error) {\n\tdeadline := time.Now().Add(writeTimeout)\n\tconn, err := callDialFunc(addr, dial, dialWithTimeout, dialDualStack, isTLS, dialTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif conn == nil {\n\t\treturn nil, errors.New(\"dialling unsuccessful. Please report this bug\")\n\t}\n\n\t// We assume that any conn that has the Handshake() method is a TLS conn already.\n\t// This doesn't cover just tls.Conn but also other TLS implementations.\n\t_, isTLSAlready := conn.(interface{ Handshake() error })\n\n\tif isTLS && !isTLSAlready {\n\t\tif writeTimeout == 0 {\n\t\t\treturn tls.Client(conn, tlsConfig), nil\n\t\t}\n\t\treturn tlsClientHandshake(conn, tlsConfig, deadline)\n\t}\n\treturn conn, nil\n}\n\nfunc callDialFunc(\n\taddr string, dial DialFunc, dialWithTimeout DialFuncWithTimeout, dialDualStack, isTLS bool, timeout time.Duration,\n) (net.Conn, error) {\n\tif dialWithTimeout != nil {\n\t\treturn dialWithTimeout(addr, timeout)\n\t}\n\tif dial != nil {\n\t\treturn dial(addr)\n\t}\n\taddr = AddMissingPort(addr, isTLS)\n\tif timeout > 0 {\n\t\tif dialDualStack {\n\t\t\treturn DialDualStackTimeout(addr, timeout)\n\t\t}\n\t\treturn DialTimeout(addr, timeout)\n\t}\n\tif dialDualStack {\n\t\treturn DialDualStack(addr)\n\t}\n\treturn Dial(addr)\n}\n\n// AddMissingPort adds a port to a host if it is missing.\n// A literal IPv6 address in hostport must be enclosed in square\n// brackets, as in \"[::1]:80\", \"[::1%lo0]:80\".\nfunc AddMissingPort(addr string, isTLS bool) string {\n\taddrLen := len(addr)\n\tif addrLen == 0 {\n\t\treturn addr\n\t}\n\n\tisIP6 := addr[0] == '['\n\tif isIP6 {\n\t\t// if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port\n\t\tisIP6WithoutPort := addr[addrLen-1] == ']'\n\t\tif !isIP6WithoutPort {\n\t\t\treturn addr\n\t\t}\n\t} else { // IPv4\n\t\tcolumnPos := strings.LastIndexByte(addr, ':')\n\t\tif columnPos > 0 {\n\t\t\treturn addr\n\t\t}\n\t}\n\tport := \":80\"\n\tif isTLS {\n\t\tport = \":443\"\n\t}\n\treturn addr + port\n}\n\n// A wantConn records state about a wanted connection\n// (that is, an active call to getConn).\n// The conn may be gotten by dialing or by finding an idle connection,\n// or a cancellation may make the conn no longer wanted.\n// These three options are racing against each other and use\n// wantConn to coordinate and agree about the winning outcome.\n//\n// Inspired by net/http/transport.go.\ntype wantConn struct {\n\terr   error\n\tready chan struct{}\n\tconn  *clientConn\n\tmu    sync.Mutex // protects conn, err, close(ready)\n}\n\n// waiting reports whether w is still waiting for an answer (connection or error).\nfunc (w *wantConn) waiting() bool {\n\tselect {\n\tcase <-w.ready:\n\t\treturn false\n\tdefault:\n\t\treturn true\n\t}\n}\n\n// tryDeliver attempts to deliver conn, err to w and reports whether it succeeded.\nfunc (w *wantConn) tryDeliver(conn *clientConn, err error) bool {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tif w.conn != nil || w.err != nil {\n\t\treturn false\n\t}\n\tw.conn = conn\n\tw.err = err\n\tif w.conn == nil && w.err == nil {\n\t\tpanic(\"fasthttp: internal error: misuse of tryDeliver\")\n\t}\n\tclose(w.ready)\n\treturn true\n}\n\n// cancel marks w as no longer wanting a result (for example, due to cancellation).\n// If a connection has been delivered already, cancel returns it with c.releaseConn.\nfunc (w *wantConn) cancel(c *HostClient, err error) {\n\tw.mu.Lock()\n\tif w.conn == nil && w.err == nil {\n\t\tclose(w.ready) // catch misbehavior in future delivery\n\t}\n\n\tconn := w.conn\n\tw.conn = nil\n\tw.err = err\n\tw.mu.Unlock()\n\n\tif conn != nil {\n\t\tc.ReleaseConn(conn)\n\t}\n}\n\n// A wantConnQueue is a queue of wantConns.\n//\n// Inspired by net/http/transport.go.\ntype wantConnQueue struct {\n\t// This is a queue, not a dequeue.\n\t// It is split into two stages - head[headPos:] and tail.\n\t// popFront is trivial (headPos++) on the first stage, and\n\t// pushBack is trivial (append) on the second stage.\n\t// If the first stage is empty, popFront can swap the\n\t// first and second stages to remedy the situation.\n\t//\n\t// This two-stage split is analogous to the use of two lists\n\t// in Okasaki's purely functional queue but without the\n\t// overhead of reversing the list when swapping stages.\n\thead    []*wantConn\n\ttail    []*wantConn\n\theadPos int\n\t// failedWaiters is the number of waiters in the head or tail queue,\n\t// but is invalid.\n\t// These state waiters cannot truly be considered as waiters; the current\n\t// implementation does not immediately remove them when they become\n\t// invalid but instead only marks them.\n\tfailedWaiters atomic.Int64\n}\n\n// len returns the number of items in the queue.\nfunc (q *wantConnQueue) len() int {\n\treturn len(q.head) - q.headPos + len(q.tail) - int(q.failedWaiters.Load())\n}\n\n// pushBack adds w to the back of the queue.\nfunc (q *wantConnQueue) pushBack(w *wantConn) {\n\tq.tail = append(q.tail, w)\n}\n\n// popFront removes and returns the wantConn at the front of the queue.\nfunc (q *wantConnQueue) popFront() *wantConn {\n\tif q.headPos >= len(q.head) {\n\t\tif len(q.tail) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\t// Pick up tail as new head, clear tail.\n\t\tq.head, q.headPos, q.tail = q.tail, 0, q.head[:0]\n\t}\n\n\tw := q.head[q.headPos]\n\tq.head[q.headPos] = nil\n\tq.headPos++\n\treturn w\n}\n\n// peekFront returns the wantConn at the front of the queue without removing it.\nfunc (q *wantConnQueue) peekFront() *wantConn {\n\tif q.headPos < len(q.head) {\n\t\treturn q.head[q.headPos]\n\t}\n\tif len(q.tail) > 0 {\n\t\treturn q.tail[0]\n\t}\n\treturn nil\n}\n\n// clearFront pops any wantConns that are no longer waiting from the head of the\n// queue, reporting whether any were popped.\nfunc (q *wantConnQueue) clearFront() (cleaned bool) {\n\tfor {\n\t\tw := q.peekFront()\n\t\tif w == nil || w.waiting() {\n\t\t\treturn cleaned\n\t\t}\n\t\tq.popFront()\n\t\tq.failedWaiters.Add(-1)\n\t\tcleaned = true\n\t}\n}\n\n// PipelineClient pipelines requests over a limited set of concurrent\n// connections to the given Addr.\n//\n// This client may be used in highly loaded HTTP-based RPC systems for reducing\n// context switches and network level overhead.\n// See https://en.wikipedia.org/wiki/HTTP_pipelining for details.\n//\n// It is forbidden copying PipelineClient instances. Create new instances\n// instead.\n//\n// It is safe calling PipelineClient methods from concurrently running\n// goroutines.\ntype PipelineClient struct {\n\tnoCopy noCopy\n\n\t// Logger for logging client errors.\n\t//\n\t// By default standard logger from log package is used.\n\tLogger Logger\n\n\t// Callback for connection establishing to the host.\n\t//\n\t// Default Dial is used if not set.\n\tDial DialFunc\n\n\t// Optional TLS config.\n\tTLSConfig *tls.Config\n\n\t// Address of the host to connect to.\n\tAddr string\n\n\t// PipelineClient name. Used in User-Agent request header.\n\tName string\n\n\tconnClients []*pipelineConnClient\n\n\t// The maximum number of concurrent connections to the Addr.\n\t//\n\t// A single connection is used by default.\n\tMaxConns int\n\n\t// The maximum number of pending pipelined requests over\n\t// a single connection to Addr.\n\t//\n\t// DefaultMaxPendingRequests is used by default.\n\tMaxPendingRequests int\n\n\t// The maximum delay before sending pipelined requests as a batch\n\t// to the server.\n\t//\n\t// By default requests are sent immediately to the server.\n\tMaxBatchDelay time.Duration\n\n\t// Idle connection to the host is closed after this duration.\n\t//\n\t// By default idle connection is closed after\n\t// DefaultMaxIdleConnDuration.\n\tMaxIdleConnDuration time.Duration\n\n\t// Buffer size for responses' reading.\n\t// This also limits the maximum header size.\n\t//\n\t// Default buffer size is used if 0.\n\tReadBufferSize int\n\n\t// Buffer size for requests' writing.\n\t//\n\t// Default buffer size is used if 0.\n\tWriteBufferSize int\n\n\t// Maximum duration for full response reading (including body).\n\t//\n\t// By default response read timeout is unlimited.\n\tReadTimeout time.Duration\n\n\t// Maximum duration for full request writing (including body).\n\t//\n\t// By default request write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\tconnClientsLock sync.Mutex\n\n\t// NoDefaultUserAgentHeader when set to true, causes the default\n\t// User-Agent header to be excluded from the Request.\n\tNoDefaultUserAgentHeader bool\n\n\t// Attempt to connect to both ipv4 and ipv6 host addresses\n\t// if set to true.\n\t//\n\t// This option is used only if default TCP dialer is used,\n\t// i.e. if Dial is blank.\n\t//\n\t// By default client connects only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\tDialDualStack bool\n\n\t// Response header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// responses to other clients expecting case-sensitive\n\t// header names. See https://github.com/valyala/fasthttp/issues/57\n\t// for details.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// Path values are sent as-is without normalization\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n\n\t// Whether to use TLS (aka SSL or HTTPS) for host connections.\n\tIsTLS bool\n}\n\ntype pipelineConnClient struct {\n\tnoCopy noCopy\n\n\tworkPool sync.Pool\n\n\tLogger Logger\n\n\tDial      DialFunc\n\tTLSConfig *tls.Config\n\tchW       chan *pipelineWork\n\tchR       chan *pipelineWork\n\n\ttlsConfig *tls.Config\n\n\tAddr                string\n\tName                string\n\tMaxPendingRequests  int\n\tMaxBatchDelay       time.Duration\n\tMaxIdleConnDuration time.Duration\n\tReadBufferSize      int\n\tWriteBufferSize     int\n\tReadTimeout         time.Duration\n\tWriteTimeout        time.Duration\n\n\tchLock sync.Mutex\n\n\ttlsConfigLock                 sync.Mutex\n\tNoDefaultUserAgentHeader      bool\n\tDialDualStack                 bool\n\tDisableHeaderNamesNormalizing bool\n\tDisablePathNormalizing        bool\n\tIsTLS                         bool\n}\n\ntype pipelineWork struct {\n\trespCopy Response\n\tdeadline time.Time\n\terr      error\n\treq      *Request\n\tresp     *Response\n\tt        *time.Timer\n\tdone     chan struct{}\n\treqCopy  Request\n}\n\n// DoTimeout performs the given request and waits for response during\n// the given timeout duration.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned during\n// the given timeout.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *PipelineClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {\n\treturn c.DoDeadline(req, resp, time.Now().Add(timeout))\n}\n\n// DoDeadline performs the given request and waits for response until\n// the given deadline.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects.\n//\n// Response is ignored if resp is nil.\n//\n// ErrTimeout is returned if the response wasn't returned until\n// the given deadline.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *PipelineClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\treturn c.getConnClient().DoDeadline(req, resp, deadline)\n}\n\nfunc (c *pipelineConnClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\tc.init()\n\n\ttimeout := time.Until(deadline)\n\tif timeout <= 0 {\n\t\treturn ErrTimeout\n\t}\n\n\tif c.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\n\tuserAgentOld := req.Header.UserAgent()\n\tif len(userAgentOld) == 0 {\n\t\tuserAgent := c.Name\n\t\tif userAgent == \"\" && !c.NoDefaultUserAgentHeader {\n\t\t\tuserAgent = defaultUserAgent\n\t\t}\n\t\tif userAgent != \"\" {\n\t\t\treq.Header.userAgent = append(req.Header.userAgent[:0], userAgent...)\n\t\t}\n\t}\n\n\tw := c.acquirePipelineWork(timeout)\n\tw.respCopy.Header.disableNormalizing = c.DisableHeaderNamesNormalizing\n\tw.req = &w.reqCopy\n\tw.resp = &w.respCopy\n\n\t// Make a copy of the request in order to avoid data races on timeouts\n\treq.copyToSkipBody(&w.reqCopy)\n\tswapRequestBody(req, &w.reqCopy)\n\n\t// Put the request to outgoing queue\n\tselect {\n\tcase c.chW <- w:\n\t\t// Fast path: len(c.ch) < cap(c.ch)\n\tdefault:\n\t\t// Slow path\n\t\tselect {\n\t\tcase c.chW <- w:\n\t\tcase <-w.t.C:\n\t\t\tc.releasePipelineWork(w)\n\t\t\treturn ErrTimeout\n\t\t}\n\t}\n\n\t// Wait for the response\n\tvar err error\n\tselect {\n\tcase <-w.done:\n\t\tif resp != nil {\n\t\t\tw.respCopy.copyToSkipBody(resp)\n\t\t\tswapResponseBody(resp, &w.respCopy)\n\t\t}\n\t\terr = w.err\n\t\tc.releasePipelineWork(w)\n\tcase <-w.t.C:\n\t\terr = ErrTimeout\n\t}\n\n\treturn err\n}\n\nfunc (c *pipelineConnClient) acquirePipelineWork(timeout time.Duration) (w *pipelineWork) {\n\tv := c.workPool.Get()\n\tif v != nil {\n\t\tw = v.(*pipelineWork)\n\t} else {\n\t\tw = &pipelineWork{\n\t\t\tdone: make(chan struct{}, 1),\n\t\t}\n\t}\n\tif timeout > 0 {\n\t\tif w.t == nil {\n\t\t\tw.t = time.NewTimer(timeout)\n\t\t} else {\n\t\t\tw.t.Reset(timeout)\n\t\t}\n\t\tw.deadline = time.Now().Add(timeout)\n\t} else {\n\t\tw.deadline = zeroTime\n\t}\n\treturn w\n}\n\nfunc (c *pipelineConnClient) releasePipelineWork(w *pipelineWork) {\n\tif w.t != nil {\n\t\tw.t.Stop()\n\t}\n\tw.reqCopy.Reset()\n\tw.respCopy.Reset()\n\tw.req = nil\n\tw.resp = nil\n\tw.err = nil\n\tc.workPool.Put(w)\n}\n\n// Do performs the given http request and sets the corresponding response.\n//\n// Request must contain at least non-zero RequestURI with full url (including\n// scheme and host) or non-zero Host header + RequestURI.\n//\n// The function doesn't follow redirects. Use Get* for following redirects.\n//\n// Response is ignored if resp is nil.\n//\n// It is recommended obtaining req and resp via AcquireRequest\n// and AcquireResponse in performance-critical code.\nfunc (c *PipelineClient) Do(req *Request, resp *Response) error {\n\treturn c.getConnClient().Do(req, resp)\n}\n\nfunc (c *pipelineConnClient) Do(req *Request, resp *Response) error {\n\tc.init()\n\n\tif c.DisablePathNormalizing {\n\t\treq.URI().DisablePathNormalizing = true\n\t}\n\n\tuserAgentOld := req.Header.UserAgent()\n\tif len(userAgentOld) == 0 {\n\t\tuserAgent := c.Name\n\t\tif userAgent == \"\" && !c.NoDefaultUserAgentHeader {\n\t\t\tuserAgent = defaultUserAgent\n\t\t}\n\t\tif userAgent != \"\" {\n\t\t\treq.Header.userAgent = append(req.Header.userAgent[:0], userAgent...)\n\t\t}\n\t}\n\n\tw := c.acquirePipelineWork(0)\n\tw.req = req\n\tif resp != nil {\n\t\tresp.Header.disableNormalizing = c.DisableHeaderNamesNormalizing\n\t\tw.resp = resp\n\t} else {\n\t\tw.resp = &w.respCopy\n\t}\n\n\t// Put the request to outgoing queue\n\tselect {\n\tcase c.chW <- w:\n\tdefault:\n\t\t// Try substituting the oldest w with the current one.\n\t\tselect {\n\t\tcase wOld := <-c.chW:\n\t\t\twOld.err = ErrPipelineOverflow\n\t\t\twOld.done <- struct{}{}\n\t\tdefault:\n\t\t}\n\t\tselect {\n\t\tcase c.chW <- w:\n\t\tdefault:\n\t\t\tc.releasePipelineWork(w)\n\t\t\treturn ErrPipelineOverflow\n\t\t}\n\t}\n\n\t// Wait for the response\n\t<-w.done\n\terr := w.err\n\n\tc.releasePipelineWork(w)\n\n\treturn err\n}\n\nfunc (c *PipelineClient) getConnClient() *pipelineConnClient {\n\tc.connClientsLock.Lock()\n\tcc := c.getConnClientUnlocked()\n\tc.connClientsLock.Unlock()\n\treturn cc\n}\n\nfunc (c *PipelineClient) getConnClientUnlocked() *pipelineConnClient {\n\tif len(c.connClients) == 0 {\n\t\treturn c.newConnClient()\n\t}\n\n\t// Return the client with the minimum number of pending requests.\n\tminCC := c.connClients[0]\n\tminReqs := minCC.PendingRequests()\n\tif minReqs == 0 {\n\t\treturn minCC\n\t}\n\tfor i := 1; i < len(c.connClients); i++ {\n\t\tcc := c.connClients[i]\n\t\treqs := cc.PendingRequests()\n\t\tif reqs == 0 {\n\t\t\treturn cc\n\t\t}\n\t\tif reqs < minReqs {\n\t\t\tminCC = cc\n\t\t\tminReqs = reqs\n\t\t}\n\t}\n\n\tmaxConns := c.MaxConns\n\tif maxConns <= 0 {\n\t\tmaxConns = 1\n\t}\n\tif len(c.connClients) < maxConns {\n\t\treturn c.newConnClient()\n\t}\n\treturn minCC\n}\n\nfunc (c *PipelineClient) newConnClient() *pipelineConnClient {\n\tcc := &pipelineConnClient{\n\t\tAddr:                          c.Addr,\n\t\tName:                          c.Name,\n\t\tNoDefaultUserAgentHeader:      c.NoDefaultUserAgentHeader,\n\t\tMaxPendingRequests:            c.MaxPendingRequests,\n\t\tMaxBatchDelay:                 c.MaxBatchDelay,\n\t\tDial:                          c.Dial,\n\t\tDialDualStack:                 c.DialDualStack,\n\t\tDisableHeaderNamesNormalizing: c.DisableHeaderNamesNormalizing,\n\t\tDisablePathNormalizing:        c.DisablePathNormalizing,\n\t\tIsTLS:                         c.IsTLS,\n\t\tTLSConfig:                     c.TLSConfig,\n\t\tMaxIdleConnDuration:           c.MaxIdleConnDuration,\n\t\tReadBufferSize:                c.ReadBufferSize,\n\t\tWriteBufferSize:               c.WriteBufferSize,\n\t\tReadTimeout:                   c.ReadTimeout,\n\t\tWriteTimeout:                  c.WriteTimeout,\n\t\tLogger:                        c.Logger,\n\t}\n\tc.connClients = append(c.connClients, cc)\n\treturn cc\n}\n\n// ErrPipelineOverflow may be returned from PipelineClient.Do*\n// if the requests' queue is overflowed.\nvar ErrPipelineOverflow = errors.New(\"pipelined requests' queue has been overflowed. Increase MaxConns and/or MaxPendingRequests\")\n\n// DefaultMaxPendingRequests is the default value\n// for PipelineClient.MaxPendingRequests.\nconst DefaultMaxPendingRequests = 1024\n\nfunc (c *pipelineConnClient) init() {\n\tc.chLock.Lock()\n\tif c.chR == nil {\n\t\tmaxPendingRequests := c.MaxPendingRequests\n\t\tif maxPendingRequests <= 0 {\n\t\t\tmaxPendingRequests = DefaultMaxPendingRequests\n\t\t}\n\t\tc.chR = make(chan *pipelineWork, maxPendingRequests)\n\t\tif c.chW == nil {\n\t\t\tc.chW = make(chan *pipelineWork, maxPendingRequests)\n\t\t}\n\t\tgo func() {\n\t\t\t// Keep restarting the worker if it fails (connection errors for example).\n\t\t\tfor {\n\t\t\t\tif err := c.worker(); err != nil {\n\t\t\t\t\tc.logger().Printf(\"error in PipelineClient(%q): %v\", c.Addr, err)\n\t\t\t\t\tif netErr, ok := err.(net.Error); ok && netErr.Timeout() {\n\t\t\t\t\t\t// Throttle client reconnections on timeout errors\n\t\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tc.chLock.Lock()\n\t\t\t\t\tstop := len(c.chR) == 0 && len(c.chW) == 0\n\t\t\t\t\tif !stop {\n\t\t\t\t\t\tc.chR = nil\n\t\t\t\t\t\tc.chW = nil\n\t\t\t\t\t}\n\t\t\t\t\tc.chLock.Unlock()\n\n\t\t\t\t\tif stop {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\tc.chLock.Unlock()\n}\n\nfunc (c *pipelineConnClient) worker() error {\n\ttlsConfig := c.cachedTLSConfig()\n\tconn, err := dialAddr(c.Addr, c.Dial, nil, c.DialDualStack, c.IsTLS, tlsConfig, 0, c.WriteTimeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Start reader and writer\n\tstopW := make(chan struct{})\n\tdoneW := make(chan error)\n\tgo func() {\n\t\tdoneW <- c.writer(conn, stopW)\n\t}()\n\tstopR := make(chan struct{})\n\tdoneR := make(chan error)\n\tgo func() {\n\t\tdoneR <- c.reader(conn, stopR)\n\t}()\n\n\t// Wait until reader and writer are stopped\n\tselect {\n\tcase err = <-doneW:\n\t\tconn.Close()\n\t\tclose(stopR)\n\t\t<-doneR\n\tcase err = <-doneR:\n\t\tconn.Close()\n\t\tclose(stopW)\n\t\t<-doneW\n\t}\n\n\t// Notify pending readers\n\tfor len(c.chR) > 0 {\n\t\tw := <-c.chR\n\t\tw.err = errPipelineConnStopped\n\t\tw.done <- struct{}{}\n\t}\n\n\treturn err\n}\n\nfunc (c *pipelineConnClient) cachedTLSConfig() *tls.Config {\n\tif !c.IsTLS {\n\t\treturn nil\n\t}\n\n\tc.tlsConfigLock.Lock()\n\tcfg := c.tlsConfig\n\tif cfg == nil {\n\t\tcfg = newClientTLSConfig(c.TLSConfig, c.Addr)\n\t\tc.tlsConfig = cfg\n\t}\n\tc.tlsConfigLock.Unlock()\n\n\treturn cfg\n}\n\nfunc (c *pipelineConnClient) writer(conn net.Conn, stopCh <-chan struct{}) error {\n\twriteBufferSize := c.WriteBufferSize\n\tif writeBufferSize <= 0 {\n\t\twriteBufferSize = defaultWriteBufferSize\n\t}\n\tbw := bufio.NewWriterSize(conn, writeBufferSize)\n\tdefer bw.Flush()\n\tchR := c.chR\n\tchW := c.chW\n\twriteTimeout := c.WriteTimeout\n\n\tmaxIdleConnDuration := c.MaxIdleConnDuration\n\tif maxIdleConnDuration <= 0 {\n\t\tmaxIdleConnDuration = DefaultMaxIdleConnDuration\n\t}\n\tmaxBatchDelay := c.MaxBatchDelay\n\n\tvar (\n\t\tstopTimer      = time.NewTimer(time.Hour)\n\t\tflushTimer     = time.NewTimer(time.Hour)\n\t\tflushTimerCh   <-chan time.Time\n\t\tinstantTimerCh = make(chan time.Time)\n\n\t\tw   *pipelineWork\n\t\terr error\n\t)\n\tclose(instantTimerCh)\n\tfor {\n\tagainChW:\n\t\tselect {\n\t\tcase w = <-chW:\n\t\t\t// Fast path: len(chW) > 0\n\t\tdefault:\n\t\t\t// Slow path\n\t\t\tstopTimer.Reset(maxIdleConnDuration)\n\t\t\tselect {\n\t\t\tcase w = <-chW:\n\t\t\tcase <-stopTimer.C:\n\t\t\t\treturn nil\n\t\t\tcase <-stopCh:\n\t\t\t\treturn nil\n\t\t\tcase <-flushTimerCh:\n\t\t\t\tif err = bw.Flush(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tflushTimerCh = nil\n\t\t\t\tgoto againChW\n\t\t\t}\n\t\t}\n\n\t\tif !w.deadline.IsZero() && time.Since(w.deadline) >= 0 {\n\t\t\tw.err = ErrTimeout\n\t\t\tw.done <- struct{}{}\n\t\t\tcontinue\n\t\t}\n\n\t\tw.resp.ParseNetConn(conn)\n\n\t\tif writeTimeout > 0 {\n\t\t\t// Set Deadline every time, since golang has fixed the performance issue\n\t\t\t// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details\n\t\t\tcurrentTime := time.Now()\n\t\t\tif err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {\n\t\t\t\tw.err = err\n\t\t\t\tw.done <- struct{}{}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err = w.req.Write(bw); err != nil {\n\t\t\tw.err = err\n\t\t\tw.done <- struct{}{}\n\t\t\treturn err\n\t\t}\n\t\tif flushTimerCh == nil && (len(chW) == 0 || len(chR) == cap(chR)) {\n\t\t\tif maxBatchDelay > 0 {\n\t\t\t\tflushTimer.Reset(maxBatchDelay)\n\t\t\t\tflushTimerCh = flushTimer.C\n\t\t\t} else {\n\t\t\t\tflushTimerCh = instantTimerCh\n\t\t\t}\n\t\t}\n\n\tagainChR:\n\t\tselect {\n\t\tcase chR <- w:\n\t\t\t// Fast path: len(chR) < cap(chR)\n\t\tdefault:\n\t\t\t// Slow path\n\t\t\tselect {\n\t\t\tcase chR <- w:\n\t\t\tcase <-stopCh:\n\t\t\t\tw.err = errPipelineConnStopped\n\t\t\t\tw.done <- struct{}{}\n\t\t\t\treturn nil\n\t\t\tcase <-flushTimerCh:\n\t\t\t\tif err = bw.Flush(); err != nil {\n\t\t\t\t\tw.err = err\n\t\t\t\t\tw.done <- struct{}{}\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tflushTimerCh = nil\n\t\t\t\tgoto againChR\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *pipelineConnClient) reader(conn net.Conn, stopCh <-chan struct{}) error {\n\treadBufferSize := c.ReadBufferSize\n\tif readBufferSize <= 0 {\n\t\treadBufferSize = defaultReadBufferSize\n\t}\n\tbr := bufio.NewReaderSize(conn, readBufferSize)\n\tchR := c.chR\n\treadTimeout := c.ReadTimeout\n\n\tvar (\n\t\tw   *pipelineWork\n\t\terr error\n\t)\n\tfor {\n\t\tselect {\n\t\tcase w = <-chR:\n\t\t\t// Fast path: len(chR) > 0\n\t\tdefault:\n\t\t\t// Slow path\n\t\t\tselect {\n\t\t\tcase w = <-chR:\n\t\t\tcase <-stopCh:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\tif readTimeout > 0 {\n\t\t\t// Set Deadline every time, since golang has fixed the performance issue\n\t\t\t// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details\n\t\t\tcurrentTime := time.Now()\n\t\t\tif err = conn.SetReadDeadline(currentTime.Add(readTimeout)); err != nil {\n\t\t\t\tw.err = err\n\t\t\t\tw.done <- struct{}{}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err = w.resp.Read(br); err != nil {\n\t\t\tw.err = err\n\t\t\tw.done <- struct{}{}\n\t\t\treturn err\n\t\t}\n\n\t\tw.done <- struct{}{}\n\t}\n}\n\nfunc (c *pipelineConnClient) logger() Logger {\n\tif c.Logger != nil {\n\t\treturn c.Logger\n\t}\n\treturn defaultLogger\n}\n\n// PendingRequests returns the current number of pending requests pipelined\n// to the server.\n//\n// This number may exceed MaxPendingRequests*MaxConns by up to two times, since\n// each connection to the server may keep up to MaxPendingRequests requests\n// in the queue before sending them to the server.\n//\n// This function may be used for balancing load among multiple PipelineClient\n// instances.\nfunc (c *PipelineClient) PendingRequests() int {\n\tc.connClientsLock.Lock()\n\tn := 0\n\tfor _, cc := range c.connClients {\n\t\tn += cc.PendingRequests()\n\t}\n\tc.connClientsLock.Unlock()\n\treturn n\n}\n\nfunc (c *pipelineConnClient) PendingRequests() int {\n\tc.init()\n\n\tc.chLock.Lock()\n\tn := len(c.chR) + len(c.chW)\n\tc.chLock.Unlock()\n\treturn n\n}\n\nvar errPipelineConnStopped = errors.New(\"pipeline connection has been stopped\")\n\nvar DefaultTransport RoundTripper = &transport{}\n\ntype transport struct{}\n\nfunc (t *transport) RoundTrip(hc *HostClient, req *Request, resp *Response) (retry bool, err error) {\n\tcustomSkipBody := resp.SkipBody\n\tcustomStreamBody := resp.StreamBody\n\n\tvar deadline time.Time\n\tif req.timeout > 0 {\n\t\tdeadline = time.Now().Add(req.timeout)\n\t}\n\n\tcc, err := hc.AcquireConn(req.timeout, req.ConnectionClose())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tconn := cc.c\n\n\tresp.ParseNetConn(conn)\n\n\twriteDeadline := deadline\n\tif hc.WriteTimeout > 0 {\n\t\ttmpWriteDeadline := time.Now().Add(hc.WriteTimeout)\n\t\tif writeDeadline.IsZero() || tmpWriteDeadline.Before(writeDeadline) {\n\t\t\twriteDeadline = tmpWriteDeadline\n\t\t}\n\t}\n\n\tif err = conn.SetWriteDeadline(writeDeadline); err != nil {\n\t\thc.CloseConn(cc)\n\t\treturn true, err\n\t}\n\n\tresetConnection := false\n\tif hc.MaxConnDuration > 0 && time.Since(cc.createdTime) > hc.MaxConnDuration && !req.ConnectionClose() {\n\t\treq.SetConnectionClose()\n\t\tresetConnection = true\n\t}\n\n\tbw := hc.AcquireWriter(conn)\n\terr = req.Write(bw)\n\n\tif resetConnection {\n\t\treq.Header.ResetConnectionClose()\n\t}\n\n\tif err == nil {\n\t\terr = bw.Flush()\n\t}\n\thc.ReleaseWriter(bw)\n\n\t// Return ErrTimeout on any timeout.\n\tif x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {\n\t\terr = ErrTimeout\n\t}\n\n\tif err != nil {\n\t\thc.CloseConn(cc)\n\t\treturn true, err\n\t}\n\n\treadDeadline := deadline\n\tif hc.ReadTimeout > 0 {\n\t\ttmpReadDeadline := time.Now().Add(hc.ReadTimeout)\n\t\tif readDeadline.IsZero() || tmpReadDeadline.Before(readDeadline) {\n\t\t\treadDeadline = tmpReadDeadline\n\t\t}\n\t}\n\n\tif err = conn.SetReadDeadline(readDeadline); err != nil {\n\t\thc.CloseConn(cc)\n\t\treturn true, err\n\t}\n\n\tif customSkipBody || req.Header.IsHead() {\n\t\tresp.SkipBody = true\n\t}\n\tif hc.DisableHeaderNamesNormalizing {\n\t\tresp.Header.DisableNormalizing()\n\t}\n\n\tbr := hc.AcquireReader(conn)\n\terr = resp.ReadLimitBody(br, hc.MaxResponseBodySize)\n\tif err != nil {\n\t\thc.ReleaseReader(br)\n\t\thc.CloseConn(cc)\n\t\t// Don't retry in case of ErrBodyTooLarge since we will just get the same again.\n\t\tneedRetry := err != ErrBodyTooLarge\n\t\treturn needRetry, err\n\t}\n\n\tcloseConn := resetConnection || req.ConnectionClose() || resp.ConnectionClose()\n\tif customStreamBody && resp.bodyStream != nil {\n\t\trbs := resp.bodyStream\n\t\tresp.bodyStream = newCloseReaderWithError(rbs, func(wErr error) error {\n\t\t\thc.ReleaseReader(br)\n\t\t\tif r, ok := rbs.(*requestStream); ok {\n\t\t\t\treleaseRequestStream(r)\n\t\t\t}\n\t\t\tif closeConn || resp.ConnectionClose() || wErr != nil {\n\t\t\t\thc.CloseConn(cc)\n\t\t\t} else {\n\t\t\t\thc.ReleaseConn(cc)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\treturn false, nil\n\t}\n\thc.ReleaseReader(br)\n\n\tif closeConn {\n\t\thc.CloseConn(cc)\n\t} else {\n\t\thc.ReleaseConn(cc)\n\t}\n\treturn false, nil\n}\n"
  },
  {
    "path": "client_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"log\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc ExampleHostClient() {\n\t// Prepare a client, which fetches webpages via HTTP proxy listening\n\t// on the localhost:8080.\n\tc := &fasthttp.HostClient{\n\t\tAddr: \"localhost:8080\",\n\t}\n\n\t// Fetch google page via local proxy.\n\tstatusCode, body, err := c.Get(nil, \"http://google.com/foo/bar\")\n\tif err != nil {\n\t\tlog.Fatalf(\"Error when loading google page through local proxy: %v\", err)\n\t}\n\tif statusCode != fasthttp.StatusOK {\n\t\tlog.Fatalf(\"Unexpected status code: %d. Expecting %d\", statusCode, fasthttp.StatusOK)\n\t}\n\tuseResponseBody(body)\n\n\t// Fetch foobar page via local proxy. Reuse body buffer.\n\tstatusCode, body, err = c.Get(body, \"http://foobar.com/google/com\")\n\tif err != nil {\n\t\tlog.Fatalf(\"Error when loading foobar page through local proxy: %v\", err)\n\t}\n\tif statusCode != fasthttp.StatusOK {\n\t\tlog.Fatalf(\"Unexpected status code: %d. Expecting %d\", statusCode, fasthttp.StatusOK)\n\t}\n\tuseResponseBody(body)\n}\n\nfunc useResponseBody(body []byte) {\n\t// Do something with body :)\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nfunc TestCloseIdleConnections(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\tif _, _, err := c.Get(nil, \"http://google.com\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconnsLen := func() int {\n\t\tc.mLock.Lock()\n\t\tdefer c.mLock.Unlock()\n\n\t\tif _, ok := c.m[\"google.com\"]; !ok {\n\t\t\treturn 0\n\t\t}\n\n\t\tc.m[\"google.com\"].connsLock.Lock()\n\t\tdefer c.m[\"google.com\"].connsLock.Unlock()\n\n\t\treturn len(c.m[\"google.com\"].conns)\n\t}\n\n\tif conns := connsLen(); conns > 1 {\n\t\tt.Errorf(\"expected 1 conns got %d\", conns)\n\t}\n\n\tc.CloseIdleConnections()\n\n\tif conns := connsLen(); conns > 0 {\n\t\tt.Errorf(\"expected 0 conns got %d\", conns)\n\t}\n}\n\nfunc TestPipelineClientSetUserAgent(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientSetUserAgent(t, 0)\n}\n\nfunc TestPipelineClientSetUserAgentTimeout(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientSetUserAgent(t, time.Second)\n}\n\nfunc testPipelineClientSetUserAgent(t *testing.T, timeout time.Duration) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\tuserAgentSeen := \"\"\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tuserAgentSeen = string(ctx.UserAgent())\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tuserAgent := \"I'm not fasthttp\"\n\tc := &HostClient{\n\t\tName: userAgent,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\tres := AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\tvar err error\n\tif timeout <= 0 {\n\t\terr = c.Do(req, res)\n\t} else {\n\t\terr = c.DoTimeout(req, res, timeout)\n\t}\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif userAgentSeen != userAgent {\n\t\tt.Fatalf(\"User-Agent defers %q != %q\", userAgentSeen, userAgent)\n\t}\n}\n\nfunc TestHostClientNegativeTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &HostClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.DoTimeout(req, nil, -time.Second); err != ErrTimeout {\n\t\tt.Fatalf(\"expected ErrTimeout error got: %+v\", err)\n\t}\n\tif err := c.DoDeadline(req, nil, time.Now().Add(-time.Second)); err != ErrTimeout {\n\t\tt.Fatalf(\"expected ErrTimeout error got: %+v\", err)\n\t}\n\tln.Close()\n}\n\nfunc TestDoDeadlineRetry(t *testing.T) {\n\tt.Parallel()\n\n\tvar tries atomic.Int32\n\tdone := make(chan struct{})\n\n\tln := fasthttputil.NewInmemoryListener()\n\tgo func() {\n\t\tfor {\n\t\t\tc, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tclose(done)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttries.Add(1)\n\t\t\tbr := bufio.NewReader(c)\n\t\t\t(&RequestHeader{}).Read(br)                      //nolint:errcheck\n\t\t\t(&Request{}).readBodyStream(br, 0, false, false) //nolint:errcheck\n\t\t\tif tries.Load() == 1 {\n\t\t\t\ttime.Sleep(time.Millisecond * 10)\n\t\t\t} else {\n\t\t\t\ttime.Sleep(time.Millisecond * 200)\n\t\t\t}\n\t\t\tc.Close()\n\t\t}\n\t}()\n\tc := &HostClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.DoDeadline(req, nil, time.Now().Add(time.Millisecond*200)); err != ErrTimeout {\n\t\tt.Fatalf(\"expected ErrTimeout error got: %+v\", err)\n\t}\n\tln.Close()\n\t<-done\n\tif tr := tries.Load(); tr != 2 {\n\t\tt.Fatalf(\"expected 2 tries got %d\", tr)\n\t}\n}\n\nfunc TestPipelineClientIssue832(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\treq := AcquireRequest()\n\t// Don't defer ReleaseRequest as we use it in a goroutine that might not be done at the end.\n\n\treq.SetHost(\"example.com\")\n\n\tres := AcquireResponse()\n\t// Don't defer ReleaseResponse as we use it in a goroutine that might not be done at the end.\n\n\tclient := PipelineClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tReadTimeout: time.Millisecond * 10,\n\t\tLogger:      &testLogger{}, // Ignore log output.\n\t}\n\n\tattempts := 10\n\tgo func() {\n\t\tfor range attempts {\n\t\t\tc, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif c != nil {\n\t\t\t\tgo func() {\n\t\t\t\t\ttime.Sleep(time.Millisecond * 50)\n\t\t\t\t\tc.Close()\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\t}()\n\n\tdone := make(chan int)\n\tgo func() {\n\t\tdefer close(done)\n\n\t\tfor range attempts {\n\t\t\tif err := client.Do(req, res); err == nil {\n\t\t\t\tt.Error(\"error expected\")\n\t\t\t}\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-time.After(time.Second * 2):\n\t\tt.Fatal(\"PipelineClient did not restart worker\")\n\tcase <-done:\n\t}\n}\n\nfunc TestClientInvalidURI(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\tvar requests atomic.Int64\n\ts := &Server{\n\t\tHandler: func(_ *RequestCtx) {\n\t\t\trequests.Add(1)\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq, res := AcquireRequest(), AcquireResponse()\n\tdefer func() {\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t}()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\\r\\n\\r\\nGET /\\r\\n\\r\\n\")\n\terr := c.Do(req, res)\n\tif err == nil {\n\t\tt.Fatal(\"expected error (missing required Host header in request)\")\n\t}\n\tif n := requests.Load(); n != 0 {\n\t\tt.Fatalf(\"0 requests expected, got %d\", n)\n\t}\n}\n\nfunc TestClientGetWithBody(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tbody := ctx.Request.Body()\n\t\t\tctx.Write(body) //nolint:errcheck\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq, res := AcquireRequest(), AcquireResponse()\n\tdefer func() {\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t}()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\treq.SetBodyString(\"test\")\n\terr := c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res.Body()) == 0 {\n\t\tt.Fatal(\"missing request body\")\n\t}\n}\n\nfunc TestClientURLAuth(t *testing.T) {\n\tt.Parallel()\n\n\tcases := map[string]string{\n\t\t\"user:pass@\": \"Basic dXNlcjpwYXNz\",\n\t\t\"foo:@\":      \"Basic Zm9vOg==\",\n\t\t\":@\":         \"\",\n\t\t\"@\":          \"\",\n\t\t\"\":           \"\",\n\t}\n\n\tch := make(chan string, 1)\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tch <- string(ctx.Request.Header.Peek(HeaderAuthorization))\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\tfor up, expected := range cases {\n\t\treq := AcquireRequest()\n\t\treq.Header.SetMethod(MethodGet)\n\t\treq.SetRequestURI(\"http://\" + up + \"example.com/foo/bar\")\n\t\tif err := c.Do(req, nil); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tval := <-ch\n\n\t\tif val != expected {\n\t\t\tt.Fatalf(\"wrong %q header: %q expected %q\", HeaderAuthorization, val, expected)\n\t\t}\n\t}\n}\n\nfunc TestClientNilResp(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.Do(req, nil); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c.DoTimeout(req, nil, time.Second); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tln.Close()\n}\n\nfunc TestClientNegativeTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.DoTimeout(req, nil, -time.Second); err != ErrTimeout {\n\t\tt.Fatalf(\"expected ErrTimeout error got: %+v\", err)\n\t}\n\tif err := c.DoDeadline(req, nil, time.Now().Add(-time.Second)); err != ErrTimeout {\n\t\tt.Fatalf(\"expected ErrTimeout error got: %+v\", err)\n\t}\n\tln.Close()\n}\n\nfunc TestPipelineClientNilResp(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &PipelineClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://example.com\")\n\tif err := c.Do(req, nil); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c.DoTimeout(req, nil, time.Second); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := c.DoDeadline(req, nil, time.Now().Add(time.Second)); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClientParseConn(t *testing.T) {\n\tt.Parallel()\n\n\tnetwork := \"tcp\"\n\tln, _ := net.Listen(network, \"127.0.0.1:0\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\thost := ln.Addr().String()\n\tc := &Client{}\n\treq, res := AcquireRequest(), AcquireResponse()\n\tdefer func() {\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t}()\n\treq.SetRequestURI(\"http://\" + host + \"\")\n\tif err := c.Do(req, res); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif res.RemoteAddr().Network() != network {\n\t\tt.Fatalf(\"req RemoteAddr parse network fail: %q, hope: %q\", res.RemoteAddr().Network(), network)\n\t}\n\tif host != res.RemoteAddr().String() {\n\t\tt.Fatalf(\"req RemoteAddr parse addr fail: %q, hope: %q\", res.RemoteAddr().String(), host)\n\t}\n\n\tif !regexp.MustCompile(`^127\\.0\\.0\\.1:\\d{4,5}$`).MatchString(res.LocalAddr().String()) {\n\t\tt.Fatalf(\"res LocalAddr addr match fail: %q, hope match: %q\", res.LocalAddr().String(), \"^127.0.0.1:[0-9]{4,5}$\")\n\t}\n}\n\nfunc TestClientPostArgs(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tbody := ctx.Request.Body()\n\t\t\tif len(body) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tctx.Write(body) //nolint:errcheck\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq, res := AcquireRequest(), AcquireResponse()\n\tdefer func() {\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t}()\n\targs := req.PostArgs()\n\targs.Add(\"addhttp2\", \"support\")\n\targs.Add(\"fast\", \"http\")\n\treq.Header.SetMethod(MethodPost)\n\treq.SetRequestURI(\"http://make.fasthttp.great?again\")\n\terr := c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res.Body()) == 0 {\n\t\tt.Fatal(\"cannot set args as body\")\n\t}\n}\n\nfunc TestClientRedirectSameSchema(t *testing.T) {\n\tt.Parallel()\n\n\tlistenHTTPS1 := testClientRedirectListener(t, true)\n\tdefer listenHTTPS1.Close()\n\n\tlistenHTTPS2 := testClientRedirectListener(t, true)\n\tdefer listenHTTPS2.Close()\n\n\tsHTTPS1 := testClientRedirectChangingSchemaServer(t, listenHTTPS1, listenHTTPS1, true)\n\tdefer sHTTPS1.Stop()\n\n\tsHTTPS2 := testClientRedirectChangingSchemaServer(t, listenHTTPS2, listenHTTPS2, false)\n\tdefer sHTTPS2.Stop()\n\n\tdestURL := fmt.Sprintf(\"https://%s/baz\", listenHTTPS1.Addr().String())\n\n\turlParsed, err := url.Parse(destURL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\treqClient := &HostClient{\n\t\tIsTLS: true,\n\t\tAddr:  urlParsed.Host,\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\n\tstatusCode, _, err := reqClient.GetTimeout(nil, destURL, 4000*time.Millisecond)\n\tif err != nil {\n\t\tt.Fatalf(\"HostClient error: %v\", err)\n\t\treturn\n\t}\n\n\tif statusCode != 200 {\n\t\tt.Fatalf(\"HostClient error code response %d\", statusCode)\n\t\treturn\n\t}\n}\n\nfunc TestClientRedirectClientChangingSchemaHttp2Https(t *testing.T) {\n\tt.Parallel()\n\n\tlistenHTTPS := testClientRedirectListener(t, true)\n\tdefer listenHTTPS.Close()\n\n\tlistenHTTP := testClientRedirectListener(t, false)\n\tdefer listenHTTP.Close()\n\n\tsHTTPS := testClientRedirectChangingSchemaServer(t, listenHTTPS, listenHTTP, true)\n\tdefer sHTTPS.Stop()\n\n\tsHTTP := testClientRedirectChangingSchemaServer(t, listenHTTPS, listenHTTP, false)\n\tdefer sHTTP.Stop()\n\n\tdestURL := fmt.Sprintf(\"http://%s/baz\", listenHTTP.Addr().String())\n\n\treqClient := &Client{\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\n\tstatusCode, _, err := reqClient.GetTimeout(nil, destURL, 4000*time.Millisecond)\n\tif err != nil {\n\t\tt.Fatalf(\"HostClient error: %v\", err)\n\t\treturn\n\t}\n\n\tif statusCode != 200 {\n\t\tt.Fatalf(\"HostClient error code response %d\", statusCode)\n\t\treturn\n\t}\n}\n\nfunc TestClientRedirectHostClientChangingSchemaHttp2Https(t *testing.T) {\n\tt.Parallel()\n\n\tlistenHTTPS := testClientRedirectListener(t, true)\n\tdefer listenHTTPS.Close()\n\n\tlistenHTTP := testClientRedirectListener(t, false)\n\tdefer listenHTTP.Close()\n\n\tsHTTPS := testClientRedirectChangingSchemaServer(t, listenHTTPS, listenHTTP, true)\n\tdefer sHTTPS.Stop()\n\n\tsHTTP := testClientRedirectChangingSchemaServer(t, listenHTTPS, listenHTTP, false)\n\tdefer sHTTP.Stop()\n\n\tdestURL := fmt.Sprintf(\"http://%s/baz\", listenHTTP.Addr().String())\n\n\turlParsed, err := url.Parse(destURL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\treqClient := &HostClient{\n\t\tAddr: urlParsed.Host,\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\n\t_, _, err = reqClient.GetTimeout(nil, destURL, 4000*time.Millisecond)\n\tif err != ErrHostClientRedirectToDifferentScheme {\n\t\tt.Fatal(\"expected HostClient error\")\n\t}\n}\n\nfunc testClientRedirectListener(t *testing.T, isTLS bool) net.Listener {\n\tvar ln net.Listener\n\tvar err error\n\tvar tlsConfig *tls.Config\n\n\tif isTLS {\n\t\tcertData, keyData, kerr := GenerateTestCertificate(\"localhost\")\n\t\tif kerr != nil {\n\t\t\tt.Fatal(kerr)\n\t\t}\n\n\t\tcert, kerr := tls.X509KeyPair(certData, keyData)\n\t\tif kerr != nil {\n\t\t\tt.Fatal(kerr)\n\t\t}\n\n\t\ttlsConfig = &tls.Config{\n\t\t\tCertificates: []tls.Certificate{cert},\n\t\t}\n\t\tln, err = tls.Listen(\"tcp\", \"localhost:0\", tlsConfig)\n\t} else {\n\t\tln, err = net.Listen(\"tcp\", \"localhost:0\")\n\t}\n\n\tif err != nil {\n\t\tt.Fatalf(\"cannot listen isTLS %v: %v\", isTLS, err)\n\t}\n\n\treturn ln\n}\n\nfunc testClientRedirectChangingSchemaServer(t *testing.T, https, http net.Listener, isTLS bool) *testEchoServer {\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif ctx.IsTLS() {\n\t\t\t\tctx.SetStatusCode(200)\n\t\t\t} else {\n\t\t\t\tctx.Redirect(fmt.Sprintf(\"https://%s/baz\", https.Addr().String()), 301)\n\t\t\t}\n\t\t},\n\t}\n\n\tvar ln net.Listener\n\tif isTLS {\n\t\tln = https\n\t} else {\n\t\tln = http\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := s.Serve(ln)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error returned from Serve(): %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\treturn &testEchoServer{\n\t\ts:  s,\n\t\tln: ln,\n\t\tch: ch,\n\t\tt:  t,\n\t}\n}\n\nfunc TestClientHeaderCase(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tc.Write([]byte(\"HTTP/1.1 200 OK\\r\\n\" + //nolint:errcheck\n\t\t\t\"content-type: text/plain\\r\\n\" +\n\t\t\t\"transfer-encoding: chunked\\r\\n\\r\\n\" +\n\t\t\t\"24\\r\\nThis is the data in the first chunk \\r\\n\" +\n\t\t\t\"1B\\r\\nand this is the second one \\r\\n\" +\n\t\t\t\"0\\r\\n\\r\\n\",\n\t\t))\n\t}()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tReadTimeout: time.Millisecond * 10,\n\n\t\t// Even without name normalizing we should parse headers correctly.\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\tcode, body, err := c.Get(nil, \"http://example.com\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif code != 200 {\n\t\tt.Errorf(\"expected status code 200 got %d\", code)\n\t}\n\tif string(body) != \"This is the data in the first chunk and this is the second one \" {\n\t\tt.Errorf(\"wrong body: %q\", body)\n\t}\n}\n\nfunc TestClientReadTimeout(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ttimeout := false\n\ts := &Server{\n\t\tHandler: func(_ *RequestCtx) {\n\t\t\tif timeout {\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t} else {\n\t\t\t\ttimeout = true\n\t\t\t}\n\t\t},\n\t\tLogger: &testLogger{}, // Don't print closed pipe errors.\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tc := &HostClient{\n\t\tReadTimeout:               time.Millisecond * 400,\n\t\tMaxIdemponentCallAttempts: 1,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\treq := AcquireRequest()\n\tres := AcquireResponse()\n\n\treq.SetRequestURI(\"http://localhost\")\n\n\t// Setting Connection: Close will make the connection be\n\t// returned to the pool.\n\treq.SetConnectionClose()\n\n\tif err := c.Do(req, res); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tReleaseRequest(req)\n\tReleaseResponse(res)\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\treq := AcquireRequest()\n\t\tres := AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://localhost\")\n\t\treq.SetConnectionClose()\n\n\t\tif err := c.Do(req, res); err != ErrTimeout {\n\t\t\tt.Errorf(\"expected ErrTimeout got %#v\", err)\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(res)\n\t\tclose(done)\n\t}()\n\n\tselect {\n\tcase <-done:\n\t\t// This shouldn't take longer than the timeout times the number of requests it is going to try to do.\n\t\t// Give it an extra second just to be sure.\n\tcase <-time.After(c.ReadTimeout*time.Duration(c.MaxIdemponentCallAttempts) + time.Second):\n\t\tt.Fatal(\"Client.ReadTimeout didn't work\")\n\t}\n}\n\nfunc TestClientDefaultUserAgent(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tuserAgentSeen := \"\"\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tuserAgentSeen = string(ctx.UserAgent())\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\tres := AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif userAgentSeen != defaultUserAgent {\n\t\tt.Fatalf(\"User-Agent defers %q != %q\", userAgentSeen, defaultUserAgent)\n\t}\n}\n\nfunc TestClientSetUserAgent(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tuserAgentSeen := \"\"\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tuserAgentSeen = string(ctx.UserAgent())\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tuserAgent := \"I'm not fasthttp\"\n\tc := &Client{\n\t\tName: userAgent,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\tres := AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif userAgentSeen != userAgent {\n\t\tt.Fatalf(\"User-Agent defers %q != %q\", userAgentSeen, userAgent)\n\t}\n}\n\nfunc TestClientNoUserAgent(t *testing.T) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\tuserAgentSeen := \"\"\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tuserAgentSeen = string(ctx.UserAgent())\n\t\t},\n\t}\n\tgo s.Serve(ln) //nolint:errcheck\n\n\tc := &Client{\n\t\tNoDefaultUserAgentHeader: true,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\treq := AcquireRequest()\n\tres := AcquireResponse()\n\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif userAgentSeen != \"\" {\n\t\tt.Fatalf(\"User-Agent wrong %q != %q\", userAgentSeen, \"\")\n\t}\n}\n\nfunc TestClientDoWithCustomHeaders(t *testing.T) {\n\tt.Parallel()\n\n\t// make sure that the client sends all the request headers and body.\n\tln := fasthttputil.NewInmemoryListener()\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\turi := \"/foo/bar/baz?a=b&cd=12\"\n\theaders := map[string]string{\n\t\t\"Foo\":          \"bar\",\n\t\t\"Host\":         \"example.com\",\n\t\t\"Content-Type\": \"asdfsdf\",\n\t\t\"a-b-c-d-f\":    \"\",\n\t}\n\tbody := \"request body\"\n\n\tch := make(chan error)\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot accept client connection: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tbr := bufio.NewReader(conn)\n\n\t\tvar req Request\n\t\tif err = req.Read(br); err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot read client request: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tif string(req.Header.Method()) != MethodPost {\n\t\t\tch <- fmt.Errorf(\"unexpected request method: %q. Expecting %q\", req.Header.Method(), MethodPost)\n\t\t\treturn\n\t\t}\n\t\treqURI := req.RequestURI()\n\t\tif string(reqURI) != uri {\n\t\t\tch <- fmt.Errorf(\"unexpected request uri: %q. Expecting %q\", reqURI, uri)\n\t\t\treturn\n\t\t}\n\t\tfor k, v := range headers {\n\t\t\thv := req.Header.Peek(k)\n\t\t\tif string(hv) != v {\n\t\t\t\tch <- fmt.Errorf(\"unexpected value for header %q: %q. Expecting %q\", k, hv, v)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tcl := req.Header.ContentLength()\n\t\tif cl != len(body) {\n\t\t\tch <- fmt.Errorf(\"unexpected content-length %d. Expecting %d\", cl, len(body))\n\t\t\treturn\n\t\t}\n\t\treqBody := req.Body()\n\t\tif string(reqBody) != body {\n\t\t\tch <- fmt.Errorf(\"unexpected request body: %q. Expecting %q\", reqBody, body)\n\t\t\treturn\n\t\t}\n\n\t\tvar resp Response\n\t\tbw := bufio.NewWriter(conn)\n\t\tif err = resp.Write(bw); err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot send response: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tif err = bw.Flush(); err != nil {\n\t\t\tch <- fmt.Errorf(\"cannot flush response: %w\", err)\n\t\t\treturn\n\t\t}\n\n\t\tch <- nil\n\t}()\n\n\tvar req Request\n\treq.Header.SetMethod(MethodPost)\n\treq.SetRequestURI(uri)\n\tfor k, v := range headers {\n\t\treq.Header.Set(k, v)\n\t}\n\treq.SetBodyString(body)\n\n\tvar resp Response\n\n\terr := c.DoTimeout(&req, &resp, time.Second)\n\tif err != nil {\n\t\tt.Fatalf(\"error when doing request: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(5 * time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestPipelineClientDoSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDoConcurrent(t, 1, 0, 0)\n}\n\nfunc TestPipelineClientDoConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDoConcurrent(t, 10, 0, 1)\n}\n\nfunc TestPipelineClientDoBatchDelayConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDoConcurrent(t, 10, 5*time.Millisecond, 1)\n}\n\nfunc TestPipelineClientDoBatchDelayConcurrentMultiConn(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDoConcurrent(t, 10, 5*time.Millisecond, 3)\n}\n\nfunc testPipelineClientDoConcurrent(t *testing.T, concurrency int, maxBatchDelay time.Duration, maxConns int) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t},\n\t}\n\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &PipelineClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConns:           maxConns,\n\t\tMaxPendingRequests: concurrency,\n\t\tMaxBatchDelay:      maxBatchDelay,\n\t\tLogger:             &testLogger{},\n\t}\n\n\tclientStopCh := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\ttestPipelineClientDo(t, c)\n\t\t\tclientStopCh <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-clientStopCh:\n\t\tcase <-time.After(3 * time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tif c.PendingRequests() != 0 {\n\t\tt.Fatalf(\"unexpected number of pending requests: %d. Expecting zero\", c.PendingRequests())\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc testPipelineClientDo(t *testing.T, c *PipelineClient) {\n\tvar err error\n\treq := AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\tresp := AcquireResponse()\n\tfor i := range 10 {\n\t\tif i&1 == 0 {\n\t\t\terr = c.DoTimeout(req, resp, time.Second)\n\t\t} else {\n\t\t\terr = c.Do(req, resp)\n\t\t}\n\t\tif err != nil {\n\t\t\tif err == ErrPipelineOverflow {\n\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"unexpected error on iteration %d: %v\", i, err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\t\tbody := string(resp.Body())\n\t\tif body != \"OK\" {\n\t\t\tt.Errorf(\"unexpected body: %q. Expecting %q\", body, \"OK\")\n\t\t}\n\n\t\t// sleep for a while, so the connection to the host may expire.\n\t\tif i%5 == 0 {\n\t\t\ttime.Sleep(30 * time.Millisecond)\n\t\t}\n\t}\n\tReleaseRequest(req)\n\tReleaseResponse(resp)\n}\n\nfunc TestPipelineClientDoDisableHeaderNamesNormalizing(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDisableHeaderNamesNormalizing(t, 0)\n}\n\nfunc TestPipelineClientDoTimeoutDisableHeaderNamesNormalizing(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipelineClientDisableHeaderNamesNormalizing(t, time.Second)\n}\n\nfunc testPipelineClientDisableHeaderNamesNormalizing(t *testing.T, timeout time.Duration) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Response.Header.Set(\"foo-BAR\", \"baz\")\n\t\t},\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &PipelineClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\tvar req Request\n\treq.SetRequestURI(\"http://aaaai.com/bsdf?sddfsd\")\n\tvar resp Response\n\tfor range 5 {\n\t\tif timeout > 0 {\n\t\t\tif err := c.DoTimeout(&req, &resp, timeout); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := c.Do(&req, &resp); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t}\n\t\thv := resp.Header.Peek(\"foo-BAR\")\n\t\tif string(hv) != \"baz\" {\n\t\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"baz\")\n\t\t}\n\t\thv = resp.Header.Peek(\"Foo-Bar\")\n\t\tif len(hv) > 0 {\n\t\t\tt.Fatalf(\"unexpected non-empty header value %q\", hv)\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestClientDoTimeoutDisableHeaderNamesNormalizing(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Response.Header.Set(\"foo-BAR\", \"baz\")\n\t\t},\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\tvar req Request\n\treq.SetRequestURI(\"http://aaaai.com/bsdf?sddfsd\")\n\tvar resp Response\n\tfor range 5 {\n\t\tif err := c.DoTimeout(&req, &resp, time.Second); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\thv := resp.Header.Peek(\"foo-BAR\")\n\t\tif string(hv) != \"baz\" {\n\t\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"baz\")\n\t\t}\n\t\thv = resp.Header.Peek(\"Foo-Bar\")\n\t\tif len(hv) > 0 {\n\t\t\tt.Fatalf(\"unexpected non-empty header value %q\", hv)\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestClientDoTimeoutDisablePathNormalizing(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\turi := ctx.URI()\n\t\t\turi.DisablePathNormalizing = true\n\t\t\tctx.Response.Header.Set(\"received-uri\", string(uri.FullURI()))\n\t\t},\n\t}\n\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tDisablePathNormalizing: true,\n\t}\n\n\turlWithEncodedPath := \"http://example.com/encoded/Y%2BY%2FY%3D/stuff\"\n\n\tvar req Request\n\treq.SetRequestURI(urlWithEncodedPath)\n\tvar resp Response\n\tfor range 5 {\n\t\tif err := c.DoTimeout(&req, &resp, time.Second); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\thv := resp.Header.Peek(\"received-uri\")\n\t\tif string(hv) != urlWithEncodedPath {\n\t\t\tt.Fatalf(\"request uri was normalized: %q. Expecting %q\", hv, urlWithEncodedPath)\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestHostClientPendingRequests(t *testing.T) {\n\tt.Parallel()\n\n\tconst concurrency = 10\n\tdoneCh := make(chan struct{})\n\treadyCh := make(chan struct{}, concurrency)\n\ts := &Server{\n\t\tHandler: func(_ *RequestCtx) {\n\t\t\treadyCh <- struct{}{}\n\t\t\t<-doneCh\n\t\t},\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\tpendingRequests := c.PendingRequests()\n\tif pendingRequests != 0 {\n\t\tt.Fatalf(\"non-zero pendingRequests: %d\", pendingRequests)\n\t}\n\n\tresultCh := make(chan error, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\treq := AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\tresp := AcquireResponse()\n\n\t\t\tif err := c.DoTimeout(req, resp, 10*time.Second); err != nil {\n\t\t\t\tresultCh <- fmt.Errorf(\"unexpected error: %w\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tresultCh <- fmt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresultCh <- nil\n\t\t}()\n\t}\n\n\t// wait while all the requests reach server\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-readyCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tpendingRequests = c.PendingRequests()\n\tif pendingRequests != concurrency {\n\t\tt.Fatalf(\"unexpected pendingRequests: %d. Expecting %d\", pendingRequests, concurrency)\n\t}\n\n\t// unblock request handlers on the server and wait until all the requests are finished.\n\tclose(doneCh)\n\tfor range concurrency {\n\t\tselect {\n\t\tcase err := <-resultCh:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tpendingRequests = c.PendingRequests()\n\tif pendingRequests != 0 {\n\t\tt.Fatalf(\"non-zero pendingRequests: %d\", pendingRequests)\n\t}\n\n\t// stop the server\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestHostClientMaxConnsWithDeadline(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\temptyBodyCount uint8\n\t\tln             = fasthttputil.NewInmemoryListener()\n\t\ttimeout        = 200 * time.Millisecond\n\t\twg             sync.WaitGroup\n\t)\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif len(ctx.PostBody()) == 0 {\n\t\t\t\temptyBodyCount++\n\t\t\t}\n\n\t\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConns: 1,\n\t}\n\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treq := AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := AcquireResponse()\n\n\t\t\tfor {\n\t\t\t\tif err := c.DoDeadline(req, resp, time.Now().Add(timeout)); err != nil {\n\t\t\t\t\tif err == ErrNoFreeConns {\n\t\t\t\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"foo\" {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestHostClientMaxConnDuration(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tvar connectionCloseCount atomic.Uint32\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"abcd\") //nolint:errcheck\n\t\t\tif ctx.Request.ConnectionClose() {\n\t\t\t\tconnectionCloseCount.Add(1)\n\t\t\t}\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConnDuration: 10 * time.Millisecond,\n\t}\n\n\tfor range 5 {\n\t\tstatusCode, body, err := c.Get(nil, \"http://aaaa.com/bbb/cc\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\tif string(body) != \"abcd\" {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t}\n\t\ttime.Sleep(c.MaxConnDuration)\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif connectionCloseCount.Load() == 0 {\n\t\tt.Fatalf(\"expecting at least one 'Connection: close' request header\")\n\t}\n}\n\nfunc TestHostClientMultipleAddrs(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Write(ctx.Host()) //nolint:errcheck\n\t\t\tctx.SetConnectionClose()\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tdialsCount := make(map[string]int)\n\tc := &HostClient{\n\t\tAddr: \"foo,bar,baz\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\tdialsCount[addr]++\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\tfor range 9 {\n\t\tstatusCode, body, err := c.Get(nil, \"http://foobar/baz/aaa?bbb=ddd\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\tif string(body) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"foobar\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif len(dialsCount) != 3 {\n\t\tt.Fatalf(\"unexpected dialsCount size %d. Expecting 3\", len(dialsCount))\n\t}\n\tfor _, k := range []string{\"foo\", \"bar\", \"baz\"} {\n\t\tif dialsCount[k] != 3 {\n\t\t\tt.Fatalf(\"unexpected dialsCount for %q. Expecting 3\", k)\n\t\t}\n\t}\n}\n\nfunc TestClientFollowRedirects(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tswitch string(ctx.Path()) {\n\t\t\tcase \"/foo\":\n\t\t\t\tu := ctx.URI()\n\t\t\t\tu.Update(\"/xy?z=wer\")\n\t\t\t\tctx.Redirect(u.String(), StatusFound)\n\t\t\tcase \"/xy\":\n\t\t\t\tu := ctx.URI()\n\t\t\t\tu.Update(\"/bar\")\n\t\t\t\tctx.Redirect(u.String(), StatusFound)\n\t\t\tcase \"/abc/*/123\":\n\t\t\t\tu := ctx.URI()\n\t\t\t\tu.Update(\"/xyz/*/456\")\n\t\t\t\tctx.Redirect(u.String(), StatusFound)\n\t\t\tdefault:\n\t\t\t\tctx.Success(\"text/plain\", ctx.Path())\n\t\t\t}\n\t\t},\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"xxx\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t}\n\n\tfor range 10 {\n\t\tstatusCode, body, err := c.GetTimeout(nil, \"http://xxx/foo\", time.Second)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\t\tif string(body) != \"/bar\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/bar\")\n\t\t}\n\t}\n\n\tfor range 10 {\n\t\tstatusCode, body, err := c.Get(nil, \"http://xxx/aaab/sss\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\t\tif string(body) != \"/aaab/sss\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/aaab/sss\")\n\t\t}\n\t}\n\n\tfor range 10 {\n\t\treq := AcquireRequest()\n\t\tresp := AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://xxx/foo\")\n\n\t\terr := c.DoRedirects(req, resp, 16)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif statusCode := resp.StatusCode(); statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tif body := string(resp.Body()); body != \"/bar\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/bar\")\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(resp)\n\t}\n\n\tfor range 10 {\n\t\treq := AcquireRequest()\n\t\tresp := AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://xxx/foo\")\n\n\t\treq.SetTimeout(time.Second)\n\t\terr := c.DoRedirects(req, resp, 16)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif statusCode := resp.StatusCode(); statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tif body := string(resp.Body()); body != \"/bar\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/bar\")\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(resp)\n\t}\n\n\tfor range 10 {\n\t\treq := AcquireRequest()\n\t\tresp := AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://xxx/foo\")\n\n\t\ttestConn, _ := net.Dial(\"tcp\", ln.Addr().String())\n\t\ttimeoutConn := &Client{\n\t\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\t\treturn &readTimeoutConn{Conn: testConn, t: time.Second}, nil\n\t\t\t},\n\t\t}\n\n\t\treq.SetTimeout(time.Millisecond)\n\t\terr := timeoutConn.DoRedirects(req, resp, 16)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(resp)\n\t}\n\n\tfor range 10 {\n\t\treq := AcquireRequest()\n\t\tresp := AcquireResponse()\n\n\t\treq.SetRequestURI(\"http://xxx/abc/*/123\")\n\t\treq.URI().DisablePathNormalizing = true\n\t\treq.DisableRedirectPathNormalizing = true\n\n\t\terr := c.DoRedirects(req, resp, 16)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif statusCode := resp.StatusCode(); statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tif body := string(resp.Body()); body != \"/xyz/*/456\" {\n\t\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", body, \"/xyz/*/456\")\n\t\t}\n\n\t\tReleaseRequest(req)\n\t\tReleaseResponse(resp)\n\t}\n\n\treq := AcquireRequest()\n\tresp := AcquireResponse()\n\n\treq.SetRequestURI(\"http://xxx/foo\")\n\n\terr := c.DoRedirects(req, resp, 0)\n\tif have, want := err, ErrTooManyRedirects; have != want {\n\t\tt.Fatalf(\"want error: %v, have %v\", want, have)\n\t}\n\n\tReleaseRequest(req)\n\tReleaseResponse(resp)\n}\n\nfunc TestClientGetTimeoutSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestClientGetTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n}\n\nfunc TestClientGetTimeoutSuccessConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientGetTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestClientDoTimeoutSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestClientDoTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n\ttestClientRequestSetTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n}\n\nfunc TestClientDoTimeoutSuccessConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientDoTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n\t\t\ttestClientRequestSetTimeoutSuccess(t, &defaultClient, \"http://\"+s.Addr(), 100)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestClientGetTimeoutError(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestConn, _ := net.Dial(\"tcp\", s.ln.Addr().String())\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &readTimeoutConn{Conn: testConn, t: time.Second}, nil\n\t\t},\n\t}\n\n\ttestClientGetTimeoutError(t, c, 100)\n}\n\nfunc TestClientGetTimeoutErrorConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestConn, _ := net.Dial(\"tcp\", s.ln.Addr().String())\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &readTimeoutConn{Conn: testConn, t: time.Second}, nil\n\t\t},\n\t\tMaxConnsPerHost: 1000,\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientGetTimeoutError(t, c, 100)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestClientDoTimeoutError(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestConn, _ := net.Dial(\"tcp\", s.ln.Addr().String())\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &readTimeoutConn{Conn: testConn, t: time.Second}, nil\n\t\t},\n\t}\n\n\ttestClientDoTimeoutError(t, c, 100)\n\ttestClientRequestSetTimeoutError(t, c, 100)\n}\n\nfunc TestClientDoTimeoutErrorConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestConn, _ := net.Dial(\"tcp\", s.ln.Addr().String())\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &readTimeoutConn{Conn: testConn, t: time.Second}, nil\n\t\t},\n\t\tMaxConnsPerHost: 1000,\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientDoTimeoutError(t, c, 100)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc testClientDoTimeoutError(t *testing.T, c *Client, n int) {\n\tvar req Request\n\tvar resp Response\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tfor range n {\n\t\terr := c.DoTimeout(&req, &resp, time.Millisecond)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\t}\n}\n\nfunc testClientGetTimeoutError(t *testing.T, c *Client, n int) {\n\tbuf := make([]byte, 10)\n\tfor range n {\n\t\tstatusCode, _, err := c.GetTimeout(buf, \"http://foobar.com/baz\", time.Millisecond)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\t\tif statusCode != 0 {\n\t\t\tt.Errorf(\"unexpected statusCode=%d. Expecting %d\", statusCode, 0)\n\t\t}\n\t}\n}\n\nfunc testClientRequestSetTimeoutError(t *testing.T, c *Client, n int) {\n\tvar req Request\n\tvar resp Response\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tfor range n {\n\t\treq.SetTimeout(time.Millisecond)\n\t\terr := c.Do(&req, &resp)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\t}\n}\n\ntype readTimeoutConn struct {\n\tnet.Conn\n\n\twc chan struct{}\n\trc chan struct{}\n\tt  time.Duration\n}\n\nfunc (r *readTimeoutConn) Read(p []byte) (int, error) {\n\t<-r.rc\n\treturn 0, os.ErrDeadlineExceeded\n}\n\nfunc (r *readTimeoutConn) Write(p []byte) (int, error) {\n\t<-r.wc\n\treturn 0, os.ErrDeadlineExceeded\n}\n\nfunc (r *readTimeoutConn) Close() error {\n\treturn nil\n}\n\nfunc (r *readTimeoutConn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *readTimeoutConn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *readTimeoutConn) SetReadDeadline(d time.Time) error {\n\tr.rc = make(chan struct{}, 1)\n\tgo func() {\n\t\ttime.Sleep(time.Until(d))\n\t\tr.rc <- struct{}{}\n\t}()\n\treturn nil\n}\n\nfunc (r *readTimeoutConn) SetWriteDeadline(d time.Time) error {\n\tr.wc = make(chan struct{}, 1)\n\tgo func() {\n\t\ttime.Sleep(time.Until(d))\n\t\tr.wc <- struct{}{}\n\t}()\n\treturn nil\n}\n\nfunc TestClientNonIdempotentRetry_BodyStream(t *testing.T) {\n\tt.Parallel()\n\n\tdialsCount := 0\n\tc := &Client{\n\t\tDial: func(_ string) (net.Conn, error) {\n\t\t\tdialsCount++\n\t\t\tswitch dialsCount {\n\t\t\tcase 1, 2:\n\t\t\t\treturn &readErrorConn{}, nil\n\t\t\tcase 3:\n\t\t\t\treturn &singleEchoConn{\n\t\t\t\t\tb: []byte(\"HTTP/1.1 345 OK\\r\\nContent-Type: foobar\\r\\n\\r\\n\"),\n\t\t\t\t}, nil\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected number of dials: %d\", dialsCount)\n\t\t\t}\n\t\t},\n\t}\n\n\tdialsCount = 0\n\n\treq := Request{}\n\tres := Response{}\n\n\treq.SetRequestURI(\"http://foobar/a/b\")\n\treq.Header.SetMethod(\"POST\")\n\tbody := bytes.NewBufferString(\"test\")\n\treq.SetBodyStream(body, body.Len())\n\n\terr := c.Do(&req, &res)\n\tif err == nil {\n\t\tt.Fatal(\"expected error from being unable to retry a bodyStream\")\n\t}\n}\n\nfunc TestClientIdempotentRequest(t *testing.T) {\n\tt.Parallel()\n\n\tdialsCount := 0\n\tc := &Client{\n\t\tDial: func(_ string) (net.Conn, error) {\n\t\t\tdialsCount++\n\t\t\tswitch dialsCount {\n\t\t\tcase 1:\n\t\t\t\treturn &singleReadConn{\n\t\t\t\t\ts: \"invalid response\",\n\t\t\t\t}, nil\n\t\t\tcase 2:\n\t\t\t\treturn &writeErrorConn{}, nil\n\t\t\tcase 3:\n\t\t\t\treturn &readErrorConn{}, nil\n\t\t\tcase 4:\n\t\t\t\treturn &singleReadConn{\n\t\t\t\t\ts: \"HTTP/1.1 345 OK\\r\\nContent-Type: foobar\\r\\nContent-Length: 7\\r\\n\\r\\n0123456\",\n\t\t\t\t}, nil\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected number of dials: %d\", dialsCount)\n\t\t\t}\n\t\t},\n\t}\n\n\t// idempotent GET must succeed.\n\tstatusCode, body, err := c.Get(nil, \"http://foobar/a/b\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif statusCode != 345 {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting 345\", statusCode)\n\t}\n\tif string(body) != \"0123456\" {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", body, \"0123456\")\n\t}\n\n\tvar args Args\n\n\t// non-idempotent POST must fail on incorrect singleReadConn\n\tdialsCount = 0\n\t_, _, err = c.Post(nil, \"http://foobar/a/b\", &args)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\n\t// non-idempotent POST must fail on incorrect singleReadConn\n\tdialsCount = 0\n\t_, _, err = c.Post(nil, \"http://foobar/a/b\", nil)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n}\n\nfunc TestClientRetryRequestWithCustomDecider(t *testing.T) {\n\tt.Parallel()\n\n\tdialsCount := 0\n\tc := &Client{\n\t\tDial: func(_ string) (net.Conn, error) {\n\t\t\tdialsCount++\n\t\t\tswitch dialsCount {\n\t\t\tcase 1:\n\t\t\t\treturn &singleReadConn{\n\t\t\t\t\ts: \"invalid response\",\n\t\t\t\t}, nil\n\t\t\tcase 2:\n\t\t\t\treturn &writeErrorConn{}, nil\n\t\t\tcase 3:\n\t\t\t\treturn &readErrorConn{}, nil\n\t\t\tcase 4:\n\t\t\t\treturn &singleReadConn{\n\t\t\t\t\ts: \"HTTP/1.1 345 OK\\r\\nContent-Type: foobar\\r\\nContent-Length: 7\\r\\n\\r\\n0123456\",\n\t\t\t\t}, nil\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected number of dials: %d\", dialsCount)\n\t\t\t}\n\t\t},\n\t\tRetryIf: func(req *Request) bool {\n\t\t\treturn req.URI().String() == \"http://foobar/a/b\"\n\t\t},\n\t}\n\n\tvar args Args\n\n\t// Post must succeed for http://foobar/a/b uri.\n\tstatusCode, body, err := c.Post(nil, \"http://foobar/a/b\", &args)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif statusCode != 345 {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting 345\", statusCode)\n\t}\n\tif string(body) != \"0123456\" {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", body, \"0123456\")\n\t}\n\n\t// POST must fail for http://foobar/a/b/c uri.\n\tdialsCount = 0\n\t_, _, err = c.Post(nil, \"http://foobar/a/b/c\", &args)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n}\n\ntype TransportDemo struct {\n\tbr *bufio.Reader\n\tbw *bufio.Writer\n}\n\nfunc (t TransportDemo) RoundTrip(hc *HostClient, req *Request, res *Response) (retry bool, err error) {\n\tif err = req.Write(t.bw); err != nil {\n\t\treturn false, err\n\t}\n\tif err = t.bw.Flush(); err != nil {\n\t\treturn false, err\n\t}\n\terr = res.Read(t.br)\n\treturn err != nil, err\n}\n\nfunc TestHostClientTransport(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"abcd\") //nolint:errcheck\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tTransport: func() RoundTripper {\n\t\t\tc, _ := ln.Dial()\n\n\t\t\tbr := bufio.NewReader(c)\n\t\t\tbw := bufio.NewWriter(c)\n\n\t\t\treturn TransportDemo{br: br, bw: bw}\n\t\t}(),\n\t}\n\n\tfor range 5 {\n\t\tstatusCode, body, err := c.Get(nil, \"http://aaaa.com/bbb/cc\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Fatalf(\"unexpected status code %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\tif string(body) != \"abcd\" {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\ntype writeErrorConn struct {\n\tnet.Conn\n}\n\nfunc (w *writeErrorConn) Write(p []byte) (int, error) {\n\treturn 1, errors.New(\"error\")\n}\n\nfunc (w *writeErrorConn) Close() error {\n\treturn nil\n}\n\nfunc (w *writeErrorConn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (w *writeErrorConn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (w *writeErrorConn) SetReadDeadline(_ time.Time) error {\n\treturn nil\n}\n\nfunc (w *writeErrorConn) SetWriteDeadline(_ time.Time) error {\n\treturn nil\n}\n\ntype readErrorConn struct {\n\tnet.Conn\n}\n\nfunc (r *readErrorConn) Read(p []byte) (int, error) {\n\treturn 0, errors.New(\"error\")\n}\n\nfunc (r *readErrorConn) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n\nfunc (r *readErrorConn) Close() error {\n\treturn nil\n}\n\nfunc (r *readErrorConn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *readErrorConn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *readErrorConn) SetReadDeadline(_ time.Time) error {\n\treturn nil\n}\n\nfunc (r *readErrorConn) SetWriteDeadline(_ time.Time) error {\n\treturn nil\n}\n\ntype singleReadConn struct {\n\tnet.Conn\n\n\ts string\n\tn int\n}\n\nfunc (r *singleReadConn) Read(p []byte) (int, error) {\n\tif len(r.s) == r.n {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(p, r.s[r.n:])\n\tr.n += n\n\treturn n, nil\n}\n\nfunc (r *singleReadConn) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n\nfunc (r *singleReadConn) Close() error {\n\treturn nil\n}\n\nfunc (r *singleReadConn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *singleReadConn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *singleReadConn) SetReadDeadline(_ time.Time) error {\n\treturn nil\n}\n\nfunc (r *singleReadConn) SetWriteDeadline(_ time.Time) error {\n\treturn nil\n}\n\ntype singleEchoConn struct {\n\tnet.Conn\n\n\tb []byte\n\tn int\n}\n\nfunc (r *singleEchoConn) Read(p []byte) (int, error) {\n\tif len(r.b) == r.n {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(p, r.b[r.n:])\n\tr.n += n\n\treturn n, nil\n}\n\nfunc (r *singleEchoConn) Write(p []byte) (int, error) {\n\tr.b = append(r.b, p...)\n\treturn len(p), nil\n}\n\nfunc (r *singleEchoConn) Close() error {\n\treturn nil\n}\n\nfunc (r *singleEchoConn) LocalAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *singleEchoConn) RemoteAddr() net.Addr {\n\treturn nil\n}\n\nfunc (r *singleEchoConn) SetReadDeadline(_ time.Time) error {\n\treturn nil\n}\n\nfunc (r *singleEchoConn) SetWriteDeadline(_ time.Time) error {\n\treturn nil\n}\n\nfunc TestSingleEchoConn(t *testing.T) {\n\tt.Parallel()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &singleEchoConn{\n\t\t\t\tb: []byte(\"HTTP/1.1 345 OK\\r\\nContent-Type: foobar\\r\\n\\r\\n\"),\n\t\t\t}, nil\n\t\t},\n\t}\n\n\treq := Request{}\n\tres := Response{}\n\n\treq.SetRequestURI(\"http://foobar/a/b\")\n\treq.Header.SetMethod(\"POST\")\n\treq.Header.Set(\"Content-Type\", \"text/plain\")\n\tbody := bytes.NewBufferString(\"test\")\n\treq.SetBodyStream(body, body.Len())\n\n\terr := c.Do(&req, &res)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif res.StatusCode() != 345 {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting 345\", res.StatusCode())\n\t}\n\texpected := \"POST /a/b HTTP/1.1\\r\\nUser-Agent: fasthttp\\r\\nHost: foobar\\r\\nContent-Type: text/plain\\r\\nContent-Length: 4\\r\\n\\r\\ntest\"\n\tif string(res.Body()) != expected {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", res.Body(), expected)\n\t}\n}\n\nfunc TestClientHTTPSInvalidServerName(t *testing.T) {\n\tt.Parallel()\n\n\tsHTTPS := startEchoServerTLS(t, \"tcp\", \"127.0.0.1:\")\n\tdefer sHTTPS.Stop()\n\n\tvar c Client\n\n\tfor range 10 {\n\t\t_, _, err := c.GetTimeout(nil, \"https://\"+sHTTPS.Addr(), time.Second)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting TLS error\")\n\t\t}\n\t}\n}\n\nfunc TestClientHTTPSConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tsHTTP := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer sHTTP.Stop()\n\n\tsHTTPS := startEchoServerTLS(t, \"tcp\", \"127.0.0.1:\")\n\tdefer sHTTPS.Stop()\n\n\tc := &Client{\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor i := range 4 {\n\t\twg.Add(1)\n\t\taddr := \"http://\" + sHTTP.Addr()\n\t\tif i&1 != 0 {\n\t\t\taddr = \"https://\" + sHTTPS.Addr()\n\t\t}\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientGet(t, c, addr, 20)\n\t\t\ttestClientPost(t, c, addr, 10)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestClientManyServers(t *testing.T) {\n\tt.Parallel()\n\n\taddrs := make([]string, 0, 10)\n\tfor range 10 {\n\t\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\t\tdefer s.Stop()\n\t\taddrs = append(addrs, s.Addr())\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor i := range 4 {\n\t\twg.Add(1)\n\t\taddr := \"http://\" + addrs[i]\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientGet(t, &defaultClient, addr, 20)\n\t\t\ttestClientPost(t, &defaultClient, addr, 10)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestClientGet(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestClientGet(t, &defaultClient, \"http://\"+s.Addr(), 100)\n}\n\nfunc TestClientPost(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\ttestClientPost(t, &defaultClient, \"http://\"+s.Addr(), 100)\n}\n\nfunc TestClientConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ts := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer s.Stop()\n\n\taddr := \"http://\" + s.Addr()\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestClientGet(t, &defaultClient, addr, 30)\n\t\t\ttestClientPost(t, &defaultClient, addr, 10)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc skipIfNotUnix(tb testing.TB) {\n\tswitch runtime.GOOS {\n\tcase \"android\", \"nacl\", \"plan9\", \"windows\":\n\t\ttb.Skipf(\"%s does not support unix sockets\", runtime.GOOS)\n\t}\n\tif runtime.GOOS == \"darwin\" && (runtime.GOARCH == \"arm\" || runtime.GOARCH == \"arm64\") {\n\t\ttb.Skip(\"iOS does not support unix, unixgram\")\n\t}\n}\n\nfunc TestHostClientGet(t *testing.T) {\n\tt.Parallel()\n\n\tskipIfNotUnix(t)\n\taddr := \"TestHostClientGet.unix\"\n\ts := startEchoServer(t, \"unix\", addr)\n\tdefer s.Stop()\n\tc := createEchoClient(\"unix\", addr)\n\n\ttestHostClientGet(t, c, 100)\n}\n\nfunc TestHostClientPost(t *testing.T) {\n\tt.Parallel()\n\n\tskipIfNotUnix(t)\n\taddr := \"./TestHostClientPost.unix\"\n\ts := startEchoServer(t, \"unix\", addr)\n\tdefer s.Stop()\n\tc := createEchoClient(\"unix\", addr)\n\n\ttestHostClientPost(t, c, 100)\n}\n\nfunc TestHostClientConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tskipIfNotUnix(t)\n\taddr := \"./TestHostClientConcurrent.unix\"\n\ts := startEchoServer(t, \"unix\", addr)\n\tdefer s.Stop()\n\tc := createEchoClient(\"unix\", addr)\n\n\tvar wg sync.WaitGroup\n\tfor range 10 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttestHostClientGet(t, c, 30)\n\t\t\ttestHostClientPost(t, c, 10)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc testClientGet(t *testing.T, c clientGetter, addr string, n int) {\n\tvar buf []byte\n\tfor i := range n {\n\t\turi := fmt.Sprintf(\"%s/foo/%d?bar=baz\", addr, i)\n\t\tstatusCode, body, err := c.Get(buf, uri)\n\t\tbuf = body\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error when doing http request: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\tresultURI := string(body)\n\t\tif resultURI != uri {\n\t\t\tt.Errorf(\"unexpected uri %q. Expecting %q\", resultURI, uri)\n\t\t}\n\t}\n}\n\nfunc testClientDoTimeoutSuccess(t *testing.T, c *Client, addr string, n int) {\n\tvar req Request\n\tvar resp Response\n\n\tfor i := range n {\n\t\turi := fmt.Sprintf(\"%s/foo/%d?bar=baz\", addr, i)\n\t\treq.SetRequestURI(uri)\n\t\tif err := c.DoTimeout(&req, &resp, time.Second); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\t\tresultURI := string(resp.Body())\n\t\tif strings.HasPrefix(uri, \"https\") {\n\t\t\tresultURI = uri[:5] + resultURI[4:]\n\t\t}\n\t\tif resultURI != uri {\n\t\t\tt.Errorf(\"unexpected uri %q. Expecting %q\", resultURI, uri)\n\t\t}\n\t}\n}\n\nfunc testClientRequestSetTimeoutSuccess(t *testing.T, c *Client, addr string, n int) {\n\tvar req Request\n\tvar resp Response\n\n\tfor i := range n {\n\t\turi := fmt.Sprintf(\"%s/foo/%d?bar=baz\", addr, i)\n\t\treq.SetRequestURI(uri)\n\t\treq.SetTimeout(time.Second)\n\t\tif err := c.Do(&req, &resp); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\t\tresultURI := string(resp.Body())\n\t\tif strings.HasPrefix(uri, \"https\") {\n\t\t\tresultURI = uri[:5] + resultURI[4:]\n\t\t}\n\t\tif resultURI != uri {\n\t\t\tt.Errorf(\"unexpected uri %q. Expecting %q\", resultURI, uri)\n\t\t}\n\t}\n}\n\nfunc testClientGetTimeoutSuccess(t *testing.T, c *Client, addr string, n int) {\n\tvar buf []byte\n\tfor i := range n {\n\t\turi := fmt.Sprintf(\"%s/foo/%d?bar=baz\", addr, i)\n\t\tstatusCode, body, err := c.GetTimeout(buf, uri, time.Second)\n\t\tbuf = body\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when doing http request: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\tresultURI := string(body)\n\t\tif strings.HasPrefix(uri, \"https\") {\n\t\t\tresultURI = uri[:5] + resultURI[4:]\n\t\t}\n\t\tif resultURI != uri {\n\t\t\tt.Errorf(\"unexpected uri %q. Expecting %q\", resultURI, uri)\n\t\t}\n\t}\n}\n\nfunc testClientPost(t *testing.T, c clientPoster, addr string, n int) {\n\tvar buf []byte\n\tvar args Args\n\tfor i := range n {\n\t\turi := fmt.Sprintf(\"%s/foo/%d?bar=baz\", addr, i)\n\t\targs.Set(\"xx\", fmt.Sprintf(\"yy%d\", i))\n\t\targs.Set(\"zzz\", fmt.Sprintf(\"qwe_%d\", i))\n\t\targsS := args.String()\n\t\tstatusCode, body, err := c.Post(buf, uri, &args)\n\t\tbuf = body\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error when doing http request: %v\", err)\n\t\t}\n\t\tif statusCode != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t\t}\n\t\ts := string(body)\n\t\tif s != argsS {\n\t\t\tt.Errorf(\"unexpected response %q. Expecting %q\", s, argsS)\n\t\t}\n\t}\n}\n\nfunc testHostClientGet(t *testing.T, c *HostClient, n int) {\n\ttestClientGet(t, c, \"http://google.com\", n)\n}\n\nfunc testHostClientPost(t *testing.T, c *HostClient, n int) {\n\ttestClientPost(t, c, \"http://post-host.com\", n)\n}\n\ntype clientPoster interface {\n\tPost(dst []byte, uri string, postArgs *Args) (int, []byte, error)\n}\n\ntype clientGetter interface {\n\tGet(dst []byte, uri string) (int, []byte, error)\n}\n\nfunc createEchoClient(network, addr string) *HostClient {\n\treturn &HostClient{\n\t\tAddr: addr,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn net.Dial(network, addr)\n\t\t},\n\t}\n}\n\ntype testEchoServer struct {\n\ts  *Server\n\tln net.Listener\n\tch chan struct{}\n\tt  *testing.T\n}\n\nfunc (s *testEchoServer) Stop() {\n\ts.ln.Close()\n\tselect {\n\tcase <-s.ch:\n\tcase <-time.After(time.Second):\n\t\ts.t.Fatalf(\"timeout when waiting for server close\")\n\t}\n}\n\nfunc (s *testEchoServer) Addr() string {\n\treturn s.ln.Addr().String()\n}\n\nfunc startEchoServerTLS(t *testing.T, network, addr string) *testEchoServer {\n\treturn startEchoServerExt(t, network, addr, true)\n}\n\nfunc startEchoServer(t *testing.T, network, addr string) *testEchoServer {\n\treturn startEchoServerExt(t, network, addr, false)\n}\n\nfunc startEchoServerExt(t *testing.T, network, addr string, isTLS bool) *testEchoServer {\n\tif network == \"unix\" {\n\t\tos.Remove(addr)\n\t}\n\tvar ln net.Listener\n\tvar err error\n\tif isTLS {\n\t\tcertData, keyData, kerr := GenerateTestCertificate(\"localhost\")\n\t\tif kerr != nil {\n\t\t\tt.Fatal(kerr)\n\t\t}\n\n\t\tcert, kerr := tls.X509KeyPair(certData, keyData)\n\t\tif kerr != nil {\n\t\t\tt.Fatal(kerr)\n\t\t}\n\n\t\ttlsConfig := &tls.Config{\n\t\t\tCertificates: []tls.Certificate{cert},\n\t\t}\n\t\tln, err = tls.Listen(network, addr, tlsConfig)\n\t} else {\n\t\tln, err = net.Listen(network, addr)\n\t}\n\tif err != nil {\n\t\tt.Fatalf(\"cannot listen %q: %v\", addr, err)\n\t}\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif ctx.IsGet() {\n\t\t\t\tctx.Success(\"text/plain\", ctx.URI().FullURI())\n\t\t\t} else if ctx.IsPost() {\n\t\t\t\tctx.PostArgs().WriteTo(ctx) //nolint:errcheck\n\t\t\t}\n\t\t},\n\t\tLogger: &testLogger{}, // Ignore log output.\n\t}\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := s.Serve(ln)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error returned from Serve(): %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\treturn &testEchoServer{\n\t\ts:  s,\n\t\tln: ln,\n\t\tch: ch,\n\t\tt:  t,\n\t}\n}\n\nfunc TestClientTLSHandshakeTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\taddr := listener.Addr().String()\n\tdefer listener.Close()\n\n\tcomplete := make(chan bool)\n\tdefer close(complete)\n\n\tgo func() {\n\t\tconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t\t<-complete\n\t\tconn.Close()\n\t}()\n\n\tclient := Client{\n\t\tWriteTimeout: 100 * time.Millisecond,\n\t\tReadTimeout:  100 * time.Millisecond,\n\t}\n\n\t_, _, err = client.Get(nil, \"https://\"+addr)\n\tif err == nil {\n\t\tt.Fatal(\"tlsClientHandshake completed successfully\")\n\t}\n\n\tif err != ErrTLSHandshakeTimeout {\n\t\tt.Errorf(\"resulting error not a timeout: %v\\nType %T: %#v\", err, err, err)\n\t}\n}\n\nfunc TestClientConfigureClientFailed(t *testing.T) {\n\tt.Parallel()\n\n\tc := &Client{\n\t\tConfigureClient: func(hc *HostClient) error {\n\t\t\treturn errors.New(\"failed to configure\")\n\t\t},\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn &singleEchoConn{\n\t\t\t\tb: []byte(\"HTTP/1.1 345 OK\\r\\nContent-Type: foobar\\r\\n\\r\\n\"),\n\t\t\t}, nil\n\t\t},\n\t}\n\n\treq := Request{}\n\treq.SetRequestURI(\"http://example.com\")\n\n\terr := c.Do(&req, &Response{})\n\tif err == nil {\n\t\tt.Fatal(\"expected error (failed to configure)\")\n\t}\n\n\tc.ConfigureClient = nil\n\terr = c.Do(&req, &Response{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestHostClientMaxConnWaitTimeoutSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\temptyBodyCount uint8\n\t\tln             = fasthttputil.NewInmemoryListener()\n\t\twg             sync.WaitGroup\n\t)\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif len(ctx.PostBody()) == 0 {\n\t\t\t\temptyBodyCount++\n\t\t\t}\n\t\t\ttime.Sleep(5 * time.Millisecond)\n\t\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConns:           1,\n\t\tMaxConnWaitTimeout: time.Second * 2,\n\t}\n\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treq := AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := AcquireResponse()\n\n\t\t\tif err := c.Do(req, resp); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"foo\" {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tif c.connsWait.len() > 0 {\n\t\tt.Errorf(\"connsWait has %v items remaining\", c.connsWait.len())\n\t}\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second * 5):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestHostClientMaxConnWaitTimeoutError(t *testing.T) {\n\tvar (\n\t\temptyBodyCount uint8\n\t\tln             = fasthttputil.NewInmemoryListener()\n\t\twg             sync.WaitGroup\n\t)\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif len(ctx.PostBody()) == 0 {\n\t\t\t\temptyBodyCount++\n\t\t\t}\n\t\t\ttime.Sleep(5 * time.Millisecond)\n\t\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t\t},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConns:           1,\n\t\tMaxConnWaitTimeout: 10 * time.Millisecond,\n\t}\n\n\tvar errNoFreeConnsCount atomic.Uint32\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treq := AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := AcquireResponse()\n\n\t\t\tif err := c.Do(req, resp); err != nil {\n\t\t\t\tif err != ErrNoFreeConns {\n\t\t\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrNoFreeConns)\n\t\t\t\t}\n\t\t\t\terrNoFreeConnsCount.Add(1)\n\t\t\t} else {\n\t\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t\t}\n\n\t\t\t\tbody := resp.Body()\n\t\t\t\tif string(body) != \"foo\" {\n\t\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\ttime.Sleep(time.Millisecond * 200)\n\n\t// Prevent a race condition with the conns cleaner that might still be running.\n\tc.connsLock.Lock()\n\tdefer c.connsLock.Unlock()\n\n\tif c.connsWait.len() > 0 {\n\t\tt.Errorf(\"connsWait has %v items remaining\", c.connsWait.len())\n\t}\n\tif count := errNoFreeConnsCount.Load(); count == 0 {\n\t\tt.Errorf(\"unexpected errorCount: %d. Expecting > 0\", count)\n\t}\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\nfunc TestHostClientMaxConnWaitTimeoutWithEarlierDeadline(t *testing.T) {\n\tt.Parallel()\n\n\tvar (\n\t\temptyBodyCount uint8\n\t\tln             = fasthttputil.NewInmemoryListener()\n\t\twg             sync.WaitGroup\n\t\t// make deadline reach earlier than conns wait timeout\n\t\tsleep              = 100 * time.Millisecond\n\t\ttimeout            = 10 * time.Millisecond\n\t\tmaxConnWaitTimeout = 50 * time.Millisecond\n\t)\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif len(ctx.PostBody()) == 0 {\n\t\t\t\temptyBodyCount++\n\t\t\t}\n\t\t\ttime.Sleep(sleep)\n\t\t\tctx.WriteString(\"foo\") //nolint:errcheck\n\t\t},\n\t\tLogger: &testLogger{}, // Don't print connection closed errors.\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tc := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tMaxConns:           1,\n\t\tMaxConnWaitTimeout: maxConnWaitTimeout,\n\t}\n\n\tvar errTimeoutCount atomic.Uint32\n\tfor range 5 {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\treq := AcquireRequest()\n\t\t\treq.SetRequestURI(\"http://foobar/baz\")\n\t\t\treq.Header.SetMethod(MethodPost)\n\t\t\treq.SetBodyString(\"bar\")\n\t\t\tresp := AcquireResponse()\n\n\t\t\tif err := c.DoDeadline(req, resp, time.Now().Add(timeout)); err != nil {\n\t\t\t\tif err != ErrTimeout {\n\t\t\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t\t\t}\n\t\t\t\terrTimeoutCount.Add(1)\n\t\t\t} else {\n\t\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t\t}\n\n\t\t\t\tbody := resp.Body()\n\t\t\t\tif string(body) != \"foo\" {\n\t\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"abcd\")\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\n\tc.connsLock.Lock()\n\tfor {\n\t\tw := c.connsWait.popFront()\n\t\tif w == nil {\n\t\t\tbreak\n\t\t}\n\t\tw.mu.Lock()\n\t\tif w.err != nil && w.err != ErrTimeout {\n\t\t\tt.Errorf(\"unexpected error: %v. Expecting %v\", w.err, ErrTimeout)\n\t\t}\n\t\tw.mu.Unlock()\n\t}\n\tc.connsLock.Unlock()\n\tif count := errTimeoutCount.Load(); count == 0 {\n\t\tt.Errorf(\"unexpected errTimeoutCount: %d. Expecting > 0\", count)\n\t}\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-serverStopCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif emptyBodyCount > 0 {\n\t\tt.Fatalf(\"at least one request body was empty\")\n\t}\n}\n\ntype TransportEmpty struct{}\n\nfunc (t TransportEmpty) RoundTrip(hc *HostClient, req *Request, res *Response) (retry bool, err error) {\n\treturn false, nil\n}\n\nfunc TestHttpsRequestWithoutParsedURL(t *testing.T) {\n\tt.Parallel()\n\n\tclient := HostClient{\n\t\tIsTLS:     true,\n\t\tTransport: TransportEmpty{},\n\t}\n\n\treq := &Request{}\n\n\treq.SetRequestURI(\"https://foo.com/bar\")\n\n\t_, err := client.doNonNilReqResp(req, &Response{})\n\tif err != nil {\n\t\tt.Fatal(\"https requests with IsTLS client must succeed\")\n\t}\n}\n\nfunc TestHostClientErrConnPoolStrategyNotImpl(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\tserver := &Server{\n\t\tHandler: func(ctx *RequestCtx) {},\n\t}\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tif err := server.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\n\tclient := &HostClient{\n\t\tAddr: \"foobar\",\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tConnPoolStrategy: ConnPoolStrategyType(100),\n\t}\n\n\treq := AcquireRequest()\n\treq.SetRequestURI(\"http://foobar/baz\")\n\n\tif err := client.Do(req, AcquireResponse()); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := client.Do(req, &Response{}); err != ErrConnPoolStrategyNotImpl {\n\t\tt.Errorf(\"expected ErrConnPoolStrategyNotImpl error, got %v\", err)\n\t}\n\tif err := client.Do(req, &Response{}); err != ErrConnPoolStrategyNotImpl {\n\t\tt.Errorf(\"expected ErrConnPoolStrategyNotImpl error, got %v\", err)\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc Test_AddMissingPort(t *testing.T) {\n\tt.Parallel()\n\n\ttype args struct {\n\t\taddr  string\n\t\tisTLS bool\n\t}\n\ttests := []struct {\n\t\tname string\n\t\twant string\n\t\targs args\n\t}{\n\t\t{\n\t\t\targs: args{addr: \"127.1\", isTLS: false}, // 127.1 is a short form of 127.0.0.1\n\t\t\twant: \"127.1:80\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"127.0.0.1\", isTLS: false},\n\t\t\twant: \"127.0.0.1:80\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"127.0.0.1\", isTLS: true},\n\t\t\twant: \"127.0.0.1:443\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"[::1]\", isTLS: false},\n\t\t\twant: \"[::1]:80\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"::1\", isTLS: false},\n\t\t\twant: \"::1\", // keep as is\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"[::1]\", isTLS: true},\n\t\t\twant: \"[::1]:443\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"127.0.0.1:8080\", isTLS: false},\n\t\t\twant: \"127.0.0.1:8080\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"127.0.0.1:8443\", isTLS: true},\n\t\t\twant: \"127.0.0.1:8443\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"[::1]:8080\", isTLS: false},\n\t\t\twant: \"[::1]:8080\",\n\t\t},\n\t\t{\n\t\t\targs: args{addr: \"[::1]:8443\", isTLS: true},\n\t\t\twant: \"[::1]:8443\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.want, func(t *testing.T) {\n\t\t\tif got := AddMissingPort(tt.args.addr, tt.args.isTLS); got != tt.want {\n\t\t\t\tt.Errorf(\"AddMissingPort() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype TransportWrapper struct {\n\tbase  RoundTripper\n\tcount *int\n\tt     *testing.T\n}\n\nfunc (tw *TransportWrapper) RoundTrip(hc *HostClient, req *Request, resp *Response) (bool, error) {\n\treq.Header.Set(\"trace-id\", \"123\")\n\ttw.assertRequestLog(req.String())\n\tretry, err := tw.transport().RoundTrip(hc, req, resp)\n\tresp.Header.Set(\"trace-id\", \"124\")\n\ttw.assertResponseLog(resp.String())\n\t*tw.count++\n\treturn retry, err\n}\n\nfunc (tw *TransportWrapper) transport() RoundTripper {\n\tif tw.base == nil {\n\t\treturn DefaultTransport\n\t}\n\treturn tw.base\n}\n\nfunc (tw *TransportWrapper) assertRequestLog(reqLog string) {\n\tif !strings.Contains(reqLog, \"Trace-Id: 123\") {\n\t\ttw.t.Errorf(\"request log should contains: %v\", \"Trace-Id: 123\")\n\t}\n}\n\nfunc (tw *TransportWrapper) assertResponseLog(respLog string) {\n\tif !strings.Contains(respLog, \"Trace-Id: 124\") {\n\t\ttw.t.Errorf(\"response log should contains: %v\", \"Trace-Id: 124\")\n\t}\n}\n\nfunc TestClientTransportEx(t *testing.T) {\n\tsHTTP := startEchoServer(t, \"tcp\", \"127.0.0.1:\")\n\tdefer sHTTP.Stop()\n\n\tsHTTPS := startEchoServerTLS(t, \"tcp\", \"127.0.0.1:\")\n\tdefer sHTTPS.Stop()\n\n\tcount := 0\n\tc := &Client{\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t\tConfigureClient: func(hc *HostClient) error {\n\t\t\thc.Transport = &TransportWrapper{base: hc.Transport, count: &count, t: t}\n\t\t\treturn nil\n\t\t},\n\t}\n\t// test transport\n\tconst loopCount = 4\n\tconst getCount = 20\n\tconst postCount = 10\n\tfor i := range loopCount {\n\t\taddr := \"http://\" + sHTTP.Addr()\n\t\tif i&1 != 0 {\n\t\t\taddr = \"https://\" + sHTTPS.Addr()\n\t\t}\n\t\t// test get\n\t\ttestClientGet(t, c, addr, getCount)\n\t\t// test post\n\t\ttestClientPost(t, c, addr, postCount)\n\t}\n\troundTripCount := loopCount * (getCount + postCount)\n\tif count != roundTripCount {\n\t\tt.Errorf(\"round trip count should be: %v\", roundTripCount)\n\t}\n}\n\nfunc Test_getRedirectURL(t *testing.T) {\n\ttype args struct {\n\t\tbaseURL                string\n\t\tlocation               []byte\n\t\tdisablePathNormalizing bool\n\t}\n\ttests := []struct {\n\t\tname string\n\t\twant string\n\t\targs args\n\t}{\n\t\t{\n\t\t\tname: \"Path normalizing enabled, no special characters in path\",\n\t\t\targs: args{\n\t\t\t\tbaseURL:                \"http://foo.example.com/abc\",\n\t\t\t\tlocation:               []byte(\"http://bar.example.com/def\"),\n\t\t\t\tdisablePathNormalizing: false,\n\t\t\t},\n\t\t\twant: \"http://bar.example.com/def\",\n\t\t},\n\t\t{\n\t\t\tname: \"Path normalizing enabled, special characters in path\",\n\t\t\targs: args{\n\t\t\t\tbaseURL:                \"http://foo.example.com/abc/*/def\",\n\t\t\t\tlocation:               []byte(\"http://bar.example.com/123/*/456\"),\n\t\t\t\tdisablePathNormalizing: false,\n\t\t\t},\n\t\t\twant: \"http://bar.example.com/123/%2A/456\",\n\t\t},\n\t\t{\n\t\t\tname: \"Path normalizing disabled, no special characters in path\",\n\t\t\targs: args{\n\t\t\t\tbaseURL:                \"http://foo.example.com/abc\",\n\t\t\t\tlocation:               []byte(\"http://bar.example.com/def\"),\n\t\t\t\tdisablePathNormalizing: true,\n\t\t\t},\n\t\t\twant: \"http://bar.example.com/def\",\n\t\t},\n\t\t{\n\t\t\tname: \"Path normalizing disabled, special characters in path\",\n\t\t\targs: args{\n\t\t\t\tbaseURL:                \"http://foo.example.com/abc/*/def\",\n\t\t\t\tlocation:               []byte(\"http://bar.example.com/123/*/456\"),\n\t\t\t\tdisablePathNormalizing: true,\n\t\t\t},\n\t\t\twant: \"http://bar.example.com/123/*/456\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := getRedirectURL(tt.args.baseURL, tt.args.location, tt.args.disablePathNormalizing); got != tt.want {\n\t\t\t\tt.Errorf(\"getRedirectURL() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype clientDoTimeOuter interface {\n\tDoTimeout(req *Request, resp *Response, timeout time.Duration) error\n}\n\nfunc TestDialTimeout(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tclient         clientDoTimeOuter\n\t\tname           string\n\t\trequestTimeout time.Duration\n\t\tshouldFailFast bool\n\t}{\n\t\t{\n\t\t\tname: \"Client should fail after a millisecond due to request timeout\",\n\t\t\tclient: &Client{\n\t\t\t\t// should be ignored due to DialTimeout\n\t\t\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t\t// should be used\n\t\t\t\tDialTimeout: func(addr string, timeout time.Duration) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(timeout)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeout: time.Millisecond,\n\t\t\tshouldFailFast: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Client should fail after a second due to no DialTimeout set\",\n\t\t\tclient: &Client{\n\t\t\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeout: time.Millisecond,\n\t\t\tshouldFailFast: false,\n\t\t},\n\t\t{\n\t\t\tname: \"HostClient should fail after a millisecond due to request timeout\",\n\t\t\tclient: &HostClient{\n\t\t\t\t// should be ignored due to DialTimeout\n\t\t\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t\t// should be used\n\t\t\t\tDialTimeout: func(addr string, timeout time.Duration) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(timeout)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeout: time.Millisecond,\n\t\t\tshouldFailFast: true,\n\t\t},\n\t\t{\n\t\t\tname: \"HostClient should fail after a second due to no DialTimeout set\",\n\t\t\tclient: &HostClient{\n\t\t\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\treturn nil, errors.New(\"timeout\")\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeout: time.Millisecond,\n\t\t\tshouldFailFast: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstart := time.Now()\n\t\t\terr := tt.client.DoTimeout(&Request{}, &Response{}, tt.requestTimeout)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"expected error (timeout)\")\n\t\t\t}\n\t\t\tif tt.shouldFailFast {\n\t\t\t\tif time.Since(start) > time.Second {\n\t\t\t\t\tt.Fatal(\"expected timeout after a millisecond\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif time.Since(start) < time.Second {\n\t\t\t\t\tt.Fatal(\"expected timeout after a second\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClientHeadWithBody(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tc.Write([]byte(\"HTTP/1.1 200 OK\\r\\n\" + //nolint:errcheck\n\t\t\t\"content-type: text/plain\\r\\n\" +\n\t\t\t\"transfer-encoding: chunked\\r\\n\\r\\n\" +\n\t\t\t\"24\\r\\nThis is the data in the first chunk \\r\\n\" +\n\t\t\t\"1B\\r\\nand this is the second one \\r\\n\" +\n\t\t\t\"0\\r\\n\\r\\n\",\n\t\t))\n\t}()\n\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tReadTimeout:               time.Millisecond * 10,\n\t\tMaxIdemponentCallAttempts: 1,\n\t}\n\n\treq := AcquireRequest()\n\treq.SetRequestURI(\"http://127.0.0.1:7070\")\n\treq.Header.SetMethod(MethodHead)\n\n\tresp := AcquireResponse()\n\n\terr := c.Do(req, resp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// The second request on the same connection is going to give a timeout as it can't find\n\t// a proper request in what is left on the connection.\n\terr = c.Do(req, resp)\n\tif err == nil {\n\t\tt.Error(\"expected timeout error\")\n\t} else if err != ErrTimeout {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestRevertPull1233(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\tt.Parallel()\n\tconst expectedStatus = http.StatusTeapot\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:8089\")\n\tdefer func() { ln.Close() }()\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tif !strings.Contains(err.Error(), \"closed\") {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = conn.Write([]byte(\"HTTP/1.1 418 Teapot\\r\\n\\r\\n\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\terr = conn.(*net.TCPConn).SetLinger(0)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tconn.Close()\n\t\t}\n\t}()\n\n\treqURL := \"http://\" + ln.Addr().String()\n\treqStrBody := \"hello 2323 23323 2323 2323 232323 323 2323 2333333 hello 2323 23323 2323 2323 232323 323 2323 2333333 hello 2323 23323 2323 2323 232323 323 2323 2333333 hello 2323 23323 2323 2323 232323 323 2323 2333333 hello 2323 23323 2323 2323 232323 323 2323 2333333\"\n\treq2 := AcquireRequest()\n\tresp2 := AcquireResponse()\n\tdefer func() {\n\t\tReleaseRequest(req2)\n\t\tReleaseResponse(resp2)\n\t}()\n\treq2.SetRequestURI(reqURL)\n\treq2.SetBodyStream(F{strings.NewReader(reqStrBody)}, -1)\n\tcli2 := Client{}\n\terr = cli2.Do(req2, resp2)\n\tif !errors.Is(err, syscall.EPIPE) && !errors.Is(err, syscall.ECONNRESET) {\n\t\tt.Errorf(\"expected error %v or %v, but got nil\", syscall.EPIPE, syscall.ECONNRESET)\n\t}\n\tif expectedStatus == resp2.StatusCode() {\n\t\tt.Errorf(\"Not Expected status code %d\", resp2.StatusCode())\n\t}\n}\n\ntype F struct {\n\t*strings.Reader\n}\n\nfunc (f F) Read(p []byte) (n int, err error) {\n\tif len(p) > 10 {\n\t\tp = p[:10]\n\t}\n\t// Ensure that subsequent segments can see the RST packet caused by sending previous\n\t// segments to a closed connection.\n\ttime.Sleep(500 * time.Microsecond)\n\treturn f.Reader.Read(p)\n}\n\nfunc TestTCPDialerFlushDNSCache(t *testing.T) {\n\tresolver := &testResolver{\n\t\tlookupCountByHost: make(map[string]int),\n\t\tresolver:          net.DefaultResolver,\n\t}\n\n\tdialer := &TCPDialer{\n\t\tDNSCacheDuration: 30 * time.Minute, // Long cache\n\t\tResolver:         resolver,\n\t}\n\n\t// First dial - should trigger DNS lookup\n\tconn1, err := dialer.DialTimeout(\"httpbin.org:80\", 5*time.Second)\n\tif err != nil {\n\t\tt.Skip(\"Dial failed:\", err)\n\t}\n\tconn1.Close()\n\n\tif resolver.lookupCountByHost[\"httpbin.org\"] != 1 {\n\t\tt.Errorf(\"Expected 1 DNS lookup after first dial, got %d\", resolver.lookupCountByHost[\"httpbin.org\"])\n\t}\n\n\t// Second dial - should use cache (no new DNS lookup)\n\tconn2, err := dialer.DialTimeout(\"httpbin.org:80\", 5*time.Second)\n\tif err != nil {\n\t\tt.Skip(\"Second dial failed:\", err)\n\t}\n\tconn2.Close()\n\n\tif resolver.lookupCountByHost[\"httpbin.org\"] != 1 {\n\t\tt.Errorf(\"Expected 1 DNS lookup after cached dial, got %d\", resolver.lookupCountByHost[\"httpbin.org\"])\n\t}\n\n\t// Flush cache - should clear all entries\n\tdialer.FlushDNSCache()\n\n\t// Third dial - should trigger new DNS lookup since cache was flushed\n\tconn3, err := dialer.DialTimeout(\"httpbin.org:80\", 5*time.Second)\n\tif err != nil {\n\t\tt.Skip(\"Third dial failed:\", err)\n\t}\n\tconn3.Close()\n\n\tif resolver.lookupCountByHost[\"httpbin.org\"] != 2 {\n\t\tt.Errorf(\"Expected 2 DNS lookups after cache flush, got %d\", resolver.lookupCountByHost[\"httpbin.org\"])\n\t}\n}\n\n// Simple test resolver that implements the Resolver interface.\ntype testResolver struct {\n\tresolver          *net.Resolver\n\tlookupCountByHost map[string]int\n}\n\nfunc (r *testResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) {\n\tr.lookupCountByHost[host]++\n\treturn r.resolver.LookupIPAddr(ctx, host)\n}\n"
  },
  {
    "path": "client_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\ntype fakeClientConn struct {\n\tnet.Conn\n\n\tch chan struct{}\n\ts  []byte\n\tn  int\n}\n\nfunc (c *fakeClientConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (c *fakeClientConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (c *fakeClientConn) Write(b []byte) (int, error) {\n\tc.ch <- struct{}{}\n\treturn len(b), nil\n}\n\nfunc (c *fakeClientConn) Read(b []byte) (int, error) {\n\tif c.n == 0 {\n\t\t// wait for request :)\n\t\t<-c.ch\n\t}\n\tn := 0\n\tfor len(b) > 0 {\n\t\tif c.n == len(c.s) {\n\t\t\tc.n = 0\n\t\t\treturn n, nil\n\t\t}\n\t\tn = copy(b, c.s[c.n:])\n\t\tc.n += n\n\t\tb = b[n:]\n\t}\n\treturn n, nil\n}\n\nfunc (c *fakeClientConn) Close() error {\n\treleaseFakeServerConn(c)\n\treturn nil\n}\n\nfunc (c *fakeClientConn) LocalAddr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   []byte{1, 2, 3, 4},\n\t\tPort: 8765,\n\t}\n}\n\nfunc (c *fakeClientConn) RemoteAddr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   []byte{1, 2, 3, 4},\n\t\tPort: 8765,\n\t}\n}\n\nfunc releaseFakeServerConn(c *fakeClientConn) {\n\tc.n = 0\n\tfakeClientConnPool.Put(c)\n}\n\nfunc acquireFakeServerConn(s []byte) *fakeClientConn {\n\tv := fakeClientConnPool.Get()\n\tif v == nil {\n\t\tc := &fakeClientConn{\n\t\t\ts:  s,\n\t\t\tch: make(chan struct{}, 1),\n\t\t}\n\t\treturn c\n\t}\n\treturn v.(*fakeClientConn)\n}\n\nvar fakeClientConnPool sync.Pool\n\nfunc BenchmarkClientGetTimeoutFastServer(b *testing.B) {\n\tbody := []byte(\"123456789099\")\n\ts := fmt.Appendf(nil, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nContent-Length: %d\\r\\n\\r\\n%s\", len(body), body)\n\tc := &Client{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn acquireFakeServerConn(s), nil\n\t\t},\n\t}\n\n\tvar nn atomic.Uint32\n\tb.RunParallel(func(pb *testing.PB) {\n\t\turl := fmt.Sprintf(\"http://foobar%d.com/aaa/bbb\", nn.Add(1))\n\t\tvar statusCode int\n\t\tvar bodyBuf []byte\n\t\tvar err error\n\t\tfor pb.Next() {\n\t\t\tstatusCode, bodyBuf, err = c.GetTimeout(bodyBuf[:0], url, time.Second)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif statusCode != StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d\", statusCode)\n\t\t\t}\n\t\t\tif !bytes.Equal(bodyBuf, body) {\n\t\t\t\tb.Fatalf(\"unexpected response body: %q. Expected %q\", bodyBuf, body)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc fasthttpEchoHandler(ctx *RequestCtx) {\n\tctx.Success(\"text/plain\", ctx.RequestURI())\n}\n\nfunc nethttpEchoHandler(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(HeaderContentType, \"text/plain\")\n\tw.Write([]byte(r.RequestURI)) //nolint:errcheck\n}\n\nfunc BenchmarkClientGetEndToEnd1TCP(b *testing.B) {\n\tbenchmarkClientGetEndToEndTCP(b, 1)\n}\n\nfunc BenchmarkClientGetEndToEnd10TCP(b *testing.B) {\n\tbenchmarkClientGetEndToEndTCP(b, 10)\n}\n\nfunc BenchmarkClientGetEndToEnd100TCP(b *testing.B) {\n\tbenchmarkClientGetEndToEndTCP(b, 100)\n}\n\nfunc benchmarkClientGetEndToEndTCP(b *testing.B, parallelism int) {\n\taddr := \"127.0.0.1:8543\"\n\n\tln, err := net.Listen(\"tcp4\", addr)\n\tif err != nil {\n\t\tb.Fatalf(\"cannot listen %q: %v\", addr, err)\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := Serve(ln, fasthttpEchoHandler); err != nil {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &Client{\n\t\tMaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://\" + addr + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar buf []byte\n\t\tfor pb.Next() {\n\t\t\tstatusCode, body, err := c.Get(buf, url)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif statusCode != StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t\t\t}\n\t\t\tif string(body) != requestURI {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, requestURI)\n\t\t\t}\n\t\t\tbuf = body\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd1TCP(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndTCP(b, 1)\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd10TCP(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndTCP(b, 10)\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd100TCP(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndTCP(b, 100)\n}\n\nfunc benchmarkNetHTTPClientGetEndToEndTCP(b *testing.B, parallelism int) {\n\taddr := \"127.0.0.1:8542\"\n\n\tln, err := net.Listen(\"tcp4\", addr)\n\tif err != nil {\n\t\tb.Fatalf(\"cannot listen %q: %v\", addr, err)\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := http.Serve(ln, http.HandlerFunc(nethttpEchoHandler)); err != nil && !strings.Contains(\n\t\t\terr.Error(), \"use of closed network connection\") {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tMaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),\n\t\t},\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://\" + addr + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tresp, err := c.Get(url)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode, http.StatusOK)\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tresp.Body.Close()\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading response body: %v\", err)\n\t\t\t}\n\t\t\tif string(body) != requestURI {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, requestURI)\n\t\t\t}\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkClientGetEndToEnd1Inmemory(b *testing.B) {\n\tbenchmarkClientGetEndToEndInmemory(b, 1)\n}\n\nfunc BenchmarkClientGetEndToEnd10Inmemory(b *testing.B) {\n\tbenchmarkClientGetEndToEndInmemory(b, 10)\n}\n\nfunc BenchmarkClientGetEndToEnd100Inmemory(b *testing.B) {\n\tbenchmarkClientGetEndToEndInmemory(b, 100)\n}\n\nfunc BenchmarkClientGetEndToEnd1000Inmemory(b *testing.B) {\n\tbenchmarkClientGetEndToEndInmemory(b, 1000)\n}\n\nfunc BenchmarkClientGetEndToEnd10KInmemory(b *testing.B) {\n\tbenchmarkClientGetEndToEndInmemory(b, 10000)\n}\n\nfunc benchmarkClientGetEndToEndInmemory(b *testing.B, parallelism int) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := Serve(ln, fasthttpEchoHandler); err != nil {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &Client{\n\t\tMaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,\n\t\tDial:            func(addr string) (net.Conn, error) { return ln.Dial() },\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://unused.host\" + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar buf []byte\n\t\tfor pb.Next() {\n\t\t\tstatusCode, body, err := c.Get(buf, url)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif statusCode != StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t\t\t}\n\t\t\tif string(body) != requestURI {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, requestURI)\n\t\t\t}\n\t\t\tbuf = body\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd1Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndInmemory(b, 1)\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd10Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndInmemory(b, 10)\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd100Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndInmemory(b, 100)\n}\n\nfunc BenchmarkNetHTTPClientGetEndToEnd1000Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientGetEndToEndInmemory(b, 1000)\n}\n\nfunc benchmarkNetHTTPClientGetEndToEndInmemory(b *testing.B, parallelism int) {\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := http.Serve(ln, http.HandlerFunc(nethttpEchoHandler)); err != nil && !strings.Contains(\n\t\t\terr.Error(), \"use of closed network connection\") {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDial:                func(_, _ string) (net.Conn, error) { return ln.Dial() },\n\t\t\tMaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),\n\t\t},\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://unused.host\" + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tresp, err := c.Get(url)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode, http.StatusOK)\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tresp.Body.Close()\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading response body: %v\", err)\n\t\t\t}\n\t\t\tif string(body) != requestURI {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, requestURI)\n\t\t\t}\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkClientEndToEndBigResponse1Inmemory(b *testing.B) {\n\tbenchmarkClientEndToEndBigResponseInmemory(b, 1)\n}\n\nfunc BenchmarkClientEndToEndBigResponse10Inmemory(b *testing.B) {\n\tbenchmarkClientEndToEndBigResponseInmemory(b, 10)\n}\n\nfunc benchmarkClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) {\n\tbigResponse := createFixedBody(1024 * 1024)\n\th := func(ctx *RequestCtx) {\n\t\tctx.SetContentType(\"text/plain\")\n\t\tctx.Write(bigResponse) //nolint:errcheck\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := Serve(ln, h); err != nil {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &Client{\n\t\tMaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,\n\t\tDial:            func(addr string) (net.Conn, error) { return ln.Dial() },\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://unused.host\" + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar req Request\n\t\treq.SetRequestURI(url)\n\t\tvar resp Response\n\t\tfor pb.Next() {\n\t\t\tif err := c.DoTimeout(&req, &resp, 5*time.Second); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\t\t\tbody := resp.Body()\n\t\t\tif !bytes.Equal(bigResponse, body) {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, bigResponse)\n\t\t\t}\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkNetHTTPClientEndToEndBigResponse1Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientEndToEndBigResponseInmemory(b, 1)\n}\n\nfunc BenchmarkNetHTTPClientEndToEndBigResponse10Inmemory(b *testing.B) {\n\tbenchmarkNetHTTPClientEndToEndBigResponseInmemory(b, 10)\n}\n\nfunc benchmarkNetHTTPClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) {\n\tbigResponse := createFixedBody(1024 * 1024)\n\th := func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Set(HeaderContentType, \"text/plain\")\n\t\tw.Write(bigResponse) //nolint:errcheck\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := http.Serve(ln, http.HandlerFunc(h)); err != nil && !strings.Contains(\n\t\t\terr.Error(), \"use of closed network connection\") {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tc := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDial:                func(_, _ string) (net.Conn, error) { return ln.Dial() },\n\t\t\tMaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),\n\t\t},\n\t\tTimeout: 5 * time.Second,\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://unused.host\" + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\treq, err := http.NewRequest(MethodGet, url, http.NoBody)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tfor pb.Next() {\n\t\t\tresp, err := c.Do(req)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode, http.StatusOK)\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tresp.Body.Close()\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading response body: %v\", err)\n\t\t\t}\n\t\t\tif !bytes.Equal(bigResponse, body) {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, bigResponse)\n\t\t\t}\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n\nfunc BenchmarkPipelineClient1(b *testing.B) {\n\tbenchmarkPipelineClient(b, 1)\n}\n\nfunc BenchmarkPipelineClient10(b *testing.B) {\n\tbenchmarkPipelineClient(b, 10)\n}\n\nfunc BenchmarkPipelineClient100(b *testing.B) {\n\tbenchmarkPipelineClient(b, 100)\n}\n\nfunc BenchmarkPipelineClient1000(b *testing.B) {\n\tbenchmarkPipelineClient(b, 1000)\n}\n\nfunc benchmarkPipelineClient(b *testing.B, parallelism int) {\n\th := func(ctx *RequestCtx) {\n\t\tctx.WriteString(\"foobar\") //nolint:errcheck\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := Serve(ln, h); err != nil {\n\t\t\tb.Errorf(\"error when serving requests: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tmaxConns := runtime.GOMAXPROCS(-1)\n\tc := &PipelineClient{\n\t\tDial:               func(addr string) (net.Conn, error) { return ln.Dial() },\n\t\tReadBufferSize:     1024 * 1024,\n\t\tWriteBufferSize:    1024 * 1024,\n\t\tMaxConns:           maxConns,\n\t\tMaxPendingRequests: parallelism * maxConns,\n\t}\n\n\trequestURI := \"/foo/bar?baz=123\"\n\turl := \"http://unused.host\" + requestURI\n\tb.SetParallelism(parallelism)\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar req Request\n\t\treq.SetRequestURI(url)\n\t\tvar resp Response\n\t\tfor pb.Next() {\n\t\t\tif err := c.Do(&req, &resp); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"foobar\" {\n\t\t\t\tb.Fatalf(\"unexpected response %q. Expecting %q\", body, \"foobar\")\n\t\t\t}\n\t\t}\n\t})\n\n\tln.Close()\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tb.Fatalf(\"server wasn't stopped\")\n\t}\n}\n"
  },
  {
    "path": "coarsetime.go",
    "content": "package fasthttp\n\nimport (\n\t\"time\"\n)\n\n// CoarseTimeNow returns the current time truncated to the nearest second.\n//\n// Deprecated: This is slower than calling time.Now() directly.\n// This is now time.Now().Truncate(time.Second) shortcut.\nfunc CoarseTimeNow() time.Time {\n\treturn time.Now().Truncate(time.Second)\n}\n"
  },
  {
    "path": "compress.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"sync\"\n\n\t\"github.com/klauspost/compress/flate\"\n\t\"github.com/klauspost/compress/gzip\"\n\t\"github.com/klauspost/compress/zlib\"\n\t\"github.com/valyala/bytebufferpool\"\n\t\"github.com/valyala/fasthttp/stackless\"\n)\n\n// Supported compression levels.\nconst (\n\tCompressNoCompression      = flate.NoCompression\n\tCompressBestSpeed          = flate.BestSpeed\n\tCompressBestCompression    = flate.BestCompression\n\tCompressDefaultCompression = 6  // flate.DefaultCompression\n\tCompressHuffmanOnly        = -2 // flate.HuffmanOnly\n)\n\nfunc acquireGzipReader(r io.Reader) (*gzip.Reader, error) {\n\tv := gzipReaderPool.Get()\n\tif v == nil {\n\t\treturn gzip.NewReader(r)\n\t}\n\tzr := v.(*gzip.Reader)\n\tif err := zr.Reset(r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn zr, nil\n}\n\nfunc releaseGzipReader(zr *gzip.Reader) {\n\tzr.Close()\n\tgzipReaderPool.Put(zr)\n}\n\nvar gzipReaderPool sync.Pool\n\nfunc acquireFlateReader(r io.Reader) (io.ReadCloser, error) {\n\tv := flateReaderPool.Get()\n\tif v == nil {\n\t\tzr, err := zlib.NewReader(r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn zr, nil\n\t}\n\tzr := v.(io.ReadCloser)\n\tif err := resetFlateReader(zr, r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn zr, nil\n}\n\nfunc releaseFlateReader(zr io.ReadCloser) {\n\tzr.Close()\n\tflateReaderPool.Put(zr)\n}\n\nfunc resetFlateReader(zr io.ReadCloser, r io.Reader) error {\n\tzrr, ok := zr.(zlib.Resetter)\n\tif !ok {\n\t\t// sanity check. should only be called with a zlib.Reader\n\t\tpanic(\"BUG: zlib.Reader doesn't implement zlib.Resetter???\")\n\t}\n\treturn zrr.Reset(r, nil)\n}\n\nvar flateReaderPool sync.Pool\n\nfunc acquireStacklessGzipWriter(w io.Writer, level int) stackless.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessGzipWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\treturn stackless.NewWriter(w, func(w io.Writer) stackless.Writer {\n\t\t\treturn acquireRealGzipWriter(w, level)\n\t\t})\n\t}\n\tsw := v.(stackless.Writer)\n\tsw.Reset(w)\n\treturn sw\n}\n\nfunc releaseStacklessGzipWriter(sw stackless.Writer, level int) {\n\tsw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessGzipWriterPoolMap[nLevel]\n\tp.Put(sw)\n}\n\nfunc acquireRealGzipWriter(w io.Writer, level int) *gzip.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := realGzipWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\tzw, err := gzip.NewWriterLevel(w, level)\n\t\tif err != nil {\n\t\t\t// gzip.NewWriterLevel only errors for invalid\n\t\t\t// compression levels. Clamp it to be min or max.\n\t\t\tif level < gzip.HuffmanOnly {\n\t\t\t\tlevel = gzip.HuffmanOnly\n\t\t\t} else {\n\t\t\t\tlevel = gzip.BestCompression\n\t\t\t}\n\t\t\tzw, _ = gzip.NewWriterLevel(w, level)\n\t\t}\n\t\treturn zw\n\t}\n\tzw := v.(*gzip.Writer)\n\tzw.Reset(w)\n\treturn zw\n}\n\nfunc releaseRealGzipWriter(zw *gzip.Writer, level int) {\n\tzw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := realGzipWriterPoolMap[nLevel]\n\tp.Put(zw)\n}\n\nvar (\n\tstacklessGzipWriterPoolMap = newCompressWriterPoolMap()\n\trealGzipWriterPoolMap      = newCompressWriterPoolMap()\n)\n\n// AppendGzipBytesLevel appends gzipped src to dst using the given\n// compression level and returns the resulting dst.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc AppendGzipBytesLevel(dst, src []byte, level int) []byte {\n\tw := &byteSliceWriter{b: dst}\n\tWriteGzipLevel(w, src, level) //nolint:errcheck\n\treturn w.b\n}\n\n// WriteGzipLevel writes gzipped p to w using the given compression level\n// and returns the number of compressed bytes written to w.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {\n\tswitch w.(type) {\n\tcase *byteSliceWriter,\n\t\t*bytes.Buffer,\n\t\t*bytebufferpool.ByteBuffer:\n\t\t// These writers don't block, so we can just use stacklessWriteGzip\n\t\tctx := &compressCtx{\n\t\t\tw:     w,\n\t\t\tp:     p,\n\t\t\tlevel: level,\n\t\t}\n\t\tstacklessWriteGzip(ctx)\n\t\treturn len(p), nil\n\tdefault:\n\t\tzw := acquireStacklessGzipWriter(w, level)\n\t\tn, err := zw.Write(p)\n\t\treleaseStacklessGzipWriter(zw, level)\n\t\treturn n, err\n\t}\n}\n\nvar (\n\tstacklessWriteGzipOnce sync.Once\n\tstacklessWriteGzipFunc func(ctx any) bool\n)\n\nfunc stacklessWriteGzip(ctx any) {\n\tstacklessWriteGzipOnce.Do(func() {\n\t\tstacklessWriteGzipFunc = stackless.NewFunc(nonblockingWriteGzip)\n\t})\n\tstacklessWriteGzipFunc(ctx)\n}\n\nfunc nonblockingWriteGzip(ctxv any) {\n\tctx := ctxv.(*compressCtx)\n\tzw := acquireRealGzipWriter(ctx.w, ctx.level)\n\n\tzw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway\n\n\treleaseRealGzipWriter(zw, ctx.level)\n}\n\n// WriteGzip writes gzipped p to w and returns the number of compressed\n// bytes written to w.\nfunc WriteGzip(w io.Writer, p []byte) (int, error) {\n\treturn WriteGzipLevel(w, p, CompressDefaultCompression)\n}\n\n// AppendGzipBytes appends gzipped src to dst and returns the resulting dst.\nfunc AppendGzipBytes(dst, src []byte) []byte {\n\treturn AppendGzipBytesLevel(dst, src, CompressDefaultCompression)\n}\n\n// WriteGunzip writes ungzipped p to w and returns the number of uncompressed\n// bytes written to w.\nfunc WriteGunzip(w io.Writer, p []byte) (int, error) {\n\treturn writeGunzip(w, p, 0)\n}\n\nfunc writeGunzip(w io.Writer, p []byte, maxBodySize int) (int, error) {\n\tr := &byteSliceReader{b: p}\n\tzr, err := acquireGzipReader(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := copyZeroAllocWithLimit(w, zr, maxBodySize)\n\treleaseGzipReader(zr)\n\tnn := int(n)\n\tif int64(nn) != n {\n\t\treturn 0, fmt.Errorf(\"too much data gunzipped: %d\", n)\n\t}\n\treturn nn, err\n}\n\n// AppendGunzipBytes appends gunzipped src to dst and returns the resulting dst.\nfunc AppendGunzipBytes(dst, src []byte) ([]byte, error) {\n\tw := &byteSliceWriter{b: dst}\n\t_, err := WriteGunzip(w, src)\n\treturn w.b, err\n}\n\n// AppendDeflateBytesLevel appends deflated src to dst using the given\n// compression level and returns the resulting dst.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc AppendDeflateBytesLevel(dst, src []byte, level int) []byte {\n\tw := &byteSliceWriter{b: dst}\n\tWriteDeflateLevel(w, src, level) //nolint:errcheck\n\treturn w.b\n}\n\n// WriteDeflateLevel writes deflated p to w using the given compression level\n// and returns the number of compressed bytes written to w.\n//\n// Supported compression levels are:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc WriteDeflateLevel(w io.Writer, p []byte, level int) (int, error) {\n\tswitch w.(type) {\n\tcase *byteSliceWriter,\n\t\t*bytes.Buffer,\n\t\t*bytebufferpool.ByteBuffer:\n\t\t// These writers don't block, so we can just use stacklessWriteDeflate\n\t\tctx := &compressCtx{\n\t\t\tw:     w,\n\t\t\tp:     p,\n\t\t\tlevel: level,\n\t\t}\n\t\tstacklessWriteDeflate(ctx)\n\t\treturn len(p), nil\n\tdefault:\n\t\tzw := acquireStacklessDeflateWriter(w, level)\n\t\tn, err := zw.Write(p)\n\t\treleaseStacklessDeflateWriter(zw, level)\n\t\treturn n, err\n\t}\n}\n\nvar (\n\tstacklessWriteDeflateOnce sync.Once\n\tstacklessWriteDeflateFunc func(ctx any) bool\n)\n\nfunc stacklessWriteDeflate(ctx any) {\n\tstacklessWriteDeflateOnce.Do(func() {\n\t\tstacklessWriteDeflateFunc = stackless.NewFunc(nonblockingWriteDeflate)\n\t})\n\tstacklessWriteDeflateFunc(ctx)\n}\n\nfunc nonblockingWriteDeflate(ctxv any) {\n\tctx := ctxv.(*compressCtx)\n\tzw := acquireRealDeflateWriter(ctx.w, ctx.level)\n\n\tzw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway\n\n\treleaseRealDeflateWriter(zw, ctx.level)\n}\n\ntype compressCtx struct {\n\tw     io.Writer\n\tp     []byte\n\tlevel int\n}\n\n// WriteDeflate writes deflated p to w and returns the number of compressed\n// bytes written to w.\nfunc WriteDeflate(w io.Writer, p []byte) (int, error) {\n\treturn WriteDeflateLevel(w, p, CompressDefaultCompression)\n}\n\n// AppendDeflateBytes appends deflated src to dst and returns the resulting dst.\nfunc AppendDeflateBytes(dst, src []byte) []byte {\n\treturn AppendDeflateBytesLevel(dst, src, CompressDefaultCompression)\n}\n\n// WriteInflate writes inflated p to w and returns the number of uncompressed\n// bytes written to w.\nfunc WriteInflate(w io.Writer, p []byte) (int, error) {\n\treturn writeInflate(w, p, 0)\n}\n\nfunc writeInflate(w io.Writer, p []byte, maxBodySize int) (int, error) {\n\tr := &byteSliceReader{b: p}\n\tzr, err := acquireFlateReader(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := copyZeroAllocWithLimit(w, zr, maxBodySize)\n\treleaseFlateReader(zr)\n\tnn := int(n)\n\tif int64(nn) != n {\n\t\treturn 0, fmt.Errorf(\"too much data inflated: %d\", n)\n\t}\n\treturn nn, err\n}\n\n// AppendInflateBytes appends inflated src to dst and returns the resulting dst.\nfunc AppendInflateBytes(dst, src []byte) ([]byte, error) {\n\tw := &byteSliceWriter{b: dst}\n\t_, err := WriteInflate(w, src)\n\treturn w.b, err\n}\n\ntype byteSliceWriter struct {\n\tb []byte\n}\n\nfunc (w *byteSliceWriter) Write(p []byte) (int, error) {\n\tw.b = append(w.b, p...)\n\treturn len(p), nil\n}\n\nfunc (w *byteSliceWriter) WriteString(s string) (int, error) {\n\tw.b = append(w.b, s...)\n\treturn len(s), nil\n}\n\ntype byteSliceReader struct {\n\tb []byte\n}\n\nfunc (r *byteSliceReader) Read(p []byte) (int, error) {\n\tif len(r.b) == 0 {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(p, r.b)\n\tr.b = r.b[n:]\n\treturn n, nil\n}\n\nfunc (r *byteSliceReader) ReadByte() (byte, error) {\n\tif len(r.b) == 0 {\n\t\treturn 0, io.EOF\n\t}\n\tn := r.b[0]\n\tr.b = r.b[1:]\n\treturn n, nil\n}\n\nfunc acquireStacklessDeflateWriter(w io.Writer, level int) stackless.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessDeflateWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\treturn stackless.NewWriter(w, func(w io.Writer) stackless.Writer {\n\t\t\treturn acquireRealDeflateWriter(w, level)\n\t\t})\n\t}\n\tsw := v.(stackless.Writer)\n\tsw.Reset(w)\n\treturn sw\n}\n\nfunc releaseStacklessDeflateWriter(sw stackless.Writer, level int) {\n\tsw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := stacklessDeflateWriterPoolMap[nLevel]\n\tp.Put(sw)\n}\n\nfunc acquireRealDeflateWriter(w io.Writer, level int) *zlib.Writer {\n\tnLevel := normalizeCompressLevel(level)\n\tp := realDeflateWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\tzw, err := zlib.NewWriterLevel(w, level)\n\t\tif err != nil {\n\t\t\t// zlib.NewWriterLevel only errors for invalid\n\t\t\t// compression levels. Clamp it to be min or max.\n\t\t\tif level < zlib.HuffmanOnly {\n\t\t\t\tlevel = zlib.HuffmanOnly\n\t\t\t} else {\n\t\t\t\tlevel = zlib.BestCompression\n\t\t\t}\n\t\t\tzw, _ = zlib.NewWriterLevel(w, level)\n\t\t}\n\t\treturn zw\n\t}\n\tzw := v.(*zlib.Writer)\n\tzw.Reset(w)\n\treturn zw\n}\n\nfunc releaseRealDeflateWriter(zw *zlib.Writer, level int) {\n\tzw.Close()\n\tnLevel := normalizeCompressLevel(level)\n\tp := realDeflateWriterPoolMap[nLevel]\n\tp.Put(zw)\n}\n\nvar (\n\tstacklessDeflateWriterPoolMap = newCompressWriterPoolMap()\n\trealDeflateWriterPoolMap      = newCompressWriterPoolMap()\n)\n\nfunc newCompressWriterPoolMap() []*sync.Pool {\n\t// Initialize pools for all the compression levels defined\n\t// in https://pkg.go.dev/compress/flate#pkg-constants .\n\t// Compression levels are normalized with normalizeCompressLevel,\n\t// so the fit [0..11].\n\tm := make([]*sync.Pool, 0, 12)\n\tfor range 12 {\n\t\tm = append(m, &sync.Pool{})\n\t}\n\treturn m\n}\n\nfunc isFileCompressible(f fs.File, minCompressRatio float64) bool {\n\t// Try compressing the first 4kb of the file\n\t// and see if it can be compressed by more than\n\t// the given minCompressRatio.\n\tb := bytebufferpool.Get()\n\tzw := acquireStacklessGzipWriter(b, CompressDefaultCompression)\n\tlr := &io.LimitedReader{\n\t\tR: f,\n\t\tN: 4096,\n\t}\n\t_, err := copyZeroAlloc(zw, lr)\n\treleaseStacklessGzipWriter(zw, CompressDefaultCompression)\n\tseeker, ok := f.(io.Seeker)\n\tif !ok {\n\t\treturn false\n\t}\n\tseeker.Seek(0, io.SeekStart) //nolint:errcheck\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tn := 4096 - lr.N\n\tzn := len(b.B)\n\tbytebufferpool.Put(b)\n\treturn float64(zn) < float64(n)*minCompressRatio\n}\n\n// normalizes compression level into [0..11], so it could be used as an index\n// in *PoolMap.\nfunc normalizeCompressLevel(level int) int {\n\t// -2 is the lowest compression level - CompressHuffmanOnly\n\t// 9 is the highest compression level - CompressBestCompression\n\tif level < -2 || level > 9 {\n\t\tlevel = CompressDefaultCompression\n\t}\n\treturn level + 2\n}\n"
  },
  {
    "path": "compress_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar compressTestcases = func() []string {\n\ta := make([]string, 0, 4)\n\ta = append(a,\n\t\t\"\",\n\t\t\"foobar\",\n\t\t\"выфаодлодл одлфываыв sd2 k34\",\n\t)\n\tbigS := createFixedBody(1e4)\n\ta = append(a, string(bigS))\n\treturn a\n}()\n\nfunc TestGzipBytesSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testGzipBytes(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestGzipBytesConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testGzipBytes); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDeflateBytesSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testDeflateBytes(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDeflateBytesConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testDeflateBytes); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testGzipBytes() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testGzipBytesSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testDeflateBytes() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testDeflateBytesSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testGzipBytesSingleCase(s string) error {\n\tprefix := []byte(\"foobar\")\n\tgzippedS := AppendGzipBytes(prefix, []byte(s))\n\tif !bytes.Equal(gzippedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when compressing %q: %q. Expecting %q\", s, gzippedS[:len(prefix)], prefix)\n\t}\n\n\tgunzippedS, err := AppendGunzipBytes(prefix, gzippedS[len(prefix):])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error when uncompressing %q: %w\", s, err)\n\t}\n\tif !bytes.Equal(gunzippedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when uncompressing %q: %q. Expecting %q\", s, gunzippedS[:len(prefix)], prefix)\n\t}\n\tgunzippedS = gunzippedS[len(prefix):]\n\tif string(gunzippedS) != s {\n\t\treturn fmt.Errorf(\"unexpected uncompressed string %q. Expecting %q\", gunzippedS, s)\n\t}\n\treturn nil\n}\n\nfunc testDeflateBytesSingleCase(s string) error {\n\tprefix := []byte(\"foobar\")\n\tdeflatedS := AppendDeflateBytes(prefix, []byte(s))\n\tif !bytes.Equal(deflatedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when compressing %q: %q. Expecting %q\", s, deflatedS[:len(prefix)], prefix)\n\t}\n\n\tinflatedS, err := AppendInflateBytes(prefix, deflatedS[len(prefix):])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error when uncompressing %q: %w\", s, err)\n\t}\n\tif !bytes.Equal(inflatedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when uncompressing %q: %q. Expecting %q\", s, inflatedS[:len(prefix)], prefix)\n\t}\n\tinflatedS = inflatedS[len(prefix):]\n\tif string(inflatedS) != s {\n\t\treturn fmt.Errorf(\"unexpected uncompressed string %q. Expecting %q\", inflatedS, s)\n\t}\n\treturn nil\n}\n\nfunc TestGzipCompressSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testGzipCompress(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestGzipCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testGzipCompress); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFlateCompressSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testFlateCompress(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFlateCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testFlateCompress); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testGzipCompress() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testGzipCompressSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testFlateCompress() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testFlateCompressSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testGzipCompressSingleCase(s string) error {\n\tvar buf bytes.Buffer\n\tzw := acquireStacklessGzipWriter(&buf, CompressDefaultCompression)\n\tif _, err := zw.Write([]byte(s)); err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\treleaseStacklessGzipWriter(zw, CompressDefaultCompression)\n\n\tzr, err := acquireGzipReader(&buf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tbody, err := io.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tif string(body) != s {\n\t\treturn fmt.Errorf(\"unexpected string after decompression: %q. Expecting %q\", body, s)\n\t}\n\treleaseGzipReader(zr)\n\treturn nil\n}\n\nfunc testFlateCompressSingleCase(s string) error {\n\tvar buf bytes.Buffer\n\tzw := acquireStacklessDeflateWriter(&buf, CompressDefaultCompression)\n\tif _, err := zw.Write([]byte(s)); err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\treleaseStacklessDeflateWriter(zw, CompressDefaultCompression)\n\n\tzr, err := acquireFlateReader(&buf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tbody, err := io.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tif string(body) != s {\n\t\treturn fmt.Errorf(\"unexpected string after decompression: %q. Expecting %q\", body, s)\n\t}\n\treleaseFlateReader(zr)\n\treturn nil\n}\n\nfunc testConcurrent(concurrency int, f func() error) error {\n\tch := make(chan error, concurrency)\n\tfor i := range concurrency {\n\t\tgo func(idx int) {\n\t\t\terr := f()\n\t\t\tif err != nil {\n\t\t\t\tch <- fmt.Errorf(\"error in goroutine %d: %w\", idx, err)\n\t\t\t}\n\t\t\tch <- nil\n\t\t}(i)\n\t}\n\tfor range concurrency {\n\t\tselect {\n\t\tcase err := <-ch:\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\treturn errors.New(\"timeout\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cookie.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar zeroTime time.Time\n\nvar (\n\t// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.\n\tCookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)\n\n\t// CookieExpireUnlimited indicates that the cookie doesn't expire.\n\tCookieExpireUnlimited = zeroTime\n)\n\n// CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.\n// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.\ntype CookieSameSite int\n\nconst (\n\t// CookieSameSiteDisabled removes the SameSite flag.\n\tCookieSameSiteDisabled CookieSameSite = iota\n\t// CookieSameSiteDefaultMode sets the SameSite flag.\n\tCookieSameSiteDefaultMode\n\t// CookieSameSiteLaxMode sets the SameSite flag with the \"Lax\" parameter.\n\tCookieSameSiteLaxMode\n\t// CookieSameSiteStrictMode sets the SameSite flag with the \"Strict\" parameter.\n\tCookieSameSiteStrictMode\n\t// CookieSameSiteNoneMode sets the SameSite flag with the \"None\" parameter.\n\t// See https://tools.ietf.org/html/draft-west-cookie-incrementalism-00\n\tCookieSameSiteNoneMode // third-party cookies are phasing out, use Partitioned cookies instead\n)\n\n// AcquireCookie returns an empty Cookie object from the pool.\n//\n// The returned object may be returned back to the pool with ReleaseCookie.\n// This allows reducing GC load.\nfunc AcquireCookie() *Cookie {\n\treturn cookiePool.Get().(*Cookie)\n}\n\n// ReleaseCookie returns the Cookie object acquired with AcquireCookie back\n// to the pool.\n//\n// Do not access released Cookie object, otherwise data races may occur.\nfunc ReleaseCookie(c *Cookie) {\n\tc.Reset()\n\tcookiePool.Put(c)\n}\n\nvar cookiePool = &sync.Pool{\n\tNew: func() any {\n\t\treturn &Cookie{}\n\t},\n}\n\n// Cookie represents HTTP response cookie.\n//\n// Do not copy Cookie objects. Create new object and use CopyTo instead.\n//\n// Cookie instance MUST NOT be used from concurrently running goroutines.\ntype Cookie struct {\n\tnoCopy noCopy\n\n\texpire time.Time\n\n\tkey    []byte\n\tvalue  []byte\n\tdomain []byte\n\tpath   []byte\n\n\tbufK []byte\n\tbufV []byte\n\n\t// maxAge=0 means no 'max-age' attribute specified.\n\t// maxAge<0 means delete cookie now, equivalently 'max-age=0'\n\t// maxAge>0 means 'max-age' attribute present and given in seconds\n\tmaxAge int\n\n\tsameSite    CookieSameSite\n\thttpOnly    bool\n\tsecure      bool\n\tpartitioned bool\n}\n\n// CopyTo copies src cookie to c.\nfunc (c *Cookie) CopyTo(src *Cookie) {\n\tc.Reset()\n\tc.key = append(c.key, src.key...)\n\tc.value = append(c.value, src.value...)\n\tc.expire = src.expire\n\tc.maxAge = src.maxAge\n\tc.domain = append(c.domain, src.domain...)\n\tc.path = append(c.path, src.path...)\n\tc.httpOnly = src.httpOnly\n\tc.secure = src.secure\n\tc.sameSite = src.sameSite\n\tc.partitioned = src.partitioned\n}\n\n// HTTPOnly returns true if the cookie is http only.\nfunc (c *Cookie) HTTPOnly() bool {\n\treturn c.httpOnly\n}\n\n// SetHTTPOnly sets cookie's httpOnly flag to the given value.\nfunc (c *Cookie) SetHTTPOnly(httpOnly bool) {\n\tc.httpOnly = httpOnly\n}\n\n// Secure returns true if the cookie is secure.\nfunc (c *Cookie) Secure() bool {\n\treturn c.secure\n}\n\n// SetSecure sets cookie's secure flag to the given value.\nfunc (c *Cookie) SetSecure(secure bool) {\n\tc.secure = secure\n}\n\n// SameSite returns the SameSite mode.\nfunc (c *Cookie) SameSite() CookieSameSite {\n\treturn c.sameSite\n}\n\n// SetSameSite sets the cookie's SameSite flag to the given value.\n// Set value CookieSameSiteNoneMode will set Secure to true also to avoid browser rejection.\nfunc (c *Cookie) SetSameSite(mode CookieSameSite) {\n\tc.sameSite = mode\n\tif mode == CookieSameSiteNoneMode {\n\t\tc.SetSecure(true)\n\t}\n}\n\n// Partitioned returns true if the cookie is partitioned.\nfunc (c *Cookie) Partitioned() bool {\n\treturn c.partitioned\n}\n\n// SetPartitioned sets the cookie's Partitioned flag to the given value.\n// Set value Partitioned to true will set Secure to true and Path to / also to avoid browser rejection.\nfunc (c *Cookie) SetPartitioned(partitioned bool) {\n\tc.partitioned = partitioned\n\tif partitioned {\n\t\tc.SetSecure(true)\n\t\tc.SetPath(\"/\")\n\t}\n}\n\n// Path returns cookie path.\nfunc (c *Cookie) Path() []byte {\n\treturn c.path\n}\n\n// SetPath sets cookie path.\nfunc (c *Cookie) SetPath(path string) {\n\tc.bufK = append(c.bufK[:0], path...)\n\tc.path = normalizePath(c.path, c.bufK)\n}\n\n// SetPathBytes sets cookie path.\nfunc (c *Cookie) SetPathBytes(path []byte) {\n\tc.bufK = append(c.bufK[:0], path...)\n\tc.path = normalizePath(c.path, c.bufK)\n}\n\n// Domain returns cookie domain.\n//\n// The returned value is valid until the Cookie reused or released (ReleaseCookie).\n// Do not store references to the returned value. Make copies instead.\nfunc (c *Cookie) Domain() []byte {\n\treturn c.domain\n}\n\n// SetDomain sets cookie domain.\nfunc (c *Cookie) SetDomain(domain string) {\n\tc.domain = append(c.domain[:0], domain...)\n}\n\n// SetDomainBytes sets cookie domain.\nfunc (c *Cookie) SetDomainBytes(domain []byte) {\n\tc.domain = append(c.domain[:0], domain...)\n}\n\n// MaxAge returns the seconds until the cookie is meant to expire or 0\n// if no max age.\nfunc (c *Cookie) MaxAge() int {\n\treturn c.maxAge\n}\n\n// SetMaxAge sets cookie expiration time based on seconds. This takes precedence\n// over any absolute expiry set on the cookie.\n//\n// 'max-age' is set when the maxAge is non-zero. That is, if maxAge = 0,\n// the 'max-age' is unset. If maxAge < 0, it indicates that the cookie should\n// be deleted immediately, equivalent to 'max-age=0'. This behavior is\n// consistent with the Go standard library's net/http package.\nfunc (c *Cookie) SetMaxAge(seconds int) {\n\tc.maxAge = seconds\n}\n\n// Expire returns cookie expiration time.\n//\n// CookieExpireUnlimited is returned if cookie doesn't expire.\nfunc (c *Cookie) Expire() time.Time {\n\texpire := c.expire\n\tif expire.IsZero() {\n\t\texpire = CookieExpireUnlimited\n\t}\n\treturn expire\n}\n\n// SetExpire sets cookie expiration time.\n//\n// Set expiration time to CookieExpireDelete for expiring (deleting)\n// the cookie on the client.\n//\n// By default cookie lifetime is limited by browser session.\nfunc (c *Cookie) SetExpire(expire time.Time) {\n\tc.expire = expire\n}\n\n// Value returns cookie value.\n//\n// The returned value is valid until the Cookie reused or released (ReleaseCookie).\n// Do not store references to the returned value. Make copies instead.\nfunc (c *Cookie) Value() []byte {\n\treturn c.value\n}\n\n// SetValue sets cookie value.\nfunc (c *Cookie) SetValue(value string) {\n\tc.value = append(c.value[:0], value...)\n}\n\n// SetValueBytes sets cookie value.\nfunc (c *Cookie) SetValueBytes(value []byte) {\n\tc.value = append(c.value[:0], value...)\n}\n\n// Key returns cookie name.\n//\n// The returned value is valid until the Cookie reused or released (ReleaseCookie).\n// Do not store references to the returned value. Make copies instead.\nfunc (c *Cookie) Key() []byte {\n\treturn c.key\n}\n\n// SetKey sets cookie name.\nfunc (c *Cookie) SetKey(key string) {\n\tc.key = append(c.key[:0], key...)\n}\n\n// SetKeyBytes sets cookie name.\nfunc (c *Cookie) SetKeyBytes(key []byte) {\n\tc.key = append(c.key[:0], key...)\n}\n\n// Reset clears the cookie.\nfunc (c *Cookie) Reset() {\n\tc.key = c.key[:0]\n\tc.value = c.value[:0]\n\tc.expire = zeroTime\n\tc.maxAge = 0\n\tc.domain = c.domain[:0]\n\tc.path = c.path[:0]\n\tc.httpOnly = false\n\tc.secure = false\n\tc.sameSite = CookieSameSiteDisabled\n\tc.partitioned = false\n}\n\n// AppendBytes appends cookie representation to dst and returns\n// the extended dst.\nfunc (c *Cookie) AppendBytes(dst []byte) []byte {\n\tif len(c.key) > 0 {\n\t\tdst = append(dst, c.key...)\n\t\tdst = append(dst, '=')\n\t}\n\tdst = append(dst, c.value...)\n\n\tif c.maxAge != 0 {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieMaxAge...)\n\t\tdst = append(dst, '=')\n\t\tif c.maxAge < 0 {\n\t\t\t// See https://github.com/valyala/fasthttp/issues/1900\n\t\t\tdst = AppendUint(dst, 0)\n\t\t} else {\n\t\t\tdst = AppendUint(dst, c.maxAge)\n\t\t}\n\t} else if !c.expire.IsZero() {\n\t\tc.bufV = AppendHTTPDate(c.bufV[:0], c.expire)\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieExpires...)\n\t\tdst = append(dst, '=')\n\t\tdst = append(dst, c.bufV...)\n\t}\n\tif len(c.domain) > 0 {\n\t\tdst = appendCookiePart(dst, strCookieDomain, c.domain)\n\t}\n\tif len(c.path) > 0 {\n\t\tdst = appendCookiePart(dst, strCookiePath, c.path)\n\t}\n\tif c.httpOnly {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieHTTPOnly...)\n\t}\n\tif c.secure {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieSecure...)\n\t}\n\tswitch c.sameSite {\n\tcase CookieSameSiteDefaultMode:\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieSameSite...)\n\tcase CookieSameSiteLaxMode:\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieSameSite...)\n\t\tdst = append(dst, '=')\n\t\tdst = append(dst, strCookieSameSiteLax...)\n\tcase CookieSameSiteStrictMode:\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieSameSite...)\n\t\tdst = append(dst, '=')\n\t\tdst = append(dst, strCookieSameSiteStrict...)\n\tcase CookieSameSiteNoneMode:\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookieSameSite...)\n\t\tdst = append(dst, '=')\n\t\tdst = append(dst, strCookieSameSiteNone...)\n\t}\n\tif c.partitioned {\n\t\tdst = append(dst, ';', ' ')\n\t\tdst = append(dst, strCookiePartitioned...)\n\t}\n\treturn dst\n}\n\n// Cookie returns cookie representation.\n//\n// The returned value is valid until the Cookie reused or released (ReleaseCookie).\n// Do not store references to the returned value. Make copies instead.\nfunc (c *Cookie) Cookie() []byte {\n\tc.bufK = c.AppendBytes(c.bufK[:0])\n\treturn c.bufK\n}\n\n// String returns cookie representation.\nfunc (c *Cookie) String() string {\n\treturn string(c.Cookie())\n}\n\n// WriteTo writes cookie representation to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (c *Cookie) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(c.Cookie())\n\treturn int64(n), err\n}\n\nvar errNoCookies = errors.New(\"no cookies found\")\n\n// Parse parses Set-Cookie header.\nfunc (c *Cookie) Parse(src string) error {\n\tc.bufK = append(c.bufK[:0], src...)\n\treturn c.ParseBytes(c.bufK)\n}\n\n// ParseBytes parses Set-Cookie header.\nfunc (c *Cookie) ParseBytes(src []byte) error {\n\tc.Reset()\n\n\tvar s cookieScanner\n\ts.b = src\n\n\tif !s.next(&c.bufK, &c.bufV) {\n\t\treturn errNoCookies\n\t}\n\n\tc.key = append(c.key, c.bufK...)\n\tc.value = append(c.value, c.bufV...)\n\n\tfor s.next(&c.bufK, &c.bufV) {\n\t\tif len(c.bufK) != 0 {\n\t\t\t// Case insensitive switch on first char\n\t\t\tswitch c.bufK[0] | 0x20 {\n\t\t\tcase 'm':\n\t\t\t\tif caseInsensitiveCompare(strCookieMaxAge, c.bufK) {\n\t\t\t\t\tmaxAge, err := ParseUint(c.bufV)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tc.maxAge = maxAge\n\t\t\t\t}\n\n\t\t\tcase 'e': // \"expires\"\n\t\t\t\tif caseInsensitiveCompare(strCookieExpires, c.bufK) {\n\t\t\t\t\tv := b2s(c.bufV)\n\t\t\t\t\t// Try the same two formats as net/http\n\t\t\t\t\t// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135\n\t\t\t\t\texptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\texptime, err = time.Parse(\"Mon, 02-Jan-2006 15:04:05 MST\", v)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tc.expire = exptime\n\t\t\t\t}\n\n\t\t\tcase 'd': // \"domain\"\n\t\t\t\tif caseInsensitiveCompare(strCookieDomain, c.bufK) {\n\t\t\t\t\tc.domain = append(c.domain, c.bufV...)\n\t\t\t\t}\n\n\t\t\tcase 'p': // \"path\"\n\t\t\t\tif caseInsensitiveCompare(strCookiePath, c.bufK) {\n\t\t\t\t\tc.path = append(c.path, c.bufV...)\n\t\t\t\t}\n\n\t\t\tcase 's': // \"samesite\"\n\t\t\t\tif caseInsensitiveCompare(strCookieSameSite, c.bufK) {\n\t\t\t\t\tif len(c.bufV) > 0 {\n\t\t\t\t\t\t// Case insensitive switch on first char\n\t\t\t\t\t\tswitch c.bufV[0] | 0x20 {\n\t\t\t\t\t\tcase 'l': // \"lax\"\n\t\t\t\t\t\t\tif caseInsensitiveCompare(strCookieSameSiteLax, c.bufV) {\n\t\t\t\t\t\t\t\tc.sameSite = CookieSameSiteLaxMode\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 's': // \"strict\"\n\t\t\t\t\t\t\tif caseInsensitiveCompare(strCookieSameSiteStrict, c.bufV) {\n\t\t\t\t\t\t\t\tc.sameSite = CookieSameSiteStrictMode\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'n': // \"none\"\n\t\t\t\t\t\t\tif caseInsensitiveCompare(strCookieSameSiteNone, c.bufV) {\n\t\t\t\t\t\t\t\tc.sameSite = CookieSameSiteNoneMode\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if len(c.bufV) != 0 {\n\t\t\t// Case insensitive switch on first char\n\t\t\tswitch c.bufV[0] | 0x20 {\n\t\t\tcase 'h': // \"httponly\"\n\t\t\t\tif caseInsensitiveCompare(strCookieHTTPOnly, c.bufV) {\n\t\t\t\t\tc.httpOnly = true\n\t\t\t\t}\n\n\t\t\tcase 's': // \"secure\"\n\t\t\t\tif caseInsensitiveCompare(strCookieSecure, c.bufV) {\n\t\t\t\t\tc.secure = true\n\t\t\t\t} else if caseInsensitiveCompare(strCookieSameSite, c.bufV) {\n\t\t\t\t\tc.sameSite = CookieSameSiteDefaultMode\n\t\t\t\t}\n\t\t\tcase 'p': // \"partitioned\"\n\t\t\t\tif caseInsensitiveCompare(strCookiePartitioned, c.bufV) {\n\t\t\t\t\tc.partitioned = true\n\t\t\t\t}\n\t\t\t}\n\t\t} // else empty or no match\n\t}\n\treturn nil\n}\n\nfunc appendCookiePart(dst, key, value []byte) []byte {\n\tdst = append(dst, ';', ' ')\n\tdst = append(dst, key...)\n\tdst = append(dst, '=')\n\treturn append(dst, value...)\n}\n\nfunc getCookieKey(dst, src []byte) []byte {\n\tn := bytes.IndexByte(src, '=')\n\tif n >= 0 {\n\t\tsrc = src[:n]\n\t}\n\treturn decodeCookieArg(dst, src, false)\n}\n\nfunc appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {\n\tfor i, n := 0, len(cookies); i < n; i++ {\n\t\tkv := &cookies[i]\n\t\tif len(kv.key) > 0 {\n\t\t\tdst = append(dst, kv.key...)\n\t\t\tdst = append(dst, '=')\n\t\t}\n\t\tdst = append(dst, kv.value...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, ';', ' ')\n\t\t}\n\t}\n\treturn dst\n}\n\n// For Response we can not use the above function as response cookies\n// already contain the key= in the value.\nfunc appendResponseCookieBytes(dst []byte, cookies []argsKV) []byte {\n\tfor i, n := 0, len(cookies); i < n; i++ {\n\t\tkv := &cookies[i]\n\t\tdst = append(dst, kv.value...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, ';', ' ')\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc parseRequestCookies(cookies []argsKV, src []byte) []argsKV {\n\tvar s cookieScanner\n\ts.b = src\n\tvar kv *argsKV\n\tcookies, kv = allocArg(cookies)\n\tfor s.next(&kv.key, &kv.value) {\n\t\tif len(kv.key) > 0 || len(kv.value) > 0 {\n\t\t\tcookies, kv = allocArg(cookies)\n\t\t}\n\t}\n\treturn releaseArg(cookies)\n}\n\ntype cookieScanner struct {\n\tb []byte\n}\n\nfunc (s *cookieScanner) next(key, val *[]byte) bool {\n\tb := s.b\n\tif len(b) == 0 {\n\t\treturn false\n\t}\n\n\tisKey := true\n\tk := 0\n\tfor i, c := range b {\n\t\tswitch c {\n\t\tcase '=':\n\t\t\tif isKey {\n\t\t\t\tisKey = false\n\t\t\t\t*key = decodeCookieArg(*key, b[:i], false)\n\t\t\t\tk = i + 1\n\t\t\t}\n\t\tcase ';':\n\t\t\tif isKey {\n\t\t\t\t*key = (*key)[:0]\n\t\t\t}\n\t\t\t*val = decodeCookieArg(*val, b[k:i], true)\n\t\t\ts.b = b[i+1:]\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif isKey {\n\t\t*key = (*key)[:0]\n\t}\n\t*val = decodeCookieArg(*val, b[k:], true)\n\ts.b = b[len(b):]\n\treturn true\n}\n\nfunc decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {\n\tfor len(src) > 0 && src[0] == ' ' {\n\t\tsrc = src[1:]\n\t}\n\tfor len(src) > 0 && src[len(src)-1] == ' ' {\n\t\tsrc = src[:len(src)-1]\n\t}\n\tif skipQuotes {\n\t\tif len(src) > 1 && src[0] == '\"' && src[len(src)-1] == '\"' {\n\t\t\tsrc = src[1 : len(src)-1]\n\t\t}\n\t}\n\treturn append(dst[:0], src...)\n}\n\n// caseInsensitiveCompare does a case insensitive equality comparison of\n// two []byte. Assumes only letters need to be matched.\nfunc caseInsensitiveCompare(a, b []byte) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i]|0x20 != b[i]|0x20 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "cookie_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCookiePanic(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\tif err := c.Parse(\";SAMeSITe=\"); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestCookieValueWithEqualAndSpaceChars(t *testing.T) {\n\tt.Parallel()\n\n\ttestCookieValueWithEqualAndSpaceChars(t, \"sth1\", \"/\", \"MTQ2NjU5NTcwN3xfUVduVXk4aG9jSmZaNzNEb1dGa1VjekY1bG9vMmxSWlJBZUN2Q1ZtZVFNMTk2YU9YaWtCVmY1eDRWZXd3M3Q5RTJRZnZMbk5mWklSSFZJcVlXTDhiSFFHWWdpdFVLd1hwbXR2UUN4QlJ1N3BITFpkS3Y4PXzDvPNn6JVDBFB2wYVYPHdkdlZBm6n1_0QB3_GWwE40Tg  ==\")\n\ttestCookieValueWithEqualAndSpaceChars(t, \"sth2\", \"/\", \"123\")\n\ttestCookieValueWithEqualAndSpaceChars(t, \"sth3\", \"/\", \"123 ==   1\")\n}\n\nfunc testCookieValueWithEqualAndSpaceChars(t *testing.T, expectedName, expectedPath, expectedValue string) {\n\tvar c Cookie\n\tc.SetKey(expectedName)\n\tc.SetPath(expectedPath)\n\tc.SetValue(expectedValue)\n\n\ts := c.String()\n\n\tvar c1 Cookie\n\tif err := c1.Parse(s); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tname := c1.Key()\n\tif string(name) != expectedName {\n\t\tt.Fatalf(\"unexpected name %q. Expecting %q\", name, expectedName)\n\t}\n\tpath := c1.Path()\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n\tvalue := c1.Value()\n\tif string(value) != expectedValue {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", value, expectedValue)\n\t}\n}\n\nfunc TestCookieSecureHttpOnly(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; HttpOnly; secure\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !c.Secure() {\n\t\tt.Fatalf(\"secure must be set\")\n\t}\n\tif !c.HTTPOnly() {\n\t\tt.Fatalf(\"HttpOnly must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing secure flag in cookie %q\", s)\n\t}\n\tif !strings.Contains(s, \"; HttpOnly\") {\n\t\tt.Fatalf(\"missing HttpOnly flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieSecure(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; secure\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !c.Secure() {\n\t\tt.Fatalf(\"secure must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing secure flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.Secure() {\n\t\tt.Fatalf(\"Unexpected secure flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"secure\") {\n\t\tt.Fatalf(\"unexpected secure flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieSameSite(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; samesite\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteDefaultMode {\n\t\tt.Fatalf(\"SameSite must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; SameSite\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=lax\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteLaxMode {\n\t\tt.Fatalf(\"SameSite Lax Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=Lax\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=strict\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteStrictMode {\n\t\tt.Fatalf(\"SameSite Strict Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=Strict\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; samesite=none\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteNoneMode {\n\t\tt.Fatalf(\"SameSite None Mode must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=None\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tc.SetSameSite(CookieSameSiteNoneMode)\n\ts = c.String()\n\tif !strings.Contains(s, \"; SameSite=None\") {\n\t\tt.Fatalf(\"missing SameSite flag in cookie %q\", s)\n\t}\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing Secure flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.SameSite() != CookieSameSiteDisabled {\n\t\tt.Fatalf(\"Unexpected SameSite flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"SameSite\") {\n\t\tt.Fatalf(\"unexpected SameSite flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookieMaxAge(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tmaxAge := 100\n\tif err := c.Parse(\"foo=bar; max-age=100\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif maxAge != c.MaxAge() {\n\t\tt.Fatalf(\"max-age must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; max-age=100\") {\n\t\tt.Fatalf(\"missing max-age flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar; expires=Tue, 10 Nov 2009 23:00:00 GMT; max-age=100;\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif maxAge != c.MaxAge() {\n\t\tt.Fatalf(\"max-age ignored\")\n\t}\n\ts = c.String()\n\tif s != \"foo=bar; max-age=100\" {\n\t\tt.Fatalf(\"missing max-age in cookie %q\", s)\n\t}\n\n\texpires := time.Unix(100, 0)\n\tc.SetExpire(expires)\n\ts = c.String()\n\tif s != \"foo=bar; max-age=100\" {\n\t\tt.Fatalf(\"expires should be ignored due to max-age: %q\", s)\n\t}\n\n\tc.SetMaxAge(0)\n\ts = c.String()\n\tif s != \"foo=bar; expires=Thu, 01 Jan 1970 00:01:40 GMT\" {\n\t\tt.Fatalf(\"missing expires %q\", s)\n\t}\n\n\tc.SetMaxAge(-100)\n\tresult := strings.ToLower(c.String())\n\tconst expectedMaxAge0 = \"max-age=0\"\n\tif !strings.Contains(result, expectedMaxAge0) {\n\t\tt.Fatalf(\"Unexpected cookie %q. Should contain %q\", result, expectedMaxAge0)\n\t}\n}\n\nfunc TestCookieHttpOnly(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; HttpOnly\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !c.HTTPOnly() {\n\t\tt.Fatalf(\"HTTPOnly must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; HttpOnly\") {\n\t\tt.Fatalf(\"missing HttpOnly flag in cookie %q\", s)\n\t}\n\n\tif err := c.Parse(\"foo=bar\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif c.HTTPOnly() {\n\t\tt.Fatalf(\"Unexpected HTTPOnly flag set\")\n\t}\n\ts = c.String()\n\tif strings.Contains(s, \"HttpOnly\") {\n\t\tt.Fatalf(\"unexpected HttpOnly flag in cookie %q\", s)\n\t}\n}\n\nfunc TestCookiePartitioned(t *testing.T) {\n\tt.Parallel()\n\n\tvar c Cookie\n\n\tif err := c.Parse(\"foo=bar; PATH=/; secure; Partitioned\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !c.Partitioned() {\n\t\tt.Fatalf(\"Partitioned must be set\")\n\t}\n\ts := c.String()\n\tif !strings.Contains(s, \"; Partitioned\") {\n\t\tt.Fatalf(\"missing Partitioned flag in cookie %q\", s)\n\t}\n\n\tif !c.Secure() {\n\t\tt.Fatalf(\"secure must be set\")\n\t}\n\ts = c.String()\n\tif !strings.Contains(s, \"; secure\") {\n\t\tt.Fatalf(\"missing secure flag in cookie %q\", s)\n\t}\n\n\tif string(c.Path()) != \"/\" {\n\t\tt.Fatalf(\"path must be set /\")\n\t}\n}\n\nfunc TestCookieAcquireReleaseSequential(t *testing.T) {\n\tt.Parallel()\n\n\ttestCookieAcquireRelease(t)\n}\n\nfunc TestCookieAcquireReleaseConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan struct{}, 10)\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestCookieAcquireRelease(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testCookieAcquireRelease(t *testing.T) {\n\tc := AcquireCookie()\n\n\tkey := \"foo\"\n\tc.SetKey(key)\n\n\tvalue := \"bar\"\n\tc.SetValue(value)\n\n\tdomain := \"foo.bar.com\"\n\tc.SetDomain(domain)\n\n\tpath := \"/foi/bar/aaa\"\n\tc.SetPath(path)\n\n\ts := c.String()\n\tc.Reset()\n\tif err := c.Parse(s); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif string(c.Key()) != key {\n\t\tt.Fatalf(\"unexpected cookie name %q. Expecting %q\", c.Key(), key)\n\t}\n\tif string(c.Value()) != value {\n\t\tt.Fatalf(\"unexpected cookie value %q. Expecting %q\", c.Value(), value)\n\t}\n\tif string(c.Domain()) != domain {\n\t\tt.Fatalf(\"unexpected domain %q. Expecting %q\", c.Domain(), domain)\n\t}\n\tif string(c.Path()) != path {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", c.Path(), path)\n\t}\n\n\tReleaseCookie(c)\n}\n\nfunc TestCookieParse(t *testing.T) {\n\tt.Parallel()\n\n\ttestCookieParse(t, \"foo\", \"foo\")\n\ttestCookieParse(t, \"foo=bar\", \"foo=bar\")\n\ttestCookieParse(t, \"foo=\", \"foo=\")\n\ttestCookieParse(t, `foo=\"bar\"`, \"foo=bar\")\n\ttestCookieParse(t, `\"foo\"=bar`, `\"foo\"=bar`)\n\ttestCookieParse(t, \"foo=bar; Domain=aaa.com; PATH=/foo/bar\", \"foo=bar; domain=aaa.com; path=/foo/bar\")\n\ttestCookieParse(t, \"foo=bar; max-age= 101 ; expires= Tue, 10 Nov 2009 23:00:00 GMT\", \"foo=bar; max-age=101\")\n\ttestCookieParse(t, \" xxx = yyy  ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;\",\n\t\t\"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b\")\n}\n\nfunc testCookieParse(t *testing.T, s, expectedS string) {\n\tvar c Cookie\n\tif err := c.Parse(s); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tresult := string(c.Cookie())\n\tif result != expectedS {\n\t\tt.Fatalf(\"unexpected cookies %q. Expecting %q. Original %q\", result, expectedS, s)\n\t}\n}\n\nfunc TestCookieAppendBytes(t *testing.T) {\n\tt.Parallel()\n\n\tc := &Cookie{}\n\n\ttestCookieAppendBytes(t, c, \"\", \"bar\", \"bar\")\n\ttestCookieAppendBytes(t, c, \"foo\", \"\", \"foo=\")\n\ttestCookieAppendBytes(t, c, \"ффф\", \"12 лодлы\", \"ффф=12 лодлы\")\n\n\tc.SetDomain(\"foobar.com\")\n\ttestCookieAppendBytes(t, c, \"a\", \"b\", \"a=b; domain=foobar.com\")\n\n\tc.SetPath(\"/a/b\")\n\ttestCookieAppendBytes(t, c, \"aa\", \"bb\", \"aa=bb; domain=foobar.com; path=/a/b\")\n\n\tc.SetExpire(CookieExpireDelete)\n\ttestCookieAppendBytes(t, c, \"xxx\", \"yyy\", \"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b\")\n}\n\nfunc testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string) {\n\tc.SetKey(key)\n\tc.SetValue(value)\n\tresult := string(c.AppendBytes(nil))\n\tif result != expectedS {\n\t\tt.Fatalf(\"Unexpected cookie %q. Expecting %q\", result, expectedS)\n\t}\n}\n\nfunc TestParseRequestCookies(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseRequestCookies(t, \"\", \"\")\n\ttestParseRequestCookies(t, \"=\", \"\")\n\ttestParseRequestCookies(t, \"foo\", \"foo\")\n\ttestParseRequestCookies(t, \"=foo\", \"foo\")\n\ttestParseRequestCookies(t, \"bar=\", \"bar=\")\n\ttestParseRequestCookies(t, \"xxx=aa;bb=c; =d; ;;e=g\", \"xxx=aa; bb=c; d; e=g\")\n\ttestParseRequestCookies(t, \"a;b;c; d=1;d=2\", \"a; b; c; d=1; d=2\")\n\ttestParseRequestCookies(t, \"   %D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc   ;s%20s=aaa  \", \"%D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc; s%20s=aaa\")\n}\n\nfunc testParseRequestCookies(t *testing.T, s, expectedS string) {\n\tcookies := parseRequestCookies(nil, []byte(s))\n\tss := string(appendRequestCookieBytes(nil, cookies))\n\tif ss != expectedS {\n\t\tt.Fatalf(\"Unexpected cookies after parsing: %q. Expecting %q. String to parse %q\", ss, expectedS, s)\n\t}\n}\n\nfunc TestAppendRequestCookieBytes(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendRequestCookieBytes(t, \"=\", \"\")\n\ttestAppendRequestCookieBytes(t, \"foo=\", \"foo=\")\n\ttestAppendRequestCookieBytes(t, \"=bar\", \"bar\")\n\ttestAppendRequestCookieBytes(t, \"привет=a bc&s s=aaa\", \"привет=a bc; s s=aaa\")\n}\n\nfunc testAppendRequestCookieBytes(t *testing.T, s, expectedS string) {\n\tkvs := strings.Split(s, \"&\")\n\tcookies := make([]argsKV, 0, len(kvs))\n\tfor _, ss := range kvs {\n\t\ttmp := strings.SplitN(ss, \"=\", 2)\n\t\tif len(tmp) != 2 {\n\t\t\tt.Fatalf(\"Cannot find '=' in %q, part of %q\", ss, s)\n\t\t}\n\t\tcookies = append(cookies, argsKV{\n\t\t\tkey:   []byte(tmp[0]),\n\t\t\tvalue: []byte(tmp[1]),\n\t\t})\n\t}\n\n\tprefix := \"foobar\"\n\tresult := string(appendRequestCookieBytes([]byte(prefix), cookies))\n\tif result[:len(prefix)] != prefix {\n\t\tt.Fatalf(\"unexpected prefix %q. Expecting %q for cookie %q\", result[:len(prefix)], prefix, s)\n\t}\n\tresult = result[len(prefix):]\n\tif result != expectedS {\n\t\tt.Fatalf(\"Unexpected result %q. Expecting %q for cookie %q\", result, expectedS, s)\n\t}\n}\n"
  },
  {
    "path": "cookie_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"testing\"\n)\n\nfunc BenchmarkCookieParseMin(b *testing.B) {\n\tvar c Cookie\n\ts := []byte(\"xxx=yyy\")\n\tfor i := 0; i < b.N; i++ {\n\t\tif err := c.ParseBytes(s); err != nil {\n\t\t\tb.Fatalf(\"unexpected error when parsing cookies: %v\", err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCookieParseNoExpires(b *testing.B) {\n\tvar c Cookie\n\ts := []byte(\"xxx=yyy; domain=foobar.com; path=/a/b\")\n\tfor i := 0; i < b.N; i++ {\n\t\tif err := c.ParseBytes(s); err != nil {\n\t\t\tb.Fatalf(\"unexpected error when parsing cookies: %v\", err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCookieParseFull(b *testing.B) {\n\tvar c Cookie\n\ts := []byte(\"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b\")\n\tfor i := 0; i < b.N; i++ {\n\t\tif err := c.ParseBytes(s); err != nil {\n\t\t\tb.Fatalf(\"unexpected error when parsing cookies: %v\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "/*\nPackage fasthttp provides fast HTTP server and client API.\n\nFasthttp provides the following features:\n\n 1. Optimized for speed. Easily handles more than 100K qps and more than 1M\n    concurrent keep-alive connections on modern hardware.\n\n 2. Optimized for low memory usage.\n\n 3. Easy 'Connection: Upgrade' support via RequestCtx.Hijack.\n\n 4. Server provides the following anti-DoS limits:\n\n    - The number of concurrent connections.\n\n    - The number of concurrent connections per client IP.\n\n    - The number of requests per connection.\n\n    - Request read timeout.\n\n    - Response write timeout.\n\n    - Maximum request header size.\n\n    - Maximum request body size.\n\n    - Maximum request execution time.\n\n    - Maximum keep-alive connection lifetime.\n\n    - Early filtering out non-GET requests.\n\n 5. A lot of additional useful info is exposed to request handler:\n\n    - Server and client address.\n\n    - Per-request logger.\n\n    - Unique request id.\n\n    - Request start time.\n\n    - Connection start time.\n\n    - Request sequence number for the current connection.\n\n 6. Client supports automatic retry on idempotent requests' failure.\n\n 7. Fasthttp API is designed with the ability to extend existing client\n    and server implementations or to write custom client and server\n    implementations from scratch.\n*/\npackage fasthttp\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Code examples\n\n* [HelloWorld server](helloworldserver)\n* [Static file server](fileserver)\n"
  },
  {
    "path": "examples/host_client/.gitignore",
    "content": "hostclient\n"
  },
  {
    "path": "examples/host_client/Makefile",
    "content": "host_client: clean\n\tgo get -u github.com/valyala/fasthttp\n\tgo build\n\nclean:\n\trm -f host_client\n"
  },
  {
    "path": "examples/host_client/README.md",
    "content": "# Host Client Example\n\nThe HostClient is useful when calling an API from a single host.\nThe example also shows how to use URI.\nYou may create the parsed URI once and reuse it in many requests.\nThe URI has a username and password for Basic Auth but you may also set other parts i.e. `SetPath()`, `SetQueryString()`.\n\n# How to build and run\nStart a web server on localhost:8080 then execute:\n\n    make\n    ./host_client\n\n"
  },
  {
    "path": "examples/host_client/hostclient.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc main() {\n\t// Get URI from a pool\n\turl := fasthttp.AcquireURI()\n\turl.Parse(nil, []byte(\"http://localhost:8080/\")) //nolint:errcheck\n\turl.SetUsername(\"Aladdin\")\n\turl.SetPassword(\"Open Sesame\")\n\n\thc := &fasthttp.HostClient{\n\t\tAddr: \"localhost:8080\", // The host address and port must be set explicitly\n\t}\n\n\treq := fasthttp.AcquireRequest()\n\treq.SetURI(url)          // copy url into request\n\tfasthttp.ReleaseURI(url) // now you may release the URI\n\n\treq.Header.SetMethod(fasthttp.MethodGet)\n\tresp := fasthttp.AcquireResponse()\n\terr := hc.Do(req, resp)\n\tfasthttp.ReleaseRequest(req)\n\tif err == nil {\n\t\tfmt.Printf(\"Response: %s\\n\", resp.Body())\n\t} else {\n\t\tfmt.Fprintf(os.Stderr, \"Connection error: %v\\n\", err)\n\t}\n\tfasthttp.ReleaseResponse(resp)\n}\n"
  },
  {
    "path": "examples/letsencrypt/letsencryptserver.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"golang.org/x/crypto/acme\"\n\t\"golang.org/x/crypto/acme/autocert\"\n)\n\nfunc requestHandler(ctx *fasthttp.RequestCtx) {\n\tctx.SetBodyString(\"hello from https!\")\n}\n\nfunc main() {\n\tm := &autocert.Manager{\n\t\tPrompt:     autocert.AcceptTOS,\n\t\tHostPolicy: autocert.HostWhitelist(\"example.com\"), // Replace with your domain.\n\t\tCache:      autocert.DirCache(\"./certs\"),\n\t}\n\n\tcfg := &tls.Config{\n\t\tGetCertificate: m.GetCertificate,\n\t\tNextProtos: []string{\n\t\t\t\"http/1.1\", acme.ALPNProto,\n\t\t},\n\t}\n\n\t// Let's Encrypt tls-alpn-01 only works on port 443.\n\tln, err := net.Listen(\"tcp4\", \"0.0.0.0:443\") // #nosec G102\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlnTLS := tls.NewListener(ln, cfg)\n\n\tif err := fasthttp.Serve(lnTLS, requestHandler); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "examples/multidomain/Makefile",
    "content": "writer: clean\n\tgo get -u github.com/valyala/fasthttp\n\tgo build\n\nclean:\n\trm -f multidomain\n"
  },
  {
    "path": "examples/multidomain/README.md",
    "content": "# Multidomain using SSL certs example\n\n* Prints two messages depending on visited host.\n\n# How to build\n\n```\nmake\n```\n\n# How to run\n\n```\n./multidomain\n```\n"
  },
  {
    "path": "examples/multidomain/multidomain.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nvar domains = make(map[string]fasthttp.RequestHandler)\n\nfunc main() {\n\tserver := &fasthttp.Server{\n\t\t// You can check the access using openssl command:\n\t\t// $ openssl s_client -connect localhost:8080 << EOF\n\t\t// > GET /\n\t\t// > Host: localhost\n\t\t// > EOF\n\t\t//\n\t\t// $ openssl s_client -connect localhost:8080 << EOF\n\t\t// > GET /\n\t\t// > Host: 127.0.0.1:8080\n\t\t// > EOF\n\t\t//\n\t\tHandler: func(ctx *fasthttp.RequestCtx) {\n\t\t\th, ok := domains[string(ctx.Host())]\n\t\t\tif !ok {\n\t\t\t\tctx.NotFound()\n\t\t\t\treturn\n\t\t\t}\n\t\t\th(ctx)\n\t\t},\n\t}\n\n\t// preparing first host\n\tcert, priv, err := fasthttp.GenerateTestCertificate(\"localhost:8080\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdomains[\"localhost:8080\"] = func(ctx *fasthttp.RequestCtx) {\n\t\tctx.WriteString(\"You are accessing to localhost:8080\\n\") //nolint:errcheck\n\t}\n\n\terr = server.AppendCertEmbed(cert, priv)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// preparing second host\n\tcert, priv, err = fasthttp.GenerateTestCertificate(\"127.0.0.1\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdomains[\"127.0.0.1:8080\"] = func(ctx *fasthttp.RequestCtx) {\n\t\tctx.WriteString(\"You are accessing to 127.0.0.1:8080\\n\") //nolint:errcheck\n\t}\n\n\terr = server.AppendCertEmbed(cert, priv)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(server.ListenAndServeTLS(\":8080\", \"\", \"\"))\n}\n"
  },
  {
    "path": "expvarhandler/expvar.go",
    "content": "// Package expvarhandler provides fasthttp-compatible request handler\n// serving expvars.\npackage expvarhandler\n\nimport (\n\t\"expvar\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nvar (\n\texpvarHandlerCalls = expvar.NewInt(\"expvarHandlerCalls\")\n\texpvarRegexpErrors = expvar.NewInt(\"expvarRegexpErrors\")\n\n\tdefaultRE = regexp.MustCompile(\".\")\n)\n\n// ExpvarHandler dumps json representation of expvars to http response.\n//\n// Expvars may be filtered by regexp provided via 'r' query argument.\n//\n// See https://pkg.go.dev/expvar for details.\nfunc ExpvarHandler(ctx *fasthttp.RequestCtx) {\n\texpvarHandlerCalls.Add(1)\n\n\tctx.Response.Reset()\n\n\tr, err := getExpvarRegexp(ctx)\n\tif err != nil {\n\t\texpvarRegexpErrors.Add(1)\n\t\tfmt.Fprintf(ctx, \"Error when obtaining expvar regexp: %v\", err)\n\t\tctx.SetStatusCode(fasthttp.StatusBadRequest)\n\t\treturn\n\t}\n\n\tfmt.Fprintf(ctx, \"{\\n\")\n\tfirst := true\n\texpvar.Do(func(kv expvar.KeyValue) {\n\t\tif r.MatchString(kv.Key) {\n\t\t\tif !first {\n\t\t\t\tfmt.Fprintf(ctx, \",\\n\")\n\t\t\t}\n\t\t\tfirst = false\n\t\t\tfmt.Fprintf(ctx, \"\\t%q: %s\", kv.Key, kv.Value)\n\t\t}\n\t})\n\tfmt.Fprintf(ctx, \"\\n}\\n\")\n\n\tctx.SetContentType(\"application/json; charset=utf-8\")\n}\n\nfunc getExpvarRegexp(ctx *fasthttp.RequestCtx) (*regexp.Regexp, error) {\n\tr := string(ctx.QueryArgs().Peek(\"r\"))\n\tif r == \"\" {\n\t\treturn defaultRE, nil\n\t}\n\trr, err := regexp.Compile(r)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot parse r=%q: %w\", r, err)\n\t}\n\treturn rr, nil\n}\n"
  },
  {
    "path": "expvarhandler/expvar_test.go",
    "content": "package expvarhandler\n\nimport (\n\t\"encoding/json\"\n\t\"expvar\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nvar once sync.Once\n\nfunc TestExpvarHandlerBasic(t *testing.T) {\n\tt.Parallel()\n\n\t// Publish panics if the same var is published more than once,\n\t// which can happen if the test is run with -count\n\tonce.Do(func() {\n\t\texpvar.Publish(\"customVar\", expvar.Func(func() any {\n\t\t\treturn \"foobar\"\n\t\t}))\n\t})\n\n\tvar ctx fasthttp.RequestCtx\n\n\texpvarHandlerCalls.Set(0)\n\n\tExpvarHandler(&ctx)\n\n\tbody := ctx.Response.Body()\n\n\tvar m map[string]any\n\tif err := json.Unmarshal(body, &m); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif _, ok := m[\"cmdline\"]; !ok {\n\t\tt.Fatalf(\"cannot locate cmdline expvar\")\n\t}\n\tif _, ok := m[\"memstats\"]; !ok {\n\t\tt.Fatalf(\"cannot locate memstats expvar\")\n\t}\n\n\tv := m[\"customVar\"]\n\tsv, ok := v.(string)\n\tif !ok {\n\t\tt.Fatalf(\"unexpected custom var type %T. Expecting string\", v)\n\t}\n\tif sv != \"foobar\" {\n\t\tt.Fatalf(\"unexpected custom var value: %q. Expecting %q\", v, \"foobar\")\n\t}\n\n\tv = m[\"expvarHandlerCalls\"]\n\tfv, ok := v.(float64)\n\tif !ok {\n\t\tt.Fatalf(\"unexpected expvarHandlerCalls type %T. Expecting float64\", v)\n\t}\n\tif int(fv) != 1 {\n\t\tt.Fatalf(\"unexpected value for expvarHandlerCalls: %v. Expecting %v\", fv, 1)\n\t}\n}\n\nfunc TestExpvarHandlerRegexp(t *testing.T) {\n\tvar ctx fasthttp.RequestCtx\n\tctx.QueryArgs().Set(\"r\", \"cmd\")\n\tExpvarHandler(&ctx)\n\tbody := string(ctx.Response.Body())\n\tif !strings.Contains(body, `\"cmdline\"`) {\n\t\tt.Fatalf(\"missing 'cmdline' expvar\")\n\t}\n\tif strings.Contains(body, `\"memstats\"`) {\n\t\tt.Fatalf(\"unexpected memstats expvar found\")\n\t}\n}\n"
  },
  {
    "path": "fasthttpadaptor/adaptor.go",
    "content": "// Package fasthttpadaptor provides helper functions for converting net/http\n// request handlers to fasthttp request handlers.\npackage fasthttpadaptor\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\n// NewFastHTTPHandlerFunc wraps net/http handler func to fasthttp\n// request handler, so it can be passed to fasthttp server.\n//\n// While this function may be used for easy switching from net/http to fasthttp,\n// it has the following drawbacks comparing to using manually written fasthttp\n// request handler:\n//\n//   - A lot of useful functionality provided by fasthttp is missing\n//     from net/http handler.\n//   - net/http -> fasthttp handler conversion has some overhead,\n//     so the returned handler will be always slower than manually written\n//     fasthttp handler.\n//\n// So it is advisable using this function only for quick net/http -> fasthttp\n// switching. Then manually convert net/http handlers to fasthttp handlers\n// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .\nfunc NewFastHTTPHandlerFunc(h http.HandlerFunc) fasthttp.RequestHandler {\n\treturn NewFastHTTPHandler(h)\n}\n\n// NewFastHTTPHandler wraps net/http handler to fasthttp request handler,\n// so it can be passed to fasthttp server.\n//\n// While this function may be used for easy switching from net/http to fasthttp,\n// it has the following drawbacks comparing to using manually written fasthttp\n// request handler:\n//\n//   - A lot of useful functionality provided by fasthttp is missing\n//     from net/http handler.\n//   - net/http -> fasthttp handler conversion has some overhead,\n//     so the returned handler will be always slower than manually written\n//     fasthttp handler.\n//\n// So it is advisable using this function only for quick net/http -> fasthttp\n// switching. Then manually convert net/http handlers to fasthttp handlers\n// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .\nfunc NewFastHTTPHandler(h http.Handler) fasthttp.RequestHandler {\n\treturn func(ctx *fasthttp.RequestCtx) {\n\t\tvar r http.Request\n\t\tif err := ConvertRequest(ctx, &r, true); err != nil {\n\t\t\tctx.Logger().Printf(\"cannot parse requestURI %q: %v\", r.RequestURI, err)\n\t\t\tctx.Error(\"Internal Server Error\", fasthttp.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tw := acquireWriter(ctx)\n\t\t// Serve the net/http handler concurrently so we can react to Flush/Hijack.\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif rec := recover(); rec != nil {\n\t\t\t\t\tctx.Logger().Printf(\"panic in net/http handler: %v\", rec)\n\n\t\t\t\t\tselect {\n\t\t\t\t\tcase w.modeCh <- modePanicked:\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Signal completion if no other mode was selected yet.\n\t\t\t\t\tselect {\n\t\t\t\t\tcase w.modeCh <- modeDone:\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t_ = w.Close()\n\t\t\t}()\n\n\t\t\th.ServeHTTP(w, r.WithContext(ctx))\n\t\t}()\n\n\t\t// Decide mode by first event.\n\t\tswitch <-w.modeCh {\n\t\tcase modeDone:\n\t\t\t// Buffered, no Flush() nor Hijack().\n\t\t\tctx.SetStatusCode(w.status())\n\t\t\thaveContentType := false\n\t\t\tfor k, vv := range w.Header() {\n\t\t\t\tif k == fasthttp.HeaderContentType {\n\t\t\t\t\thaveContentType = true\n\t\t\t\t}\n\n\t\t\t\tfor _, v := range vv {\n\t\t\t\t\tctx.Response.Header.Add(k, v)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !haveContentType {\n\t\t\t\t// From net/http.ResponseWriter.Write:\n\t\t\t\t// If the Header does not contain a Content-Type line, Write adds a Content-Type set\n\t\t\t\t// to the result of passing the initial 512 bytes of written data to DetectContentType.\n\t\t\t\tl := min(len(w.responseBody), 512)\n\t\t\t\tif l > 0 {\n\t\t\t\t\tctx.Response.Header.Set(fasthttp.HeaderContentType, http.DetectContentType(w.responseBody[:l]))\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(w.responseBody) > 0 {\n\t\t\t\tctx.Response.SetBody(w.responseBody)\n\t\t\t}\n\t\t\treleaseWriter(w)\n\n\t\tcase modeFlushed:\n\t\t\t// Streaming: send headers and start SetBodyStreamWriter.\n\t\t\tctx.SetStatusCode(w.status())\n\n\t\t\thaveContentType := false\n\t\t\tfor k, vv := range w.Header() {\n\t\t\t\t// No Content-Length when streaming.\n\t\t\t\tif k == fasthttp.HeaderContentLength {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif k == fasthttp.HeaderContentType {\n\t\t\t\t\thaveContentType = true\n\t\t\t\t}\n\t\t\t\tfor _, v := range vv {\n\t\t\t\t\tctx.Response.Header.Add(k, v)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !haveContentType {\n\t\t\t\tw.mu.Lock()\n\t\t\t\tif len(w.responseBody) > 0 {\n\t\t\t\t\tl := min(len(w.responseBody), 512)\n\t\t\t\t\tctx.Response.Header.Set(fasthttp.HeaderContentType, http.DetectContentType(w.responseBody[:l]))\n\t\t\t\t}\n\t\t\t\tw.mu.Unlock()\n\t\t\t}\n\n\t\t\tctx.SetBodyStreamWriter(func(bw *bufio.Writer) {\n\t\t\t\t// Ensure cleanup only after the stream completes.\n\t\t\t\tdefer releaseWriter(w)\n\n\t\t\t\t// Send pre-flush bytes.\n\t\t\t\tif b := w.consumePreflush(); len(b) > 0 {\n\t\t\t\t\t_, _ = bw.Write(b)\n\t\t\t\t\t_ = bw.Flush()\n\t\t\t\t}\n\n\t\t\t\t// Stream subsequent writes from the pipe until EOF.\n\t\t\t\tbuf := bufferPool.Get().(*[]byte)\n\t\t\t\tdefer bufferPool.Put(buf)\n\n\t\t\t\tfor {\n\t\t\t\t\tn, err := w.pr.Read(*buf)\n\t\t\t\t\tif n > 0 {\n\t\t\t\t\t\tif _, e := bw.Write((*buf)[:n]); e != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif e := bw.Flush(); e != nil {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Signal the writer that streaming is ready so Flush() can return.\n\t\t\tclose(w.streamReady)\n\n\t\tcase modeHijacked:\n\t\t\treturn\n\n\t\tcase modePanicked:\n\t\t\tpanic(\"net/http handler panicked\")\n\t\t}\n\t}\n}\n\nvar bufferPool = sync.Pool{\n\tNew: func() any {\n\t\tb := make([]byte, 32*1024)\n\t\treturn &b\n\t},\n}\n\nconst (\n\tmodeDone = iota + 1\n\tmodeFlushed\n\tmodeHijacked\n\tmodePanicked\n)\n\n// Writer implements http.ResponseWriter + http.Flusher + http.Hijacker for the adaptor.\ntype writer struct {\n\tctx        *fasthttp.RequestCtx\n\th          http.Header\n\tstatusCode atomic.Int64\n\n\tmu           sync.Mutex\n\tresponseBody []byte\n\tbufPool      *[]byte\n\n\tpr *io.PipeReader\n\tpw *io.PipeWriter\n\n\thijacked atomic.Bool\n\n\tmodeCh chan int\n\n\tstreamReady chan struct{}\n\n\tflushOnce sync.Once\n\tcloseOnce sync.Once\n}\n\nfunc acquireWriter(ctx *fasthttp.RequestCtx) *writer {\n\tpr, pw := io.Pipe()\n\treturn &writer{\n\t\tctx:          ctx,\n\t\th:            make(http.Header),\n\t\tresponseBody: nil,\n\t\tpr:           pr,\n\t\tpw:           pw,\n\t\tmodeCh:       make(chan int, 1),\n\t\tstreamReady:  make(chan struct{}),\n\t}\n}\n\nfunc releaseWriter(w *writer) {\n\t_ = w.Close()\n\tif w.bufPool != nil {\n\t\tbufferPool.Put(w.bufPool)\n\t\tw.bufPool = nil\n\t}\n}\n\nfunc (w *writer) Header() http.Header {\n\treturn w.h\n}\n\nfunc (w *writer) WriteHeader(code int) {\n\t// Allow the same codes as net/http.\n\tif code < 100 || code > 999 {\n\t\tpanic(fmt.Sprintf(\"invalid WriteHeader code %v\", code))\n\t}\n\tw.statusCode.CompareAndSwap(0, int64(code))\n}\n\nfunc (w *writer) Write(p []byte) (int, error) {\n\tselect {\n\tcase <-w.streamReady:\n\t\treturn w.pw.Write(p)\n\tdefault:\n\t}\n\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tif w.responseBody == nil {\n\t\tw.bufPool = bufferPool.Get().(*[]byte)\n\t\tw.responseBody = (*w.bufPool)[:0]\n\t}\n\tw.responseBody = append(w.responseBody, p...)\n\treturn len(p), nil\n}\n\nfunc (w *writer) Flush() {\n\tw.flushOnce.Do(func() {\n\t\tselect {\n\t\tcase w.modeCh <- modeFlushed:\n\t\tdefault:\n\t\t}\n\t})\n\t<-w.streamReady\n}\n\ntype wrappedConn struct {\n\tnet.Conn\n\n\twg   sync.WaitGroup\n\tonce sync.Once\n}\n\nfunc (c *wrappedConn) Close() (err error) {\n\tc.once.Do(func() {\n\t\terr = c.Conn.Close()\n\t\tc.wg.Done()\n\t})\n\treturn err\n}\n\nfunc (w *writer) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tif !w.hijacked.CompareAndSwap(false, true) {\n\t\treturn nil, nil, http.ErrHijacked\n\t}\n\n\t// Tell fasthttp not to send any HTTP response before hijacking.\n\tw.ctx.HijackSetNoResponse(true)\n\n\tconn := &wrappedConn{Conn: w.ctx.Conn()}\n\tconn.wg.Add(1)\n\tw.ctx.Hijack(func(net.Conn) {\n\t\tconn.wg.Wait()\n\t})\n\n\tbufW := bufio.NewWriter(conn)\n\n\t// Write any unflushed body to the hijacked connection buffer.\n\tunflushedBody := w.consumePreflush()\n\tif len(unflushedBody) > 0 {\n\t\tif _, err := bufW.Write(unflushedBody); err != nil {\n\t\t\t_ = conn.Close()\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\tselect {\n\tcase w.modeCh <- modeHijacked:\n\tdefault:\n\t}\n\n\treturn conn, &bufio.ReadWriter{Reader: bufio.NewReader(conn), Writer: bufW}, nil\n}\n\nfunc (w *writer) Close() error {\n\tw.closeOnce.Do(func() {\n\t\t_ = w.pw.Close()\n\t\t_ = w.pr.Close()\n\t})\n\treturn nil\n}\n\n// status returns the effective status code (defaults to 200).\nfunc (w *writer) status() int {\n\tcode := int(w.statusCode.Load())\n\tif code == 0 {\n\t\treturn http.StatusOK\n\t}\n\treturn code\n}\n\n// consumePreflush returns pre-flush bytes and clears the buffer.\nfunc (w *writer) consumePreflush() []byte {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif len(w.responseBody) == 0 {\n\t\treturn nil\n\t}\n\tout := w.responseBody\n\tw.responseBody = nil\n\treturn out\n}\n"
  },
  {
    "path": "fasthttpadaptor/adaptor_test.go",
    "content": "package fasthttpadaptor\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nfunc TestNewFastHTTPHandler(t *testing.T) {\n\tt.Parallel()\n\n\texpectedMethod := fasthttp.MethodPost\n\texpectedProto := \"HTTP/1.1\"\n\texpectedProtoMajor := 1\n\texpectedProtoMinor := 1\n\texpectedRequestURI := \"/foo/bar?baz=123\"\n\texpectedBody := \"<!doctype html><html>\"\n\texpectedContentLength := len(expectedBody)\n\texpectedHost := \"foobar.com\"\n\texpectedRemoteAddr := \"1.2.3.4:6789\"\n\texpectedHeader := map[string]string{\n\t\t\"Foo-Bar\":         \"baz\",\n\t\t\"Abc\":             \"defg\",\n\t\t\"XXX-Remote-Addr\": \"123.43.4543.345\",\n\t}\n\texpectedURL, err := url.ParseRequestURI(expectedRequestURI)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedContextKey := \"contextKey\"\n\texpectedContextValue := \"contextValue\"\n\texpectedContentType := \"text/html; charset=utf-8\"\n\n\tcallsCount := 0\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tcallsCount++\n\t\tif r.Method != expectedMethod {\n\t\t\tt.Fatalf(\"unexpected method %q. Expecting %q\", r.Method, expectedMethod)\n\t\t}\n\t\tif r.Proto != expectedProto {\n\t\t\tt.Fatalf(\"unexpected proto %q. Expecting %q\", r.Proto, expectedProto)\n\t\t}\n\t\tif r.ProtoMajor != expectedProtoMajor {\n\t\t\tt.Fatalf(\"unexpected protoMajor %d. Expecting %d\", r.ProtoMajor, expectedProtoMajor)\n\t\t}\n\t\tif r.ProtoMinor != expectedProtoMinor {\n\t\t\tt.Fatalf(\"unexpected protoMinor %d. Expecting %d\", r.ProtoMinor, expectedProtoMinor)\n\t\t}\n\t\tif r.RequestURI != expectedRequestURI {\n\t\t\tt.Fatalf(\"unexpected requestURI %q. Expecting %q\", r.RequestURI, expectedRequestURI)\n\t\t}\n\t\tif r.ContentLength != int64(expectedContentLength) {\n\t\t\tt.Fatalf(\"unexpected contentLength %d. Expecting %d\", r.ContentLength, expectedContentLength)\n\t\t}\n\t\tif len(r.TransferEncoding) != 0 {\n\t\t\tt.Fatalf(\"unexpected transferEncoding %q. Expecting []\", r.TransferEncoding)\n\t\t}\n\t\tif r.Host != expectedHost {\n\t\t\tt.Fatalf(\"unexpected host %q. Expecting %q\", r.Host, expectedHost)\n\t\t}\n\t\tif r.RemoteAddr != expectedRemoteAddr {\n\t\t\tt.Fatalf(\"unexpected remoteAddr %q. Expecting %q\", r.RemoteAddr, expectedRemoteAddr)\n\t\t}\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when reading request body: %v\", err)\n\t\t}\n\t\tif string(body) != expectedBody {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t\t}\n\t\tif !reflect.DeepEqual(r.URL, expectedURL) {\n\t\t\tt.Fatalf(\"unexpected URL: %#v. Expecting %#v\", r.URL, expectedURL)\n\t\t}\n\t\tif r.Context().Value(expectedContextKey) != expectedContextValue {\n\t\t\tt.Fatalf(\"unexpected context value for key %q. Expecting %q\", expectedContextKey, expectedContextValue)\n\t\t}\n\n\t\tfor k, expectedV := range expectedHeader {\n\t\t\tv := r.Header.Get(k)\n\t\t\tif v != expectedV {\n\t\t\t\tt.Fatalf(\"unexpected header value %q for key %q. Expecting %q\", v, k, expectedV)\n\t\t\t}\n\t\t}\n\n\t\tw.Header().Set(\"Header1\", \"value1\")\n\t\tw.Header().Set(\"Header2\", \"value2\")\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\tif _, err := w.Write(body); err != nil {\n\t\t\tt.Fatalf(\"unexpected error when writing response body: %v\", err)\n\t\t}\n\t}\n\tfasthttpH := NewFastHTTPHandler(http.HandlerFunc(nethttpH))\n\tfasthttpH = setContextValueMiddleware(fasthttpH, expectedContextKey, expectedContextValue)\n\n\tvar ctx fasthttp.RequestCtx\n\tvar req fasthttp.Request\n\n\treq.Header.SetMethod(expectedMethod)\n\treq.SetRequestURI(expectedRequestURI)\n\treq.Header.SetHost(expectedHost)\n\tif _, err := req.BodyWriter().Write([]byte(expectedBody)); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing request body: %v\", err)\n\t}\n\tfor k, v := range expectedHeader {\n\t\treq.Header.Set(k, v)\n\t}\n\n\tremoteAddr, err := net.ResolveTCPAddr(\"tcp\", expectedRemoteAddr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tctx.Init(&req, remoteAddr, nil)\n\n\tfasthttpH(&ctx)\n\n\tif callsCount != 1 {\n\t\tt.Fatalf(\"unexpected callsCount: %d. Expecting 1\", callsCount)\n\t}\n\n\tresp := &ctx.Response\n\tif resp.StatusCode() != fasthttp.StatusBadRequest {\n\t\tt.Fatalf(\"unexpected statusCode: %d. Expecting %d\", resp.StatusCode(), fasthttp.StatusBadRequest)\n\t}\n\tif string(resp.Header.Peek(\"Header1\")) != \"value1\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", resp.Header.Peek(\"Header1\"), \"value1\")\n\t}\n\tif string(resp.Header.Peek(\"Header2\")) != \"value2\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", resp.Header.Peek(\"Header2\"), \"value2\")\n\t}\n\tif string(resp.Body()) != expectedBody {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting %q\", resp.Body(), expectedBody)\n\t}\n\tif string(resp.Header.Peek(\"Content-Type\")) != expectedContentType {\n\t\tt.Fatalf(\"unexpected response content-type %q. Expecting %q\", string(resp.Header.Peek(\"Content-Type\")), expectedContentType)\n\t}\n}\n\nfunc TestNewFastHTTPHandlerWithCookies(t *testing.T) {\n\texpectedMethod := fasthttp.MethodPost\n\texpectedRequestURI := \"/foo/bar?baz=123\"\n\texpectedHost := \"foobar.com\"\n\texpectedRemoteAddr := \"1.2.3.4:6789\"\n\n\tvar ctx fasthttp.RequestCtx\n\tvar req fasthttp.Request\n\n\treq.Header.SetMethod(expectedMethod)\n\treq.SetRequestURI(expectedRequestURI)\n\treq.Header.SetHost(expectedHost)\n\treq.Header.SetCookie(\"cookieOne\", \"valueCookieOne\")\n\treq.Header.SetCookie(\"cookieTwo\", \"valueCookieTwo\")\n\n\tremoteAddr, err := net.ResolveTCPAddr(\"tcp\", expectedRemoteAddr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tctx.Init(&req, remoteAddr, nil)\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\t// real handler warped by middleware, in this example do nothing\n\t}\n\tfasthttpH := NewFastHTTPHandler(http.HandlerFunc(nethttpH))\n\n\tnetMiddleware := func(_ http.ResponseWriter, r *http.Request) {\n\t\t// assume middleware do some change on r, such as reset header's host\n\t\tr.Header.Set(\"Host\", \"example.com\")\n\t\t// convert ctx again in case request may modify by middleware\n\t\tctx.Request.Header.Set(\"Host\", r.Header.Get(\"Host\"))\n\t\t// since cookies of r are not changed, expect \"cookieOne=valueCookieOne\"\n\t\tcookie, _ := r.Cookie(\"cookieOne\")\n\t\tif err != nil {\n\t\t\t// will error, but if line 172 is commented, then no error will happen\n\t\t\tt.Errorf(\"should not error\")\n\t\t}\n\t\tif cookie.Value != \"valueCookieOne\" {\n\t\t\tt.Errorf(\"cookie error, expect %s, find %s\", \"valueCookieOne\", cookie.Value)\n\t\t}\n\t\t// instead of using responseWriter and r, use ctx again, like what have done in fiber\n\t\tfasthttpH(&ctx)\n\t}\n\tfastMiddleware := NewFastHTTPHandler(http.HandlerFunc(netMiddleware))\n\tfastMiddleware(&ctx)\n}\n\nfunc setContextValueMiddleware(next fasthttp.RequestHandler, key string, value any) fasthttp.RequestHandler {\n\treturn func(ctx *fasthttp.RequestCtx) {\n\t\tctx.SetUserValue(key, value)\n\t\tnext(ctx)\n\t}\n}\n\nfunc TestHijack(t *testing.T) {\n\tt.Parallel()\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tif f, ok := w.(http.Hijacker); !ok {\n\t\t\tt.Errorf(\"expected http.ResponseWriter to implement http.Hijacker\")\n\t\t} else {\n\t\t\tif _, err := w.Write([]byte(\"foo\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif c, rw, err := f.Hijack(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t} else {\n\t\t\t\tif _, err := rw.WriteString(\"bar\"); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tif err := rw.Flush(); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tif err := c.Close(); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: NewFastHTTPHandler(http.HandlerFunc(nethttpH)),\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tbuf, err := io.ReadAll(c)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif string(buf) != \"foobar\" {\n\t\t\tt.Errorf(\"unexpected response: %q. Expecting %q\", buf, \"foobar\")\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestFlushHandler(t *testing.T) {\n\tt.Parallel()\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tif f, ok := w.(http.Flusher); !ok {\n\t\t\tt.Errorf(\"expected http.ResponseWriter to implement http.Flusher\")\n\t\t} else {\n\t\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\t\tw.Header().Set(\"Content-Length\", \"6\")\n\t\t\tw.Header().Set(\"X-Foo\", \"bar\")\n\n\t\t\tif _, err := w.Write([]byte(\"foo\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tf.Flush()\n\n\t\t\ttime.Sleep(time.Millisecond * 500)\n\n\t\t\tif _, err := w.Write([]byte(\"bar\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tf.Flush()\n\t\t}\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: NewFastHTTPHandler(http.HandlerFunc(nethttpH)),\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tresp, err := http.ReadResponse(bufio.NewReader(c), nil)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error reading response: %v\", err)\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode, http.StatusOK)\n\t\t}\n\n\t\tif resp.Header.Get(\"Content-Type\") != \"text/plain; charset=utf-8\" {\n\t\t\tt.Errorf(\"unexpected Content-Type header: %q. Expecting %q\", resp.Header.Get(\"Content-Type\"), \"text/plain; charset=utf-8\")\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tresp.Body.Close()\n\t\tif err != nil && err != io.ErrUnexpectedEOF {\n\t\t\tt.Errorf(\"unexpected error reading body: %v\", err)\n\t\t}\n\n\t\tif string(body) != \"foobar\" {\n\t\t\tt.Errorf(\"unexpected response body: %q. Expecting %q\", body, \"foobar\")\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestFlushHandlerClosed(t *testing.T) {\n\tt.Parallel()\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tif f, ok := w.(http.Flusher); !ok {\n\t\t\tt.Errorf(\"expected http.ResponseWriter to implement http.Flusher\")\n\t\t} else {\n\t\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\t\tw.Header().Set(\"Content-Length\", \"6\")\n\t\t\tw.Header().Set(\"X-Foo\", \"bar\")\n\n\t\t\tif _, err := w.Write([]byte(\"foo\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tf.Flush()\n\n\t\t\ttime.Sleep(time.Second)\n\n\t\t\tif _, err := w.Write([]byte(\"bar\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tf.Flush()\n\t\t}\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: NewFastHTTPHandler(http.HandlerFunc(nethttpH)),\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\ttime.AfterFunc(500*time.Millisecond, func() {\n\t\t\tc.Close()\n\t\t})\n\t\tresp, err := http.ReadResponse(bufio.NewReader(c), nil)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error reading response: %v\", err)\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode, http.StatusOK)\n\t\t}\n\n\t\tif resp.Header.Get(\"Content-Type\") != \"text/plain; charset=utf-8\" {\n\t\t\tt.Errorf(\"unexpected Content-Type header: %q. Expecting %q\", resp.Header.Get(\"Content-Type\"), \"text/plain; charset=utf-8\")\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tresp.Body.Close()\n\t\tif err != nil && err != io.ErrUnexpectedEOF {\n\t\t\tt.Errorf(\"unexpected error reading body: %v\", err)\n\t\t}\n\n\t\tif string(body) != \"foo\" {\n\t\t\tt.Errorf(\"unexpected response body: %q. Expecting %q\", body, \"foo\")\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestHijackFlush(t *testing.T) {\n\tt.Parallel()\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tif f, ok := w.(http.Hijacker); !ok {\n\t\t\tt.Errorf(\"expected http.ResponseWriter to implement http.Hijacker\")\n\t\t} else {\n\t\t\tif _, err := w.Write([]byte(\"foo\")); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif c, rw, err := f.Hijack(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t} else {\n\t\t\t\tif _, err := rw.WriteString(\"bar\"); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tif err := rw.Flush(); err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(time.Second)\n\n\t\t\t\t_ = c.Close()\n\t\t\t}\n\t\t}\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: NewFastHTTPHandler(http.HandlerFunc(nethttpH)),\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\ttime.AfterFunc(500*time.Millisecond, func() {\n\t\t\tc.Close()\n\t\t})\n\t\tbuf, err := io.ReadAll(c)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif string(buf) != \"foobar\" {\n\t\t\tt.Errorf(\"unexpected response: %q. Expecting %q\", buf, \"foobar\")\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestResourceRecyclingUnderLoad_OneEndpoint(t *testing.T) {\n\tt.Parallel()\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"Hello World!\")\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: NewFastHTTPHandler(http.HandlerFunc(handler)),\n\t}\n\n\trequestCount := 10\n\tresponseTimeout := 500 * time.Millisecond\n\texpectedBody := \"Hello World!\"\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tfor reqID := 1; reqID <= requestCount; reqID++ {\n\t\treq := httptest.NewRequest(\"GET\", \"/\", http.NoBody)\n\t\tbody, err := sendRequest(ln, req, responseTimeout)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"[%d] unexpected error sending request: %v\", reqID, err)\n\t\t}\n\t\tif string(body) != expectedBody {\n\t\t\tt.Errorf(\"[%d] unexpected response: %q. Expecting %q\", reqID, body, expectedBody)\n\t\t}\n\t}\n}\n\nfunc TestResourceRecyclingUnderLoad_MultipleEndpoints(t *testing.T) {\n\tt.Parallel()\n\n\thandlers := []struct {\n\t\tendpoint     string\n\t\thandler      fasthttp.RequestHandler\n\t\texpectedBody string\n\t}{\n\t\t{\n\t\t\tendpoint: \"/done\",\n\t\t\thandler: NewFastHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tfmt.Fprintf(w, \"Hello World!\")\n\t\t\t})),\n\t\t\texpectedBody: \"Hello World!\",\n\t\t},\n\t\t{\n\t\t\tendpoint: \"/flush\",\n\t\t\thandler: NewFastHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif f, ok := w.(http.Flusher); ok {\n\t\t\t\t\tif _, err := w.Write([]byte(\"foo\")); err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tf.Flush()\n\t\t\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\t\t\tif _, err := w.Write([]byte(\"bar\")); err != nil {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tf.Flush()\n\t\t\t\t} else {\n\t\t\t\t\thttp.Error(w, \"Flusher not supported\", http.StatusInternalServerError)\n\t\t\t\t}\n\t\t\t})),\n\t\t\texpectedBody: \"foobar\",\n\t\t},\n\t\t{\n\t\t\tendpoint: \"/hijack\",\n\t\t\thandler: NewFastHTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif hj, ok := w.(http.Hijacker); ok {\n\t\t\t\t\tconn, rw, err := hj.Hijack()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdefer conn.Close()\n\t\t\t\t\tif _, err := rw.WriteString(\"hijacked\"); err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\trw.Flush()\n\t\t\t\t} else {\n\t\t\t\t\thttp.Error(w, \"Hijacker not supported\", http.StatusInternalServerError)\n\t\t\t\t}\n\t\t\t})),\n\t\t\texpectedBody: \"hijacked\",\n\t\t},\n\t}\n\n\ts := &fasthttp.Server{\n\t\tHandler: func(ctx *fasthttp.RequestCtx) {\n\t\t\tfor _, h := range handlers {\n\t\t\t\tif string(ctx.Path()) == h.endpoint {\n\t\t\t\t\th.handler(ctx)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.Error(\"Not Found\", fasthttp.StatusNotFound)\n\t\t},\n\t}\n\n\trepeatCount := 3\n\tresponseTimeout := 500 * time.Millisecond\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tfor range repeatCount {\n\t\tfor _, handler := range handlers {\n\t\t\treq := httptest.NewRequest(\"GET\", handler.endpoint, http.NoBody)\n\t\t\tbody, err := sendRequest(ln, req, responseTimeout)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"[%s] unexpected error sending request: %v\", handler.endpoint, err)\n\t\t\t}\n\t\t\tif string(body) != handler.expectedBody {\n\t\t\t\tt.Errorf(\"[%s] unexpected response: %q. Expecting %q\", handler.endpoint, body, handler.expectedBody)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc sendRequest(ln *fasthttputil.InmemoryListener, req *http.Request, responseTimeout time.Duration) ([]byte, error) {\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := req.Write(c); err != nil {\n\t\treturn nil, err\n\t}\n\n\ttime.AfterFunc(responseTimeout, func() {\n\t\tc.Close()\n\t})\n\tresponse, err := io.ReadAll(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(response)), nil)\n\tif err != nil {\n\t\t// Hijacked response, return the full response instead of the parsed body.\n\t\treturn response, nil\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn body, nil\n}\n\nfunc TestNewFastHTTPHandlerPanic(t *testing.T) {\n\tvar ctx fasthttp.RequestCtx\n\tvar req fasthttp.Request\n\n\treq.Header.SetMethod(fasthttp.MethodPost)\n\treq.SetRequestURI(\"/\")\n\treq.Header.SetHost(\"example.com\")\n\n\tremoteAddr, err := net.ResolveTCPAddr(\"tcp\", \"1.2.3.4:6789\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tctx.Init(&req, remoteAddr, nil)\n\n\tnethttpH := func(w http.ResponseWriter, r *http.Request) {\n\t\tpanic(\"test panic\")\n\t}\n\tfasthttpH := NewFastHTTPHandler(http.HandlerFunc(nethttpH))\n\n\tdefer func() {\n\t\trecover() //nolint:errcheck\n\t}()\n\n\tfasthttpH(&ctx)\n\n\tt.Error(\"expected panic, but it didn't happen\")\n}\n"
  },
  {
    "path": "fasthttpadaptor/b2s.go",
    "content": "package fasthttpadaptor\n\nimport \"unsafe\"\n\n// b2s converts byte slice to a string without memory allocation.\n// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .\nfunc b2s(b []byte) string {\n\treturn unsafe.String(unsafe.SliceData(b), len(b))\n}\n"
  },
  {
    "path": "fasthttpadaptor/request.go",
    "content": "package fasthttpadaptor\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\n// ConvertRequest converts a fasthttp.Request to an http.Request.\n// forServer should be set to true when the http.Request is going to be passed to a http.Handler.\n//\n// The http.Request must not be used after the fasthttp handler has returned!\n// Memory in use by the http.Request will be reused after your handler has returned!\nfunc ConvertRequest(ctx *fasthttp.RequestCtx, r *http.Request, forServer bool) error {\n\tbody := ctx.PostBody()\n\tstrRequestURI := b2s(ctx.RequestURI())\n\n\trURL, err := url.ParseRequestURI(strRequestURI)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Method = b2s(ctx.Method())\n\tr.Proto = b2s(ctx.Request.Header.Protocol())\n\tif r.Proto == \"HTTP/2\" {\n\t\tr.ProtoMajor = 2\n\t} else {\n\t\tr.ProtoMajor = 1\n\t}\n\tr.ProtoMinor = 1\n\tr.ContentLength = int64(len(body))\n\tr.RemoteAddr = ctx.RemoteAddr().String()\n\tr.Host = b2s(ctx.Host())\n\tr.TLS = ctx.TLSConnectionState()\n\tr.Body = io.NopCloser(bytes.NewReader(body))\n\tr.URL = rURL\n\n\tif forServer {\n\t\tr.RequestURI = strRequestURI\n\t}\n\n\tif r.Header == nil {\n\t\tr.Header = make(http.Header)\n\t} else if len(r.Header) > 0 {\n\t\tfor k := range r.Header {\n\t\t\tdelete(r.Header, k)\n\t\t}\n\t}\n\n\tfor k, v := range ctx.Request.Header.All() {\n\t\tsk := b2s(k)\n\t\tsv := b2s(v)\n\n\t\tswitch sk {\n\t\tcase \"Transfer-Encoding\":\n\t\t\tr.TransferEncoding = append(r.TransferEncoding, sv)\n\t\tdefault:\n\t\t\tif sk == fasthttp.HeaderCookie {\n\t\t\t\tsv = strings.Clone(sv)\n\t\t\t}\n\t\t\tr.Header.Set(sk, sv)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "fasthttpadaptor/request_test.go",
    "content": "package fasthttpadaptor\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc BenchmarkConvertRequest(b *testing.B) {\n\tvar httpReq http.Request\n\n\tctx := &fasthttp.RequestCtx{\n\t\tRequest: fasthttp.Request{\n\t\t\tHeader:        fasthttp.RequestHeader{},\n\t\t\tUseHostHeader: false,\n\t\t},\n\t}\n\tctx.Request.Header.SetMethod(\"GET\")\n\tctx.Request.Header.Set(\"x\", \"test\")\n\tctx.Request.Header.Set(\"y\", \"test\")\n\tctx.Request.SetRequestURI(\"/test\")\n\tctx.Request.SetHost(\"test\")\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = ConvertRequest(ctx, &httpReq, true)\n\t}\n}\n"
  },
  {
    "path": "fasthttpproxy/dialer.go",
    "content": "package fasthttpproxy\n\nimport (\n\t\"bufio\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"golang.org/x/net/http/httpproxy\"\n\t\"golang.org/x/net/proxy\"\n)\n\nvar (\n\tcolonTLSPort = \":443\"\n\ttmpURL       = &url.URL{Scheme: httpsScheme, Host: \"example.com\"}\n)\n\n// Dialer embeds both fasthttp.TCPDialer and httpproxy.Config, allowing it\n// to take advantage of the optimizations provided by fasthttp for dialing while also\n// utilizing the finer-grained configuration options offered by httpproxy.\ntype Dialer struct {\n\tfasthttp.TCPDialer\n\t// Support HTTPProxy, HTTPSProxy and NoProxy configuration.\n\t//\n\t// HTTPProxy represents the value of the HTTP_PROXY or\n\t// http_proxy environment variable. It will be used as the proxy\n\t// URL for HTTP requests unless overridden by NoProxy.\n\t//\n\t// HTTPSProxy represents the HTTPS_PROXY or https_proxy\n\t// environment variable. It will be used as the proxy URL for\n\t// HTTPS requests unless overridden by NoProxy.\n\t//\n\t// NoProxy represents the NO_PROXY or no_proxy environment\n\t// variable. It specifies a string that contains comma-separated values\n\t// specifying hosts that should be excluded from proxying. Each value is\n\t// represented by an IP address prefix (1.2.3.4), an IP address prefix in\n\t// CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*).\n\t// An IP address prefix and domain name can also include a literal port\n\t// number (1.2.3.4:80).\n\t// A domain name matches that name and all subdomains. A domain name with\n\t// a leading \".\" matches subdomains only. For example \"foo.com\" matches\n\t// \"foo.com\" and \"bar.foo.com\"; \".y.com\" matches \"x.y.com\" but not \"y.com\".\n\t// A single asterisk (*) indicates that no proxying should be done.\n\t// A best effort is made to parse the string and errors are\n\t// ignored.\n\thttpproxy.Config\n\n\t// Attempt to connect to both ipv4 and ipv6 addresses if set to true.\n\t// By default, dial only to ipv4 addresses,\n\t// since unfortunately ipv6 remains broken in many networks worldwide :)\n\t//\n\t// This field from the fasthttp client is provided redundantly here because\n\t// when we customize the Dial function for the client, its DialDualStack field\n\t// configuration becomes ineffective.\n\tDialDualStack bool\n\t// Dial timeout.\n\t//\n\t// This field from the fasthttp client is provided redundantly here because\n\t// when we customize the Dial function for the client, its DialTimeout field\n\t// configuration becomes ineffective.\n\tTimeout time.Duration\n\t// The timeout for sending a CONNECT request when using an HTTP proxy.\n\tConnectTimeout time.Duration\n}\n\n// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter\n// determines whether the proxy address comes from Dialer.Config or from environment variables.\nfunc (d *Dialer) GetDialFunc(useEnv bool) (fasthttp.DialFunc, error) {\n\tconfig := &d.Config\n\tif useEnv {\n\t\tconfig = httpproxy.FromEnvironment()\n\t}\n\tproxyURLIsSame := config.HTTPSProxy == config.HTTPProxy && config.NoProxy == \"\"\n\tnetwork := \"tcp4\"\n\tif d.DialDualStack {\n\t\tnetwork = \"tcp\"\n\t}\n\tproxyFunc := config.ProxyFunc()\n\tif proxyURLIsSame {\n\t\tvar proxyURL *url.URL\n\t\tvar proxyDialer proxy.Dialer\n\t\tproxyURL, err := proxyFunc(tmpURL)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif proxyURL == nil {\n\t\t\t// dial directly\n\t\t\treturn func(addr string) (net.Conn, error) {\n\t\t\t\treturn d.Dial(network, addr)\n\t\t\t}, nil\n\t\t}\n\t\tswitch proxyURL.Scheme {\n\t\tcase \"socks5\", \"socks5h\":\n\t\t\tproxyDialer, err = proxy.FromURL(proxyURL, d)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"http\":\n\t\t\tproxyAddr, auth := addrAndAuth(proxyURL, nil)\n\t\t\tproxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {\n\t\t\t\treturn httpProxyDial(d, network, addr, proxyAddr, auth)\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"proxy: unknown scheme: \" + proxyURL.Scheme)\n\t\t}\n\t\treturn func(addr string) (net.Conn, error) {\n\t\t\treturn proxyDialer.Dial(network, addr)\n\t\t}, nil\n\t}\n\t// slow path when the proxyURL changes along with the request URL.\n\tvar authCache sync.Map\n\treturn func(addr string) (conn net.Conn, err error) {\n\t\tvar proxyDialer proxy.Dialer\n\t\tvar proxyURL *url.URL\n\t\tscheme := httpsScheme\n\t\tif !strings.HasSuffix(addr, colonTLSPort) {\n\t\t\tscheme = httpScheme\n\t\t}\n\t\treqURL := &url.URL{Host: addr, Scheme: scheme}\n\t\tproxyURL, err = proxyFunc(reqURL)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif proxyURL == nil {\n\t\t\t// dial directly\n\t\t\treturn d.Dial(network, addr)\n\t\t}\n\t\tswitch proxyURL.Scheme {\n\t\tcase \"socks5\", \"socks5h\":\n\t\t\tproxyDialer, err = proxy.FromURL(proxyURL, d)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"http\":\n\t\t\tproxyAddr, auth := addrAndAuth(proxyURL, &authCache)\n\t\t\tproxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {\n\t\t\t\treturn httpProxyDial(d, network, addr, proxyAddr, auth)\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"proxy: unknown scheme: \" + proxyURL.Scheme)\n\t\t}\n\t\treturn proxyDialer.Dial(network, addr)\n\t}, nil\n}\n\n// Dial is solely for implementing the proxy.Dialer interface.\nfunc (d *Dialer) Dial(network, addr string) (net.Conn, error) {\n\tif network == \"tcp4\" {\n\t\tif d.Timeout > 0 {\n\t\t\treturn d.DialTimeout(addr, d.Timeout)\n\t\t}\n\t\treturn d.TCPDialer.Dial(addr)\n\t}\n\tif network == \"tcp\" {\n\t\tif d.Timeout > 0 {\n\t\t\treturn d.DialDualStackTimeout(addr, d.Timeout)\n\t\t}\n\t\treturn d.TCPDialer.DialDualStack(addr)\n\t}\n\terr := errors.New(\"dont support the network: \" + network)\n\treturn nil, err\n}\n\nfunc (d *Dialer) connectTimeout() time.Duration {\n\treturn d.ConnectTimeout\n}\n\n// In the httpProxyDial function, the proxy.Dialer that implements\n// this interface can retrieve timeout information when sending the CONNECT\n// method to the HTTP proxy.\ntype httpProxyDialer interface {\n\tconnectTimeout() time.Duration\n}\n\n// DialerFunc Make a function of type func(network, addr string) (net.Conn, error)\n// implement the proxy.Dialer interface.\ntype DialerFunc func(network, addr string) (net.Conn, error)\n\nfunc (d DialerFunc) Dial(network, addr string) (net.Conn, error) {\n\treturn d(network, addr)\n}\n\n// Establish a connection through an HTTP proxy.\nfunc httpProxyDial(dialer proxy.Dialer, network, addr, proxyAddr, auth string) (net.Conn, error) {\n\tconn, err := dialer.Dial(network, proxyAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar connectTimeout time.Duration\n\thp, ok := dialer.(httpProxyDialer)\n\tif ok {\n\t\tconnectTimeout = hp.connectTimeout()\n\t}\n\n\tif connectTimeout > 0 {\n\t\tif err = conn.SetDeadline(time.Now().Add(connectTimeout)); err != nil {\n\t\t\t_ = conn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = conn.SetDeadline(time.Time{})\n\t\t}()\n\t}\n\treq := \"CONNECT \" + addr + \" HTTP/1.1\\r\\nHost: \" + addr + \"\\r\\n\"\n\tif auth != \"\" {\n\t\treq += \"Proxy-Authorization: Basic \" + auth + \"\\r\\n\"\n\t}\n\treq += \"\\r\\n\"\n\t_, err = conn.Write([]byte(req))\n\tif err != nil {\n\t\t_ = conn.Close()\n\t\treturn nil, err\n\t}\n\tres := fasthttp.AcquireResponse()\n\tdefer fasthttp.ReleaseResponse(res)\n\tres.SkipBody = true\n\tif err = res.Read(bufio.NewReaderSize(conn, 1024)); err != nil {\n\t\t_ = conn.Close()\n\t\treturn nil, err\n\t}\n\tif res.Header.StatusCode() != 200 {\n\t\t_ = conn.Close()\n\t\terr = fmt.Errorf(\"could not connect to proxyAddr: %s status code: %d\", proxyAddr, res.Header.StatusCode())\n\t\treturn nil, err\n\t}\n\treturn conn, err\n}\n\n// Cache authentication information for HTTP proxies.\ntype proxyInfo struct {\n\tauth string\n\taddr string\n}\n\nfunc addrAndAuth(pu *url.URL, authCache *sync.Map) (proxyAddr, auth string) {\n\tif pu.User == nil {\n\t\tproxyAddr = pu.Host + pu.Path\n\t\treturn proxyAddr, auth\n\t}\n\tif authCache != nil {\n\t\tif v, ok := authCache.Load(pu); ok {\n\t\t\tinfo := v.(*proxyInfo)\n\t\t\treturn info.addr, info.auth\n\t\t}\n\t}\n\tinfo := &proxyInfo{\n\t\tauth: base64.StdEncoding.EncodeToString([]byte(pu.User.String())),\n\t\taddr: pu.Host + pu.Path,\n\t}\n\tif authCache != nil {\n\t\tauthCache.Store(pu, info)\n\t}\n\treturn info.addr, info.auth\n}\n"
  },
  {
    "path": "fasthttpproxy/dialer_test.go",
    "content": "package fasthttpproxy\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"golang.org/x/net/http/httpproxy\"\n)\n\nfunc TestDialer_GetDialFunc(t *testing.T) {\n\tcounts := make([]atomic.Int64, 4)\n\tproxyListenPorts := []string{\"8001\", \"8002\", \"8003\", \"8004\"}\n\tlns := startProxyServer(t, proxyListenPorts, counts)\n\tdefer func() {\n\t\tfor _, l := range lns {\n\t\t\tl.Close()\n\t\t}\n\t}()\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:\"+proxyListenPorts[2])\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:\"+proxyListenPorts[3])\n\tt.Setenv(\"NO_PROXY\", \"github.com\")\n\ttype fields struct {\n\t\thttpProxy  string\n\t\thttpsProxy string\n\t\tnoProxy    string\n\t}\n\ttype args struct {\n\t\tuseEnv bool\n\t}\n\ttests := []struct {\n\t\tname           string\n\t\tfields         fields\n\t\targs           args\n\t\twantCounts     []int64\n\t\tdialAddr       string\n\t\twantErrMessage string\n\t}{\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration. dial https host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 1, 0, 0},\n\t\t\tdialAddr:   \"github.io:443\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration. dial http host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{1, 0, 0, 0},\n\t\t\tdialAddr:   \"github.io:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration. dial http host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration. dial https host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:443\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the env. dial http host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: true,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 1, 0},\n\t\t\tdialAddr:   \"github.io:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the env. dial https host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: true,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 1},\n\t\t\tdialAddr:   \"github.io:443\",\n\t\t},\n\n\t\t{\n\t\t\tname: \"proxy information comes from the env. dial http host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: true,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the env. dial https host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[1],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: true,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:443\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration and httpProxy same with httpsProxy. dial http host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{1, 0, 0, 0},\n\t\t\tdialAddr:   \"github.io:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration and httpProxy same with httpsProxy. dial https host\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{1, 0, 0, 0},\n\t\t\tdialAddr:   \"github.io:443\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration and httpProxy same with httpsProxy. dial http host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:80\",\n\t\t},\n\t\t{\n\t\t\tname: \"proxy information comes from the configuration and httpProxy same with httpsProxy. dial https host matched with noProxy\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"http://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\tnoProxy:    \"github.com\",\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts: []int64{0, 0, 0, 0},\n\t\t\tdialAddr:   \"github.com:443\",\n\t\t},\n\t\t{\n\t\t\tname: \"return an error for unsupported proxy protocols.\",\n\t\t\tfields: fields{\n\t\t\t\thttpProxy:  \"socket6://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t\thttpsProxy: \"socket6://127.0.0.1:\" + proxyListenPorts[0],\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tuseEnv: false,\n\t\t\t},\n\t\t\twantCounts:     []int64{0, 0, 0, 0},\n\t\t\tdialAddr:       \"github.io:80\",\n\t\t\twantErrMessage: \"proxy: unknown scheme: socket6\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := getDialer(tt.fields.httpProxy, tt.fields.httpsProxy, tt.fields.noProxy)\n\t\t\tdialFunc, err := d.GetDialFunc(tt.args.useEnv)\n\t\t\tif (err != nil) != (tt.wantErrMessage != \"\") {\n\t\t\t\tt.Fatalf(\"GetDialFunc() error = %v, wantErr %v\", err, tt.wantErrMessage)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif tt.wantErrMessage != \"\" {\n\t\t\t\tif err.Error() != tt.wantErrMessage {\n\t\t\t\t\tt.Fatalf(\"want error message: %s, got: %s\", err.Error(), tt.wantErrMessage)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = dialFunc(tt.dialAddr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif !countsEqual(getCounts(counts), tt.wantCounts) {\n\t\t\t\tt.Errorf(\"GetDialFunc() counts = %v, want %v\", getCounts(counts), tt.wantCounts)\n\t\t\t}\n\t\t})\n\t\tfor i := range counts {\n\t\t\tcounts[i].Store(0)\n\t\t}\n\t}\n}\n\nfunc startProxyServer(t *testing.T, ports []string, counts []atomic.Int64) (lns []net.Listener) {\n\tfor i, port := range ports {\n\t\tln, err := net.Listen(\"tcp\", \":\"+port)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tlns = append(lns, ln)\n\t\ti := i\n\t\tgo func() {\n\t\t\treq := fasthttp.AcquireRequest()\n\t\t\tfor {\n\t\t\t\tconn, err := ln.Accept()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif err != io.EOF && !strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = req.Read(bufio.NewReader(conn))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\tif string(req.Header.Method()) == \"CONNECT\" {\n\t\t\t\t\tcounts[i].Add(1)\n\t\t\t\t}\n\t\t\t\t_, err = conn.Write([]byte(\"HTTP/1.1 200 Connection Established\\r\\n\\r\\n\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\treq.Reset()\n\t\t\t}\n\t\t\tfasthttp.ReleaseRequest(req)\n\t\t}()\n\t}\n\treturn lns\n}\n\nfunc getDialer(httpProxy, httpsProxy, noProxy string) *Dialer {\n\treturn &Dialer{\n\t\tConfig: httpproxy.Config{\n\t\t\tHTTPProxy:  httpProxy,\n\t\t\tHTTPSProxy: httpsProxy,\n\t\t\tNoProxy:    noProxy,\n\t\t},\n\t}\n}\n\nfunc getCounts(counts []atomic.Int64) (r []int64) {\n\tfor i := range counts {\n\t\tr = append(r, counts[i].Load())\n\t}\n\treturn r\n}\n\nfunc countsEqual(a, b []int64) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif b[i] != a[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "fasthttpproxy/doc.go",
    "content": "// Package fasthttpproxy provides SOCKS5 and HTTP proxy support for fasthttp.\npackage fasthttpproxy\n"
  },
  {
    "path": "fasthttpproxy/http.go",
    "content": "package fasthttpproxy\n\nimport (\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"golang.org/x/net/http/httpproxy\"\n)\n\n// FasthttpHTTPDialer returns a fasthttp.DialFunc that dials using\n// the provided HTTP proxy.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpHTTPDialer(\"username:password@localhost:9050\"),\n//\t}\nfunc FasthttpHTTPDialer(proxy string) fasthttp.DialFunc {\n\treturn FasthttpHTTPDialerTimeout(proxy, 0)\n}\n\n// FasthttpHTTPDialerTimeout returns a fasthttp.DialFunc that dials using\n// the provided HTTP proxy using the given timeout.\n// The timeout parameter determines both the dial timeout and the CONNECT request timeout.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpHTTPDialerTimeout(\"username:password@localhost:9050\", time.Second * 2),\n//\t}\nfunc FasthttpHTTPDialerTimeout(proxy string, timeout time.Duration) fasthttp.DialFunc {\n\td := Dialer{Config: httpproxy.Config{HTTPProxy: proxy, HTTPSProxy: proxy}, Timeout: timeout, ConnectTimeout: timeout}\n\tdialFunc, _ := d.GetDialFunc(false)\n\treturn dialFunc\n}\n\n// FasthttpHTTPDialerDualStack returns a fasthttp.DialFunc that dials using\n// the provided HTTP proxy with support for both IPv4 and IPv6.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpHTTPDialerDualStack(\"username:password@localhost:9050\"),\n//\t}\nfunc FasthttpHTTPDialerDualStack(proxy string) fasthttp.DialFunc {\n\treturn FasthttpHTTPDialerDualStackTimeout(proxy, 0)\n}\n\n// FasthttpHTTPDialerDualStackTimeout returns a fasthttp.DialFunc that dials using\n// the provided HTTP proxy with support for both IPv4 and IPv6, using the given timeout.\n// The timeout parameter determines both the dial timeout and the CONNECT request timeout.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpHTTPDialerDualStackTimeout(\"username:password@localhost:9050\", time.Second * 2),\n//\t}\nfunc FasthttpHTTPDialerDualStackTimeout(proxy string, timeout time.Duration) fasthttp.DialFunc {\n\td := Dialer{\n\t\tConfig: httpproxy.Config{HTTPProxy: proxy, HTTPSProxy: proxy}, Timeout: timeout, ConnectTimeout: timeout,\n\t\tDialDualStack: true,\n\t}\n\tdialFunc, _ := d.GetDialFunc(false)\n\treturn dialFunc\n}\n"
  },
  {
    "path": "fasthttpproxy/proxy_env.go",
    "content": "package fasthttpproxy\n\nimport (\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nconst (\n\thttpsScheme = \"https\"\n\thttpScheme  = \"http\"\n)\n\n// FasthttpProxyHTTPDialer returns a fasthttp.DialFunc that dials using\n// the env(HTTP_PROXY, HTTPS_PROXY and NO_PROXY) configured HTTP proxy.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpProxyHTTPDialer(),\n//\t}\nfunc FasthttpProxyHTTPDialer() fasthttp.DialFunc {\n\treturn FasthttpProxyHTTPDialerTimeout(0)\n}\n\n// FasthttpProxyHTTPDialerTimeout returns a fasthttp.DialFunc that dials using\n// the env(HTTP_PROXY, HTTPS_PROXY and NO_PROXY) configured HTTP proxy using the given timeout.\n// The timeout parameter determines both the dial timeout and the CONNECT request timeout.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpProxyHTTPDialerTimeout(time.Second * 2),\n//\t}\nfunc FasthttpProxyHTTPDialerTimeout(timeout time.Duration) fasthttp.DialFunc {\n\td := Dialer{Timeout: timeout, ConnectTimeout: timeout}\n\tdialFunc, _ := d.GetDialFunc(true)\n\treturn dialFunc\n}\n"
  },
  {
    "path": "fasthttpproxy/socks5.go",
    "content": "package fasthttpproxy\n\nimport (\n\t\"github.com/valyala/fasthttp\"\n\t\"golang.org/x/net/http/httpproxy\"\n)\n\n// FasthttpSocksDialer returns a fasthttp.DialFunc that dials using\n// the provided SOCKS5 proxy.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpSocksDialer(\"socks5://localhost:9050\"),\n//\t}\nfunc FasthttpSocksDialer(proxyAddr string) fasthttp.DialFunc {\n\td := Dialer{Config: httpproxy.Config{HTTPProxy: proxyAddr, HTTPSProxy: proxyAddr}}\n\tdialFunc, _ := d.GetDialFunc(false)\n\treturn dialFunc\n}\n\n// FasthttpSocksDialerDualStack returns a fasthttp.DialFunc that dials using\n// the provided SOCKS5 proxy with support for both IPv4 and IPv6.\n//\n// Example usage:\n//\n//\tc := &fasthttp.Client{\n//\t\tDial: fasthttpproxy.FasthttpSocksDialerDualStack(\"socks5://localhost:9050\"),\n//\t}\nfunc FasthttpSocksDialerDualStack(proxyAddr string) fasthttp.DialFunc {\n\td := Dialer{Config: httpproxy.Config{HTTPProxy: proxyAddr, HTTPSProxy: proxyAddr}, DialDualStack: true}\n\tdialFunc, _ := d.GetDialFunc(false)\n\treturn dialFunc\n}\n"
  },
  {
    "path": "fasthttputil/doc.go",
    "content": "// Package fasthttputil provides utility functions for fasthttp.\npackage fasthttputil\n"
  },
  {
    "path": "fasthttputil/inmemory_listener.go",
    "content": "package fasthttputil\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"sync\"\n)\n\n// ErrInmemoryListenerClosed indicates that the InmemoryListener is already closed.\nvar ErrInmemoryListenerClosed = errors.New(\"InmemoryListener is already closed: use of closed network connection\")\n\n// InmemoryListener provides in-memory dialer<->net.Listener implementation.\n//\n// It may be used either for fast in-process client<->server communications\n// without network stack overhead or for client<->server tests.\ntype InmemoryListener struct {\n\tlistenerAddr net.Addr\n\tconns        chan acceptConn\n\taddrLock     sync.RWMutex\n\tlock         sync.Mutex\n\tclosed       bool\n}\n\ntype acceptConn struct {\n\tconn     net.Conn\n\taccepted chan struct{}\n}\n\n// NewInmemoryListener returns new in-memory dialer<->net.Listener.\nfunc NewInmemoryListener() *InmemoryListener {\n\treturn &InmemoryListener{\n\t\tconns: make(chan acceptConn, 1024),\n\t}\n}\n\n// SetLocalAddr sets the (simulated) local address for the listener.\nfunc (ln *InmemoryListener) SetLocalAddr(localAddr net.Addr) {\n\tln.addrLock.Lock()\n\tdefer ln.addrLock.Unlock()\n\n\tln.listenerAddr = localAddr\n}\n\n// Accept implements net.Listener's Accept.\n//\n// It is safe calling Accept from concurrently running goroutines.\n//\n// Accept returns new connection per each Dial call.\nfunc (ln *InmemoryListener) Accept() (net.Conn, error) {\n\tc, ok := <-ln.conns\n\tif !ok {\n\t\treturn nil, ErrInmemoryListenerClosed\n\t}\n\tclose(c.accepted)\n\treturn c.conn, nil\n}\n\n// Close implements net.Listener's Close.\nfunc (ln *InmemoryListener) Close() error {\n\tvar err error\n\n\tln.lock.Lock()\n\tif !ln.closed {\n\t\tclose(ln.conns)\n\t\tln.closed = true\n\t} else {\n\t\terr = ErrInmemoryListenerClosed\n\t}\n\tln.lock.Unlock()\n\treturn err\n}\n\ntype inmemoryAddr int\n\nfunc (inmemoryAddr) Network() string {\n\treturn \"inmemory\"\n}\n\nfunc (inmemoryAddr) String() string {\n\treturn \"InmemoryListener\"\n}\n\n// Addr implements net.Listener's Addr.\nfunc (ln *InmemoryListener) Addr() net.Addr {\n\tln.addrLock.RLock()\n\tdefer ln.addrLock.RUnlock()\n\n\tif ln.listenerAddr != nil {\n\t\treturn ln.listenerAddr\n\t}\n\n\treturn inmemoryAddr(0)\n}\n\n// Dial creates new client<->server connection.\n// Just like a real Dial it only returns once the server\n// has accepted the connection.\n//\n// It is safe calling Dial from concurrently running goroutines.\nfunc (ln *InmemoryListener) Dial() (net.Conn, error) {\n\treturn ln.DialWithLocalAddr(nil)\n}\n\n// DialWithLocalAddr creates new client<->server connection.\n// Just like a real Dial it only returns once the server\n// has accepted the connection. The local address of the\n// client connection can be set with local.\n//\n// It is safe calling Dial from concurrently running goroutines.\nfunc (ln *InmemoryListener) DialWithLocalAddr(local net.Addr) (net.Conn, error) {\n\tpc := NewPipeConns()\n\n\tpc.SetAddresses(local, ln.Addr(), ln.Addr(), local)\n\n\tcConn := pc.Conn1()\n\tsConn := pc.Conn2()\n\tln.lock.Lock()\n\taccepted := make(chan struct{})\n\tif !ln.closed {\n\t\tln.conns <- acceptConn{conn: sConn, accepted: accepted}\n\t\t// Wait until the connection has been accepted.\n\t\t<-accepted\n\t} else {\n\t\t_ = sConn.Close()\n\t\t_ = cConn.Close()\n\t\tcConn = nil\n\t}\n\tln.lock.Unlock()\n\n\tif cConn == nil {\n\t\treturn nil, ErrInmemoryListenerClosed\n\t}\n\treturn cConn, nil\n}\n"
  },
  {
    "path": "fasthttputil/inmemory_listener_test.go",
    "content": "package fasthttputil\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestInmemoryListener(t *testing.T) {\n\tln := NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tfor i := range 10 {\n\t\tgo func(n int) {\n\t\t\tconn, err := ln.Dial()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t\treq := fmt.Sprintf(\"request_%d\", n)\n\t\t\tnn, err := conn.Write([]byte(req))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif nn != len(req) {\n\t\t\t\tt.Errorf(\"unexpected number of bytes written: %d. Expecting %d\", nn, len(req))\n\t\t\t}\n\t\t\tbuf := make([]byte, 30)\n\t\t\tnn, err = conn.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tbuf = buf[:nn]\n\t\t\tresp := fmt.Sprintf(\"response_%d\", n)\n\t\t\tif nn != len(resp) {\n\t\t\t\tt.Errorf(\"unexpected number of bytes read: %d. Expecting %d\", nn, len(resp))\n\t\t\t}\n\t\t\tif string(buf) != resp {\n\t\t\t\tt.Errorf(\"unexpected response %q. Expecting %q\", buf, resp)\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}(i)\n\t}\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tclose(serverCh)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t\tbuf := make([]byte, 30)\n\t\t\tn, err := conn.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tbuf = buf[:n]\n\t\t\tif !bytes.HasPrefix(buf, []byte(\"request_\")) {\n\t\t\t\tt.Errorf(\"unexpected request prefix %q. Expecting %q\", buf, \"request_\")\n\t\t\t}\n\t\t\tresp := fmt.Sprintf(\"response_%s\", buf[len(\"request_\"):])\n\t\t\tn, err = conn.Write([]byte(resp))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif n != len(resp) {\n\t\t\t\tt.Errorf(\"unexpected number of bytes written: %d. Expecting %d\", n, len(resp))\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\n// echoServerHandler implements http.Handler.\ntype echoServerHandler struct {\n\tt *testing.T\n}\n\nfunc (s *echoServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(200)\n\ttime.Sleep(time.Millisecond * 100)\n\tif _, err := io.Copy(w, r.Body); err != nil {\n\t\ts.t.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc testInmemoryListenerHTTP(t *testing.T, f func(t *testing.T, client *http.Client)) {\n\tln := NewInmemoryListener()\n\tdefer ln.Close()\n\n\tclient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\treturn ln.Dial()\n\t\t\t},\n\t\t},\n\t\tTimeout: time.Second,\n\t}\n\n\tserver := &http.Server{\n\t\tHandler: &echoServerHandler{t: t},\n\t}\n\n\tgo func() {\n\t\tif err := server.Serve(ln); err != nil && err != http.ErrServerClosed {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tf(t, client)\n\n\tctx, cancel := context.WithTimeout(t.Context(), time.Millisecond*100)\n\tdefer cancel()\n\tserver.Shutdown(ctx) //nolint:errcheck\n}\n\nfunc testInmemoryListenerHTTPSingle(t *testing.T, client *http.Client, content string) {\n\tres, err := client.Post(\"http://...\", \"text/plain\", bytes.NewBufferString(content))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer func() { _ = res.Body.Close() }()\n\tb, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\ts := string(b)\n\tif string(b) != content {\n\t\tt.Fatalf(\"unexpected response %q, expecting %q\", s, content)\n\t}\n}\n\nfunc TestInmemoryListenerHTTPSingle(t *testing.T) {\n\ttestInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {\n\t\ttestInmemoryListenerHTTPSingle(t, client, \"request\")\n\t})\n}\n\nfunc TestInmemoryListenerHTTPSerial(t *testing.T) {\n\ttestInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {\n\t\tfor i := range 10 {\n\t\t\ttestInmemoryListenerHTTPSingle(t, client, fmt.Sprintf(\"request_%d\", i))\n\t\t}\n\t})\n}\n\nfunc TestInmemoryListenerHTTPConcurrent(t *testing.T) {\n\ttestInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {\n\t\tvar wg sync.WaitGroup\n\t\tfor i := range 10 {\n\t\t\twg.Add(1)\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\ttestInmemoryListenerHTTPSingle(t, client, fmt.Sprintf(\"request_%d\", i))\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\t})\n}\n\nfunc acceptLoop(ln net.Listener) {\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tconn.Close()\n\t}\n}\n\nfunc TestInmemoryListenerAddrDefault(t *testing.T) {\n\tln := NewInmemoryListener()\n\n\tverifyAddr(t, ln.Addr(), inmemoryAddr(0))\n\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tc.Close()\n\t}()\n\n\tlc, err := ln.Accept()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tverifyAddr(t, lc.LocalAddr(), inmemoryAddr(0))\n\tverifyAddr(t, lc.RemoteAddr(), pipeAddr(0))\n\n\tgo acceptLoop(ln)\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tverifyAddr(t, c.LocalAddr(), pipeAddr(0))\n\tverifyAddr(t, c.RemoteAddr(), inmemoryAddr(0))\n}\n\nfunc verifyAddr(t *testing.T, got, expected net.Addr) {\n\tif got != expected {\n\t\tt.Fatalf(\"unexpected addr: %v. Expecting %v\", got, expected)\n\t}\n}\n\nfunc TestInmemoryListenerAddrCustom(t *testing.T) {\n\tln := NewInmemoryListener()\n\n\tlistenerAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 12345}\n\n\tln.SetLocalAddr(listenerAddr)\n\n\tverifyAddr(t, ln.Addr(), listenerAddr)\n\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tc.Close()\n\t}()\n\n\tlc, err := ln.Accept()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tverifyAddr(t, lc.LocalAddr(), listenerAddr)\n\tverifyAddr(t, lc.RemoteAddr(), pipeAddr(0))\n\n\tgo acceptLoop(ln)\n\n\tclientAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 65432}\n\n\tc, err := ln.DialWithLocalAddr(clientAddr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tverifyAddr(t, c.LocalAddr(), clientAddr)\n\tverifyAddr(t, c.RemoteAddr(), listenerAddr)\n}\n"
  },
  {
    "path": "fasthttputil/inmemory_listener_timing_test.go",
    "content": "package fasthttputil_test\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nvar (\n\tcertblock = []byte(`-----BEGIN CERTIFICATE-----\nMIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV\nBAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV\nMRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D\nK2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te\n+z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij\nL5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1\nxRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY\n6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG\nSIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98\nL3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2\n45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li\nK91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6\nX+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI\nwhdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd\n-----END CERTIFICATE-----\n`)\n\tkeyblock = []byte(`-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG\n3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U\nwq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0\nFlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf\nIJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg\nGeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF\nsh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2\nsNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D\nuGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb\nK2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3\nYqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+\nDVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk\nB0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV\nBggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x\nIzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY\nwqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj\nwZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D\nFICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m\ntiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX\nfQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU\nILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk\nK/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT\n6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt\n9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN\nCj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV\nc257YgaWmjK9uB0Y2r2VxS0G\n-----END PRIVATE KEY-----`)\n)\n\n// BenchmarkPlainStreaming measures end-to-end plaintext streaming performance\n// for fasthttp client and server.\n//\n// It issues http requests over a small number of keep-alive connections.\nfunc BenchmarkPlainStreaming(b *testing.B) {\n\tbenchmark(b, streamingHandler, false)\n}\n\n// BenchmarkPlainHandshake measures end-to-end plaintext handshake performance\n// for fasthttp client and server.\n//\n// It re-establishes new connection per each http request.\nfunc BenchmarkPlainHandshake(b *testing.B) {\n\tbenchmark(b, handshakeHandler, false)\n}\n\n// BenchmarkTLSStreaming measures end-to-end TLS streaming performance\n// for fasthttp client and server.\n//\n// It issues http requests over a small number of TLS keep-alive connections.\nfunc BenchmarkTLSStreaming(b *testing.B) {\n\tbenchmark(b, streamingHandler, true)\n}\n\nfunc benchmark(b *testing.B, h fasthttp.RequestHandler, isTLS bool) {\n\tvar serverTLSConfig, clientTLSConfig *tls.Config\n\tif isTLS {\n\t\tcert, err := tls.X509KeyPair(certblock, keyblock)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"cannot load TLS certificate: %v\", err)\n\t\t}\n\t\tserverTLSConfig = &tls.Config{\n\t\t\tCertificates:             []tls.Certificate{cert},\n\t\t\tPreferServerCipherSuites: true,\n\t\t}\n\t\tserverTLSConfig.CurvePreferences = []tls.CurveID{}\n\t\tclientTLSConfig = &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t}\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\tserverStopCh := make(chan struct{})\n\tgo func() {\n\t\tserverLn := net.Listener(ln)\n\t\tif serverTLSConfig != nil {\n\t\t\tserverLn = tls.NewListener(serverLn, serverTLSConfig)\n\t\t}\n\t\tif err := fasthttp.Serve(serverLn, h); err != nil {\n\t\t\tb.Errorf(\"unexpected error in server: %v\", err)\n\t\t}\n\t\tclose(serverStopCh)\n\t}()\n\tc := &fasthttp.HostClient{\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tIsTLS:     isTLS,\n\t\tTLSConfig: clientTLSConfig,\n\t}\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\trunRequests(b, pb, c, isTLS)\n\t})\n\tln.Close()\n\t<-serverStopCh\n}\n\nfunc streamingHandler(ctx *fasthttp.RequestCtx) {\n\tctx.WriteString(\"foobar\") //nolint:errcheck\n}\n\nfunc handshakeHandler(ctx *fasthttp.RequestCtx) {\n\tstreamingHandler(ctx)\n\n\t// Explicitly close connection after each response.\n\tctx.SetConnectionClose()\n}\n\nfunc runRequests(b *testing.B, pb *testing.PB, c *fasthttp.HostClient, isTLS bool) {\n\tvar req fasthttp.Request\n\tif isTLS {\n\t\treq.SetRequestURI(\"https://foo.bar/baz\")\n\t} else {\n\t\treq.SetRequestURI(\"http://foo.bar/baz\")\n\t}\n\tvar resp fasthttp.Response\n\tfor pb.Next() {\n\t\tif err := c.Do(&req, &resp); err != nil {\n\t\t\tb.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != fasthttp.StatusOK {\n\t\t\tb.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), fasthttp.StatusOK)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "fasthttputil/pipeconns.go",
    "content": "package fasthttputil\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n)\n\n// NewPipeConns returns new bi-directional connection pipe.\n//\n// PipeConns is NOT safe for concurrent use by multiple goroutines!\nfunc NewPipeConns() *PipeConns {\n\tch1 := make(chan *byteBuffer, 4)\n\tch2 := make(chan *byteBuffer, 4)\n\n\tpc := &PipeConns{\n\t\tstopCh: make(chan struct{}),\n\t}\n\tpc.c1.rCh = ch1\n\tpc.c1.wCh = ch2\n\tpc.c2.rCh = ch2\n\tpc.c2.wCh = ch1\n\tpc.c1.pc = pc\n\tpc.c2.pc = pc\n\treturn pc\n}\n\n// PipeConns provides bi-directional connection pipe,\n// which use in-process memory as a transport.\n//\n// PipeConns must be created by calling NewPipeConns.\n//\n// PipeConns has the following additional features comparing to connections\n// returned from net.Pipe():\n//\n//   - It is faster.\n//   - It buffers Write calls, so there is no need to have concurrent goroutine\n//     calling Read in order to unblock each Write call.\n//   - It supports read and write deadlines.\n//\n// PipeConns is NOT safe for concurrent use by multiple goroutines!\ntype PipeConns struct {\n\tstopCh     chan struct{}\n\tc1         pipeConn\n\tc2         pipeConn\n\tstopChLock sync.Mutex\n}\n\n// SetAddresses sets the local and remote addresses for the connection.\nfunc (pc *PipeConns) SetAddresses(localAddr1, remoteAddr1, localAddr2, remoteAddr2 net.Addr) {\n\tpc.c1.addrLock.Lock()\n\tdefer pc.c1.addrLock.Unlock()\n\n\tpc.c2.addrLock.Lock()\n\tdefer pc.c2.addrLock.Unlock()\n\n\tpc.c1.localAddr = localAddr1\n\tpc.c1.remoteAddr = remoteAddr1\n\n\tpc.c2.localAddr = localAddr2\n\tpc.c2.remoteAddr = remoteAddr2\n}\n\n// Conn1 returns the first end of bi-directional pipe.\n//\n// Data written to Conn1 may be read from Conn2.\n// Data written to Conn2 may be read from Conn1.\nfunc (pc *PipeConns) Conn1() net.Conn {\n\treturn &pc.c1\n}\n\n// Conn2 returns the second end of bi-directional pipe.\n//\n// Data written to Conn2 may be read from Conn1.\n// Data written to Conn1 may be read from Conn2.\nfunc (pc *PipeConns) Conn2() net.Conn {\n\treturn &pc.c2\n}\n\n// Close closes pipe connections.\nfunc (pc *PipeConns) Close() error {\n\tpc.stopChLock.Lock()\n\tselect {\n\tcase <-pc.stopCh:\n\tdefault:\n\t\tclose(pc.stopCh)\n\t}\n\tpc.stopChLock.Unlock()\n\n\treturn nil\n}\n\ntype pipeConn struct {\n\tlocalAddr  net.Addr\n\tremoteAddr net.Addr\n\tb          *byteBuffer\n\n\trCh chan *byteBuffer\n\twCh chan *byteBuffer\n\tpc  *PipeConns\n\n\treadDeadlineTimer  *time.Timer\n\twriteDeadlineTimer *time.Timer\n\n\treadDeadlineCh  <-chan time.Time\n\twriteDeadlineCh <-chan time.Time\n\n\tbb []byte\n\n\taddrLock sync.RWMutex\n\n\treadDeadlineChLock sync.Mutex\n}\n\nfunc (c *pipeConn) Write(p []byte) (int, error) {\n\tb := acquireByteBuffer()\n\tb.b = append(b.b[:0], p...)\n\n\tselect {\n\tcase <-c.pc.stopCh:\n\t\treleaseByteBuffer(b)\n\t\treturn 0, ErrConnectionClosed\n\tdefault:\n\t}\n\n\tselect {\n\tcase c.wCh <- b:\n\tdefault:\n\t\tselect {\n\t\tcase c.wCh <- b:\n\t\tcase <-c.writeDeadlineCh:\n\t\t\tc.writeDeadlineCh = closedDeadlineCh\n\t\t\treturn 0, ErrTimeout\n\t\tcase <-c.pc.stopCh:\n\t\t\treleaseByteBuffer(b)\n\t\t\treturn 0, ErrConnectionClosed\n\t\t}\n\t}\n\n\treturn len(p), nil\n}\n\nfunc (c *pipeConn) WriteString(s string) (int, error) {\n\treturn c.Write(s2b(s))\n}\n\nfunc (c *pipeConn) Read(p []byte) (int, error) {\n\tmayBlock := true\n\tnn := 0\n\tfor len(p) > 0 {\n\t\tn, err := c.read(p, mayBlock)\n\t\tnn += n\n\t\tif err != nil {\n\t\t\tif !mayBlock && err == errWouldBlock {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn nn, err\n\t\t}\n\t\tp = p[n:]\n\t\tmayBlock = false\n\t}\n\n\treturn nn, nil\n}\n\nfunc (c *pipeConn) read(p []byte, mayBlock bool) (int, error) {\n\tif len(c.bb) == 0 {\n\t\tif err := c.readNextByteBuffer(mayBlock); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\tn := copy(p, c.bb)\n\tc.bb = c.bb[n:]\n\n\treturn n, nil\n}\n\nfunc (c *pipeConn) readNextByteBuffer(mayBlock bool) error {\n\treleaseByteBuffer(c.b)\n\tc.b = nil\n\n\tselect {\n\tcase c.b = <-c.rCh:\n\tdefault:\n\t\tif !mayBlock {\n\t\t\treturn errWouldBlock\n\t\t}\n\t\tc.readDeadlineChLock.Lock()\n\t\treadDeadlineCh := c.readDeadlineCh\n\t\tc.readDeadlineChLock.Unlock()\n\t\tselect {\n\t\tcase c.b = <-c.rCh:\n\t\tcase <-readDeadlineCh:\n\t\t\tc.readDeadlineChLock.Lock()\n\t\t\tc.readDeadlineCh = closedDeadlineCh\n\t\t\tc.readDeadlineChLock.Unlock()\n\t\t\t// rCh may contain data when deadline is reached.\n\t\t\t// Read the data before returning ErrTimeout.\n\t\t\tselect {\n\t\t\tcase c.b = <-c.rCh:\n\t\t\tdefault:\n\t\t\t\treturn ErrTimeout\n\t\t\t}\n\t\tcase <-c.pc.stopCh:\n\t\t\t// rCh may contain data when stopCh is closed.\n\t\t\t// Read the data before returning EOF.\n\t\t\tselect {\n\t\t\tcase c.b = <-c.rCh:\n\t\t\tdefault:\n\t\t\t\treturn io.EOF\n\t\t\t}\n\t\t}\n\t}\n\n\tc.bb = c.b.b\n\treturn nil\n}\n\nvar errWouldBlock = errors.New(\"would block\")\n\n// ErrConnectionClosed indicates that the underlying connection is closed. It could mean that the client has disconnected.\nvar ErrConnectionClosed = errors.New(\"connection closed\")\n\ntype timeoutError struct{}\n\nfunc (e *timeoutError) Error() string {\n\treturn \"timeout\"\n}\n\n// Timeout implements the Timeout method of the net.Error interface.\n// This allows for checks like:\n//\n//\tif x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {\nfunc (e *timeoutError) Timeout() bool {\n\treturn true\n}\n\n// ErrTimeout is returned from Read() or Write() on timeout.\nvar ErrTimeout = &timeoutError{}\n\nfunc (c *pipeConn) Close() error {\n\treturn c.pc.Close()\n}\n\nfunc (c *pipeConn) LocalAddr() net.Addr {\n\tc.addrLock.RLock()\n\tdefer c.addrLock.RUnlock()\n\n\tif c.localAddr != nil {\n\t\treturn c.localAddr\n\t}\n\n\treturn pipeAddr(0)\n}\n\nfunc (c *pipeConn) RemoteAddr() net.Addr {\n\tc.addrLock.RLock()\n\tdefer c.addrLock.RUnlock()\n\n\tif c.remoteAddr != nil {\n\t\treturn c.remoteAddr\n\t}\n\n\treturn pipeAddr(0)\n}\n\nfunc (c *pipeConn) SetDeadline(deadline time.Time) error {\n\tc.SetReadDeadline(deadline)  //nolint:errcheck\n\tc.SetWriteDeadline(deadline) //nolint:errcheck\n\treturn nil\n}\n\nfunc (c *pipeConn) SetReadDeadline(deadline time.Time) error {\n\tif c.readDeadlineTimer == nil {\n\t\tc.readDeadlineTimer = time.NewTimer(time.Hour)\n\t}\n\treadDeadlineCh := updateTimer(c.readDeadlineTimer, deadline)\n\tc.readDeadlineChLock.Lock()\n\tc.readDeadlineCh = readDeadlineCh\n\tc.readDeadlineChLock.Unlock()\n\treturn nil\n}\n\nfunc (c *pipeConn) SetWriteDeadline(deadline time.Time) error {\n\tif c.writeDeadlineTimer == nil {\n\t\tc.writeDeadlineTimer = time.NewTimer(time.Hour)\n\t}\n\tc.writeDeadlineCh = updateTimer(c.writeDeadlineTimer, deadline)\n\treturn nil\n}\n\nfunc updateTimer(t *time.Timer, deadline time.Time) <-chan time.Time {\n\tif !t.Stop() {\n\t\tselect {\n\t\tcase <-t.C:\n\t\tdefault:\n\t\t}\n\t}\n\tif deadline.IsZero() {\n\t\treturn nil\n\t}\n\td := time.Until(deadline)\n\tif d <= 0 {\n\t\treturn closedDeadlineCh\n\t}\n\tt.Reset(d)\n\treturn t.C\n}\n\nvar closedDeadlineCh = func() <-chan time.Time {\n\tch := make(chan time.Time)\n\tclose(ch)\n\treturn ch\n}()\n\ntype pipeAddr int\n\nfunc (pipeAddr) Network() string {\n\treturn \"pipe\"\n}\n\nfunc (pipeAddr) String() string {\n\treturn \"pipe\"\n}\n\ntype byteBuffer struct {\n\tb []byte\n}\n\nfunc acquireByteBuffer() *byteBuffer {\n\treturn byteBufferPool.Get().(*byteBuffer)\n}\n\nfunc releaseByteBuffer(b *byteBuffer) {\n\tif b != nil {\n\t\tbyteBufferPool.Put(b)\n\t}\n}\n\nvar byteBufferPool = &sync.Pool{\n\tNew: func() any {\n\t\treturn &byteBuffer{\n\t\t\tb: make([]byte, 1024),\n\t\t}\n\t},\n}\n"
  },
  {
    "path": "fasthttputil/pipeconns_test.go",
    "content": "package fasthttputil\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestPipeConnsWriteTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tpc := NewPipeConns()\n\tc1 := pc.Conn1()\n\n\tdeadline := time.Now().Add(time.Millisecond)\n\tif err := c1.SetWriteDeadline(deadline); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tdata := []byte(\"foobar\")\n\tfor {\n\t\t_, err := c1.Write(data)\n\t\tif err != nil {\n\t\t\tif err == ErrTimeout {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\n\tfor range 10 {\n\t\t_, err := c1.Write(data)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\t}\n\n\t// read the written data\n\tc2 := pc.Conn2()\n\tif err := c2.SetReadDeadline(time.Now().Add(10 * time.Millisecond)); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tfor {\n\t\t_, err := c2.Read(data)\n\t\tif err != nil {\n\t\t\tif err == ErrTimeout {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\n\tfor range 10 {\n\t\t_, err := c2.Read(data)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting error\")\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, ErrTimeout)\n\t\t}\n\t}\n}\n\nfunc TestPipeConnsPositiveReadTimeout(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipeConnsReadTimeout(t, time.Millisecond)\n}\n\nfunc TestPipeConnsNegativeReadTimeout(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipeConnsReadTimeout(t, -time.Second)\n}\n\nvar zeroTime time.Time\n\nfunc testPipeConnsReadTimeout(t *testing.T, timeout time.Duration) {\n\tpc := NewPipeConns()\n\tc1 := pc.Conn1()\n\n\tdeadline := time.Now().Add(timeout)\n\tif err := c1.SetReadDeadline(deadline); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar buf [1]byte\n\tfor i := range 10 {\n\t\t_, err := c1.Read(buf[:])\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting error on iteration %d\", i)\n\t\t}\n\t\tif err != ErrTimeout {\n\t\t\tt.Fatalf(\"unexpected error on iteration %d: %v. Expecting %v\", i, err, ErrTimeout)\n\t\t}\n\t}\n\n\t// disable deadline and send data from c2 to c1\n\tif err := c1.SetReadDeadline(zeroTime); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tdata := []byte(\"foobar\")\n\tc2 := pc.Conn2()\n\tif _, err := c2.Write(data); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdataBuf := make([]byte, len(data))\n\tif _, err := io.ReadFull(c1, dataBuf); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(data, dataBuf) {\n\t\tt.Fatalf(\"unexpected data received: %q. Expecting %q\", dataBuf, data)\n\t}\n}\n\nfunc TestPipeConnsCloseWhileReadWriteConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\ttestPipeConnsCloseWhileReadWriteSerial(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestPipeConnsCloseWhileReadWriteSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipeConnsCloseWhileReadWriteSerial(t)\n}\n\nfunc testPipeConnsCloseWhileReadWriteSerial(t *testing.T) {\n\tfor range 10 {\n\t\ttestPipeConnsCloseWhileReadWrite(t)\n\t}\n}\n\nfunc testPipeConnsCloseWhileReadWrite(t *testing.T) {\n\tpc := NewPipeConns()\n\tc1 := pc.Conn1()\n\tc2 := pc.Conn2()\n\n\treadCh := make(chan error)\n\tgo func() {\n\t\tvar err error\n\t\tif _, err = io.Copy(io.Discard, c1); err != nil {\n\t\t\tif err != ErrConnectionClosed {\n\t\t\t\terr = fmt.Errorf(\"unexpected error: %w\", err)\n\t\t\t} else {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t}\n\t\treadCh <- err\n\t}()\n\n\twriteCh := make(chan error)\n\tgo func() {\n\t\tvar err error\n\t\tfor {\n\t\t\tif _, err = c2.Write([]byte(\"foobar\")); err != nil {\n\t\t\t\tif err != ErrConnectionClosed {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected error: %w\", err)\n\t\t\t\t} else {\n\t\t\t\t\terr = nil\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twriteCh <- err\n\t}()\n\n\ttime.Sleep(10 * time.Millisecond)\n\tif err := c1.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := c2.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase err := <-readCh:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error in reader: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\tselect {\n\tcase err := <-writeCh:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error in writer: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestPipeConnsReadWriteSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipeConnsReadWriteSerial(t)\n}\n\nfunc TestPipeConnsReadWriteConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ttestConcurrency(t, 10, testPipeConnsReadWriteSerial)\n}\n\nfunc testPipeConnsReadWriteSerial(t *testing.T) {\n\tpc := NewPipeConns()\n\ttestPipeConnsReadWrite(t, pc.Conn1(), pc.Conn2())\n\n\tpc = NewPipeConns()\n\ttestPipeConnsReadWrite(t, pc.Conn2(), pc.Conn1())\n}\n\nfunc testPipeConnsReadWrite(t *testing.T, c1, c2 net.Conn) {\n\tdefer c1.Close()\n\tdefer c2.Close()\n\n\tvar buf [32]byte\n\tfor i := range 10 {\n\t\t// The first write\n\t\ts1 := fmt.Sprintf(\"foo_%d\", i)\n\t\tn, err := c1.Write([]byte(s1))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != len(s1) {\n\t\t\tt.Fatalf(\"unexpected number of bytes written: %d. Expecting %d\", n, len(s1))\n\t\t}\n\n\t\t// The second write\n\t\ts2 := fmt.Sprintf(\"bar_%d\", i)\n\t\tn, err = c1.Write([]byte(s2))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != len(s2) {\n\t\t\tt.Fatalf(\"unexpected number of bytes written: %d. Expecting %d\", n, len(s2))\n\t\t}\n\n\t\t// Read data written above in two writes\n\t\ts := s1 + s2\n\t\tn, err = c2.Read(buf[:])\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != len(s) {\n\t\t\tt.Fatalf(\"unexpected number of bytes read: %d. Expecting %d\", n, len(s))\n\t\t}\n\t\tif string(buf[:n]) != s {\n\t\t\tt.Fatalf(\"unexpected string read: %q. Expecting %q\", buf[:n], s)\n\t\t}\n\t}\n}\n\nfunc TestPipeConnsCloseSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestPipeConnsCloseSerial(t)\n}\n\nfunc TestPipeConnsCloseConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\ttestConcurrency(t, 10, testPipeConnsCloseSerial)\n}\n\nfunc testPipeConnsCloseSerial(t *testing.T) {\n\tpc := NewPipeConns()\n\ttestPipeConnsClose(t, pc.Conn1(), pc.Conn2())\n\n\tpc = NewPipeConns()\n\ttestPipeConnsClose(t, pc.Conn2(), pc.Conn1())\n}\n\nfunc testPipeConnsClose(t *testing.T, c1, c2 net.Conn) {\n\tif err := c1.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tvar buf [10]byte\n\n\t// attempt writing to closed conn\n\tfor range 10 {\n\t\tn, err := c1.Write(buf[:])\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting error\")\n\t\t}\n\t\tif n != 0 {\n\t\t\tt.Fatalf(\"unexpected number of bytes written: %d. Expecting 0\", n)\n\t\t}\n\t}\n\n\t// attempt reading from closed conn\n\tfor range 10 {\n\t\tn, err := c2.Read(buf[:])\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expecting error\")\n\t\t}\n\t\tif err != io.EOF {\n\t\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t\t}\n\t\tif n != 0 {\n\t\t\tt.Fatalf(\"unexpected number of bytes read: %d. Expecting 0\", n)\n\t\t}\n\t}\n\n\tif err := c2.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// attempt closing already closed conns\n\tfor range 10 {\n\t\tif err := c1.Close(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif err := c2.Close(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n}\n\nfunc testConcurrency(t *testing.T, concurrency int, f func(*testing.T)) {\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tf(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestPipeConnsAddrDefault(t *testing.T) {\n\tt.Parallel()\n\n\tpc := NewPipeConns()\n\tc1 := pc.Conn1()\n\n\tif c1.LocalAddr() != pipeAddr(0) {\n\t\tt.Fatalf(\"unexpected local address: %v\", c1.LocalAddr())\n\t}\n\n\tif c1.RemoteAddr() != pipeAddr(0) {\n\t\tt.Fatalf(\"unexpected remote address: %v\", c1.RemoteAddr())\n\t}\n}\n\nfunc TestPipeConnsAddrCustom(t *testing.T) {\n\tt.Parallel()\n\n\tpc := NewPipeConns()\n\n\taddr1 := &net.TCPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 1234}\n\taddr2 := &net.TCPAddr{IP: net.IPv4(5, 6, 7, 8), Port: 5678}\n\taddr3 := &net.TCPAddr{IP: net.IPv4(9, 10, 11, 12), Port: 9012}\n\taddr4 := &net.TCPAddr{IP: net.IPv4(13, 14, 15, 16), Port: 3456}\n\n\tpc.SetAddresses(addr1, addr2, addr3, addr4)\n\n\tc1 := pc.Conn1()\n\n\tif c1.LocalAddr() != addr1 {\n\t\tt.Fatalf(\"unexpected local address: %v\", c1.LocalAddr())\n\t}\n\n\tif c1.RemoteAddr() != addr2 {\n\t\tt.Fatalf(\"unexpected remote address: %v\", c1.RemoteAddr())\n\t}\n\n\tc2 := pc.Conn1()\n\n\tif c2.LocalAddr() != addr1 {\n\t\tt.Fatalf(\"unexpected local address: %v\", c2.LocalAddr())\n\t}\n\n\tif c2.RemoteAddr() != addr2 {\n\t\tt.Fatalf(\"unexpected remote address: %v\", c2.RemoteAddr())\n\t}\n}\n"
  },
  {
    "path": "fasthttputil/s2b.go",
    "content": "package fasthttputil\n\nimport \"unsafe\"\n\n// s2b converts string to a byte slice without memory allocation.\nfunc s2b(s string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(s), len(s))\n}\n"
  },
  {
    "path": "fs.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html\"\n\t\"io\"\n\t\"io/fs\"\n\t\"maps\"\n\t\"mime\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/andybalholm/brotli\"\n\t\"github.com/klauspost/compress/gzip\"\n\t\"github.com/klauspost/compress/zstd\"\n\t\"github.com/valyala/bytebufferpool\"\n)\n\n// ServeFileBytesUncompressed returns HTTP response containing file contents\n// from the given path.\n//\n// Directory contents is returned if path points to directory.\n//\n// ServeFileBytes may be used for saving network traffic when serving files\n// with good compression ratio.\n//\n// See also RequestCtx.SendFileBytes.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc ServeFileBytesUncompressed(ctx *RequestCtx, path []byte) {\n\tServeFileUncompressed(ctx, b2s(path))\n}\n\n// ServeFileUncompressed returns HTTP response containing file contents\n// from the given path.\n//\n// Directory contents is returned if path points to directory.\n//\n// ServeFile may be used for saving network traffic when serving files\n// with good compression ratio.\n//\n// See also RequestCtx.SendFile.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc ServeFileUncompressed(ctx *RequestCtx, path string) {\n\tctx.Request.Header.DelBytes(strAcceptEncoding)\n\tServeFile(ctx, path)\n}\n\n// ServeFileBytes returns HTTP response containing compressed file contents\n// from the given path.\n//\n// HTTP response may contain uncompressed file contents in the following cases:\n//\n//   - Missing 'Accept-Encoding: gzip' request header.\n//   - No write access to directory containing the file.\n//\n// Directory contents is returned if path points to directory.\n//\n// Use ServeFileBytesUncompressed is you don't need serving compressed\n// file contents.\n//\n// See also RequestCtx.SendFileBytes.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc ServeFileBytes(ctx *RequestCtx, path []byte) {\n\tServeFile(ctx, b2s(path))\n}\n\n// ServeFile returns HTTP response containing compressed file contents\n// from the given path.\n//\n// HTTP response may contain uncompressed file contents in the following cases:\n//\n//   - Missing 'Accept-Encoding: gzip' request header.\n//   - No write access to directory containing the file.\n//\n// Directory contents is returned if path points to directory.\n//\n// Use ServeFileUncompressed is you don't need serving compressed file contents.\n//\n// See also RequestCtx.SendFile.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc ServeFile(ctx *RequestCtx, path string) {\n\trootFSOnce.Do(func() {\n\t\trootFSHandler = rootFS.NewRequestHandler()\n\t})\n\n\tif path == \"\" || !filepath.IsAbs(path) {\n\t\t// extend relative path to absolute path\n\t\thasTrailingSlash := path != \"\" && (path[len(path)-1] == '/' || path[len(path)-1] == '\\\\')\n\n\t\tvar err error\n\t\tpath = filepath.FromSlash(path)\n\t\tif path, err = filepath.Abs(path); err != nil {\n\t\t\tctx.Logger().Printf(\"cannot resolve path %q to absolute file path: %v\", path, err)\n\t\t\tctx.Error(\"Internal Server Error\", StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tif hasTrailingSlash {\n\t\t\tpath += \"/\"\n\t\t}\n\t}\n\n\t// convert the path to forward slashes regardless the OS in order to set the URI properly\n\t// the handler will convert back to OS path separator before opening the file\n\tpath = filepath.ToSlash(path)\n\n\tctx.Request.SetRequestURI(path)\n\trootFSHandler(ctx)\n}\n\nvar (\n\trootFSOnce sync.Once\n\trootFS     = &FS{\n\t\tRoot:               \"\",\n\t\tAllowEmptyRoot:     true,\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tAcceptByteRange:    true,\n\t}\n\trootFSHandler RequestHandler\n)\n\n// ServeFS returns HTTP response containing compressed file contents from the given fs.FS's path.\n//\n// HTTP response may contain uncompressed file contents in the following cases:\n//\n//   - Missing 'Accept-Encoding: gzip' request header.\n//   - No write access to directory containing the file.\n//\n// Directory contents is returned if path points to directory.\n//\n// See also ServeFile.\nfunc ServeFS(ctx *RequestCtx, filesystem fs.FS, path string) {\n\tf := &FS{\n\t\tFS:                 filesystem,\n\t\tRoot:               \"\",\n\t\tAllowEmptyRoot:     true,\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tAcceptByteRange:    true,\n\t}\n\thandler := f.NewRequestHandler()\n\n\tctx.Request.SetRequestURI(path)\n\thandler(ctx)\n}\n\n// PathRewriteFunc must return new request path based on arbitrary ctx\n// info such as ctx.Path().\n//\n// Path rewriter is used in FS for translating the current request\n// to the local filesystem path relative to FS.Root.\n//\n// The returned path must not contain '/../' substrings due to security reasons,\n// since such paths may refer files outside FS.Root.\n//\n// The returned path may refer to ctx members. For example, ctx.Path().\ntype PathRewriteFunc func(ctx *RequestCtx) []byte\n\n// NewVHostPathRewriter returns path rewriter, which strips slashesCount\n// leading slashes from the path and prepends the path with request's host,\n// thus simplifying virtual hosting for static files.\n//\n// Examples:\n//\n//   - host=foobar.com, slashesCount=0, original path=\"/foo/bar\".\n//     Resulting path: \"/foobar.com/foo/bar\"\n//\n//   - host=img.aaa.com, slashesCount=1, original path=\"/images/123/456.jpg\"\n//     Resulting path: \"/img.aaa.com/123/456.jpg\"\nfunc NewVHostPathRewriter(slashesCount int) PathRewriteFunc {\n\treturn func(ctx *RequestCtx) []byte {\n\t\tpath := stripLeadingSlashes(ctx.Path(), slashesCount)\n\t\thost := ctx.Host()\n\t\tif n := bytes.IndexByte(host, '/'); n >= 0 {\n\t\t\thost = nil\n\t\t}\n\t\tif len(host) == 0 {\n\t\t\thost = strInvalidHost\n\t\t}\n\t\tb := bytebufferpool.Get()\n\t\tb.B = append(b.B, '/')\n\t\tb.B = append(b.B, host...)\n\t\tb.B = append(b.B, path...)\n\t\tctx.URI().SetPathBytes(b.B)\n\t\tbytebufferpool.Put(b)\n\n\t\treturn ctx.Path()\n\t}\n}\n\nvar strInvalidHost = []byte(\"invalid-host\")\n\n// NewPathSlashesStripper returns path rewriter, which strips slashesCount\n// leading slashes from the path.\n//\n// Examples:\n//\n//   - slashesCount = 0, original path: \"/foo/bar\", result: \"/foo/bar\"\n//   - slashesCount = 1, original path: \"/foo/bar\", result: \"/bar\"\n//   - slashesCount = 2, original path: \"/foo/bar\", result: \"\"\n//\n// The returned path rewriter may be used as FS.PathRewrite .\nfunc NewPathSlashesStripper(slashesCount int) PathRewriteFunc {\n\treturn func(ctx *RequestCtx) []byte {\n\t\treturn stripLeadingSlashes(ctx.Path(), slashesCount)\n\t}\n}\n\n// NewPathPrefixStripper returns path rewriter, which removes prefixSize bytes\n// from the path prefix.\n//\n// Examples:\n//\n//   - prefixSize = 0, original path: \"/foo/bar\", result: \"/foo/bar\"\n//   - prefixSize = 3, original path: \"/foo/bar\", result: \"o/bar\"\n//   - prefixSize = 7, original path: \"/foo/bar\", result: \"r\"\n//\n// The returned path rewriter may be used as FS.PathRewrite .\nfunc NewPathPrefixStripper(prefixSize int) PathRewriteFunc {\n\treturn func(ctx *RequestCtx) []byte {\n\t\tpath := ctx.Path()\n\t\tif len(path) >= prefixSize {\n\t\t\tpath = path[prefixSize:]\n\t\t}\n\t\treturn path\n\t}\n}\n\n// FS represents settings for request handler serving static files\n// from the local filesystem.\n//\n// It is prohibited copying FS values. Create new values instead.\ntype FS struct {\n\tnoCopy noCopy\n\n\t// FS is filesystem to serve files from. eg: embed.FS os.DirFS\n\tFS fs.FS\n\n\t// Path rewriting function.\n\t//\n\t// By default request path is not modified.\n\tPathRewrite PathRewriteFunc\n\n\t// PathNotFound fires when file is not found in filesystem\n\t// this functions tries to replace \"Cannot open requested path\"\n\t// server response giving to the programmer the control of server flow.\n\t//\n\t// By default PathNotFound returns\n\t// \"Cannot open requested path\"\n\tPathNotFound RequestHandler\n\n\t// Suffixes list to add to compressedFileSuffix depending on encoding\n\t//\n\t// This value has sense only if Compress is set.\n\t//\n\t// FSCompressedFileSuffixes is used by default.\n\tCompressedFileSuffixes map[string]string\n\n\t// If CleanStop is set, the channel can be closed to stop the cleanup handlers\n\t// for the FS RequestHandlers created with NewRequestHandler.\n\t// NEVER close this channel while the handler is still being used!\n\tCleanStop chan struct{}\n\n\th RequestHandler\n\n\t// Path to the root directory to serve files from.\n\tRoot string\n\n\t// Path to the compressed root directory to serve files from. If this value\n\t// is empty, Root is used.\n\tCompressRoot string\n\n\t// Suffix to add to the name of cached compressed file.\n\t//\n\t// This value has sense only if Compress is set.\n\t//\n\t// FSCompressedFileSuffix is used by default.\n\tCompressedFileSuffix string\n\n\t// List of index file names to try opening during directory access.\n\t//\n\t// For example:\n\t//\n\t//     * index.html\n\t//     * index.htm\n\t//     * my-super-index.xml\n\t//\n\t// By default the list is empty.\n\tIndexNames []string\n\n\t// Expiration duration for inactive file handlers.\n\t//\n\t// FSHandlerCacheDuration is used by default.\n\tCacheDuration time.Duration\n\n\tonce sync.Once\n\n\t// AllowEmptyRoot controls what happens when Root is empty. When false (default) it will default to the\n\t// current working directory. An empty root is mostly useful when you want to use absolute paths\n\t// on windows that are on different filesystems. On linux setting your Root to \"/\" already allows you to use\n\t// absolute paths on any filesystem.\n\tAllowEmptyRoot bool\n\n\t// Uses brotli encoding and fallbacks to zstd or gzip in responses if set to true, uses zstd or gzip if set to false.\n\t//\n\t// This value has sense only if Compress is set.\n\t//\n\t// Brotli encoding is disabled by default.\n\tCompressBrotli bool\n\n\t// Uses zstd encoding and fallbacks to gzip in responses if set to true, uses gzip if set to false.\n\t//\n\t// This value has sense only if Compress is set.\n\t//\n\t// zstd encoding is disabled by default.\n\tCompressZstd bool\n\n\t// Index pages for directories without files matching IndexNames\n\t// are automatically generated if set.\n\t//\n\t// Directory index generation may be quite slow for directories\n\t// with many files (more than 1K), so it is discouraged enabling\n\t// index pages' generation for such directories.\n\t//\n\t// By default index pages aren't generated.\n\tGenerateIndexPages bool\n\n\t// Transparently compresses responses if set to true.\n\t//\n\t// The server tries minimizing CPU usage by caching compressed files.\n\t// It adds CompressedFileSuffix suffix to the original file name and\n\t// tries saving the resulting compressed file under the new file name.\n\t// So it is advisable to give the server write access to Root\n\t// and to all inner folders in order to minimize CPU usage when serving\n\t// compressed responses.\n\t//\n\t// Transparent compression is disabled by default.\n\tCompress bool\n\n\t// Enables byte range requests if set to true.\n\t//\n\t// Byte range requests are disabled by default.\n\tAcceptByteRange bool\n\n\t// SkipCache if true, will cache no file handler.\n\t//\n\t// By default is false.\n\tSkipCache bool\n}\n\n// FSCompressedFileSuffix is the suffix FS adds to the original file names\n// when trying to store compressed file under the new file name.\n// See FS.Compress for details.\nconst FSCompressedFileSuffix = \".fasthttp.gz\"\n\n// FSCompressedFileSuffixes is the suffixes FS adds to the original file names depending on encoding\n// when trying to store compressed file under the new file name.\n// See FS.Compress for details.\nvar FSCompressedFileSuffixes = map[string]string{\n\t\"gzip\": \".fasthttp.gz\",\n\t\"br\":   \".fasthttp.br\",\n\t\"zstd\": \".fasthttp.zst\",\n}\n\n// FSHandlerCacheDuration is the default expiration duration for inactive\n// file handlers opened by FS.\nconst FSHandlerCacheDuration = 10 * time.Second\n\n// FSHandler returns request handler serving static files from\n// the given root folder.\n//\n// stripSlashes indicates how many leading slashes must be stripped\n// from requested path before searching requested file in the root folder.\n// Examples:\n//\n//   - stripSlashes = 0, original path: \"/foo/bar\", result: \"/foo/bar\"\n//   - stripSlashes = 1, original path: \"/foo/bar\", result: \"/bar\"\n//   - stripSlashes = 2, original path: \"/foo/bar\", result: \"\"\n//\n// The returned request handler automatically generates index pages\n// for directories without index.html.\n//\n// The returned handler caches requested file handles\n// for FSHandlerCacheDuration.\n// Make sure your program has enough 'max open files' limit aka\n// 'ulimit -n' if root folder contains many files.\n//\n// Do not create multiple request handler instances for the same\n// (root, stripSlashes) arguments - just reuse a single instance.\n// Otherwise goroutine leak will occur.\nfunc FSHandler(root string, stripSlashes int) RequestHandler {\n\tfs := &FS{\n\t\tRoot:               root,\n\t\tIndexNames:         []string{\"index.html\"},\n\t\tGenerateIndexPages: true,\n\t\tAcceptByteRange:    true,\n\t}\n\tif stripSlashes > 0 {\n\t\tfs.PathRewrite = NewPathSlashesStripper(stripSlashes)\n\t}\n\treturn fs.NewRequestHandler()\n}\n\n// NewRequestHandler returns new request handler with the given FS settings.\n//\n// The returned handler caches requested file handles\n// for FS.CacheDuration.\n// Make sure your program has enough 'max open files' limit aka\n// 'ulimit -n' if FS.Root folder contains many files.\n//\n// Do not create multiple request handlers from a single FS instance -\n// just reuse a single request handler.\nfunc (fs *FS) NewRequestHandler() RequestHandler {\n\tfs.once.Do(fs.initRequestHandler)\n\treturn fs.h\n}\n\nfunc (fs *FS) normalizeRoot(root string) string {\n\t// fs.FS uses relative paths, that paths are slash-separated on all systems, even Windows.\n\tif fs.FS == nil {\n\t\t// Serve files from the current working directory if Root is empty or if Root is a relative path.\n\t\tif (!fs.AllowEmptyRoot && root == \"\") || (root != \"\" && !filepath.IsAbs(root)) {\n\t\t\tpath, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tpath = \".\"\n\t\t\t}\n\t\t\troot = path + \"/\" + root\n\t\t}\n\n\t\t// convert the root directory slashes to the native format\n\t\troot = filepath.FromSlash(root)\n\t} else {\n\t\tif root == \"\" {\n\t\t\treturn root\n\t\t}\n\n\t\t// Normalize fs.FS roots to slash-separated relative paths.\n\t\troot = strings.ReplaceAll(root, \"\\\\\", \"/\")\n\t\troot = strings.TrimLeft(root, \"/\")\n\t\tif root == \"\" {\n\t\t\treturn root\n\t\t}\n\t\troot = path.Clean(root)\n\t\tif root == \".\" {\n\t\t\treturn \".\"\n\t\t}\n\t\treturn root\n\t}\n\n\t// strip trailing slashes from the root path\n\tfor root != \"\" && root[len(root)-1] == os.PathSeparator {\n\t\troot = root[:len(root)-1]\n\t}\n\treturn root\n}\n\nfunc (fs *FS) initRequestHandler() {\n\troot := fs.normalizeRoot(fs.Root)\n\n\tcompressRoot := fs.CompressRoot\n\tif compressRoot == \"\" {\n\t\tcompressRoot = root\n\t} else {\n\t\tcompressRoot = fs.normalizeRoot(compressRoot)\n\t}\n\n\tcompressedFileSuffixes := fs.CompressedFileSuffixes\n\tif compressedFileSuffixes[\"br\"] == \"\" || compressedFileSuffixes[\"gzip\"] == \"\" ||\n\t\tcompressedFileSuffixes[\"zstd\"] == \"\" || compressedFileSuffixes[\"br\"] == compressedFileSuffixes[\"gzip\"] ||\n\t\tcompressedFileSuffixes[\"br\"] == compressedFileSuffixes[\"zstd\"] ||\n\t\tcompressedFileSuffixes[\"gzip\"] == compressedFileSuffixes[\"zstd\"] {\n\t\t// Copy global map\n\t\tcompressedFileSuffixes = make(map[string]string, len(FSCompressedFileSuffixes))\n\t\tmaps.Copy(compressedFileSuffixes, FSCompressedFileSuffixes)\n\t}\n\n\tif fs.CompressedFileSuffix != \"\" {\n\t\tcompressedFileSuffixes[\"gzip\"] = fs.CompressedFileSuffix\n\t\tcompressedFileSuffixes[\"br\"] = FSCompressedFileSuffixes[\"br\"]\n\t\tcompressedFileSuffixes[\"zstd\"] = FSCompressedFileSuffixes[\"zstd\"]\n\t}\n\n\th := &fsHandler{\n\t\tfilesystem:             fs.FS,\n\t\troot:                   root,\n\t\tindexNames:             fs.IndexNames,\n\t\tpathRewrite:            fs.PathRewrite,\n\t\tgenerateIndexPages:     fs.GenerateIndexPages,\n\t\tcompress:               fs.Compress,\n\t\tcompressBrotli:         fs.CompressBrotli,\n\t\tcompressZstd:           fs.CompressZstd,\n\t\tcompressRoot:           compressRoot,\n\t\tpathNotFound:           fs.PathNotFound,\n\t\tacceptByteRange:        fs.AcceptByteRange,\n\t\tcompressedFileSuffixes: compressedFileSuffixes,\n\t}\n\n\th.cacheManager = newCacheManager(fs)\n\n\tif h.filesystem == nil {\n\t\th.filesystem = &osFS{} // It provides os.Open and os.Stat\n\t}\n\n\tfs.h = h.handleRequest\n}\n\ntype fsHandler struct {\n\tsmallFileReaderPool sync.Pool\n\tfilesystem          fs.FS\n\n\tcacheManager cacheManager\n\n\tpathRewrite            PathRewriteFunc\n\tpathNotFound           RequestHandler\n\tcompressedFileSuffixes map[string]string\n\n\troot               string\n\tcompressRoot       string\n\tindexNames         []string\n\tgenerateIndexPages bool\n\tcompress           bool\n\tcompressBrotli     bool\n\tcompressZstd       bool\n\tacceptByteRange    bool\n}\n\ntype fsFile struct {\n\tlastModified time.Time\n\n\tt               time.Time\n\tf               fs.File\n\th               *fsHandler\n\tfilename        string // fs.FileInfo.Name() return filename, isn't filepath.\n\tcontentType     string\n\tdirIndex        []byte\n\tlastModifiedStr []byte\n\n\tbigFiles      []*bigFileReader\n\tcontentLength int\n\treadersCount  int\n\n\tbigFilesLock sync.Mutex\n\tcompressed   bool\n}\n\nfunc (ff *fsFile) NewReader() (io.Reader, error) {\n\tif ff.isBig() {\n\t\treturn ff.bigFileReader()\n\t}\n\treturn ff.smallFileReader(), nil\n}\n\nfunc (ff *fsFile) smallFileReader() io.Reader {\n\tv := ff.h.smallFileReaderPool.Get()\n\tif v == nil {\n\t\tv = &fsSmallFileReader{}\n\t}\n\tr := v.(*fsSmallFileReader)\n\tr.ff = ff\n\tr.endPos = ff.contentLength\n\tif r.startPos > 0 {\n\t\tpanic(\"bug: fsSmallFileReader with non-nil startPos found in the pool\")\n\t}\n\treturn r\n}\n\n// Files bigger than this size are sent with sendfile.\nconst maxSmallFileSize = 2 * 4096\n\nfunc (ff *fsFile) isBig() bool {\n\tif _, ok := ff.h.filesystem.(*osFS); !ok { // fs.FS only uses bigFileReader, memory cache uses fsSmallFileReader\n\t\treturn ff.f != nil\n\t}\n\treturn ff.contentLength > maxSmallFileSize && len(ff.dirIndex) == 0\n}\n\nfunc (ff *fsFile) bigFileReader() (io.Reader, error) {\n\tif ff.f == nil {\n\t\treturn nil, errors.New(\"bug: ff.f must be non-nil in bigFileReader\")\n\t}\n\n\tvar r io.Reader\n\n\tff.bigFilesLock.Lock()\n\tn := len(ff.bigFiles)\n\tif n > 0 {\n\t\tr = ff.bigFiles[n-1]\n\t\tff.bigFiles = ff.bigFiles[:n-1]\n\t}\n\tff.bigFilesLock.Unlock()\n\n\tif r != nil {\n\t\treturn r, nil\n\t}\n\n\tf, err := ff.h.filesystem.Open(ff.filename)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot open already opened file: %w\", err)\n\t}\n\treturn &bigFileReader{\n\t\tf:  f,\n\t\tff: ff,\n\t\tr:  f,\n\t}, nil\n}\n\nfunc (ff *fsFile) Release() {\n\tif ff.f != nil {\n\t\t_ = ff.f.Close()\n\n\t\tif ff.isBig() {\n\t\t\tff.bigFilesLock.Lock()\n\t\t\tfor _, r := range ff.bigFiles {\n\t\t\t\t_ = r.f.Close()\n\t\t\t}\n\t\t\tff.bigFilesLock.Unlock()\n\t\t}\n\t}\n}\n\nfunc (ff *fsFile) decReadersCount() {\n\tff.h.cacheManager.Lock()\n\tff.readersCount--\n\tif ff.readersCount < 0 {\n\t\tpanic(\"bug: fsFile.readersCount < 0\")\n\t}\n\tff.h.cacheManager.Unlock()\n}\n\n// bigFileReader attempts to trigger sendfile\n// for sending big files over the wire.\ntype bigFileReader struct {\n\tf  fs.File\n\tff *fsFile\n\tr  io.Reader\n\tlr io.LimitedReader\n}\n\nfunc (r *bigFileReader) UpdateByteRange(startPos, endPos int) error {\n\tseeker, ok := r.f.(io.Seeker)\n\tif !ok {\n\t\treturn errors.New(\"must implement io.Seeker\")\n\t}\n\tif _, err := seeker.Seek(int64(startPos), io.SeekStart); err != nil {\n\t\treturn err\n\t}\n\tr.r = &r.lr\n\tr.lr.R = r.f\n\tr.lr.N = int64(endPos - startPos + 1)\n\treturn nil\n}\n\nfunc (r *bigFileReader) Read(p []byte) (int, error) {\n\treturn r.r.Read(p)\n}\n\nfunc (r *bigFileReader) WriteTo(w io.Writer) (int64, error) {\n\tif rf, ok := w.(io.ReaderFrom); ok {\n\t\t// fast path. Send file must be triggered\n\t\treturn rf.ReadFrom(r.r)\n\t}\n\n\t// slow path\n\treturn copyZeroAlloc(w, r.r)\n}\n\nfunc (r *bigFileReader) Close() error {\n\tr.r = r.f\n\tseeker, ok := r.f.(io.Seeker)\n\tif !ok {\n\t\t_ = r.f.Close()\n\t\treturn errors.New(\"must implement io.Seeker\")\n\t}\n\tn, err := seeker.Seek(0, io.SeekStart)\n\tif err == nil {\n\t\tif n == 0 {\n\t\t\tff := r.ff\n\t\t\tff.bigFilesLock.Lock()\n\t\t\tff.bigFiles = append(ff.bigFiles, r)\n\t\t\tff.bigFilesLock.Unlock()\n\t\t} else {\n\t\t\t_ = r.f.Close()\n\t\t\terr = errors.New(\"bug: File.Seek(0, io.SeekStart) returned (non-zero, nil)\")\n\t\t}\n\t} else {\n\t\t_ = r.f.Close()\n\t}\n\tr.ff.decReadersCount()\n\treturn err\n}\n\ntype fsSmallFileReader struct {\n\tff       *fsFile\n\tstartPos int\n\tendPos   int\n}\n\nfunc (r *fsSmallFileReader) Close() error {\n\tff := r.ff\n\tff.decReadersCount()\n\tr.ff = nil\n\tr.startPos = 0\n\tr.endPos = 0\n\tff.h.smallFileReaderPool.Put(r)\n\treturn nil\n}\n\nfunc (r *fsSmallFileReader) UpdateByteRange(startPos, endPos int) error {\n\tr.startPos = startPos\n\tr.endPos = endPos + 1\n\treturn nil\n}\n\nfunc (r *fsSmallFileReader) Read(p []byte) (int, error) {\n\ttailLen := r.endPos - r.startPos\n\tif tailLen <= 0 {\n\t\treturn 0, io.EOF\n\t}\n\tif len(p) > tailLen {\n\t\tp = p[:tailLen]\n\t}\n\n\tff := r.ff\n\tif ff.f != nil {\n\t\tra, ok := ff.f.(io.ReaderAt)\n\t\tif !ok {\n\t\t\treturn 0, errors.New(\"must implement io.ReaderAt\")\n\t\t}\n\t\tn, err := ra.ReadAt(p, int64(r.startPos))\n\t\tr.startPos += n\n\t\treturn n, err\n\t}\n\n\tn := copy(p, ff.dirIndex[r.startPos:])\n\tr.startPos += n\n\treturn n, nil\n}\n\nfunc (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {\n\tff := r.ff\n\n\tvar n int\n\tvar err error\n\tif ff.f == nil {\n\t\tn, err = w.Write(ff.dirIndex[r.startPos:r.endPos])\n\t\treturn int64(n), err\n\t}\n\n\tif rf, ok := w.(io.ReaderFrom); ok {\n\t\treturn rf.ReadFrom(r)\n\t}\n\n\tcurPos := r.startPos\n\tbufv := copyBufPool.Get()\n\tbuf := bufv.([]byte)\n\tfor err == nil {\n\t\ttailLen := r.endPos - curPos\n\t\tif tailLen <= 0 {\n\t\t\tbreak\n\t\t}\n\t\tif len(buf) > tailLen {\n\t\t\tbuf = buf[:tailLen]\n\t\t}\n\t\tra, ok := ff.f.(io.ReaderAt)\n\t\tif !ok {\n\t\t\treturn 0, errors.New(\"must implement io.ReaderAt\")\n\t\t}\n\t\tn, err = ra.ReadAt(buf, int64(curPos))\n\t\tnw, errw := w.Write(buf[:n])\n\t\tcurPos += nw\n\t\tif errw == nil && nw != n {\n\t\t\terrw = errors.New(\"bug: Write(p) returned (n, nil), where n != len(p)\")\n\t\t}\n\t\tif err == nil {\n\t\t\terr = errw\n\t\t}\n\t}\n\tcopyBufPool.Put(bufv)\n\n\tif err == io.EOF {\n\t\terr = nil\n\t}\n\treturn int64(curPos - r.startPos), err\n}\n\ntype cacheManager interface {\n\tLock()\n\tUnlock()\n\tGetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool)\n\tSetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile\n}\n\nvar (\n\t_ cacheManager = (*inMemoryCacheManager)(nil)\n\t_ cacheManager = (*noopCacheManager)(nil)\n)\n\ntype CacheKind uint8\n\nconst (\n\tdefaultCacheKind CacheKind = iota\n\tbrotliCacheKind\n\tgzipCacheKind\n\tzstdCacheKind\n)\n\nfunc newCacheManager(fs *FS) cacheManager {\n\tif fs.SkipCache {\n\t\treturn &noopCacheManager{}\n\t}\n\n\tcacheDuration := fs.CacheDuration\n\tif cacheDuration <= 0 {\n\t\tcacheDuration = FSHandlerCacheDuration\n\t}\n\n\tinstance := &inMemoryCacheManager{\n\t\tcacheDuration: cacheDuration,\n\t\tcache:         make(map[string]*fsFile),\n\t\tcacheBrotli:   make(map[string]*fsFile),\n\t\tcacheGzip:     make(map[string]*fsFile),\n\t\tcacheZstd:     make(map[string]*fsFile),\n\t}\n\n\tgo instance.handleCleanCache(fs.CleanStop)\n\n\treturn instance\n}\n\ntype noopCacheManager struct {\n\tcacheLock sync.Mutex\n}\n\nfunc (n *noopCacheManager) Lock() {\n\tn.cacheLock.Lock()\n}\n\nfunc (n *noopCacheManager) Unlock() {\n\tn.cacheLock.Unlock()\n}\n\nfunc (*noopCacheManager) GetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool) {\n\treturn nil, false\n}\n\nfunc (n *noopCacheManager) SetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile {\n\tn.cacheLock.Lock()\n\tff.readersCount++\n\tn.cacheLock.Unlock()\n\treturn ff\n}\n\ntype inMemoryCacheManager struct {\n\tcache         map[string]*fsFile\n\tcacheBrotli   map[string]*fsFile\n\tcacheGzip     map[string]*fsFile\n\tcacheZstd     map[string]*fsFile\n\tcacheDuration time.Duration\n\tcacheLock     sync.Mutex\n}\n\nfunc (cm *inMemoryCacheManager) Lock() {\n\tcm.cacheLock.Lock()\n}\n\nfunc (cm *inMemoryCacheManager) Unlock() {\n\tcm.cacheLock.Unlock()\n}\n\nfunc (cm *inMemoryCacheManager) getFsCache(cacheKind CacheKind) map[string]*fsFile {\n\tfileCache := cm.cache\n\tswitch cacheKind {\n\tcase brotliCacheKind:\n\t\tfileCache = cm.cacheBrotli\n\tcase gzipCacheKind:\n\t\tfileCache = cm.cacheGzip\n\tcase zstdCacheKind:\n\t\tfileCache = cm.cacheZstd\n\t}\n\n\treturn fileCache\n}\n\nfunc (cm *inMemoryCacheManager) GetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool) {\n\tfileCache := cm.getFsCache(cacheKind)\n\n\tcm.cacheLock.Lock()\n\tff, ok := fileCache[string(path)]\n\tif ok {\n\t\tff.readersCount++\n\t}\n\tcm.cacheLock.Unlock()\n\n\treturn ff, ok\n}\n\nfunc (cm *inMemoryCacheManager) SetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile {\n\tfileCache := cm.getFsCache(cacheKind)\n\n\tcm.cacheLock.Lock()\n\tff1, ok := fileCache[string(path)]\n\tif !ok {\n\t\tfileCache[string(path)] = ff\n\t\tff.readersCount++\n\t} else {\n\t\tff1.readersCount++\n\t}\n\tcm.cacheLock.Unlock()\n\n\tif ok {\n\t\t// The file has been already opened by another\n\t\t// goroutine, so close the current file and use\n\t\t// the file opened by another goroutine instead.\n\t\tff.Release()\n\t\tff = ff1\n\t}\n\n\treturn ff\n}\n\nfunc (cm *inMemoryCacheManager) handleCleanCache(cleanStop chan struct{}) {\n\tvar pendingFiles []*fsFile\n\n\tclean := func() {\n\t\tpendingFiles = cm.cleanCache(pendingFiles)\n\t}\n\n\tif cleanStop != nil {\n\t\tt := time.NewTicker(cm.cacheDuration / 2)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-t.C:\n\t\t\t\tclean()\n\t\t\tcase _, stillOpen := <-cleanStop:\n\t\t\t\t// Ignore values send on the channel, only stop when it is closed.\n\t\t\t\tif !stillOpen {\n\t\t\t\t\tt.Stop()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor {\n\t\ttime.Sleep(cm.cacheDuration / 2)\n\t\tclean()\n\t}\n}\n\nfunc (cm *inMemoryCacheManager) cleanCache(pendingFiles []*fsFile) []*fsFile {\n\tvar filesToRelease []*fsFile\n\n\tcm.cacheLock.Lock()\n\n\t// Close files which couldn't be closed before due to non-zero\n\t// readers count on the previous run.\n\tvar remainingFiles []*fsFile\n\tfor _, ff := range pendingFiles {\n\t\tif ff.readersCount > 0 {\n\t\t\tremainingFiles = append(remainingFiles, ff)\n\t\t} else {\n\t\t\tfilesToRelease = append(filesToRelease, ff)\n\t\t}\n\t}\n\tpendingFiles = remainingFiles\n\n\tpendingFiles, filesToRelease = cleanCacheNolock(cm.cache, pendingFiles, filesToRelease, cm.cacheDuration)\n\tpendingFiles, filesToRelease = cleanCacheNolock(cm.cacheBrotli, pendingFiles, filesToRelease, cm.cacheDuration)\n\tpendingFiles, filesToRelease = cleanCacheNolock(cm.cacheGzip, pendingFiles, filesToRelease, cm.cacheDuration)\n\tpendingFiles, filesToRelease = cleanCacheNolock(cm.cacheZstd, pendingFiles, filesToRelease, cm.cacheDuration)\n\n\tcm.cacheLock.Unlock()\n\n\tfor _, ff := range filesToRelease {\n\t\tff.Release()\n\t}\n\n\treturn pendingFiles\n}\n\nfunc cleanCacheNolock(\n\tcache map[string]*fsFile, pendingFiles, filesToRelease []*fsFile, cacheDuration time.Duration,\n) ([]*fsFile, []*fsFile) {\n\tt := time.Now()\n\tfor k, ff := range cache {\n\t\tif t.Sub(ff.t) > cacheDuration {\n\t\t\tif ff.readersCount > 0 {\n\t\t\t\t// There are pending readers on stale file handle,\n\t\t\t\t// so we cannot close it. Put it into pendingFiles\n\t\t\t\t// so it will be closed later.\n\t\t\t\tpendingFiles = append(pendingFiles, ff)\n\t\t\t} else {\n\t\t\t\tfilesToRelease = append(filesToRelease, ff)\n\t\t\t}\n\t\t\tdelete(cache, k)\n\t\t}\n\t}\n\treturn pendingFiles, filesToRelease\n}\n\nfunc (h *fsHandler) pathToFilePath(path []byte, hasTrailingSlash bool) string {\n\tif hasTrailingSlash {\n\t\tpath = path[:len(path)-1]\n\t}\n\thasLeadingSlash := len(path) > 0 && path[0] == '/'\n\n\tif _, ok := h.filesystem.(*osFS); !ok {\n\t\troot := h.root\n\t\tif root == \".\" {\n\t\t\troot = \"\"\n\t\t}\n\t\tif len(path) < 1 || (hasLeadingSlash && len(path) == 1) {\n\t\t\tif h.root == \".\" {\n\t\t\t\treturn \".\"\n\t\t\t}\n\t\t\treturn root\n\t\t}\n\n\t\tif root == \"\" {\n\t\t\tif hasLeadingSlash {\n\t\t\t\treturn string(path[1:])\n\t\t\t}\n\t\t\treturn string(path)\n\t\t}\n\n\t\t// Use byte buffer pool to avoid string concatenation allocations.\n\t\tb := bytebufferpool.Get()\n\t\tdefer bytebufferpool.Put(b)\n\n\t\tb.B = append(b.B, root...)\n\t\tb.B = append(b.B, '/')\n\t\tif hasLeadingSlash {\n\t\t\tb.B = append(b.B, path[1:]...)\n\t\t} else {\n\t\t\tb.B = append(b.B, path...)\n\t\t}\n\n\t\treturn string(b.B)\n\t}\n\n\t// Use byte buffer pool to avoid string concatenation allocations\n\tb := bytebufferpool.Get()\n\tdefer bytebufferpool.Put(b)\n\n\tb.B = append(b.B, h.root...)\n\tif hasLeadingSlash {\n\t\tb.B = append(b.B, path...)\n\t} else {\n\t\tif h.root != \"\" && len(path) > 0 {\n\t\t\tb.B = append(b.B, '/')\n\t\t}\n\t\tb.B = append(b.B, path...)\n\t}\n\n\treturn filepath.FromSlash(string(b.B))\n}\n\nfunc (h *fsHandler) filePathToCompressed(filePath string) string {\n\tif h.root == h.compressRoot {\n\t\treturn filePath\n\t}\n\tif !strings.HasPrefix(filePath, h.root) {\n\t\treturn filePath\n\t}\n\treturn filepath.FromSlash(h.compressRoot + filePath[len(h.root):])\n}\n\nfunc (h *fsHandler) handleRequest(ctx *RequestCtx) {\n\tvar path []byte\n\tif h.pathRewrite != nil {\n\t\tpath = h.pathRewrite(ctx)\n\t} else {\n\t\tpath = ctx.Path()\n\t}\n\thasTrailingSlash := len(path) > 0 && path[len(path)-1] == '/'\n\n\tif n := bytes.IndexByte(path, 0); n >= 0 {\n\t\tctx.Logger().Printf(\"cannot serve path with nil byte at position %d: %q\", n, path)\n\t\tctx.Error(\"Are you a hacker?\", StatusBadRequest)\n\t\treturn\n\t}\n\tif h.pathRewrite != nil {\n\t\t// There is no need to check for '/../' if path = ctx.Path(),\n\t\t// since ctx.Path must normalize and sanitize the path.\n\n\t\tif n := bytes.Index(path, strSlashDotDotSlash); n >= 0 {\n\t\t\tctx.Logger().Printf(\"cannot serve path with '/../' at position %d due to security reasons: %q\", n, path)\n\t\t\tctx.Error(\"Internal Server Error\", StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\tmustCompress := false\n\tfileCacheKind := defaultCacheKind\n\tfileEncoding := \"\"\n\tbyteRange := ctx.Request.Header.peek(strRange)\n\tif len(byteRange) == 0 && h.compress {\n\t\tswitch {\n\t\tcase h.compressBrotli && ctx.Request.Header.HasAcceptEncodingBytes(strBr):\n\t\t\tmustCompress = true\n\t\t\tfileCacheKind = brotliCacheKind\n\t\t\tfileEncoding = \"br\"\n\t\tcase h.compressZstd && ctx.Request.Header.HasAcceptEncodingBytes(strZstd):\n\t\t\tmustCompress = true\n\t\t\tfileCacheKind = zstdCacheKind\n\t\t\tfileEncoding = \"zstd\"\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strGzip):\n\t\t\tmustCompress = true\n\t\t\tfileCacheKind = gzipCacheKind\n\t\t\tfileEncoding = \"gzip\"\n\t\t}\n\t}\n\n\tff, ok := h.cacheManager.GetFileFromCache(fileCacheKind, path)\n\tif !ok {\n\t\tfilePath := h.pathToFilePath(path, hasTrailingSlash)\n\n\t\tvar err error\n\t\tff, err = h.openFSFile(filePath, mustCompress, fileEncoding)\n\t\tif mustCompress && err == errNoCreatePermission {\n\t\t\tctx.Logger().Printf(\"insufficient permissions for saving compressed file for %q. Serving uncompressed file. \"+\n\t\t\t\t\"Allow write access to the directory with this file in order to improve fasthttp performance\", filePath)\n\t\t\tmustCompress = false\n\t\t\tff, err = h.openFSFile(filePath, mustCompress, fileEncoding)\n\t\t}\n\n\t\tif errors.Is(err, errDirIndexRequired) {\n\t\t\tif !hasTrailingSlash {\n\t\t\t\tctx.RedirectBytes(append(path, '/'), StatusFound)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tff, err = h.openIndexFile(ctx, filePath, mustCompress, fileEncoding)\n\t\t\tif err != nil {\n\t\t\t\tctx.Logger().Printf(\"cannot open dir index %q: %v\", filePath, err)\n\t\t\t\tctx.Error(\"Directory index is forbidden\", StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\tctx.Logger().Printf(\"cannot open file %q: %v\", filePath, err)\n\t\t\tif h.pathNotFound == nil {\n\t\t\t\tctx.Error(\"Cannot open requested path\", StatusNotFound)\n\t\t\t} else {\n\t\t\t\tctx.SetStatusCode(StatusNotFound)\n\t\t\t\th.pathNotFound(ctx)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tff = h.cacheManager.SetFileToCache(fileCacheKind, path, ff)\n\t}\n\n\tif !ctx.IfModifiedSince(ff.lastModified) {\n\t\tff.decReadersCount()\n\t\tctx.NotModified()\n\t\treturn\n\t}\n\n\tr, err := ff.NewReader()\n\tif err != nil {\n\t\tff.decReadersCount()\n\t\tctx.Logger().Printf(\"cannot obtain file reader for path=%q: %v\", path, err)\n\t\tctx.Error(\"Internal Server Error\", StatusInternalServerError)\n\t\treturn\n\t}\n\n\thdr := &ctx.Response.Header\n\tif ff.compressed {\n\t\tswitch fileEncoding {\n\t\tcase \"br\":\n\t\t\thdr.SetContentEncodingBytes(strBr)\n\t\t\thdr.addVaryBytes(strAcceptEncoding)\n\t\tcase \"gzip\":\n\t\t\thdr.SetContentEncodingBytes(strGzip)\n\t\t\thdr.addVaryBytes(strAcceptEncoding)\n\t\tcase \"zstd\":\n\t\t\thdr.SetContentEncodingBytes(strZstd)\n\t\t\thdr.addVaryBytes(strAcceptEncoding)\n\t\t}\n\t}\n\n\tstatusCode := StatusOK\n\tcontentLength := ff.contentLength\n\tif h.acceptByteRange {\n\t\thdr.setNonSpecial(strAcceptRanges, strBytes)\n\t\tif len(byteRange) > 0 {\n\t\t\tstartPos, endPos, err := ParseByteRange(byteRange, contentLength)\n\t\t\tif err != nil {\n\t\t\t\t_ = r.(io.Closer).Close()\n\t\t\t\tctx.Logger().Printf(\"cannot parse byte range %q for path=%q: %v\", byteRange, path, err)\n\t\t\t\tctx.Error(\"Range Not Satisfiable\", StatusRequestedRangeNotSatisfiable)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err = r.(byteRangeUpdater).UpdateByteRange(startPos, endPos); err != nil {\n\t\t\t\t_ = r.(io.Closer).Close()\n\t\t\t\tctx.Logger().Printf(\"cannot seek byte range %q for path=%q: %v\", byteRange, path, err)\n\t\t\t\tctx.Error(\"Internal Server Error\", StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thdr.SetContentRange(startPos, endPos, contentLength)\n\t\t\tcontentLength = endPos - startPos + 1\n\t\t\tstatusCode = StatusPartialContent\n\t\t}\n\t}\n\n\thdr.setNonSpecial(strLastModified, ff.lastModifiedStr)\n\tif !ctx.IsHead() {\n\t\tctx.SetBodyStream(r, contentLength)\n\t} else {\n\t\tctx.Response.ResetBody()\n\t\tctx.Response.SkipBody = true\n\t\tctx.Response.Header.SetContentLength(contentLength)\n\t\tif rc, ok := r.(io.Closer); ok {\n\t\t\tif err := rc.Close(); err != nil {\n\t\t\t\tctx.Logger().Printf(\"cannot close file reader: %v\", err)\n\t\t\t\tctx.Error(\"Internal Server Error\", StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\thdr.noDefaultContentType = true\n\tif len(hdr.ContentType()) == 0 {\n\t\tctx.SetContentType(ff.contentType)\n\t}\n\tctx.SetStatusCode(statusCode)\n}\n\ntype byteRangeUpdater interface {\n\tUpdateByteRange(startPos, endPos int) error\n}\n\n// ParseByteRange parses 'Range: bytes=...' header value.\n//\n// It follows https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 .\nfunc ParseByteRange(byteRange []byte, contentLength int) (startPos, endPos int, err error) {\n\tb := byteRange\n\tif !bytes.HasPrefix(b, strBytes) {\n\t\treturn 0, 0, fmt.Errorf(\"unsupported range units: %q. Expecting %q\", byteRange, strBytes)\n\t}\n\n\tb = b[len(strBytes):]\n\tif len(b) == 0 || b[0] != '=' {\n\t\treturn 0, 0, fmt.Errorf(\"missing byte range in %q\", byteRange)\n\t}\n\tb = b[1:]\n\n\tn := bytes.IndexByte(b, '-')\n\tif n < 0 {\n\t\treturn 0, 0, fmt.Errorf(\"missing the end position of byte range in %q\", byteRange)\n\t}\n\n\tif n == 0 {\n\t\tv, err := ParseUint(b[n+1:])\n\t\tif err != nil {\n\t\t\treturn 0, 0, err\n\t\t}\n\t\tstartPos := max(contentLength-v, 0)\n\t\treturn startPos, contentLength - 1, nil\n\t}\n\n\tif startPos, err = ParseUint(b[:n]); err != nil {\n\t\treturn 0, 0, err\n\t}\n\tif startPos >= contentLength {\n\t\treturn 0, 0, fmt.Errorf(\"the start position of byte range cannot exceed %d. byte range %q\", contentLength-1, byteRange)\n\t}\n\n\tb = b[n+1:]\n\tif len(b) == 0 {\n\t\treturn startPos, contentLength - 1, nil\n\t}\n\n\tif endPos, err = ParseUint(b); err != nil {\n\t\treturn 0, 0, err\n\t}\n\tif endPos >= contentLength {\n\t\tendPos = contentLength - 1\n\t}\n\tif endPos < startPos {\n\t\treturn 0, 0, fmt.Errorf(\"the start position of byte range cannot exceed the end position. byte range %q\", byteRange)\n\t}\n\treturn startPos, endPos, nil\n}\n\nfunc (h *fsHandler) openIndexFile(ctx *RequestCtx, dirPath string, mustCompress bool, fileEncoding string) (*fsFile, error) {\n\tfor _, indexName := range h.indexNames {\n\t\tindexFilePath := indexName\n\t\tif dirPath != \"\" {\n\t\t\tindexFilePath = dirPath + \"/\" + indexName\n\t\t}\n\n\t\tff, err := h.openFSFile(indexFilePath, mustCompress, fileEncoding)\n\t\tif err == nil {\n\t\t\treturn ff, nil\n\t\t}\n\t\tif mustCompress && err == errNoCreatePermission {\n\t\t\tctx.Logger().Printf(\"insufficient permissions for saving compressed file for %q. Serving uncompressed file. \"+\n\t\t\t\t\"Allow write access to the directory with this file in order to improve fasthttp performance\", indexFilePath)\n\t\t\tmustCompress = false\n\t\t\treturn h.openFSFile(indexFilePath, mustCompress, fileEncoding)\n\t\t}\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn nil, fmt.Errorf(\"cannot open file %q: %w\", indexFilePath, err)\n\t\t}\n\t}\n\n\tif !h.generateIndexPages {\n\t\treturn nil, fmt.Errorf(\"cannot access directory without index page. Directory %q\", dirPath)\n\t}\n\n\treturn h.createDirIndex(ctx, dirPath, mustCompress, fileEncoding)\n}\n\nvar (\n\terrDirIndexRequired   = errors.New(\"directory index required\")\n\terrNoCreatePermission = errors.New(\"no 'create file' permissions\")\n)\n\nfunc (h *fsHandler) createDirIndex(ctx *RequestCtx, dirPath string, mustCompress bool, fileEncoding string) (*fsFile, error) {\n\tw := &bytebufferpool.ByteBuffer{}\n\n\tbase := ctx.URI()\n\n\t// io/fs doesn't support ReadDir with empty path.\n\tif dirPath == \"\" {\n\t\tdirPath = \".\"\n\t}\n\n\tbasePathEscaped := html.EscapeString(string(base.Path()))\n\t_, _ = fmt.Fprintf(w, \"<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>\", basePathEscaped)\n\t_, _ = fmt.Fprintf(w, \"<h1>%s</h1>\", basePathEscaped)\n\t_, _ = fmt.Fprintf(w, \"<ul>\")\n\n\tif len(basePathEscaped) > 1 {\n\t\tvar parentURI URI\n\t\tbase.CopyTo(&parentURI)\n\t\tparentURI.Update(string(base.Path()) + \"/..\")\n\t\tparentPathEscaped := html.EscapeString(string(parentURI.Path()))\n\t\t_, _ = fmt.Fprintf(w, `<li><a href=\"%s\" class=\"dir\">..</a></li>`, parentPathEscaped)\n\t}\n\n\tdirEntries, err := fs.ReadDir(h.filesystem, dirPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfm := make(map[string]fs.FileInfo, len(dirEntries))\n\tfilenames := make([]string, 0, len(dirEntries))\nnestedContinue:\n\tfor _, de := range dirEntries {\n\t\tname := de.Name()\n\t\tfor _, cfs := range h.compressedFileSuffixes {\n\t\t\tif strings.HasSuffix(name, cfs) {\n\t\t\t\t// Do not show compressed files on index page.\n\t\t\t\tcontinue nestedContinue\n\t\t\t}\n\t\t}\n\t\tfi, err := de.Info()\n\t\tif err != nil {\n\t\t\tctx.Logger().Printf(\"cannot fetch information from dir entry %q: %v, skip\", name, err)\n\n\t\t\tcontinue nestedContinue\n\t\t}\n\n\t\tfm[name] = fi\n\t\tfilenames = append(filenames, name)\n\t}\n\n\tvar u URI\n\tbase.CopyTo(&u)\n\tu.Update(string(u.Path()) + \"/\")\n\n\tsort.Strings(filenames)\n\tfor _, name := range filenames {\n\t\tu.Update(name)\n\t\tpathEscaped := html.EscapeString(string(u.Path()))\n\t\tfi := fm[name]\n\t\tauxStr := \"dir\"\n\t\tclassName := \"dir\"\n\t\tif !fi.IsDir() {\n\t\t\tauxStr = fmt.Sprintf(\"file, %d bytes\", fi.Size())\n\t\t\tclassName = \"file\"\n\t\t}\n\t\t_, _ = fmt.Fprintf(w, `<li><a href=\"%s\" class=\"%s\">%s</a>, %s, last modified %s</li>`,\n\t\t\tpathEscaped, className, html.EscapeString(name), auxStr, fsModTime(fi.ModTime()))\n\t}\n\n\t_, _ = fmt.Fprintf(w, \"</ul></body></html>\")\n\n\tif mustCompress {\n\t\tvar zbuf bytebufferpool.ByteBuffer\n\t\tswitch fileEncoding {\n\t\tcase \"br\":\n\t\t\tzbuf.B = AppendBrotliBytesLevel(zbuf.B, w.B, CompressDefaultCompression)\n\t\tcase \"gzip\":\n\t\t\tzbuf.B = AppendGzipBytesLevel(zbuf.B, w.B, CompressDefaultCompression)\n\t\tcase \"zstd\":\n\t\t\tzbuf.B = AppendZstdBytesLevel(zbuf.B, w.B, CompressZstdDefault)\n\t\t}\n\t\tw = &zbuf\n\t}\n\n\tdirIndex := w.B\n\tlastModified := time.Now()\n\tff := &fsFile{\n\t\th:               h,\n\t\tdirIndex:        dirIndex,\n\t\tcontentType:     \"text/html; charset=utf-8\",\n\t\tcontentLength:   len(dirIndex),\n\t\tcompressed:      mustCompress,\n\t\tlastModified:    lastModified,\n\t\tlastModifiedStr: AppendHTTPDate(nil, lastModified),\n\n\t\tt: lastModified,\n\t}\n\treturn ff, nil\n}\n\nconst (\n\tfsMinCompressRatio        = 0.8\n\tfsMaxCompressibleFileSize = 8 * 1024 * 1024\n)\n\nfunc (h *fsHandler) compressAndOpenFSFile(filePath, fileEncoding string) (*fsFile, error) {\n\tf, err := h.filesystem.Open(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for file %q: %w\", filePath, err)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\t_ = f.Close()\n\t\treturn nil, errDirIndexRequired\n\t}\n\n\tif strings.HasSuffix(filePath, h.compressedFileSuffixes[fileEncoding]) ||\n\t\tfileInfo.Size() > fsMaxCompressibleFileSize ||\n\t\t!isFileCompressible(f, fsMinCompressRatio) {\n\t\treturn h.newFSFile(f, fileInfo, false, filePath, \"\")\n\t}\n\n\tcompressedFilePath := h.filePathToCompressed(filePath)\n\n\tif _, ok := h.filesystem.(*osFS); !ok {\n\t\treturn h.newCompressedFSFileCache(f, fileInfo, compressedFilePath, fileEncoding)\n\t}\n\n\tif compressedFilePath != filePath {\n\t\tif err := os.MkdirAll(filepath.Dir(compressedFilePath), 0o750); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tcompressedFilePath += h.compressedFileSuffixes[fileEncoding]\n\n\tabsPath, err := filepath.Abs(compressedFilePath)\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"cannot determine absolute path for %q: %v\", compressedFilePath, err)\n\t}\n\n\tflock := getFileLock(absPath)\n\tflock.Lock()\n\tff, err := h.compressFileNolock(f, fileInfo, filePath, compressedFilePath, fileEncoding)\n\tflock.Unlock()\n\n\treturn ff, err\n}\n\nfunc (h *fsHandler) compressFileNolock(\n\tf fs.File, fileInfo fs.FileInfo, filePath, compressedFilePath, fileEncoding string,\n) (*fsFile, error) {\n\t// Attempt to open compressed file created by another concurrent\n\t// goroutine.\n\t// It is safe opening such a file, since the file creation\n\t// is guarded by file mutex - see getFileLock call.\n\tif _, err := os.Stat(compressedFilePath); err == nil {\n\t\t_ = f.Close()\n\t\treturn h.newCompressedFSFile(compressedFilePath, fileEncoding)\n\t}\n\n\t// Create temporary file, so concurrent goroutines don't use\n\t// it until it is created.\n\ttmpFilePath := compressedFilePath + \".tmp\"\n\tzf, err := os.Create(tmpFilePath)\n\tif err != nil {\n\t\t_ = f.Close()\n\t\tif !errors.Is(err, fs.ErrPermission) {\n\t\t\treturn nil, fmt.Errorf(\"cannot create temporary file %q: %w\", tmpFilePath, err)\n\t\t}\n\t\treturn nil, errNoCreatePermission\n\t}\n\tswitch fileEncoding {\n\tcase \"br\":\n\t\tzw := acquireStacklessBrotliWriter(zf, CompressDefaultCompression)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessBrotliWriter(zw, CompressDefaultCompression)\n\tcase \"gzip\":\n\t\tzw := acquireStacklessGzipWriter(zf, CompressDefaultCompression)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessGzipWriter(zw, CompressDefaultCompression)\n\tcase \"zstd\":\n\t\tzw := acquireStacklessZstdWriter(zf, CompressZstdDefault)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessZstdWriter(zw, CompressZstdDefault)\n\t}\n\t_ = zf.Close()\n\t_ = f.Close()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error when compressing file %q to %q: %w\", filePath, tmpFilePath, err)\n\t}\n\tif err = os.Chtimes(tmpFilePath, time.Now(), fileInfo.ModTime()); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot change modification time to %v for tmp file %q: %v\",\n\t\t\tfileInfo.ModTime(), tmpFilePath, err)\n\t}\n\tif err = os.Rename(tmpFilePath, compressedFilePath); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot move compressed file from %q to %q: %w\", tmpFilePath, compressedFilePath, err)\n\t}\n\treturn h.newCompressedFSFile(compressedFilePath, fileEncoding)\n}\n\n// newCompressedFSFileCache use memory cache compressed files.\nfunc (h *fsHandler) newCompressedFSFileCache(f fs.File, fileInfo fs.FileInfo, filePath, fileEncoding string) (*fsFile, error) {\n\tvar (\n\t\tw   = &bytebufferpool.ByteBuffer{}\n\t\terr error\n\t)\n\n\tswitch fileEncoding {\n\tcase \"br\":\n\t\tzw := acquireStacklessBrotliWriter(w, CompressDefaultCompression)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessBrotliWriter(zw, CompressDefaultCompression)\n\tcase \"gzip\":\n\t\tzw := acquireStacklessGzipWriter(w, CompressDefaultCompression)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessGzipWriter(zw, CompressDefaultCompression)\n\tcase \"zstd\":\n\t\tzw := acquireStacklessZstdWriter(w, CompressZstdDefault)\n\t\t_, err = copyZeroAlloc(zw, f)\n\t\tif errf := zw.Flush(); err == nil {\n\t\t\terr = errf\n\t\t}\n\t\treleaseStacklessZstdWriter(zw, CompressZstdDefault)\n\t}\n\tdefer func() { _ = f.Close() }()\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error when compressing file %q: %w\", filePath, err)\n\t}\n\n\tseeker, ok := f.(io.Seeker)\n\tif !ok {\n\t\treturn nil, errors.New(\"not implemented io.Seeker\")\n\t}\n\tif _, err = seeker.Seek(0, io.SeekStart); err != nil {\n\t\treturn nil, err\n\t}\n\n\text := fileExtension(fileInfo.Name(), false, h.compressedFileSuffixes[fileEncoding])\n\tcontentType := mime.TypeByExtension(ext)\n\tif contentType == \"\" {\n\t\tdata, err := readFileHeader(f, false, fileEncoding)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot read header of the file %q: %w\", fileInfo.Name(), err)\n\t\t}\n\t\tcontentType = http.DetectContentType(data)\n\t}\n\n\tdirIndex := w.B\n\tlastModified := fileInfo.ModTime()\n\tff := &fsFile{\n\t\th:               h,\n\t\tdirIndex:        dirIndex,\n\t\tcontentType:     contentType,\n\t\tcontentLength:   len(dirIndex),\n\t\tcompressed:      true,\n\t\tlastModified:    lastModified,\n\t\tlastModifiedStr: AppendHTTPDate(nil, lastModified),\n\n\t\tt: time.Now(),\n\t}\n\n\treturn ff, nil\n}\n\nfunc (h *fsHandler) newCompressedFSFile(filePath, fileEncoding string) (*fsFile, error) {\n\tf, err := h.filesystem.Open(filePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot open compressed file %q: %w\", filePath, err)\n\t}\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for compressed file %q: %w\", filePath, err)\n\t}\n\treturn h.newFSFile(f, fileInfo, true, filePath, fileEncoding)\n}\n\nfunc (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding string) (*fsFile, error) {\n\tfilePathOriginal := filePath\n\tif mustCompress {\n\t\tfilePath += h.compressedFileSuffixes[fileEncoding]\n\t}\n\tf, err := h.filesystem.Open(filePath)\n\tif err != nil {\n\t\tif mustCompress && errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn h.compressAndOpenFSFile(filePathOriginal, fileEncoding)\n\t\t}\n\n\t\t// If the file is not found and the path is empty, let's return errDirIndexRequired error.\n\t\tif filePath == \"\" && (errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid)) {\n\t\t\treturn nil, errDirIndexRequired\n\t\t}\n\n\t\treturn nil, err\n\t}\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for file %q: %w\", filePath, err)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\t_ = f.Close()\n\t\tif mustCompress {\n\t\t\treturn nil, fmt.Errorf(\"directory with unexpected suffix found: %q. Suffix: %q\",\n\t\t\t\tfilePath, h.compressedFileSuffixes[fileEncoding])\n\t\t}\n\t\treturn nil, errDirIndexRequired\n\t}\n\n\tif mustCompress {\n\t\tfileInfoOriginal, err := fs.Stat(h.filesystem, filePathOriginal)\n\t\tif err != nil {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"cannot obtain info for original file %q: %w\", filePathOriginal, err)\n\t\t}\n\n\t\t// Only re-create the compressed file if there was more than a second between the mod times.\n\t\t// On macOS the gzip seems to truncate the nanoseconds in the mod time causing the original file\n\t\t// to look newer than the gzipped file.\n\t\tif fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {\n\t\t\t// The compressed file became stale. Re-create it.\n\t\t\t_ = f.Close()\n\t\t\t_ = os.Remove(filePath)\n\t\t\treturn h.compressAndOpenFSFile(filePathOriginal, fileEncoding)\n\t\t}\n\t}\n\n\treturn h.newFSFile(f, fileInfo, mustCompress, filePath, fileEncoding)\n}\n\nfunc (h *fsHandler) newFSFile(f fs.File, fileInfo fs.FileInfo, compressed bool, filePath, fileEncoding string) (*fsFile, error) {\n\tn := fileInfo.Size()\n\tcontentLength := int(n)\n\tif n != int64(contentLength) {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"too big file: %d bytes\", n)\n\t}\n\n\t// detect content-type\n\text := fileExtension(fileInfo.Name(), compressed, h.compressedFileSuffixes[fileEncoding])\n\tcontentType := mime.TypeByExtension(ext)\n\tif contentType == \"\" {\n\t\tdata, err := readFileHeader(f, compressed, fileEncoding)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot read header of the file %q: %w\", fileInfo.Name(), err)\n\t\t}\n\t\tcontentType = http.DetectContentType(data)\n\t}\n\n\tlastModified := fileInfo.ModTime()\n\tff := &fsFile{\n\t\th:               h,\n\t\tf:               f,\n\t\tfilename:        filePath,\n\t\tcontentType:     contentType,\n\t\tcontentLength:   contentLength,\n\t\tcompressed:      compressed,\n\t\tlastModified:    lastModified,\n\t\tlastModifiedStr: AppendHTTPDate(nil, lastModified),\n\n\t\tt: time.Now(),\n\t}\n\treturn ff, nil\n}\n\nfunc readFileHeader(f io.Reader, compressed bool, fileEncoding string) ([]byte, error) {\n\tr := f\n\tvar (\n\t\tbr  *brotli.Reader\n\t\tzr  *gzip.Reader\n\t\tzsr *zstd.Decoder\n\t)\n\tif compressed {\n\t\tvar err error\n\t\tswitch fileEncoding {\n\t\tcase \"br\":\n\t\t\tif br, err = acquireBrotliReader(f); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr = br\n\t\tcase \"gzip\":\n\t\t\tif zr, err = acquireGzipReader(f); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr = zr\n\t\tcase \"zstd\":\n\t\t\tif zsr, err = acquireZstdReader(f); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tr = zsr\n\t\t}\n\t}\n\n\tlr := &io.LimitedReader{\n\t\tR: r,\n\t\tN: 512,\n\t}\n\tdata, err := io.ReadAll(lr)\n\tseeker, ok := f.(io.Seeker)\n\tif !ok {\n\t\treturn nil, errors.New(\"must implement io.Seeker\")\n\t}\n\tif _, err := seeker.Seek(0, io.SeekStart); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif br != nil {\n\t\treleaseBrotliReader(br)\n\t}\n\n\tif zr != nil {\n\t\treleaseGzipReader(zr)\n\t}\n\n\tif zsr != nil {\n\t\treleaseZstdReader(zsr)\n\t}\n\n\treturn data, err\n}\n\nfunc stripLeadingSlashes(path []byte, stripSlashes int) []byte {\n\tfor stripSlashes > 0 && len(path) > 0 {\n\t\tif path[0] != '/' {\n\t\t\t// developer sanity-check\n\t\t\tpanic(\"BUG: path must start with slash\")\n\t\t}\n\t\tn := bytes.IndexByte(path[1:], '/')\n\t\tif n < 0 {\n\t\t\tpath = path[:0]\n\t\t\tbreak\n\t\t}\n\t\tpath = path[n+1:]\n\t\tstripSlashes--\n\t}\n\treturn path\n}\n\nfunc fileExtension(path string, compressed bool, compressedFileSuffix string) string {\n\tif compressed && strings.HasSuffix(path, compressedFileSuffix) {\n\t\tpath = path[:len(path)-len(compressedFileSuffix)]\n\t}\n\tn := strings.LastIndexByte(path, '.')\n\tif n < 0 {\n\t\treturn \"\"\n\t}\n\treturn path[n:]\n}\n\n// FileLastModified returns last modified time for the file.\nfunc FileLastModified(path string) (time.Time, error) {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn zeroTime, err\n\t}\n\tfileInfo, err := f.Stat()\n\t_ = f.Close()\n\tif err != nil {\n\t\treturn zeroTime, err\n\t}\n\treturn fsModTime(fileInfo.ModTime()), nil\n}\n\nfunc fsModTime(t time.Time) time.Time {\n\treturn t.In(time.UTC).Truncate(time.Second)\n}\n\nvar filesLockMap sync.Map\n\nfunc getFileLock(absPath string) *sync.Mutex {\n\tv, _ := filesLockMap.LoadOrStore(absPath, &sync.Mutex{})\n\tfilelock := v.(*sync.Mutex)\n\treturn filelock\n}\n\nvar _ fs.FS = (*osFS)(nil)\n\ntype osFS struct{}\n\nfunc (o *osFS) Open(name string) (fs.File, error)     { return os.Open(name) }\nfunc (o *osFS) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }\n"
  },
  {
    "path": "fs_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"log\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc ExampleFS() {\n\tfs := &fasthttp.FS{\n\t\t// Path to directory to serve.\n\t\tRoot: \"/var/www/static-site\",\n\n\t\t// Generate index pages if client requests directory contents.\n\t\tGenerateIndexPages: true,\n\n\t\t// Enable transparent compression to save network traffic.\n\t\tCompress: true,\n\t}\n\n\t// Create request handler for serving static files.\n\th := fs.NewRequestHandler()\n\n\t// Start the server.\n\tif err := fasthttp.ListenAndServe(\":8080\", h); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "fs_fs_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"embed\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\t\"time\"\n)\n\n//go:embed fasthttputil fs.go README.md testdata examples\nvar fsTestFilesystem embed.FS\n\nfunc TestFSServeFileHead(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodHead)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFS(&ctx, fsTestFilesystem, \"fs.go\")\n\n\tvar resp Response\n\tresp.SkipBody = true\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q\", ce)\n\t}\n\n\tbody := resp.Body()\n\tif len(body) > 0 {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting empty body\", body)\n\t}\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tcontentLength := resp.Header.ContentLength()\n\tif contentLength != len(expectedBody) {\n\t\tt.Fatalf(\"unexpected Content-Length: %d. expecting %d\", contentLength, len(expectedBody))\n\t}\n}\n\nfunc TestFSServeFileCompressed(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// should prefer brotli over zstd, gzip and ignore unknown encoding\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\tServeFS(&ctx, fsTestFilesystem, \"fs.go\")\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"br\")\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err := resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unbrotli response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expected len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer zstd over gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, wompwomp\")\n\tServeFS(&ctx, fsTestFilesystem, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"zstd\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"zstd\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyUnzstd()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unzstd response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expected len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, wompwomp\")\n\tServeFS(&ctx, fsTestFilesystem, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"gzip\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on gunzip response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expected len=%d\", len(body), len(expectedBody))\n\t}\n}\n\nfunc TestFSServeFileUncompressed(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\tServeFileUncompressed(&ctx, \"fs.go\")\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting \\\"\\\"\", string(ce))\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif len(vary) > 0 {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting \\\"\\\"\", string(vary))\n\t}\n\n\tbody := resp.Body()\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n}\n\nfunc TestFSFSByteRangeConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:              fsTestFilesystem,\n\t\tRoot:            \"\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSByteRange(t, h, \"/fs.go\")\n\t\t\t\ttestFSByteRange(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\tcase <-ch:\n\t\t}\n\t}\n}\n\nfunc TestFSFSByteRangeSingleThread(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:              fsTestFilesystem,\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSByteRange(t, h, \"/fs.go\")\n\ttestFSByteRange(t, h, \"/README.md\")\n}\n\nfunc TestFSFSCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\t// go 1.16 timeout may occur\n\tif strings.HasPrefix(runtime.Version(), \"go1.16\") {\n\t\tt.SkipNow()\n\t}\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:                 fsTestFilesystem,\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSFSCompress(t, h, \"/fs.go\")\n\t\t\t\ttestFSFSCompress(t, h, \"/examples/\")\n\t\t\t\ttestFSFSCompress(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second * 4):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestFSFSCompressSingleThread(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:                 fsTestFilesystem,\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSFSCompress(t, h, \"/fs.go\")\n\ttestFSFSCompress(t, h, \"/examples/\")\n\ttestFSFSCompress(t, h, \"/README.md\")\n}\n\nfunc testFSFSCompress(t *testing.T, h RequestHandler, filePath string) {\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\t// get uncompressed\n\tctx.Request.SetRequestURI(filePath)\n\th(&ctx)\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting \\\"\\\"\", string(ce))\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif len(vary) > 0 {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting \\\"\\\"\", string(vary))\n\t}\n\n\texpectedBody := bytes.Clone(resp.Body())\n\n\t// should prefer brotli over zstd, gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q\", string(ce), \"br\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err := resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unbrotli response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n\n\t// should prefer zstd over gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"zstd\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q\", string(ce), \"zstd\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyUnzstd()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unzstd response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n\n\t// should prefer gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q. filePath=%q\", string(ce), \"gzip\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on gunzip response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n}\n\nfunc TestFSServeFileContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFS(&ctx, fsTestFilesystem, \"testdata/test.png\")\n\n\tvar resp Response\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\texpected := []byte(\"image/png\")\n\tif !bytes.Equal(resp.Header.ContentType(), expected) {\n\t\tt.Fatalf(\"Unexpected Content-Type, expected: %q got %q\", expected, resp.Header.ContentType())\n\t}\n}\n\nfunc TestFSServeFileDirectoryRedirect(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"http://foobar.com\")\n\tctx.Init(&req, nil, nil)\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, fsTestFilesystem, \"fasthttputil\")\n\tif ctx.Response.StatusCode() != StatusFound {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil' without trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusFound)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, fsTestFilesystem, \"fasthttputil/\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil/' with trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, fsTestFilesystem, \"fs.go\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for file '/fs.go'. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n}\n\nvar dirTestFilesystem = os.DirFS(\".\")\n\nfunc TestDirFSServeFileHead(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodHead)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFS(&ctx, dirTestFilesystem, \"fs.go\")\n\n\tvar resp Response\n\tresp.SkipBody = true\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q\", ce)\n\t}\n\n\tbody := resp.Body()\n\tif len(body) > 0 {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting empty body\", body)\n\t}\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tcontentLength := resp.Header.ContentLength()\n\tif contentLength != len(expectedBody) {\n\t\tt.Fatalf(\"unexpected Content-Length: %d. expecting %d\", contentLength, len(expectedBody))\n\t}\n}\n\nfunc TestDirFSServeFileCompressed(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// should prefer brotli over zstd, gzip and ignore unknown encoding\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\tServeFS(&ctx, dirTestFilesystem, \"fs.go\")\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"br\")\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err := resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unbrotli response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer zstd over gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, wompwomp\")\n\tServeFS(&ctx, dirTestFilesystem, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"zstd\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"zstd\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyUnzstd()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unzstd response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, wompwomp\")\n\tServeFS(&ctx, dirTestFilesystem, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"gzip\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on gunzip response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n}\n\nfunc TestDirFSFSByteRangeConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:              dirTestFilesystem,\n\t\tRoot:            \"\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSByteRange(t, h, \"/fs.go\")\n\t\t\t\ttestFSByteRange(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\tcase <-ch:\n\t\t}\n\t}\n}\n\nfunc TestDirFSFSByteRangeSingleThread(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:              dirTestFilesystem,\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSByteRange(t, h, \"/fs.go\")\n\ttestFSByteRange(t, h, \"/README.md\")\n}\n\nfunc TestDirFSFSCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:                 dirTestFilesystem,\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSFSCompress(t, h, \"/fs.go\")\n\t\t\t\ttestFSFSCompress(t, h, \"/examples/\")\n\t\t\t\ttestFSFSCompress(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second * 2):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestDirFSFSCompressSingleThread(t *testing.T) {\n\tt.Parallel()\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tFS:                 dirTestFilesystem,\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t}\n\th := fs.NewRequestHandler()\n\n\ttestFSFSCompress(t, h, \"/fs.go\")\n\ttestFSFSCompress(t, h, \"/examples/\")\n\ttestFSFSCompress(t, h, \"/README.md\")\n}\n\nfunc TestDirFSServeFileContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFS(&ctx, dirTestFilesystem, \"testdata/test.png\")\n\n\tvar resp Response\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\texpected := []byte(\"image/png\")\n\tif !bytes.Equal(resp.Header.ContentType(), expected) {\n\t\tt.Fatalf(\"Unexpected Content-Type, expected: %q got %q\", expected, resp.Header.ContentType())\n\t}\n}\n\nfunc TestDirFSServeFileDirectoryRedirect(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"http://foobar.com\")\n\tctx.Init(&req, nil, nil)\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, dirTestFilesystem, \"fasthttputil\")\n\tif ctx.Response.StatusCode() != StatusFound {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil' without trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusFound)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, dirTestFilesystem, \"fasthttputil/\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil/' with trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFS(&ctx, dirTestFilesystem, \"fs.go\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for file '/fs.go'. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n}\n\nfunc TestFSFSGenerateIndexOsDirFS(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"dirFS\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tfs := &FS{\n\t\t\tFS:                 dirTestFilesystem,\n\t\t\tRoot:               \".\",\n\t\t\tGenerateIndexPages: true,\n\t\t}\n\t\th := fs.NewRequestHandler()\n\n\t\tvar ctx RequestCtx\n\t\tvar req Request\n\t\tctx.Init(&req, nil, nil)\n\n\t\th(&ctx)\n\n\t\tcases := []string{\"/\", \"//\", \"\"}\n\t\tfor _, c := range cases {\n\t\t\tctx.Request.Reset()\n\t\t\tctx.Response.Reset()\n\n\t\t\treq.Header.SetMethod(MethodGet)\n\t\t\treq.SetRequestURI(\"http://foobar.com\" + c)\n\t\t\th(&ctx)\n\n\t\t\tif ctx.Response.StatusCode() != StatusOK {\n\t\t\t\tt.Fatalf(\"unexpected status code %d for path %q. Expecting %d\", ctx.Response.StatusCode(), ctx.Response.StatusCode(), StatusOK)\n\t\t\t}\n\n\t\t\tif !bytes.Contains(ctx.Response.Body(), []byte(\"fasthttputil\")) {\n\t\t\t\tt.Fatalf(\"unexpected body %q. Expecting to contain %q\", ctx.Response.Body(), \"fasthttputil\")\n\t\t\t}\n\n\t\t\tif !bytes.Contains(ctx.Response.Body(), []byte(\"fs.go\")) {\n\t\t\t\tt.Fatalf(\"unexpected body %q. Expecting to contain %q\", ctx.Response.Body(), \"fs.go\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"embedFS\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tfs := &FS{\n\t\t\tFS:                 fsTestFilesystem,\n\t\t\tRoot:               \".\",\n\t\t\tGenerateIndexPages: true,\n\t\t}\n\t\th := fs.NewRequestHandler()\n\n\t\tvar ctx RequestCtx\n\t\tvar req Request\n\t\tctx.Init(&req, nil, nil)\n\n\t\th(&ctx)\n\n\t\tcases := []string{\"/\", \"//\", \"\"}\n\t\tfor _, c := range cases {\n\t\t\tctx.Request.Reset()\n\t\t\tctx.Response.Reset()\n\n\t\t\treq.Header.SetMethod(MethodGet)\n\t\t\treq.SetRequestURI(\"http://foobar.com\" + c)\n\t\t\th(&ctx)\n\n\t\t\tif ctx.Response.StatusCode() != StatusOK {\n\t\t\t\tt.Fatalf(\"unexpected status code %d for path %q. Expecting %d\", ctx.Response.StatusCode(), ctx.Response.StatusCode(), StatusOK)\n\t\t\t}\n\n\t\t\tif !bytes.Contains(ctx.Response.Body(), []byte(\"fasthttputil\")) {\n\t\t\t\tt.Fatalf(\"unexpected body %q. Expecting to contain %q\", ctx.Response.Body(), \"fasthttputil\")\n\t\t\t}\n\n\t\t\tif !bytes.Contains(ctx.Response.Body(), []byte(\"fs.go\")) {\n\t\t\t\tt.Fatalf(\"unexpected body %q. Expecting to contain %q\", ctx.Response.Body(), \"fs.go\")\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestFSRootEnforcement(t *testing.T) {\n\tt.Parallel()\n\n\tmemFS := fstest.MapFS{\n\t\t\"public/index.html\":  {Data: []byte(\"<h1>Public</h1>\")},\n\t\t\"secret/admin.json\":  {Data: []byte(`{\"admin\": true, \"key\": \"s3cret\"}`)},\n\t\t\"public/nested/info\": {Data: []byte(\"nested\")},\n\t}\n\n\ttmpDir := t.TempDir()\n\tif err := os.MkdirAll(filepath.Join(tmpDir, \"public\"), 0o755); err != nil {\n\t\tt.Fatalf(\"cannot create public dir: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Join(tmpDir, \"secret\"), 0o755); err != nil {\n\t\tt.Fatalf(\"cannot create secret dir: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(tmpDir, \"public\", \"index.html\"), []byte(\"<h1>Public</h1>\"), 0o644); err != nil {\n\t\tt.Fatalf(\"cannot create public index: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(tmpDir, \"secret\", \"admin.json\"), []byte(`{\"admin\": true, \"key\": \"s3cret\"}`), 0o644); err != nil {\n\t\tt.Fatalf(\"cannot create secret admin file: %v\", err)\n\t}\n\n\ttype testCase struct {\n\t\tname        string\n\t\troot        string\n\t\tfilesystem  fs.FS\n\t\tpathRewrite PathRewriteFunc\n\t}\n\n\tcases := make([]testCase, 0, 9)\n\tfor _, root := range []string{\"public\", \"public/\", \"./public\", \"/public\"} {\n\t\tcases = append(\n\t\t\tcases,\n\t\t\ttestCase{\n\t\t\t\tname:       \"mapfs/\" + root,\n\t\t\t\troot:       root,\n\t\t\t\tfilesystem: memFS,\n\t\t\t}, testCase{\n\t\t\t\tname:       \"dirfs/\" + root,\n\t\t\t\troot:       root,\n\t\t\t\tfilesystem: os.DirFS(tmpDir),\n\t\t\t},\n\t\t)\n\t}\n\n\tcases = append(cases, testCase{\n\t\tname:       \"mapfs/pathrewrite-no-leading-slash\",\n\t\troot:       \"./public/\",\n\t\tfilesystem: memFS,\n\t\tpathRewrite: func(ctx *RequestCtx) []byte {\n\t\t\treturn bytes.TrimPrefix(ctx.Path(), []byte(\"/\"))\n\t\t},\n\t})\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstop := make(chan struct{})\n\t\t\tdefer close(stop)\n\n\t\t\tfs := &FS{\n\t\t\t\tRoot:           tc.root,\n\t\t\t\tFS:             tc.filesystem,\n\t\t\t\tAllowEmptyRoot: true,\n\t\t\t\tCleanStop:      stop,\n\t\t\t\tPathRewrite:    tc.pathRewrite,\n\t\t\t}\n\t\t\th := fs.NewRequestHandler()\n\n\t\t\tvar ctx RequestCtx\n\t\t\tctx.Init(&Request{}, nil, TestLogger{t: t})\n\n\t\t\tcheckStatus := func(uri string, expected int) {\n\t\t\t\tctx.Request.Reset()\n\t\t\t\tctx.Response.Reset()\n\t\t\t\tctx.Request.SetRequestURI(uri)\n\t\t\t\th(&ctx)\n\t\t\t\tif ctx.Response.StatusCode() != expected {\n\t\t\t\t\tt.Fatalf(\"unexpected status code for %s: %d. Expecting %d\", uri, ctx.Response.StatusCode(), expected)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcheckStatus(\"http://localhost/index.html\", StatusOK)\n\t\t\tcheckStatus(\"http://localhost/secret/admin.json\", StatusNotFound)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "fs_handler_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\n// Setup file handlers (aka 'file server config').\nvar (\n\t// Handler for serving images from /img/ path,\n\t// i.e. /img/foo/bar.jpg will be served from\n\t// /var/www/images/foo/bar.jpb .\n\timgPrefix  = []byte(\"/img/\")\n\timgHandler = fasthttp.FSHandler(\"/var/www/images\", 1)\n\n\t// Handler for serving css from /static/css/ path,\n\t// i.e. /static/css/foo/bar.css will be served from\n\t// /home/dev/css/foo/bar.css .\n\tcssPrefix  = []byte(\"/static/css/\")\n\tcssHandler = fasthttp.FSHandler(\"/home/dev/css\", 2)\n\n\t// Handler for serving the rest of requests,\n\t// i.e. /foo/bar/baz.html will be served from\n\t// /var/www/files/foo/bar/baz.html .\n\tfilesHandler = fasthttp.FSHandler(\"/var/www/files\", 0)\n)\n\n// Main request handler.\nfunc requestHandler(ctx *fasthttp.RequestCtx) {\n\tpath := ctx.Path()\n\tswitch {\n\tcase bytes.HasPrefix(path, imgPrefix):\n\t\timgHandler(ctx)\n\tcase bytes.HasPrefix(path, cssPrefix):\n\t\tcssHandler(ctx)\n\tdefault:\n\t\tfilesHandler(ctx)\n\t}\n}\n\nfunc ExampleFSHandler() {\n\tif err := fasthttp.ListenAndServe(\":80\", requestHandler); err != nil {\n\t\tlog.Fatalf(\"Error in server: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "fs_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\ntype TestLogger struct {\n\tt *testing.T\n}\n\nfunc (t TestLogger) Printf(format string, args ...any) {\n\tt.t.Logf(format, args...)\n}\n\nfunc TestNewVHostPathRewriter(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.SetRequestURI(\"/foo/bar/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tf := NewVHostPathRewriter(0)\n\tpath := f(&ctx)\n\texpectedPath := \"/foobar.com/foo/bar/baz\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"https://aaa.bbb.cc/one/two/three/four?asdf=dsf\")\n\tf = NewVHostPathRewriter(2)\n\tpath = f(&ctx)\n\texpectedPath = \"/aaa.bbb.cc/three/four\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n}\n\nfunc TestNewVHostPathRewriterMaliciousHost(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetHost(\"/../../../etc/passwd\")\n\treq.SetRequestURI(\"/foo/bar/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tf := NewVHostPathRewriter(0)\n\tpath := f(&ctx)\n\texpectedPath := \"/invalid-host/\"\n\tif string(path) != expectedPath {\n\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", path, expectedPath)\n\t}\n}\n\nfunc testPathNotFound(t *testing.T, pathNotFoundFunc RequestHandler) {\n\tt.Helper()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"http//some.url/file\")\n\tctx.Init(&req, nil, TestLogger{t: t})\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tfs := &FS{\n\t\tRoot:         \"./\",\n\t\tPathNotFound: pathNotFoundFunc,\n\t\tCleanStop:    stop,\n\t}\n\tfs.NewRequestHandler()(&ctx)\n\n\tif pathNotFoundFunc == nil {\n\t\t// different to ...\n\t\tif !bytes.Equal(ctx.Response.Body(),\n\t\t\t[]byte(\"Cannot open requested path\")) {\n\t\t\tt.Fatalf(\"response defers. Response: %q\", ctx.Response.Body())\n\t\t}\n\t} else {\n\t\t// Equals to ...\n\t\tif bytes.Equal(ctx.Response.Body(),\n\t\t\t[]byte(\"Cannot open requested path\")) {\n\t\t\tt.Fatalf(\"response defers. Response: %q\", ctx.Response.Body())\n\t\t}\n\t}\n}\n\nfunc TestPathNotFound(t *testing.T) {\n\tt.Parallel()\n\n\ttestPathNotFound(t, nil)\n}\n\nfunc TestPathNotFoundFunc(t *testing.T) {\n\tt.Parallel()\n\n\ttestPathNotFound(t, func(ctx *RequestCtx) {\n\t\tctx.WriteString(\"Not found hehe\") //nolint:errcheck\n\t})\n}\n\nfunc TestServeFileHead(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodHead)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFile(&ctx, \"fs.go\")\n\n\tvar resp Response\n\tresp.SkipBody = true\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q\", ce)\n\t}\n\n\tbody := resp.Body()\n\tif len(body) > 0 {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting empty body\", body)\n\t}\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tcontentLength := resp.Header.ContentLength()\n\tif contentLength != len(expectedBody) {\n\t\tt.Fatalf(\"unexpected Content-Length: %d. expecting %d\", contentLength, len(expectedBody))\n\t}\n}\n\nfunc TestServeFileSmallNoReadFrom(t *testing.T) {\n\tt.Parallel()\n\n\texpectedStr := \"hello, world!\"\n\ttempFile := filepath.Join(t.TempDir(), \"hello\")\n\n\tif err := os.WriteFile(tempFile, []byte(expectedStr), 0o666); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFile(&ctx, tempFile)\n\n\treader, ok := ctx.Response.bodyStream.(*fsSmallFileReader)\n\tif !ok {\n\t\tt.Fatal(\"expected fsSmallFileReader\")\n\t}\n\tdefer reader.ff.Release()\n\n\tbuf := bytes.NewBuffer(nil)\n\n\tn, err := reader.WriteTo(pureWriter{w: buf})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif n != int64(len(expectedStr)) {\n\t\tt.Fatalf(\"expected %d bytes, got %d bytes\", len(expectedStr), n)\n\t}\n\n\tbody := buf.String()\n\tif body != expectedStr {\n\t\tt.Fatalf(\"expected '%q'\", expectedStr)\n\t}\n}\n\ntype pureWriter struct {\n\tw io.Writer\n}\n\nfunc (pw pureWriter) Write(p []byte) (nn int, err error) {\n\treturn pw.w.Write(p)\n}\n\nfunc TestServeFileCompressed(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// should prefer brotli over zstd, gzip and ignore unknown encoding\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\tServeFile(&ctx, \"fs.go\")\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting %q\", string(ce), \"br\")\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err := resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unbrotli response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer zstd over gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, wompwomp\")\n\tServeFile(&ctx, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"zstd\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"zstd\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyUnzstd()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unzstd response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n\n\t// should prefer gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, wompwomp\")\n\tServeFile(&ctx, \"fs.go\")\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding' %q. Expecting %q\", string(ce), \"gzip\")\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on gunzip response body: %v\", err)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n}\n\nfunc TestServeFileUncompressed(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\texpectedBody, err := getFileContents(\"/fs.go\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tctx.Request.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\tServeFileUncompressed(&ctx, \"fs.go\")\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting \\\"\\\"\", string(ce))\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif len(vary) > 0 {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting \\\"\\\"\", string(vary))\n\t}\n\n\tbody := resp.Body()\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d\", len(body), len(expectedBody))\n\t}\n}\n\nfunc TestFSByteRangeConcurrent(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSByteRangeConcurrent(t, &FS{\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t})\n}\n\nfunc TestFSByteRangeConcurrentSkipCache(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSByteRangeConcurrent(t, &FS{\n\t\tRoot:            \".\",\n\t\tSkipCache:       true,\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t})\n}\n\nfunc runFSByteRangeConcurrent(t *testing.T, fs *FS) {\n\tt.Helper()\n\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSByteRange(t, h, \"/fs.go\")\n\t\t\t\ttestFSByteRange(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\tcase <-ch:\n\t\t}\n\t}\n}\n\nfunc TestFSByteRangeSingleThread(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSByteRangeSingleThread(t, &FS{\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t\tCleanStop:       stop,\n\t})\n}\n\nfunc TestFSByteRangeSingleThreadSkipCache(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSByteRangeSingleThread(t, &FS{\n\t\tRoot:            \".\",\n\t\tAcceptByteRange: true,\n\t\tSkipCache:       true,\n\t\tCleanStop:       stop,\n\t})\n}\n\nfunc runFSByteRangeSingleThread(t *testing.T, fs *FS) {\n\tt.Helper()\n\n\th := fs.NewRequestHandler()\n\n\ttestFSByteRange(t, h, \"/fs.go\")\n\ttestFSByteRange(t, h, \"/README.md\")\n}\n\nfunc testFSByteRange(t *testing.T, h RequestHandler, filePath string) {\n\tt.Helper()\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\texpectedBody, err := getFileContents(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read file %q: %v\", filePath, err)\n\t}\n\n\tfileSize := len(expectedBody)\n\tstartPos := rand.Intn(fileSize)\n\tendPos := rand.Intn(fileSize)\n\tif endPos < startPos {\n\t\tstartPos, endPos = endPos, startPos\n\t}\n\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.SetByteRange(startPos, endPos)\n\th(&ctx)\n\n\tvar resp Response\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusPartialContent {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusPartialContent, filePath)\n\t}\n\tcr := resp.Header.Peek(HeaderContentRange)\n\n\texpectedCR := fmt.Sprintf(\"bytes %d-%d/%d\", startPos, endPos, fileSize)\n\tif string(cr) != expectedCR {\n\t\tt.Fatalf(\"unexpected content-range %q. Expecting %q. filePath=%q\", cr, expectedCR, filePath)\n\t}\n\tbody := resp.Body()\n\tbodySize := endPos - startPos + 1\n\tif len(body) != bodySize {\n\t\tt.Fatalf(\"unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d\",\n\t\t\tlen(body), bodySize, filePath, startPos, endPos)\n\t}\n\n\texpectedBody = expectedBody[startPos : endPos+1]\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d\",\n\t\t\tbody, expectedBody, filePath, startPos, endPos)\n\t}\n}\n\nfunc getFileContents(path string) ([]byte, error) {\n\tpath = \".\" + path\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\treturn io.ReadAll(f)\n}\n\nfunc TestParseByteRangeSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttestParseByteRangeSuccess(t, \"bytes=0-0\", 1, 0, 0)\n\ttestParseByteRangeSuccess(t, \"bytes=1234-6789\", 6790, 1234, 6789)\n\n\ttestParseByteRangeSuccess(t, \"bytes=123-\", 456, 123, 455)\n\ttestParseByteRangeSuccess(t, \"bytes=-1\", 1, 0, 0)\n\ttestParseByteRangeSuccess(t, \"bytes=-123\", 456, 333, 455)\n\n\t// End position exceeding content-length. It should be updated to content-length-1.\n\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35\n\ttestParseByteRangeSuccess(t, \"bytes=1-2345\", 234, 1, 233)\n\ttestParseByteRangeSuccess(t, \"bytes=0-2345\", 2345, 0, 2344)\n\n\t// Start position overflow. Whole range must be returned.\n\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35\n\ttestParseByteRangeSuccess(t, \"bytes=-567\", 56, 0, 55)\n}\n\nfunc testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) {\n\tt.Helper()\n\n\tstartPos1, endPos1, err := ParseByteRange([]byte(v), contentLength)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. v=%q, contentLength=%d\", err, v, contentLength)\n\t}\n\tif startPos1 != startPos {\n\t\tt.Fatalf(\"unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d\", startPos1, startPos, v, contentLength)\n\t}\n\tif endPos1 != endPos {\n\t\tt.Fatalf(\"unexpected endPos=%d. Expecting %d. v=%q, contentLength=%d\", endPos1, endPos, v, contentLength)\n\t}\n}\n\nfunc TestParseByteRangeError(t *testing.T) {\n\tt.Parallel()\n\n\t// invalid value\n\ttestParseByteRangeError(t, \"asdfasdfas\", 1234)\n\n\t// invalid units\n\ttestParseByteRangeError(t, \"foobar=1-34\", 600)\n\n\t// missing '-'\n\ttestParseByteRangeError(t, \"bytes=1234\", 1235)\n\n\t// non-numeric range\n\ttestParseByteRangeError(t, \"bytes=foobar\", 123)\n\ttestParseByteRangeError(t, \"bytes=1-foobar\", 123)\n\ttestParseByteRangeError(t, \"bytes=df-344\", 545)\n\n\t// multiple byte ranges\n\ttestParseByteRangeError(t, \"bytes=1-2,4-6\", 123)\n\n\t// byte range exceeding contentLength\n\ttestParseByteRangeError(t, \"bytes=123-\", 12)\n\n\t// startPos exceeding endPos\n\ttestParseByteRangeError(t, \"bytes=123-34\", 1234)\n}\n\nfunc testParseByteRangeError(t *testing.T, v string, contentLength int) {\n\tt.Helper()\n\n\t_, _, err := ParseByteRange([]byte(v), contentLength)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error when parsing byte range %q\", v)\n\t}\n}\n\nfunc TestFSCompressConcurrent(t *testing.T) {\n\t// Don't run this test on Windows, the Windows GitHub actions are too slow and timeout too often.\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\t// This test can't run parallel as files in / might be changed by other tests.\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSCompressConcurrent(t, &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t})\n}\n\nfunc TestFSCompressConcurrentSkipCache(t *testing.T) {\n\t// Don't run this test on Windows, the Windows GitHub actions are too slow and timeout too often.\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\t// This test can't run parallel as files in / might be changed by other tests.\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSCompressConcurrent(t, &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tSkipCache:          true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t})\n}\n\nfunc runFSCompressConcurrent(t *testing.T, fs *FS) {\n\tt.Helper()\n\n\th := fs.NewRequestHandler()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 5 {\n\t\t\t\ttestFSCompress(t, h, \"/fs.go\")\n\t\t\t\ttestFSCompress(t, h, \"/examples/\")\n\t\t\t\ttestFSCompress(t, h, \"/README.md\")\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second * 2):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc TestFSCompressSingleThread(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSCompressSingleThread(t, &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t})\n}\n\nfunc TestFSCompressSingleThreadSkipCache(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\trunFSCompressSingleThread(t, &FS{\n\t\tRoot:               \".\",\n\t\tGenerateIndexPages: true,\n\t\tSkipCache:          true,\n\t\tCompress:           true,\n\t\tCompressBrotli:     true,\n\t\tCompressZstd:       true,\n\t\tCleanStop:          stop,\n\t})\n}\n\nfunc runFSCompressSingleThread(t *testing.T, fs *FS) {\n\tt.Helper()\n\n\th := fs.NewRequestHandler()\n\n\ttestFSCompress(t, h, \"/fs.go\")\n\ttestFSCompress(t, h, \"/\")\n\ttestFSCompress(t, h, \"/README.md\")\n}\n\nfunc testFSCompress(t *testing.T, h RequestHandler, filePath string) {\n\tt.Helper()\n\n\t// File locking is flaky on Windows.\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tvar ctx RequestCtx\n\tctx.Init(&Request{}, nil, nil)\n\n\tvar resp Response\n\n\t// get uncompressed\n\tctx.Request.SetRequestURI(filePath)\n\th(&ctx)\n\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) > 0 {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting \\\"\\\"\", string(ce))\n\t}\n\n\tvary := resp.Header.PeekBytes(strVary)\n\tif len(vary) > 0 {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting \\\"\\\"\", string(vary))\n\t}\n\n\texpectedBody := bytes.Clone(resp.Body())\n\n\t// should prefer brotli over zstd, gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, br, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting %q. filePath=%q\", string(ce), \"br\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err := resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unbrotli response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n\n\t// should prefer zstd over gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, zstd, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"zstd\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting %q. filePath=%q\", string(ce), \"zstd\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyUnzstd()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on unzstd response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n\n\t// should prefer gzip and ignore unknown encoding\n\tctx.Request.Reset()\n\tctx.Request.SetRequestURI(filePath)\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, wompwomp\")\n\th(&ctx)\n\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. filePath=%q\", err, filePath)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d. filePath=%q\", resp.StatusCode(), StatusOK, filePath)\n\t}\n\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected 'Content-Encoding': %q. Expecting %q. filePath=%q\", string(ce), \"gzip\", filePath)\n\t}\n\n\tvary = resp.Header.PeekBytes(strVary)\n\tif !bytes.Equal(vary, strAcceptEncoding) {\n\t\tt.Fatalf(\"unexpected 'Vary': %q. Expecting %q\", string(vary), HeaderAcceptEncoding)\n\t}\n\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error on gunzip response body: %v. filePath=%q\", err, filePath)\n\t}\n\tif !bytes.Equal(body, expectedBody) {\n\t\tt.Fatalf(\"unexpected body: len=%d. Expecting len=%d. filePath=%q\", len(body), len(expectedBody), filePath)\n\t}\n}\n\nfunc TestFSHandlerSingleThread(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\trequestHandler := FSHandler(\".\", 0)\n\n\tf, err := os.Open(\".\")\n\tif err != nil {\n\t\tt.Fatalf(\"cannot open cwd: %v\", err)\n\t}\n\n\tfilenames, err := f.Readdirnames(0)\n\tf.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read dirnames in cwd: %v\", err)\n\t}\n\tsort.Strings(filenames)\n\n\tfor range 3 {\n\t\tfsHandlerTest(t, requestHandler, filenames)\n\t}\n}\n\nfunc TestFSHandlerConcurrent(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\trequestHandler := FSHandler(\".\", 0)\n\n\tf, err := os.Open(\".\")\n\tif err != nil {\n\t\tt.Fatalf(\"cannot open cwd: %v\", err)\n\t}\n\n\tfilenames, err := f.Readdirnames(0)\n\tf.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read dirnames in cwd: %v\", err)\n\t}\n\tsort.Strings(filenames)\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tfor range 3 {\n\t\t\t\tfsHandlerTest(t, requestHandler, filenames)\n\t\t\t}\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc fsHandlerTest(t *testing.T, requestHandler RequestHandler, filenames []string) {\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\tctx.Request.Header.SetHost(\"foobar.com\")\n\n\tfilesTested := 0\n\tfor _, name := range filenames {\n\t\tf, err := os.Open(name)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"cannot open file %q: %v\", name, err)\n\t\t}\n\t\tstat, err := f.Stat()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"cannot get file stat %q: %v\", name, err)\n\t\t}\n\t\tif stat.IsDir() {\n\t\t\tf.Close()\n\t\t\tcontinue\n\t\t}\n\t\tdata, err := io.ReadAll(f)\n\t\tf.Close()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"cannot read file contents %q: %v\", name, err)\n\t\t}\n\n\t\tctx.URI().Update(name)\n\t\trequestHandler(&ctx)\n\t\tif ctx.Response.bodyStream == nil {\n\t\t\tt.Fatalf(\"response body stream must be non-empty\")\n\t\t}\n\t\tbody, err := io.ReadAll(ctx.Response.bodyStream)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error when reading response body stream: %v\", err)\n\t\t}\n\t\tif !bytes.Equal(body, data) {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", body, data)\n\t\t}\n\t\tfilesTested++\n\t\tif filesTested >= 10 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// verify index page generation\n\tctx.URI().Update(\"/\")\n\trequestHandler(&ctx)\n\tif ctx.Response.bodyStream == nil {\n\t\tt.Fatalf(\"response body stream must be non-empty\")\n\t}\n\tbody, err := io.ReadAll(ctx.Response.bodyStream)\n\tif err != nil {\n\t\tt.Fatalf(\"error when reading response body stream: %v\", err)\n\t}\n\tif len(body) == 0 {\n\t\tt.Fatalf(\"index page must be non-empty\")\n\t}\n}\n\nfunc TestStripPathSlashes(t *testing.T) {\n\tt.Parallel()\n\n\ttestStripPathSlashes(t, \"\", 0, \"\")\n\ttestStripPathSlashes(t, \"\", 10, \"\")\n\ttestStripPathSlashes(t, \"/\", 1, \"\")\n\ttestStripPathSlashes(t, \"/\", 10, \"\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 0, \"/foo/bar/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 1, \"/bar/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 2, \"/baz\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 3, \"\")\n\ttestStripPathSlashes(t, \"/foo/bar/baz\", 10, \"\")\n}\n\nfunc testStripPathSlashes(t *testing.T, path string, stripSlashes int, expectedPath string) {\n\tt.Helper()\n\n\ts := stripLeadingSlashes([]byte(path), stripSlashes)\n\tif string(s) != expectedPath {\n\t\tt.Fatalf(\"unexpected path after stripping %q with stripSlashes=%d: %q. Expecting %q\", path, stripSlashes, s, expectedPath)\n\t}\n}\n\nfunc TestFileExtension(t *testing.T) {\n\tt.Parallel()\n\n\ttestFileExtension(t, \"foo.bar\", false, \"zzz\", \".bar\")\n\ttestFileExtension(t, \"foobar\", false, \"zzz\", \"\")\n\ttestFileExtension(t, \"foo.bar.baz\", false, \"zzz\", \".baz\")\n\ttestFileExtension(t, \"\", false, \"zzz\", \"\")\n\ttestFileExtension(t, \"/a/b/c.d/efg.jpg\", false, \".zzz\", \".jpg\")\n\n\ttestFileExtension(t, \"foo.bar\", true, \".zzz\", \".bar\")\n\ttestFileExtension(t, \"foobar.zzz\", true, \".zzz\", \"\")\n\ttestFileExtension(t, \"foo.bar.baz.fasthttp.gz\", true, \".fasthttp.gz\", \".baz\")\n\ttestFileExtension(t, \"\", true, \".zzz\", \"\")\n\ttestFileExtension(t, \"/a/b/c.d/efg.jpg.xxx\", true, \".xxx\", \".jpg\")\n}\n\nfunc testFileExtension(t *testing.T, path string, compressed bool, compressedFileSuffix, expectedExt string) {\n\tt.Helper()\n\n\text := fileExtension(path, compressed, compressedFileSuffix)\n\tif ext != expectedExt {\n\t\tt.Fatalf(\"unexpected file extension for file %q: %q. Expecting %q\", path, ext, expectedExt)\n\t}\n}\n\nfunc TestServeFileContentType(t *testing.T) {\n\t// This test can't run parallel as files in / might be changed by other tests.\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.SetMethod(MethodGet)\n\treq.SetRequestURI(\"http://foobar.com/baz\")\n\tctx.Init(&req, nil, nil)\n\n\tServeFile(&ctx, \"testdata/test.png\")\n\n\tvar resp Response\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\texpected := []byte(\"image/png\")\n\tif !bytes.Equal(resp.Header.ContentType(), expected) {\n\t\tt.Fatalf(\"Unexpected Content-Type, expected: %q got %q\", expected, resp.Header.ContentType())\n\t}\n}\n\nfunc TestServeFileDirectoryRedirect(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"http://foobar.com\")\n\tctx.Init(&req, nil, nil)\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFile(&ctx, \"fasthttputil\")\n\tif ctx.Response.StatusCode() != StatusFound {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil' without trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusFound)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFile(&ctx, \"fasthttputil/\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for directory '/fasthttputil/' with trailing slash. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tServeFile(&ctx, \"fs.go\")\n\tif ctx.Response.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"Unexpected status code %d for file '/fs.go'. Expecting %d.\", ctx.Response.StatusCode(), StatusOK)\n\t}\n}\n\nfunc TestFileCacheForZstd(t *testing.T) {\n\tf, err := os.CreateTemp(os.TempDir(), \"test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdata := bytes.Repeat([]byte(\"1\"), 1000)\n\tchangedData := bytes.Repeat([]byte(\"2\"), 1000)\n\t_, err = f.Write(data)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = f.Sync()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfs := FS{Root: os.TempDir(), Compress: true, CompressZstd: true, CacheDuration: time.Second * 60}\n\th := fs.NewRequestHandler()\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.Header.Set(\"Accept-Encoding\", \"zstd\")\n\treq.SetRequestURI(\"http://foobar.com/\" + strings.TrimPrefix(f.Name(), os.TempDir()))\n\tctx.Init(&req, nil, nil)\n\th(&ctx)\n\tif !bytes.Equal(ctx.Response.Header.ContentEncoding(), []byte(\"zstd\")) {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q. Expecting %q\", ctx.Response.Header.ContentEncoding(), \"zstd\")\n\t}\n\tctx.Response.Reset()\n\t_, err = f.Seek(0, io.SeekStart)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = f.Write(changedData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tf.Close()\n\th(&ctx)\n\tif !bytes.Equal(ctx.Response.Header.ContentEncoding(), []byte(\"zstd\")) {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q. Expecting %q\", ctx.Response.Header.ContentEncoding(), \"zstd\")\n\t}\n\td, err := acquireZstdReader(strings.NewReader(string(ctx.Response.Body())))\n\tif err != nil {\n\t\tt.Fatalf(\"invalid zstd reader\")\n\t}\n\tplainText, err := io.ReadAll(d)\n\td.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !bytes.Equal(plainText, data) {\n\t\tt.Fatalf(\"Unexpected response body %q. Expecting %q . Zstd cache doesn't work\", plainText, data)\n\t}\n\tctx.Request.Header.Del(\"Accept-Encoding\")\n\tctx.Response.Reset()\n\th(&ctx)\n\tif !bytes.Equal(ctx.Response.Header.ContentEncoding(), []byte(\"\")) {\n\t\tt.Fatalf(\"Unexpected 'Content-Encoding' %q. Expecting %q\", ctx.Response.Header.ContentEncoding(), \"\")\n\t}\n\tif !bytes.Equal(ctx.Response.Body(), changedData) {\n\t\tt.Fatalf(\"Unexpected response body %q. Expecting %q\", ctx.Response.Body(), data)\n\t}\n}\n"
  },
  {
    "path": "fuzz_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net/textproto\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc FuzzCookieParse(f *testing.F) {\n\tf.Add([]byte(`xxx=yyy`))\n\tf.Add([]byte(`xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b`))\n\tf.Add([]byte(\" \\n\\t\\\"\"))\n\n\tf.Fuzz(func(t *testing.T, cookie []byte) {\n\t\tvar c Cookie\n\n\t\t_ = c.ParseBytes(cookie)\n\n\t\tw := bytes.Buffer{}\n\t\tif _, err := c.WriteTo(&w); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n}\n\nfunc FuzzVisitHeaderParams(f *testing.F) {\n\tf.Add([]byte(`application/json; v=1; foo=bar; q=0.938; param=param; param=\"big fox\"; q=0.43`))\n\tf.Add([]byte(`*/*`))\n\tf.Add([]byte(`\\\\`))\n\tf.Add([]byte(`text/plain; foo=\"\\\\\\\"\\'\\\\''\\'\"`))\n\n\tf.Fuzz(func(t *testing.T, header []byte) {\n\t\tVisitHeaderParams(header, func(key, value []byte) bool {\n\t\t\tif len(key) == 0 {\n\t\t\t\tt.Errorf(\"Unexpected length zero parameter, failed input was: %s\", header)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t})\n}\n\nfunc FuzzResponseReadLimitBody(f *testing.F) {\n\tf.Add([]byte(\"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\"), 1024)\n\tf.Add([]byte(\" 0\\nTrAnsfer-EnCoding:0\\n\\n0\\r\\n1:0\\n        00\\n 000\\n\\n\"), 24922)\n\tf.Add([]byte(\" 0\\n0:\\n 0\\n :\\n\"), 1048532)\n\n\t// Case found by OSS-Fuzz.\n\tb, err := base64.StdEncoding.DecodeString(\"oeYAdyAyClRyYW5zZmVyLUVuY29kaW5nOmlka7AKCjANCiA6MAogOgogOgogPgAAAAAAAAAgICAhICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiA6CiA6CiAgOgogOgogYDogCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogOgogOgogIDoKIDoKIGA6IAoKIDoKBSAgOgogOgogOgogOgogIDoKIDoKIGA6IAAgIAA6CiA6CiA6CjoKIDoKIDoWCiAyIOgKIDogugogOjAKIDoKIDoKBSAgOgogOgogOgogOgogIDoKIDoKIGA6IAAgIAAAAAAAAABaYQ==\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tf.Add(b[:len(b)-2], int(binary.LittleEndian.Uint16(b[len(b)-2:])))\n\n\tf.Fuzz(func(t *testing.T, body []byte, maxBodySize int) {\n\t\tif len(body) > 1024*1024 || maxBodySize > 1024*1024 {\n\t\t\treturn\n\t\t}\n\t\t// Only test with a max for the body, otherwise a very large Content-Length will just OOM.\n\t\tif maxBodySize <= 0 {\n\t\t\treturn\n\t\t}\n\n\t\tres := AcquireResponse()\n\t\tdefer ReleaseResponse(res)\n\n\t\t_ = res.ReadLimitBody(bufio.NewReader(bytes.NewReader(body)), maxBodySize)\n\t})\n}\n\nfunc FuzzRequestReadLimitBody(f *testing.F) {\n\tf.Add([]byte(\"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nfoobar\\r\\n\\r\\n\"), 1024)\n\n\tf.Fuzz(func(t *testing.T, body []byte, maxBodySize int) {\n\t\tif len(body) > 1024*1024 || maxBodySize > 1024*1024 {\n\t\t\treturn\n\t\t}\n\t\t// Only test with a max for the body, otherwise a very large Content-Length will just OOM.\n\t\tif maxBodySize <= 0 {\n\t\t\treturn\n\t\t}\n\n\t\treq := AcquireRequest()\n\t\tdefer ReleaseRequest(req)\n\n\t\t_ = req.ReadLimitBody(bufio.NewReader(bytes.NewReader(body)), maxBodySize)\n\t})\n}\n\nfunc FuzzURIUpdateBytes(f *testing.F) {\n\tf.Add([]byte(`http://foobar.com/aaa/bb?cc`))\n\tf.Add([]byte(`//foobar.com/aaa/bb?cc`))\n\tf.Add([]byte(`/aaa/bb?cc`))\n\tf.Add([]byte(`xx?yy=abc`))\n\n\tf.Fuzz(func(t *testing.T, uri []byte) {\n\t\tvar u URI\n\n\t\tu.UpdateBytes(uri)\n\n\t\tw := bytes.Buffer{}\n\t\tif _, err := u.WriteTo(&w); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n}\n\nfunc FuzzURIParse(f *testing.F) {\n\tf.Add(`http://foobar.com/aaa/bb?cc#dd`)\n\tf.Add(`http://google.com?github.com`)\n\tf.Add(`http://google.com#@github.com`)\n\n\tf.Fuzz(func(t *testing.T, uri string) {\n\t\t// Limit the size of the URI to avoid OOMs or timeouts.\n\t\t// When using Server or Client the maximum URI is dicated by the maximum header size,\n\t\t// which defaults to defaultReadBufferSize (4096 bytes).\n\t\tif len(uri) > defaultReadBufferSize {\n\t\t\treturn\n\t\t}\n\n\t\tvar u URI\n\n\t\turi = strings.ToLower(uri)\n\n\t\tif !strings.HasPrefix(uri, \"http://\") && !strings.HasPrefix(uri, \"https://\") {\n\t\t\treturn\n\t\t}\n\n\t\tif u.Parse(nil, []byte(uri)) != nil {\n\t\t\treturn\n\t\t}\n\n\t\tnu, err := url.Parse(uri)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif string(u.Host()) != nu.Host {\n\t\t\tt.Fatalf(\"%q: unexpected host: %q. Expecting %q\", uri, u.Host(), nu.Host)\n\t\t}\n\t\tif string(u.QueryString()) != nu.RawQuery {\n\t\t\tt.Fatalf(\"%q: unexpected query string: %q. Expecting %q\", uri, u.QueryString(), nu.RawQuery)\n\t\t}\n\t})\n}\n\nfunc FuzzTestHeaderScanner(f *testing.F) {\n\tf.Add([]byte(\"Host: example.com\\r\\nUser-Agent: Go-http-client/1.1\\r\\nAccept-Encoding: gzip, deflate\\r\\n\\r\\n\"))\n\tf.Add([]byte(\"Content-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 27\\r\\n\\r\\nname=John+Doe&age=30\"))\n\n\tf.Fuzz(func(t *testing.T, data []byte) {\n\t\tif !bytes.Contains(data, []byte(\"\\r\\n\\r\\n\")) {\n\t\t\treturn\n\t\t}\n\t\tif len(data) > 1024*1024 {\n\t\t\treturn\n\t\t}\n\n\t\tt.Logf(\"%q\", data)\n\n\t\ttmp, herr := textproto.NewReader(bufio.NewReader(bytes.NewReader(data))).ReadMIMEHeader()\n\t\th := map[string][]string(tmp)\n\n\t\tvar s headerScanner\n\t\ts.b = data\n\t\tf := make(map[string][]string)\n\t\tfor s.next() {\n\t\t\t// ReadMIMEHeader normalizes header keys, headerScanner doesn't by default.\n\t\t\tnormalizeHeaderKey(s.key, false)\n\n\t\t\t// textproto.ReadMIMEHeader will validate the header value, since we compare\n\t\t\t// errors we should do this as well.\n\t\t\tfor _, c := range s.value {\n\t\t\t\tif !validHeaderValueByte(c) {\n\t\t\t\t\ts.err = fmt.Errorf(\"malformed MIME header: invalid byte %q in value %q for key %q\", c, s.value, s.key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif s.err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tkey := string(s.key)\n\t\t\tvalue := string(s.value)\n\n\t\t\tif _, ok := f[key]; !ok {\n\t\t\t\tf[key] = []string{}\n\t\t\t}\n\t\t\tf[key] = append(f[key], value)\n\t\t}\n\n\t\tif s.err != nil && herr == nil {\n\t\t\tt.Errorf(\"unexpected error from headerScanner: %v: %v\", s.err, h)\n\t\t} else if s.err == nil && herr != nil {\n\t\t\tt.Errorf(\"unexpected error from textproto.NewReader: %v: %v\", herr, f)\n\t\t}\n\n\t\tif !reflect.DeepEqual(h, f) {\n\t\t\tt.Errorf(\"headers mismatch:\\ntextproto: %v\\nfasthttp: %v\", h, f)\n\t\t}\n\t})\n}\n\nfunc FuzzRequestReadLimitBodyAllocations(f *testing.F) {\n\tf.Add([]byte(\"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nfoobar\\r\\n\\r\\n\"), 1024)\n\tf.Add([]byte(\"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nWithTabs: \\t v1 \\t\\r\\nWithTabs-Start: \\t \\t v1 \\r\\nWithTabs-End: v1 \\t \\t\\t\\t\\r\\nWithTabs-Multi-Line: \\t v1 \\t;\\r\\n \\t v2 \\t;\\r\\n\\t v3\\r\\n\\r\\n\"), 1024)\n\n\tf.Fuzz(func(t *testing.T, body []byte, maxBodySize int) {\n\t\tif len(body) > 1024*1024 || maxBodySize > 1024*1024 {\n\t\t\treturn\n\t\t}\n\t\t// Only test with a max for the body, otherwise a very large Content-Length will just OOM.\n\t\tif maxBodySize <= 0 {\n\t\t\treturn\n\t\t}\n\n\t\tt.Logf(\"%d %q\", maxBodySize, body)\n\n\t\treq := Request{}\n\t\ta := bytes.NewReader(body)\n\t\tb := bufio.NewReader(a)\n\n\t\tif err := req.ReadLimitBody(b, maxBodySize); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tn := testing.AllocsPerRun(200, func() {\n\t\t\treq.Reset()\n\t\t\ta.Reset(body)\n\t\t\tb.Reset(a)\n\n\t\t\t_ = req.ReadLimitBody(b, maxBodySize)\n\t\t})\n\n\t\tif n != 0 {\n\t\t\tt.Fatalf(\"expected 0 allocations, got %f\", n)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/valyala/fasthttp\n\ngo 1.24.0\n\ntoolchain go1.24.1\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.2.0\n\tgithub.com/klauspost/compress v1.18.4\n\tgithub.com/valyala/bytebufferpool v1.0.0\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/net v0.50.0\n\tgolang.org/x/sys v0.41.0\n)\n\nrequire golang.org/x/text v0.34.0 // indirect\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=\ngithub.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=\ngithub.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=\ngithub.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\n"
  },
  {
    "path": "header.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"iter\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nconst (\n\trChar = byte('\\r')\n\tnChar = byte('\\n')\n)\n\ntype header struct {\n\th       []argsKV\n\tcookies []argsKV\n\n\tbufK               []byte\n\tbufV               []byte\n\tcontentLengthBytes []byte\n\tcontentType        []byte\n\tprotocol           []byte\n\n\tmulHeader [][]byte\n\ttrailer   [][]byte\n\n\tcontentLength int\n\n\tdisableNormalizing    bool\n\tsecureErrorLogMessage bool\n\tnoHTTP11              bool\n\tconnectionClose       bool\n\tnoDefaultContentType  bool\n}\n\n// ResponseHeader represents HTTP response header.\n//\n// It is forbidden copying ResponseHeader instances.\n// Create new instances instead and use CopyTo.\n//\n// ResponseHeader instance MUST NOT be used from concurrently running\n// goroutines.\ntype ResponseHeader struct {\n\theader\n\n\tnoCopy noCopy\n\n\tstatusMessage   []byte\n\tcontentEncoding []byte\n\tserver          []byte\n\n\tstatusCode int\n\n\tnoDefaultDate bool\n}\n\n// RequestHeader represents HTTP request header.\n//\n// It is forbidden copying RequestHeader instances.\n// Create new instances instead and use CopyTo.\n//\n// RequestHeader instance MUST NOT be used from concurrently running\n// goroutines.\ntype RequestHeader struct {\n\theader\n\n\tnoCopy noCopy\n\n\tmethod     []byte\n\trequestURI []byte\n\thost       []byte\n\tuserAgent  []byte\n\n\t// stores an immutable copy of headers as they were received from the\n\t// wire.\n\trawHeaders []byte\n\n\tdisableSpecialHeader bool\n\tcookiesCollected     bool\n}\n\n// SetContentRange sets 'Content-Range: bytes startPos-endPos/contentLength'\n// header.\nfunc (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) {\n\tb := h.bufV[:0]\n\tb = append(b, strBytes...)\n\tb = append(b, ' ')\n\tb = AppendUint(b, startPos)\n\tb = append(b, '-')\n\tb = AppendUint(b, endPos)\n\tb = append(b, '/')\n\tb = AppendUint(b, contentLength)\n\th.bufV = b\n\n\th.setNonSpecial(strContentRange, h.bufV)\n}\n\n// SetByteRange sets 'Range: bytes=startPos-endPos' header.\n//\n//   - If startPos is negative, then 'bytes=-startPos' value is set.\n//   - If endPos is negative, then 'bytes=startPos-' value is set.\nfunc (h *RequestHeader) SetByteRange(startPos, endPos int) {\n\tb := h.bufV[:0]\n\tb = append(b, strBytes...)\n\tb = append(b, '=')\n\tif startPos >= 0 {\n\t\tb = AppendUint(b, startPos)\n\t} else {\n\t\tendPos = -startPos\n\t}\n\tb = append(b, '-')\n\tif endPos >= 0 {\n\t\tb = AppendUint(b, endPos)\n\t}\n\th.bufV = b\n\n\th.setNonSpecial(strRange, h.bufV)\n}\n\n// StatusCode returns response status code.\nfunc (h *ResponseHeader) StatusCode() int {\n\tif h.statusCode == 0 {\n\t\treturn StatusOK\n\t}\n\treturn h.statusCode\n}\n\n// SetStatusCode sets response status code.\nfunc (h *ResponseHeader) SetStatusCode(statusCode int) {\n\th.statusCode = statusCode\n}\n\n// StatusMessage returns response status message.\nfunc (h *ResponseHeader) StatusMessage() []byte {\n\treturn h.statusMessage\n}\n\n// SetStatusMessage sets response status message bytes.\nfunc (h *ResponseHeader) SetStatusMessage(statusMessage []byte) {\n\th.statusMessage = append(h.statusMessage[:0], statusMessage...)\n}\n\n// SetProtocol sets response protocol bytes.\nfunc (h *ResponseHeader) SetProtocol(protocol []byte) {\n\th.protocol = append(h.protocol[:0], protocol...)\n}\n\n// SetLastModified sets 'Last-Modified' header to the given value.\nfunc (h *ResponseHeader) SetLastModified(t time.Time) {\n\th.bufV = AppendHTTPDate(h.bufV[:0], t)\n\th.setNonSpecial(strLastModified, h.bufV)\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (h *header) ConnectionClose() bool {\n\treturn h.connectionClose\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (h *header) SetConnectionClose() {\n\th.connectionClose = true\n}\n\n// ResetConnectionClose clears 'Connection: close' header if it exists.\nfunc (h *header) ResetConnectionClose() {\n\tif h.connectionClose {\n\t\th.connectionClose = false\n\t\th.h = delAllArgs(h.h, HeaderConnection)\n\t}\n}\n\n// ConnectionUpgrade returns true if 'Connection: Upgrade' header is set.\nfunc (h *ResponseHeader) ConnectionUpgrade() bool {\n\treturn hasHeaderValue(h.Peek(HeaderConnection), strUpgrade)\n}\n\n// ConnectionUpgrade returns true if 'Connection: Upgrade' header is set.\nfunc (h *RequestHeader) ConnectionUpgrade() bool {\n\treturn hasHeaderValue(h.Peek(HeaderConnection), strUpgrade)\n}\n\n// PeekCookie is able to returns cookie by a given key from response.\nfunc (h *ResponseHeader) PeekCookie(key string) []byte {\n\treturn peekArgStr(h.cookies, key)\n}\n\n// ContentLength returns Content-Length header value.\n//\n// It may be negative:\n// -1 means Transfer-Encoding: chunked.\n// -2 means Transfer-Encoding: identity.\nfunc (h *ResponseHeader) ContentLength() int {\n\treturn h.contentLength\n}\n\n// ContentLength returns Content-Length header value.\n//\n// It may be negative:\n// -1 means Transfer-Encoding: chunked.\n// -2 means Transfer-Encoding: identity.\nfunc (h *RequestHeader) ContentLength() int {\n\tif h.disableSpecialHeader {\n\t\t// Parse Content-Length from raw headers when special headers are disabled\n\t\tv := peekArgBytes(h.h, strContentLength)\n\t\tif len(v) == 0 {\n\t\t\t// Check for Transfer-Encoding: chunked\n\t\t\tte := peekArgBytes(h.h, strTransferEncoding)\n\t\t\tif bytes.Equal(te, strChunked) {\n\t\t\t\treturn -1 // chunked\n\t\t\t}\n\t\t\treturn -2 // identity\n\t\t}\n\t\tn, err := parseContentLength(v)\n\t\tif err != nil {\n\t\t\treturn -2 // identity on parse error\n\t\t}\n\t\treturn n\n\t}\n\treturn h.contentLength\n}\n\n// SetContentLength sets Content-Length header value.\n//\n// Content-Length may be negative:\n// -1 means Transfer-Encoding: chunked.\n// -2 means Transfer-Encoding: identity.\nfunc (h *ResponseHeader) SetContentLength(contentLength int) {\n\tif h.mustSkipContentLength() {\n\t\treturn\n\t}\n\th.contentLength = contentLength\n\tif contentLength >= 0 {\n\t\th.contentLengthBytes = AppendUint(h.contentLengthBytes[:0], contentLength)\n\t\th.h = delAllArgs(h.h, HeaderTransferEncoding)\n\t\treturn\n\t} else if contentLength == -1 {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t\th.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)\n\t\treturn\n\t}\n\th.SetConnectionClose()\n}\n\nfunc (h *ResponseHeader) mustSkipContentLength() bool {\n\t// From http/1.1 specs:\n\t// All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body\n\tstatusCode := h.StatusCode()\n\n\t// Fast path.\n\tif statusCode < 100 || statusCode == StatusOK {\n\t\treturn false\n\t}\n\n\t// Slow path.\n\treturn statusCode == StatusNotModified || statusCode == StatusNoContent || statusCode < 200\n}\n\n// SetContentLength sets Content-Length header value.\n//\n// Negative content-length sets 'Transfer-Encoding: chunked' header.\nfunc (h *RequestHeader) SetContentLength(contentLength int) {\n\th.contentLength = contentLength\n\tif contentLength >= 0 {\n\t\th.contentLengthBytes = AppendUint(h.contentLengthBytes[:0], contentLength)\n\t\th.h = delAllArgs(h.h, HeaderTransferEncoding)\n\t} else {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t\th.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)\n\t}\n}\n\nfunc (h *ResponseHeader) isCompressibleContentType() bool {\n\tcontentType := h.ContentType()\n\treturn bytes.HasPrefix(contentType, strTextSlash) ||\n\t\tbytes.HasPrefix(contentType, strApplicationSlash) ||\n\t\tbytes.HasPrefix(contentType, strImageSVG) ||\n\t\tbytes.HasPrefix(contentType, strImageIcon) ||\n\t\tbytes.HasPrefix(contentType, strFontSlash) ||\n\t\tbytes.HasPrefix(contentType, strMultipartSlash)\n}\n\n// ContentType returns Content-Type header value.\nfunc (h *ResponseHeader) ContentType() []byte {\n\tcontentType := h.contentType\n\tif !h.noDefaultContentType && len(h.contentType) == 0 {\n\t\tcontentType = defaultContentType\n\t}\n\treturn contentType\n}\n\n// SetContentType sets Content-Type header value.\nfunc (h *header) SetContentType(contentType string) {\n\th.contentType = append(h.contentType[:0], contentType...)\n}\n\n// SetContentTypeBytes sets Content-Type header value.\nfunc (h *header) SetContentTypeBytes(contentType []byte) {\n\th.contentType = append(h.contentType[:0], contentType...)\n}\n\n// ContentEncoding returns Content-Encoding header value.\nfunc (h *ResponseHeader) ContentEncoding() []byte {\n\treturn h.contentEncoding\n}\n\n// SetContentEncoding sets Content-Encoding header value.\nfunc (h *ResponseHeader) SetContentEncoding(contentEncoding string) {\n\th.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)\n}\n\n// SetContentEncodingBytes sets Content-Encoding header value.\nfunc (h *ResponseHeader) SetContentEncodingBytes(contentEncoding []byte) {\n\th.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)\n}\n\n// addVaryBytes add value to the 'Vary' header if it's not included.\nfunc (h *ResponseHeader) addVaryBytes(value []byte) {\n\tv := h.peek(strVary)\n\tif len(v) == 0 {\n\t\t// 'Vary' is not set\n\t\th.SetBytesV(HeaderVary, value)\n\t} else if !bytes.Contains(v, value) {\n\t\t// 'Vary' is set and not contains target value\n\t\th.SetBytesV(HeaderVary, append(append(v, ','), value...))\n\t} // else: 'Vary' is set and contains target value\n}\n\n// Server returns Server header value.\nfunc (h *ResponseHeader) Server() []byte {\n\treturn h.server\n}\n\n// SetServer sets Server header value.\nfunc (h *ResponseHeader) SetServer(server string) {\n\th.server = append(h.server[:0], server...)\n}\n\n// SetServerBytes sets Server header value.\nfunc (h *ResponseHeader) SetServerBytes(server []byte) {\n\th.server = append(h.server[:0], server...)\n}\n\n// ContentType returns Content-Type header value.\nfunc (h *RequestHeader) ContentType() []byte {\n\tif h.disableSpecialHeader {\n\t\treturn peekArgBytes(h.h, []byte(HeaderContentType))\n\t}\n\treturn h.contentType\n}\n\n// ContentEncoding returns Content-Encoding header value.\nfunc (h *RequestHeader) ContentEncoding() []byte {\n\treturn peekArgBytes(h.h, strContentEncoding)\n}\n\n// SetContentEncoding sets Content-Encoding header value.\nfunc (h *RequestHeader) SetContentEncoding(contentEncoding string) {\n\th.SetBytesK(strContentEncoding, contentEncoding)\n}\n\n// SetContentEncodingBytes sets Content-Encoding header value.\nfunc (h *RequestHeader) SetContentEncodingBytes(contentEncoding []byte) {\n\th.setNonSpecial(strContentEncoding, contentEncoding)\n}\n\n// SetMultipartFormBoundary sets the following Content-Type:\n// 'multipart/form-data; boundary=...'\n// where ... is substituted by the given boundary.\nfunc (h *RequestHeader) SetMultipartFormBoundary(boundary string) {\n\tb := h.bufV[:0]\n\tb = append(b, strMultipartFormData...)\n\tb = append(b, ';', ' ')\n\tb = append(b, strBoundary...)\n\tb = append(b, '=')\n\tb = append(b, boundary...)\n\th.bufV = b\n\n\th.SetContentTypeBytes(h.bufV)\n}\n\n// SetMultipartFormBoundaryBytes sets the following Content-Type:\n// 'multipart/form-data; boundary=...'\n// where ... is substituted by the given boundary.\nfunc (h *RequestHeader) SetMultipartFormBoundaryBytes(boundary []byte) {\n\tb := h.bufV[:0]\n\tb = append(b, strMultipartFormData...)\n\tb = append(b, ';', ' ')\n\tb = append(b, strBoundary...)\n\tb = append(b, '=')\n\tb = append(b, boundary...)\n\th.bufV = b\n\n\th.SetContentTypeBytes(h.bufV)\n}\n\n// SetTrailer sets header Trailer value for chunked response\n// to indicate which headers will be sent after the body.\n//\n// Use Set to set the trailer header later.\n//\n// Trailers are only supported with chunked transfer.\n// Trailers allow the sender to include additional headers at the end of chunked messages.\n//\n// The following trailers are forbidden:\n// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),\n// 2. routing (e.g., Host),\n// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),\n// 4. authentication (e.g., see [RFC7235] and [RFC6265]),\n// 5. response control data (e.g., see Section 7.1 of [RFC7231]),\n// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)\n//\n// Return ErrBadTrailer if contain any forbidden trailers.\nfunc (h *header) SetTrailer(trailer string) error {\n\treturn h.SetTrailerBytes(s2b(trailer))\n}\n\n// SetTrailerBytes sets Trailer header value for chunked response\n// to indicate which headers will be sent after the body.\n//\n// Use Set to set the trailer header later.\n//\n// Trailers are only supported with chunked transfer.\n// Trailers allow the sender to include additional headers at the end of chunked messages.\n//\n// The following trailers are forbidden:\n// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),\n// 2. routing (e.g., Host),\n// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),\n// 4. authentication (e.g., see [RFC7235] and [RFC6265]),\n// 5. response control data (e.g., see Section 7.1 of [RFC7231]),\n// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)\n//\n// Return ErrBadTrailer if contain any forbidden trailers.\nfunc (h *header) SetTrailerBytes(trailer []byte) error {\n\th.trailer = h.trailer[:0]\n\treturn h.AddTrailerBytes(trailer)\n}\n\n// AddTrailer add Trailer header value for chunked response\n// to indicate which headers will be sent after the body.\n//\n// Use Set to set the trailer header later.\n//\n// Trailers are only supported with chunked transfer.\n// Trailers allow the sender to include additional headers at the end of chunked messages.\n//\n// The following trailers are forbidden:\n// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),\n// 2. routing (e.g., Host),\n// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),\n// 4. authentication (e.g., see [RFC7235] and [RFC6265]),\n// 5. response control data (e.g., see Section 7.1 of [RFC7231]),\n// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)\n//\n// Return ErrBadTrailer if contain any forbidden trailers.\nfunc (h *header) AddTrailer(trailer string) error {\n\treturn h.AddTrailerBytes(s2b(trailer))\n}\n\nvar (\n\tErrBadTrailer                    = errors.New(\"contain forbidden trailer\")\n\tErrReadingResponseHeaders        = errors.New(\"error when reading response headers\")\n\tErrReadingResponseTrailer        = errors.New(\"error when reading response trailer\")\n\tErrResponseFirstLineMissingSpace = errors.New(\"cannot find whitespace in the first line of response\")\n\tErrUnexpectedStatusCodeChar      = errors.New(\"unexpected char at the end of status code\")\n\tErrMissingRequestMethod          = errors.New(\"cannot find http request method\")\n\tErrUnsupportedRequestMethod      = errors.New(\"unsupported http request method\")\n\tErrExtraWhitespaceInRequestLine  = errors.New(\"extra whitespace in request line\")\n\tErrEmptyRequestURI               = errors.New(\"requestURI cannot be empty\")\n\tErrDuplicateContentLength        = errors.New(\"duplicate Content-Length header\")\n\tErrUnsupportedTransferEncoding   = errors.New(\"unsupported Transfer-Encoding\")\n\tErrNonNumericChars               = errors.New(\"non-numeric chars found\")\n\tErrNeedMore                      = errors.New(\"need more data: cannot find trailing lf\")\n\tErrSmallReadBuffer               = errors.New(\"small read buffer. Increase ReadBufferSize\")\n)\n\n// AddTrailerBytes add Trailer header value for chunked response\n// to indicate which headers will be sent after the body.\n//\n// Use Set to set the trailer header later.\n//\n// Trailers are only supported with chunked transfer.\n// Trailers allow the sender to include additional headers at the end of chunked messages.\n//\n// The following trailers are forbidden:\n// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),\n// 2. routing (e.g., Host),\n// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),\n// 4. authentication (e.g., see [RFC7235] and [RFC6265]),\n// 5. response control data (e.g., see Section 7.1 of [RFC7231]),\n// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)\n//\n// Return ErrBadTrailer if contain any forbidden trailers.\nfunc (h *header) AddTrailerBytes(trailer []byte) (err error) {\n\tfor i := -1; i+1 < len(trailer); {\n\t\ttrailer = trailer[i+1:]\n\t\ti = bytes.IndexByte(trailer, ',')\n\t\tif i < 0 {\n\t\t\ti = len(trailer)\n\t\t}\n\t\tkey := trailer[:i]\n\t\tfor len(key) > 0 && key[0] == ' ' {\n\t\t\tkey = key[1:]\n\t\t}\n\t\tfor len(key) > 0 && key[len(key)-1] == ' ' {\n\t\t\tkey = key[:len(key)-1]\n\t\t}\n\t\t// Forbidden by RFC 7230, section 4.1.2\n\t\tif isBadTrailer(key) {\n\t\t\terr = ErrBadTrailer\n\t\t\tcontinue\n\t\t}\n\t\th.bufK = append(h.bufK[:0], key...)\n\t\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(h.bufK, ' ') != -1)\n\t\tif cap(h.trailer) > len(h.trailer) {\n\t\t\th.trailer = h.trailer[:len(h.trailer)+1]\n\t\t\th.trailer[len(h.trailer)-1] = append(h.trailer[len(h.trailer)-1][:0], h.bufK...)\n\t\t} else {\n\t\t\tkey = make([]byte, len(h.bufK))\n\t\t\tcopy(key, h.bufK)\n\t\t\th.trailer = append(h.trailer, key)\n\t\t}\n\t}\n\n\treturn err\n}\n\n// validHeaderFieldByte returns true if c valid header field byte\n// as defined by RFC 7230.\nfunc validHeaderFieldByte(c byte) bool {\n\treturn c < 128 && validHeaderFieldByteTable[c] == 1\n}\n\n// validHeaderValueByte returns true if c valid header value byte\n// as defined by RFC 7230.\nfunc validHeaderValueByte(c byte) bool {\n\treturn validHeaderValueByteTable[c] == 1\n}\n\n// isValidHeaderKey returns true if a is a valid header key.\nfunc isValidHeaderKey(a []byte) bool {\n\tif len(a) == 0 {\n\t\treturn false\n\t}\n\n\t// See if a looks like a header key. If not, return it unchanged.\n\tnoCanon := false\n\tfor _, c := range a {\n\t\tif validHeaderFieldByte(c) {\n\t\t\tcontinue\n\t\t}\n\t\t// Don't canonicalize.\n\t\tif c == ' ' {\n\t\t\t// We accept invalid headers with a space before the\n\t\t\t// colon, but must not canonicalize them.\n\t\t\t// See https://go.dev/issue/34540.\n\t\t\tnoCanon = true\n\t\t\tcontinue\n\t\t}\n\t\treturn false\n\t}\n\tif noCanon {\n\t\treturn true\n\t}\n\n\treturn true\n}\n\n// VisitHeaderParams calls f for each parameter in the given header bytes.\n// It stops processing when f returns false or an invalid parameter is found.\n// Parameter values may be quoted, in which case \\ is treated as an escape\n// character, and the value is unquoted before being passed to value.\n// See: https://www.rfc-editor.org/rfc/rfc9110#section-5.6.6\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\nfunc VisitHeaderParams(b []byte, f func(key, value []byte) bool) {\n\tfor len(b) > 0 {\n\t\tidxSemi := 0\n\t\tfor idxSemi < len(b) && b[idxSemi] != ';' {\n\t\t\tidxSemi++\n\t\t}\n\t\tif idxSemi >= len(b) {\n\t\t\treturn\n\t\t}\n\t\tb = b[idxSemi+1:]\n\t\tfor len(b) > 0 && b[0] == ' ' {\n\t\t\tb = b[1:]\n\t\t}\n\n\t\tn := 0\n\t\tif len(b) == 0 || !validHeaderFieldByte(b[n]) {\n\t\t\treturn\n\t\t}\n\t\tn++\n\t\tfor n < len(b) && validHeaderFieldByte(b[n]) {\n\t\t\tn++\n\t\t}\n\n\t\tif n >= len(b)-1 || b[n] != '=' {\n\t\t\treturn\n\t\t}\n\t\tparam := b[:n]\n\t\tn++\n\n\t\tswitch {\n\t\tcase validHeaderFieldByte(b[n]):\n\t\t\tm := n\n\t\t\tn++\n\t\t\tfor n < len(b) && validHeaderFieldByte(b[n]) {\n\t\t\t\tn++\n\t\t\t}\n\t\t\tif !f(param, b[m:n]) {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase b[n] == '\"':\n\t\t\tfoundEndQuote := false\n\t\t\tescaping := false\n\t\t\tn++\n\t\t\tm := n\n\t\t\tfor ; n < len(b); n++ {\n\t\t\t\tif b[n] == '\"' && !escaping {\n\t\t\t\t\tfoundEndQuote = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tescaping = (b[n] == '\\\\' && !escaping)\n\t\t\t}\n\t\t\tif !foundEndQuote {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !f(param, b[m:n]) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tn++\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t\tb = b[n:]\n\t}\n}\n\n// MultipartFormBoundary returns boundary part\n// from 'multipart/form-data; boundary=...' Content-Type.\nfunc (h *RequestHeader) MultipartFormBoundary() []byte {\n\tb := h.ContentType()\n\tif !bytes.HasPrefix(b, strMultipartFormData) {\n\t\treturn nil\n\t}\n\tb = b[len(strMultipartFormData):]\n\tif len(b) == 0 || b[0] != ';' {\n\t\treturn nil\n\t}\n\n\tvar n int\n\tfor len(b) > 0 {\n\t\tn++\n\t\tfor len(b) > n && b[n] == ' ' {\n\t\t\tn++\n\t\t}\n\t\tb = b[n:]\n\t\tif !bytes.HasPrefix(b, strBoundary) {\n\t\t\tif n = bytes.IndexByte(b, ';'); n < 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tb = b[len(strBoundary):]\n\t\tif len(b) == 0 || b[0] != '=' {\n\t\t\treturn nil\n\t\t}\n\t\tb = b[1:]\n\t\tif n = bytes.IndexByte(b, ';'); n >= 0 {\n\t\t\tb = b[:n]\n\t\t}\n\t\tif len(b) > 1 && b[0] == '\"' && b[len(b)-1] == '\"' {\n\t\t\tb = b[1 : len(b)-1]\n\t\t}\n\t\treturn b\n\t}\n\treturn nil\n}\n\n// Host returns Host header value.\nfunc (h *RequestHeader) Host() []byte {\n\tif h.disableSpecialHeader {\n\t\treturn peekArgBytes(h.h, []byte(HeaderHost))\n\t}\n\treturn h.host\n}\n\n// SetHost sets Host header value.\nfunc (h *RequestHeader) SetHost(host string) {\n\th.host = append(h.host[:0], host...)\n}\n\n// SetHostBytes sets Host header value.\nfunc (h *RequestHeader) SetHostBytes(host []byte) {\n\th.host = append(h.host[:0], host...)\n}\n\n// UserAgent returns User-Agent header value.\nfunc (h *RequestHeader) UserAgent() []byte {\n\tif h.disableSpecialHeader {\n\t\treturn peekArgBytes(h.h, []byte(HeaderUserAgent))\n\t}\n\treturn h.userAgent\n}\n\n// SetUserAgent sets User-Agent header value.\nfunc (h *RequestHeader) SetUserAgent(userAgent string) {\n\th.userAgent = append(h.userAgent[:0], userAgent...)\n}\n\n// SetUserAgentBytes sets User-Agent header value.\nfunc (h *RequestHeader) SetUserAgentBytes(userAgent []byte) {\n\th.userAgent = append(h.userAgent[:0], userAgent...)\n}\n\n// Referer returns Referer header value.\nfunc (h *RequestHeader) Referer() []byte {\n\treturn peekArgBytes(h.h, strReferer)\n}\n\n// SetReferer sets Referer header value.\nfunc (h *RequestHeader) SetReferer(referer string) {\n\th.SetBytesK(strReferer, referer)\n}\n\n// SetRefererBytes sets Referer header value.\nfunc (h *RequestHeader) SetRefererBytes(referer []byte) {\n\th.setNonSpecial(strReferer, referer)\n}\n\n// Method returns HTTP request method.\nfunc (h *RequestHeader) Method() []byte {\n\tif len(h.method) == 0 {\n\t\treturn []byte(MethodGet)\n\t}\n\treturn h.method\n}\n\n// SetMethod sets HTTP request method.\nfunc (h *RequestHeader) SetMethod(method string) {\n\th.method = append(h.method[:0], method...)\n}\n\n// SetMethodBytes sets HTTP request method.\nfunc (h *RequestHeader) SetMethodBytes(method []byte) {\n\th.method = append(h.method[:0], method...)\n}\n\n// Protocol returns HTTP protocol.\nfunc (h *header) Protocol() []byte {\n\tif len(h.protocol) == 0 {\n\t\treturn strHTTP11\n\t}\n\treturn h.protocol\n}\n\n// SetProtocol sets HTTP request protocol.\nfunc (h *RequestHeader) SetProtocol(protocol string) {\n\th.protocol = append(h.protocol[:0], protocol...)\n\th.noHTTP11 = !bytes.Equal(h.protocol, strHTTP11)\n}\n\n// SetProtocolBytes sets HTTP request protocol.\nfunc (h *RequestHeader) SetProtocolBytes(protocol []byte) {\n\th.protocol = append(h.protocol[:0], protocol...)\n\th.noHTTP11 = !bytes.Equal(h.protocol, strHTTP11)\n}\n\n// RequestURI returns RequestURI from the first HTTP request line.\nfunc (h *RequestHeader) RequestURI() []byte {\n\trequestURI := h.requestURI\n\tif len(requestURI) == 0 {\n\t\trequestURI = strSlash\n\t}\n\treturn requestURI\n}\n\n// SetRequestURI sets RequestURI for the first HTTP request line.\n// RequestURI must be properly encoded.\n// Use URI.RequestURI for constructing proper RequestURI if unsure.\nfunc (h *RequestHeader) SetRequestURI(requestURI string) {\n\th.requestURI = append(h.requestURI[:0], requestURI...)\n}\n\n// SetRequestURIBytes sets RequestURI for the first HTTP request line.\n// RequestURI must be properly encoded.\n// Use URI.RequestURI for constructing proper RequestURI if unsure.\nfunc (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {\n\th.requestURI = append(h.requestURI[:0], requestURI...)\n}\n\n// IsGet returns true if request method is GET.\nfunc (h *RequestHeader) IsGet() bool {\n\treturn string(h.Method()) == MethodGet\n}\n\n// IsPost returns true if request method is POST.\nfunc (h *RequestHeader) IsPost() bool {\n\treturn string(h.Method()) == MethodPost\n}\n\n// IsPut returns true if request method is PUT.\nfunc (h *RequestHeader) IsPut() bool {\n\treturn string(h.Method()) == MethodPut\n}\n\n// IsHead returns true if request method is HEAD.\nfunc (h *RequestHeader) IsHead() bool {\n\treturn string(h.Method()) == MethodHead\n}\n\n// IsDelete returns true if request method is DELETE.\nfunc (h *RequestHeader) IsDelete() bool {\n\treturn string(h.Method()) == MethodDelete\n}\n\n// IsConnect returns true if request method is CONNECT.\nfunc (h *RequestHeader) IsConnect() bool {\n\treturn string(h.Method()) == MethodConnect\n}\n\n// IsOptions returns true if request method is OPTIONS.\nfunc (h *RequestHeader) IsOptions() bool {\n\treturn string(h.Method()) == MethodOptions\n}\n\n// IsTrace returns true if request method is TRACE.\nfunc (h *RequestHeader) IsTrace() bool {\n\treturn string(h.Method()) == MethodTrace\n}\n\n// IsPatch returns true if request method is PATCH.\nfunc (h *RequestHeader) IsPatch() bool {\n\treturn string(h.Method()) == MethodPatch\n}\n\n// IsHTTP11 returns true if the header is HTTP/1.1.\nfunc (h *header) IsHTTP11() bool {\n\treturn !h.noHTTP11\n}\n\n// HasAcceptEncoding returns true if the header contains\n// the given Accept-Encoding value.\nfunc (h *RequestHeader) HasAcceptEncoding(acceptEncoding string) bool {\n\th.bufV = append(h.bufV[:0], acceptEncoding...)\n\treturn h.HasAcceptEncodingBytes(h.bufV)\n}\n\n// HasAcceptEncodingBytes returns true if the header contains\n// the given Accept-Encoding value.\nfunc (h *RequestHeader) HasAcceptEncodingBytes(acceptEncoding []byte) bool {\n\tae := h.peek(strAcceptEncoding)\n\tn := bytes.Index(ae, acceptEncoding)\n\tif n < 0 {\n\t\treturn false\n\t}\n\tb := ae[n+len(acceptEncoding):]\n\tif len(b) > 0 && b[0] != ',' {\n\t\treturn false\n\t}\n\tif n == 0 {\n\t\treturn true\n\t}\n\treturn ae[n-1] == ' '\n}\n\n// Len returns the number of headers set,\n// i.e. the number of times f is called in VisitAll.\nfunc (h *ResponseHeader) Len() int {\n\tn := 0\n\tfor range h.All() {\n\t\tn++\n\t}\n\treturn n\n}\n\n// Len returns the number of headers set,\n// i.e. the number of times f is called in VisitAll.\nfunc (h *RequestHeader) Len() int {\n\tn := 0\n\tfor range h.All() {\n\t\tn++\n\t}\n\treturn n\n}\n\n// DisableSpecialHeader disables special header processing.\n// fasthttp will not set any special headers for you, such as Host, Content-Type, User-Agent, etc.\n// You must set everything yourself.\n// If RequestHeader.Read() is called, special headers will be ignored.\n// This can be used to control case and order of special headers.\n// This is generally not recommended.\n// The previous setting is returned.\nfunc (h *RequestHeader) DisableSpecialHeader() bool {\n\torig := h.disableSpecialHeader\n\th.disableSpecialHeader = true\n\treturn orig\n}\n\n// EnableSpecialHeader enables special header processing.\n// fasthttp will send Host, Content-Type, User-Agent, etc headers for you.\n// This is suggested and enabled by default.\n// The previous setting is returned.\nfunc (h *RequestHeader) EnableSpecialHeader() bool {\n\torig := h.disableSpecialHeader\n\th.disableSpecialHeader = false\n\treturn orig\n}\n\n// DisableNormalizing disables header names' normalization.\n//\n// By default all the header names are normalized by uppercasing\n// the first letter and all the first letters following dashes,\n// while lowercasing all the other letters.\n// Examples:\n//\n//   - CONNECTION -> Connection\n//   - conteNT-tYPE -> Content-Type\n//   - foo-bar-baz -> Foo-Bar-Baz\n//\n// Disable header names' normalization only if know what are you doing.\n// The previous setting is returned.\nfunc (h *header) DisableNormalizing() bool {\n\torig := h.disableNormalizing\n\th.disableNormalizing = true\n\treturn orig\n}\n\n// EnableNormalizing enables header names' normalization.\n//\n// Header names are normalized by uppercasing the first letter and\n// all the first letters following dashes, while lowercasing all\n// the other letters.\n// Examples:\n//\n//   - CONNECTION -> Connection\n//   - conteNT-tYPE -> Content-Type\n//   - foo-bar-baz -> Foo-Bar-Baz\n//\n// This is enabled by default unless disabled using DisableNormalizing().\n// The previous setting is returned.\nfunc (h *header) EnableNormalizing() bool {\n\torig := h.disableNormalizing\n\th.disableNormalizing = false\n\treturn orig\n}\n\n// SetNoDefaultContentType allows you to control if a default Content-Type header will be set (false) or not (true).\nfunc (h *header) SetNoDefaultContentType(noDefaultContentType bool) {\n\th.noDefaultContentType = noDefaultContentType\n}\n\n// Reset clears response header.\nfunc (h *ResponseHeader) Reset() {\n\th.disableNormalizing = false\n\th.SetNoDefaultContentType(false)\n\th.noDefaultDate = false\n\th.resetSkipNormalize()\n}\n\nfunc (h *ResponseHeader) resetSkipNormalize() {\n\th.noHTTP11 = false\n\th.connectionClose = false\n\n\th.statusCode = 0\n\th.statusMessage = h.statusMessage[:0]\n\th.protocol = h.protocol[:0]\n\th.contentLength = 0\n\th.contentLengthBytes = h.contentLengthBytes[:0]\n\n\th.contentType = h.contentType[:0]\n\th.contentEncoding = h.contentEncoding[:0]\n\th.server = h.server[:0]\n\n\th.h = h.h[:0]\n\th.cookies = h.cookies[:0]\n\th.trailer = h.trailer[:0]\n\th.mulHeader = h.mulHeader[:0]\n}\n\n// Reset clears request header.\nfunc (h *RequestHeader) Reset() {\n\th.disableSpecialHeader = false\n\th.disableNormalizing = false\n\th.SetNoDefaultContentType(false)\n\th.resetSkipNormalize()\n}\n\nfunc (h *RequestHeader) resetSkipNormalize() {\n\th.noHTTP11 = false\n\th.connectionClose = false\n\n\th.contentLength = 0\n\th.contentLengthBytes = h.contentLengthBytes[:0]\n\n\th.method = h.method[:0]\n\th.protocol = h.protocol[:0]\n\th.requestURI = h.requestURI[:0]\n\th.host = h.host[:0]\n\th.contentType = h.contentType[:0]\n\th.userAgent = h.userAgent[:0]\n\th.trailer = h.trailer[:0]\n\th.mulHeader = h.mulHeader[:0]\n\n\th.h = h.h[:0]\n\th.cookies = h.cookies[:0]\n\th.cookiesCollected = false\n\n\th.rawHeaders = h.rawHeaders[:0]\n}\n\nfunc (h *header) copyTo(dst *header) {\n\tdst.disableNormalizing = h.disableNormalizing\n\tdst.noHTTP11 = h.noHTTP11\n\tdst.connectionClose = h.connectionClose\n\tdst.noDefaultContentType = h.noDefaultContentType\n\tdst.contentLength = h.contentLength\n\tdst.contentLengthBytes = append(dst.contentLengthBytes, h.contentLengthBytes...)\n\n\tdst.protocol = append(dst.protocol, h.protocol...)\n\tdst.contentType = append(dst.contentType, h.contentType...)\n\tdst.trailer = copyTrailer(dst.trailer, h.trailer)\n\tdst.cookies = copyArgs(dst.cookies, h.cookies)\n\tdst.h = copyArgs(dst.h, h.h)\n}\n\n// CopyTo copies all the headers to dst.\nfunc (h *ResponseHeader) CopyTo(dst *ResponseHeader) {\n\tdst.Reset()\n\n\th.copyTo(&dst.header)\n\n\tdst.noDefaultDate = h.noDefaultDate\n\tdst.statusCode = h.statusCode\n\tdst.statusMessage = append(dst.statusMessage, h.statusMessage...)\n\tdst.contentEncoding = append(dst.contentEncoding, h.contentEncoding...)\n\tdst.server = append(dst.server, h.server...)\n}\n\n// CopyTo copies all the headers to dst.\nfunc (h *RequestHeader) CopyTo(dst *RequestHeader) {\n\tdst.Reset()\n\n\th.copyTo(&dst.header)\n\n\tdst.method = append(dst.method, h.method...)\n\tdst.requestURI = append(dst.requestURI, h.requestURI...)\n\tdst.host = append(dst.host, h.host...)\n\tdst.userAgent = append(dst.userAgent, h.userAgent...)\n\tdst.cookiesCollected = h.cookiesCollected\n\tdst.rawHeaders = append(dst.rawHeaders, h.rawHeaders...)\n}\n\n// All returns an iterator over key-value pairs in h.\n//\n// The key and value may invalid outside the iteration loop.\n// Copy key and/or value contents for each iteration if you need retaining\n// them.\n//\n// Making modifications to the ResponseHeader during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (h *ResponseHeader) All() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\tif len(h.contentLengthBytes) > 0 && !yield(strContentLength, h.contentLengthBytes) {\n\t\t\treturn\n\t\t}\n\t\tif contentType := h.ContentType(); len(contentType) > 0 && !yield(strContentType, contentType) {\n\t\t\treturn\n\t\t}\n\n\t\tif contentEncoding := h.ContentEncoding(); len(contentEncoding) > 0 && !yield(strContentEncoding, contentEncoding) {\n\t\t\treturn\n\t\t}\n\n\t\tif server := h.Server(); len(server) > 0 && !yield(strServer, server) {\n\t\t\treturn\n\t\t}\n\n\t\tfor i := range h.cookies {\n\t\t\tif !yield(strSetCookie, h.cookies[i].value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif len(h.trailer) > 0 && !yield(strTrailer, appendTrailerBytes(nil, h.trailer, strCommaSpace)) {\n\t\t\treturn\n\t\t}\n\n\t\tfor i := range h.h {\n\t\t\tif !yield(h.h[i].key, h.h[i].value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif h.ConnectionClose() && !yield(strConnection, strClose) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// VisitAll calls f for each header.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// Deprecated: Use All instead.\nfunc (h *ResponseHeader) VisitAll(f func(key, value []byte)) {\n\th.All()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// Trailers returns an iterator over trailers in h.\n//\n// The value of trailer may invalid outside the iteration loop.\nfunc (h *header) Trailers() iter.Seq[[]byte] {\n\treturn func(yield func([]byte) bool) {\n\t\tfor i := range h.trailer {\n\t\t\tif !yield(h.trailer[i]) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VisitAllTrailer calls f for each response Trailer.\n//\n// f must not retain references to value after returning.\n//\n// Deprecated: Use Trailers instead.\nfunc (h *header) VisitAllTrailer(f func(value []byte)) {\n\th.Trailers()(func(v []byte) bool {\n\t\tf(v)\n\t\treturn true\n\t})\n}\n\n// Cookies returns an iterator over key-value paired response cookie in h.\n//\n// Cookie name is passed in key and the whole Set-Cookie header value\n// is passed in value for each iteration. Value may be parsed with\n// Cookie.ParseBytes().\n//\n// The key and value may invalid outside the iteration loop.\n// Copy key and/or value contents for each iteration if you need retaining\n// them.\n//\n// Making modifications to the ResponseHeader during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (h *ResponseHeader) Cookies() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\tfor i := range h.cookies {\n\t\t\tif !yield(h.cookies[i].key, h.cookies[i].value) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VisitAllCookie calls f for each response cookie.\n//\n// Cookie name is passed in key and the whole Set-Cookie header value\n// is passed in value on each f invocation. Value may be parsed\n// with Cookie.ParseBytes().\n//\n// f must not retain references to key and/or value after returning.\n//\n// Deprecated: Use Cookies instead.\nfunc (h *ResponseHeader) VisitAllCookie(f func(key, value []byte)) {\n\th.Cookies()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// Cookies returns an iterator over key-value pairs request cookie in h.\n//\n// The key and value may invalid outside the iteration loop.\n// Copy key and/or value contents for each iteration if you need retaining\n// them.\n//\n// Making modifications to the RequestHeader during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (h *RequestHeader) Cookies() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\th.collectCookies()\n\t\tfor i := range h.cookies {\n\t\t\tif !yield(h.cookies[i].key, h.cookies[i].value) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VisitAllCookie calls f for each request cookie.\n//\n// f must not retain references to key and/or value after returning.\n//\n// Deprecated: Use Cookies instead.\nfunc (h *RequestHeader) VisitAllCookie(f func(key, value []byte)) {\n\th.Cookies()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// All returns an iterator over key-value pairs in h.\n//\n// The key and value may invalid outside the iteration loop.\n// Copy key and/or value contents for each iteration if you need retaining\n// them.\n//\n// To get the headers in order they were received use AllInOrder.\n//\n// Making modifications to the RequestHeader during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (h *RequestHeader) All() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\tif host := h.Host(); len(host) > 0 && !yield(strHost, host) {\n\t\t\treturn\n\t\t}\n\t\tif len(h.contentLengthBytes) > 0 && !yield(strContentLength, h.contentLengthBytes) {\n\t\t\treturn\n\t\t}\n\n\t\tif contentType := h.ContentType(); len(contentType) > 0 && !yield(strContentType, contentType) {\n\t\t\treturn\n\t\t}\n\n\t\tif userAgent := h.UserAgent(); len(userAgent) > 0 && !yield(strUserAgent, userAgent) {\n\t\t\treturn\n\t\t}\n\n\t\tif len(h.trailer) > 0 && !yield(strTrailer, appendTrailerBytes(nil, h.trailer, strCommaSpace)) {\n\t\t\treturn\n\t\t}\n\n\t\th.collectCookies()\n\n\t\tif len(h.cookies) > 0 {\n\t\t\th.bufV = appendRequestCookieBytes(h.bufV[:0], h.cookies)\n\t\t\tif !yield(strCookie, h.bufV) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor i := range h.h {\n\t\t\tif !yield(h.h[i].key, h.h[i].value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif h.ConnectionClose() && !yield(strConnection, strClose) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// VisitAll calls f for each header.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// To get the headers in order they were received use VisitAllInOrder.\n//\n// Deprecated: Use All instead.\nfunc (h *RequestHeader) VisitAll(f func(key, value []byte)) {\n\th.All()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// AllInOrder returns an iterator over key-value pairs in h in the order they\n// were received.\n//\n// The key and value may invalid outside the iteration loop.\n// Copy key and/or value contents for each iteration if you need retaining\n// them.\n//\n// The returned iterator is slightly slower than All because it has to reparse\n// the raw headers to get the order.\n//\n// Making modifications to the RequestHeader during the iteration loop leads to undefined\n// behavior and can cause panics.\nfunc (h *RequestHeader) AllInOrder() iter.Seq2[[]byte, []byte] {\n\treturn func(yield func([]byte, []byte) bool) {\n\t\tvar s headerScanner\n\t\ts.b = h.rawHeaders\n\t\tfor s.next() {\n\t\t\tnormalizeHeaderKey(s.key, h.disableNormalizing || bytes.IndexByte(s.key, ' ') != -1)\n\t\t\tif len(s.key) > 0 {\n\t\t\t\tif !yield(s.key, s.value) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VisitAllInOrder calls f for each header in the order they were received.\n//\n// f must not retain references to key and/or value after returning.\n// Copy key and/or value contents before returning if you need retaining them.\n//\n// This function is slightly slower than VisitAll because it has to reparse the\n// raw headers to get the order.\n//\n// Deprecated: Use AllInOrder instead.\nfunc (h *RequestHeader) VisitAllInOrder(f func(key, value []byte)) {\n\th.AllInOrder()(func(key, value []byte) bool {\n\t\tf(key, value)\n\t\treturn true\n\t})\n}\n\n// Del deletes header with the given key.\nfunc (h *ResponseHeader) Del(key string) {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\th.del(h.bufK)\n}\n\n// DelBytes deletes header with the given key.\nfunc (h *ResponseHeader) DelBytes(key []byte) {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\th.del(h.bufK)\n}\n\nfunc (h *ResponseHeader) del(key []byte) {\n\tswitch string(key) {\n\tcase HeaderContentType:\n\t\th.contentType = h.contentType[:0]\n\tcase HeaderContentEncoding:\n\t\th.contentEncoding = h.contentEncoding[:0]\n\tcase HeaderServer:\n\t\th.server = h.server[:0]\n\tcase HeaderSetCookie:\n\t\th.cookies = h.cookies[:0]\n\tcase HeaderContentLength:\n\t\th.contentLength = 0\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\tcase HeaderConnection:\n\t\th.connectionClose = false\n\tcase HeaderTrailer:\n\t\th.trailer = h.trailer[:0]\n\t}\n\th.h = delAllArgs(h.h, b2s(key))\n}\n\n// Del deletes header with the given key.\nfunc (h *RequestHeader) Del(key string) {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\th.del(h.bufK)\n}\n\n// DelBytes deletes header with the given key.\nfunc (h *RequestHeader) DelBytes(key []byte) {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\th.del(h.bufK)\n}\n\nfunc (h *RequestHeader) del(key []byte) {\n\tswitch string(key) {\n\tcase HeaderHost:\n\t\th.host = h.host[:0]\n\tcase HeaderContentType:\n\t\th.contentType = h.contentType[:0]\n\tcase HeaderUserAgent:\n\t\th.userAgent = h.userAgent[:0]\n\tcase HeaderCookie:\n\t\th.cookies = h.cookies[:0]\n\tcase HeaderContentLength:\n\t\th.contentLength = 0\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\tcase HeaderConnection:\n\t\th.connectionClose = false\n\tcase HeaderTrailer:\n\t\th.trailer = h.trailer[:0]\n\t}\n\th.h = delAllArgs(h.h, b2s(key))\n}\n\n// setSpecialHeader handles special headers and return true when a header is processed.\nfunc (h *ResponseHeader) setSpecialHeader(key, value []byte) bool {\n\tif len(key) == 0 {\n\t\treturn false\n\t}\n\n\tswitch key[0] | 0x20 {\n\tcase 'c':\n\t\tswitch {\n\t\tcase caseInsensitiveCompare(strContentType, key):\n\t\t\th.SetContentTypeBytes(value)\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strContentLength, key):\n\t\t\tif contentLength, err := parseContentLength(value); err == nil {\n\t\t\t\th.contentLength = contentLength\n\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], value...)\n\t\t\t}\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strContentEncoding, key):\n\t\t\th.SetContentEncodingBytes(value)\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strConnection, key):\n\t\t\tif bytes.Equal(strClose, value) {\n\t\t\t\th.SetConnectionClose()\n\t\t\t} else {\n\t\t\t\th.ResetConnectionClose()\n\t\t\t\th.setNonSpecial(key, value)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\tcase 's':\n\t\tif caseInsensitiveCompare(strServer, key) {\n\t\t\th.SetServerBytes(value)\n\t\t\treturn true\n\t\t} else if caseInsensitiveCompare(strSetCookie, key) {\n\t\t\tvar kv *argsKV\n\t\t\th.cookies, kv = allocArg(h.cookies)\n\t\t\tkv.key = getCookieKey(kv.key, value)\n\t\t\tkv.value = append(kv.value[:0], value...)\n\t\t\treturn true\n\t\t}\n\tcase 't':\n\t\tif caseInsensitiveCompare(strTransferEncoding, key) {\n\t\t\t// Transfer-Encoding is managed automatically.\n\t\t\treturn true\n\t\t} else if caseInsensitiveCompare(strTrailer, key) {\n\t\t\t_ = h.SetTrailerBytes(value)\n\t\t\treturn true\n\t\t}\n\tcase 'd':\n\t\tif caseInsensitiveCompare(strDate, key) {\n\t\t\t// Date is managed automatically.\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// setNonSpecial directly put into map i.e. not a basic header.\nfunc (h *header) setNonSpecial(key, value []byte) {\n\th.h = setArgBytes(h.h, key, value, argsHasValue)\n}\n\n// setSpecialHeader handles special headers and return true when a header is processed.\nfunc (h *RequestHeader) setSpecialHeader(key, value []byte) bool {\n\tif len(key) == 0 || h.disableSpecialHeader {\n\t\treturn false\n\t}\n\n\tswitch key[0] | 0x20 {\n\tcase 'c':\n\t\tswitch {\n\t\tcase caseInsensitiveCompare(strContentType, key):\n\t\t\th.SetContentTypeBytes(value)\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strContentLength, key):\n\t\t\tif contentLength, err := parseContentLength(value); err == nil {\n\t\t\t\th.contentLength = contentLength\n\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], value...)\n\t\t\t}\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strConnection, key):\n\t\t\tif bytes.Equal(strClose, value) {\n\t\t\t\th.SetConnectionClose()\n\t\t\t} else {\n\t\t\t\th.ResetConnectionClose()\n\t\t\t\th.setNonSpecial(key, value)\n\t\t\t}\n\t\t\treturn true\n\t\tcase caseInsensitiveCompare(strCookie, key):\n\t\t\th.collectCookies()\n\t\t\th.cookies = parseRequestCookies(h.cookies, value)\n\t\t\treturn true\n\t\t}\n\tcase 't':\n\t\tif caseInsensitiveCompare(strTransferEncoding, key) {\n\t\t\t// Transfer-Encoding is managed automatically.\n\t\t\treturn true\n\t\t} else if caseInsensitiveCompare(strTrailer, key) {\n\t\t\t_ = h.SetTrailerBytes(value)\n\t\t\treturn true\n\t\t}\n\tcase 'h':\n\t\tif caseInsensitiveCompare(strHost, key) {\n\t\t\th.SetHostBytes(value)\n\t\t\treturn true\n\t\t}\n\tcase 'u':\n\t\tif caseInsensitiveCompare(strUserAgent, key) {\n\t\t\th.SetUserAgentBytes(value)\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Add adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use Set for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Server, Transfer-Encoding\n// and Date headers can only be set once and will overwrite the previous value,\n// while Set-Cookie will not clear previous cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked response body.\nfunc (h *ResponseHeader) Add(key, value string) {\n\th.AddBytesKV(s2b(key), s2b(value))\n}\n\n// AddBytesK adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesK for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Server, Transfer-Encoding\n// and Date headers can only be set once and will overwrite the previous value,\n// while Set-Cookie will not clear previous cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked response body.\nfunc (h *ResponseHeader) AddBytesK(key []byte, value string) {\n\th.AddBytesKV(key, s2b(value))\n}\n\n// AddBytesV adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesV for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Server, Transfer-Encoding\n// and Date headers can only be set once and will overwrite the previous value,\n// while Set-Cookie will not clear previous cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked response body.\nfunc (h *ResponseHeader) AddBytesV(key string, value []byte) {\n\th.AddBytesKV(s2b(key), value)\n}\n\n// AddBytesKV adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesKV for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Server, Transfer-Encoding\n// and Date headers can only be set once and will overwrite the previous value,\n// while the Set-Cookie header will not clear previous cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked response body.\nfunc (h *ResponseHeader) AddBytesKV(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\n\th.bufK = getHeaderKeyBytes(h.bufK, b2s(key), h.disableNormalizing)\n\th.h = appendArgBytes(h.h, h.bufK, value, argsHasValue)\n}\n\n// Set sets the given 'key: value' header.\n//\n// Please note that the Set-Cookie header will not clear previous cookies,\n// use SetCookie instead to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked response body.\n//\n// Use Add for setting multiple header values under the same key.\nfunc (h *ResponseHeader) Set(key, value string) {\n\th.bufK, h.bufV = initHeaderKV(h.bufK, h.bufV, key, value, h.disableNormalizing)\n\th.SetCanonical(h.bufK, h.bufV)\n}\n\n// SetBytesK sets the given 'key: value' header.\n//\n// Please note that the Set-Cookie header will not clear previous cookies,\n// use SetCookie instead to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked response body.\n//\n// Use AddBytesK for setting multiple header values under the same key.\nfunc (h *ResponseHeader) SetBytesK(key []byte, value string) {\n\th.bufV = append(h.bufV[:0], value...)\n\th.SetBytesKV(key, h.bufV)\n}\n\n// SetBytesV sets the given 'key: value' header.\n//\n// Please note that the Set-Cookie header will not clear previous cookies,\n// use SetCookie instead to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked response body.\n//\n// Use AddBytesV for setting multiple header values under the same key.\nfunc (h *ResponseHeader) SetBytesV(key string, value []byte) {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\th.SetCanonical(h.bufK, value)\n}\n\n// SetBytesKV sets the given 'key: value' header.\n//\n// Please note that the Set-Cookie header will not clear previous cookies,\n// use SetCookie instead to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked response body.\n//\n// Use AddBytesKV for setting multiple header values under the same key.\nfunc (h *ResponseHeader) SetBytesKV(key, value []byte) {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\th.SetCanonical(h.bufK, value)\n}\n\n// SetCanonical sets the given 'key: value' header assuming that\n// key is in canonical form.\n//\n// Please note that the Set-Cookie header will not clear previous cookies,\n// use SetCookie instead to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked response body.\nfunc (h *ResponseHeader) SetCanonical(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\th.setNonSpecial(key, value)\n}\n\n// SetCookie sets the given response cookie.\n//\n// It is safe re-using the cookie after the function returns.\nfunc (h *ResponseHeader) SetCookie(cookie *Cookie) {\n\th.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue)\n}\n\n// SetCookie sets 'key: value' cookies.\nfunc (h *RequestHeader) SetCookie(key, value string) {\n\th.collectCookies()\n\th.cookies = setArg(h.cookies, key, value, argsHasValue)\n}\n\n// SetCookieBytesK sets 'key: value' cookies.\nfunc (h *RequestHeader) SetCookieBytesK(key []byte, value string) {\n\th.SetCookie(b2s(key), value)\n}\n\n// SetCookieBytesKV sets 'key: value' cookies.\nfunc (h *RequestHeader) SetCookieBytesKV(key, value []byte) {\n\th.SetCookie(b2s(key), b2s(value))\n}\n\n// DelClientCookie instructs the client to remove the given cookie.\n// This doesn't work for a cookie with specific domain or path,\n// you should delete it manually like:\n//\n//\tc := AcquireCookie()\n//\tc.SetKey(key)\n//\tc.SetDomain(\"example.com\")\n//\tc.SetPath(\"/path\")\n//\tc.SetExpire(CookieExpireDelete)\n//\th.SetCookie(c)\n//\tReleaseCookie(c)\n//\n// Use DelCookie if you want just removing the cookie from response header.\nfunc (h *ResponseHeader) DelClientCookie(key string) {\n\th.DelCookie(key)\n\n\tc := AcquireCookie()\n\tc.SetKey(key)\n\tc.SetExpire(CookieExpireDelete)\n\th.SetCookie(c)\n\tReleaseCookie(c)\n}\n\n// DelClientCookieBytes instructs the client to remove the given cookie.\n// This doesn't work for a cookie with specific domain or path,\n// you should delete it manually like:\n//\n//\tc := AcquireCookie()\n//\tc.SetKey(key)\n//\tc.SetDomain(\"example.com\")\n//\tc.SetPath(\"/path\")\n//\tc.SetExpire(CookieExpireDelete)\n//\th.SetCookie(c)\n//\tReleaseCookie(c)\n//\n// Use DelCookieBytes if you want just removing the cookie from response header.\nfunc (h *ResponseHeader) DelClientCookieBytes(key []byte) {\n\th.DelClientCookie(b2s(key))\n}\n\n// DelCookie removes cookie under the given key from response header.\n//\n// Note that DelCookie doesn't remove the cookie from the client.\n// Use DelClientCookie instead.\nfunc (h *ResponseHeader) DelCookie(key string) {\n\th.cookies = delAllArgs(h.cookies, key)\n}\n\n// DelCookieBytes removes cookie under the given key from response header.\n//\n// Note that DelCookieBytes doesn't remove the cookie from the client.\n// Use DelClientCookieBytes instead.\nfunc (h *ResponseHeader) DelCookieBytes(key []byte) {\n\th.DelCookie(b2s(key))\n}\n\n// DelCookie removes cookie under the given key.\nfunc (h *RequestHeader) DelCookie(key string) {\n\th.collectCookies()\n\th.cookies = delAllArgs(h.cookies, key)\n}\n\n// DelCookieBytes removes cookie under the given key.\nfunc (h *RequestHeader) DelCookieBytes(key []byte) {\n\th.DelCookie(b2s(key))\n}\n\n// DelAllCookies removes all the cookies from response headers.\nfunc (h *ResponseHeader) DelAllCookies() {\n\th.cookies = h.cookies[:0]\n}\n\n// DelAllCookies removes all the cookies from request headers.\nfunc (h *RequestHeader) DelAllCookies() {\n\th.collectCookies()\n\th.cookies = h.cookies[:0]\n}\n\n// Add adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use Set for setting a single header for the given key.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked request body.\nfunc (h *RequestHeader) Add(key, value string) {\n\th.AddBytesKV(s2b(key), s2b(value))\n}\n\n// AddBytesK adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesK for setting a single header for the given key.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked request body.\nfunc (h *RequestHeader) AddBytesK(key []byte, value string) {\n\th.AddBytesKV(key, s2b(value))\n}\n\n// AddBytesV adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesV for setting a single header for the given key.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked request body.\nfunc (h *RequestHeader) AddBytesV(key string, value []byte) {\n\th.AddBytesKV(s2b(key), value)\n}\n\n// AddBytesKV adds the given 'key: value' header.\n//\n// Multiple headers with the same key may be added with this function.\n// Use SetBytesKV for setting a single header for the given key.\n//\n// the Content-Type, Content-Length, Connection, Transfer-Encoding,\n// Host and User-Agent headers can only be set once and will overwrite\n// the previous value, while the Cookie header will not clear previous cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),\n// it will be sent after the chunked request body.\nfunc (h *RequestHeader) AddBytesKV(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\n\th.bufK = getHeaderKeyBytes(h.bufK, b2s(key), h.disableNormalizing)\n\th.h = appendArgBytes(h.h, h.bufK, value, argsHasValue)\n}\n\n// Set sets the given 'key: value' header.\n//\n// Please note that the Cookie header will not clear previous cookies,\n// delete cookies before calling in order to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked request body.\n//\n// Use Add for setting multiple header values under the same key.\nfunc (h *RequestHeader) Set(key, value string) {\n\th.bufK, h.bufV = initHeaderKV(h.bufK, h.bufV, key, value, h.disableNormalizing)\n\th.SetCanonical(h.bufK, h.bufV)\n}\n\n// SetBytesK sets the given 'key: value' header.\n//\n// Please note that the Cookie header will not clear previous cookies,\n// delete cookies before calling in order to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked request body.\n//\n// Use AddBytesK for setting multiple header values under the same key.\nfunc (h *RequestHeader) SetBytesK(key []byte, value string) {\n\th.bufV = append(h.bufV[:0], value...)\n\th.SetBytesKV(key, h.bufV)\n}\n\n// SetBytesV sets the given 'key: value' header.\n//\n// Please note that the Cookie header will not clear previous cookies,\n// delete cookies before calling in order to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked request body.\n//\n// Use AddBytesV for setting multiple header values under the same key.\nfunc (h *RequestHeader) SetBytesV(key string, value []byte) {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\th.SetCanonical(h.bufK, value)\n}\n\n// SetBytesKV sets the given 'key: value' header.\n//\n// Please note that the Cookie header will not clear previous cookies,\n// delete cookies before calling in order to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked request body.\n//\n// Use AddBytesKV for setting multiple header values under the same key.\nfunc (h *RequestHeader) SetBytesKV(key, value []byte) {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\th.SetCanonical(h.bufK, value)\n}\n\n// SetCanonical sets the given 'key: value' header assuming that\n// key is in canonical form.\n//\n// Please note that the Cookie header will not clear previous cookies,\n// delete cookies before calling in order to reset cookies.\n//\n// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),\n// it will be sent after the chunked request body.\nfunc (h *RequestHeader) SetCanonical(key, value []byte) {\n\tif h.setSpecialHeader(key, value) {\n\t\treturn\n\t}\n\th.setNonSpecial(key, value)\n}\n\n// Peek returns header value for the given key.\n//\n// The returned value is valid until the response is released,\n// either though ReleaseResponse or your request handler returning.\n// Do not store references to the returned value. Make copies instead.\nfunc (h *ResponseHeader) Peek(key string) []byte {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\treturn h.peek(h.bufK)\n}\n\n// PeekBytes returns header value for the given key.\n//\n// The returned value is valid until the response is released,\n// either though ReleaseResponse or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) PeekBytes(key []byte) []byte {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\treturn h.peek(h.bufK)\n}\n\n// Peek returns header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) Peek(key string) []byte {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\treturn h.peek(h.bufK)\n}\n\n// PeekBytes returns header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) PeekBytes(key []byte) []byte {\n\th.bufK = append(h.bufK[:0], key...)\n\tnormalizeHeaderKey(h.bufK, h.disableNormalizing || bytes.IndexByte(key, ' ') != -1)\n\treturn h.peek(h.bufK)\n}\n\nfunc (h *ResponseHeader) peek(key []byte) []byte {\n\tswitch string(key) {\n\tcase HeaderContentType:\n\t\treturn h.ContentType()\n\tcase HeaderContentEncoding:\n\t\treturn h.ContentEncoding()\n\tcase HeaderServer:\n\t\treturn h.Server()\n\tcase HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\treturn strClose\n\t\t}\n\t\treturn peekArgBytes(h.h, key)\n\tcase HeaderContentLength:\n\t\treturn h.contentLengthBytes\n\tcase HeaderSetCookie:\n\t\treturn appendResponseCookieBytes(nil, h.cookies)\n\tcase HeaderTrailer:\n\t\treturn appendTrailerBytes(nil, h.trailer, strCommaSpace)\n\tdefault:\n\t\treturn peekArgBytes(h.h, key)\n\t}\n}\n\nfunc (h *RequestHeader) peek(key []byte) []byte {\n\tswitch string(key) {\n\tcase HeaderHost:\n\t\treturn h.Host()\n\tcase HeaderContentType:\n\t\treturn h.ContentType()\n\tcase HeaderUserAgent:\n\t\treturn h.UserAgent()\n\tcase HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\treturn strClose\n\t\t}\n\t\treturn peekArgBytes(h.h, key)\n\tcase HeaderContentLength:\n\t\treturn h.contentLengthBytes\n\tcase HeaderCookie:\n\t\tif h.cookiesCollected {\n\t\t\treturn appendRequestCookieBytes(nil, h.cookies)\n\t\t}\n\t\treturn peekArgBytes(h.h, key)\n\tcase HeaderTrailer:\n\t\treturn appendTrailerBytes(nil, h.trailer, strCommaSpace)\n\tdefault:\n\t\treturn peekArgBytes(h.h, key)\n\t}\n}\n\n// PeekAll returns all header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) PeekAll(key string) [][]byte {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\treturn h.peekAll(h.bufK)\n}\n\nfunc (h *RequestHeader) peekAll(key []byte) [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tswitch string(key) {\n\tcase HeaderHost:\n\t\tif host := h.Host(); len(host) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, host)\n\t\t}\n\tcase HeaderContentType:\n\t\tif contentType := h.ContentType(); len(contentType) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentType)\n\t\t}\n\tcase HeaderUserAgent:\n\t\tif ua := h.UserAgent(); len(ua) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, ua)\n\t\t}\n\tcase HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\th.mulHeader = append(h.mulHeader, strClose)\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tcase HeaderContentLength:\n\t\th.mulHeader = append(h.mulHeader, h.contentLengthBytes)\n\tcase HeaderCookie:\n\t\tif h.cookiesCollected {\n\t\t\th.mulHeader = append(h.mulHeader, appendRequestCookieBytes(nil, h.cookies))\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tcase HeaderTrailer:\n\t\th.mulHeader = append(h.mulHeader, appendTrailerBytes(nil, h.trailer, strCommaSpace))\n\tdefault:\n\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t}\n\treturn h.mulHeader\n}\n\n// PeekAll returns all header value for the given key.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseResponse or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) PeekAll(key string) [][]byte {\n\th.bufK = getHeaderKeyBytes(h.bufK, key, h.disableNormalizing)\n\treturn h.peekAll(h.bufK)\n}\n\nfunc (h *ResponseHeader) peekAll(key []byte) [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tswitch string(key) {\n\tcase HeaderContentType:\n\t\tif contentType := h.ContentType(); len(contentType) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentType)\n\t\t}\n\tcase HeaderContentEncoding:\n\t\tif contentEncoding := h.ContentEncoding(); len(contentEncoding) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, contentEncoding)\n\t\t}\n\tcase HeaderServer:\n\t\tif server := h.Server(); len(server) > 0 {\n\t\t\th.mulHeader = append(h.mulHeader, server)\n\t\t}\n\tcase HeaderConnection:\n\t\tif h.ConnectionClose() {\n\t\t\th.mulHeader = append(h.mulHeader, strClose)\n\t\t} else {\n\t\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t\t}\n\tcase HeaderContentLength:\n\t\th.mulHeader = append(h.mulHeader, h.contentLengthBytes)\n\tcase HeaderSetCookie:\n\t\th.mulHeader = append(h.mulHeader, appendResponseCookieBytes(nil, h.cookies))\n\tcase HeaderTrailer:\n\t\th.mulHeader = append(h.mulHeader, appendTrailerBytes(nil, h.trailer, strCommaSpace))\n\tdefault:\n\t\th.mulHeader = peekAllArgBytesToDst(h.mulHeader, h.h, key)\n\t}\n\treturn h.mulHeader\n}\n\n// PeekKeys return all header keys.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) PeekKeys() [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tfor key := range h.All() {\n\t\th.mulHeader = append(h.mulHeader, key)\n\t}\n\treturn h.mulHeader\n}\n\n// PeekKeys return all header keys.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) PeekKeys() [][]byte {\n\th.mulHeader = h.mulHeader[:0]\n\tfor key := range h.All() {\n\t\th.mulHeader = append(h.mulHeader, key)\n\t}\n\treturn h.mulHeader\n}\n\n// PeekTrailerKeys return all trailer keys.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseResponse or your request handler returning.\n// Any future calls to the Peek* will modify the returned value.\n// Do not store references to returned value. Make copies instead.\nfunc (h *header) PeekTrailerKeys() [][]byte {\n\treturn h.trailer\n}\n\n// Cookie returns cookie for the given key.\nfunc (h *RequestHeader) Cookie(key string) []byte {\n\th.collectCookies()\n\treturn peekArgStr(h.cookies, key)\n}\n\n// CookieBytes returns cookie for the given key.\nfunc (h *RequestHeader) CookieBytes(key []byte) []byte {\n\th.collectCookies()\n\treturn peekArgBytes(h.cookies, key)\n}\n\n// Cookie fills cookie for the given cookie.Key.\n//\n// Returns false if cookie with the given cookie.Key is missing.\nfunc (h *ResponseHeader) Cookie(cookie *Cookie) bool {\n\tv := peekArgBytes(h.cookies, cookie.Key())\n\tif v == nil {\n\t\treturn false\n\t}\n\tcookie.ParseBytes(v) //nolint:errcheck\n\treturn true\n}\n\n// Read reads response header from r.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (h *ResponseHeader) Read(r *bufio.Reader) error {\n\tn := 1\n\tfor {\n\t\terr := h.tryRead(r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif err != ErrNeedMore {\n\t\t\th.resetSkipNormalize()\n\t\t\treturn err\n\t\t}\n\t\tn = r.Buffered() + 1\n\t}\n}\n\nfunc (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error {\n\th.resetSkipNormalize()\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\t// Return ErrTimeout on any timeout.\n\t\tif x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {\n\t\t\treturn ErrTimeout\n\t\t}\n\t\t// treat all other errors on the first byte read as EOF\n\t\tif n == 1 || err == io.EOF {\n\t\t\treturn io.EOF\n\t\t}\n\n\t\t// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .\n\t\tif err == bufio.ErrBufferFull {\n\t\t\tif h.secureErrorLogMessage {\n\t\t\t\treturn &ErrSmallBuffer{\n\t\t\t\t\terror: ErrReadingResponseHeaders,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ErrSmallBuffer{\n\t\t\t\terror: fmt.Errorf(\"error when reading response headers: %w\", ErrSmallReadBuffer),\n\t\t\t}\n\t\t}\n\n\t\treturn fmt.Errorf(\"error when reading response headers: %w\", err)\n\t}\n\tb = mustPeekBuffered(r)\n\theadersLen, errParse := h.parse(b)\n\tif errParse != nil {\n\t\treturn headerError(\"response\", err, errParse, b, h.secureErrorLogMessage)\n\t}\n\tmustDiscard(r, headersLen)\n\treturn nil\n}\n\n// ReadTrailer reads response trailer header from r.\n//\n// io.EOF is returned if r is closed before reading the first byte.\nfunc (h *header) ReadTrailer(r *bufio.Reader) error {\n\tn := 1\n\tfor {\n\t\terr := h.tryReadTrailer(r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif err != ErrNeedMore {\n\t\t\treturn err\n\t\t}\n\t\tn = r.Buffered() + 1\n\t}\n}\n\nfunc (h *header) tryReadTrailer(r *bufio.Reader, n int) error {\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\t// Return ErrTimeout on any timeout.\n\t\tif x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {\n\t\t\treturn ErrTimeout\n\t\t}\n\n\t\tif n == 1 || err == io.EOF {\n\t\t\treturn io.EOF\n\t\t}\n\n\t\t// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .\n\t\tif err == bufio.ErrBufferFull {\n\t\t\tif h.secureErrorLogMessage {\n\t\t\t\treturn &ErrSmallBuffer{\n\t\t\t\t\terror: ErrReadingResponseTrailer,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &ErrSmallBuffer{\n\t\t\t\terror: fmt.Errorf(\"error when reading response trailer: %w\", ErrSmallReadBuffer),\n\t\t\t}\n\t\t}\n\n\t\treturn fmt.Errorf(\"error when reading response trailer: %w\", err)\n\t}\n\tb = mustPeekBuffered(r)\n\thh, headersLen, errParse := parseTrailer(b, h.h, h.disableNormalizing)\n\th.h = hh\n\tif errParse != nil {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\t\treturn headerError(\"response\", err, errParse, b, h.secureErrorLogMessage)\n\t}\n\tmustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc headerError(typ string, err, errParse error, b []byte, secureErrorLogMessage bool) error {\n\tif errParse != ErrNeedMore {\n\t\treturn headerErrorMsg(typ, errParse, b, secureErrorLogMessage)\n\t}\n\tif err == nil {\n\t\treturn ErrNeedMore\n\t}\n\n\t// Buggy servers may leave trailing CRLFs after http body.\n\t// Treat this case as EOF.\n\tif isOnlyCRLF(b) {\n\t\treturn io.EOF\n\t}\n\n\tif err != bufio.ErrBufferFull {\n\t\treturn headerErrorMsg(typ, err, b, secureErrorLogMessage)\n\t}\n\treturn &ErrSmallBuffer{\n\t\terror: headerErrorMsg(typ, ErrSmallReadBuffer, b, secureErrorLogMessage),\n\t}\n}\n\nfunc headerErrorMsg(typ string, err error, b []byte, secureErrorLogMessage bool) error {\n\tif secureErrorLogMessage {\n\t\treturn fmt.Errorf(\"error when reading %s headers: %w. Buffer size=%d\", typ, err, len(b))\n\t}\n\treturn fmt.Errorf(\"error when reading %s headers: %w. Buffer size=%d, contents: %s\", typ, err, len(b), bufferSnippet(b))\n}\n\n// Read reads request header from r.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (h *RequestHeader) Read(r *bufio.Reader) error {\n\treturn h.readLoop(r, true)\n}\n\n// readLoop reads request header from r optionally loops until it has enough data.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (h *RequestHeader) readLoop(r *bufio.Reader, waitForMore bool) error {\n\tn := 1\n\tfor {\n\t\terr := h.tryRead(r, n)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !waitForMore || err != ErrNeedMore {\n\t\t\th.resetSkipNormalize()\n\t\t\treturn err\n\t\t}\n\t\tn = r.Buffered() + 1\n\t}\n}\n\nfunc (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {\n\th.resetSkipNormalize()\n\tb, err := r.Peek(n)\n\tif len(b) == 0 {\n\t\tif err == io.EOF {\n\t\t\treturn err\n\t\t}\n\n\t\tif err == nil {\n\t\t\tpanic(\"bufio.Reader.Peek() returned nil, nil\")\n\t\t}\n\n\t\t// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .\n\t\tif err == bufio.ErrBufferFull {\n\t\t\treturn &ErrSmallBuffer{\n\t\t\t\terror: fmt.Errorf(\"error when reading request headers: %w (n=%d, r.Buffered()=%d)\", ErrSmallReadBuffer, n, r.Buffered()),\n\t\t\t}\n\t\t}\n\n\t\t// n == 1 on the first read for the request.\n\t\tif n == 1 {\n\t\t\t// We didn't read a single byte.\n\t\t\treturn ErrNothingRead{error: err}\n\t\t}\n\n\t\treturn fmt.Errorf(\"error when reading request headers: %w\", err)\n\t}\n\tb = mustPeekBuffered(r)\n\theadersLen, errParse := h.parse(b)\n\tif errParse != nil {\n\t\treturn headerError(\"request\", err, errParse, b, h.secureErrorLogMessage)\n\t}\n\tmustDiscard(r, headersLen)\n\treturn nil\n}\n\nfunc bufferSnippet(b []byte) string {\n\tn := len(b)\n\tstart := 200\n\tend := n - start\n\tif start >= end {\n\t\tstart = n\n\t\tend = n\n\t}\n\tbStart, bEnd := b[:start], b[end:]\n\tif len(bEnd) == 0 {\n\t\treturn fmt.Sprintf(\"%q\", b)\n\t}\n\treturn fmt.Sprintf(\"%q...%q\", bStart, bEnd)\n}\n\nfunc isOnlyCRLF(b []byte) bool {\n\tfor _, ch := range b {\n\t\tif ch != rChar && ch != nChar {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc updateServerDate() {\n\trefreshServerDate()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Second)\n\t\t\trefreshServerDate()\n\t\t}\n\t}()\n}\n\nvar (\n\tserverDate     atomic.Value\n\tserverDateOnce sync.Once // serverDateOnce.Do(updateServerDate)\n)\n\nfunc refreshServerDate() {\n\tb := AppendHTTPDate(nil, time.Now())\n\tserverDate.Store(b)\n}\n\n// Write writes response header to w.\nfunc (h *ResponseHeader) Write(w *bufio.Writer) error {\n\t_, err := w.Write(h.Header())\n\treturn err\n}\n\n// WriteTo writes response header to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (h *ResponseHeader) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(h.Header())\n\treturn int64(n), err\n}\n\n// Header returns response header representation.\n//\n// Headers that set as Trailer will not represent. Use TrailerHeader for trailers.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) Header() []byte {\n\th.bufV = h.AppendBytes(h.bufV[:0])\n\treturn h.bufV\n}\n\n// writeTrailer writes response trailer to w.\nfunc (h *ResponseHeader) writeTrailer(w *bufio.Writer) error {\n\t_, err := w.Write(h.TrailerHeader())\n\treturn err\n}\n\n// TrailerHeader returns response trailer header representation.\n//\n// Trailers will only be received with chunked transfer.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *ResponseHeader) TrailerHeader() []byte {\n\th.bufV = h.bufV[:0]\n\tfor _, t := range h.trailer {\n\t\tvalue := h.peek(t)\n\t\th.bufV = appendHeaderLine(h.bufV, t, value)\n\t}\n\th.bufV = append(h.bufV, strCRLF...)\n\treturn h.bufV\n}\n\n// String returns response header representation.\nfunc (h *ResponseHeader) String() string {\n\treturn string(h.Header())\n}\n\n// appendStatusLine appends the response status line to dst and returns\n// the extended dst.\nfunc (h *ResponseHeader) appendStatusLine(dst []byte) []byte {\n\tstatusCode := h.StatusCode()\n\tif statusCode < 0 {\n\t\tstatusCode = StatusOK\n\t}\n\treturn formatStatusLine(dst, h.Protocol(), statusCode, h.StatusMessage())\n}\n\n// AppendBytes appends response header representation to dst and returns\n// the extended dst.\nfunc (h *ResponseHeader) AppendBytes(dst []byte) []byte {\n\tdst = h.appendStatusLine(dst[:0])\n\n\tserver := h.Server()\n\tif len(server) != 0 {\n\t\tdst = appendHeaderLine(dst, strServer, server)\n\t}\n\n\tif !h.noDefaultDate {\n\t\tserverDateOnce.Do(updateServerDate)\n\t\tdst = appendHeaderLine(dst, strDate, serverDate.Load().([]byte))\n\t}\n\n\t// Append Content-Type only for non-zero responses\n\t// or if it is explicitly set.\n\t// See https://github.com/valyala/fasthttp/issues/28 .\n\tif h.ContentLength() != 0 || len(h.contentType) > 0 {\n\t\tcontentType := h.ContentType()\n\t\tif len(contentType) > 0 {\n\t\t\tdst = appendHeaderLine(dst, strContentType, contentType)\n\t\t}\n\t}\n\tcontentEncoding := h.ContentEncoding()\n\tif len(contentEncoding) > 0 {\n\t\tdst = appendHeaderLine(dst, strContentEncoding, contentEncoding)\n\t}\n\n\tif len(h.contentLengthBytes) > 0 {\n\t\tdst = appendHeaderLine(dst, strContentLength, h.contentLengthBytes)\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\n\t\t// Exclude trailer from header\n\t\texclude := false\n\t\tfor _, t := range h.trailer {\n\t\t\tif bytes.Equal(kv.key, t) {\n\t\t\t\texclude = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !exclude && (h.noDefaultDate || !bytes.Equal(kv.key, strDate)) {\n\t\t\tdst = appendHeaderLine(dst, kv.key, kv.value)\n\t\t}\n\t}\n\n\tif len(h.trailer) > 0 {\n\t\tdst = appendHeaderLine(dst, strTrailer, appendTrailerBytes(nil, h.trailer, strCommaSpace))\n\t}\n\n\tn := len(h.cookies)\n\tif n > 0 {\n\t\tfor i := range n {\n\t\t\tkv := &h.cookies[i]\n\t\t\tdst = appendHeaderLine(dst, strSetCookie, kv.value)\n\t\t}\n\t}\n\n\tif h.ConnectionClose() {\n\t\tdst = appendHeaderLine(dst, strConnection, strClose)\n\t}\n\n\treturn append(dst, strCRLF...)\n}\n\n// Write writes request header to w.\nfunc (h *RequestHeader) Write(w *bufio.Writer) error {\n\t_, err := w.Write(h.Header())\n\treturn err\n}\n\n// WriteTo writes request header to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (h *RequestHeader) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(h.Header())\n\treturn int64(n), err\n}\n\n// Header returns request header representation.\n//\n// Headers that set as Trailer will not represent. Use TrailerHeader for trailers.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) Header() []byte {\n\th.bufV = h.AppendBytes(h.bufV[:0])\n\treturn h.bufV\n}\n\n// writeTrailer writes request trailer to w.\nfunc (h *RequestHeader) writeTrailer(w *bufio.Writer) error {\n\t_, err := w.Write(h.TrailerHeader())\n\treturn err\n}\n\n// TrailerHeader returns request trailer header representation.\n//\n// Trailers will only be received with chunked transfer.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (h *RequestHeader) TrailerHeader() []byte {\n\th.bufV = h.bufV[:0]\n\tfor _, t := range h.trailer {\n\t\tvalue := h.peek(t)\n\t\th.bufV = appendHeaderLine(h.bufV, t, value)\n\t}\n\th.bufV = append(h.bufV, strCRLF...)\n\treturn h.bufV\n}\n\n// RawHeaders returns raw header key/value bytes.\n//\n// Depending on server configuration, header keys may be normalized to\n// capital-case in place.\n//\n// This copy is set aside during parsing, so empty slice is returned for all\n// cases where parsing did not happen. Similarly, request line is not stored\n// during parsing and can not be returned.\n//\n// The slice is not safe to use after the handler returns.\nfunc (h *RequestHeader) RawHeaders() []byte {\n\treturn h.rawHeaders\n}\n\n// String returns request header representation.\nfunc (h *RequestHeader) String() string {\n\treturn string(h.Header())\n}\n\n// AppendBytes appends request header representation to dst and returns\n// the extended dst.\nfunc (h *RequestHeader) AppendBytes(dst []byte) []byte {\n\tdst = append(dst, h.Method()...)\n\tdst = append(dst, ' ')\n\tdst = append(dst, h.RequestURI()...)\n\tdst = append(dst, ' ')\n\tdst = append(dst, h.Protocol()...)\n\tdst = append(dst, strCRLF...)\n\n\tuserAgent := h.UserAgent()\n\tif len(userAgent) > 0 && !h.disableSpecialHeader {\n\t\tdst = appendHeaderLine(dst, strUserAgent, userAgent)\n\t}\n\n\thost := h.Host()\n\tif len(host) > 0 && !h.disableSpecialHeader {\n\t\tdst = appendHeaderLine(dst, strHost, host)\n\t}\n\n\tcontentType := h.ContentType()\n\tif !h.noDefaultContentType && len(contentType) == 0 && !h.ignoreBody() {\n\t\tcontentType = strDefaultContentType\n\t}\n\tif len(contentType) > 0 && !h.disableSpecialHeader {\n\t\tdst = appendHeaderLine(dst, strContentType, contentType)\n\t}\n\tif len(h.contentLengthBytes) > 0 && !h.disableSpecialHeader {\n\t\tdst = appendHeaderLine(dst, strContentLength, h.contentLengthBytes)\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\t\t// Exclude trailer from header\n\t\texclude := false\n\t\tfor _, t := range h.trailer {\n\t\t\tif bytes.Equal(kv.key, t) {\n\t\t\t\texclude = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !exclude {\n\t\t\tdst = appendHeaderLine(dst, kv.key, kv.value)\n\t\t}\n\t}\n\n\tif len(h.trailer) > 0 {\n\t\tdst = appendHeaderLine(dst, strTrailer, appendTrailerBytes(nil, h.trailer, strCommaSpace))\n\t}\n\n\t// there is no need in h.collectCookies() here, since if cookies aren't collected yet,\n\t// they all are located in h.h.\n\tn := len(h.cookies)\n\tif n > 0 && !h.disableSpecialHeader {\n\t\tdst = append(dst, strCookie...)\n\t\tdst = append(dst, strColonSpace...)\n\t\tdst = appendRequestCookieBytes(dst, h.cookies)\n\t\tdst = append(dst, strCRLF...)\n\t}\n\n\tif h.ConnectionClose() && !h.disableSpecialHeader {\n\t\tdst = appendHeaderLine(dst, strConnection, strClose)\n\t}\n\n\treturn append(dst, strCRLF...)\n}\n\nfunc appendHeaderLine(dst, key, value []byte) []byte {\n\tdst = append(dst, key...)\n\tdst = append(dst, strColonSpace...)\n\tdst = append(dst, value...)\n\treturn append(dst, strCRLF...)\n}\n\nfunc (h *ResponseHeader) parse(buf []byte) (int, error) {\n\tm, err := h.parseFirstLine(buf)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := h.parseHeaders(buf[m:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn m + n, nil\n}\n\nfunc (h *RequestHeader) ignoreBody() bool {\n\treturn h.IsGet() || h.IsHead()\n}\n\nfunc (h *RequestHeader) parse(buf []byte) (int, error) {\n\tm, err := h.parseFirstLine(buf)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\th.rawHeaders, _, err = readRawHeaders(h.rawHeaders[:0], buf[m:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tvar n int\n\tn, err = h.parseHeaders(buf[m:])\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn m + n, nil\n}\n\nfunc parseTrailer(src []byte, dest []argsKV, disableNormalizing bool) ([]argsKV, int, error) {\n\t// Skip any 0 length chunk.\n\tif src[0] == '0' {\n\t\tskip := len(strCRLF) + 1\n\t\tif len(src) < skip {\n\t\t\treturn dest, 0, io.EOF\n\t\t}\n\t\tsrc = src[skip:]\n\t}\n\n\tvar s headerScanner\n\ts.b = src\n\n\tfor s.next() {\n\t\tif len(s.key) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tdisable := disableNormalizing\n\t\tfor _, ch := range s.key {\n\t\t\tif !validHeaderFieldByte(ch) {\n\t\t\t\t// We accept invalid headers with a space before the\n\t\t\t\t// colon, but must not canonicalize them.\n\t\t\t\t// See: https://github.com/valyala/fasthttp/issues/1917\n\t\t\t\tif ch == ' ' {\n\t\t\t\t\tdisable = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn dest, 0, fmt.Errorf(\"invalid trailer key %q\", s.key)\n\t\t\t}\n\t\t}\n\t\t// Forbidden by RFC 7230, section 4.1.2\n\t\tif isBadTrailer(s.key) {\n\t\t\treturn dest, 0, fmt.Errorf(\"forbidden trailer key %q\", s.key)\n\t\t}\n\t\tnormalizeHeaderKey(s.key, disable)\n\t\tdest = appendArgBytes(dest, s.key, s.value, argsHasValue)\n\t}\n\tif s.err != nil {\n\t\treturn dest, 0, s.err\n\t}\n\treturn dest, s.r, nil\n}\n\nfunc isBadTrailer(key []byte) bool {\n\tif len(key) == 0 {\n\t\treturn true\n\t}\n\n\tswitch key[0] | 0x20 {\n\tcase 'a':\n\t\treturn caseInsensitiveCompare(key, strAuthorization)\n\tcase 'c':\n\t\t// Security fix: Changed > to >= to properly block Content-Type header in trailers\n\t\tif len(key) >= len(HeaderContentType) && caseInsensitiveCompare(key[:8], strContentType[:8]) {\n\t\t\t// skip compare prefix 'Content-'\n\t\t\treturn caseInsensitiveCompare(key[8:], strContentEncoding[8:]) ||\n\t\t\t\tcaseInsensitiveCompare(key[8:], strContentLength[8:]) ||\n\t\t\t\tcaseInsensitiveCompare(key[8:], strContentType[8:]) ||\n\t\t\t\tcaseInsensitiveCompare(key[8:], strContentRange[8:])\n\t\t}\n\t\treturn caseInsensitiveCompare(key, strConnection) ||\n\t\t\t// Security: Block Cookie header in trailers to prevent session hijacking\n\t\t\tcaseInsensitiveCompare(key, strCookie)\n\tcase 'e':\n\t\treturn caseInsensitiveCompare(key, strExpect)\n\tcase 'h':\n\t\treturn caseInsensitiveCompare(key, strHost)\n\tcase 'k':\n\t\treturn caseInsensitiveCompare(key, strKeepAlive)\n\tcase 'l':\n\t\t// Security: Block Location header in trailers to prevent redirect attacks\n\t\treturn caseInsensitiveCompare(key, strLocation)\n\tcase 'm':\n\t\treturn caseInsensitiveCompare(key, strMaxForwards)\n\tcase 'p':\n\t\tif len(key) >= len(HeaderProxyConnection) && caseInsensitiveCompare(key[:6], strProxyConnection[:6]) {\n\t\t\t// skip compare prefix 'Proxy-'\n\t\t\treturn caseInsensitiveCompare(key[6:], strProxyConnection[6:]) ||\n\t\t\t\tcaseInsensitiveCompare(key[6:], strProxyAuthenticate[6:]) ||\n\t\t\t\tcaseInsensitiveCompare(key[6:], strProxyAuthorization[6:])\n\t\t}\n\tcase 'r':\n\t\treturn caseInsensitiveCompare(key, strRange)\n\tcase 's':\n\t\t// Security: Block Set-Cookie header in trailers\n\t\treturn caseInsensitiveCompare(key, strSetCookie)\n\tcase 't':\n\t\treturn caseInsensitiveCompare(key, strTE) ||\n\t\t\tcaseInsensitiveCompare(key, strTrailer) ||\n\t\t\tcaseInsensitiveCompare(key, strTransferEncoding)\n\tcase 'w':\n\t\treturn caseInsensitiveCompare(key, strWWWAuthenticate)\n\tcase 'x':\n\t\t// Security: Block X-Forwarded-* and X-Real-IP headers to prevent IP spoofing\n\t\treturn (len(key) >= 11 && caseInsensitiveCompare(key[:11], []byte(\"x-forwarded\"))) ||\n\t\t\t(len(key) >= 9 && caseInsensitiveCompare(key[:9], []byte(\"x-real-ip\")))\n\t}\n\treturn false\n}\n\nfunc (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) {\n\tbNext := buf\n\tvar b []byte\n\tvar err error\n\tfor len(b) == 0 {\n\t\tif b, bNext, err = nextLine(bNext); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\t// parse protocol\n\tn := bytes.IndexByte(b, ' ')\n\tif n < 0 {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrResponseFirstLineMissingSpace\n\t\t}\n\t\treturn 0, fmt.Errorf(\"cannot find whitespace in the first line of response %q\", buf)\n\t}\n\th.noHTTP11 = !bytes.Equal(b[:n], strHTTP11)\n\tb = b[n+1:]\n\n\t// parse status code\n\th.statusCode, n, err = parseUintBuf(b)\n\tif err != nil {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, fmt.Errorf(\"cannot parse response status code: %w\", err)\n\t\t}\n\t\treturn 0, fmt.Errorf(\"cannot parse response status code: %w. Response %q\", err, buf)\n\t}\n\tif len(b) > n && b[n] != ' ' {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrUnexpectedStatusCodeChar\n\t\t}\n\t\treturn 0, fmt.Errorf(\"unexpected char at the end of status code. Response %q\", buf)\n\t}\n\tif len(b) > n+1 {\n\t\th.SetStatusMessage(b[n+1:])\n\t}\n\n\treturn len(buf) - len(bNext), nil\n}\n\nfunc isValidMethod(method []byte) bool {\n\tfor _, ch := range method {\n\t\tif validMethodValueByteTable[ch] == 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (h *RequestHeader) parseFirstLine(buf []byte) (int, error) {\n\tbNext := buf\n\tvar b []byte\n\tvar err error\n\tfor len(b) == 0 {\n\t\tif b, bNext, err = nextLine(bNext); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\t// parse method\n\tn := bytes.IndexByte(b, ' ')\n\tif n <= 0 {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrMissingRequestMethod\n\t\t}\n\t\treturn 0, fmt.Errorf(\"cannot find http request method in %q\", buf)\n\t}\n\th.method = append(h.method[:0], b[:n]...)\n\n\tif !isValidMethod(h.method) {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrUnsupportedRequestMethod\n\t\t}\n\t\treturn 0, fmt.Errorf(\"unsupported http request method %q in %q\", h.method, buf)\n\t}\n\n\tb = b[n+1:]\n\n\t// Check for extra whitespace after method - only one space should separate method from URI\n\tif len(b) > 0 && b[0] == ' ' {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrExtraWhitespaceInRequestLine\n\t\t}\n\t\treturn 0, fmt.Errorf(\"extra whitespace in request line %q\", buf)\n\t}\n\n\t// parse requestURI - RFC 9112 requires exactly one space between components\n\tn = bytes.IndexByte(b, ' ')\n\tif n < 0 {\n\t\treturn 0, fmt.Errorf(\"cannot find whitespace in the first line of request %q\", buf)\n\t} else if n == 0 {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrEmptyRequestURI\n\t\t}\n\t\treturn 0, fmt.Errorf(\"requestURI cannot be empty in %q\", buf)\n\t}\n\n\t// Check for extra whitespace - only one space should separate URI from HTTP version\n\tif n+1 < len(b) && b[n+1] == ' ' {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, ErrExtraWhitespaceInRequestLine\n\t\t}\n\t\treturn 0, fmt.Errorf(\"extra whitespace in request line %q\", buf)\n\t}\n\n\tprotoStr := b[n+1:]\n\n\t// Follow RFCs 7230 and 9112 and require that HTTP versions match the following pattern: HTTP/[0-9]\\.[0-9]\n\tif len(protoStr) != len(strHTTP11) {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q\", protoStr)\n\t\t}\n\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q in %q\", protoStr, buf)\n\t}\n\tif !bytes.HasPrefix(protoStr, strHTTP11[:5]) {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q\", protoStr)\n\t\t}\n\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q in %q\", protoStr, buf)\n\t}\n\tif protoStr[5] < '0' || protoStr[5] > '9' || protoStr[7] < '0' || protoStr[7] > '9' {\n\t\tif h.secureErrorLogMessage {\n\t\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q\", protoStr)\n\t\t}\n\t\treturn 0, fmt.Errorf(\"unsupported HTTP version %q in %q\", protoStr, buf)\n\t}\n\n\th.noHTTP11 = !bytes.Equal(protoStr, strHTTP11)\n\th.protocol = append(h.protocol[:0], protoStr...)\n\th.requestURI = append(h.requestURI[:0], b[:n]...)\n\n\treturn len(buf) - len(bNext), nil\n}\n\nfunc readRawHeaders(dst, buf []byte) ([]byte, int, error) {\n\tn := bytes.IndexByte(buf, nChar)\n\tif n < 0 {\n\t\treturn dst[:0], 0, ErrNeedMore\n\t}\n\tif (n == 1 && buf[0] == rChar) || n == 0 {\n\t\t// empty headers\n\t\treturn dst, n + 1, nil\n\t}\n\n\tn++\n\tb := buf\n\tm := n\n\tfor {\n\t\tb = b[m:]\n\t\tm = bytes.IndexByte(b, nChar)\n\t\tif m < 0 {\n\t\t\treturn dst, 0, ErrNeedMore\n\t\t}\n\t\tm++\n\t\tn += m\n\t\tif (m == 2 && b[0] == rChar) || m == 1 {\n\t\t\tdst = append(dst, buf[:n]...)\n\t\t\treturn dst, n, nil\n\t\t}\n\t}\n}\n\nfunc (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {\n\t// 'identity' content-length by default\n\th.contentLength = -2\n\n\tvar s headerScanner\n\ts.b = buf\n\tvar kv *argsKV\n\n\tfor s.next() {\n\t\tif len(s.key) == 0 {\n\t\t\th.connectionClose = true\n\t\t\treturn 0, fmt.Errorf(\"invalid header key %q\", s.key)\n\t\t}\n\n\t\tdisableNormalizing := h.disableNormalizing\n\t\tfor _, ch := range s.key {\n\t\t\tif !validHeaderFieldByte(ch) {\n\t\t\t\th.connectionClose = true\n\t\t\t\t// We accept invalid headers with a space before the\n\t\t\t\t// colon, but must not canonicalize them.\n\t\t\t\t// See: https://github.com/valyala/fasthttp/issues/1917\n\t\t\t\tif ch == ' ' {\n\t\t\t\t\tdisableNormalizing = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn 0, fmt.Errorf(\"invalid header key %q\", s.key)\n\t\t\t}\n\t\t}\n\t\tnormalizeHeaderKey(s.key, disableNormalizing)\n\n\t\tfor _, ch := range s.value {\n\t\t\tif !validHeaderValueByte(ch) {\n\t\t\t\th.connectionClose = true\n\t\t\t\treturn 0, fmt.Errorf(\"invalid header value %q\", s.value)\n\t\t\t}\n\t\t}\n\n\t\tswitch s.key[0] | 0x20 {\n\t\tcase 'c':\n\t\t\tif caseInsensitiveCompare(s.key, strContentType) {\n\t\t\t\th.contentType = append(h.contentType[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strContentEncoding) {\n\t\t\t\th.contentEncoding = append(h.contentEncoding[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strContentLength) {\n\t\t\t\tif h.contentLength != -1 {\n\t\t\t\t\tvar err error\n\t\t\t\t\th.contentLength, err = parseContentLength(s.value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\th.contentLength = -2\n\t\t\t\t\t\th.connectionClose = true\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strConnection) {\n\t\t\t\tif bytes.Equal(s.value, strClose) {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t} else {\n\t\t\t\t\th.connectionClose = false\n\t\t\t\t\th.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase 's':\n\t\t\tif caseInsensitiveCompare(s.key, strServer) {\n\t\t\t\th.server = append(h.server[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strSetCookie) {\n\t\t\t\th.cookies, kv = allocArg(h.cookies)\n\t\t\t\tkv.key = getCookieKey(kv.key, s.value)\n\t\t\t\tkv.value = append(kv.value[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase 't':\n\t\t\tif caseInsensitiveCompare(s.key, strTransferEncoding) {\n\t\t\t\tif len(s.value) > 0 && !bytes.Equal(s.value, strIdentity) {\n\t\t\t\t\th.contentLength = -1\n\t\t\t\t\th.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strTrailer) {\n\t\t\t\terr := h.SetTrailerBytes(s.value)\n\t\t\t\tif err != nil {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\th.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)\n\t}\n\n\tif s.err != nil {\n\t\th.connectionClose = true\n\t\treturn 0, s.err\n\t}\n\n\tif h.contentLength < 0 {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t}\n\tif h.contentLength == -2 && !h.ConnectionUpgrade() && !h.mustSkipContentLength() {\n\t\t// According to modern HTTP/1.1 specifications (RFC 7230):\n\t\t// `identity` as a value for `Transfer-Encoding` was removed\n\t\t// in the errata to RFC 2616.\n\t\t// Therefore, we do not include `Transfer-Encoding: identity` in the header.\n\t\t// See: https://github.com/valyala/fasthttp/issues/1909\n\t\th.connectionClose = true\n\t}\n\tif h.noHTTP11 && !h.connectionClose {\n\t\t// close connection for non-http/1.1 response unless 'Connection: keep-alive' is set.\n\t\tv := peekArgBytes(h.h, strConnection)\n\t\th.connectionClose = !hasHeaderValue(v, strKeepAlive)\n\t}\n\n\treturn s.r, nil\n}\n\nfunc (h *RequestHeader) parseHeaders(buf []byte) (int, error) {\n\th.contentLength = -2\n\n\tcontentLengthSeen := false\n\n\tvar s headerScanner\n\ts.b = buf\n\n\tfor s.next() {\n\t\tif len(s.key) == 0 {\n\t\t\th.connectionClose = true\n\t\t\treturn 0, fmt.Errorf(\"invalid header key %q\", s.key)\n\t\t}\n\n\t\tdisableNormalizing := h.disableNormalizing\n\t\tfor _, ch := range s.key {\n\t\t\tif !validHeaderFieldByte(ch) {\n\t\t\t\tif ch == ' ' {\n\t\t\t\t\tdisableNormalizing = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\th.connectionClose = true\n\t\t\t\treturn 0, fmt.Errorf(\"invalid header key %q\", s.key)\n\t\t\t}\n\t\t}\n\t\tnormalizeHeaderKey(s.key, disableNormalizing)\n\n\t\tfor _, ch := range s.value {\n\t\t\tif !validHeaderValueByte(ch) {\n\t\t\t\th.connectionClose = true\n\t\t\t\treturn 0, fmt.Errorf(\"invalid header value %q\", s.value)\n\t\t\t}\n\t\t}\n\n\t\tif h.disableSpecialHeader {\n\t\t\th.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch s.key[0] | 0x20 {\n\t\tcase 'h':\n\t\t\tif caseInsensitiveCompare(s.key, strHost) {\n\t\t\t\th.host = append(h.host[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase 'u':\n\t\t\tif caseInsensitiveCompare(s.key, strUserAgent) {\n\t\t\t\th.userAgent = append(h.userAgent[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase 'c':\n\t\t\tif caseInsensitiveCompare(s.key, strContentType) {\n\t\t\t\th.contentType = append(h.contentType[:0], s.value...)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strContentLength) {\n\t\t\t\tif contentLengthSeen {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t\treturn 0, ErrDuplicateContentLength\n\t\t\t\t}\n\t\t\t\tcontentLengthSeen = true\n\n\t\t\t\tif h.contentLength != -1 {\n\t\t\t\t\tvar err error\n\t\t\t\t\th.contentLength, err = parseContentLength(s.value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\th.contentLength = -2\n\t\t\t\t\t\th.connectionClose = true\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t\th.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strConnection) {\n\t\t\t\tif bytes.Equal(s.value, strClose) {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t} else {\n\t\t\t\t\th.connectionClose = false\n\t\t\t\t\th.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\tcase 't':\n\t\t\tif caseInsensitiveCompare(s.key, strTransferEncoding) {\n\t\t\t\tisIdentity := caseInsensitiveCompare(s.value, strIdentity)\n\t\t\t\tisChunked := caseInsensitiveCompare(s.value, strChunked)\n\n\t\t\t\tif !isIdentity && !isChunked {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t\tif h.secureErrorLogMessage {\n\t\t\t\t\t\treturn 0, ErrUnsupportedTransferEncoding\n\t\t\t\t\t}\n\t\t\t\t\treturn 0, fmt.Errorf(\"unsupported Transfer-Encoding: %q\", s.value)\n\t\t\t\t}\n\n\t\t\t\tif isChunked {\n\t\t\t\t\th.contentLength = -1\n\t\t\t\t\th.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif caseInsensitiveCompare(s.key, strTrailer) {\n\t\t\t\terr := h.SetTrailerBytes(s.value)\n\t\t\t\tif err != nil {\n\t\t\t\t\th.connectionClose = true\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\th.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)\n\t}\n\n\tif s.err != nil {\n\t\th.connectionClose = true\n\t\treturn 0, s.err\n\t}\n\n\tif h.contentLength < 0 {\n\t\th.contentLengthBytes = h.contentLengthBytes[:0]\n\t}\n\tif h.noHTTP11 && !h.connectionClose {\n\t\t// close connection for non-http/1.1 request unless 'Connection: keep-alive' is set.\n\t\tv := peekArgBytes(h.h, strConnection)\n\t\th.connectionClose = !hasHeaderValue(v, strKeepAlive)\n\t}\n\treturn s.r, nil\n}\n\nfunc (h *RequestHeader) collectCookies() {\n\tif h.cookiesCollected {\n\t\treturn\n\t}\n\n\tfor i, n := 0, len(h.h); i < n; i++ {\n\t\tkv := &h.h[i]\n\t\tif caseInsensitiveCompare(kv.key, strCookie) {\n\t\t\th.cookies = parseRequestCookies(h.cookies, kv.value)\n\t\t\ttmp := *kv\n\t\t\tcopy(h.h[i:], h.h[i+1:])\n\t\t\tn--\n\t\t\ti--\n\t\t\th.h[n] = tmp\n\t\t\th.h = h.h[:n]\n\t\t}\n\t}\n\th.cookiesCollected = true\n}\n\nfunc parseContentLength(b []byte) (int, error) {\n\tv, n, err := parseUintBuf(b)\n\tif err != nil {\n\t\treturn -1, fmt.Errorf(\"cannot parse Content-Length: %w\", err)\n\t}\n\tif n != len(b) {\n\t\treturn -1, fmt.Errorf(\"cannot parse Content-Length: %w\", ErrNonNumericChars)\n\t}\n\treturn v, nil\n}\n\ntype headerValueScanner struct {\n\tb     []byte\n\tvalue []byte\n}\n\nfunc (s *headerValueScanner) next() bool {\n\tb := s.b\n\tif len(b) == 0 {\n\t\treturn false\n\t}\n\tbefore, after, ok := bytes.Cut(b, []byte{','})\n\tif !ok {\n\t\ts.value = stripSpace(b)\n\t\ts.b = b[len(b):]\n\t\treturn true\n\t}\n\ts.value = stripSpace(before)\n\ts.b = after\n\treturn true\n}\n\nfunc stripSpace(b []byte) []byte {\n\tfor len(b) > 0 && b[0] == ' ' {\n\t\tb = b[1:]\n\t}\n\tfor len(b) > 0 && b[len(b)-1] == ' ' {\n\t\tb = b[:len(b)-1]\n\t}\n\treturn b\n}\n\nfunc hasHeaderValue(s, value []byte) bool {\n\tvar vs headerValueScanner\n\tvs.b = s\n\tfor vs.next() {\n\t\tif caseInsensitiveCompare(vs.value, value) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc nextLine(b []byte) ([]byte, []byte, error) {\n\tnNext := bytes.IndexByte(b, nChar)\n\tif nNext < 0 {\n\t\treturn nil, nil, ErrNeedMore\n\t}\n\tn := nNext\n\tif n > 0 && b[n-1] == rChar {\n\t\tn--\n\t}\n\treturn b[:n], b[nNext+1:], nil\n}\n\nfunc initHeaderKV(bufK, bufV []byte, key, value string, disableNormalizing bool) ([]byte, []byte) {\n\tbufK = getHeaderKeyBytes(bufK, key, disableNormalizing)\n\t// https://tools.ietf.org/html/rfc7230#section-3.2.4\n\tbufV = append(bufV[:0], value...)\n\tbufV = removeNewLines(bufV)\n\treturn bufK, bufV\n}\n\nfunc getHeaderKeyBytes(bufK []byte, key string, disableNormalizing bool) []byte {\n\tbufK = append(bufK[:0], key...)\n\tnormalizeHeaderKey(bufK, disableNormalizing || bytes.IndexByte(bufK, ' ') != -1)\n\treturn bufK\n}\n\nfunc normalizeHeaderKey(b []byte, disableNormalizing bool) {\n\tif disableNormalizing {\n\t\treturn\n\t}\n\n\tn := len(b)\n\tif n == 0 {\n\t\treturn\n\t}\n\n\t// If the header isn't valid, we don't normalize it.\n\tfor _, c := range b {\n\t\tif !validHeaderFieldByte(c) {\n\t\t\treturn\n\t\t}\n\t}\n\n\tupper := true\n\tfor i, c := range b {\n\t\tif upper {\n\t\t\tc = toUpperTable[c]\n\t\t} else {\n\t\t\tc = toLowerTable[c]\n\t\t}\n\t\tupper = c == '-'\n\t\tb[i] = c\n\t}\n}\n\n// removeNewLines will replace `\\r` and `\\n` with an empty space.\nfunc removeNewLines(raw []byte) []byte {\n\t// check if a `\\r` is present and save the position.\n\t// if no `\\r` is found, check if a `\\n` is present.\n\tfoundR := bytes.IndexByte(raw, rChar)\n\tfoundN := bytes.IndexByte(raw, nChar)\n\tstart := 0\n\n\tswitch {\n\tcase foundN != -1:\n\t\tif foundR > foundN {\n\t\t\tstart = foundN\n\t\t} else if foundR != -1 {\n\t\t\tstart = foundR\n\t\t}\n\tcase foundR != -1:\n\t\tstart = foundR\n\tdefault:\n\t\treturn raw\n\t}\n\n\tfor i := start; i < len(raw); i++ {\n\t\tswitch raw[i] {\n\t\tcase rChar, nChar:\n\t\t\traw[i] = ' '\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn raw\n}\n\n// AppendNormalizedHeaderKey appends normalized header key (name) to dst\n// and returns the resulting dst.\n//\n// Normalized header key starts with uppercase letter. The first letters\n// after dashes are also uppercased. All the other letters are lowercased.\n// Examples:\n//\n//   - coNTENT-TYPe -> Content-Type\n//   - HOST -> Host\n//   - foo-bar-baz -> Foo-Bar-Baz\nfunc AppendNormalizedHeaderKey(dst []byte, key string) []byte {\n\tdst = append(dst, key...)\n\tnormalizeHeaderKey(dst[len(dst)-len(key):], false)\n\treturn dst\n}\n\n// AppendNormalizedHeaderKeyBytes appends normalized header key (name) to dst\n// and returns the resulting dst.\n//\n// Normalized header key starts with uppercase letter. The first letters\n// after dashes are also uppercased. All the other letters are lowercased.\n// Examples:\n//\n//   - coNTENT-TYPe -> Content-Type\n//   - HOST -> Host\n//   - foo-bar-baz -> Foo-Bar-Baz\nfunc AppendNormalizedHeaderKeyBytes(dst, key []byte) []byte {\n\treturn AppendNormalizedHeaderKey(dst, b2s(key))\n}\n\nfunc appendTrailerBytes(dst []byte, trailer [][]byte, sep []byte) []byte {\n\tfor i, n := 0, len(trailer); i < n; i++ {\n\t\tdst = append(dst, trailer[i]...)\n\t\tif i+1 < n {\n\t\t\tdst = append(dst, sep...)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc copyTrailer(dst, src [][]byte) [][]byte {\n\tif cap(dst) >= len(src) {\n\t\tdst = dst[:len(src)]\n\t} else {\n\t\tdst = append(dst[:0], src...)\n\t}\n\n\tfor i := range dst {\n\t\tl := len(src[i])\n\t\tif cap(dst[i]) >= l {\n\t\t\tdst[i] = dst[i][:l]\n\t\t} else {\n\t\t\tdst[i] = make([]byte, l)\n\t\t}\n\t\tcopy(dst[i], src[i])\n\t}\n\treturn dst\n}\n\n// ErrNothingRead is returned when a keep-alive connection is closed,\n// either because the remote closed it or because of a read timeout.\ntype ErrNothingRead struct {\n\terror\n}\n\n// ErrSmallBuffer is returned when the provided buffer size is too small\n// for reading request and/or response headers.\n//\n// ReadBufferSize value from Server or clients should reduce the number\n// of such errors.\ntype ErrSmallBuffer struct {\n\terror\n}\n\nfunc mustPeekBuffered(r *bufio.Reader) []byte {\n\tbuf, err := r.Peek(r.Buffered())\n\tif len(buf) == 0 || err != nil {\n\t\tpanic(fmt.Sprintf(\"bufio.Reader.Peek() returned unexpected data (%q, %v)\", buf, err))\n\t}\n\treturn buf\n}\n\nfunc mustDiscard(r *bufio.Reader, n int) {\n\tif _, err := r.Discard(n); err != nil {\n\t\tpanic(fmt.Sprintf(\"bufio.Reader.Discard(%d) failed: %v\", n, err))\n\t}\n}\n"
  },
  {
    "path": "header_regression_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestIssue28ResponseWithoutBodyNoContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\t// Empty response without content-type\n\ts := r.String()\n\tif strings.Contains(s, \"Content-Type\") {\n\t\tt.Fatalf(\"unexpected Content-Type found in response header with empty body: %q\", s)\n\t}\n\n\t// Explicitly set content-type\n\tr.Header.SetContentType(\"foo/bar\")\n\ts = r.String()\n\tif !strings.Contains(s, \"Content-Type: foo/bar\\r\\n\") {\n\t\tt.Fatalf(\"missing explicitly set content-type for empty response: %q\", s)\n\t}\n\n\t// Non-empty response.\n\tr.Reset()\n\tr.SetBodyString(\"foobar\")\n\ts = r.String()\n\tif !strings.Contains(s, fmt.Sprintf(\"Content-Type: %s\\r\\n\", defaultContentType)) {\n\t\tt.Fatalf(\"missing default content-type for non-empty response: %q\", s)\n\t}\n\n\t// Non-empty response with custom content-type.\n\tr.Header.SetContentType(\"aaa/bbb\")\n\ts = r.String()\n\tif !strings.Contains(s, \"Content-Type: aaa/bbb\\r\\n\") {\n\t\tt.Fatalf(\"missing custom content-type: %q\", s)\n\t}\n}\n\nfunc TestIssue6RequestHeaderSetContentType(t *testing.T) {\n\tt.Parallel()\n\n\ttestIssue6RequestHeaderSetContentType(t, MethodGet)\n\ttestIssue6RequestHeaderSetContentType(t, MethodPost)\n\ttestIssue6RequestHeaderSetContentType(t, MethodPut)\n\ttestIssue6RequestHeaderSetContentType(t, MethodPatch)\n}\n\nfunc testIssue6RequestHeaderSetContentType(t *testing.T, method string) {\n\tcontentType := \"application/json\"\n\tcontentLength := 123\n\n\tvar h RequestHeader\n\th.SetMethod(method)\n\th.SetRequestURI(\"http://localhost/test\")\n\th.SetContentType(contentType)\n\th.SetContentLength(contentLength)\n\n\tissue6VerifyRequestHeader(t, &h, contentType, contentLength, method)\n\n\ts := h.String()\n\n\tvar h1 RequestHeader\n\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tissue6VerifyRequestHeader(t, &h1, contentType, contentLength, method)\n}\n\nfunc issue6VerifyRequestHeader(t *testing.T, h *RequestHeader, contentType string, contentLength int, method string) {\n\tif string(h.ContentType()) != contentType {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q. method=%q\", h.ContentType(), contentType, method)\n\t}\n\tif string(h.Method()) != method {\n\t\tt.Fatalf(\"unexpected method: %q. Expecting %q\", h.Method(), method)\n\t}\n\tif h.ContentLength() != contentLength {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting %d. method=%q\", h.ContentLength(), contentLength, method)\n\t}\n}\n"
  },
  {
    "path": "header_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestResponseHeaderAddContentType(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Add(\"Content-Type\", \"test\")\n\n\tgot := string(h.Peek(\"Content-Type\"))\n\texpected := \"test\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tvar buf bytes.Buffer\n\tif _, err := h.WriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing header: %v\", err)\n\t}\n\n\tif n := strings.Count(buf.String(), \"Content-Type: \"); n != 1 {\n\t\tt.Errorf(\"Content-Type occurred %d times\", n)\n\t}\n}\n\nfunc TestResponseHeaderAddContentEncoding(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Add(\"Content-Encoding\", \"test\")\n\n\tgot := string(h.Peek(\"Content-Encoding\"))\n\texpected := \"test\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tvar buf bytes.Buffer\n\tif _, err := h.WriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing header: %v\", err)\n\t}\n\n\tif n := strings.Count(buf.String(), \"Content-Encoding: \"); n != 1 {\n\t\tt.Errorf(\"Content-Encoding occurred %d times\", n)\n\t}\n}\n\nfunc TestResponseHeaderMultiLineValue(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"HTTP/1.1 200 SuperOK\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Content-Type: foo/bar;\\r\\n\\tnewline;\\r\\n another/newline\\r\\n\" +\n\t\t\"Foo: Bar\\r\\n\" +\n\t\t\"Multi-Line: one;\\r\\n two\\r\\n\" +\n\t\t\"Values: v1;\\r\\n v2; v3;\\r\\n v4;\\tv5\\r\\n\" +\n\t\t\"\\r\\n\"\n\theader := new(ResponseHeader)\n\tif _, err := header.parse([]byte(s)); err != nil {\n\t\tt.Fatalf(\"parse headers with multi-line values failed, %v\", err)\n\t}\n\tresponse, err := http.ReadResponse(bufio.NewReader(strings.NewReader(s)), nil)\n\tif err != nil {\n\t\tt.Fatalf(\"parse response using net/http failed, %v\", err)\n\t}\n\tdefer func() { _ = response.Body.Close() }()\n\n\tif !bytes.Equal(header.StatusMessage(), []byte(\"SuperOK\")) {\n\t\tt.Errorf(\"parse status line with non-default value failed, got: '%q' want: 'SuperOK'\", header.StatusMessage())\n\t}\n\n\theader.SetProtocol([]byte(\"HTTP/3.3\"))\n\tif !bytes.Equal(header.Protocol(), []byte(\"HTTP/3.3\")) {\n\t\tt.Errorf(\"parse protocol with non-default value failed, got: '%q' want: 'HTTP/3.3'\", header.Protocol())\n\t}\n\n\tif !bytes.Equal(header.appendStatusLine(nil), []byte(\"HTTP/3.3 200 SuperOK\\r\\n\")) {\n\t\tt.Errorf(\"parse status line with non-default value failed, got: '%q' want: 'HTTP/3.3 200 SuperOK'\", header.Protocol())\n\t}\n\n\theader.SetStatusMessage(nil)\n\n\tif !bytes.Equal(header.appendStatusLine(nil), []byte(\"HTTP/3.3 200 OK\\r\\n\")) {\n\t\tt.Errorf(\"parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'\", header.appendStatusLine(nil))\n\t}\n\n\theader.SetStatusMessage(s2b(StatusMessage(200)))\n\n\tif !bytes.Equal(header.appendStatusLine(nil), []byte(\"HTTP/3.3 200 OK\\r\\n\")) {\n\t\tt.Errorf(\"parse status line with default protocol value failed, got: '%q' want: 'HTTP/3.3 200 OK'\", header.appendStatusLine(nil))\n\t}\n\n\tfor name, vals := range response.Header {\n\t\tgot := string(header.Peek(name))\n\t\twant := vals[0]\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"unexpected %q got: %q want: %q\", name, got, want)\n\t\t}\n\t}\n}\n\nfunc TestIssue1808(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"HTTP/1.1 200\\r\\n\" +\n\t\t\"WithTabs: \\t v1 \\t\\r\\n\" + // \"v1\"\n\t\t\"WithTabs-Start: \\t \\t v1 \\r\\n\" + // \"v1\"\n\t\t\"WithTabs-End: v1 \\t \\t\\t\\t\\r\\n\" + // \"v1\"\n\t\t\"WithTabs-Multi-Line: \\t v1 \\t;\\r\\n \\t v2 \\t;\\r\\n\\t v3\\r\\n\" + // \"v1 \\t; v2 \\t; v3\"\n\t\t\"\\r\\n\"\n\n\tresHeader := new(ResponseHeader)\n\tif _, err := resHeader.parse([]byte(s)); err != nil {\n\t\tt.Fatalf(\"parse headers with tabs values failed, %v\", err)\n\t}\n\n\tgroundTruth := map[string]string{\n\t\t\"WithTabs\":            \"v1\",\n\t\t\"WithTabs-Start\":      \"v1\",\n\t\t\"WithTabs-End\":        \"v1\",\n\t\t\"WithTabs-Multi-Line\": \"v1 \\t; v2 \\t; v3\",\n\t}\n\n\tfor name, want := range groundTruth {\n\t\tif got := b2s(resHeader.Peek(name)); got != want {\n\t\t\tt.Errorf(\"ResponseHeader.parser() unexpected %q got: %q want: %q\", name, got, want)\n\t\t}\n\t}\n\n\ts = \"GET / HTTP/1.1\\r\\n\" +\n\t\t\"WithTabs: \\t v1 \\t\\r\\n\" + // \"v1\"\n\t\t\"WithTabs-Start: \\t \\t v1 \\r\\n\" + // \"v1\"\n\t\t\"WithTabs-End: v1 \\t \\t\\t\\t\\r\\n\" + // \"v1\"\n\t\t\"WithTabs-Multi-Line: \\t v1 \\t;\\r\\n \\t v2 \\t;\\r\\n\\t v3\\r\\n\" + // \"v1 \\t; v2 \\t; v3\"\n\t\t\"\\r\\n\"\n\n\treqHeader := new(RequestHeader)\n\tif _, err := reqHeader.parse([]byte(s)); err != nil {\n\t\tt.Fatalf(\"parse headers with tabs values failed, %v\", err)\n\t}\n\n\tfor name, want := range groundTruth {\n\t\tif got := b2s(reqHeader.Peek(name)); got != want {\n\t\t\tt.Errorf(\"RequestHeader.parser() unexpected %q got: %q want: %q\", name, got, want)\n\t\t}\n\t}\n}\n\nfunc TestResponseHeaderMultiLinePanicked(t *testing.T) {\n\tt.Parallel()\n\n\t// Input generated by fuzz testing that caused the parser to panic.\n\ts, _ := base64.StdEncoding.DecodeString(\"aAEAIDoKKDoKICA6CgkKCiA6CiA6CgkpCiA6CiA6CiA6Cig6CiAgOgoJCgogOgogOgoJKQogOgogOgogOgogOgogOgoJOg86CiA6CiA6Cig6CiAyCg==\")\n\theader := new(RequestHeader)\n\tif _, err := header.parse(s); err == nil {\n\t\tt.Error(\"expected error, got <nil>\")\n\t}\n}\n\nfunc TestRequestHeaderLooseBackslashR(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.1\\r\\n\" +\n\t\t\"Host: go.dev\\r\\n\" +\n\t\t\"\\rFoo: bar\\r\\n\" +\n\t\t\"\\r\\n\"\n\theader := new(RequestHeader)\n\tif _, err := header.parse([]byte(s)); err == nil {\n\t\tt.Fatal(\"expected error, got <nil>\")\n\t}\n}\n\nfunc TestResponseHeaderEmptyValueFromHeader(t *testing.T) {\n\tt.Parallel()\n\n\tvar h1 ResponseHeader\n\th1.SetContentType(\"foo/bar\")\n\th1.Set(\"EmptyValue1\", \"\")\n\th1.Set(\"EmptyValue2\", \" \")\n\ts := h1.String()\n\n\tvar h ResponseHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(h.ContentType(), h1.ContentType()) {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q\", h.ContentType(), h1.ContentType())\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc TestResponseHeaderEmptyValueFromString(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"HTTP/1.1 200 OK\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Content-Type: foo/bar\\r\\n\" +\n\t\t\"EmptyValue2: \\r\\n\" +\n\t\t\"\\r\\n\"\n\n\tvar h ResponseHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(h.ContentType()) != \"foo/bar\" {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q\", h.ContentType(), \"foo/bar\")\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc TestRequestHeaderEmptyValueFromHeader(t *testing.T) {\n\tt.Parallel()\n\n\tvar h1 RequestHeader\n\th1.SetRequestURI(\"/foo/bar\")\n\th1.SetHost(\"foobar\")\n\th1.Set(\"EmptyValue1\", \"\")\n\th1.Set(\"EmptyValue2\", \" \")\n\ts := h1.String()\n\n\tvar h RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(h.Host(), h1.Host()) {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), h1.Host())\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc TestRequestHeaderEmptyValueFromString(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.1\\r\\n\" +\n\t\t\"EmptyValue1:\\r\\n\" +\n\t\t\"Host: foobar\\r\\n\" +\n\t\t\"EmptyValue2: \\r\\n\" +\n\t\t\"\\r\\n\"\n\tvar h RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(h.Host()) != \"foobar\" {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t}\n\tv1 := h.Peek(\"EmptyValue1\")\n\tif len(v1) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t}\n\tv2 := h.Peek(\"EmptyValue2\")\n\tif len(v2) > 0 {\n\t\tt.Fatalf(\"expecting empty value. Got %q\", v2)\n\t}\n}\n\nfunc TestRequestRawHeaders(t *testing.T) {\n\tt.Parallel()\n\n\tkvs := \"hOsT: foobar\\r\\n\" +\n\t\t\"value:  b\\r\\n\" +\n\t\t\"uSeR agent: agent\\r\\n\" +\n\t\t\"\\r\\n\"\n\tt.Run(\"normalized\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.1\\r\\n\" + kvs\n\t\texp := kvs\n\t\tvar h RequestHeader\n\t\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\t\tif err := h.Read(br); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif string(h.Host()) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t}\n\t\tv2 := h.Peek(\"Value\")\n\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t}\n\t\t// We accept invalid headers with a space.\n\t\t// See: https://github.com/valyala/fasthttp/issues/1917\n\t\tv3 := h.Peek(\"uSeR agent\")\n\t\tif !bytes.Equal(v3, []byte(\"agent\")) {\n\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v3)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n\tfor _, n := range []int{0, 1, 4, 8} {\n\t\tt.Run(fmt.Sprintf(\"post-%dk\", n), func(t *testing.T) {\n\t\t\tl := 1024 * n\n\t\t\tbody := make([]byte, l)\n\t\t\tfor i := range body {\n\t\t\t\tbody[i] = 'a'\n\t\t\t}\n\t\t\tcl := fmt.Sprintf(\"Content-Length: %d\\r\\n\", l)\n\t\t\ts := \"POST / HTTP/1.1\\r\\n\" + cl + kvs + string(body)\n\t\t\texp := cl + kvs\n\t\t\tvar h RequestHeader\n\t\t\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\t\t\tif err := h.Read(br); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif string(h.Host()) != \"foobar\" {\n\t\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t\t}\n\t\t\tv2 := h.Peek(\"Value\")\n\t\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t\t}\n\t\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t\t}\n\t\t})\n\t}\n\tt.Run(\"http10\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.0\\r\\n\" + kvs\n\t\texp := kvs\n\t\tvar h RequestHeader\n\t\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\t\tif err := h.Read(br); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif string(h.Host()) != \"foobar\" {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"foobar\")\n\t\t}\n\t\tv2 := h.Peek(\"Value\")\n\t\tif !bytes.Equal(v2, []byte{'b'}) {\n\t\t\tt.Fatalf(\"expecting non empty value. Got %q\", v2)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n\tt.Run(\"no-kvs\", func(t *testing.T) {\n\t\ts := \"GET / HTTP/1.1\\r\\n\\r\\n\"\n\t\texp := \"\"\n\t\tvar h RequestHeader\n\t\th.DisableNormalizing()\n\t\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\t\tif err := h.Read(br); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(h.Host()) != 0 {\n\t\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h.Host(), \"\")\n\t\t}\n\t\tv1 := h.Peek(\"NoKey\")\n\t\tif len(v1) > 0 {\n\t\t\tt.Fatalf(\"expecting empty value. Got %q\", v1)\n\t\t}\n\t\tif raw := h.RawHeaders(); string(raw) != exp {\n\t\t\tt.Fatalf(\"expected header %q, got %q\", exp, raw)\n\t\t}\n\t})\n}\n\nfunc TestRequestDisableSpecialHeaders(t *testing.T) {\n\tt.Parallel()\n\n\t// Test original header functionality\n\tkvs := \"Host: foobar\\r\\n\" +\n\t\t\"User-Agent: ua\\r\\n\" +\n\t\t\"Non-Special: val\\r\\n\" +\n\t\t\"\\r\\n\"\n\n\tvar h RequestHeader\n\th.DisableSpecialHeader()\n\n\ts := \"GET / HTTP/1.0\\r\\n\" + kvs\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\t// assert order of all headers preserved\n\tif h.String() != s {\n\t\tt.Fatalf(\"Headers not equal: %q. Expecting %q\", h.String(), s)\n\t}\n\th.SetCanonical([]byte(\"host\"), []byte(\"notfoobar\"))\n\tif string(h.Host()) != \"foobar\" {\n\t\tt.Fatalf(\"unexpected: %q. Expecting %q\", h.Host(), \"foobar\")\n\t}\n\tif h.String() != \"GET / HTTP/1.0\\r\\nHost: foobar\\r\\nUser-Agent: ua\\r\\nNon-Special: val\\r\\nhost: notfoobar\\r\\n\\r\\n\" {\n\t\tt.Fatalf(\"custom special header ordering failed: %q\", h.String())\n\t}\n\n\t// Test body parsing with DisableSpecialHeader - should work correctly after fix\n\ttestBody := \"a=b&test=123\"\n\trawRequest := \"POST /test HTTP/1.1\\r\\n\" +\n\t\t\"Host: example.com\\r\\n\" +\n\t\t\"Content-Type: application/x-www-form-urlencoded\\r\\n\" +\n\t\t\"Content-Length: \" + strconv.Itoa(len(testBody)) + \"\\r\\n\" +\n\t\t\"\\r\\n\" +\n\t\ttestBody\n\n\tvar req Request\n\treq.Header.DisableSpecialHeader()\n\n\tbr2 := bufio.NewReader(bytes.NewBufferString(rawRequest))\n\tif err := req.ReadLimitBody(br2, 0); err != nil {\n\t\tt.Fatalf(\"unexpected error reading request: %v\", err)\n\t}\n\n\t// Verify Content-Length is correctly parsed with DisableSpecialHeader\n\tif req.Header.ContentLength() != len(testBody) {\n\t\tt.Fatalf(\"ContentLength() incorrect with DisableSpecialHeader: got %d, expected %d\",\n\t\t\treq.Header.ContentLength(), len(testBody))\n\t}\n\n\t// Verify body is preserved with DisableSpecialHeader\n\tif string(req.Body()) != testBody {\n\t\tt.Fatalf(\"body content incorrect with DisableSpecialHeader: got %q, expected %q\",\n\t\t\tstring(req.Body()), testBody)\n\t}\n}\n\nfunc TestRequestDisableSpecialHeadersChunked(t *testing.T) {\n\tt.Parallel()\n\n\ttestBody := \"chunked-test\"\n\trawRequest := \"POST /test HTTP/1.1\\r\\n\" +\n\t\t\"Host: example.com\\r\\n\" +\n\t\t\"Transfer-Encoding: chunked\\r\\n\" +\n\t\t\"\\r\\n\" +\n\t\t\"c\\r\\n\" +\n\t\ttestBody + \"\\r\\n\" +\n\t\t\"0\\r\\n\\r\\n\"\n\n\tvar req Request\n\treq.Header.DisableSpecialHeader()\n\n\tbr := bufio.NewReader(bytes.NewBufferString(rawRequest))\n\tif err := req.ReadLimitBody(br, 0); err != nil {\n\t\tt.Fatalf(\"unexpected error reading chunked request: %v\", err)\n\t}\n\n\t// Verify chunked encoding is detected\n\tif req.Header.ContentLength() != -1 {\n\t\tt.Fatalf(\"chunked encoding not detected with DisableSpecialHeader: got %d, expected -1\",\n\t\t\treq.Header.ContentLength())\n\t}\n\n\t// Verify chunked body is preserved\n\tif string(req.Body()) != testBody {\n\t\tt.Fatalf(\"chunked body incorrect with DisableSpecialHeader: got %q, expected %q\",\n\t\t\tstring(req.Body()), testBody)\n\t}\n}\n\nfunc TestRequestDisableSpecialHeadersIdentity(t *testing.T) {\n\tt.Parallel()\n\n\trawRequest := \"GET /test HTTP/1.1\\r\\n\" +\n\t\t\"Host: example.com\\r\\n\" +\n\t\t\"\\r\\n\"\n\n\tvar req Request\n\treq.Header.DisableSpecialHeader()\n\n\tbr := bufio.NewReader(bytes.NewBufferString(rawRequest))\n\tif err := req.ReadLimitBody(br, 0); err != nil {\n\t\tt.Fatalf(\"unexpected error reading identity request: %v\", err)\n\t}\n\n\t// Verify identity encoding is detected\n\tif req.Header.ContentLength() != -2 {\n\t\tt.Fatalf(\"identity encoding not detected with DisableSpecialHeader: got %d, expected -2\",\n\t\t\treq.Header.ContentLength())\n\t}\n}\n\nfunc TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.Set(\"Cookie\", \"ID&14\")\n\ts := h.String()\n\n\tif !strings.Contains(s, \"Cookie: ID&14\") {\n\t\tt.Fatalf(\"Missing cookie in request header: %q\", s)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tcookie := h1.Peek(HeaderCookie)\n\tif string(cookie) != \"ID&14\" {\n\t\tt.Fatalf(\"unexpected cooke: %q. Expecting %q\", cookie, \"ID&14\")\n\t}\n\n\tcookie = h1.Cookie(\"\")\n\tif string(cookie) != \"ID&14\" {\n\t\tt.Fatalf(\"unexpected cooke: %q. Expecting %q\", cookie, \"ID&14\")\n\t}\n}\n\nfunc TestResponseHeaderDefaultStatusCode(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\tstatusCode := h.StatusCode()\n\tif statusCode != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusOK)\n\t}\n}\n\nfunc TestResponseHeaderDelClientCookie(t *testing.T) {\n\tt.Parallel()\n\n\tcookieName := \"foobar\"\n\n\tvar h ResponseHeader\n\tc := AcquireCookie()\n\tc.SetKey(cookieName)\n\tc.SetValue(\"aasdfsdaf\")\n\th.SetCookie(c)\n\n\th.DelClientCookieBytes([]byte(cookieName))\n\tif !h.Cookie(c) {\n\t\tt.Fatalf(\"expecting cookie %q\", c.Key())\n\t}\n\tif !c.Expire().Equal(CookieExpireDelete) {\n\t\tt.Fatalf(\"unexpected cookie expiration time: %q. Expecting %q\", c.Expire(), CookieExpireDelete)\n\t}\n\tif len(c.Value()) > 0 {\n\t\tt.Fatalf(\"unexpected cookie value: %q. Expecting empty value\", c.Value())\n\t}\n\tReleaseCookie(c)\n}\n\nfunc TestResponseHeaderAdd(t *testing.T) {\n\tt.Parallel()\n\n\tm := make(map[string]struct{})\n\tvar h ResponseHeader\n\th.Add(\"aaa\", \"bbb\")\n\th.Add(\"content-type\", \"xxx\")\n\tm[\"bbb\"] = struct{}{}\n\tm[\"xxx\"] = struct{}{}\n\tfor i := range 10 {\n\t\tv := strconv.Itoa(i)\n\t\th.Add(\"Foo-Bar\", v)\n\t\tm[v] = struct{}{}\n\t}\n\tif h.Len() != 12 {\n\t\tt.Fatalf(\"unexpected header len %d. Expecting 12\", h.Len())\n\t}\n\n\tfor k, v := range h.All() {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"Content-Type\":\n\t\t\tif _, ok := m[string(v)]; !ok {\n\t\t\t\tt.Fatalf(\"unexpected value found %q. key %q\", v, k)\n\t\t\t}\n\t\t\tdelete(m, string(v))\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t}\n\tif len(m) > 0 {\n\t\tt.Fatalf(\"%d headers are missed\", len(m))\n\t}\n\n\ts := h.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tvar h1 ResponseHeader\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tfor k, v := range h.All() {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"Content-Type\":\n\t\t\tm[string(v)] = struct{}{}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t}\n\tif len(m) != 12 {\n\t\tt.Fatalf(\"unexpected number of headers: %d. Expecting 12\", len(m))\n\t}\n}\n\nfunc TestRequestHeaderAdd(t *testing.T) {\n\tt.Parallel()\n\n\tm := make(map[string]struct{})\n\tvar h RequestHeader\n\th.Add(\"aaa\", \"bbb\")\n\th.Add(\"user-agent\", \"xxx\")\n\tm[\"bbb\"] = struct{}{}\n\tm[\"xxx\"] = struct{}{}\n\tfor i := range 10 {\n\t\tv := strconv.Itoa(i)\n\t\th.Add(\"Foo-Bar\", v)\n\t\tm[v] = struct{}{}\n\t}\n\tif h.Len() != 12 {\n\t\tt.Fatalf(\"unexpected header len %d. Expecting 12\", h.Len())\n\t}\n\n\tfor k, v := range h.All() {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"User-Agent\":\n\t\t\tif _, ok := m[string(v)]; !ok {\n\t\t\t\tt.Fatalf(\"unexpected value found %q. key %q\", v, k)\n\t\t\t}\n\t\t\tdelete(m, string(v))\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t}\n\tif len(m) > 0 {\n\t\tt.Fatalf(\"%d headers are missed\", len(m))\n\t}\n\n\ts := h.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tvar h1 RequestHeader\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tfor k, v := range h.All() {\n\t\tswitch string(k) {\n\t\tcase \"Aaa\", \"Foo-Bar\", \"User-Agent\":\n\t\t\tm[string(v)] = struct{}{}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected key found: %q\", k)\n\t\t}\n\t}\n\tif len(m) != 12 {\n\t\tt.Fatalf(\"unexpected number of headers: %d. Expecting 12\", len(m))\n\t}\n\ts1 := h1.String()\n\tif s != s1 {\n\t\tt.Fatalf(\"unexpected headers %q. Expecting %q\", s1, s)\n\t}\n}\n\nfunc TestHasHeaderValue(t *testing.T) {\n\tt.Parallel()\n\n\ttestHasHeaderValue(t, \"foobar\", \"foobar\", true)\n\ttestHasHeaderValue(t, \"foobar\", \"foo\", false)\n\ttestHasHeaderValue(t, \"foobar\", \"bar\", false)\n\ttestHasHeaderValue(t, \"keep-alive, Upgrade\", \"keep-alive\", true)\n\ttestHasHeaderValue(t, \"keep-alive  ,    Upgrade\", \"Upgrade\", true)\n\ttestHasHeaderValue(t, \"keep-alive, Upgrade\", \"Upgrade-foo\", false)\n\ttestHasHeaderValue(t, \"keep-alive, Upgrade\", \"Upgr\", false)\n\ttestHasHeaderValue(t, \"foo  ,   bar,  baz   ,\", \"foo\", true)\n\ttestHasHeaderValue(t, \"foo  ,   bar,  baz   ,\", \"bar\", true)\n\ttestHasHeaderValue(t, \"foo  ,   bar,  baz   ,\", \"baz\", true)\n\ttestHasHeaderValue(t, \"foo  ,   bar,  baz   ,\", \"ba\", false)\n\ttestHasHeaderValue(t, \"foo, \", \"\", true)\n\ttestHasHeaderValue(t, \"foo\", \"\", false)\n}\n\nfunc testHasHeaderValue(t *testing.T, s, value string, has bool) {\n\tok := hasHeaderValue([]byte(s), []byte(value))\n\tif ok != has {\n\t\tt.Fatalf(\"unexpected hasHeaderValue(%q, %q)=%v. Expecting %v\", s, value, ok, has)\n\t}\n}\n\nfunc TestRequestHeaderDel(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.Set(\"Foo-Bar\", \"baz\")\n\th.Set(\"aaa\", \"bbb\")\n\th.Set(HeaderConnection, \"keep-alive\")\n\th.Set(\"Content-Type\", \"aaa\")\n\th.Set(HeaderHost, \"aaabbb\")\n\th.Set(\"User-Agent\", \"asdfas\")\n\th.Set(\"Content-Length\", \"1123\")\n\th.Set(\"Cookie\", \"foobar=baz\")\n\th.Set(HeaderTrailer, \"foo, bar\")\n\n\th.Del(\"foo-bar\")\n\th.Del(\"connection\")\n\th.DelBytes([]byte(\"content-type\"))\n\th.Del(\"Host\")\n\th.Del(\"user-agent\")\n\th.Del(\"content-length\")\n\th.Del(\"cookie\")\n\th.Del(\"trailer\")\n\n\thv := h.Peek(\"aaa\")\n\tif string(hv) != \"bbb\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"bbb\")\n\t}\n\thv = h.Peek(\"Foo-Bar\")\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderContentType)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderHost)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderUserAgent)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderContentLength)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderCookie)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderTrailer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\n\tcv := h.Cookie(\"foobar\")\n\tif len(cv) > 0 {\n\t\tt.Fatalf(\"unexpected cookie obtained: %q\", cv)\n\t}\n\tif h.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", h.ContentLength())\n\t}\n}\n\nfunc TestResponseHeaderDel(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\th.Set(\"Foo-Bar\", \"baz\")\n\th.Set(\"aaa\", \"bbb\")\n\th.Set(HeaderConnection, \"keep-alive\")\n\th.Set(HeaderContentType, \"aaa\")\n\th.Set(HeaderContentEncoding, \"gzip\")\n\th.Set(HeaderServer, \"aaabbb\")\n\th.Set(HeaderContentLength, \"1123\")\n\th.Set(HeaderTrailer, \"foo, bar\")\n\n\tvar c Cookie\n\tc.SetKey(\"foo\")\n\tc.SetValue(\"bar\")\n\th.SetCookie(&c)\n\n\th.Del(\"foo-bar\")\n\th.Del(\"connection\")\n\th.DelBytes([]byte(\"content-type\"))\n\th.Del(HeaderServer)\n\th.Del(\"content-length\")\n\th.Del(\"set-cookie\")\n\th.Del(\"trailer\")\n\n\thv := h.Peek(\"aaa\")\n\tif string(hv) != \"bbb\" {\n\t\tt.Fatalf(\"unexpected header value: %q. Expecting %q\", hv, \"bbb\")\n\t}\n\thv = h.Peek(\"Foo-Bar\")\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero header value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderConnection)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderContentType)\n\tif !bytes.Equal(hv, defaultContentType) {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q\", hv, defaultContentType)\n\t}\n\thv = h.Peek(HeaderContentEncoding)\n\tif string(hv) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected content-encoding: %q. Expecting %q\", hv, \"gzip\")\n\t}\n\thv = h.Peek(HeaderServer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderContentLength)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\thv = h.Peek(HeaderTrailer)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"non-zero value: %q\", hv)\n\t}\n\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"unexpected cookie obtained: %q\", &c)\n\t}\n\tif h.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", h.ContentLength())\n\t}\n}\n\nfunc TestResponseHeaderSetTrailerGetBytes(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\th.noDefaultDate = true\n\th.Set(\"Foo\", \"bar\")\n\th.Set(HeaderTrailer, \"Baz\")\n\th.Set(\"Baz\", \"test\")\n\n\theaderBytes := h.Header()\n\tn, err := h.parseFirstLine(headerBytes)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif string(headerBytes[n:]) != \"Foo: bar\\r\\nTrailer: Baz\\r\\n\\r\\n\" {\n\t\tt.Fatalf(\"Unexpected header: %q. Expected %q\", headerBytes[n:], \"Foo: bar\\r\\nTrailer: Baz\\r\\n\\r\\n\")\n\t}\n\tif string(h.TrailerHeader()) != \"Baz: test\\r\\n\\r\\n\" {\n\t\tt.Fatalf(\"Unexpected trailer header: %q. Expected %q\", h.TrailerHeader(), \"Baz: test\\r\\n\\r\\n\")\n\t}\n}\n\nfunc TestRequestHeaderSetTrailerGetBytes(t *testing.T) {\n\tt.Parallel()\n\n\th := &RequestHeader{}\n\th.Set(\"Foo\", \"bar\")\n\th.Set(HeaderTrailer, \"Baz\")\n\th.Set(\"Baz\", \"test\")\n\n\theaderBytes := h.Header()\n\tn, err := h.parseFirstLine(headerBytes)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif string(headerBytes[n:]) != \"Foo: bar\\r\\nTrailer: Baz\\r\\n\\r\\n\" {\n\t\tt.Fatalf(\"Unexpected header: %q. Expected %q\", headerBytes[n:], \"Foo: bar\\nTrailer: Baz\\r\\n\\r\\n\")\n\t}\n\tif string(h.TrailerHeader()) != \"Baz: test\\r\\n\\r\\n\" {\n\t\tt.Fatalf(\"Unexpected trailer header: %q. Expected %q\", h.TrailerHeader(), \"Baz: test\\r\\n\\r\\n\")\n\t}\n}\n\nfunc TestAppendNormalizedHeaderKeyBytes(t *testing.T) {\n\tt.Parallel()\n\n\ttestAppendNormalizedHeaderKeyBytes(t, \"\", \"\")\n\ttestAppendNormalizedHeaderKeyBytes(t, \"Content-Type\", \"Content-Type\")\n\ttestAppendNormalizedHeaderKeyBytes(t, \"foO-bAr-BAZ\", \"Foo-Bar-Baz\")\n}\n\nfunc testAppendNormalizedHeaderKeyBytes(t *testing.T, key, expectedKey string) {\n\tbuf := []byte(\"foobar\")\n\tresult := AppendNormalizedHeaderKeyBytes(buf, []byte(key))\n\tnormalizedKey := result[len(buf):]\n\tif string(normalizedKey) != expectedKey {\n\t\tt.Fatalf(\"unexpected normalized key %q. Expecting %q\", normalizedKey, expectedKey)\n\t}\n}\n\nfunc TestRequestHeaderHTTP10ConnectionClose(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.0\\r\\nHost: foobar\\r\\n\\r\\n\"\n\tvar h RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting 'Connection: close' request header\")\n\t}\n}\n\nfunc TestRequestHeaderHTTP10ConnectionKeepAlive(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.0\\r\\nHost: foobar\\r\\nConnection: keep-alive\\r\\n\\r\\n\"\n\tvar h RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected 'Connection: close' request header\")\n\t}\n}\n\nfunc TestBufferSnippet(t *testing.T) {\n\tt.Parallel()\n\n\ttestBufferSnippet(t, \"\", `\"\"`)\n\ttestBufferSnippet(t, \"foobar\", `\"foobar\"`)\n\n\tb := string(createFixedBody(199))\n\tbExpected := fmt.Sprintf(\"%q\", b)\n\ttestBufferSnippet(t, b, bExpected)\n\tfor range 10 {\n\t\tb += \"foobar\"\n\t\tbExpected = fmt.Sprintf(\"%q\", b)\n\t\ttestBufferSnippet(t, b, bExpected)\n\t}\n\n\tb = string(createFixedBody(400))\n\tbExpected = fmt.Sprintf(\"%q\", b)\n\ttestBufferSnippet(t, b, bExpected)\n\tfor range 10 {\n\t\tb += \"sadfqwer\"\n\t\tbExpected = fmt.Sprintf(\"%q...%q\", b[:200], b[len(b)-200:])\n\t\ttestBufferSnippet(t, b, bExpected)\n\t}\n}\n\nfunc testBufferSnippet(t *testing.T, buf, expectedSnippet string) {\n\tsnippet := bufferSnippet([]byte(buf))\n\tif snippet != expectedSnippet {\n\t\tt.Fatalf(\"unexpected snippet %q. Expecting %q\", snippet, expectedSnippet)\n\t}\n}\n\nfunc TestResponseHeaderTrailingCRLFSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttrailingCRLF := \"\\r\\n\\r\\n\\r\\n\"\n\ts := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 123\\r\\n\\r\\n\" + trailingCRLF\n\n\tvar r ResponseHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// try reading the trailing CRLF. It must return EOF\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n}\n\nfunc TestResponseHeaderTrailingCRLFError(t *testing.T) {\n\tt.Parallel()\n\n\ttrailingCRLF := \"\\r\\nerror\\r\\n\\r\\n\"\n\ts := \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 123\\r\\n\\r\\n\" + trailingCRLF\n\n\tvar r ResponseHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// try reading the trailing CRLF. It must return EOF\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestRequestHeaderTrailingCRLFSuccess(t *testing.T) {\n\tt.Parallel()\n\n\ttrailingCRLF := \"\\r\\n\\r\\n\\r\\n\"\n\ts := \"GET / HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\" + trailingCRLF\n\n\tvar r RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// try reading the trailing CRLF. It must return EOF\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n}\n\nfunc TestRequestHeaderTrailingCRLFError(t *testing.T) {\n\tt.Parallel()\n\n\ttrailingCRLF := \"\\r\\nerror\\r\\n\\r\\n\"\n\ts := \"GET / HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\" + trailingCRLF\n\n\tvar r RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// try reading the trailing CRLF. It must return EOF\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestRequestHeaderReadEOF(t *testing.T) {\n\tt.Parallel()\n\n\tvar r RequestHeader\n\n\tbr := bufio.NewReader(&bytes.Buffer{})\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n\n\t// incomplete request header mustn't return io.EOF\n\tbr = bufio.NewReader(bytes.NewBufferString(\"GET \"))\n\terr = r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"expecting non-EOF error\")\n\t}\n}\n\nfunc TestResponseHeaderReadEOF(t *testing.T) {\n\tt.Parallel()\n\n\tvar r ResponseHeader\n\n\tbr := bufio.NewReader(&bytes.Buffer{})\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n\n\t// incomplete response header mustn't return io.EOF\n\tbr = bufio.NewReader(bytes.NewBufferString(\"HTTP/1.1 \"))\n\terr = r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"expecting non-EOF error\")\n\t}\n}\n\nfunc TestResponseHeaderOldVersion(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\ts := \"HTTP/1.0 200 OK\\r\\nContent-Length: 5\\r\\nContent-Type: aaa\\r\\n\\r\\n12345\"\n\ts += \"HTTP/1.0 200 OK\\r\\nContent-Length: 2\\r\\nContent-Type: ass\\r\\nConnection: keep-alive\\r\\n\\r\\n42\"\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting 'Connection: close' for the response with old http protocol\")\n\t}\n\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected 'Connection: close' for keep-alive response with old http protocol\")\n\t}\n}\n\nfunc TestRequestHeaderSetByteRange(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestHeaderSetByteRange(t, 0, 10, \"bytes=0-10\")\n\ttestRequestHeaderSetByteRange(t, 123, -1, \"bytes=123-\")\n\ttestRequestHeaderSetByteRange(t, -234, 58349, \"bytes=-234\")\n}\n\nfunc testRequestHeaderSetByteRange(t *testing.T, startPos, endPos int, expectedV string) {\n\tvar h RequestHeader\n\th.SetByteRange(startPos, endPos)\n\tv := h.Peek(HeaderRange)\n\tif string(v) != expectedV {\n\t\tt.Fatalf(\"unexpected range: %q. Expecting %q. startPos=%d, endPos=%d\", v, expectedV, startPos, endPos)\n\t}\n}\n\nfunc TestResponseHeaderSetContentRange(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseHeaderSetContentRange(t, 0, 0, 1, \"bytes 0-0/1\")\n\ttestResponseHeaderSetContentRange(t, 123, 456, 789, \"bytes 123-456/789\")\n}\n\nfunc testResponseHeaderSetContentRange(t *testing.T, startPos, endPos, contentLength int, expectedV string) {\n\tvar h ResponseHeader\n\th.SetContentRange(startPos, endPos, contentLength)\n\tv := h.Peek(HeaderContentRange)\n\tif string(v) != expectedV {\n\t\tt.Fatalf(\"unexpected content-range: %q. Expecting %q. startPos=%d, endPos=%d, contentLength=%d\",\n\t\t\tv, expectedV, startPos, endPos, contentLength)\n\t}\n}\n\nfunc TestRequestHeaderHasAcceptEncoding(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestHeaderHasAcceptEncoding(t, \"\", \"gzip\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip\", \"sdhc\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"deflate\", \"deflate\", true)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"gzi\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"dhc\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"sdh\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"zip\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"flat\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"flate\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"def\", false)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"gzip\", true)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"deflate\", true)\n\ttestRequestHeaderHasAcceptEncoding(t, \"gzip, deflate, sdhc\", \"sdhc\", true)\n}\n\nfunc testRequestHeaderHasAcceptEncoding(t *testing.T, ae, v string, resultExpected bool) {\n\tvar h RequestHeader\n\th.Set(HeaderAcceptEncoding, ae)\n\tresult := h.HasAcceptEncoding(v)\n\tif result != resultExpected {\n\t\tt.Fatalf(\"unexpected result in HasAcceptEncoding(%q, %q): %v. Expecting %v\", ae, v, result, resultExpected)\n\t}\n}\n\nfunc TestVisitHeaderParams(t *testing.T) {\n\tt.Parallel()\n\ttestVisitHeaderParams(t, \"text/plain;charset=utf-8;q=0.39\", [][2]string{{\"charset\", \"utf-8\"}, {\"q\", \"0.39\"}})\n\ttestVisitHeaderParams(t, \"text/plain;   foo=bar   ;\", [][2]string{{\"foo\", \"bar\"}})\n\ttestVisitHeaderParams(t, `text/plain;      foo=\"bar\";   `, [][2]string{{\"foo\", \"bar\"}})\n\ttestVisitHeaderParams(t, `text/plain; foo=\"text/plain,text/html;charset=\\\"utf-8\\\"\"`, [][2]string{{\"foo\", `text/plain,text/html;charset=\\\"utf-8\\\"`}})\n\ttestVisitHeaderParams(t, \"text/plain foo=bar\", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain;\", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain; \", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain; foo\", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain; foo=\", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain; =bar\", [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain; foo = bar\", [][2]string{})\n\ttestVisitHeaderParams(t, `text/plain; foo=\"bar`, [][2]string{})\n\ttestVisitHeaderParams(t, \"text/plain;;foo=bar\", [][2]string{})\n\n\tparsed := make([][2]string, 0)\n\tVisitHeaderParams([]byte(`text/plain; foo=bar; charset=utf-8`), func(key, value []byte) bool {\n\t\tparsed = append(parsed, [2]string{string(key), string(value)})\n\t\treturn !bytes.Equal(key, []byte(\"foo\"))\n\t})\n\n\tif len(parsed) != 1 {\n\t\tt.Fatalf(\"expected 1 HTTP parameter, parsed %v\", len(parsed))\n\t}\n\n\tif parsed[0] != [2]string{\"foo\", \"bar\"} {\n\t\tt.Fatalf(\"unexpected parameter %v=%v. Expecting foo=bar\", parsed[0][0], parsed[0][1])\n\t}\n}\n\nfunc testVisitHeaderParams(t *testing.T, header string, expectedParams [][2]string) {\n\tparsed := make([][2]string, 0)\n\tVisitHeaderParams([]byte(header), func(key, value []byte) bool {\n\t\tparsed = append(parsed, [2]string{string(key), string(value)})\n\t\treturn true\n\t})\n\n\tif len(parsed) != len(expectedParams) {\n\t\tt.Fatalf(\"expected %v HTTP parameters, parsed %v\", len(expectedParams), len(parsed))\n\t}\n\n\tfor i := range expectedParams {\n\t\tif expectedParams[i] != parsed[i] {\n\t\t\tt.Fatalf(\"unexpected parameter %v=%v. Expecting %v=%v\", parsed[i][0], parsed[i][1], expectedParams[i][0], expectedParams[i][1])\n\t\t}\n\t}\n}\n\nfunc TestRequestMultipartFormBoundary(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=foobar\\r\\n\\r\\n\", \"foobar\")\n\n\t// incorrect content-type\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\", \"\")\n\n\t// empty boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=\\r\\n\\r\\n\", \"\")\n\n\t// missing boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data\\r\\n\\r\\n\", \"\")\n\n\t// boundary after other content-type params\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data;   foo=bar;   boundary=--aaabb  \\r\\n\\r\\n\", \"--aaabb\")\n\n\t// quoted boundary\n\ttestRequestMultipartFormBoundary(t, \"POST / HTTP/1.1\\r\\nContent-Type: multipart/form-data; boundary=\\\"foobar\\\"\\r\\n\\r\\n\", \"foobar\")\n\n\tvar h RequestHeader\n\th.SetMultipartFormBoundary(\"foobarbaz\")\n\tb := h.MultipartFormBoundary()\n\tif string(b) != \"foobarbaz\" {\n\t\tt.Fatalf(\"unexpected boundary %q. Expecting %q\", b, \"foobarbaz\")\n\t}\n}\n\nfunc testRequestMultipartFormBoundary(t *testing.T, s, boundary string) {\n\tvar h RequestHeader\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. s=%q, boundary=%q\", err, s, boundary)\n\t}\n\n\tb := h.MultipartFormBoundary()\n\tif string(b) != boundary {\n\t\tt.Fatalf(\"unexpected boundary %q. Expecting %q. s=%q\", b, boundary, s)\n\t}\n}\n\nfunc TestResponseHeaderConnectionUpgrade(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nConnection: Upgrade, HTTP2-Settings\\r\\n\\r\\n\",\n\t\ttrue, true)\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nConnection: keep-alive, Upgrade\\r\\n\\r\\n\",\n\t\ttrue, true)\n\n\t// non-http/1.1 protocol has 'connection: close' by default, which also disables 'connection: upgrade'\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.0 200 OK\\r\\nContent-Length: 10\\r\\nConnection: Upgrade, HTTP2-Settings\\r\\n\\r\\n\",\n\t\tfalse, false)\n\n\t// explicit keep-alive for non-http/1.1, so 'connection: upgrade' works\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.0 200 OK\\r\\nContent-Length: 10\\r\\nConnection: Upgrade, keep-alive\\r\\n\\r\\n\",\n\t\ttrue, true)\n\n\t// implicit keep-alive for http/1.1\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\n\\r\\n\", false, true)\n\n\t// no content-length, so 'connection: close' is assumed\n\ttestResponseHeaderConnectionUpgrade(t, \"HTTP/1.1 200 OK\\r\\n\\r\\n\", false, false)\n}\n\nfunc testResponseHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {\n\tvar h ResponseHeader\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. Response header %q\", err, s)\n\t}\n\tupgrade := h.ConnectionUpgrade()\n\tif upgrade != isUpgrade {\n\t\tt.Fatalf(\"unexpected 'connection: upgrade' when parsing response header: %v. Expecting %v. header %q. v=%q\",\n\t\t\tupgrade, isUpgrade, s, h.Peek(\"Connection\"))\n\t}\n\tkeepAlive := !h.ConnectionClose()\n\tif keepAlive != isKeepAlive {\n\t\tt.Fatalf(\"unexpected 'connection: keep-alive' when parsing response header: %v. Expecting %v. header %q. v=%q\",\n\t\t\tkeepAlive, isKeepAlive, s, &h)\n\t}\n}\n\nfunc TestRequestHeaderConnectionUpgrade(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.1\\r\\nConnection: Upgrade, HTTP2-Settings\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\ttrue, true)\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.1\\r\\nConnection: keep-alive,Upgrade\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\ttrue, true)\n\n\t// non-http/1.1 has 'connection: close' by default, which resets 'connection: upgrade'\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.0\\r\\nConnection: Upgrade, HTTP2-Settings\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\tfalse, false)\n\n\t// explicit 'connection: keep-alive' in non-http/1.1\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.0\\r\\nConnection: foo, Upgrade, keep-alive\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\ttrue, true)\n\n\t// no upgrade\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.1\\r\\nConnection: Upgradess, foobar\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\tfalse, true)\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.1\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\tfalse, true)\n\n\t// explicit connection close\n\ttestRequestHeaderConnectionUpgrade(t, \"GET /foobar HTTP/1.1\\r\\nConnection: close\\r\\nHost: foobar.com\\r\\n\\r\\n\",\n\t\tfalse, false)\n}\n\nfunc testRequestHeaderConnectionUpgrade(t *testing.T, s string, isUpgrade, isKeepAlive bool) {\n\tvar h RequestHeader\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. Request header %q\", err, s)\n\t}\n\tupgrade := h.ConnectionUpgrade()\n\tif upgrade != isUpgrade {\n\t\tt.Fatalf(\"unexpected 'connection: upgrade' when parsing request header: %v. Expecting %v. header %q\",\n\t\t\tupgrade, isUpgrade, s)\n\t}\n\tkeepAlive := !h.ConnectionClose()\n\tif keepAlive != isKeepAlive {\n\t\tt.Fatalf(\"unexpected 'connection: keep-alive' when parsing request header: %v. Expecting %v. header %q\",\n\t\t\tkeepAlive, isKeepAlive, s)\n\t}\n}\n\nfunc TestRequestHeaderProxyWithCookie(t *testing.T) {\n\tt.Parallel()\n\n\t// Proxy request header (read it, then write it without touching any headers).\n\tvar h RequestHeader\n\tr := bytes.NewBufferString(\"GET /foo HTTP/1.1\\r\\nFoo: bar\\r\\nHost: aaa.com\\r\\nCookie: foo=bar; bazzz=aaaaaaa; x=y\\r\\nCookie: aqqqqq=123\\r\\n\\r\\n\")\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr.Reset(w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(h1.RequestURI()) != \"/foo\" {\n\t\tt.Fatalf(\"unexpected requestURI: %q. Expecting %q\", h1.RequestURI(), \"/foo\")\n\t}\n\tif string(h1.Host()) != \"aaa.com\" {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", h1.Host(), \"aaa.com\")\n\t}\n\tif string(h1.Peek(\"Foo\")) != \"bar\" {\n\t\tt.Fatalf(\"unexpected Foo: %q. Expecting %q\", h1.Peek(\"Foo\"), \"bar\")\n\t}\n\tif string(h1.Cookie(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"unexpected cookie foo=%q. Expecting %q\", h1.Cookie(\"foo\"), \"bar\")\n\t}\n\tif string(h1.Cookie(\"bazzz\")) != \"aaaaaaa\" {\n\t\tt.Fatalf(\"unexpected cookie bazzz=%q. Expecting %q\", h1.Cookie(\"bazzz\"), \"aaaaaaa\")\n\t}\n\tif string(h1.Cookie(\"x\")) != \"y\" {\n\t\tt.Fatalf(\"unexpected cookie x=%q. Expecting %q\", h1.Cookie(\"x\"), \"y\")\n\t}\n\tif string(h1.Cookie(\"aqqqqq\")) != \"123\" {\n\t\tt.Fatalf(\"unexpected cookie aqqqqq=%q. Expecting %q\", h1.Cookie(\"aqqqqq\"), \"123\")\n\t}\n}\n\nfunc TestResponseHeaderFirstByteReadEOF(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\tr := &errorReader{err: errors.New(\"non-eof error\")}\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error %v. Expecting %v\", err, io.EOF)\n\t}\n}\n\ntype errorReader struct {\n\terr error\n}\n\nfunc (r *errorReader) Read(p []byte) (int, error) {\n\treturn 0, r.err\n}\n\nfunc TestRequestHeaderEmptyMethod(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\tif !h.IsGet() {\n\t\tt.Fatalf(\"empty method must be equivalent to GET\")\n\t}\n}\n\nfunc TestResponseHeaderHTTPVer(t *testing.T) {\n\tt.Parallel()\n\n\t// non-http/1.1\n\ttestResponseHeaderHTTPVer(t, \"HTTP/1.0 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 123\\r\\n\\r\\n\", true)\n\ttestResponseHeaderHTTPVer(t, \"HTTP/0.9 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 123\\r\\n\\r\\n\", true)\n\ttestResponseHeaderHTTPVer(t, \"foobar 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 123\\r\\n\\r\\n\", true)\n\n\t// http/1.1\n\ttestResponseHeaderHTTPVer(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 123\\r\\n\\r\\n\", false)\n}\n\nfunc TestRequestHeaderHTTPVer(t *testing.T) {\n\tt.Parallel()\n\n\t// non-http/1.1\n\ttestRequestHeaderHTTPVer(t, \"GET / HTTP/1.0\\r\\nHost: aa.com\\r\\n\\r\\n\", true)\n\ttestRequestHeaderHTTPVer(t, \"GET / HTTP/0.9\\r\\nHost: aa.com\\r\\n\\r\\n\", true)\n\n\t// http/1.1\n\ttestRequestHeaderHTTPVer(t, \"GET / HTTP/1.1\\r\\nHost: a.com\\r\\n\\r\\n\", false)\n}\n\nfunc testResponseHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {\n\tvar h ResponseHeader\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. response=%q\", err, s)\n\t}\n\tif h.ConnectionClose() != connectionClose {\n\t\tt.Fatalf(\"unexpected connectionClose %v. Expecting %v. response=%q\", h.ConnectionClose(), connectionClose, s)\n\t}\n}\n\nfunc testRequestHeaderHTTPVer(t *testing.T, s string, connectionClose bool) {\n\tt.Helper()\n\n\tvar h RequestHeader\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. request=%q\", err, s)\n\t}\n\tif h.ConnectionClose() != connectionClose {\n\t\tt.Fatalf(\"unexpected connectionClose %v. Expecting %v. request=%q\", h.ConnectionClose(), connectionClose, s)\n\t}\n}\n\nfunc TestResponseHeaderCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.Set(HeaderSetCookie, \"foo=bar\")\n\th.Set(HeaderContentType, \"foobar\")\n\th.Set(HeaderContentEncoding, \"gzip\")\n\th.Set(\"AAA-BBB\", \"aaaa\")\n\th.Set(HeaderTrailer, \"foo, bar\")\n\n\tvar h1 ResponseHeader\n\th.CopyTo(&h1)\n\tif !bytes.Equal(h1.Peek(\"Set-cookie\"), h.Peek(\"Set-Cookie\")) {\n\t\tt.Fatalf(\"unexpected cookie %q. Expected %q\", h1.Peek(\"set-cookie\"), h.Peek(\"set-cookie\"))\n\t}\n\tif !bytes.Equal(h1.Peek(HeaderContentType), h.Peek(HeaderContentType)) {\n\t\tt.Fatalf(\"unexpected content-type %q. Expected %q\", h1.Peek(\"content-type\"), h.Peek(\"content-type\"))\n\t}\n\tif !bytes.Equal(h1.Peek(HeaderContentEncoding), h.Peek(HeaderContentEncoding)) {\n\t\tt.Fatalf(\"unexpected content-encoding %q. Expected %q\", h1.Peek(\"content-encoding\"), h.Peek(\"content-encoding\"))\n\t}\n\tif !bytes.Equal(h1.Peek(\"aaa-bbb\"), h.Peek(\"AAA-BBB\")) {\n\t\tt.Fatalf(\"unexpected aaa-bbb %q. Expected %q\", h1.Peek(\"aaa-bbb\"), h.Peek(\"aaa-bbb\"))\n\t}\n\tif !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {\n\t\tt.Fatalf(\"unexpected trailer %q. Expected %q\", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))\n\t}\n\n\t// flush buf\n\th.bufK = []byte{}\n\th.bufV = []byte{}\n\th1.bufK = []byte{}\n\th1.bufV = []byte{}\n\n\tif !reflect.DeepEqual(&h, &h1) {\n\t\tt.Fatalf(\"ResponseHeaderCopyTo fail, src: \\n%+v\\ndst: \\n%+v\\n\", &h, &h1)\n\t}\n}\n\nfunc TestRequestHeaderCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\th.Set(HeaderCookie, \"aa=bb; cc=dd\")\n\th.Set(HeaderContentType, \"foobar\")\n\th.Set(HeaderContentEncoding, \"gzip\")\n\th.Set(HeaderHost, \"aaaa\")\n\th.Set(\"aaaxxx\", \"123\")\n\th.Set(HeaderTrailer, \"foo, bar\")\n\th.noDefaultContentType = true\n\n\tvar h1 RequestHeader\n\th.CopyTo(&h1)\n\tif !bytes.Equal(h1.Peek(\"cookie\"), h.Peek(HeaderCookie)) {\n\t\tt.Fatalf(\"unexpected cookie after copying: %q. Expected %q\", h1.Peek(\"cookie\"), h.Peek(\"cookie\"))\n\t}\n\tif !bytes.Equal(h1.Peek(\"content-type\"), h.Peek(HeaderContentType)) {\n\t\tt.Fatalf(\"unexpected content-type %q. Expected %q\", h1.Peek(\"content-type\"), h.Peek(\"content-type\"))\n\t}\n\tif !bytes.Equal(h1.Peek(\"content-encoding\"), h.Peek(HeaderContentEncoding)) {\n\t\tt.Fatalf(\"unexpected content-encoding %q. Expected %q\", h1.Peek(\"content-encoding\"), h.Peek(\"content-encoding\"))\n\t}\n\tif !bytes.Equal(h1.Peek(\"host\"), h.Peek(\"host\")) {\n\t\tt.Fatalf(\"unexpected host %q. Expected %q\", h1.Peek(\"host\"), h.Peek(\"host\"))\n\t}\n\tif !bytes.Equal(h1.Peek(\"aaaxxx\"), h.Peek(\"aaaxxx\")) {\n\t\tt.Fatalf(\"unexpected aaaxxx %q. Expected %q\", h1.Peek(\"aaaxxx\"), h.Peek(\"aaaxxx\"))\n\t}\n\tif !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {\n\t\tt.Fatalf(\"unexpected trailer %q. Expected %q\", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))\n\t}\n\n\t// flush buf\n\th.bufK = []byte{}\n\th.bufV = []byte{}\n\th1.bufK = []byte{}\n\th1.bufV = []byte{}\n\n\tif !reflect.DeepEqual(&h, &h1) {\n\t\tt.Fatalf(\"RequestHeaderCopyTo fail, src: \\n%+v\\ndst: \\n%+v\\n\", &h, &h1)\n\t}\n}\n\nfunc TestResponseContentTypeNoDefaultNotEmpty(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.SetNoDefaultContentType(true)\n\th.SetContentLength(5)\n\n\theaders := h.String()\n\n\tif strings.Contains(headers, \"Content-Type: \\r\\n\") {\n\t\tt.Fatalf(\"ResponseContentTypeNoDefaultNotEmpty fail, response: \\n%+v\\noutcome: \\n%q\\n\", &h, headers)\n\t}\n}\n\nfunc TestRequestContentTypeDefaultNotEmpty(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.SetMethod(MethodPost)\n\th.SetContentLength(5)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif string(h1.contentType) != \"application/octet-stream\" {\n\t\tt.Fatalf(\"unexpected Content-Type %q. Expecting %q\", h1.contentType, \"application/octet-stream\")\n\t}\n}\n\nfunc TestRequestContentTypeNoDefault(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.SetMethod(MethodDelete)\n\th.SetNoDefaultContentType(true)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif len(h1.contentType) != 0 {\n\t\tt.Fatalf(\"unexpected Content-Type %q. Expecting %q\", h1.contentType, \"\")\n\t}\n}\n\nfunc TestResponseDateNoDefaultNotEmpty(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.noDefaultDate = true\n\n\theaders := h.String()\n\n\tif strings.Contains(headers, \"\\r\\nDate: \") {\n\t\tt.Fatalf(\"ResponseDateNoDefaultNotEmpty fail, response: \\n%+v\\noutcome: \\n%q\\n\", &h, headers)\n\t}\n}\n\nfunc TestRequestHeaderConnectionClose(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\th.Set(HeaderConnection, \"close\")\n\th.Set(HeaderHost, \"foobar\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"connection: close not set\")\n\t}\n\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(&w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading request header: %v\", err)\n\t}\n\n\tif !h1.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close value: %v\", h1.ConnectionClose())\n\t}\n\tif string(h1.Peek(HeaderConnection)) != \"close\" {\n\t\tt.Fatalf(\"unexpected connection value: %q. Expecting %q\", h.Peek(\"Connection\"), \"close\")\n\t}\n}\n\nfunc TestRequestHeaderSetCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\th.Set(\"Cookie\", \"foo=bar; baz=aaa\")\n\th.Set(\"cOOkie\", \"xx=yyy\")\n\n\tif string(h.Cookie(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"Unexpected cookie %q. Expecting %q\", h.Cookie(\"foo\"), \"bar\")\n\t}\n\tif string(h.Cookie(\"baz\")) != \"aaa\" {\n\t\tt.Fatalf(\"Unexpected cookie %q. Expecting %q\", h.Cookie(\"baz\"), \"aaa\")\n\t}\n\tif string(h.Cookie(\"xx\")) != \"yyy\" {\n\t\tt.Fatalf(\"unexpected cookie %q. Expecting %q\", h.Cookie(\"xx\"), \"yyy\")\n\t}\n}\n\nfunc TestResponseHeaderSetCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.Set(\"set-cookie\", \"foo=bar; path=/aa/bb; domain=aaa.com\")\n\th.Set(HeaderSetCookie, \"aaaaa=bxx\")\n\n\tvar c Cookie\n\tc.SetKey(\"foo\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"cannot obtain %q cookie\", c.Key())\n\t}\n\tif string(c.Value()) != \"bar\" {\n\t\tt.Fatalf(\"unexpected cookie value %q. Expected %q\", c.Value(), \"bar\")\n\t}\n\tif string(c.Path()) != \"/aa/bb\" {\n\t\tt.Fatalf(\"unexpected cookie path %q. Expected %q\", c.Path(), \"/aa/bb\")\n\t}\n\tif string(c.Domain()) != \"aaa.com\" {\n\t\tt.Fatalf(\"unexpected cookie domain %q. Expected %q\", c.Domain(), \"aaa.com\")\n\t}\n\n\tc.SetKey(\"aaaaa\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"cannot obtain %q cookie\", c.Key())\n\t}\n\tif string(c.Value()) != \"bxx\" {\n\t\tt.Fatalf(\"unexpected cookie value %q. Expecting %q\", c.Value(), \"bxx\")\n\t}\n}\n\nfunc TestResponseHeaderVisitAll(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\tr := bytes.NewBufferString(\"HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nContent-Encoding: gzip\\r\\nContent-Length: 123\\r\\nSet-Cookie: aa=bb; path=/foo/bar\\r\\nSet-Cookie: ccc\\r\\nTrailer: Foo, Bar\\r\\n\\r\\n\")\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif h.Len() != 6 {\n\t\tt.Fatalf(\"Unexpected number of headers: %d. Expected 6\", h.Len())\n\t}\n\tcontentLengthCount := 0\n\tcontentTypeCount := 0\n\tcontentEncodingCount := 0\n\tcookieCount := 0\n\th.VisitAll(func(key, value []byte) {\n\t\tk := string(key)\n\t\tv := string(value)\n\t\tswitch k {\n\t\tcase HeaderContentLength:\n\t\t\tif v != string(h.Peek(k)) {\n\t\t\t\tt.Fatalf(\"unexpected content-length: %q. Expecting %q\", v, h.Peek(k))\n\t\t\t}\n\t\t\tcontentLengthCount++\n\t\tcase HeaderContentType:\n\t\t\tif v != string(h.Peek(k)) {\n\t\t\t\tt.Fatalf(\"Unexpected content-type: %q. Expected %q\", v, h.Peek(k))\n\t\t\t}\n\t\t\tcontentTypeCount++\n\t\tcase HeaderContentEncoding:\n\t\t\tif v != string(h.Peek(k)) {\n\t\t\t\tt.Fatalf(\"Unexpected content-encoding: %q. Expected %q\", v, h.Peek(k))\n\t\t\t}\n\t\t\tcontentEncodingCount++\n\t\tcase HeaderSetCookie:\n\t\t\tif cookieCount == 0 && v != \"aa=bb; path=/foo/bar\" {\n\t\t\t\tt.Fatalf(\"unexpected cookie header: %q. Expected %q\", v, \"aa=bb; path=/foo/bar\")\n\t\t\t}\n\t\t\tif cookieCount == 1 && v != \"ccc\" {\n\t\t\t\tt.Fatalf(\"unexpected cookie header: %q. Expected %q\", v, \"ccc\")\n\t\t\t}\n\t\t\tcookieCount++\n\t\tcase HeaderTrailer:\n\t\t\tif v != \"Foo, Bar\" {\n\t\t\t\tt.Fatalf(\"Unexpected trailer header %q. Expected %q\", v, \"Foo, Bar\")\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected header %q=%q\", k, v)\n\t\t}\n\t})\n\tif contentLengthCount != 1 {\n\t\tt.Fatalf(\"unexpected number of content-length headers: %d. Expected 1\", contentLengthCount)\n\t}\n\tif contentTypeCount != 1 {\n\t\tt.Fatalf(\"unexpected number of content-type headers: %d. Expected 1\", contentTypeCount)\n\t}\n\tif contentEncodingCount != 1 {\n\t\tt.Fatalf(\"unexpected number of content-encoding headers: %d. Expected 1\", contentEncodingCount)\n\t}\n\tif cookieCount != 2 {\n\t\tt.Fatalf(\"unexpected number of cookie header: %d. Expected 2\", cookieCount)\n\t}\n}\n\nfunc TestRequestHeaderVisitAll(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\tr := bytes.NewBufferString(\"GET / HTTP/1.1\\r\\nHost: aa.com\\r\\nXX: YYY\\r\\nXX: ZZ\\r\\nCookie: a=b; c=d\\r\\nTrailer: Foo, Bar\\r\\n\\r\\n\")\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif h.Len() != 5 {\n\t\tt.Fatalf(\"Unexpected number of header: %d. Expected 5\", h.Len())\n\t}\n\thostCount := 0\n\txxCount := 0\n\tcookieCount := 0\n\th.VisitAll(func(key, value []byte) {\n\t\tk := string(key)\n\t\tv := string(value)\n\t\tswitch k {\n\t\tcase HeaderHost:\n\t\t\tif v != string(h.Peek(k)) {\n\t\t\t\tt.Fatalf(\"Unexpected host value %q. Expected %q\", v, h.Peek(k))\n\t\t\t}\n\t\t\thostCount++\n\t\tcase \"Xx\":\n\t\t\tif xxCount == 0 && v != \"YYY\" {\n\t\t\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", v, \"YYY\")\n\t\t\t}\n\t\t\tif xxCount == 1 && v != \"ZZ\" {\n\t\t\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", v, \"ZZ\")\n\t\t\t}\n\t\t\txxCount++\n\t\tcase HeaderCookie:\n\t\t\tif v != \"a=b; c=d\" {\n\t\t\t\tt.Fatalf(\"Unexpected cookie %q. Expected %q\", v, \"a=b; c=d\")\n\t\t\t}\n\t\t\tcookieCount++\n\t\tcase HeaderTrailer:\n\t\t\tif v != \"Foo, Bar\" {\n\t\t\t\tt.Fatalf(\"Unexpected trailer header %q. Expected %q\", v, \"Foo, Bar\")\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Fatalf(\"Unexpected header %q=%q\", k, v)\n\t\t}\n\t})\n\tif hostCount != 1 {\n\t\tt.Fatalf(\"Unexpected number of host headers detected %d. Expected 1\", hostCount)\n\t}\n\tif xxCount != 2 {\n\t\tt.Fatalf(\"Unexpected number of xx headers detected %d. Expected 2\", xxCount)\n\t}\n\tif cookieCount != 1 {\n\t\tt.Fatalf(\"Unexpected number of cookie headers %d. Expected 1\", cookieCount)\n\t}\n}\n\nfunc TestRequestHeaderVisitAllInOrder(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\n\tr := bytes.NewBufferString(\"GET / HTTP/1.1\\r\\nContent-Type: aa\\r\\nCookie: a=b\\r\\nHost: example.com\\r\\nUser-Agent: xxx\\r\\n\\r\\n\")\n\tbr := bufio.NewReader(r)\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif h.Len() != 4 {\n\t\tt.Fatalf(\"Unexpected number of headers: %d. Expected 4\", h.Len())\n\t}\n\n\torder := []string{\n\t\tHeaderContentType,\n\t\tHeaderCookie,\n\t\tHeaderHost,\n\t\tHeaderUserAgent,\n\t}\n\tvalues := []string{\n\t\t\"aa\",\n\t\t\"a=b\",\n\t\t\"example.com\",\n\t\t\"xxx\",\n\t}\n\n\th.VisitAllInOrder(func(key, value []byte) {\n\t\tif len(order) == 0 {\n\t\t\tt.Fatalf(\"no more headers expected, got %q\", key)\n\t\t}\n\t\tif order[0] != string(key) {\n\t\t\tt.Fatalf(\"expected header %q got %q\", order[0], key)\n\t\t}\n\t\tif values[0] != string(value) {\n\t\t\tt.Fatalf(\"expected header value %q got %q\", values[0], value)\n\t\t}\n\t\torder = order[1:]\n\t\tvalues = values[1:]\n\t})\n}\n\nfunc TestResponseHeaderAddTrailerError(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\terr := h.AddTrailer(\"Foo,   Content-Length , bAr,Transfer-Encoding, uSer aGent\")\n\texpectedTrailer := \"Foo, Bar, uSer aGent\"\n\n\tif !errors.Is(err, ErrBadTrailer) {\n\t\tt.Fatalf(\"unexpected err %q. Expected %q\", err, ErrBadTrailer)\n\t}\n\tif trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {\n\t\tt.Fatalf(\"unexpected trailer %q. Expected %q\", trailer, expectedTrailer)\n\t}\n}\n\nfunc TestRequestHeaderAddTrailerError(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\terr := h.AddTrailer(\"Foo,   Content-Length , Bar,Transfer-Encoding,\")\n\texpectedTrailer := \"Foo, Bar\"\n\n\tif !errors.Is(err, ErrBadTrailer) {\n\t\tt.Fatalf(\"unexpected err %q. Expected %q\", err, ErrBadTrailer)\n\t}\n\tif trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {\n\t\tt.Fatalf(\"unexpected trailer %q. Expected %q\", trailer, expectedTrailer)\n\t}\n}\n\n// Security tests for trailer handling vulnerability fix.\nfunc TestTrailerSecurityVulnerabilityFix(t *testing.T) {\n\tt.Parallel()\n\n\t// Test cases for headers that should be blocked in trailers\n\tdangerousHeaders := []struct {\n\t\tname        string\n\t\theader      string\n\t\tdescription string\n\t}{\n\t\t{\"Content-Type\", \"Content-Type\", \"off-by-one fix: exactly 'Content-Type' should be blocked\"},\n\t\t{\"Cookie\", \"Cookie\", \"session hijacking prevention\"},\n\t\t{\"Set-Cookie\", \"Set-Cookie\", \"session hijacking prevention\"},\n\t\t{\"Location\", \"Location\", \"redirect attack prevention\"},\n\t\t{\"X-Forwarded-For\", \"X-Forwarded-For\", \"IP spoofing prevention\"},\n\t\t{\"X-Forwarded-Host\", \"X-Forwarded-Host\", \"IP spoofing prevention\"},\n\t\t{\"X-Forwarded-Proto\", \"X-Forwarded-Proto\", \"IP spoofing prevention\"},\n\t\t{\"X-Real-IP\", \"X-Real-IP\", \"IP spoofing prevention\"},\n\t\t{\"X-Real-Ip\", \"X-Real-Ip\", \"IP spoofing prevention (case insensitive)\"},\n\t\t{\"Authorization\", \"Authorization\", \"auth bypass prevention\"},\n\t\t{\"Host\", \"Host\", \"host header attack prevention\"},\n\t\t{\"Connection\", \"Connection\", \"connection control prevention\"},\n\t}\n\n\t// Test RequestHeader AddTrailer blocking dangerous headers.\n\tfor _, tc := range dangerousHeaders {\n\t\tt.Run(\"RequestHeader_\"+tc.name, func(t *testing.T) {\n\t\t\tvar h RequestHeader\n\t\t\terr := h.AddTrailer(tc.header)\n\t\t\tif !errors.Is(err, ErrBadTrailer) {\n\t\t\t\tt.Fatalf(\"Expected ErrBadTrailer for %s (%s), got: %v\", tc.header, tc.description, err)\n\t\t\t}\n\n\t\t\t// Verify trailer header is empty since the dangerous header was rejected\n\t\t\tif trailer := string(h.Peek(HeaderTrailer)); trailer != \"\" {\n\t\t\t\tt.Fatalf(\"Expected empty trailer after rejecting %s, got: %q\", tc.header, trailer)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Test ResponseHeader AddTrailer blocking dangerous headers\n\tfor _, tc := range dangerousHeaders {\n\t\tt.Run(\"ResponseHeader_\"+tc.name, func(t *testing.T) {\n\t\t\tvar h ResponseHeader\n\t\t\terr := h.AddTrailer(tc.header)\n\n\t\t\tif !errors.Is(err, ErrBadTrailer) {\n\t\t\t\tt.Fatalf(\"Expected ErrBadTrailer for %s (%s), got: %v\", tc.header, tc.description, err)\n\t\t\t}\n\n\t\t\t// Verify trailer header is empty since the dangerous header was rejected\n\t\t\tif trailer := string(h.Peek(HeaderTrailer)); trailer != \"\" {\n\t\t\t\tt.Fatalf(\"Expected empty trailer after rejecting %s, got: %q\", tc.header, trailer)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Test that safe headers are still allowed\n\tsafeHeaders := []string{\"Foo\", \"X-Custom-Safe\", \"My-App-Trailer\", \"Debug-Info\"}\n\n\tfor _, header := range safeHeaders {\n\t\tt.Run(\"Safe_RequestHeader_\"+header, func(t *testing.T) {\n\t\t\tvar h RequestHeader\n\t\t\terr := h.AddTrailer(header)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error for safe header %s, got: %v\", header, err)\n\t\t\t}\n\n\t\t\t// Verify the safe header was added to trailer\n\t\t\tif trailer := string(h.Peek(HeaderTrailer)); trailer != header {\n\t\t\t\tt.Fatalf(\"Expected trailer %q for safe header, got: %q\", header, trailer)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Safe_ResponseHeader_\"+header, func(t *testing.T) {\n\t\t\tvar h ResponseHeader\n\t\t\terr := h.AddTrailer(header)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error for safe header %s, got: %v\", header, err)\n\t\t\t}\n\n\t\t\t// Verify the safe header was added to trailer\n\t\t\tif trailer := string(h.Peek(HeaderTrailer)); trailer != header {\n\t\t\t\tt.Fatalf(\"Expected trailer %q for safe header, got: %q\", header, trailer)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTrailerParsingSecurityFix(t *testing.T) {\n\tt.Parallel()\n\n\t// Test the specific vulnerability scenario: malicious trailers should be rejected\n\t// Test that dangerous trailers in chunked body are properly blocked\n\n\tdangerousTrailers := []string{\n\t\t\"Content-Type: text/malicious\\r\\n\\r\\n\",\n\t\t\"X-Forwarded-For: attacker.com\\r\\n\\r\\n\",\n\t\t\"X-Real-IP: 1.1.1.1\\r\\n\\r\\n\",\n\t\t\"Cookie: evil\\r\\n\\r\\n\",\n\t\t\"Location: http://evil.com\\r\\n\\r\\n\",\n\t}\n\n\tfor i, trailer := range dangerousTrailers {\n\t\tt.Run(\"Request_\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\tvar h RequestHeader\n\t\t\tr := bytes.NewBufferString(trailer)\n\t\t\tbr := bufio.NewReader(r)\n\n\t\t\terr := h.ReadTrailer(br)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Expected error when reading dangerous trailer, but got none: %s\", trailer)\n\t\t\t}\n\n\t\t\t// The error should mention forbidden trailer\n\t\t\tif !strings.Contains(err.Error(), \"forbidden trailer\") {\n\t\t\t\tt.Fatalf(\"Expected 'forbidden trailer' error for %s, got: %v\", trailer, err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Response_\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\tvar h ResponseHeader\n\t\t\tr := bytes.NewBufferString(trailer)\n\t\t\tbr := bufio.NewReader(r)\n\n\t\t\terr := h.ReadTrailer(br)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Expected error when reading dangerous trailer, but got none: %s\", trailer)\n\t\t\t}\n\n\t\t\t// The error should mention forbidden trailer\n\t\t\tif !strings.Contains(err.Error(), \"forbidden trailer\") {\n\t\t\t\tt.Fatalf(\"Expected 'forbidden trailer' error for %s, got: %v\", trailer, err)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Test that safe trailers still work\n\tsafeTrailers := []string{\n\t\t\"Foo: bar\\r\\n\\r\\n\",\n\t\t\"X-Custom-Header: value\\r\\n\\r\\n\",\n\t\t\"Debug-Info: test\\r\\n\\r\\n\",\n\t}\n\n\tfor i, trailer := range safeTrailers {\n\t\tt.Run(\"Safe_Request_\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\tvar h RequestHeader\n\t\t\tr := bytes.NewBufferString(trailer)\n\t\t\tbr := bufio.NewReader(r)\n\n\t\t\terr := h.ReadTrailer(br)\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\tt.Fatalf(\"Expected no error for safe trailer %s, got: %v\", trailer, err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Safe_Response_\"+strconv.Itoa(i), func(t *testing.T) {\n\t\t\tvar h ResponseHeader\n\t\t\tr := bytes.NewBufferString(trailer)\n\t\t\tbr := bufio.NewReader(r)\n\n\t\t\terr := h.ReadTrailer(br)\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\tt.Fatalf(\"Expected no error for safe trailer %s, got: %v\", trailer, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResponseHeaderCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\tvar c Cookie\n\n\tc.SetKey(\"foobar\")\n\tc.SetValue(\"aaa\")\n\th.SetCookie(&c)\n\n\tc.SetKey(\"йцук\")\n\tc.SetDomain(\"foobar.com\")\n\th.SetCookie(&c)\n\n\tc.Reset()\n\tc.SetKey(\"foobar\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"Cannot find cookie %q\", c.Key())\n\t}\n\n\tvar expectedC1 Cookie\n\texpectedC1.SetKey(\"foobar\")\n\texpectedC1.SetValue(\"aaa\")\n\tif !equalCookie(&expectedC1, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%#v\\nExpected\\n%#v\\n\", &c, &expectedC1)\n\t}\n\n\tc.SetKey(\"йцук\")\n\tif !h.Cookie(&c) {\n\t\tt.Fatalf(\"cannot find cookie %q\", c.Key())\n\t}\n\n\tvar expectedC2 Cookie\n\texpectedC2.SetKey(\"йцук\")\n\texpectedC2.SetValue(\"aaa\")\n\texpectedC2.SetDomain(\"foobar.com\")\n\tif !equalCookie(&expectedC2, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC2)\n\t}\n\n\tfor key, value := range h.Cookies() {\n\t\tvar cc Cookie\n\t\tif err := cc.ParseBytes(value); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif !bytes.Equal(key, cc.Key()) {\n\t\t\tt.Fatalf(\"Unexpected cookie key %q. Expected %q\", key, cc.Key())\n\t\t}\n\t\tswitch {\n\t\tcase bytes.Equal(key, []byte(\"foobar\")):\n\t\t\tif !equalCookie(&expectedC1, &cc) {\n\t\t\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &cc, &expectedC1)\n\t\t\t}\n\t\tcase bytes.Equal(key, []byte(\"йцук\")):\n\t\t\tif !equalCookie(&expectedC2, &cc) {\n\t\t\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &cc, &expectedC2)\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected cookie key %q\", key)\n\t\t}\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\th.DelAllCookies()\n\n\tvar h1 ResponseHeader\n\tbr := bufio.NewReader(w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tc.SetKey(\"foobar\")\n\tif !h1.Cookie(&c) {\n\t\tt.Fatalf(\"Cannot find cookie %q\", c.Key())\n\t}\n\tif !equalCookie(&expectedC1, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC1)\n\t}\n\n\th1.DelCookie(\"foobar\")\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\tif h1.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\n\tc.SetKey(\"йцук\")\n\tif !h1.Cookie(&c) {\n\t\tt.Fatalf(\"cannot find cookie %q\", c.Key())\n\t}\n\tif !equalCookie(&expectedC2, &c) {\n\t\tt.Fatalf(\"unexpected cookie\\n%v\\nExpected\\n%v\\n\", &c, &expectedC2)\n\t}\n\n\th1.DelCookie(\"йцук\")\n\tif h.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n\tif h1.Cookie(&c) {\n\t\tt.Fatalf(\"Unexpected cookie found: %v\", &c)\n\t}\n}\n\nfunc equalCookie(c1, c2 *Cookie) bool {\n\tif !bytes.Equal(c1.Key(), c2.Key()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Value(), c2.Value()) {\n\t\treturn false\n\t}\n\tif !c1.Expire().Equal(c2.Expire()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Domain(), c2.Domain()) {\n\t\treturn false\n\t}\n\tif !bytes.Equal(c1.Path(), c2.Path()) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc TestRequestHeaderCookie(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.SetRequestURI(\"/foobar\")\n\th.Set(HeaderHost, \"foobar.com\")\n\n\th.SetCookie(\"foo\", \"bar\")\n\th.SetCookie(\"привет\", \"мир\")\n\n\tif string(h.Cookie(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h.Cookie(\"foo\"), \"bar\")\n\t}\n\tif string(h.Cookie(\"привет\")) != \"мир\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h.Cookie(\"привет\"), \"мир\")\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := h.Write(bw); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(w)\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif !bytes.Equal(h1.Cookie(\"foo\"), h.Cookie(\"foo\")) {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h1.Cookie(\"foo\"), h.Cookie(\"foo\"))\n\t}\n\th1.DelCookie(\"foo\")\n\tif len(h1.Cookie(\"foo\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h1.Cookie(\"foo\"))\n\t}\n\tif !bytes.Equal(h1.Cookie(\"привет\"), h.Cookie(\"привет\")) {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h1.Cookie(\"привет\"), h.Cookie(\"привет\"))\n\t}\n\th1.DelCookie(\"привет\")\n\tif len(h1.Cookie(\"привет\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h1.Cookie(\"привет\"))\n\t}\n\n\th.DelAllCookies()\n\tif len(h.Cookie(\"foo\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h.Cookie(\"foo\"))\n\t}\n\tif len(h.Cookie(\"привет\")) > 0 {\n\t\tt.Fatalf(\"Unexpected cookie found: %q\", h.Cookie(\"привет\"))\n\t}\n}\n\nfunc TestResponseHeaderCookieIssue4(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\tc := AcquireCookie()\n\tc.SetKey(\"foo\")\n\tc.SetValue(\"bar\")\n\th.SetCookie(c)\n\n\tif string(h.Peek(HeaderSetCookie)) != \"foo=bar\" {\n\t\tt.Fatalf(\"Unexpected Set-Cookie header %q. Expected %q\", h.Peek(HeaderSetCookie), \"foo=bar\")\n\t}\n\tcookieSeen := false\n\tfor key := range h.All() {\n\t\tif string(key) == HeaderSetCookie {\n\t\t\tcookieSeen = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !cookieSeen {\n\t\tt.Fatalf(\"Set-Cookie not present in VisitAll\")\n\t}\n\n\tc = AcquireCookie()\n\tc.SetKey(\"foo\")\n\th.Cookie(c)\n\tif string(c.Value()) != \"bar\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", c.Value(), \"bar\")\n\t}\n\n\tif string(h.Peek(HeaderSetCookie)) != \"foo=bar\" {\n\t\tt.Fatalf(\"Unexpected Set-Cookie header %q. Expected %q\", h.Peek(HeaderSetCookie), \"foo=bar\")\n\t}\n\tcookieSeen = false\n\tfor key := range h.All() {\n\t\tif string(key) == HeaderSetCookie {\n\t\t\tcookieSeen = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !cookieSeen {\n\t\tt.Fatalf(\"Set-Cookie not present in VisitAll\")\n\t}\n}\n\nfunc TestRequestHeaderCookieIssue313(t *testing.T) {\n\tt.Parallel()\n\n\tvar h RequestHeader\n\th.SetRequestURI(\"/\")\n\th.Set(HeaderHost, \"foobar.com\")\n\n\th.SetCookie(\"foo\", \"bar\")\n\n\tif string(h.Peek(HeaderCookie)) != \"foo=bar\" {\n\t\tt.Fatalf(\"Unexpected Cookie header %q. Expected %q\", h.Peek(HeaderCookie), \"foo=bar\")\n\t}\n\tcookieSeen := false\n\tfor key := range h.All() {\n\t\tif string(key) == HeaderCookie {\n\t\t\tcookieSeen = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !cookieSeen {\n\t\tt.Fatalf(\"Cookie not present in VisitAll\")\n\t}\n\n\tif string(h.Cookie(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"Unexpected cookie value %q. Expected %q\", h.Cookie(\"foo\"), \"bar\")\n\t}\n\n\tif string(h.Peek(HeaderCookie)) != \"foo=bar\" {\n\t\tt.Fatalf(\"Unexpected Cookie header %q. Expected %q\", h.Peek(HeaderCookie), \"foo=bar\")\n\t}\n\tcookieSeen = false\n\tfor key := range h.All() {\n\t\tif string(key) == HeaderCookie {\n\t\t\tcookieSeen = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !cookieSeen {\n\t\tt.Fatalf(\"Cookie not present in VisitAll\")\n\t}\n}\n\nfunc TestRequestHeaderMethod(t *testing.T) {\n\tt.Parallel()\n\n\t// common http methods\n\ttestRequestHeaderMethod(t, MethodGet)\n\ttestRequestHeaderMethod(t, MethodPost)\n\ttestRequestHeaderMethod(t, MethodHead)\n\ttestRequestHeaderMethod(t, MethodDelete)\n\n\t// non-http methods\n\ttestRequestHeaderMethod(t, \"foobar\")\n\ttestRequestHeaderMethod(t, \"ABC\")\n}\n\nfunc testRequestHeaderMethod(t *testing.T, expectedMethod string) {\n\tvar h RequestHeader\n\th.SetMethod(expectedMethod)\n\tm := h.Method()\n\tif string(m) != expectedMethod {\n\t\tt.Fatalf(\"unexpected method: %q. Expecting %q\", m, expectedMethod)\n\t}\n\n\ts := h.String()\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := h1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tm1 := h1.Method()\n\tif !bytes.Equal(m, m1) {\n\t\tt.Fatalf(\"unexpected method: %q. Expecting %q\", m, m1)\n\t}\n}\n\nfunc TestRequestHeaderSetGet(t *testing.T) {\n\tt.Parallel()\n\n\th := &RequestHeader{}\n\th.SetRequestURI(\"/aa/bbb\")\n\th.SetMethod(MethodPost)\n\th.Set(\"foo\", \"bar\")\n\th.Set(\"host\", \"12345\")\n\th.Set(\"content-type\", \"aaa/bbb\")\n\th.Set(\"content-length\", \"1234\")\n\th.Set(\"user-agent\", \"aaabbb\")\n\th.Set(\"referer\", \"axcv\")\n\th.Set(\"baz\", \"xxxxx\")\n\th.Set(\"transfer-encoding\", \"chunked\")\n\th.Set(\"connection\", \"close\")\n\n\texpectRequestHeaderGet(t, h, \"Foo\", \"bar\")\n\texpectRequestHeaderGet(t, h, HeaderHost, \"12345\")\n\texpectRequestHeaderGet(t, h, HeaderContentType, \"aaa/bbb\")\n\texpectRequestHeaderGet(t, h, HeaderContentLength, \"1234\")\n\texpectRequestHeaderGet(t, h, \"USER-AGent\", \"aaabbb\")\n\texpectRequestHeaderGet(t, h, HeaderReferer, \"axcv\")\n\texpectRequestHeaderGet(t, h, \"baz\", \"xxxxx\")\n\texpectRequestHeaderGet(t, h, HeaderTransferEncoding, \"\")\n\texpectRequestHeaderGet(t, h, \"connecTION\", \"close\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"unset connection: close\")\n\t}\n\n\tif h.ContentLength() != 1234 {\n\t\tt.Fatalf(\"Unexpected content-length %d. Expected %d\", h.ContentLength(), 1234)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\terr := h.Write(bw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when writing request header: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing request header: %v\", err)\n\t}\n\n\tvar h1 RequestHeader\n\tbr := bufio.NewReader(w)\n\tif err = h1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading request header: %v\", err)\n\t}\n\n\tif h1.ContentLength() != h.ContentLength() {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", h1.ContentLength(), h.ContentLength())\n\t}\n\n\texpectRequestHeaderGet(t, &h1, \"Foo\", \"bar\")\n\texpectRequestHeaderGet(t, &h1, \"HOST\", \"12345\")\n\texpectRequestHeaderGet(t, &h1, HeaderContentType, \"aaa/bbb\")\n\texpectRequestHeaderGet(t, &h1, HeaderContentLength, \"1234\")\n\texpectRequestHeaderGet(t, &h1, \"USER-AGent\", \"aaabbb\")\n\texpectRequestHeaderGet(t, &h1, HeaderReferer, \"axcv\")\n\texpectRequestHeaderGet(t, &h1, \"baz\", \"xxxxx\")\n\texpectRequestHeaderGet(t, &h1, HeaderTransferEncoding, \"\")\n\texpectRequestHeaderGet(t, &h1, HeaderConnection, \"close\")\n\tif !h1.ConnectionClose() {\n\t\tt.Fatalf(\"unset connection: close\")\n\t}\n}\n\nfunc TestResponseHeaderSetGet(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\th.Set(\"foo\", \"bar\")\n\th.Set(\"content-type\", \"aaa/bbb\")\n\th.Set(\"content-encoding\", \"gzip\")\n\th.Set(\"connection\", \"close\")\n\th.Set(\"content-length\", \"1234\")\n\th.Set(HeaderServer, \"aaaa\")\n\th.Set(\"baz\", \"xxxxx\")\n\th.Set(HeaderTransferEncoding, \"chunked\")\n\n\texpectResponseHeaderGet(t, h, \"Foo\", \"bar\")\n\texpectResponseHeaderGet(t, h, HeaderContentType, \"aaa/bbb\")\n\texpectResponseHeaderGet(t, h, HeaderContentEncoding, \"gzip\")\n\texpectResponseHeaderGet(t, h, HeaderConnection, \"close\")\n\texpectResponseHeaderGet(t, h, HeaderContentLength, \"1234\")\n\texpectResponseHeaderGet(t, h, \"seRVer\", \"aaaa\")\n\texpectResponseHeaderGet(t, h, \"baz\", \"xxxxx\")\n\texpectResponseHeaderGet(t, h, HeaderTransferEncoding, \"\")\n\n\tif h.ContentLength() != 1234 {\n\t\tt.Fatalf(\"Unexpected content-length %d. Expected %d\", h.ContentLength(), 1234)\n\t}\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"Unexpected Connection: close value %v. Expected %v\", h.ConnectionClose(), true)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\terr := h.Write(bw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when writing response header: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing response header: %v\", err)\n\t}\n\n\tvar h1 ResponseHeader\n\tbr := bufio.NewReader(w)\n\tif err = h1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response header: %v\", err)\n\t}\n\n\tif h1.ContentLength() != h.ContentLength() {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", h1.ContentLength(), h.ContentLength())\n\t}\n\tif h1.ConnectionClose() != h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close %v. Expected %v\", h1.ConnectionClose(), h.ConnectionClose())\n\t}\n\n\texpectResponseHeaderGet(t, &h1, \"Foo\", \"bar\")\n\texpectResponseHeaderGet(t, &h1, HeaderContentType, \"aaa/bbb\")\n\texpectResponseHeaderGet(t, &h1, HeaderContentEncoding, \"gzip\")\n\texpectResponseHeaderGet(t, &h1, HeaderConnection, \"close\")\n\texpectResponseHeaderGet(t, &h1, \"seRVer\", \"aaaa\")\n\texpectResponseHeaderGet(t, &h1, \"baz\", \"xxxxx\")\n}\n\nfunc expectRequestHeaderGet(t *testing.T, h *RequestHeader, key, expectedValue string) {\n\tif string(h.Peek(key)) != expectedValue {\n\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q\", key, h.Peek(key), expectedValue)\n\t}\n}\n\nfunc expectResponseHeaderGet(t *testing.T, h *ResponseHeader, key, expectedValue string) {\n\tif string(h.Peek(key)) != expectedValue {\n\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q\", key, h.Peek(key), expectedValue)\n\t}\n}\n\nfunc TestResponseHeaderConnectionClose(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseHeaderConnectionClose(t, true)\n\ttestResponseHeaderConnectionClose(t, false)\n}\n\nfunc testResponseHeaderConnectionClose(t *testing.T, connectionClose bool) {\n\th := &ResponseHeader{}\n\tif connectionClose {\n\t\th.SetConnectionClose()\n\t}\n\th.SetContentLength(123)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\terr := h.Write(bw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when writing response header: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing response header: %v\", err)\n\t}\n\n\tvar h1 ResponseHeader\n\tbr := bufio.NewReader(w)\n\terr = h1.Read(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response header: %v\", err)\n\t}\n\tif h1.ConnectionClose() != h.ConnectionClose() {\n\t\tt.Fatalf(\"Unexpected value for ConnectionClose: %v. Expected %v\", h1.ConnectionClose(), h.ConnectionClose())\n\t}\n}\n\nfunc TestRequestHeaderTooBig(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"GET / HTTP/1.1\\r\\nHost: aaa.com\\r\\n\" + getHeaders(10500) + \"\\r\\n\"\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReaderSize(r, 4096)\n\th := &RequestHeader{}\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading too big header\")\n\t}\n}\n\nfunc TestResponseHeaderTooBig(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"HTTP/1.1 200 OK\\r\\nContent-Type: sss\\r\\nContent-Length: 0\\r\\n\" + getHeaders(100500) + \"\\r\\n\"\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReaderSize(r, 4096)\n\th := &ResponseHeader{}\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading too big header\")\n\t}\n}\n\ntype bufioPeekReader struct {\n\ts string\n\tn int\n}\n\nfunc (r *bufioPeekReader) Read(b []byte) (int, error) {\n\tif r.s == \"\" {\n\t\treturn 0, io.EOF\n\t}\n\n\tr.n++\n\tn := min(len(r.s), r.n)\n\tsrc := []byte(r.s[:n])\n\tr.s = r.s[n:]\n\tn = copy(b, src)\n\treturn n, nil\n}\n\nfunc TestRequestHeaderBufioPeek(t *testing.T) {\n\tt.Parallel()\n\n\tr := &bufioPeekReader{\n\t\ts: \"GET / HTTP/1.1\\r\\nHost: foobar.com\\r\\n\" + getHeaders(10) + \"\\r\\naaaa\",\n\t}\n\tbr := bufio.NewReaderSize(r, 4096)\n\th := &RequestHeader{}\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading request: %v\", err)\n\t}\n\tverifyRequestHeader(t, h, -2, \"/\", \"foobar.com\", \"\", \"\")\n}\n\nfunc TestResponseHeaderBufioPeek(t *testing.T) {\n\tt.Parallel()\n\n\tr := &bufioPeekReader{\n\t\ts: \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: text/plain\\r\\nContent-Encoding: gzip\\r\\n\" + getHeaders(10) + \"\\r\\n0123456789\",\n\t}\n\tbr := bufio.NewReaderSize(r, 4096)\n\th := &ResponseHeader{}\n\tif err := h.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response: %v\", err)\n\t}\n\tverifyResponseHeader(t, h, 200, 10, \"text/plain\", \"gzip\")\n}\n\nfunc getHeaders(n int) string {\n\th := make([]string, 0, n)\n\tfor i := range n {\n\t\th = append(h, fmt.Sprintf(\"Header_%d: Value_%d\\r\\n\", i, i))\n\t}\n\treturn strings.Join(h, \"\")\n}\n\nfunc TestResponseHeaderReadSuccess(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\n\t// straight order of content-length and content-type\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\",\n\t\t200, 123, \"text/html\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close\")\n\t}\n\n\t// reverse order of content-length and content-type\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 202 OK\\r\\nContent-Type: text/plain; encoding=utf-8\\r\\nContent-Length: 543\\r\\nConnection: close\\r\\n\\r\\n\",\n\t\t202, 543, \"text/plain; encoding=utf-8\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: close\")\n\t}\n\n\t// transfer-encoding: chunked\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 505 Internal error\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\",\n\t\t505, -1, \"text/html\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close\")\n\t}\n\n\t// reverse order of content-type and transfer-encoding\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 343 foobar\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: text/json\\r\\n\\r\\n\",\n\t\t343, -1, \"text/json\")\n\n\t// additional headers\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 100 Continue\\r\\nFoobar: baz\\r\\nContent-Type: aaa/bbb\\r\\nUser-Agent: x\\r\\nContent-Length: 123\\r\\nZZZ: werer\\r\\n\\r\\n\",\n\t\t100, 123, \"aaa/bbb\")\n\n\t// ancient http protocol\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/0.9 300 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\nqqqq\",\n\t\t300, 123, \"text/html\")\n\n\t// lf instead of crlf\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\",\n\t\t200, 123, \"text/html\")\n\n\t// No space after colon\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\nContent-Length:34\\nContent-Type: sss\\r\\n\\r\\naaaa\",\n\t\t200, 34, \"sss\")\n\n\t// space in multiline value with \\r\\n\\r\\n as body separator\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 34\\r\\nContent-Type: sss\\r\\n vvv\\r\\n\\r\\naaaa\",\n\t\t200, 34, \"sss vvv\")\n\n\t// invalid case\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 400 OK\\nconTEnt-leNGTH: 123\\nConTENT-TYPE: ass\\r\\n\\r\\n\",\n\t\t400, 123, \"ass\")\n\n\t// duplicate content-length\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 456\\r\\nContent-Type: foo/bar\\r\\nContent-Length: 321\\r\\n\\r\\n\",\n\t\t200, 321, \"foo/bar\")\n\n\t// duplicate content-type\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 234\\r\\nContent-Type: foo/bar\\r\\nContent-Type: baz/bar\\r\\n\\r\\n\",\n\t\t200, 234, \"baz/bar\")\n\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 300 OK\\r\\nContent-Type: foo/barr\\r\\nTransfer-Encoding: chunked\\r\\nContent-Length: 354\\r\\n\\r\\n\",\n\t\t300, -1, \"foo/barr\")\n\n\t// duplicate transfer-encoding: chunked\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\",\n\t\t200, -1, \"text/html\")\n\n\t// no reason string in the first line\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 456\\r\\nContent-Type: xxx/yyy\\r\\nContent-Length: 134\\r\\n\\r\\naaaxxx\",\n\t\t456, 134, \"xxx/yyy\")\n\n\t// blank lines before the first line\n\ttestResponseHeaderReadSuccess(t, h, \"\\r\\nHTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 0\\r\\n\\r\\nsss\",\n\t\t200, 0, \"aa\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close\")\n\t}\n\n\t// no content-length (informational responses)\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 101 OK\\r\\n\\r\\n\",\n\t\t101, -2, \"text/plain; charset=utf-8\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: keep-alive for informational response\")\n\t}\n\n\t// no content-length (no-content responses)\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 204 OK\\r\\n\\r\\n\",\n\t\t204, -2, \"text/plain; charset=utf-8\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: keep-alive for no-content response\")\n\t}\n\n\t// no content-length (not-modified responses)\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 304 OK\\r\\n\\r\\n\",\n\t\t304, -2, \"text/plain; charset=utf-8\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: keep-alive for not-modified response\")\n\t}\n\n\t// no content-length (identity transfer-encoding)\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdefg\",\n\t\t200, -2, \"foo/bar\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: close for identity response\")\n\t}\n\t// See https://github.com/valyala/fasthttp/issues/1909\n\tif hasArg(h.h, HeaderTransferEncoding) {\n\t\tt.Fatalf(\"unexpected header: 'Transfer-Encoding' should not be present in parsed headers\")\n\t}\n\n\t// no content-type\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 400 OK\\r\\nContent-Length: 123\\r\\n\\r\\nfoiaaa\",\n\t\t400, 123, string(defaultContentType))\n\n\t// no content-type and no default\n\th.SetNoDefaultContentType(true)\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 400 OK\\r\\nContent-Length: 123\\r\\n\\r\\nfoiaaa\",\n\t\t400, 123, \"\")\n\th.SetNoDefaultContentType(false)\n\n\t// no headers\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\n\\r\\naaaabbb\",\n\t\t200, -2, string(defaultContentType))\n\tif !h.IsHTTP11() {\n\t\tt.Fatalf(\"expecting http/1.1 protocol\")\n\t}\n\n\t// ancient http protocol\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.0 203 OK\\r\\nContent-Length: 123\\r\\nContent-Type: foobar\\r\\n\\r\\naaa\",\n\t\t203, 123, \"foobar\")\n\tif h.IsHTTP11() {\n\t\tt.Fatalf(\"ancient protocol must be non-http/1.1\")\n\t}\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: close for ancient protocol\")\n\t}\n\n\t// ancient http protocol with 'Connection: keep-alive' header.\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.0 403 aa\\r\\nContent-Length: 0\\r\\nContent-Type: 2\\r\\nConnection: Keep-Alive\\r\\n\\r\\nww\",\n\t\t403, 0, \"2\")\n\tif h.IsHTTP11() {\n\t\tt.Fatalf(\"ancient protocol must be non-http/1.1\")\n\t}\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connection: keep-alive for ancient protocol\")\n\t}\n}\n\nfunc TestRequestHeaderReadSuccess(t *testing.T) {\n\tt.Parallel()\n\n\th := &RequestHeader{}\n\n\t// simple headers\n\ttestRequestHeaderReadSuccess(t, h, \"GET /foo/bar HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\",\n\t\t-2, \"/foo/bar\", \"google.com\", \"\", \"\")\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close header\")\n\t}\n\n\t// simple headers with body\n\ttestRequestHeaderReadSuccess(t, h, \"GET /a/bar HTTP/1.1\\r\\nHost: gole.com\\r\\nconneCTION: close\\r\\n\\r\\nfoobar\",\n\t\t-2, \"/a/bar\", \"gole.com\", \"\", \"\")\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"connection: close unset\")\n\t}\n\n\t// ancient http protocol\n\ttestRequestHeaderReadSuccess(t, h, \"GET /bar HTTP/1.0\\r\\nHost: gole\\r\\n\\r\\npppp\",\n\t\t-2, \"/bar\", \"gole\", \"\", \"\")\n\tif h.IsHTTP11() {\n\t\tt.Fatalf(\"ancient http protocol cannot be http/1.1\")\n\t}\n\tif !h.ConnectionClose() {\n\t\tt.Fatalf(\"expecting connectionClose for ancient http protocol\")\n\t}\n\n\t// ancient http protocol with 'Connection: keep-alive' header\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aa HTTP/1.0\\r\\nHost: bb\\r\\nConnection: keep-alive\\r\\n\\r\\nxxx\",\n\t\t-2, \"/aa\", \"bb\", \"\", \"\")\n\tif h.IsHTTP11() {\n\t\tt.Fatalf(\"ancient http protocol cannot be http/1.1\")\n\t}\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected 'connection: close' for ancient http protocol\")\n\t}\n\n\t// complex headers with body\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aabar HTTP/1.1\\r\\nAAA: bbb\\r\\nHost: ole.com\\r\\nAA: bb\\r\\n\\r\\nzzz\",\n\t\t-2, \"/aabar\", \"ole.com\", \"\", \"\")\n\tif !h.IsHTTP11() {\n\t\tt.Fatalf(\"expecting http/1.1 protocol\")\n\t}\n\tif h.ConnectionClose() {\n\t\tt.Fatalf(\"unexpected connection: close\")\n\t}\n\n\t// post method\n\ttestRequestHeaderReadSuccess(t, h, \"POST /aaa?bbb HTTP/1.1\\r\\nHost: foobar.com\\r\\nContent-Length: 1235\\r\\nContent-Type: aaa\\r\\n\\r\\nabcdef\",\n\t\t1235, \"/aaa?bbb\", \"foobar.com\", \"\", \"aaa\")\n\n\t// no space after colon\n\ttestRequestHeaderReadSuccess(t, h, \"GET /a HTTP/1.1\\r\\nHost:aaaxd\\r\\n\\r\\nsdfds\",\n\t\t-2, \"/a\", \"aaaxd\", \"\", \"\")\n\n\t// get with zero content-length\n\ttestRequestHeaderReadSuccess(t, h, \"GET /xxx HTTP/1.1\\r\\nHost: aaa.com\\nContent-Length: 0\\r\\n\\r\\n\",\n\t\t0, \"/xxx\", \"aaa.com\", \"\", \"\")\n\n\t// get with non-zero content-length\n\ttestRequestHeaderReadSuccess(t, h, \"GET /xxx HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 123\\r\\n\\r\\n\",\n\t\t123, \"/xxx\", \"aaa.com\", \"\", \"\")\n\n\t// invalid case\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aaa HTTP/1.1\\r\\nhoST: bbb.com\\r\\n\\r\\naas\",\n\t\t-2, \"/aaa\", \"bbb.com\", \"\", \"\")\n\n\t// referer\n\ttestRequestHeaderReadSuccess(t, h, \"GET /asdf HTTP/1.1\\r\\nHost: aaa.com\\r\\nReferer: bb.com\\r\\n\\r\\naaa\",\n\t\t-2, \"/asdf\", \"aaa.com\", \"bb.com\", \"\")\n\n\t// duplicate host\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aa HTTP/1.1\\r\\nHost: aaaaaa.com\\r\\nHost: bb.com\\r\\n\\r\\n\",\n\t\t-2, \"/aa\", \"bb.com\", \"\", \"\")\n\n\t// post with duplicate content-type\n\ttestRequestHeaderReadSuccess(t, h, \"POST /a HTTP/1.1\\r\\nHost: aa\\r\\nContent-Type: ab\\r\\nContent-Length: 123\\r\\nContent-Type: xx\\r\\n\\r\\n\",\n\t\t123, \"/a\", \"aa\", \"\", \"xx\")\n\n\t// non-post with content-type\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aaa HTTP/1.1\\r\\nHost: bbb.com\\r\\nContent-Type: aaab\\r\\n\\r\\n\",\n\t\t-2, \"/aaa\", \"bbb.com\", \"\", \"aaab\")\n\n\t// non-post with content-length\n\ttestRequestHeaderReadSuccess(t, h, \"HEAD / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 123\\r\\n\\r\\n\",\n\t\t123, \"/\", \"aaa.com\", \"\", \"\")\n\n\t// non-post with content-type and content-length\n\ttestRequestHeaderReadSuccess(t, h, \"GET /aa HTTP/1.1\\r\\nHost: aa.com\\r\\nContent-Type: abd/test\\r\\nContent-Length: 123\\r\\n\\r\\n\",\n\t\t123, \"/aa\", \"aa.com\", \"\", \"abd/test\")\n\n\t// request uri with hostname\n\ttestRequestHeaderReadSuccess(t, h, \"GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\\r\\nHost: aa.cOM\\r\\n\\r\\ntrail\",\n\t\t-2, \"http://gooGle.com/foO/%20bar?xxx#aaa\", \"aa.cOM\", \"\", \"\")\n\n\t// blank lines before the first line\n\ttestRequestHeaderReadSuccess(t, h, \"\\r\\n\\n\\r\\nGET /aaa HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\nsss\",\n\t\t-2, \"/aaa\", \"aaa.com\", \"\", \"\")\n\n\t// request uri with spaces - should be rejected per RFC 9112\n\ttestRequestHeaderReadError(t, h, \"GET /foo/ bar baz HTTP/1.1\\r\\nHost: aa.com\\r\\n\\r\\nxxx\")\n\n\t// no host\n\ttestRequestHeaderReadSuccess(t, h, \"GET /foo/bar HTTP/1.1\\r\\nFOObar: assdfd\\r\\n\\r\\naaa\",\n\t\t-2, \"/foo/bar\", \"\", \"\", \"\")\n\n\t// no host, no headers\n\ttestRequestHeaderReadSuccess(t, h, \"GET /foo/bar HTTP/1.1\\r\\n\\r\\nfoobar\",\n\t\t-2, \"/foo/bar\", \"\", \"\", \"\")\n\n\t// post without content-length and content-type\n\ttestRequestHeaderReadSuccess(t, h, \"POST /aaa HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\nzxc\",\n\t\t-2, \"/aaa\", \"aaa.com\", \"\", \"\")\n\n\t// post without content-type\n\ttestRequestHeaderReadSuccess(t, h, \"POST /abc HTTP/1.1\\r\\nHost: aa.com\\r\\nContent-Length: 123\\r\\n\\r\\npoiuy\",\n\t\t123, \"/abc\", \"aa.com\", \"\", \"\")\n\n\t// post without content-length\n\ttestRequestHeaderReadSuccess(t, h, \"POST /abc HTTP/1.1\\r\\nHost: aa.com\\r\\nContent-Type: adv\\r\\n\\r\\n123456\",\n\t\t-2, \"/abc\", \"aa.com\", \"\", \"adv\")\n\n\t// put request\n\ttestRequestHeaderReadSuccess(t, h, \"PUT /faa HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 123\\r\\nContent-Type: aaa\\r\\n\\r\\nxwwere\",\n\t\t123, \"/faa\", \"aaa.com\", \"\", \"aaa\")\n}\n\nfunc TestResponseHeaderReadError(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\n\t// incorrect first line\n\ttestResponseHeaderReadError(t, h, \"\")\n\ttestResponseHeaderReadError(t, h, \"fo\")\n\ttestResponseHeaderReadError(t, h, \"foobarbaz\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 \")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 s\")\n\n\t// non-numeric status code\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 foobar OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 123foobar OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 foobar344 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\n\t// non-numeric content-length\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: faaa\\r\\nContent-Type: text/html\\r\\n\\r\\nfoobar\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 201 OK\\r\\nContent-Length: 123aa\\r\\nContent-Type: text/ht\\r\\n\\r\\naaa\")\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: aa124\\r\\nContent-Type: html\\r\\n\\r\\nxx\")\n\n\t// no headers\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\n\")\n\n\t// no trailing crlf\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\")\n\n\t// forbidden trailer\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: -1\\r\\nTrailer: Foo, Content-Length\\r\\n\\r\\n\")\n\n\t// no protocol in the first line\n\ttestResponseHeaderReadError(t, h, \"GET /foo/bar\\r\\nHost: google.com\\r\\n\\r\\nisdD\")\n\n\t// zero-length headers\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\n: zero-key\\r\\n\\r\\n\")\n\n\t// Space before header name\n\ttestResponseHeaderReadError(t, h, \"HTTP/1.1 200 OK\\r\\n foo: bar\\r\\n\\r\\n\")\n}\n\nfunc TestResponseHeaderReadErrorSecureLog(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\th.secureErrorLogMessage = true\n\n\t// incorrect first line\n\ttestResponseHeaderReadSecuredError(t, h, \"fo\")\n\ttestResponseHeaderReadSecuredError(t, h, \"foobarbaz\")\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1\")\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 \")\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 s\")\n\n\t// non-numeric status code\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 foobar OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 123foobar OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 foobar344 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\\r\\n\")\n\n\t// no headers\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 200 OK\\r\\n\")\n\n\t// no trailing crlf\n\ttestResponseHeaderReadSecuredError(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Length: 123\\r\\nContent-Type: text/html\\r\\n\")\n}\n\nfunc TestRequestHeaderReadError(t *testing.T) {\n\tt.Parallel()\n\n\th := &RequestHeader{}\n\n\t// incorrect first line\n\ttestRequestHeaderReadError(t, h, \"\")\n\ttestRequestHeaderReadError(t, h, \"fo\")\n\ttestRequestHeaderReadError(t, h, \"GET \")\n\ttestRequestHeaderReadError(t, h, \"GET / HTTP/1.1\\r\")\n\n\t// missing RequestURI\n\ttestRequestHeaderReadError(t, h, \"GET  HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\t// post with invalid content-length\n\ttestRequestHeaderReadError(t, h, \"POST /a HTTP/1.1\\r\\nHost: bb\\r\\nContent-Type: aa\\r\\nContent-Length: dff\\r\\n\\r\\nqwerty\")\n\n\t// forbidden trailer\n\ttestRequestHeaderReadError(t, h, \"POST /a HTTP/1.1\\r\\nContent-Length: -1\\r\\nTrailer: Foo, Content-Length\\r\\n\\r\\n\")\n\n\t// post with duplicate content-length\n\ttestRequestHeaderReadError(t, h, \"POST /xx HTTP/1.1\\r\\nHost: aa\\r\\nContent-Type: s\\r\\nContent-Length: 13\\r\\nContent-Length: 1\\r\\n\\r\\n\")\n\n\t// Zero-length header\n\ttestRequestHeaderReadError(t, h, \"GET /foo/bar HTTP/1.1\\r\\n: zero-key\\r\\n\\r\\n\")\n\n\t// Invalid method\n\ttestRequestHeaderReadError(t, h, \"G(ET /foo/bar HTTP/1.1\\r\\n: zero-key\\r\\n\\r\\n\")\n\n\t// Space before header name\n\ttestRequestHeaderReadError(t, h, \"G(ET /foo/bar HTTP/1.1\\r\\n foo: bar\\r\\n\\r\\n\")\n}\n\nfunc TestRequestHeaderReadSecuredError(t *testing.T) {\n\tt.Parallel()\n\n\th := &RequestHeader{}\n\th.secureErrorLogMessage = true\n\n\t// incorrect first line\n\ttestRequestHeaderReadSecuredError(t, h, \"fo\")\n\ttestRequestHeaderReadSecuredError(t, h, \"GET \")\n\ttestRequestHeaderReadSecuredError(t, h, \"GET / HTTP/1.1\\r\")\n\n\t// missing RequestURI\n\ttestRequestHeaderReadSecuredError(t, h, \"GET  HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\t// post with invalid content-length\n\ttestRequestHeaderReadSecuredError(t, h, \"POST /a HTTP/1.1\\r\\nHost: bb\\r\\nContent-Type: aa\\r\\nContent-Length: dff\\r\\n\\r\\nqwerty\")\n}\n\nfunc testResponseHeaderReadError(t *testing.T, h *ResponseHeader, headers string) {\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading response header %q\", headers)\n\t}\n\t// make sure response header works after error\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Type: foo/bar\\r\\nContent-Length: 12345\\r\\n\\r\\nsss\",\n\t\t200, 12345, \"foo/bar\")\n}\n\nfunc testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers string) {\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading response header %q\", headers)\n\t}\n\tif strings.Contains(err.Error(), headers) {\n\t\tt.Fatalf(\"Not expecting header content in err %q\", err)\n\t}\n\t// make sure response header works after error\n\ttestResponseHeaderReadSuccess(t, h, \"HTTP/1.1 200 OK\\r\\nContent-Type: foo/bar\\r\\nContent-Length: 12345\\r\\n\\r\\nsss\",\n\t\t200, 12345, \"foo/bar\")\n}\n\nfunc testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) {\n\tt.Helper()\n\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading request header %q\", headers)\n\t}\n\n\t// make sure request header works after error\n\ttestRequestHeaderReadSuccess(t, h, \"GET /foo/bar HTTP/1.1\\r\\nHost: aaaa\\r\\n\\r\\nxxx\",\n\t\t-2, \"/foo/bar\", \"aaaa\", \"\", \"\")\n}\n\nfunc testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers string) {\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when reading request header %q\", headers)\n\t}\n\tif strings.Contains(err.Error(), headers) {\n\t\tt.Fatalf(\"Not expecting header content in err %q\", err)\n\t}\n\t// make sure request header works after error\n\ttestRequestHeaderReadSuccess(t, h, \"GET /foo/bar HTTP/1.1\\r\\nHost: aaaa\\r\\n\\r\\nxxx\",\n\t\t-2, \"/foo/bar\", \"aaaa\", \"\", \"\")\n}\n\nfunc testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,\n\texpectedContentType string,\n) {\n\tt.Helper()\n\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response headers: %v. headers=%q\", err, headers)\n\t}\n\tverifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType, \"\")\n}\n\nfunc testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int,\n\texpectedRequestURI, expectedHost, expectedReferer, expectedContentType string,\n) {\n\tt.Helper()\n\n\tr := bytes.NewBufferString(headers)\n\tbr := bufio.NewReader(r)\n\terr := h.Read(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing request headers: %v. headers=%q\", err, headers)\n\t}\n\tverifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType)\n}\n\nfunc verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType, expectedContentEncoding string) {\n\tt.Helper()\n\n\tif h.StatusCode() != expectedStatusCode {\n\t\tt.Fatalf(\"Unexpected status code %d. Expected %d\", h.StatusCode(), expectedStatusCode)\n\t}\n\tif h.ContentLength() != expectedContentLength {\n\t\tt.Fatalf(\"Unexpected content length %d. Expected %d\", h.ContentLength(), expectedContentLength)\n\t}\n\tif string(h.ContentType()) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content type %q. Expected %q\", h.ContentType(), expectedContentType)\n\t}\n\tif string(h.ContentEncoding()) != expectedContentEncoding {\n\t\tt.Fatalf(\"Unexpected content encoding %q. Expected %q\", h.ContentEncoding(), expectedContentEncoding)\n\t}\n}\n\nfunc verifyResponseHeaderConnection(t *testing.T, h *ResponseHeader, expectConnection string) {\n\tif string(h.Peek(HeaderConnection)) != expectConnection {\n\t\tt.Fatalf(\"Unexpected Connection %q. Expected %q\", h.Peek(HeaderConnection), expectConnection)\n\t}\n}\n\nfunc verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength int,\n\texpectedRequestURI, expectedHost, expectedReferer, expectedContentType string,\n) {\n\tif h.ContentLength() != expectedContentLength {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", h.ContentLength(), expectedContentLength)\n\t}\n\tif string(h.RequestURI()) != expectedRequestURI {\n\t\tt.Fatalf(\"Unexpected RequestURI %q. Expected %q\", h.RequestURI(), expectedRequestURI)\n\t}\n\tif string(h.Peek(HeaderHost)) != expectedHost {\n\t\tt.Fatalf(\"Unexpected host %q. Expected %q\", h.Peek(HeaderHost), expectedHost)\n\t}\n\tif string(h.Peek(HeaderReferer)) != expectedReferer {\n\t\tt.Fatalf(\"Unexpected referer %q. Expected %q\", h.Peek(HeaderReferer), expectedReferer)\n\t}\n\tif string(h.Peek(HeaderContentType)) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content-type %q. Expected %q\", h.Peek(HeaderContentType), expectedContentType)\n\t}\n}\n\ntype peeker interface {\n\tPeek(key string) []byte\n}\n\nfunc verifyTrailer(t *testing.T, h peeker, expectedTrailers map[string]string) {\n\tfor k, v := range expectedTrailers {\n\t\tgot := h.Peek(k)\n\t\tif !bytes.Equal(got, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, got)\n\t\t}\n\t}\n}\n\nfunc TestRequestHeader_PeekAll(t *testing.T) {\n\tt.Parallel()\n\th := &RequestHeader{}\n\th.Add(HeaderConnection, \"keep-alive\")\n\th.Add(\"Content-Type\", \"aaa\")\n\th.Add(HeaderHost, \"aaabbb\")\n\th.Add(\"User-Agent\", \"asdfas\")\n\th.Add(\"Content-Length\", \"1123\")\n\th.Add(\"Cookie\", \"foobar=baz\")\n\th.Add(HeaderTrailer, \"foo, bar\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\texpectRequestHeaderAll(t, h, HeaderConnection, [][]byte{s2b(\"keep-alive\")})\n\texpectRequestHeaderAll(t, h, \"Content-Type\", [][]byte{s2b(\"aaa\")})\n\texpectRequestHeaderAll(t, h, HeaderHost, [][]byte{s2b(\"aaabbb\")})\n\texpectRequestHeaderAll(t, h, \"User-Agent\", [][]byte{s2b(\"asdfas\")})\n\texpectRequestHeaderAll(t, h, \"Content-Length\", [][]byte{s2b(\"1123\")})\n\texpectRequestHeaderAll(t, h, \"Cookie\", [][]byte{s2b(\"foobar=baz\")})\n\texpectRequestHeaderAll(t, h, HeaderTrailer, [][]byte{s2b(\"Foo, Bar\")})\n\texpectRequestHeaderAll(t, h, \"aaa\", [][]byte{s2b(\"aaa\"), s2b(\"bbb\")})\n\n\th.Del(\"Content-Type\")\n\th.Del(HeaderHost)\n\th.Del(\"aaa\")\n\texpectRequestHeaderAll(t, h, \"Content-Type\", [][]byte{})\n\texpectRequestHeaderAll(t, h, HeaderHost, [][]byte{})\n\texpectRequestHeaderAll(t, h, \"aaa\", [][]byte{})\n}\n\nfunc expectRequestHeaderAll(t *testing.T, h *RequestHeader, key string, expectedValue [][]byte) {\n\tif len(h.PeekAll(key)) != len(expectedValue) {\n\t\tt.Fatalf(\"Unexpected size for key %q: %d. Expected %d\", key, len(h.PeekAll(key)), len(expectedValue))\n\t}\n\tif !reflect.DeepEqual(h.PeekAll(key), expectedValue) {\n\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q\", key, h.PeekAll(key), expectedValue)\n\t}\n}\n\nfunc TestResponseHeader_PeekAll(t *testing.T) {\n\tt.Parallel()\n\n\th := &ResponseHeader{}\n\th.Add(HeaderContentType, \"aaa/bbb\")\n\th.Add(HeaderContentEncoding, \"gzip\")\n\th.Add(HeaderConnection, \"close\")\n\th.Add(HeaderContentLength, \"1234\")\n\th.Add(HeaderServer, \"aaaa\")\n\th.Add(HeaderSetCookie, \"cccc\")\n\th.Add(\"aaa\", \"aaa\")\n\th.Add(\"aaa\", \"bbb\")\n\n\texpectResponseHeaderAll(t, h, HeaderContentType, [][]byte{s2b(\"aaa/bbb\")})\n\texpectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{s2b(\"gzip\")})\n\texpectResponseHeaderAll(t, h, HeaderConnection, [][]byte{s2b(\"close\")})\n\texpectResponseHeaderAll(t, h, HeaderContentLength, [][]byte{s2b(\"1234\")})\n\texpectResponseHeaderAll(t, h, HeaderServer, [][]byte{s2b(\"aaaa\")})\n\texpectResponseHeaderAll(t, h, HeaderSetCookie, [][]byte{s2b(\"cccc\")})\n\texpectResponseHeaderAll(t, h, \"aaa\", [][]byte{s2b(\"aaa\"), s2b(\"bbb\")})\n\n\th.Del(HeaderContentType)\n\th.Del(HeaderContentEncoding)\n\texpectResponseHeaderAll(t, h, HeaderContentType, [][]byte{defaultContentType})\n\texpectResponseHeaderAll(t, h, HeaderContentEncoding, [][]byte{})\n}\n\nfunc expectResponseHeaderAll(t *testing.T, h *ResponseHeader, key string, expectedValue [][]byte) {\n\tif len(h.PeekAll(key)) != len(expectedValue) {\n\t\tt.Fatalf(\"Unexpected size for key %q: %d. Expected %d\", key, len(h.PeekAll(key)), len(expectedValue))\n\t}\n\tif !reflect.DeepEqual(h.PeekAll(key), expectedValue) {\n\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q\", key, h.PeekAll(key), expectedValue)\n\t}\n}\n\nfunc TestRequestHeader_Keys(t *testing.T) {\n\th := &RequestHeader{}\n\th.Add(HeaderConnection, \"keep-alive\")\n\th.Add(\"Content-Type\", \"aaa\")\n\terr := h.SetTrailer(\"aaa,bbb,ccc\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tactualKeys := h.PeekKeys()\n\texpectedKeys := [][]byte{[]byte(\"Content-Type\"), []byte(\"Trailer\"), []byte(\"Connection\")}\n\tif !reflect.DeepEqual(actualKeys, expectedKeys) {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", actualKeys, expectedKeys)\n\t}\n\tactualTrailerKeys := h.PeekTrailerKeys()\n\texpectedTrailerKeys := [][]byte{s2b(\"Aaa\"), s2b(\"Bbb\"), s2b(\"Ccc\")}\n\tif !reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", actualTrailerKeys, expectedTrailerKeys)\n\t}\n}\n\nfunc TestResponseHeader_Keys(t *testing.T) {\n\th := &ResponseHeader{}\n\th.Add(HeaderConnection, \"keep-alive\")\n\th.Add(\"Content-Type\", \"aaa\")\n\terr := h.SetTrailer(\"aaa,bbb,ccc\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tactualKeys := h.PeekKeys()\n\texpectedKeys := [][]byte{[]byte(\"Content-Type\"), []byte(\"Trailer\"), []byte(\"Connection\")}\n\tif !reflect.DeepEqual(actualKeys, expectedKeys) {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", actualKeys, expectedKeys)\n\t}\n\tactualTrailerKeys := h.PeekTrailerKeys()\n\texpectedTrailerKeys := [][]byte{s2b(\"Aaa\"), s2b(\"Bbb\"), s2b(\"Ccc\")}\n\tif !reflect.DeepEqual(actualTrailerKeys, expectedTrailerKeys) {\n\t\tt.Fatalf(\"Unexpected value %q. Expected %q\", actualTrailerKeys, expectedTrailerKeys)\n\t}\n}\n\nfunc TestAddVaryHeader(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.addVaryBytes([]byte(\"Accept-Encoding\"))\n\tgot := string(h.Peek(\"Vary\"))\n\texpected := \"Accept-Encoding\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tvar buf bytes.Buffer\n\tif _, err := h.WriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing header: %v\", err)\n\t}\n\n\tif n := strings.Count(buf.String(), \"Vary: \"); n != 1 {\n\t\tt.Errorf(\"Vary occurred %d times\", n)\n\t}\n}\n\nfunc TestAddVaryHeaderExisting(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.Set(\"Vary\", \"Accept\")\n\th.addVaryBytes([]byte(\"Accept-Encoding\"))\n\tgot := string(h.Peek(\"Vary\"))\n\texpected := \"Accept,Accept-Encoding\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tvar buf bytes.Buffer\n\tif _, err := h.WriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing header: %v\", err)\n\t}\n\n\tif n := strings.Count(buf.String(), \"Vary: \"); n != 1 {\n\t\tt.Errorf(\"Vary occurred %d times\", n)\n\t}\n}\n\nfunc TestAddVaryHeaderExistingAcceptEncoding(t *testing.T) {\n\tt.Parallel()\n\n\tvar h ResponseHeader\n\n\th.Set(\"Vary\", \"Accept-Encoding\")\n\th.addVaryBytes([]byte(\"Accept-Encoding\"))\n\tgot := string(h.Peek(\"Vary\"))\n\texpected := \"Accept-Encoding\"\n\tif got != expected {\n\t\tt.Errorf(\"expected %q got %q\", expected, got)\n\t}\n\n\tvar buf bytes.Buffer\n\tif _, err := h.WriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing header: %v\", err)\n\t}\n\n\tif n := strings.Count(buf.String(), \"Vary: \"); n != 1 {\n\t\tt.Errorf(\"Vary occurred %d times\", n)\n\t}\n}\n\nfunc TestRequestHeaderExtraWhitespace(t *testing.T) {\n\tvar h RequestHeader\n\n\t// Test cases that should fail due to extra whitespace\n\ttestCases := []string{\n\t\t\"GET  /foo HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",    // Extra space after method\n\t\t\"GET   /foo HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",   // Multiple spaces after method\n\t\t\"GET /foo  HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",    // Extra space before HTTP version\n\t\t\"GET /foo   HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",   // Multiple spaces before HTTP version\n\t\t\"GET  /foo  HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",   // Extra spaces in both places\n\t\t\"GET   /foo   HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\", // Multiple extra spaces in both places\n\t}\n\n\tfor i, testCase := range testCases {\n\t\tbr := bufio.NewReader(bytes.NewBufferString(testCase))\n\t\terr := h.Read(br)\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Test case %d should have failed but didn't. Request: %q\", i, testCase)\n\t\t}\n\t\tif !strings.Contains(err.Error(), \"extra whitespace\") {\n\t\t\tt.Errorf(\"Test case %d should have failed with 'extra whitespace' error but got: %v\", i, err)\n\t\t}\n\t}\n}\n\nfunc TestRequestHeaderValidWhitespace(t *testing.T) {\n\tvar h RequestHeader\n\n\t// Test cases that should succeed (proper single space separation)\n\ttestCases := []struct {\n\t\trequest        string\n\t\texpectedMethod string\n\t\texpectedURI    string\n\t}{\n\t\t{\n\t\t\t\"GET /foo HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",\n\t\t\t\"GET\",\n\t\t\t\"/foo\",\n\t\t},\n\t\t{\n\t\t\t\"POST /api/v1/users HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",\n\t\t\t\"POST\",\n\t\t\t\"/api/v1/users\",\n\t\t},\n\t\t{\n\t\t\t\"PUT /resource HTTP/1.0\\r\\nHost: example.com\\r\\n\\r\\n\",\n\t\t\t\"PUT\",\n\t\t\t\"/resource\",\n\t\t},\n\t}\n\n\tfor i, testCase := range testCases {\n\t\tbr := bufio.NewReader(bytes.NewBufferString(testCase.request))\n\t\terr := h.Read(br)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Test case %d should have succeeded but failed with: %v. Request: %q\", i, err, testCase.request)\n\t\t\tcontinue\n\t\t}\n\n\t\tif string(h.Method()) != testCase.expectedMethod {\n\t\t\tt.Errorf(\"Test case %d: expected method %q but got %q\", i, testCase.expectedMethod, h.Method())\n\t\t}\n\n\t\tif string(h.RequestURI()) != testCase.expectedURI {\n\t\t\tt.Errorf(\"Test case %d: expected URI %q but got %q\", i, testCase.expectedURI, h.RequestURI())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "header_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nvar strFoobar = []byte(\"foobar.com\")\n\n// it has the same length as Content-Type.\nvar strNonSpecialHeader = []byte(\"Dontent-Type\")\n\ntype benchReadBuf struct {\n\ts []byte\n\tn int\n}\n\nfunc (r *benchReadBuf) Read(p []byte) (int, error) {\n\tif r.n == len(r.s) {\n\t\treturn 0, io.EOF\n\t}\n\n\tn := copy(p, r.s[r.n:])\n\tr.n += n\n\treturn n, nil\n}\n\nfunc BenchmarkRequestHeaderRead(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h RequestHeader\n\t\tbuf := &benchReadBuf{\n\t\t\ts: []byte(\n\t\t\t\t\"GET /foo/bar HTTP/1.1\\r\\n\" +\n\t\t\t\t\t\"Host: foobar.com\\r\\n\" +\n\t\t\t\t\t\"User-Agent: aaa.bbb\\r\\n\" +\n\t\t\t\t\t\"Referer: http://google.com/aaa/bbb\\r\\n\" +\n\t\t\t\t\t\"Content-Type: text/html\\r\\n\" +\n\t\t\t\t\t\"Server: aaa 1/2.3\\r\\n\" +\n\t\t\t\t\t\"Test: 1.2.3\\r\\n\" +\n\t\t\t\t\t\"Foo: bar\\r\\n\" +\n\t\t\t\t\t\"X-Forwarded-For: 1.2.3.4\\r\\n\" +\n\t\t\t\t\t\"X-Forwarded-Proto: https\\r\\n\" +\n\t\t\t\t\t\"\\r\\n\",\n\t\t\t),\n\t\t}\n\t\tbr := bufio.NewReader(buf)\n\t\tfor pb.Next() {\n\t\t\tbuf.n = 0\n\t\t\tbr.Reset(buf)\n\t\t\th.Reset()\n\t\t\tif err := h.Read(br); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading header: %v\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Benchmark net/http as a comparison.\nfunc BenchmarkRequestHeaderReadNetHttp(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tbuf := &benchReadBuf{\n\t\t\ts: []byte(\n\t\t\t\t\"GET /foo/bar HTTP/1.1\\r\\n\" +\n\t\t\t\t\t\"Host: foobar.com\\r\\n\" +\n\t\t\t\t\t\"User-Agent: aaa.bbb\\r\\n\" +\n\t\t\t\t\t\"Referer: http://google.com/aaa/bbb\\r\\n\" +\n\t\t\t\t\t\"Content-Type: text/html\\r\\n\" +\n\t\t\t\t\t\"Test: 1.2.3\\r\\n\" +\n\t\t\t\t\t\"Foo: bar\\r\\n\" +\n\t\t\t\t\t\"X-Forwarded-For: 1.2.3.4\\r\\n\" +\n\t\t\t\t\t\"X-Forwarded-Proto: https\\r\\n\" +\n\t\t\t\t\t\"\\r\\n\",\n\t\t\t),\n\t\t}\n\t\tbr := bufio.NewReader(buf)\n\t\tfor pb.Next() {\n\t\t\tbuf.n = 0\n\t\t\tbr.Reset(buf)\n\n\t\t\tif _, err := http.ReadRequest(br); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading header: %v\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkResponseHeaderRead(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h ResponseHeader\n\t\tbuf := &benchReadBuf{\n\t\t\ts: []byte(\n\t\t\t\t\"HTTP/1.1 200 OK\\r\\n\" +\n\t\t\t\t\t\"Content-Type: text/html\\r\\n\" +\n\t\t\t\t\t\"Server: aaa 1/2.3\\r\\n\" +\n\t\t\t\t\t\"Test: 1.2.3\\r\\n\" +\n\t\t\t\t\t\"Foo: bar\\r\\n\" +\n\t\t\t\t\t\"Content-Length: 1256\\r\\n\" +\n\t\t\t\t\t\"Content-Encoding: gzip\\r\\n\" +\n\t\t\t\t\t\"Cache-Control: no-cache\\r\\n\" +\n\t\t\t\t\t\"\\r\\n\",\n\t\t\t),\n\t\t}\n\t\tbr := bufio.NewReader(buf)\n\t\tfor pb.Next() {\n\t\t\tbuf.n = 0\n\t\t\tbr.Reset(buf)\n\t\t\th.Reset()\n\t\t\tif err := h.Read(br); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when reading header: %v\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkRequestHeaderWrite(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h RequestHeader\n\t\th.SetRequestURI(\"/foo/bar\")\n\t\th.SetHost(\"foobar.com\")\n\t\th.SetUserAgent(\"aaa.bbb\")\n\t\th.SetReferer(\"http://google.com/aaa/bbb\")\n\t\tvar w bytebufferpool.ByteBuffer\n\t\tfor pb.Next() {\n\t\t\tif _, err := h.WriteTo(&w); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when writing header: %v\", err)\n\t\t\t}\n\t\t\tw.Reset()\n\t\t}\n\t})\n}\n\nfunc BenchmarkResponseHeaderWrite(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h ResponseHeader\n\t\th.SetStatusCode(200)\n\t\th.SetContentType(\"text/html\")\n\t\th.SetContentLength(1256)\n\t\th.SetServer(\"aaa 1/2.3\")\n\t\th.Set(\"Test\", \"1.2.3\")\n\t\tvar w bytebufferpool.ByteBuffer\n\t\tfor pb.Next() {\n\t\t\tif _, err := h.WriteTo(&w); err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error when writing header: %v\", err)\n\t\t\t}\n\t\t\tw.Reset()\n\t\t}\n\t})\n}\n\n// Result: 2.2 ns/op.\nfunc BenchmarkRequestHeaderPeekBytesSpecialHeader(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h RequestHeader\n\t\th.SetContentTypeBytes(strFoobar)\n\t\tfor pb.Next() {\n\t\t\tv := h.PeekBytes(strContentType)\n\t\t\tif !bytes.Equal(v, strFoobar) {\n\t\t\t\tb.Fatalf(\"unexpected result: %q. Expected %q\", v, strFoobar)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Result: 2.9 ns/op.\nfunc BenchmarkRequestHeaderPeekBytesNonSpecialHeader(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h RequestHeader\n\t\th.SetBytesKV(strNonSpecialHeader, strFoobar)\n\t\tfor pb.Next() {\n\t\t\tv := h.PeekBytes(strNonSpecialHeader)\n\t\t\tif !bytes.Equal(v, strFoobar) {\n\t\t\t\tb.Fatalf(\"unexpected result: %q. Expected %q\", v, strFoobar)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Result: 2.3 ns/op.\nfunc BenchmarkResponseHeaderPeekBytesSpecialHeader(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h ResponseHeader\n\t\th.SetContentTypeBytes(strFoobar)\n\t\tfor pb.Next() {\n\t\t\tv := h.PeekBytes(strContentType)\n\t\t\tif !bytes.Equal(v, strFoobar) {\n\t\t\t\tb.Fatalf(\"unexpected result: %q. Expected %q\", v, strFoobar)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Result: 2.9 ns/op.\nfunc BenchmarkResponseHeaderPeekBytesNonSpecialHeader(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar h ResponseHeader\n\t\th.SetBytesKV(strNonSpecialHeader, strFoobar)\n\t\tfor pb.Next() {\n\t\t\tv := h.PeekBytes(strNonSpecialHeader)\n\t\t\tif !bytes.Equal(v, strFoobar) {\n\t\t\t\tb.Fatalf(\"unexpected result: %q. Expected %q\", v, strFoobar)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkNormalizeHeaderKeyCommonCase(b *testing.B) {\n\tsrc := []byte(\"User-Agent-Host-Content-Type-Content-Length-Server\")\n\tbenchmarkNormalizeHeaderKey(b, src)\n}\n\nfunc BenchmarkNormalizeHeaderKeyLowercase(b *testing.B) {\n\tsrc := []byte(\"user-agent-host-content-type-content-length-server\")\n\tbenchmarkNormalizeHeaderKey(b, src)\n}\n\nfunc BenchmarkNormalizeHeaderKeyUppercase(b *testing.B) {\n\tsrc := []byte(\"USER-AGENT-HOST-CONTENT-TYPE-CONTENT-LENGTH-SERVER\")\n\tbenchmarkNormalizeHeaderKey(b, src)\n}\n\nfunc benchmarkNormalizeHeaderKey(b *testing.B, src []byte) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tbuf := make([]byte, len(src))\n\t\tfor pb.Next() {\n\t\t\tcopy(buf, src)\n\t\t\tnormalizeHeaderKey(buf, false)\n\t\t}\n\t})\n}\n\nfunc BenchmarkVisitHeaderParams(b *testing.B) {\n\tvar h RequestHeader\n\th.SetBytesKV(strContentType, []byte(`text/plain  ;  foo=bar  ;   param2=\"dquote is: [\\\"], ok?\" ; version=1; q=0.324  `))\n\n\theader := h.ContentType()\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor n := 0; n < b.N; n++ {\n\t\tVisitHeaderParams(header, func(key, value []byte) bool { return true })\n\t}\n}\n\nfunc BenchmarkRemoveNewLines(b *testing.B) {\n\ttype testcase struct {\n\t\tvalue         string\n\t\texpectedValue string\n\t}\n\n\ttestcases := []testcase{\n\t\t{value: \"MaliciousValue\", expectedValue: \"MaliciousValue\"},\n\t\t{value: \"MaliciousValue\\r\\n\", expectedValue: \"MaliciousValue  \"},\n\t\t{value: \"Malicious\\nValue\", expectedValue: \"Malicious Value\"},\n\t\t{value: \"Malicious\\rValue\", expectedValue: \"Malicious Value\"},\n\t}\n\n\tfor i, tcase := range testcases {\n\t\tcaseName := strconv.FormatInt(int64(i), 10)\n\t\tb.Run(caseName, func(subB *testing.B) {\n\t\t\tsubB.ReportAllocs()\n\t\t\tvar h RequestHeader\n\t\t\tfor i := 0; i < subB.N; i++ {\n\t\t\t\th.Set(\"Test\", tcase.value)\n\t\t\t}\n\t\t\tsubB.StopTimer()\n\t\t\tactualValue := string(h.Peek(\"Test\"))\n\n\t\t\tif actualValue != tcase.expectedValue {\n\t\t\t\tsubB.Errorf(\"unexpected value, got: %+v\", actualValue)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkRequestHeaderIsGet(b *testing.B) {\n\treq := &RequestHeader{method: []byte(MethodGet)}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\treq.IsGet()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "headers.go",
    "content": "package fasthttp\n\nconst (\n\tHeaderAccept                          = \"Accept\"\n\tHeaderAcceptCH                        = \"Accept-CH\"\n\tHeaderAcceptCharset                   = \"Accept-Charset\"\n\tHeaderAcceptCHLifetime                = \"Accept-CH-Lifetime\"\n\tHeaderAcceptEncoding                  = \"Accept-Encoding\"\n\tHeaderAcceptLanguage                  = \"Accept-Language\"\n\tHeaderAcceptPatch                     = \"Accept-Patch\"\n\tHeaderAcceptPushPolicy                = \"Accept-Push-Policy\"\n\tHeaderAcceptRanges                    = \"Accept-Ranges\"\n\tHeaderAcceptSignature                 = \"Accept-Signature\"\n\tHeaderAccessControlAllowCredentials   = \"Access-Control-Allow-Credentials\"\n\tHeaderAccessControlAllowHeaders       = \"Access-Control-Allow-Headers\"\n\tHeaderAccessControlAllowMethods       = \"Access-Control-Allow-Methods\"\n\tHeaderAccessControlAllowOrigin        = \"Access-Control-Allow-Origin\"\n\tHeaderAccessControlExposeHeaders      = \"Access-Control-Expose-Headers\"\n\tHeaderAccessControlMaxAge             = \"Access-Control-Max-Age\"\n\tHeaderAccessControlRequestHeaders     = \"Access-Control-Request-Headers\"\n\tHeaderAccessControlRequestMethod      = \"Access-Control-Request-Method\"\n\tHeaderAge                             = \"Age\"\n\tHeaderAllow                           = \"Allow\"\n\tHeaderAltSvc                          = \"Alt-Svc\"\n\tHeaderAuthorization                   = \"Authorization\"\n\tHeaderCacheControl                    = \"Cache-Control\"\n\tHeaderClearSiteData                   = \"Clear-Site-Data\"\n\tHeaderConnection                      = \"Connection\"\n\tHeaderContentDisposition              = \"Content-Disposition\"\n\tHeaderContentDPR                      = \"Content-DPR\"\n\tHeaderContentEncoding                 = \"Content-Encoding\"\n\tHeaderContentLanguage                 = \"Content-Language\"\n\tHeaderContentLength                   = \"Content-Length\"\n\tHeaderContentLocation                 = \"Content-Location\"\n\tHeaderContentRange                    = \"Content-Range\"\n\tHeaderContentSecurityPolicy           = \"Content-Security-Policy\"\n\tHeaderContentSecurityPolicyReportOnly = \"Content-Security-Policy-Report-Only\"\n\tHeaderContentType                     = \"Content-Type\"\n\tHeaderCookie                          = \"Cookie\"\n\tHeaderCrossOriginResourcePolicy       = \"Cross-Origin-Resource-Policy\"\n\tHeaderDate                            = \"Date\"\n\tHeaderDNT                             = \"DNT\"\n\tHeaderDPR                             = \"DPR\"\n\tHeaderEarlyData                       = \"Early-Data\"\n\tHeaderETag                            = \"ETag\"\n\tHeaderExpect                          = \"Expect\"\n\tHeaderExpectCT                        = \"Expect-CT\"\n\tHeaderExpires                         = \"Expires\"\n\tHeaderFeaturePolicy                   = \"Feature-Policy\"\n\tHeaderForwarded                       = \"Forwarded\"\n\tHeaderFrom                            = \"From\"\n\tHeaderHost                            = \"Host\"\n\tHeaderIfMatch                         = \"If-Match\"\n\tHeaderIfModifiedSince                 = \"If-Modified-Since\"\n\tHeaderIfNoneMatch                     = \"If-None-Match\"\n\tHeaderIfRange                         = \"If-Range\"\n\tHeaderIfUnmodifiedSince               = \"If-Unmodified-Since\"\n\tHeaderIndex                           = \"Index\"\n\tHeaderKeepAlive                       = \"Keep-Alive\"\n\tHeaderLargeAllocation                 = \"Large-Allocation\"\n\tHeaderLastEventID                     = \"Last-Event-ID\"\n\tHeaderLastModified                    = \"Last-Modified\"\n\tHeaderLink                            = \"Link\"\n\tHeaderLocation                        = \"Location\"\n\tHeaderMaxForwards                     = \"Max-Forwards\"\n\tHeaderNEL                             = \"NEL\"\n\tHeaderOrigin                          = \"Origin\"\n\tHeaderPingFrom                        = \"Ping-From\"\n\tHeaderPingTo                          = \"Ping-To\"\n\tHeaderPragma                          = \"Pragma\"\n\tHeaderProxyAuthenticate               = \"Proxy-Authenticate\"\n\tHeaderProxyAuthorization              = \"Proxy-Authorization\"\n\tHeaderProxyConnection                 = \"Proxy-Connection\"\n\tHeaderPublicKeyPins                   = \"Public-Key-Pins\"\n\tHeaderPublicKeyPinsReportOnly         = \"Public-Key-Pins-Report-Only\"\n\tHeaderPushPolicy                      = \"Push-Policy\"\n\tHeaderRange                           = \"Range\"\n\tHeaderReferer                         = \"Referer\"\n\tHeaderReferrerPolicy                  = \"Referrer-Policy\"\n\tHeaderReportTo                        = \"Report-To\"\n\tHeaderRetryAfter                      = \"Retry-After\"\n\tHeaderSaveData                        = \"Save-Data\"\n\tHeaderSecWebSocketAccept              = \"Sec-WebSocket-Accept\"\n\tHeaderSecWebSocketExtensions          = \"Sec-WebSocket-Extensions\" // #nosec G101\n\tHeaderSecWebSocketKey                 = \"Sec-WebSocket-Key\"\n\tHeaderSecWebSocketProtocol            = \"Sec-WebSocket-Protocol\"\n\tHeaderSecWebSocketVersion             = \"Sec-WebSocket-Version\"\n\tHeaderServer                          = \"Server\"\n\tHeaderServerTiming                    = \"Server-Timing\"\n\tHeaderSetCookie                       = \"Set-Cookie\"\n\tHeaderSignature                       = \"Signature\"\n\tHeaderSignedHeaders                   = \"Signed-Headers\"\n\tHeaderSourceMap                       = \"SourceMap\"\n\tHeaderStrictTransportSecurity         = \"Strict-Transport-Security\"\n\tHeaderTE                              = \"TE\"\n\tHeaderTimingAllowOrigin               = \"Timing-Allow-Origin\"\n\tHeaderTk                              = \"Tk\"\n\tHeaderTrailer                         = \"Trailer\"\n\tHeaderTransferEncoding                = \"Transfer-Encoding\"\n\tHeaderUpgrade                         = \"Upgrade\"\n\tHeaderUpgradeInsecureRequests         = \"Upgrade-Insecure-Requests\"\n\tHeaderUserAgent                       = \"User-Agent\"\n\tHeaderVary                            = \"Vary\"\n\tHeaderVia                             = \"Via\"\n\tHeaderViewportWidth                   = \"Viewport-Width\"\n\tHeaderWarning                         = \"Warning\"\n\tHeaderWidth                           = \"Width\"\n\tHeaderWWWAuthenticate                 = \"WWW-Authenticate\"\n\tHeaderXContentTypeOptions             = \"X-Content-Type-Options\"\n\tHeaderXDNSPrefetchControl             = \"X-DNS-Prefetch-Control\"\n\tHeaderXDownloadOptions                = \"X-Download-Options\"\n\tHeaderXForwardedFor                   = \"X-Forwarded-For\"\n\tHeaderXForwardedHost                  = \"X-Forwarded-Host\"\n\tHeaderXForwardedProto                 = \"X-Forwarded-Proto\"\n\tHeaderXFrameOptions                   = \"X-Frame-Options\"\n\tHeaderXPermittedCrossDomainPolicies   = \"X-Permitted-Cross-Domain-Policies\"\n\tHeaderXPingback                       = \"X-Pingback\"\n\tHeaderXPoweredBy                      = \"X-Powered-By\"\n\tHeaderXRequestedWith                  = \"X-Requested-With\"\n\tHeaderXRobotsTag                      = \"X-Robots-Tag\"\n\tHeaderXUACompatible                   = \"X-UA-Compatible\"\n\tHeaderXXSSProtection                  = \"X-XSS-Protection\"\n)\n"
  },
  {
    "path": "headerscanner.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n)\n\ntype headerScanner struct {\n\tinitialized bool\n\n\tb []byte\n\tr int\n\n\tkey   []byte\n\tvalue []byte\n\n\terr error\n}\n\nfunc (s *headerScanner) next() bool {\n\tif !s.initialized {\n\t\tif bytes.HasPrefix(s.b, strCRLF) {\n\t\t\ts.r = 2\n\t\t\treturn false\n\t\t}\n\n\t\ti := bytes.Index(s.b, strCRLFCRLF)\n\t\tif i < 0 {\n\t\t\ts.err = ErrNeedMore\n\t\t\treturn false\n\t\t}\n\t\ti += 4\n\n\t\ts.b = s.b[:i]\n\t\tif len(s.b) > 0 && (s.b[0] == ' ' || s.b[0] == '\\t') {\n\t\t\ts.err = errors.New(\"invalid headers, headers cannot start with space or tab\")\n\t\t\treturn false\n\t\t}\n\n\t\ts.initialized = true\n\t}\n\n\tkv, err := s.readContinuedLineSlice()\n\tif len(kv) == 0 {\n\t\ts.err = err\n\t\treturn false\n\t}\n\n\t// Key ends at first colon.\n\tk, v, ok := bytes.Cut(kv, strColon)\n\tif !ok {\n\t\ts.err = fmt.Errorf(\"malformed MIME header line: %q\", kv)\n\t\treturn false\n\t}\n\tif !isValidHeaderKey(k) {\n\t\ts.err = fmt.Errorf(\"malformed MIME header line: %q\", kv)\n\t\treturn false\n\t}\n\n\t// Skip initial spaces in value.\n\tv = bytes.TrimLeft(v, \" \\t\")\n\n\ts.key = k\n\ts.value = v\n\n\tif err != nil {\n\t\ts.err = err\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// readLine reads a line from b, starting at s.r, and returns it.\nfunc (s *headerScanner) readLine() (line []byte) {\n\tsearchStart := 0\n\n\tfor {\n\t\tif i := bytes.IndexByte(s.b[s.r+searchStart:], '\\n'); i >= 0 {\n\t\t\ti += searchStart\n\t\t\tline = s.b[s.r : s.r+i+1]\n\t\t\ts.r += i + 1\n\t\t\tbreak\n\t\t}\n\n\t\tsearchStart = len(s.b) - s.r\n\t}\n\n\tif len(line) == 0 {\n\t\treturn nil\n\t}\n\n\t// drop \\n and possible preceding \\r\n\tif line[len(line)-1] == '\\n' {\n\t\tdrop := 1\n\t\tif len(line) > 1 && line[len(line)-2] == '\\r' {\n\t\t\tdrop = 2\n\t\t}\n\t\tline = line[:len(line)-drop]\n\t}\n\treturn line\n}\n\n// readContinuedLineSlice reads continued lines from b until it finds a line\n// that does not start with a space or tab, or it reaches the end of b.\nfunc (s *headerScanner) readContinuedLineSlice() ([]byte, error) {\n\tline := s.readLine()\n\tif len(line) == 0 { // blank line - no continuation\n\t\treturn line, nil\n\t}\n\n\tif bytes.IndexByte(line, ':') < 0 {\n\t\treturn nil, fmt.Errorf(\"malformed MIME header: missing colon: %q\", line)\n\t}\n\n\t// If the line doesn't start with a space or tab, we are done.\n\tif len(s.b)-s.r > 1 {\n\t\tpeek := s.b[s.r : s.r+2]\n\t\tif len(peek) > 0 && (isASCIILetter(peek[0]) || peek[0] == '\\n') ||\n\t\t\tlen(peek) == 2 && peek[0] == '\\r' && peek[1] == '\\n' {\n\t\t\treturn trim(line), nil\n\t\t}\n\t}\n\n\tmline := trim(line)\n\n\t// Read continuation lines.\n\tfor s.skipSpace() {\n\t\tmline = append(mline, ' ')\n\t\tline := s.readLine()\n\t\tmline = append(mline, trim(line)...)\n\t}\n\treturn mline, nil\n}\n\n// skipSpace skips one or multiple spaces and tabs in b.\nfunc (s *headerScanner) skipSpace() bool {\n\tskipped := false\n\tfor {\n\t\tc := s.b[s.r]\n\t\tif c != ' ' && c != '\\t' {\n\t\t\tbreak\n\t\t}\n\t\ts.r++\n\t\tskipped = true\n\t}\n\treturn skipped\n}\n\nfunc isASCIILetter(b byte) bool {\n\tb |= 0x20 // Make lower case.\n\treturn 'a' <= b && b <= 'z'\n}\n\n// trim returns s with leading and trailing spaces and tabs removed.\n// It does not assume Unicode or UTF-8.\nfunc trim(s []byte) []byte {\n\ti := 0\n\tfor i < len(s) && (s[i] == ' ' || s[i] == '\\t') {\n\t\ti++\n\t}\n\tn := len(s)\n\tfor n > i && (s[n-1] == ' ' || s[n-1] == '\\t') {\n\t\tn--\n\t}\n\treturn s[i:n]\n}\n"
  },
  {
    "path": "http.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nvar (\n\trequestBodyPoolSizeLimit  = -1\n\tresponseBodyPoolSizeLimit = -1\n)\n\n// SetBodySizePoolLimit set the max body size for bodies to be returned to the pool.\n// If the body size is larger it will be released instead of put back into the pool for reuse.\nfunc SetBodySizePoolLimit(reqBodyLimit, respBodyLimit int) {\n\trequestBodyPoolSizeLimit = reqBodyLimit\n\tresponseBodyPoolSizeLimit = respBodyLimit\n}\n\n// Request represents HTTP request.\n//\n// It is forbidden copying Request instances. Create new instances\n// and use CopyTo instead.\n//\n// Request instance MUST NOT be used from concurrently running goroutines.\ntype Request struct {\n\tnoCopy noCopy\n\n\tbodyStream io.Reader\n\tw          requestBodyWriter\n\tbody       *bytebufferpool.ByteBuffer\n\n\tmultipartForm         *multipart.Form\n\tmultipartFormBoundary string\n\n\tpostArgs   Args\n\tuserValues userData\n\n\tbodyRaw []byte\n\n\turi URI\n\n\t// Request header.\n\t//\n\t// Copying Header by value is forbidden. Use pointer to Header instead.\n\tHeader RequestHeader\n\n\t// Request timeout. Usually set by DoDeadline or DoTimeout\n\t// if <= 0, means not set\n\ttimeout time.Duration\n\n\tsecureErrorLogMessage bool\n\n\t// Group bool members in order to reduce Request object size.\n\tparsedURI      bool\n\tparsedPostArgs bool\n\turiParseErr    error\n\n\tkeepBodyBuffer bool\n\n\t// Used by Server to indicate the request was received on a HTTPS endpoint.\n\t// Client/HostClient shouldn't use this field but should depend on the uri.scheme instead.\n\tisTLS bool\n\n\t// Use Host header (request.Header.SetHost) instead of the host from SetRequestURI, SetHost, or URI().SetHost\n\tUseHostHeader bool\n\n\t// DisableRedirectPathNormalizing disables redirect path normalization when used with DoRedirects.\n\t//\n\t// By default redirect path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisableRedirectPathNormalizing bool\n}\n\n// Response represents HTTP response.\n//\n// It is forbidden copying Response instances. Create new instances\n// and use CopyTo instead.\n//\n// Response instance MUST NOT be used from concurrently running goroutines.\ntype Response struct {\n\tnoCopy noCopy\n\n\tbodyStream io.Reader\n\n\t// Remote TCPAddr from concurrently net.Conn.\n\traddr net.Addr\n\t// Local TCPAddr from concurrently net.Conn.\n\tladdr net.Addr\n\tw     responseBodyWriter\n\tbody  *bytebufferpool.ByteBuffer\n\n\tbodyRaw []byte\n\n\t// Response header.\n\t//\n\t// Copying Header by value is forbidden. Use pointer to Header instead.\n\tHeader ResponseHeader\n\n\t// Flush headers as soon as possible without waiting for first body bytes.\n\t// Relevant for bodyStream only.\n\tImmediateHeaderFlush bool\n\n\t// StreamBody enables response body streaming.\n\t// Use SetBodyStream to set the body stream.\n\tStreamBody bool\n\n\t// Response.Read() skips reading body if set to true.\n\t// Use it for reading HEAD responses.\n\t//\n\t// Response.Write() skips writing body if set to true.\n\t// Use it for writing HEAD responses.\n\tSkipBody bool\n\n\tkeepBodyBuffer        bool\n\tsecureErrorLogMessage bool\n}\n\n// SetHost sets host for the request.\nfunc (req *Request) SetHost(host string) {\n\treq.URI().SetHost(host)\n}\n\n// SetHostBytes sets host for the request.\nfunc (req *Request) SetHostBytes(host []byte) {\n\treq.URI().SetHostBytes(host)\n}\n\n// Host returns the host for the given request.\nfunc (req *Request) Host() []byte {\n\treturn req.URI().Host()\n}\n\n// SetRequestURI sets RequestURI.\nfunc (req *Request) SetRequestURI(requestURI string) {\n\treq.Header.SetRequestURI(requestURI)\n\treq.parsedURI = false\n\treq.uriParseErr = nil\n}\n\n// SetRequestURIBytes sets RequestURI.\nfunc (req *Request) SetRequestURIBytes(requestURI []byte) {\n\treq.Header.SetRequestURIBytes(requestURI)\n\treq.parsedURI = false\n\treq.uriParseErr = nil\n}\n\n// RequestURI returns request's URI.\nfunc (req *Request) RequestURI() []byte {\n\tif req.parsedURI {\n\t\trequestURI := req.uri.RequestURI()\n\t\treq.SetRequestURIBytes(requestURI)\n\t}\n\treturn req.Header.RequestURI()\n}\n\n// StatusCode returns response status code.\nfunc (resp *Response) StatusCode() int {\n\treturn resp.Header.StatusCode()\n}\n\n// SetStatusCode sets response status code.\nfunc (resp *Response) SetStatusCode(statusCode int) {\n\tresp.Header.SetStatusCode(statusCode)\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (resp *Response) ConnectionClose() bool {\n\treturn resp.Header.ConnectionClose()\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (resp *Response) SetConnectionClose() {\n\tresp.Header.SetConnectionClose()\n}\n\n// ConnectionClose returns true if 'Connection: close' header is set.\nfunc (req *Request) ConnectionClose() bool {\n\treturn req.Header.ConnectionClose()\n}\n\n// SetConnectionClose sets 'Connection: close' header.\nfunc (req *Request) SetConnectionClose() {\n\treq.Header.SetConnectionClose()\n}\n\n// GetTimeOut retrieves the timeout duration set for the Request.\n//\n// This method returns a time.Duration that determines how long the request\n// can wait before it times out. In the default use case, the timeout applies\n// to the entire request lifecycle, including both receiving the response\n// headers and the response body.\nfunc (req *Request) GetTimeOut() time.Duration {\n\treturn req.timeout\n}\n\n// SendFile registers file on the given path to be used as response body\n// when Write is called.\n//\n// Note that SendFile doesn't set Content-Type, so set it yourself\n// with Header.SetContentType.\nfunc (resp *Response) SendFile(path string) error {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\tf.Close()\n\t\treturn err\n\t}\n\tsize64 := fileInfo.Size()\n\tsize := int(size64)\n\tif int64(size) != size64 {\n\t\tsize = -1\n\t}\n\n\tresp.Header.SetLastModified(fileInfo.ModTime())\n\tresp.SetBodyStream(f, size)\n\treturn nil\n}\n\n// SetBodyStream sets request body stream and, optionally body size.\n//\n// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// Note that GET and HEAD requests cannot have body.\n//\n// See also SetBodyStreamWriter.\nfunc (req *Request) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\treq.ResetBody()\n\treq.bodyStream = bodyStream\n\treq.Header.SetContentLength(bodySize)\n}\n\n// SetBodyStream sets response body stream and, optionally body size.\n//\n// If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// See also SetBodyStreamWriter.\nfunc (resp *Response) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\tresp.ResetBody()\n\tresp.bodyStream = bodyStream\n\tresp.Header.SetContentLength(bodySize)\n}\n\n// IsBodyStream returns true if body is set via SetBodyStream*.\nfunc (req *Request) IsBodyStream() bool {\n\treturn req.bodyStream != nil\n}\n\n// IsBodyStream returns true if body is set via SetBodyStream*.\nfunc (resp *Response) IsBodyStream() bool {\n\treturn resp.bodyStream != nil\n}\n\n// SetBodyStreamWriter registers the given sw for populating request body.\n//\n// This function may be used in the following cases:\n//\n//   - if request body is too big (more than 10MB).\n//   - if request body is streamed from slow external sources.\n//   - if request body must be streamed to the server in chunks\n//     (aka `http client push` or `chunked transfer-encoding`).\n//\n// Note that GET and HEAD requests cannot have body.\n//\n// See also SetBodyStream.\nfunc (req *Request) SetBodyStreamWriter(sw StreamWriter) {\n\tsr := NewStreamReader(sw)\n\treq.SetBodyStream(sr, -1)\n}\n\n// SetBodyStreamWriter registers the given sw for populating response body.\n//\n// This function may be used in the following cases:\n//\n//   - if response body is too big (more than 10MB).\n//   - if response body is streamed from slow external sources.\n//   - if response body must be streamed to the client in chunks\n//     (aka `http server push` or `chunked transfer-encoding`).\n//\n// See also SetBodyStream.\nfunc (resp *Response) SetBodyStreamWriter(sw StreamWriter) {\n\tsr := NewStreamReader(sw)\n\tresp.SetBodyStream(sr, -1)\n}\n\n// BodyWriter returns writer for populating response body.\n//\n// If used inside RequestHandler, the returned writer must not be used\n// after returning from RequestHandler. Use RequestCtx.Write\n// or SetBodyStreamWriter in this case.\nfunc (resp *Response) BodyWriter() io.Writer {\n\tresp.w.r = resp\n\treturn &resp.w\n}\n\n// BodyStream returns io.Reader.\n//\n// You must CloseBodyStream or ReleaseRequest after you use it.\nfunc (req *Request) BodyStream() io.Reader {\n\treturn req.bodyStream\n}\n\nfunc (req *Request) CloseBodyStream() error {\n\treturn req.closeBodyStream()\n}\n\n// BodyStream returns io.Reader.\n//\n// You must CloseBodyStream or ReleaseResponse after you use it.\nfunc (resp *Response) BodyStream() io.Reader {\n\treturn resp.bodyStream\n}\n\nfunc (resp *Response) CloseBodyStream() error {\n\treturn resp.closeBodyStream(nil)\n}\n\ntype ReadCloserWithError interface {\n\tio.Reader\n\tCloseWithError(err error) error\n}\n\ntype closeReader struct {\n\tio.Reader\n\n\tcloseFunc func(err error) error\n}\n\nfunc newCloseReaderWithError(r io.Reader, closeFunc func(err error) error) ReadCloserWithError {\n\tif r == nil {\n\t\tpanic(`BUG: reader is nil`)\n\t}\n\treturn &closeReader{Reader: r, closeFunc: closeFunc}\n}\n\nfunc (c *closeReader) CloseWithError(err error) error {\n\tif c.closeFunc == nil {\n\t\treturn nil\n\t}\n\treturn c.closeFunc(err)\n}\n\n// BodyWriter returns writer for populating request body.\nfunc (req *Request) BodyWriter() io.Writer {\n\treq.w.r = req\n\treturn &req.w\n}\n\ntype responseBodyWriter struct {\n\tr *Response\n}\n\nfunc (w *responseBodyWriter) Write(p []byte) (int, error) {\n\tw.r.AppendBody(p)\n\treturn len(p), nil\n}\n\nfunc (w *responseBodyWriter) WriteString(s string) (int, error) {\n\tw.r.AppendBodyString(s)\n\treturn len(s), nil\n}\n\ntype requestBodyWriter struct {\n\tr *Request\n}\n\nfunc (w *requestBodyWriter) Write(p []byte) (int, error) {\n\tw.r.AppendBody(p)\n\treturn len(p), nil\n}\n\nfunc (w *requestBodyWriter) WriteString(s string) (int, error) {\n\tw.r.AppendBodyString(s)\n\treturn len(s), nil\n}\n\nfunc (resp *Response) ParseNetConn(conn net.Conn) {\n\tresp.raddr = conn.RemoteAddr()\n\tresp.laddr = conn.LocalAddr()\n}\n\n// RemoteAddr returns the remote network address. The Addr returned is shared\n// by all invocations of RemoteAddr, so do not modify it.\nfunc (resp *Response) RemoteAddr() net.Addr {\n\treturn resp.raddr\n}\n\n// LocalAddr returns the local network address. The Addr returned is shared\n// by all invocations of LocalAddr, so do not modify it.\nfunc (resp *Response) LocalAddr() net.Addr {\n\treturn resp.laddr\n}\n\n// Body returns response body.\n//\n// The returned value is valid until the response is released,\n// either though ReleaseResponse or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (resp *Response) Body() []byte {\n\tif resp.bodyStream != nil {\n\t\tbodyBuf := resp.bodyBuffer()\n\t\tbodyBuf.Reset()\n\t\t_, err := copyZeroAlloc(bodyBuf, resp.bodyStream)\n\t\tresp.closeBodyStream(err) //nolint:errcheck\n\t\tif err != nil {\n\t\t\tbodyBuf.SetString(err.Error())\n\t\t}\n\t}\n\treturn resp.bodyBytes()\n}\n\nfunc (resp *Response) bodyBytes() []byte {\n\tif resp.bodyRaw != nil {\n\t\treturn resp.bodyRaw\n\t}\n\tif resp.body == nil {\n\t\treturn nil\n\t}\n\treturn resp.body.B\n}\n\nfunc (req *Request) bodyBytes() []byte {\n\tif req.bodyRaw != nil {\n\t\treturn req.bodyRaw\n\t}\n\tif req.bodyStream != nil {\n\t\tbodyBuf := req.bodyBuffer()\n\t\tbodyBuf.Reset()\n\t\t_, err := copyZeroAlloc(bodyBuf, req.bodyStream)\n\t\treq.closeBodyStream() //nolint:errcheck\n\t\tif err != nil {\n\t\t\tbodyBuf.SetString(err.Error())\n\t\t}\n\t}\n\tif req.body == nil {\n\t\treturn nil\n\t}\n\treturn req.body.B\n}\n\nfunc (resp *Response) bodyBuffer() *bytebufferpool.ByteBuffer {\n\tif resp.body == nil {\n\t\tresp.body = responseBodyPool.Get()\n\t}\n\tresp.bodyRaw = nil\n\treturn resp.body\n}\n\nfunc (req *Request) bodyBuffer() *bytebufferpool.ByteBuffer {\n\tif req.body == nil {\n\t\treq.body = requestBodyPool.Get()\n\t}\n\treq.bodyRaw = nil\n\treturn req.body\n}\n\nvar (\n\tresponseBodyPool bytebufferpool.Pool\n\trequestBodyPool  bytebufferpool.Pool\n)\n\n// BodyGunzip returns un-gzipped body data.\n//\n// This method may be used if the request header contains\n// 'Content-Encoding: gzip' for reading un-gzipped body.\n// Use Body for reading gzipped request body.\nfunc (req *Request) BodyGunzip() ([]byte, error) {\n\treturn req.BodyGunzipWithLimit(0)\n}\n\n// BodyGunzipWithLimit returns un-gzipped body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (req *Request) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\n\treturn gunzipData(req.Body(), maxBodySize)\n}\n\n// BodyGunzip returns un-gzipped body data.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding: gzip' for reading un-gzipped body.\n// Use Body for reading gzipped response body.\nfunc (resp *Response) BodyGunzip() ([]byte, error) {\n\treturn resp.BodyGunzipWithLimit(0)\n}\n\n// BodyGunzipWithLimit returns un-gzipped body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (resp *Response) BodyGunzipWithLimit(maxBodySize int) ([]byte, error) {\n\treturn gunzipData(resp.Body(), maxBodySize)\n}\n\nfunc gunzipData(p []byte, maxBodySize int) ([]byte, error) {\n\tvar bb bytebufferpool.ByteBuffer\n\t_, err := writeGunzip(&bb, p, maxBodySize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bb.B, nil\n}\n\n// BodyUnbrotli returns un-brotlied body data.\n//\n// This method may be used if the request header contains\n// 'Content-Encoding: br' for reading un-brotlied body.\n// Use Body for reading brotlied request body.\nfunc (req *Request) BodyUnbrotli() ([]byte, error) {\n\treturn req.BodyUnbrotliWithLimit(0)\n}\n\n// BodyUnbrotliWithLimit returns un-brotlied body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (req *Request) BodyUnbrotliWithLimit(maxBodySize int) ([]byte, error) {\n\treturn unBrotliData(req.Body(), maxBodySize)\n}\n\n// BodyUnbrotli returns un-brotlied body data.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding: br' for reading un-brotlied body.\n// Use Body for reading brotlied response body.\nfunc (resp *Response) BodyUnbrotli() ([]byte, error) {\n\treturn resp.BodyUnbrotliWithLimit(0)\n}\n\n// BodyUnbrotliWithLimit returns un-brotlied body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (resp *Response) BodyUnbrotliWithLimit(maxBodySize int) ([]byte, error) {\n\treturn unBrotliData(resp.Body(), maxBodySize)\n}\n\nfunc unBrotliData(p []byte, maxBodySize int) ([]byte, error) {\n\tvar bb bytebufferpool.ByteBuffer\n\t_, err := writeUnbrotli(&bb, p, maxBodySize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bb.B, nil\n}\n\n// BodyInflate returns inflated body data.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding: deflate' for reading inflated request body.\n// Use Body for reading deflated request body.\nfunc (req *Request) BodyInflate() ([]byte, error) {\n\treturn req.BodyInflateWithLimit(0)\n}\n\n// BodyInflateWithLimit returns inflated body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (req *Request) BodyInflateWithLimit(maxBodySize int) ([]byte, error) {\n\treturn inflateData(req.Body(), maxBodySize)\n}\n\n// BodyInflate returns inflated body data.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding: deflate' for reading inflated response body.\n// Use Body for reading deflated response body.\nfunc (resp *Response) BodyInflate() ([]byte, error) {\n\treturn resp.BodyInflateWithLimit(0)\n}\n\n// BodyInflateWithLimit returns inflated body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (resp *Response) BodyInflateWithLimit(maxBodySize int) ([]byte, error) {\n\treturn inflateData(resp.Body(), maxBodySize)\n}\n\nfunc (ctx *RequestCtx) RequestBodyStream() io.Reader {\n\treturn ctx.Request.bodyStream\n}\n\nfunc (req *Request) BodyUnzstd() ([]byte, error) {\n\treturn req.BodyUnzstdWithLimit(0)\n}\n\n// BodyUnzstdWithLimit returns un-zstd body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (req *Request) BodyUnzstdWithLimit(maxBodySize int) ([]byte, error) {\n\treturn unzstdData(req.Body(), maxBodySize)\n}\n\nfunc (resp *Response) BodyUnzstd() ([]byte, error) {\n\treturn resp.BodyUnzstdWithLimit(0)\n}\n\n// BodyUnzstdWithLimit returns un-zstd body data and limits the size\n// of uncompressed body data to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (resp *Response) BodyUnzstdWithLimit(maxBodySize int) ([]byte, error) {\n\treturn unzstdData(resp.Body(), maxBodySize)\n}\n\nfunc unzstdData(p []byte, maxBodySize int) ([]byte, error) {\n\tvar bb bytebufferpool.ByteBuffer\n\t_, err := writeUnzstd(&bb, p, maxBodySize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bb.B, nil\n}\n\nfunc inflateData(p []byte, maxBodySize int) ([]byte, error) {\n\tvar bb bytebufferpool.ByteBuffer\n\t_, err := writeInflate(&bb, p, maxBodySize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bb.B, nil\n}\n\nvar ErrContentEncodingUnsupported = errors.New(\"unsupported Content-Encoding\")\n\n// BodyUncompressed returns body data and if needed decompresses it from gzip,\n// deflate, brotli or zstd.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding' for reading uncompressed request body.\n// Use Body for reading the raw request body.\nfunc (req *Request) BodyUncompressed() ([]byte, error) {\n\treturn req.BodyUncompressedWithLimit(0)\n}\n\n// BodyUncompressedWithLimit returns body data and if needed decompresses it from gzip,\n// deflate, brotli or zstd. The size of uncompressed data is limited to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (req *Request) BodyUncompressedWithLimit(maxBodySize int) ([]byte, error) {\n\tswitch string(req.Header.ContentEncoding()) {\n\tcase \"\":\n\t\treturn req.Body(), nil\n\tcase \"deflate\":\n\t\treturn req.BodyInflateWithLimit(maxBodySize)\n\tcase \"gzip\":\n\t\treturn req.BodyGunzipWithLimit(maxBodySize)\n\tcase \"br\":\n\t\treturn req.BodyUnbrotliWithLimit(maxBodySize)\n\tcase \"zstd\":\n\t\treturn req.BodyUnzstdWithLimit(maxBodySize)\n\tdefault:\n\t\treturn nil, ErrContentEncodingUnsupported\n\t}\n}\n\n// BodyUncompressed returns body data and if needed decompresses it from gzip,\n// deflate, brotli or zstd.\n//\n// This method may be used if the response header contains\n// 'Content-Encoding' for reading uncompressed response body.\n// Use Body for reading the raw response body.\nfunc (resp *Response) BodyUncompressed() ([]byte, error) {\n\treturn resp.BodyUncompressedWithLimit(0)\n}\n\n// BodyUncompressedWithLimit returns body data and if needed decompresses it from gzip,\n// deflate, brotli or zstd. The size of uncompressed data is limited to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\nfunc (resp *Response) BodyUncompressedWithLimit(maxBodySize int) ([]byte, error) {\n\tswitch string(resp.Header.ContentEncoding()) {\n\tcase \"\":\n\t\treturn resp.Body(), nil\n\tcase \"deflate\":\n\t\treturn resp.BodyInflateWithLimit(maxBodySize)\n\tcase \"gzip\":\n\t\treturn resp.BodyGunzipWithLimit(maxBodySize)\n\tcase \"br\":\n\t\treturn resp.BodyUnbrotliWithLimit(maxBodySize)\n\tcase \"zstd\":\n\t\treturn resp.BodyUnzstdWithLimit(maxBodySize)\n\tdefault:\n\t\treturn nil, ErrContentEncodingUnsupported\n\t}\n}\n\n// BodyWriteTo writes request body to w.\nfunc (req *Request) BodyWriteTo(w io.Writer) error {\n\tif req.bodyStream != nil {\n\t\t_, err := copyZeroAlloc(w, req.bodyStream)\n\t\treq.closeBodyStream() //nolint:errcheck\n\t\treturn err\n\t}\n\tif req.onlyMultipartForm() {\n\t\treturn WriteMultipartForm(w, req.multipartForm, req.multipartFormBoundary)\n\t}\n\t_, err := w.Write(req.bodyBytes())\n\treturn err\n}\n\n// BodyWriteTo writes response body to w.\nfunc (resp *Response) BodyWriteTo(w io.Writer) error {\n\tif resp.bodyStream != nil {\n\t\t_, err := copyZeroAlloc(w, resp.bodyStream)\n\t\tresp.closeBodyStream(err) //nolint:errcheck\n\t\treturn err\n\t}\n\t_, err := w.Write(resp.bodyBytes())\n\treturn err\n}\n\n// AppendBody appends p to response body.\n//\n// It is safe re-using p after the function returns.\nfunc (resp *Response) AppendBody(p []byte) {\n\tresp.closeBodyStream(nil)  //nolint:errcheck\n\tresp.bodyBuffer().Write(p) //nolint:errcheck\n}\n\n// AppendBodyString appends s to response body.\nfunc (resp *Response) AppendBodyString(s string) {\n\tresp.closeBodyStream(nil)        //nolint:errcheck\n\tresp.bodyBuffer().WriteString(s) //nolint:errcheck\n}\n\n// SetBody sets response body.\n//\n// It is safe re-using body argument after the function returns.\nfunc (resp *Response) SetBody(body []byte) {\n\tresp.closeBodyStream(nil) //nolint:errcheck\n\tbodyBuf := resp.bodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.Write(body) //nolint:errcheck\n}\n\n// SetBodyString sets response body.\nfunc (resp *Response) SetBodyString(body string) {\n\tresp.closeBodyStream(nil) //nolint:errcheck\n\tbodyBuf := resp.bodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.WriteString(body) //nolint:errcheck\n}\n\n// ResetBody resets response body.\nfunc (resp *Response) ResetBody() {\n\tresp.bodyRaw = nil\n\tresp.closeBodyStream(nil) //nolint:errcheck\n\tif resp.body != nil {\n\t\tif resp.keepBodyBuffer {\n\t\t\tresp.body.Reset()\n\t\t} else {\n\t\t\tresponseBodyPool.Put(resp.body)\n\t\t\tresp.body = nil\n\t\t}\n\t}\n}\n\n// SetBodyRaw sets response body, but without copying it.\n//\n// From this point onward the body argument must not be changed.\nfunc (resp *Response) SetBodyRaw(body []byte) {\n\tresp.ResetBody()\n\tresp.bodyRaw = body\n}\n\n// SetBodyRaw sets response body, but without copying it.\n//\n// From this point onward the body argument must not be changed.\nfunc (req *Request) SetBodyRaw(body []byte) {\n\treq.ResetBody()\n\treq.bodyRaw = body\n}\n\n// ReleaseBody retires the response body if it is greater than \"size\" bytes.\n//\n// This permits GC to reclaim the large buffer.  If used, must be before\n// ReleaseResponse.\n//\n// Use this method only if you really understand how it works.\n// The majority of workloads don't need this method.\nfunc (resp *Response) ReleaseBody(size int) {\n\tresp.bodyRaw = nil\n\tif resp.body == nil {\n\t\treturn\n\t}\n\tif cap(resp.body.B) > size {\n\t\tresp.closeBodyStream(nil) //nolint:errcheck\n\t\tresp.body = nil\n\t}\n}\n\n// ReleaseBody retires the request body if it is greater than \"size\" bytes.\n//\n// This permits GC to reclaim the large buffer.  If used, must be before\n// ReleaseRequest.\n//\n// Use this method only if you really understand how it works.\n// The majority of workloads don't need this method.\nfunc (req *Request) ReleaseBody(size int) {\n\treq.bodyRaw = nil\n\tif req.body == nil {\n\t\treturn\n\t}\n\tif cap(req.body.B) > size {\n\t\treq.closeBodyStream() //nolint:errcheck\n\t\treq.body = nil\n\t}\n}\n\n// SwapBody swaps response body with the given body and returns\n// the previous response body.\n//\n// It is forbidden to use the body passed to SwapBody after\n// the function returns.\nfunc (resp *Response) SwapBody(body []byte) []byte {\n\tbb := resp.bodyBuffer()\n\n\tif resp.bodyStream != nil {\n\t\tbb.Reset()\n\t\t_, err := copyZeroAlloc(bb, resp.bodyStream)\n\t\tresp.closeBodyStream(err) //nolint:errcheck\n\t\tif err != nil {\n\t\t\tbb.Reset()\n\t\t\tbb.SetString(err.Error())\n\t\t}\n\t}\n\n\tresp.bodyRaw = nil\n\n\toldBody := bb.B\n\tbb.B = body\n\treturn oldBody\n}\n\n// SwapBody swaps request body with the given body and returns\n// the previous request body.\n//\n// It is forbidden to use the body passed to SwapBody after\n// the function returns.\nfunc (req *Request) SwapBody(body []byte) []byte {\n\tbb := req.bodyBuffer()\n\n\tif req.bodyStream != nil {\n\t\tbb.Reset()\n\t\t_, err := copyZeroAlloc(bb, req.bodyStream)\n\t\treq.closeBodyStream() //nolint:errcheck\n\t\tif err != nil {\n\t\t\tbb.Reset()\n\t\t\tbb.SetString(err.Error())\n\t\t}\n\t}\n\n\treq.bodyRaw = nil\n\n\toldBody := bb.B\n\tbb.B = body\n\treturn oldBody\n}\n\n// Body returns request body.\n//\n// The returned value is valid until the request is released,\n// either though ReleaseRequest or your request handler returning.\n// Do not store references to returned value. Make copies instead.\nfunc (req *Request) Body() []byte {\n\tif req.bodyRaw != nil {\n\t\treturn req.bodyRaw\n\t} else if req.onlyMultipartForm() {\n\t\tbody, err := marshalMultipartForm(req.multipartForm, req.multipartFormBoundary)\n\t\tif err != nil {\n\t\t\treturn []byte(err.Error())\n\t\t}\n\t\treturn body\n\t}\n\treturn req.bodyBytes()\n}\n\n// AppendBody appends p to request body.\n//\n// It is safe re-using p after the function returns.\nfunc (req *Request) AppendBody(p []byte) {\n\treq.RemoveMultipartFormFiles()\n\treq.closeBodyStream()     //nolint:errcheck\n\treq.bodyBuffer().Write(p) //nolint:errcheck\n}\n\n// AppendBodyString appends s to request body.\nfunc (req *Request) AppendBodyString(s string) {\n\treq.RemoveMultipartFormFiles()\n\treq.closeBodyStream()           //nolint:errcheck\n\treq.bodyBuffer().WriteString(s) //nolint:errcheck\n}\n\n// SetBody sets request body.\n//\n// It is safe re-using body argument after the function returns.\nfunc (req *Request) SetBody(body []byte) {\n\treq.RemoveMultipartFormFiles()\n\treq.closeBodyStream() //nolint:errcheck\n\treq.bodyBuffer().Set(body)\n}\n\n// SetBodyString sets request body.\nfunc (req *Request) SetBodyString(body string) {\n\treq.RemoveMultipartFormFiles()\n\treq.closeBodyStream() //nolint:errcheck\n\treq.bodyBuffer().SetString(body)\n}\n\n// ResetBody resets request body.\nfunc (req *Request) ResetBody() {\n\treq.bodyRaw = nil\n\treq.RemoveMultipartFormFiles()\n\treq.closeBodyStream() //nolint:errcheck\n\tif req.body != nil {\n\t\tif req.keepBodyBuffer {\n\t\t\treq.body.Reset()\n\t\t} else {\n\t\t\trequestBodyPool.Put(req.body)\n\t\t\treq.body = nil\n\t\t}\n\t}\n}\n\n// CopyTo copies req contents to dst except of body stream.\nfunc (req *Request) CopyTo(dst *Request) {\n\treq.copyToSkipBody(dst)\n\tswitch {\n\tcase req.bodyRaw != nil:\n\t\tdst.bodyRaw = append(dst.bodyRaw[:0], req.bodyRaw...)\n\t\tif dst.body != nil {\n\t\t\tdst.body.Reset()\n\t\t}\n\tcase req.body != nil:\n\t\tdst.bodyBuffer().Set(req.body.B)\n\tcase dst.body != nil:\n\t\tdst.body.Reset()\n\t}\n}\n\nfunc (req *Request) copyToSkipBody(dst *Request) {\n\tdst.Reset()\n\treq.Header.CopyTo(&dst.Header)\n\n\treq.uri.CopyTo(&dst.uri)\n\tdst.parsedURI = req.parsedURI\n\tdst.uriParseErr = req.uriParseErr\n\n\treq.postArgs.CopyTo(&dst.postArgs)\n\tdst.parsedPostArgs = req.parsedPostArgs\n\tdst.isTLS = req.isTLS\n\n\tdst.UseHostHeader = req.UseHostHeader\n\n\t// do not copy multipartForm - it will be automatically\n\t// re-created on the first call to MultipartForm.\n}\n\n// CopyTo copies resp contents to dst except of body stream.\nfunc (resp *Response) CopyTo(dst *Response) {\n\tresp.copyToSkipBody(dst)\n\tswitch {\n\tcase resp.bodyRaw != nil:\n\t\tdst.bodyRaw = append(dst.bodyRaw, resp.bodyRaw...)\n\t\tif dst.body != nil {\n\t\t\tdst.body.Reset()\n\t\t}\n\tcase resp.body != nil:\n\t\tdst.bodyBuffer().Set(resp.body.B)\n\tcase dst.body != nil:\n\t\tdst.body.Reset()\n\t}\n}\n\nfunc (resp *Response) copyToSkipBody(dst *Response) {\n\tdst.Reset()\n\tresp.Header.CopyTo(&dst.Header)\n\tdst.SkipBody = resp.SkipBody\n\tdst.raddr = resp.raddr\n\tdst.laddr = resp.laddr\n}\n\nfunc swapRequestBody(a, b *Request) {\n\ta.body, b.body = b.body, a.body\n\ta.bodyRaw, b.bodyRaw = b.bodyRaw, a.bodyRaw\n\ta.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream\n\n\t// This code assumes that if a requestStream was swapped the headers are also swapped or copied.\n\tif rs, ok := a.bodyStream.(*requestStream); ok {\n\t\trs.header = &a.Header\n\t}\n\tif rs, ok := b.bodyStream.(*requestStream); ok {\n\t\trs.header = &b.Header\n\t}\n}\n\nfunc swapResponseBody(a, b *Response) {\n\ta.body, b.body = b.body, a.body\n\ta.bodyRaw, b.bodyRaw = b.bodyRaw, a.bodyRaw\n\ta.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream\n}\n\n// URI returns request URI.\nfunc (req *Request) URI() *URI {\n\treq.parseURI() //nolint:errcheck\n\treturn &req.uri\n}\n\n// SetURI initializes request URI.\n// Use this method if a single URI may be reused across multiple requests.\n// Otherwise, you can just use SetRequestURI() and it will be parsed as new URI.\n// The URI is copied and can be safely modified later.\nfunc (req *Request) SetURI(newURI *URI) {\n\tif newURI != nil {\n\t\tnewURI.CopyTo(&req.uri)\n\t\treq.parsedURI = true\n\t\treq.uriParseErr = nil\n\t\treturn\n\t}\n\treq.uri.Reset()\n\treq.parsedURI = false\n\treq.uriParseErr = nil\n}\n\nfunc (req *Request) parseURI() error {\n\tif req.parsedURI {\n\t\treturn req.uriParseErr\n\t}\n\n\treq.parsedURI = true\n\treq.uriParseErr = req.uri.parse(req.Header.Host(), req.Header.RequestURI(), req.isTLS)\n\treturn req.uriParseErr\n}\n\n// PostArgs returns POST arguments.\nfunc (req *Request) PostArgs() *Args {\n\treq.parsePostArgs()\n\treturn &req.postArgs\n}\n\nfunc (req *Request) parsePostArgs() {\n\tif req.parsedPostArgs {\n\t\treturn\n\t}\n\treq.parsedPostArgs = true\n\n\tif !bytes.HasPrefix(req.Header.ContentType(), strPostArgsContentType) {\n\t\treturn\n\t}\n\treq.postArgs.ParseBytes(req.bodyBytes())\n}\n\n// ErrNoMultipartForm means that the request's Content-Type\n// isn't 'multipart/form-data'.\nvar ErrNoMultipartForm = errors.New(\"request Content-Type has bad boundary or is not multipart/form-data\")\n\n// MultipartForm returns request's multipart form.\n//\n// Returns ErrNoMultipartForm if request's Content-Type\n// isn't 'multipart/form-data'.\n//\n// This method is equivalent to MultipartFormWithLimit(0), i.e. no body size\n// limit is applied during multipart parsing.\n//\n// RemoveMultipartFormFiles must be called after returned multipart form\n// is processed.\nfunc (req *Request) MultipartForm() (*multipart.Form, error) {\n\treturn req.MultipartFormWithLimit(0)\n}\n\n// MultipartFormWithLimit returns request's multipart form and limits the\n// read multipart body size to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\n//\n// Returns ErrNoMultipartForm if request's Content-Type\n// isn't 'multipart/form-data'.\n//\n// RemoveMultipartFormFiles must be called after returned multipart form\n// is processed.\nfunc (req *Request) MultipartFormWithLimit(maxBodySize int) (*multipart.Form, error) {\n\tif req.multipartForm != nil {\n\t\treturn req.multipartForm, nil\n\t}\n\n\treq.multipartFormBoundary = string(req.Header.MultipartFormBoundary())\n\tif req.multipartFormBoundary == \"\" {\n\t\treturn nil, ErrNoMultipartForm\n\t}\n\n\tvar err error\n\tce := req.Header.peek(strContentEncoding)\n\n\tif req.bodyStream != nil {\n\t\tbodyStream := req.bodyStream\n\t\tvar lr *io.LimitedReader\n\t\tif bytes.Equal(ce, strGzip) {\n\t\t\tif bodyStream, err = gzip.NewReader(bodyStream); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot gunzip request body: %w\", err)\n\t\t\t}\n\t\t} else if len(ce) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"unsupported Content-Encoding: %q\", ce)\n\t\t}\n\t\tif maxBodySize > 0 {\n\t\t\tlr = &io.LimitedReader{\n\t\t\t\tR: bodyStream,\n\t\t\t\tN: int64(maxBodySize) + 1,\n\t\t\t}\n\t\t\tbodyStream = lr\n\t\t}\n\n\t\tmr := multipart.NewReader(bodyStream, req.multipartFormBoundary)\n\t\treq.multipartForm, err = mr.ReadForm(8 * 1024)\n\t\tif err != nil {\n\t\t\tif lr != nil && lr.N <= 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %w\", ErrBodyTooLarge)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %w\", err)\n\t\t}\n\t\tif lr != nil && lr.N <= 0 {\n\t\t\treq.RemoveMultipartFormFiles()\n\t\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %w\", ErrBodyTooLarge)\n\t\t}\n\t} else {\n\t\tbody := req.bodyBytes()\n\t\tif bytes.Equal(ce, strGzip) {\n\t\t\tif body, err = gunzipData(body, maxBodySize); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot gunzip request body: %w\", err)\n\t\t\t}\n\t\t} else if len(ce) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"unsupported Content-Encoding: %q\", ce)\n\t\t}\n\t\tif maxBodySize > 0 && len(body) > maxBodySize {\n\t\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %w\", ErrBodyTooLarge)\n\t\t}\n\n\t\treq.multipartForm, err = readMultipartForm(bytes.NewReader(body), req.multipartFormBoundary, len(body), len(body))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn req.multipartForm, nil\n}\n\nfunc marshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) {\n\tvar buf bytebufferpool.ByteBuffer\n\tif err := WriteMultipartForm(&buf, f, boundary); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.B, nil\n}\n\n// WriteMultipartForm writes the given multipart form f with the given\n// boundary to w.\nfunc WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {\n\t// Do not care about memory allocations here, since multipart\n\t// form processing is slow.\n\tif boundary == \"\" {\n\t\treturn errors.New(\"form boundary cannot be empty\")\n\t}\n\n\tmw := multipart.NewWriter(w)\n\tif err := mw.SetBoundary(boundary); err != nil {\n\t\treturn fmt.Errorf(\"cannot use form boundary %q: %w\", boundary, err)\n\t}\n\n\t// marshal values\n\tfor k, vv := range f.Value {\n\t\tfor _, v := range vv {\n\t\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot write form field %q value %q: %w\", k, v, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// marshal files\n\tfor k, fvv := range f.File {\n\t\tfor _, fv := range fvv {\n\t\t\tvw, err := mw.CreatePart(fv.Header)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot create form file %q (%q): %w\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tfh, err := fv.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot open form file %q (%q): %w\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tif _, err = copyZeroAlloc(vw, fh); err != nil {\n\t\t\t\t_ = fh.Close()\n\t\t\t\treturn fmt.Errorf(\"error when copying form file %q (%q): %w\", k, fv.Filename, err)\n\t\t\t}\n\t\t\tif err = fh.Close(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"cannot close form file %q (%q): %w\", k, fv.Filename, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := mw.Close(); err != nil {\n\t\treturn fmt.Errorf(\"error when closing multipart form writer: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc readMultipartForm(r io.Reader, boundary string, size, maxInMemoryFileSize int) (*multipart.Form, error) {\n\t// Do not care about memory allocations here, since they are tiny\n\t// compared to multipart data (aka multi-MB files) usually sent\n\t// in multipart/form-data requests.\n\n\tif size <= 0 {\n\t\treturn nil, fmt.Errorf(\"form size must be greater than 0. Given %d\", size)\n\t}\n\tlr := io.LimitReader(r, int64(size))\n\tmr := multipart.NewReader(lr, boundary)\n\tf, err := mr.ReadForm(int64(maxInMemoryFileSize))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot read multipart/form-data body: %w\", err)\n\t}\n\treturn f, nil\n}\n\n// Reset clears request contents.\nfunc (req *Request) Reset() {\n\treq.userValues.Reset() // it should be at the top, since some values might implement io.Closer interface\n\tif requestBodyPoolSizeLimit >= 0 && req.body != nil {\n\t\treq.ReleaseBody(requestBodyPoolSizeLimit)\n\t}\n\treq.Header.Reset()\n\treq.resetSkipHeader()\n\treq.timeout = 0\n\treq.UseHostHeader = false\n\treq.DisableRedirectPathNormalizing = false\n}\n\nfunc (req *Request) resetSkipHeader() {\n\treq.ResetBody()\n\treq.uri.Reset()\n\treq.parsedURI = false\n\treq.uriParseErr = nil\n\treq.postArgs.Reset()\n\treq.parsedPostArgs = false\n\treq.isTLS = false\n}\n\n// RemoveMultipartFormFiles removes multipart/form-data temporary files\n// associated with the request.\nfunc (req *Request) RemoveMultipartFormFiles() {\n\tif req.multipartForm != nil {\n\t\t// Do not check for error, since these files may be deleted or moved\n\t\t// to new places by user code.\n\t\treq.multipartForm.RemoveAll() //nolint:errcheck\n\t\treq.multipartForm = nil\n\t}\n\treq.multipartFormBoundary = \"\"\n}\n\n// Reset clears response contents.\nfunc (resp *Response) Reset() {\n\tif responseBodyPoolSizeLimit >= 0 && resp.body != nil {\n\t\tresp.ReleaseBody(responseBodyPoolSizeLimit)\n\t}\n\tresp.resetSkipHeader()\n\tresp.Header.Reset()\n\tresp.SkipBody = false\n\tresp.raddr = nil\n\tresp.laddr = nil\n\tresp.ImmediateHeaderFlush = false\n\tresp.StreamBody = false\n}\n\nfunc (resp *Response) resetSkipHeader() {\n\tresp.ResetBody()\n}\n\n// Read reads request (including body) from the given r.\n//\n// RemoveMultipartFormFiles or Reset must be called after\n// reading multipart/form-data request in order to delete temporarily\n// uploaded files.\n//\n// If MayContinue returns true, the caller must:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (req *Request) Read(r *bufio.Reader) error {\n\treturn req.ReadLimitBody(r, 0)\n}\n\nconst defaultMaxInMemoryFileSize = 16 * 1024 * 1024\n\n// ErrGetOnly is returned when server expects only GET requests,\n// but some other type of request came (Server.GetOnly option is true).\nvar ErrGetOnly = errors.New(\"non-GET request received\")\n\n// ReadLimitBody reads request from the given r, limiting the body size.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\n//\n// RemoveMultipartFormFiles or Reset must be called after\n// reading multipart/form-data request in order to delete temporarily\n// uploaded files.\n//\n// If MayContinue returns true, the caller must:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (req *Request) ReadLimitBody(r *bufio.Reader, maxBodySize int) error {\n\treq.resetSkipHeader()\n\tif err := req.Header.Read(r); err != nil {\n\t\treturn err\n\t}\n\n\treturn req.readLimitBody(r, maxBodySize, false, true)\n}\n\nfunc (req *Request) readLimitBody(r *bufio.Reader, maxBodySize int, getOnly, preParseMultipartForm bool) error {\n\t// Do not reset the request here - the caller must reset it before\n\t// calling this method.\n\n\tif getOnly && !req.Header.IsGet() && !req.Header.IsHead() {\n\t\treturn ErrGetOnly\n\t}\n\n\tif req.MayContinue() {\n\t\t// 'Expect: 100-continue' header found. Let the caller deciding\n\t\t// whether to read request body or\n\t\t// to return StatusExpectationFailed.\n\t\treturn nil\n\t}\n\n\treturn req.ContinueReadBody(r, maxBodySize, preParseMultipartForm)\n}\n\nfunc (req *Request) readBodyStream(r *bufio.Reader, maxBodySize int, getOnly, preParseMultipartForm bool) error {\n\t// Do not reset the request here - the caller must reset it before\n\t// calling this method.\n\n\tif getOnly && !req.Header.IsGet() && !req.Header.IsHead() {\n\t\treturn ErrGetOnly\n\t}\n\n\tif req.MayContinue() {\n\t\t// 'Expect: 100-continue' header found. Let the caller deciding\n\t\t// whether to read request body or\n\t\t// to return StatusExpectationFailed.\n\t\treturn nil\n\t}\n\n\treturn req.ContinueReadBodyStream(r, maxBodySize, preParseMultipartForm)\n}\n\n// MayContinue returns true if the request contains\n// 'Expect: 100-continue' header.\n//\n// The caller must do one of the following actions if MayContinue returns true:\n//\n//   - Either send StatusExpectationFailed response if request headers don't\n//     satisfy the caller.\n//   - Or send StatusContinue response before reading request body\n//     with ContinueReadBody.\n//   - Or close the connection.\nfunc (req *Request) MayContinue() bool {\n\treturn bytes.Equal(req.Header.peek(strExpect), str100Continue)\n}\n\n// ContinueReadBody reads request body if request header contains\n// 'Expect: 100-continue'.\n//\n// The caller must send StatusContinue response before calling this method.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\nfunc (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int, preParseMultipartForm ...bool) error {\n\tvar err error\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength > 0 {\n\t\tif maxBodySize > 0 && contentLength > maxBodySize {\n\t\t\treturn ErrBodyTooLarge\n\t\t}\n\n\t\tif len(preParseMultipartForm) == 0 || preParseMultipartForm[0] {\n\t\t\t// Pre-read multipart form data of known length.\n\t\t\t// This way we limit memory usage for large file uploads, since their contents\n\t\t\t// is streamed into temporary files if file size exceeds defaultMaxInMemoryFileSize.\n\t\t\treq.multipartFormBoundary = string(req.Header.MultipartFormBoundary())\n\t\t\tif req.multipartFormBoundary != \"\" && len(req.Header.peek(strContentEncoding)) == 0 {\n\t\t\t\treq.multipartForm, err = readMultipartForm(r, req.multipartFormBoundary, contentLength, defaultMaxInMemoryFileSize)\n\t\t\t\tif err != nil {\n\t\t\t\t\treq.Reset()\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif contentLength == -2 {\n\t\t// identity body has no sense for http requests, since\n\t\t// the end of body is determined by connection close.\n\t\t// So just ignore request body for requests without\n\t\t// 'Content-Length' and 'Transfer-Encoding' headers.\n\t\t// refer to https://tools.ietf.org/html/rfc7230#section-3.3.2\n\t\tif !req.Header.ignoreBody() {\n\t\t\treq.Header.SetContentLength(0)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err = req.ReadBody(r, contentLength, maxBodySize); err != nil {\n\t\treturn err\n\t}\n\n\tif contentLength == -1 {\n\t\terr = req.Header.ReadTrailer(r)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ReadBody reads request body from the given r, limiting the body size.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\nfunc (req *Request) ReadBody(r *bufio.Reader, contentLength, maxBodySize int) (err error) {\n\tbodyBuf := req.bodyBuffer()\n\tbodyBuf.Reset()\n\n\tswitch {\n\tcase contentLength >= 0:\n\t\tbodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B)\n\tcase contentLength == -1:\n\t\tbodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B)\n\t\tif err == nil && len(bodyBuf.B) == 0 {\n\t\t\treq.Header.SetContentLength(0)\n\t\t}\n\tdefault:\n\t\tbodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B)\n\t\treq.Header.SetContentLength(len(bodyBuf.B))\n\t}\n\n\tif err != nil {\n\t\treq.Reset()\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ContinueReadBodyStream reads request body if request header contains\n// 'Expect: 100-continue'.\n//\n// The caller must send StatusContinue response before calling this method.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\nfunc (req *Request) ContinueReadBodyStream(r *bufio.Reader, maxBodySize int, preParseMultipartForm ...bool) error {\n\tvar err error\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength > 0 {\n\t\tif len(preParseMultipartForm) == 0 || preParseMultipartForm[0] {\n\t\t\t// Pre-read multipart form data of known length.\n\t\t\t// This way we limit memory usage for large file uploads, since their contents\n\t\t\t// is streamed into temporary files if file size exceeds defaultMaxInMemoryFileSize.\n\t\t\treq.multipartFormBoundary = b2s(req.Header.MultipartFormBoundary())\n\t\t\tif req.multipartFormBoundary != \"\" && len(req.Header.peek(strContentEncoding)) == 0 {\n\t\t\t\treq.multipartForm, err = readMultipartForm(r, req.multipartFormBoundary, contentLength, defaultMaxInMemoryFileSize)\n\t\t\t\tif err != nil {\n\t\t\t\t\treq.Reset()\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif contentLength == -2 {\n\t\t// identity body has no sense for http requests, since\n\t\t// the end of body is determined by connection close.\n\t\t// So just ignore request body for requests without\n\t\t// 'Content-Length' and 'Transfer-Encoding' headers.\n\n\t\t// refer to https://tools.ietf.org/html/rfc7230#section-3.3.2\n\t\tif !req.Header.ignoreBody() {\n\t\t\treq.Header.SetContentLength(0)\n\t\t}\n\t\treturn nil\n\t}\n\n\tbodyBuf := req.bodyBuffer()\n\tbodyBuf.Reset()\n\tbodyBuf.B, err = readBodyWithStreaming(r, contentLength, maxBodySize, bodyBuf.B)\n\tif err != nil {\n\t\tif err == ErrBodyTooLarge {\n\t\t\treq.Header.SetContentLength(contentLength)\n\t\t\treq.body = bodyBuf\n\t\t\treq.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)\n\t\t\treturn nil\n\t\t}\n\t\tif err == errChunkedStream {\n\t\t\treq.body = bodyBuf\n\t\t\treq.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)\n\t\t\treturn nil\n\t\t}\n\t\treq.Reset()\n\t\treturn err\n\t}\n\n\treq.body = bodyBuf\n\treq.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)\n\treq.Header.SetContentLength(contentLength)\n\treturn nil\n}\n\n// Read reads response (including body) from the given r.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (resp *Response) Read(r *bufio.Reader) error {\n\treturn resp.ReadLimitBody(r, 0)\n}\n\n// ReadLimitBody reads response headers from the given r,\n// then reads the body using the ReadBody function and limiting the body size.\n//\n// If resp.SkipBody is true then it skips reading the response body.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\n//\n// io.EOF is returned if r is closed before reading the first header byte.\nfunc (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error {\n\tresp.resetSkipHeader()\n\terr := resp.Header.Read(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif resp.Header.statusCode == StatusContinue {\n\t\t// Read the next response according to http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html .\n\t\tif err = resp.Header.Read(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif !resp.mustSkipBody() {\n\t\terr = resp.ReadBody(r, maxBodySize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// A response without a body can't have trailers.\n\tif resp.Header.ContentLength() == -1 && !resp.StreamBody && !resp.mustSkipBody() {\n\t\terr = resp.Header.ReadTrailer(r)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ReadBody reads response body from the given r, limiting the body size.\n//\n// If maxBodySize > 0 and the body size exceeds maxBodySize,\n// then ErrBodyTooLarge is returned.\nfunc (resp *Response) ReadBody(r *bufio.Reader, maxBodySize int) (err error) {\n\tbodyBuf := resp.bodyBuffer()\n\tbodyBuf.Reset()\n\n\tcontentLength := resp.Header.ContentLength()\n\tswitch {\n\tcase contentLength >= 0:\n\t\tbodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B)\n\t\tif err == ErrBodyTooLarge && resp.StreamBody {\n\t\t\tresp.bodyStream = acquireRequestStream(bodyBuf, r, &resp.Header)\n\t\t\terr = nil\n\t\t}\n\tcase contentLength == -1:\n\t\tif resp.StreamBody {\n\t\t\tresp.bodyStream = acquireRequestStream(bodyBuf, r, &resp.Header)\n\t\t} else {\n\t\t\tbodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B)\n\t\t}\n\tdefault:\n\t\tif resp.StreamBody {\n\t\t\tresp.bodyStream = acquireRequestStream(bodyBuf, r, &resp.Header)\n\t\t} else {\n\t\t\tbodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B)\n\t\t\tresp.Header.SetContentLength(len(bodyBuf.B))\n\t\t}\n\t}\n\tif err == nil && resp.StreamBody && resp.bodyStream == nil {\n\t\tresp.bodyStream = bytes.NewReader(bodyBuf.B)\n\t}\n\treturn err\n}\n\nfunc (resp *Response) mustSkipBody() bool {\n\treturn resp.SkipBody || resp.Header.mustSkipContentLength()\n}\n\nvar errRequestHostRequired = errors.New(\"missing required Host header in request\")\n\n// WriteTo writes request to w. It implements io.WriterTo.\nfunc (req *Request) WriteTo(w io.Writer) (int64, error) {\n\treturn writeBufio(req, w)\n}\n\n// WriteTo writes response to w. It implements io.WriterTo.\nfunc (resp *Response) WriteTo(w io.Writer) (int64, error) {\n\treturn writeBufio(resp, w)\n}\n\nfunc writeBufio(hw httpWriter, w io.Writer) (int64, error) {\n\tsw := acquireStatsWriter(w)\n\tbw := acquireBufioWriter(sw)\n\terrw := hw.Write(bw)\n\terrf := bw.Flush()\n\treleaseBufioWriter(bw)\n\tn := sw.bytesWritten\n\treleaseStatsWriter(sw)\n\n\terr := errw\n\tif err == nil {\n\t\terr = errf\n\t}\n\treturn n, err\n}\n\ntype statsWriter struct {\n\tw            io.Writer\n\tbytesWritten int64\n}\n\nfunc (w *statsWriter) Write(p []byte) (int, error) {\n\tn, err := w.w.Write(p)\n\tw.bytesWritten += int64(n)\n\treturn n, err\n}\n\nfunc (w *statsWriter) WriteString(s string) (int, error) {\n\tn, err := w.w.Write(s2b(s))\n\tw.bytesWritten += int64(n)\n\treturn n, err\n}\n\nfunc acquireStatsWriter(w io.Writer) *statsWriter {\n\tv := statsWriterPool.Get()\n\tif v == nil {\n\t\treturn &statsWriter{\n\t\t\tw: w,\n\t\t}\n\t}\n\tsw := v.(*statsWriter)\n\tsw.w = w\n\treturn sw\n}\n\nfunc releaseStatsWriter(sw *statsWriter) {\n\tsw.w = nil\n\tsw.bytesWritten = 0\n\tstatsWriterPool.Put(sw)\n}\n\nvar statsWriterPool sync.Pool\n\nfunc acquireBufioWriter(w io.Writer) *bufio.Writer {\n\tv := bufioWriterPool.Get()\n\tif v == nil {\n\t\treturn bufio.NewWriter(w)\n\t}\n\tbw := v.(*bufio.Writer)\n\tbw.Reset(w)\n\treturn bw\n}\n\nfunc releaseBufioWriter(bw *bufio.Writer) {\n\tbufioWriterPool.Put(bw)\n}\n\nvar bufioWriterPool sync.Pool\n\nfunc (req *Request) onlyMultipartForm() bool {\n\treturn req.multipartForm != nil && (req.body == nil || len(req.body.B) == 0)\n}\n\n// Write writes request to w.\n//\n// Write doesn't flush request to w for performance reasons.\n//\n// See also WriteTo.\nfunc (req *Request) Write(w *bufio.Writer) error {\n\tif len(req.Header.Host()) == 0 || req.parsedURI {\n\t\turi := req.URI()\n\t\thost := uri.Host()\n\t\tif len(req.Header.Host()) == 0 {\n\t\t\tif len(host) == 0 {\n\t\t\t\treturn errRequestHostRequired\n\t\t\t}\n\t\t\treq.Header.SetHostBytes(host)\n\t\t} else if !req.UseHostHeader {\n\t\t\treq.Header.SetHostBytes(host)\n\t\t}\n\t\treq.Header.SetRequestURIBytes(uri.RequestURI())\n\n\t\tif len(uri.username) > 0 {\n\t\t\t// RequestHeader.SetBytesKV only uses RequestHeader.bufKV.key\n\t\t\t// So we are free to use RequestHeader.bufKV.value as a scratch pad for\n\t\t\t// the base64 encoding.\n\t\t\tnl := len(uri.username) + len(uri.password) + 1\n\t\t\tnb := nl + len(strBasicSpace)\n\t\t\ttl := nb + base64.StdEncoding.EncodedLen(nl)\n\t\t\tif tl > cap(req.Header.bufV) {\n\t\t\t\treq.Header.bufV = make([]byte, 0, tl)\n\t\t\t}\n\t\t\tbuf := req.Header.bufV[:0]\n\t\t\tbuf = append(buf, uri.username...)\n\t\t\tbuf = append(buf, strColon...)\n\t\t\tbuf = append(buf, uri.password...)\n\t\t\tbuf = append(buf, strBasicSpace...)\n\t\t\tbase64.StdEncoding.Encode(buf[nb:tl], buf[:nl])\n\t\t\treq.Header.SetBytesKV(strAuthorization, buf[nl:tl])\n\t\t}\n\t}\n\n\tif req.bodyStream != nil {\n\t\treturn req.writeBodyStream(w)\n\t}\n\n\tbody := req.bodyBytes()\n\tvar err error\n\tif req.onlyMultipartForm() {\n\t\tbody, err = marshalMultipartForm(req.multipartForm, req.multipartFormBoundary)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error when marshaling multipart form: %w\", err)\n\t\t}\n\t\treq.Header.SetMultipartFormBoundary(req.multipartFormBoundary)\n\t}\n\n\thasBody := false\n\tif len(body) == 0 {\n\t\tbody = req.postArgs.QueryString()\n\t}\n\tif len(body) != 0 || !req.Header.ignoreBody() {\n\t\thasBody = true\n\t\treq.Header.SetContentLength(len(body))\n\t}\n\tif err = req.Header.Write(w); err != nil {\n\t\treturn err\n\t}\n\tif hasBody {\n\t\t_, err = w.Write(body)\n\t} else if len(body) > 0 {\n\t\tif req.secureErrorLogMessage {\n\t\t\treturn errors.New(\"non-zero body for non-POST request\")\n\t\t}\n\t\treturn fmt.Errorf(\"non-zero body for non-POST request. body=%q\", body)\n\t}\n\treturn err\n}\n\n// WriteGzip writes response with gzipped body to w.\n//\n// The method gzips response body and sets 'Content-Encoding: gzip'\n// header before writing response to w.\n//\n// WriteGzip doesn't flush response to w for performance reasons.\nfunc (resp *Response) WriteGzip(w *bufio.Writer) error {\n\treturn resp.WriteGzipLevel(w, CompressDefaultCompression)\n}\n\n// WriteGzipLevel writes response with gzipped body to w.\n//\n// Level is the desired compression level:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\n//\n// The method gzips response body and sets 'Content-Encoding: gzip'\n// header before writing response to w.\n//\n// WriteGzipLevel doesn't flush response to w for performance reasons.\nfunc (resp *Response) WriteGzipLevel(w *bufio.Writer, level int) error {\n\tresp.gzipBody(level)\n\treturn resp.Write(w)\n}\n\n// WriteDeflate writes response with deflated body to w.\n//\n// The method deflates response body and sets 'Content-Encoding: deflate'\n// header before writing response to w.\n//\n// WriteDeflate doesn't flush response to w for performance reasons.\nfunc (resp *Response) WriteDeflate(w *bufio.Writer) error {\n\treturn resp.WriteDeflateLevel(w, CompressDefaultCompression)\n}\n\n// WriteDeflateLevel writes response with deflated body to w.\n//\n// Level is the desired compression level:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\n//\n// The method deflates response body and sets 'Content-Encoding: deflate'\n// header before writing response to w.\n//\n// WriteDeflateLevel doesn't flush response to w for performance reasons.\nfunc (resp *Response) WriteDeflateLevel(w *bufio.Writer, level int) error {\n\tresp.deflateBody(level)\n\treturn resp.Write(w)\n}\n\nfunc (resp *Response) brotliBody(level int) {\n\tif len(resp.Header.ContentEncoding()) > 0 {\n\t\t// It looks like the body is already compressed.\n\t\t// Do not compress it again.\n\t\treturn\n\t}\n\n\tif !resp.Header.isCompressibleContentType() {\n\t\t// The content-type cannot be compressed.\n\t\treturn\n\t}\n\n\tif resp.bodyStream != nil {\n\t\t// Reset Content-Length to -1, since it is impossible\n\t\t// to determine body size beforehand of streamed compression.\n\t\t// For https://github.com/valyala/fasthttp/issues/176 .\n\t\tresp.Header.SetContentLength(-1)\n\n\t\t// Do not care about memory allocations here, since brotli is slow\n\t\t// and allocates a lot of memory by itself.\n\t\tbs := resp.bodyStream\n\t\tresp.bodyStream = NewStreamReader(func(sw *bufio.Writer) {\n\t\t\tzw := acquireStacklessBrotliWriter(sw, level)\n\t\t\tfw := &flushWriter{\n\t\t\t\twf: zw,\n\t\t\t\tbw: sw,\n\t\t\t}\n\t\t\t_, wErr := copyZeroAlloc(fw, bs)\n\t\t\treleaseStacklessBrotliWriter(zw, level)\n\t\t\tswitch v := bs.(type) {\n\t\t\tcase io.Closer:\n\t\t\t\tv.Close()\n\t\t\tcase ReadCloserWithError:\n\t\t\t\tv.CloseWithError(wErr) //nolint:errcheck\n\t\t\t}\n\t\t})\n\t} else {\n\t\tbodyBytes := resp.bodyBytes()\n\t\tif len(bodyBytes) < minCompressLen {\n\t\t\t// There is no sense in spending CPU time on small body compression,\n\t\t\t// since there is a very high probability that the compressed\n\t\t\t// body size will be bigger than the original body size.\n\t\t\treturn\n\t\t}\n\t\tw := responseBodyPool.Get()\n\t\tw.B = AppendBrotliBytesLevel(w.B, bodyBytes, level)\n\n\t\t// Hack: swap resp.body with w.\n\t\tif resp.body != nil {\n\t\t\tresponseBodyPool.Put(resp.body)\n\t\t}\n\t\tresp.body = w\n\t\tresp.bodyRaw = nil\n\t}\n\tresp.Header.SetContentEncodingBytes(strBr)\n\tresp.Header.addVaryBytes(strAcceptEncoding)\n}\n\nfunc (resp *Response) gzipBody(level int) {\n\tif len(resp.Header.ContentEncoding()) > 0 {\n\t\t// It looks like the body is already compressed.\n\t\t// Do not compress it again.\n\t\treturn\n\t}\n\n\tif !resp.Header.isCompressibleContentType() {\n\t\t// The content-type cannot be compressed.\n\t\treturn\n\t}\n\n\tif resp.bodyStream != nil {\n\t\t// Reset Content-Length to -1, since it is impossible\n\t\t// to determine body size beforehand of streamed compression.\n\t\t// For https://github.com/valyala/fasthttp/issues/176 .\n\t\tresp.Header.SetContentLength(-1)\n\n\t\t// Do not care about memory allocations here, since gzip is slow\n\t\t// and allocates a lot of memory by itself.\n\t\tbs := resp.bodyStream\n\t\tresp.bodyStream = NewStreamReader(func(sw *bufio.Writer) {\n\t\t\tzw := acquireStacklessGzipWriter(sw, level)\n\t\t\tfw := &flushWriter{\n\t\t\t\twf: zw,\n\t\t\t\tbw: sw,\n\t\t\t}\n\t\t\t_, wErr := copyZeroAlloc(fw, bs)\n\t\t\treleaseStacklessGzipWriter(zw, level)\n\t\t\tswitch v := bs.(type) {\n\t\t\tcase io.Closer:\n\t\t\t\tv.Close()\n\t\t\tcase ReadCloserWithError:\n\t\t\t\tv.CloseWithError(wErr) //nolint:errcheck\n\t\t\t}\n\t\t})\n\t} else {\n\t\tbodyBytes := resp.bodyBytes()\n\t\tif len(bodyBytes) < minCompressLen {\n\t\t\t// There is no sense in spending CPU time on small body compression,\n\t\t\t// since there is a very high probability that the compressed\n\t\t\t// body size will be bigger than the original body size.\n\t\t\treturn\n\t\t}\n\t\tw := responseBodyPool.Get()\n\t\tw.B = AppendGzipBytesLevel(w.B, bodyBytes, level)\n\n\t\t// Hack: swap resp.body with w.\n\t\tif resp.body != nil {\n\t\t\tresponseBodyPool.Put(resp.body)\n\t\t}\n\t\tresp.body = w\n\t\tresp.bodyRaw = nil\n\t}\n\tresp.Header.SetContentEncodingBytes(strGzip)\n\tresp.Header.addVaryBytes(strAcceptEncoding)\n}\n\nfunc (resp *Response) deflateBody(level int) {\n\tif len(resp.Header.ContentEncoding()) > 0 {\n\t\t// It looks like the body is already compressed.\n\t\t// Do not compress it again.\n\t\treturn\n\t}\n\n\tif !resp.Header.isCompressibleContentType() {\n\t\t// The content-type cannot be compressed.\n\t\treturn\n\t}\n\n\tif resp.bodyStream != nil {\n\t\t// Reset Content-Length to -1, since it is impossible\n\t\t// to determine body size beforehand of streamed compression.\n\t\t// For https://github.com/valyala/fasthttp/issues/176 .\n\t\tresp.Header.SetContentLength(-1)\n\n\t\t// Do not care about memory allocations here, since flate is slow\n\t\t// and allocates a lot of memory by itself.\n\t\tbs := resp.bodyStream\n\t\tresp.bodyStream = NewStreamReader(func(sw *bufio.Writer) {\n\t\t\tzw := acquireStacklessDeflateWriter(sw, level)\n\t\t\tfw := &flushWriter{\n\t\t\t\twf: zw,\n\t\t\t\tbw: sw,\n\t\t\t}\n\t\t\t_, wErr := copyZeroAlloc(fw, bs)\n\t\t\treleaseStacklessDeflateWriter(zw, level)\n\t\t\tswitch v := bs.(type) {\n\t\t\tcase io.Closer:\n\t\t\t\tv.Close()\n\t\t\tcase ReadCloserWithError:\n\t\t\t\tv.CloseWithError(wErr) //nolint:errcheck\n\t\t\t}\n\t\t})\n\t} else {\n\t\tbodyBytes := resp.bodyBytes()\n\t\tif len(bodyBytes) < minCompressLen {\n\t\t\t// There is no sense in spending CPU time on small body compression,\n\t\t\t// since there is a very high probability that the compressed\n\t\t\t// body size will be bigger than the original body size.\n\t\t\treturn\n\t\t}\n\t\tw := responseBodyPool.Get()\n\t\tw.B = AppendDeflateBytesLevel(w.B, bodyBytes, level)\n\n\t\t// Hack: swap resp.body with w.\n\t\tif resp.body != nil {\n\t\t\tresponseBodyPool.Put(resp.body)\n\t\t}\n\t\tresp.body = w\n\t\tresp.bodyRaw = nil\n\t}\n\tresp.Header.SetContentEncodingBytes(strDeflate)\n\tresp.Header.addVaryBytes(strAcceptEncoding)\n}\n\nfunc (resp *Response) zstdBody(level int) {\n\tif len(resp.Header.ContentEncoding()) > 0 {\n\t\treturn\n\t}\n\n\tif !resp.Header.isCompressibleContentType() {\n\t\treturn\n\t}\n\n\tif resp.bodyStream != nil {\n\t\t// Reset Content-Length to -1, since it is impossible\n\t\t// to determine body size beforehand of streamed compression.\n\t\t// For\n\t\tresp.Header.SetContentLength(-1)\n\n\t\t// Do not care about memory allocations here, since flate is slow\n\t\t// and allocates a lot of memory by itself.\n\t\tbs := resp.bodyStream\n\t\tresp.bodyStream = NewStreamReader(func(sw *bufio.Writer) {\n\t\t\tzw := acquireStacklessZstdWriter(sw, level)\n\t\t\tfw := &flushWriter{\n\t\t\t\twf: zw,\n\t\t\t\tbw: sw,\n\t\t\t}\n\t\t\t_, wErr := copyZeroAlloc(fw, bs)\n\t\t\treleaseStacklessZstdWriter(zw, level)\n\t\t\tswitch v := bs.(type) {\n\t\t\tcase io.Closer:\n\t\t\t\tv.Close()\n\t\t\tcase ReadCloserWithError:\n\t\t\t\tv.CloseWithError(wErr) //nolint:errcheck\n\t\t\t}\n\t\t})\n\t} else {\n\t\tbodyBytes := resp.bodyBytes()\n\t\tif len(bodyBytes) < minCompressLen {\n\t\t\treturn\n\t\t}\n\t\tw := responseBodyPool.Get()\n\t\tw.B = AppendZstdBytesLevel(w.B, bodyBytes, level)\n\n\t\tif resp.body != nil {\n\t\t\tresponseBodyPool.Put(resp.body)\n\t\t}\n\t\tresp.body = w\n\t\tresp.bodyRaw = nil\n\t}\n\tresp.Header.SetContentEncodingBytes(strZstd)\n\tresp.Header.addVaryBytes(strAcceptEncoding)\n}\n\n// Bodies with sizes smaller than minCompressLen aren't compressed at all.\nconst minCompressLen = 200\n\ntype writeFlusher interface {\n\tio.Writer\n\tFlush() error\n}\n\ntype flushWriter struct {\n\twf writeFlusher\n\tbw *bufio.Writer\n}\n\nfunc (w *flushWriter) Write(p []byte) (int, error) {\n\tn, err := w.wf.Write(p)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif err = w.wf.Flush(); err != nil {\n\t\treturn 0, err\n\t}\n\tif err = w.bw.Flush(); err != nil {\n\t\treturn 0, err\n\t}\n\treturn n, nil\n}\n\nfunc (w *flushWriter) WriteString(s string) (int, error) {\n\treturn w.Write(s2b(s))\n}\n\n// Write writes response to w.\n//\n// Write doesn't flush response to w for performance reasons.\n//\n// See also WriteTo.\nfunc (resp *Response) Write(w *bufio.Writer) error {\n\tsendBody := !resp.mustSkipBody()\n\n\tif resp.bodyStream != nil {\n\t\treturn resp.writeBodyStream(w, sendBody)\n\t}\n\n\tbody := resp.bodyBytes()\n\tbodyLen := len(body)\n\tif sendBody || bodyLen > 0 {\n\t\tresp.Header.SetContentLength(bodyLen)\n\t}\n\tif err := resp.Header.Write(w); err != nil {\n\t\treturn err\n\t}\n\tif sendBody {\n\t\tif _, err := w.Write(body); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (req *Request) writeBodyStream(w *bufio.Writer) error {\n\tvar err error\n\n\tcontentLength := req.Header.ContentLength()\n\tif contentLength < 0 {\n\t\tlrSize := limitedReaderSize(req.bodyStream)\n\t\tif lrSize >= 0 {\n\t\t\tcontentLength = int(lrSize)\n\t\t\tif int64(contentLength) != lrSize {\n\t\t\t\tcontentLength = -1\n\t\t\t}\n\t\t\tif contentLength >= 0 {\n\t\t\t\treq.Header.SetContentLength(contentLength)\n\t\t\t}\n\t\t}\n\t}\n\tif contentLength >= 0 {\n\t\tif err = req.Header.Write(w); err == nil {\n\t\t\terr = writeBodyFixedSize(w, req.bodyStream, int64(contentLength))\n\t\t}\n\t} else {\n\t\treq.Header.SetContentLength(-1)\n\t\terr = req.Header.Write(w)\n\t\tif err == nil {\n\t\t\terr = writeBodyChunked(w, req.bodyStream)\n\t\t}\n\t\tif err == nil {\n\t\t\terr = req.Header.writeTrailer(w)\n\t\t}\n\t}\n\terrc := req.closeBodyStream()\n\tif err == nil {\n\t\terr = errc\n\t}\n\treturn err\n}\n\n// ErrBodyStreamWritePanic is returned when panic happens during writing body stream.\ntype ErrBodyStreamWritePanic struct {\n\terror\n}\n\nfunc (resp *Response) writeBodyStream(w *bufio.Writer, sendBody bool) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = &ErrBodyStreamWritePanic{\n\t\t\t\terror: fmt.Errorf(\"panic while writing body stream: %+v\", r),\n\t\t\t}\n\t\t}\n\t}()\n\n\tcontentLength := resp.Header.ContentLength()\n\tif contentLength < 0 {\n\t\tlrSize := limitedReaderSize(resp.bodyStream)\n\t\tif lrSize >= 0 {\n\t\t\tcontentLength = int(lrSize)\n\t\t\tif int64(contentLength) != lrSize {\n\t\t\t\tcontentLength = -1\n\t\t\t}\n\t\t\tif contentLength >= 0 {\n\t\t\t\tresp.Header.SetContentLength(contentLength)\n\t\t\t}\n\t\t}\n\t}\n\tif contentLength >= 0 {\n\t\tif err = resp.Header.Write(w); err == nil {\n\t\t\tif resp.ImmediateHeaderFlush {\n\t\t\t\terr = w.Flush()\n\t\t\t}\n\t\t\tif err == nil && sendBody {\n\t\t\t\terr = writeBodyFixedSize(w, resp.bodyStream, int64(contentLength))\n\t\t\t}\n\t\t}\n\t} else {\n\t\tresp.Header.SetContentLength(-1)\n\t\tif err = resp.Header.Write(w); err == nil {\n\t\t\tif resp.ImmediateHeaderFlush {\n\t\t\t\terr = w.Flush()\n\t\t\t}\n\t\t\tif err == nil && sendBody {\n\t\t\t\terr = writeBodyChunked(w, resp.bodyStream)\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\terr = resp.Header.writeTrailer(w)\n\t\t\t}\n\t\t}\n\t}\n\terrc := resp.closeBodyStream(err)\n\tif err == nil {\n\t\terr = errc\n\t}\n\treturn err\n}\n\nfunc (req *Request) closeBodyStream() error {\n\tif req.bodyStream == nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tif bsc, ok := req.bodyStream.(io.Closer); ok {\n\t\terr = bsc.Close()\n\t}\n\tif rs, ok := req.bodyStream.(*requestStream); ok {\n\t\treleaseRequestStream(rs)\n\t}\n\treq.bodyStream = nil\n\treturn err\n}\n\nfunc (resp *Response) closeBodyStream(wErr error) error {\n\tif resp.bodyStream == nil {\n\t\treturn nil\n\t}\n\tvar err error\n\tif bsc, ok := resp.bodyStream.(io.Closer); ok {\n\t\terr = bsc.Close()\n\t}\n\tif bsc, ok := resp.bodyStream.(ReadCloserWithError); ok {\n\t\terr = bsc.CloseWithError(wErr)\n\t}\n\tif bsr, ok := resp.bodyStream.(*requestStream); ok {\n\t\treleaseRequestStream(bsr)\n\t}\n\tresp.bodyStream = nil\n\treturn err\n}\n\n// String returns request representation.\n//\n// Returns error message instead of request representation on error.\n//\n// Use Write instead of String for performance-critical code.\nfunc (req *Request) String() string {\n\treturn getHTTPString(req)\n}\n\n// String returns response representation.\n//\n// Returns error message instead of response representation on error.\n//\n// Use Write instead of String for performance-critical code.\nfunc (resp *Response) String() string {\n\treturn getHTTPString(resp)\n}\n\n// SetUserValue stores the given value (arbitrary object)\n// under the given key in Request.\n//\n// The value stored in Request may be obtained by UserValue*.\n//\n// This functionality may be useful for passing arbitrary values between\n// functions involved in request processing.\n//\n// All the values are removed from Request after returning from the top\n// RequestHandler. Additionally, Close method is called on each value\n// implementing io.Closer before removing the value from Request.\nfunc (req *Request) SetUserValue(key, value any) {\n\treq.userValues.Set(key, value)\n}\n\n// SetUserValueBytes stores the given value (arbitrary object)\n// under the given key in Request.\n//\n// The value stored in Request may be obtained by UserValue*.\n//\n// This functionality may be useful for passing arbitrary values between\n// functions involved in request processing.\n//\n// All the values stored in Request are deleted after returning from RequestHandler.\nfunc (req *Request) SetUserValueBytes(key []byte, value any) {\n\treq.userValues.SetBytes(key, value)\n}\n\n// UserValue returns the value stored via SetUserValue* under the given key.\nfunc (req *Request) UserValue(key any) any {\n\treturn req.userValues.Get(key)\n}\n\n// UserValueBytes returns the value stored via SetUserValue*\n// under the given key.\nfunc (req *Request) UserValueBytes(key []byte) any {\n\treturn req.userValues.GetBytes(key)\n}\n\n// VisitUserValues calls visitor for each existing userValue with a key that is a string or []byte.\n//\n// visitor must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (req *Request) VisitUserValues(visitor func([]byte, any)) {\n\tfor i, n := 0, len(req.userValues); i < n; i++ {\n\t\tkv := &req.userValues[i]\n\t\tif _, ok := kv.key.(string); ok {\n\t\t\tvisitor(s2b(kv.key.(string)), kv.value)\n\t\t}\n\t}\n}\n\n// VisitUserValuesAll calls visitor for each existing userValue.\n//\n// visitor must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (req *Request) VisitUserValuesAll(visitor func(any, any)) {\n\tfor i, n := 0, len(req.userValues); i < n; i++ {\n\t\tkv := &req.userValues[i]\n\t\tvisitor(kv.key, kv.value)\n\t}\n}\n\n// ResetUserValues allows to reset user values from Request Context.\nfunc (req *Request) ResetUserValues() {\n\treq.userValues.Reset()\n}\n\n// RemoveUserValue removes the given key and the value under it in Request.\nfunc (req *Request) RemoveUserValue(key any) {\n\treq.userValues.Remove(key)\n}\n\n// RemoveUserValueBytes removes the given key and the value under it in Request.\nfunc (req *Request) RemoveUserValueBytes(key []byte) {\n\treq.userValues.RemoveBytes(key)\n}\n\nfunc getHTTPString(hw httpWriter) string {\n\tw := bytebufferpool.Get()\n\tdefer bytebufferpool.Put(w)\n\n\tbw := bufio.NewWriter(w)\n\tif err := hw.Write(bw); err != nil {\n\t\treturn err.Error()\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\treturn err.Error()\n\t}\n\ts := string(w.B)\n\treturn s\n}\n\ntype httpWriter interface {\n\tWrite(w *bufio.Writer) error\n}\n\nfunc writeBodyChunked(w *bufio.Writer, r io.Reader) error {\n\tvbuf := copyBufPool.Get()\n\tbuf := vbuf.([]byte)\n\n\tvar err error\n\tvar n int\n\tfor {\n\t\tn, err = r.Read(buf)\n\t\tif n == 0 {\n\t\t\tif err == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err == io.EOF {\n\t\t\t\tif err = writeChunk(w, buf[:0]); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif err = writeChunk(w, buf[:n]); err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tcopyBufPool.Put(vbuf)\n\treturn err\n}\n\nfunc limitedReaderSize(r io.Reader) int64 {\n\tlr, ok := r.(*io.LimitedReader)\n\tif !ok {\n\t\treturn -1\n\t}\n\treturn lr.N\n}\n\nfunc writeBodyFixedSize(w *bufio.Writer, r io.Reader, size int64) error {\n\tif size > maxSmallFileSize {\n\t\tearlyFlush := false\n\t\tswitch r := r.(type) {\n\t\tcase *os.File:\n\t\t\tearlyFlush = true\n\t\tcase *io.LimitedReader:\n\t\t\t_, earlyFlush = r.R.(*os.File)\n\t\t}\n\t\tif earlyFlush {\n\t\t\t// w buffer must be empty for triggering\n\t\t\t// sendfile path in bufio.Writer.ReadFrom.\n\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tn, err := copyZeroAlloc(w, r)\n\n\tif n != size && err == nil {\n\t\terr = fmt.Errorf(\"copied %d bytes from body stream instead of %d bytes\", n, size)\n\t}\n\treturn err\n}\n\n// copyZeroAlloc optimizes io.Copy by calling ReadFrom or WriteTo only when\n// copying between os.File and net.TCPConn. If the reader has a WriteTo\n// method, it uses WriteTo for copying; if the writer has a ReadFrom method,\n// it uses ReadFrom for copying. If neither method is available, it gets a\n// buffer from sync.Pool to perform the copy.\n//\n// io.CopyBuffer always uses the WriterTo or ReadFrom interface if it's\n// available. however, os.File and net.TCPConn unfortunately have a\n// fallback in their WriterTo that calls io.Copy if sendfile isn't possible.\n//\n// See issue: https://github.com/valyala/fasthttp/issues/1889\n//\n// sendfile can only be triggered when copying between os.File and net.TCPConn.\n// Since the function confirming zero-copy is a private function, we use\n// ReadFrom only in this specific scenario. For all other cases, we prioritize\n// using our own copyBuffer method.\n//\n// o: our copyBuffer\n// r: readFrom\n// w: writeTo\n//\n// write\\read *File  *TCPConn  writeTo  other\n// *File        o       r         w       o\n// *TCPConn    w,r      o         w       o\n// readFrom     r       r         w       r\n// other        o       o         w       o\n//\n//nolint:dupword\nfunc copyZeroAlloc(w io.Writer, r io.Reader) (int64, error) {\n\tvar readerIsFile, readerIsConn bool\n\n\tswitch r := r.(type) {\n\tcase *os.File:\n\t\treaderIsFile = true\n\tcase *net.TCPConn:\n\t\treaderIsConn = true\n\tcase io.WriterTo:\n\t\treturn r.WriteTo(w)\n\t}\n\n\tswitch w := w.(type) {\n\tcase *os.File:\n\t\tif readerIsConn {\n\t\t\treturn w.ReadFrom(r)\n\t\t}\n\tcase *net.TCPConn:\n\t\tif readerIsFile {\n\t\t\t// net.WriteTo requires go1.22 or later\n\t\t\t// Benchmark tests show that on Windows, WriteTo performs\n\t\t\t// significantly better than ReadFrom. On Linux, however,\n\t\t\t// ReadFrom slightly outperforms WriteTo. When possible,\n\t\t\t// copyZeroAlloc aims to perform  better than or as well\n\t\t\t// as io.Copy, so we use WriteTo whenever possible for\n\t\t\t// optimal performance.\n\t\t\tif rt, ok := r.(io.WriterTo); ok {\n\t\t\t\treturn rt.WriteTo(w)\n\t\t\t}\n\t\t\treturn w.ReadFrom(r)\n\t\t}\n\tcase io.ReaderFrom:\n\t\treturn w.ReadFrom(r)\n\t}\n\n\tvbuf := copyBufPool.Get()\n\tbuf := vbuf.([]byte)\n\tn, err := copyBuffer(w, r, buf)\n\tcopyBufPool.Put(vbuf)\n\treturn n, err\n}\n\n// copyBuffer is rewritten from io.copyBuffer. We do not check if src has a\n// WriteTo method, if dst has a ReadFrom method, or if buf is empty.\nfunc copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {\n\tfor {\n\t\tnr, er := src.Read(buf)\n\t\tif nr > 0 {\n\t\t\tnw, ew := dst.Write(buf[0:nr])\n\t\t\tif nw < 0 || nr < nw {\n\t\t\t\tnw = 0\n\t\t\t\tif ew == nil {\n\t\t\t\t\tew = errors.New(\"invalid write result\")\n\t\t\t\t}\n\t\t\t}\n\t\t\twritten += int64(nw)\n\t\t\tif ew != nil {\n\t\t\t\terr = ew\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif nr != nw {\n\t\t\t\terr = io.ErrShortWrite\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif er != nil {\n\t\t\tif er != io.EOF {\n\t\t\t\terr = er\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn written, err\n}\n\nvar copyBufPool = sync.Pool{\n\tNew: func() any {\n\t\treturn make([]byte, 4096)\n\t},\n}\n\nfunc writeChunk(w *bufio.Writer, b []byte) error {\n\tn := len(b)\n\tif err := writeHexInt(w, n); err != nil {\n\t\treturn err\n\t}\n\tif _, err := w.Write(strCRLF); err != nil {\n\t\treturn err\n\t}\n\tif _, err := w.Write(b); err != nil {\n\t\treturn err\n\t}\n\t// If is end chunk, write CRLF after writing trailer\n\tif n > 0 {\n\t\tif _, err := w.Write(strCRLF); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn w.Flush()\n}\n\n// ErrBodyTooLarge is returned if either request or response body exceeds\n// the given limit.\nvar ErrBodyTooLarge = errors.New(\"body size exceeds the given limit\")\n\nfunc copyZeroAllocWithLimit(w io.Writer, r io.Reader, maxBodySize int) (int64, error) {\n\tif maxBodySize <= 0 {\n\t\treturn copyZeroAlloc(w, r)\n\t}\n\n\tlr := &io.LimitedReader{\n\t\tR: r,\n\t\tN: int64(maxBodySize) + 1,\n\t}\n\tn, err := copyZeroAlloc(w, lr)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\tif lr.N <= 0 {\n\t\treturn n, ErrBodyTooLarge\n\t}\n\treturn n, nil\n}\n\nfunc readBody(r *bufio.Reader, contentLength, maxBodySize int, dst []byte) ([]byte, error) {\n\tif maxBodySize > 0 && contentLength > maxBodySize {\n\t\treturn dst, ErrBodyTooLarge\n\t}\n\treturn appendBodyFixedSize(r, dst, contentLength)\n}\n\nvar errChunkedStream = errors.New(\"chunked stream\")\n\nfunc readBodyWithStreaming(r *bufio.Reader, contentLength, maxBodySize int, dst []byte) (b []byte, err error) {\n\tif contentLength == -1 {\n\t\t// handled in requestStream.Read()\n\t\treturn b, errChunkedStream\n\t}\n\n\tdst = dst[:0]\n\n\treadN := min(maxBodySize, contentLength)\n\treadN = min(readN, 8*1024)\n\n\t// A fixed-length pre-read function should be used here; otherwise,\n\t// it may read content beyond the request body into areas outside\n\t// the br buffer. This could affect the handling of the next request\n\t// in the br buffer, if there is one. The original two branches can\n\t// be handled with this single branch. by the way,\n\t// fix issue: https://github.com/valyala/fasthttp/issues/1816\n\tb, err = appendBodyFixedSize(r, dst, readN)\n\tif err != nil {\n\t\treturn b, err\n\t}\n\tif contentLength > maxBodySize {\n\t\treturn b, ErrBodyTooLarge\n\t}\n\treturn b, nil\n}\n\nfunc readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {\n\tdst = dst[:cap(dst)]\n\tif len(dst) == 0 {\n\t\tdst = make([]byte, 1024)\n\t}\n\toffset := 0\n\tfor {\n\t\tnn, err := r.Read(dst[offset:])\n\t\tif nn <= 0 {\n\t\t\tswitch {\n\t\t\tcase errors.Is(err, io.EOF):\n\t\t\t\treturn dst[:offset], nil\n\t\t\tcase err != nil:\n\t\t\t\treturn dst[:offset], err\n\t\t\tdefault:\n\t\t\t\treturn dst[:offset], fmt.Errorf(\"bufio.Read() returned (%d, nil)\", nn)\n\t\t\t}\n\t\t}\n\t\toffset += nn\n\t\tif maxBodySize > 0 && offset > maxBodySize {\n\t\t\treturn dst[:offset], ErrBodyTooLarge\n\t\t}\n\t\tif len(dst) == offset {\n\t\t\tn := roundUpForSliceCap(2 * offset)\n\t\t\tif maxBodySize > 0 && n > maxBodySize {\n\t\t\t\tn = maxBodySize + 1\n\t\t\t}\n\t\t\tb := make([]byte, n)\n\t\t\tcopy(b, dst)\n\t\t\tdst = b\n\t\t}\n\t}\n}\n\nfunc appendBodyFixedSize(r *bufio.Reader, dst []byte, n int) ([]byte, error) {\n\tif n == 0 {\n\t\treturn dst, nil\n\t}\n\n\toffset := len(dst)\n\tdstLen := offset + n\n\tif cap(dst) < dstLen {\n\t\tb := make([]byte, roundUpForSliceCap(dstLen))\n\t\tcopy(b, dst)\n\t\tdst = b\n\t}\n\tdst = dst[:dstLen]\n\n\tfor {\n\t\tnn, err := r.Read(dst[offset:])\n\t\tif nn <= 0 {\n\t\t\tswitch {\n\t\t\tcase errors.Is(err, io.EOF):\n\t\t\t\treturn dst[:offset], io.ErrUnexpectedEOF\n\t\t\tcase err != nil:\n\t\t\t\treturn dst[:offset], err\n\t\t\tdefault:\n\t\t\t\treturn dst[:offset], fmt.Errorf(\"bufio.Read() returned (%d, nil)\", nn)\n\t\t\t}\n\t\t}\n\t\toffset += nn\n\t\tif offset == dstLen {\n\t\t\treturn dst, nil\n\t\t}\n\t}\n}\n\n// ErrBrokenChunk is returned when server receives a broken chunked body (Transfer-Encoding: chunked).\ntype ErrBrokenChunk struct {\n\terror\n}\n\nfunc readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {\n\tif len(dst) > 0 {\n\t\t// data integrity might be in danger. No idea what we received,\n\t\t// but nothing we should write to.\n\t\tpanic(\"BUG: expected zero-length buffer\")\n\t}\n\n\tstrCRLFLen := len(strCRLF)\n\tfor {\n\t\tchunkSize, err := parseChunkSize(r)\n\t\tif err != nil {\n\t\t\treturn dst, err\n\t\t}\n\t\tif chunkSize == 0 {\n\t\t\treturn dst, err\n\t\t}\n\t\tif maxBodySize > 0 && len(dst)+chunkSize > maxBodySize {\n\t\t\treturn dst, ErrBodyTooLarge\n\t\t}\n\t\tdst, err = appendBodyFixedSize(r, dst, chunkSize+strCRLFLen)\n\t\tif err != nil {\n\t\t\treturn dst, err\n\t\t}\n\t\tif !bytes.Equal(dst[len(dst)-strCRLFLen:], strCRLF) {\n\t\t\treturn dst, ErrBrokenChunk{\n\t\t\t\terror: errors.New(\"cannot find crlf at the end of chunk\"),\n\t\t\t}\n\t\t}\n\t\tdst = dst[:len(dst)-strCRLFLen]\n\t}\n}\n\nfunc parseChunkSize(r *bufio.Reader) (int, error) {\n\tn, err := readHexInt(r)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tfor {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\terror: fmt.Errorf(\"cannot read '\\\\r' char at the end of chunk size: %w\", err),\n\t\t\t}\n\t\t}\n\t\t// Skip chunk extension after chunk size.\n\t\t// Add support later if anyone needs it.\n\t\tif c != '\\r' {\n\t\t\t// Security: Don't allow newlines in chunk extensions.\n\t\t\t// This can lead to request smuggling issues with some reverse proxies.\n\t\t\tif c == '\\n' {\n\t\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\t\terror: errors.New(\"invalid character '\\\\n' after chunk size\"),\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err := r.UnreadByte(); err != nil {\n\t\t\treturn -1, ErrBrokenChunk{\n\t\t\t\terror: fmt.Errorf(\"cannot unread '\\\\r' char at the end of chunk size: %w\", err),\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\terr = readCrLf(r)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\treturn n, nil\n}\n\nfunc readCrLf(r *bufio.Reader) error {\n\tfor _, exp := range []byte{'\\r', '\\n'} {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\treturn ErrBrokenChunk{\n\t\t\t\terror: fmt.Errorf(\"cannot read %q char at the end of chunk size: %w\", exp, err),\n\t\t\t}\n\t\t}\n\t\tif c != exp {\n\t\t\treturn ErrBrokenChunk{\n\t\t\t\terror: fmt.Errorf(\"unexpected char %q at the end of chunk size. Expected %q\", c, exp),\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// SetTimeout sets timeout for the request.\n//\n// The following code:\n//\n//\treq.SetTimeout(t)\n//\tc.Do(&req, &resp)\n//\n// is equivalent to\n//\n//\tc.DoTimeout(&req, &resp, t)\nfunc (req *Request) SetTimeout(t time.Duration) {\n\treq.timeout = t\n}\n"
  },
  {
    "path": "http_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\nfunc TestInvalidTrailers(t *testing.T) {\n\tt.Parallel()\n\n\tif err := (&Response{}).Read(bufio.NewReader(strings.NewReader(\" 0\\nTransfer-Encoding:\\xff\\n\\n0\\r\\n0\"))); !errors.Is(err, io.EOF) {\n\t\tt.Fatalf(\"%#v\", err)\n\t}\n\tif err := (&Response{}).Read(bufio.NewReader(strings.NewReader(\"\\xff \\nTRaILeR:,\\n\\n\"))); !errors.Is(err, errEmptyInt) {\n\t\tt.Fatal(err)\n\t}\n\tif err := (&Response{}).Read(bufio.NewReader(strings.NewReader(\"TRaILeR:,\\n\\n\"))); !strings.Contains(err.Error(), \"cannot find whitespace in the first line of response\") {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestResponseEmptyTransferEncoding(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tbody := \"Some body\"\n\tbr := bufio.NewReader(bytes.NewBufferString(\"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nTransfer-Encoding: \\r\\nContent-Length: 9\\r\\n\\r\\n\" + body))\n\terr := r.Read(br)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got := string(r.Body()); got != body {\n\t\tt.Fatalf(\"expected %q got %q\", body, got)\n\t}\n}\n\n// Don't send the fragment/hash/# part of a URL to the server.\nfunc TestFragmentInURIRequest(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\treq.SetRequestURI(\"https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#events\")\n\n\tvar b bytes.Buffer\n\treq.WriteTo(&b) //nolint:errcheck\n\tgot := b.String()\n\texpected := \"GET /ee/user/project/integrations/webhooks.html HTTP/1.1\\r\\nHost: docs.gitlab.com\\r\\n\\r\\n\"\n\n\tif got != expected {\n\t\tt.Errorf(\"got %q expected %q\", got, expected)\n\t}\n}\n\nfunc TestIssue875(t *testing.T) {\n\tt.Parallel()\n\n\ttype testcase struct {\n\t\turi              string\n\t\texpectedRedirect string\n\t\texpectedLocation string\n\t}\n\n\ttestcases := []testcase{\n\t\t{\n\t\t\turi:              `http://localhost:3000/?redirect=foo%0d%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`,\n\t\t\texpectedRedirect: \"foo\\r\\nSet-Cookie: SESSIONID=MaliciousValue\\r\\n\",\n\t\t\texpectedLocation: \"Location: foo  Set-Cookie: SESSIONID=MaliciousValue\",\n\t\t},\n\t\t{\n\t\t\turi:              `http://localhost:3000/?redirect=foo%0dSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`,\n\t\t\texpectedRedirect: \"foo\\rSet-Cookie: SESSIONID=MaliciousValue\\r\\n\",\n\t\t\texpectedLocation: \"Location: foo Set-Cookie: SESSIONID=MaliciousValue\",\n\t\t},\n\t\t{\n\t\t\turi:              `http://localhost:3000/?redirect=foo%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`,\n\t\t\texpectedRedirect: \"foo\\nSet-Cookie: SESSIONID=MaliciousValue\\r\\n\",\n\t\t\texpectedLocation: \"Location: foo Set-Cookie: SESSIONID=MaliciousValue\",\n\t\t},\n\t}\n\n\tfor i, tcase := range testcases {\n\t\tcaseName := strconv.FormatInt(int64(i), 10)\n\t\tt.Run(caseName, func(subT *testing.T) {\n\t\t\tctx := &RequestCtx{\n\t\t\t\tRequest:  Request{},\n\t\t\t\tResponse: Response{},\n\t\t\t}\n\t\t\tctx.Request.SetRequestURI(tcase.uri)\n\n\t\t\tq := string(ctx.QueryArgs().Peek(\"redirect\"))\n\t\t\tif q != tcase.expectedRedirect {\n\t\t\t\tsubT.Errorf(\"unexpected redirect query value, got: %+v\", q)\n\t\t\t}\n\t\t\tctx.Response.Header.Set(\"Location\", q)\n\n\t\t\tif !strings.Contains(ctx.Response.String(), tcase.expectedLocation) {\n\t\t\t\tsubT.Errorf(\"invalid escaping, got\\n%q\", ctx.Response.String())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRequestCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\t// empty copy\n\ttestRequestCopyTo(t, &req)\n\n\t// init\n\texpectedContentType := \"application/x-www-form-urlencoded; charset=UTF-8\"\n\texpectedHost := \"test.com\"\n\texpectedBody := \"0123=56789\"\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: %s\\r\\nContent-Type: %s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\texpectedHost, expectedContentType, len(expectedBody), expectedBody)\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := req.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\ttestRequestCopyTo(t, &req)\n}\n\nfunc TestResponseCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\t// empty copy\n\ttestResponseCopyTo(t, &resp)\n\n\t// init resp\n\tresp.laddr = zeroTCPAddr\n\tresp.SkipBody = true\n\tresp.Header.SetStatusCode(200)\n\tresp.SetBodyString(\"test\")\n\ttestResponseCopyTo(t, &resp)\n}\n\nfunc testRequestCopyTo(t *testing.T, src *Request) {\n\tvar dst Request\n\tsrc.CopyTo(&dst)\n\n\t// Compare serialized representations.\n\tif src.String() != dst.String() || !bytes.Equal(src.Body(), dst.Body()) {\n\t\tt.Fatalf(\"RequestCopyTo fail, src: \\n%+v\\ndst: \\n%+v\\n\", src, &dst)\n\t}\n}\n\nfunc testResponseCopyTo(t *testing.T, src *Response) {\n\tvar dst Response\n\tsrc.CopyTo(&dst)\n\n\tif !reflect.DeepEqual(src, &dst) {\n\t\tt.Fatalf(\"ResponseCopyTo fail, src: \\n%+v\\ndst: \\n%+v\\n\", src, &dst)\n\t}\n}\n\nfunc TestRequestBodyStreamWithTrailer(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestBodyStreamWithTrailer(t, nil, false)\n\n\tbody := createFixedBody(1e5)\n\ttestRequestBodyStreamWithTrailer(t, body, false)\n\ttestRequestBodyStreamWithTrailer(t, body, true)\n}\n\nfunc testRequestBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {\n\texpectedTrailer := map[string]string{\n\t\t\"foo\": \"testfoo\",\n\t\t\"bar\": \"testbar\",\n\t}\n\n\tvar req1 Request\n\treq1.Header.disableNormalizing = disableNormalizing\n\treq1.SetHost(\"google.com\")\n\treq1.SetBodyStream(bytes.NewBuffer(body), -1)\n\tfor k, v := range expectedTrailer {\n\t\terr := req1.Header.AddTrailer(k)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\treq1.Header.Set(k, v)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := req1.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar req2 Request\n\treq2.Header.disableNormalizing = disableNormalizing\n\tbr := bufio.NewReader(w)\n\tif err := req2.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\treqBody := req2.Body()\n\tif !bytes.Equal(reqBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", reqBody, body)\n\t}\n\n\tfor k, v := range expectedTrailer {\n\t\tkBytes := []byte(k)\n\t\tnormalizeHeaderKey(kBytes, disableNormalizing)\n\t\tr := req2.Header.Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer header %q: %q. Expecting %q\", kBytes, r, v)\n\t\t}\n\t}\n}\n\nfunc TestResponseBodyStreamWithTrailer(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseBodyStreamWithTrailer(t, nil, false)\n\n\tbody := createFixedBody(1e5)\n\ttestResponseBodyStreamWithTrailer(t, body, false)\n\ttestResponseBodyStreamWithTrailer(t, body, true)\n}\n\nfunc testResponseBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {\n\texpectedTrailer := map[string]string{\n\t\t\"foo\": \"testfoo\",\n\t\t\"bar\": \"testbar\",\n\t}\n\tvar resp1 Response\n\tresp1.Header.disableNormalizing = disableNormalizing\n\tresp1.SetBodyStream(bytes.NewReader(body), -1)\n\tfor k, v := range expectedTrailer {\n\t\terr := resp1.Header.AddTrailer(k)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tresp1.Header.Set(k, v)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := resp1.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar resp2 Response\n\tresp2.Header.disableNormalizing = disableNormalizing\n\tbr := bufio.NewReader(w)\n\tif err := resp2.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\trespBody := resp2.Body()\n\tif !bytes.Equal(respBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", respBody, body)\n\t}\n\n\tfor k, v := range expectedTrailer {\n\t\tkBytes := []byte(k)\n\t\tnormalizeHeaderKey(kBytes, disableNormalizing)\n\t\tr := resp2.Header.Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer header %q: %q. Expecting %q\", kBytes, r, v)\n\t\t}\n\t}\n}\n\nfunc TestResponseBodyStreamDeflate(t *testing.T) {\n\tt.Parallel()\n\n\tbody := createFixedBody(1e5)\n\n\t// Verifies https://github.com/valyala/fasthttp/issues/176\n\t// when Content-Length is explicitly set.\n\ttestResponseBodyStreamDeflate(t, body, len(body))\n\n\t// Verifies that 'transfer-encoding: chunked' works as expected.\n\ttestResponseBodyStreamDeflate(t, body, -1)\n}\n\nfunc TestResponseBodyStreamGzip(t *testing.T) {\n\tt.Parallel()\n\n\tbody := createFixedBody(1e5)\n\n\t// Verifies https://github.com/valyala/fasthttp/issues/176\n\t// when Content-Length is explicitly set.\n\ttestResponseBodyStreamGzip(t, body, len(body))\n\n\t// Verifies that 'transfer-encoding: chunked' works as expected.\n\ttestResponseBodyStreamGzip(t, body, -1)\n}\n\nfunc testResponseBodyStreamDeflate(t *testing.T, body []byte, bodySize int) {\n\tvar r Response\n\tr.SetBodyStream(bytes.NewReader(body), bodySize)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := r.WriteDeflate(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar resp Response\n\tbr := bufio.NewReader(w)\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\trespBody, err := resp.BodyInflate()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(respBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", respBody, body)\n\t}\n\t// check for invalid\n\tresp.SetBodyRaw([]byte(\"invalid\"))\n\t_, errDeflate := resp.BodyInflate()\n\tif errDeflate == nil || errDeflate.Error() != \"zlib: invalid header\" {\n\t\tt.Fatalf(\"expected error: 'zlib: invalid header' but was %v\", errDeflate)\n\t}\n}\n\nfunc testResponseBodyStreamGzip(t *testing.T, body []byte, bodySize int) {\n\tvar r Response\n\tr.SetBodyStream(bytes.NewReader(body), bodySize)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := r.WriteGzip(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar resp Response\n\tbr := bufio.NewReader(w)\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\trespBody, err := resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !bytes.Equal(respBody, body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", respBody, body)\n\t}\n\t// check for invalid\n\tresp.SetBodyRaw([]byte(\"invalid\"))\n\t_, errUnzip := resp.BodyGunzip()\n\tif errUnzip == nil || errUnzip.Error() != \"unexpected EOF\" {\n\t\tt.Fatalf(\"expected error: 'unexpected EOF' but was %v\", errUnzip)\n\t}\n}\n\nfunc TestResponseWriteGzipNilBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := r.WriteGzip(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestResponseWriteDeflateNilBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := r.WriteDeflate(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestResponseBodyUncompressed(t *testing.T) {\n\tbody := \"body\"\n\tvar r Response\n\tr.SetBodyStream(bytes.NewReader([]byte(body)), len(body))\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := r.WriteDeflate(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar resp Response\n\tbr := bufio.NewReader(w)\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := resp.Header.ContentEncoding()\n\tif string(ce) != \"deflate\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %s\", ce)\n\t}\n\trespBody, err := resp.BodyUncompressed()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(respBody) != body {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", respBody, body)\n\t}\n\n\t// check for invalid encoding\n\tresp.Header.SetContentEncoding(\"invalid\")\n\t_, decodeErr := resp.BodyUncompressed()\n\tif decodeErr != ErrContentEncodingUnsupported {\n\t\tt.Fatalf(\"unexpected error: %v\", decodeErr)\n\t}\n}\n\nfunc TestBodyDecodeWithLimitTooLarge(t *testing.T) {\n\tt.Parallel()\n\n\tbody := bytes.Repeat([]byte(\"a\"), 2*1024)\n\tmaxBodySize := 1024\n\n\ttestCases := []struct {\n\t\tname     string\n\t\tencoding string\n\t\tencode   func([]byte) []byte\n\t}{\n\t\t{\n\t\t\tname:     \"gzip\",\n\t\t\tencoding: \"gzip\",\n\t\t\tencode: func(src []byte) []byte {\n\t\t\t\treturn AppendGzipBytes(nil, src)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"deflate\",\n\t\t\tencoding: \"deflate\",\n\t\t\tencode: func(src []byte) []byte {\n\t\t\t\treturn AppendDeflateBytes(nil, src)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"brotli\",\n\t\t\tencoding: \"br\",\n\t\t\tencode: func(src []byte) []byte {\n\t\t\t\treturn AppendBrotliBytes(nil, src)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"zstd\",\n\t\t\tencoding: \"zstd\",\n\t\t\tencode: func(src []byte) []byte {\n\t\t\t\treturn AppendZstdBytes(nil, src)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name+\"_request_uncompressed\", func(t *testing.T) {\n\t\t\tvar req Request\n\t\t\treq.Header.SetContentEncoding(testCase.encoding)\n\t\t\treq.SetBodyRaw(testCase.encode(body))\n\t\t\t_, err := req.BodyUncompressedWithLimit(maxBodySize)\n\t\t\tif !errors.Is(err, ErrBodyTooLarge) {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(testCase.name+\"_response_uncompressed\", func(t *testing.T) {\n\t\t\tvar resp Response\n\t\t\tresp.Header.SetContentEncoding(testCase.encoding)\n\t\t\tresp.SetBodyRaw(testCase.encode(body))\n\t\t\t_, err := resp.BodyUncompressedWithLimit(maxBodySize)\n\t\t\tif !errors.Is(err, ErrBodyTooLarge) {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRequestMultipartFormWithLimitGzip(t *testing.T) {\n\tt.Parallel()\n\n\tvar formBodyBuffer bytes.Buffer\n\tmw := multipart.NewWriter(&formBodyBuffer)\n\tif err := mw.WriteField(\"foo\", strings.Repeat(\"a\", 8*1024)); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tboundary := mw.Boundary()\n\tif err := mw.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tformBody := formBodyBuffer.Bytes()\n\tgzippedBody := AppendGzipBytes(nil, formBody)\n\n\tt.Run(\"buffered_too_large\", func(t *testing.T) {\n\t\tvar req Request\n\t\treq.Header.SetMultipartFormBoundary(boundary)\n\t\treq.Header.SetContentEncoding(\"gzip\")\n\t\treq.SetBodyRaw(gzippedBody)\n\n\t\t_, err := req.MultipartFormWithLimit(len(formBody) - 1)\n\t\tif !errors.Is(err, ErrBodyTooLarge) {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"streamed_too_large\", func(t *testing.T) {\n\t\tvar req Request\n\t\treq.Header.SetMultipartFormBoundary(boundary)\n\t\treq.Header.SetContentEncoding(\"gzip\")\n\t\treq.SetBodyStream(bytes.NewReader(gzippedBody), len(gzippedBody))\n\n\t\t_, err := req.MultipartFormWithLimit(len(formBody) - 1)\n\t\tif !errors.Is(err, ErrBodyTooLarge) {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"buffered_success\", func(t *testing.T) {\n\t\tvar req Request\n\t\treq.Header.SetMultipartFormBoundary(boundary)\n\t\treq.Header.SetContentEncoding(\"gzip\")\n\t\treq.SetBodyRaw(gzippedBody)\n\n\t\tf, err := req.MultipartFormWithLimit(len(formBody))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tdefer req.RemoveMultipartFormFiles()\n\n\t\tvv := f.Value[\"foo\"]\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected values count: %d\", len(vv))\n\t\t}\n\t\tif vv[0] != strings.Repeat(\"a\", 8*1024) {\n\t\t\tt.Fatalf(\"unexpected value length: %d\", len(vv[0]))\n\t\t}\n\t})\n}\n\nfunc TestResponseSwapBodySerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestResponseSwapBody(t)\n}\n\nfunc TestResponseSwapBodyConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan struct{})\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestResponseSwapBody(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testResponseSwapBody(t *testing.T) {\n\tvar b []byte\n\tr := AcquireResponse()\n\tfor range 20 {\n\t\tbOrig := r.Body()\n\t\tb = r.SwapBody(b)\n\t\tif !bytes.Equal(bOrig, b) {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, bOrig)\n\t\t}\n\t\tr.AppendBodyString(\"foobar\")\n\t}\n\n\ts := \"aaaabbbbcccc\"\n\tb = b[:0]\n\tfor range 10 {\n\t\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\t\tb = r.SwapBody(b)\n\t\tif string(b) != s {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, s)\n\t\t}\n\t\tb = r.SwapBody(b)\n\t\tif len(b) > 0 {\n\t\t\tt.Fatalf(\"unexpected body with non-zero size returned: %q\", b)\n\t\t}\n\t}\n\tReleaseResponse(r)\n}\n\nfunc TestRequestSwapBodySerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestSwapBody(t)\n}\n\nfunc TestRequestSwapBodyConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan struct{})\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestRequestSwapBody(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testRequestSwapBody(t *testing.T) {\n\tvar b []byte\n\tr := AcquireRequest()\n\tfor range 20 {\n\t\tbOrig := r.Body()\n\t\tb = r.SwapBody(b)\n\t\tif !bytes.Equal(bOrig, b) {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, bOrig)\n\t\t}\n\t\tr.AppendBodyString(\"foobar\")\n\t}\n\n\ts := \"aaaabbbbcccc\"\n\tb = b[:0]\n\tfor range 10 {\n\t\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\t\tb = r.SwapBody(b)\n\t\tif string(b) != s {\n\t\t\tt.Fatalf(\"unexpected body returned: %q. Expecting %q\", b, s)\n\t\t}\n\t\tb = r.SwapBody(b)\n\t\tif len(b) > 0 {\n\t\t\tt.Fatalf(\"unexpected body with non-zero size returned: %q\", b)\n\t\t}\n\t}\n\tReleaseRequest(r)\n}\n\nfunc TestRequestHostFromRequestURI(t *testing.T) {\n\tt.Parallel()\n\n\thExpected := \"foobar.com\"\n\tvar req Request\n\treq.SetRequestURI(\"http://proxy-host:123/foobar?baz\")\n\treq.SetHost(hExpected)\n\th := req.Host()\n\tif string(h) != hExpected {\n\t\tt.Fatalf(\"unexpected host set: %q. Expecting %q\", h, hExpected)\n\t}\n}\n\nfunc TestRequestHostFromHeader(t *testing.T) {\n\tt.Parallel()\n\n\thExpected := \"foobar.com\"\n\tvar req Request\n\treq.Header.SetHost(hExpected)\n\th := req.Host()\n\tif string(h) != hExpected {\n\t\tt.Fatalf(\"unexpected host set: %q. Expecting %q\", h, hExpected)\n\t}\n}\n\nfunc TestRequestContentTypeWithCharsetIssue100(t *testing.T) {\n\tt.Parallel()\n\n\texpectedContentType := \"application/x-www-form-urlencoded; charset=UTF-8\"\n\texpectedBody := \"0123=56789\"\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nContent-Type: %s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\texpectedContentType, len(expectedBody), expectedBody)\n\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tvar r Request\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tbody := r.Body()\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\tct := r.Header.ContentType()\n\tif string(ct) != expectedContentType {\n\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, expectedContentType)\n\t}\n\targs := r.PostArgs()\n\tif args.Len() != 1 {\n\t\tt.Fatalf(\"unexpected number of POST args: %d. Expecting 1\", args.Len())\n\t}\n\tav := args.Peek(\"0123\")\n\tif string(av) != \"56789\" {\n\t\tt.Fatalf(\"unexpected POST arg value: %q. Expecting %q\", av, \"56789\")\n\t}\n}\n\nfunc TestRequestReadMultipartFormWithFile(t *testing.T) {\n\tt.Parallel()\n\n\tb := strings.ReplaceAll(`------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\n`, \"\\n\", \"\\r\\n\")\n\n\ts := fmt.Sprintf(strings.ReplaceAll(`POST /upload HTTP/1.1\nHost: localhost:10000\nContent-Length: %d\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n%stailfoobar`, \"\\n\", \"\\r\\n\"), len(b), b)\n\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\n\tvar r Request\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\ttail, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(tail) != \"tailfoobar\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"tailfoobar\")\n\t}\n\n\tf, err := r.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer r.RemoveMultipartFormFiles()\n\n\t// verify values\n\tif len(f.Value) != 1 {\n\t\tt.Fatalf(\"unexpected number of values in multipart form: %d. Expecting 1\", len(f.Value))\n\t}\n\tfor k, vv := range f.Value {\n\t\tif k != \"f1\" {\n\t\t\tt.Fatalf(\"unexpected value name %q. Expecting %q\", k, \"f1\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v != \"value1\" {\n\t\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"value1\")\n\t\t}\n\t}\n\n\t// verify files\n\tif len(f.File) != 1 {\n\t\tt.Fatalf(\"unexpected number of file values in multipart form: %d. Expecting 1\", len(f.File))\n\t}\n\tfor k, vv := range f.File {\n\t\tif k != \"fileaaa\" {\n\t\t\tt.Fatalf(\"unexpected file value name %q. Expecting %q\", k, \"fileaaa\")\n\t\t}\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of file values %d. Expecting 1\", len(vv))\n\t\t}\n\t\tv := vv[0]\n\t\tif v.Filename != \"TODO\" {\n\t\t\tt.Fatalf(\"unexpected filename %q. Expecting %q\", v.Filename, \"TODO\")\n\t\t}\n\t\tct := v.Header.Get(\"Content-Type\")\n\t\tif ct != \"application/octet-stream\" {\n\t\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", ct, \"application/octet-stream\")\n\t\t}\n\t}\n}\n\nfunc TestRequestSetURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\turi := \"/foo/bar?baz\"\n\tu := &URI{}\n\tu.Parse(nil, []byte(uri)) //nolint:errcheck\n\t// Set request uri via SetURI()\n\tr.SetURI(u) // copies URI\n\t// modifying an original URI struct doesn't affect stored URI inside of request\n\tu.SetPath(\"newPath\")\n\tif string(r.RequestURI()) != uri {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.RequestURI(), uri)\n\t}\n\n\t// Set request uri to nil just resets the URI\n\tr.Reset()\n\turi = \"/\"\n\tr.SetURI(nil)\n\tif string(r.RequestURI()) != uri {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.RequestURI(), uri)\n\t}\n}\n\nfunc TestRequestRequestURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\t// Set request uri via SetRequestURI()\n\turi := \"/foo/bar?baz\"\n\tr.SetRequestURI(uri)\n\tif string(r.RequestURI()) != uri {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.RequestURI(), uri)\n\t}\n\n\t// Set request uri via Request.URI().Update()\n\tr.Reset()\n\turi = \"/aa/bbb?ccc=sdfsdf\"\n\tr.URI().Update(uri)\n\tif string(r.RequestURI()) != uri {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.RequestURI(), uri)\n\t}\n\n\t// update query args in the request uri\n\tqa := r.URI().QueryArgs()\n\tqa.Reset()\n\tqa.Set(\"foo\", \"bar\")\n\turi = \"/aa/bbb?foo=bar\"\n\tif string(r.RequestURI()) != uri {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.RequestURI(), uri)\n\t}\n}\n\nfunc TestRequestUpdateURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\tr.Header.SetHost(\"aaa.bbb\")\n\tr.SetRequestURI(\"/lkjkl/kjl\")\n\n\t// Modify request uri and host via URI() object and make sure\n\t// the requestURI and Host header are properly updated\n\tu := r.URI()\n\tu.SetPath(\"/123/432.html\")\n\tu.SetHost(\"foobar.com\")\n\ta := u.QueryArgs()\n\ta.Set(\"aaa\", \"bcse\")\n\n\ts := r.String()\n\tif !strings.HasPrefix(s, \"GET /123/432.html?aaa=bcse\") {\n\t\tt.Fatalf(\"cannot find %q in %q\", \"GET /123/432.html?aaa=bcse\", s)\n\t}\n\tif !strings.Contains(s, \"\\r\\nHost: foobar.com\\r\\n\") {\n\t\tt.Fatalf(\"cannot find %q in %q\", \"\\r\\nHost: foobar.com\\r\\n\", s)\n\t}\n}\n\nfunc TestUseHostHeader(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\tr.UseHostHeader = true\n\tr.Header.SetHost(\"aaa.bbb\")\n\tr.SetRequestURI(\"/lkjkl/kjl\")\n\n\t// Modify request uri and host via URI() object and make sure\n\t// the requestURI and Host header are properly updated\n\tu := r.URI()\n\tu.SetPath(\"/123/432.html\")\n\tu.SetHost(\"foobar.com\")\n\ta := u.QueryArgs()\n\ta.Set(\"aaa\", \"bcse\")\n\n\ts := r.String()\n\tif !strings.HasPrefix(s, \"GET /123/432.html?aaa=bcse\") {\n\t\tt.Fatalf(\"cannot find %q in %q\", \"GET /123/432.html?aaa=bcse\", s)\n\t}\n\tif !strings.Contains(s, \"\\r\\nHost: aaa.bbb\\r\\n\") {\n\t\tt.Fatalf(\"cannot find %q in %q\", \"\\r\\nHost: aaa.bbb\\r\\n\", s)\n\t}\n}\n\nfunc TestUseHostHeader2(t *testing.T) {\n\tt.Parallel()\n\ttestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Host != \"SomeHost\" {\n\t\t\thttp.Error(w, fmt.Sprintf(\"Expected Host header to be '%q', but got '%q'\", \"SomeHost\", r.Host), http.StatusBadRequest)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t}\n\t}))\n\tdefer testServer.Close()\n\n\tclient := &Client{}\n\treq := AcquireRequest()\n\tdefer ReleaseRequest(req)\n\tresp := AcquireResponse()\n\tdefer ReleaseResponse(resp)\n\n\treq.SetRequestURI(testServer.URL)\n\treq.UseHostHeader = true\n\treq.Header.SetHost(\"SomeHost\")\n\tif err := client.DoTimeout(req, resp, 1*time.Second); err != nil {\n\t\tt.Fatalf(\"DoTimeout returned an error '%v'\", err)\n\t}\n\tif resp.StatusCode() != http.StatusOK {\n\t\tt.Fatalf(\"DoTimeout: %v\", resp.body)\n\t}\n\tif err := client.Do(req, resp); err != nil {\n\t\tt.Fatalf(\"DoTimeout returned an error '%v'\", err)\n\t}\n\tif resp.StatusCode() != http.StatusOK {\n\t\tt.Fatalf(\"Do: %q\", resp.body)\n\t}\n}\n\nfunc TestUseHostHeaderAfterRelease(t *testing.T) {\n\tt.Parallel()\n\treq := AcquireRequest()\n\treq.UseHostHeader = true\n\tReleaseRequest(req)\n\n\treq = AcquireRequest()\n\tdefer ReleaseRequest(req)\n\tif req.UseHostHeader {\n\t\tt.Fatalf(\"UseHostHeader was not released in ReleaseRequest()\")\n\t}\n}\n\nfunc TestRequestBodyStreamMultipleBodyCalls(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\ts := \"foobar baz abc\"\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\tfor i := range 10 {\n\t\tbody := r.Body()\n\t\tif string(body) != s {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q. iteration %d\", body, s, i)\n\t\t}\n\t}\n}\n\nfunc TestResponseBodyStreamMultipleBodyCalls(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\ts := \"foobar baz abc\"\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(bytes.NewBufferString(s), len(s))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\tfor i := range 10 {\n\t\tbody := r.Body()\n\t\tif string(body) != s {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q. iteration %d\", body, s, i)\n\t\t}\n\t}\n}\n\nfunc TestRequestBodyWriteToPlain(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\texpectedS := \"foobarbaz\"\n\tr.AppendBodyString(expectedS)\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestResponseBodyWriteToPlain(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"foobarbaz\"\n\tr.AppendBodyString(expectedS)\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestResponseBodyWriteToStream(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"aaabbbccc\"\n\tbuf := bytes.NewBufferString(expectedS)\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStream(buf, len(expectedS))\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\ttestBodyWriteTo(t, &r, expectedS, false)\n}\n\nfunc TestRequestBodyWriteToMultipart(t *testing.T) {\n\tt.Parallel()\n\n\texpectedS := \"--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_0\\\"\\r\\n\\r\\nvalue_0\\r\\n--foobar--\\r\\n\"\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=foobar\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tlen(expectedS), expectedS)\n\n\tvar r Request\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\ntype bodyWriterTo interface {\n\tBodyWriteTo(io.Writer) error\n\tBody() []byte\n}\n\nfunc testBodyWriteTo(t *testing.T, bw bodyWriterTo, expectedS string, isRetainedBody bool) {\n\tvar buf bytebufferpool.ByteBuffer\n\tif err := bw.BodyWriteTo(&buf); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\ts := buf.B\n\tif string(s) != expectedS {\n\t\tt.Fatalf(\"unexpected result %q. Expecting %q\", s, expectedS)\n\t}\n\n\tbody := bw.Body()\n\tif isRetainedBody {\n\t\tif string(body) != expectedS {\n\t\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedS)\n\t\t}\n\t} else {\n\t\tif len(body) > 0 {\n\t\t\tt.Fatalf(\"unexpected non-zero body after BodyWriteTo: %q\", body)\n\t\t}\n\t}\n}\n\nfunc TestRequestReadEOF(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tbr := bufio.NewReader(&bytes.Buffer{})\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n\n\t// incomplete request mustn't return io.EOF\n\tbr = bufio.NewReader(bytes.NewBufferString(\"POST / HTTP/1.1\\r\\nContent-Type: aa\\r\\nContent-Length: 1234\\r\\n\\r\\nIncomplete body\"))\n\terr = r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"expecting non-EOF error\")\n\t}\n}\n\nfunc TestResponseReadEOF(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tbr := bufio.NewReader(&bytes.Buffer{})\n\terr := r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err != io.EOF {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v\", err, io.EOF)\n\t}\n\n\t// incomplete response mustn't return io.EOF\n\tbr = bufio.NewReader(bytes.NewBufferString(\"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 123\\r\\n\\r\\nIncomplete body\"))\n\terr = r.Read(br)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n\tif err == io.EOF {\n\t\tt.Fatalf(\"expecting non-EOF error\")\n\t}\n}\n\nfunc TestRequestReadNoBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tbr := bufio.NewReader(bytes.NewBufferString(\"GET / HTTP/1.1\\r\\n\\r\\n\"))\n\terr := r.Read(br)\n\tr.SetHost(\"foobar\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\ts := r.String()\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected Content-Length\")\n\t}\n}\n\nfunc TestRequestReadNoBodyStreaming(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tr.Header.contentLength = -2\n\n\tbr := bufio.NewReader(bytes.NewBufferString(\"GET / HTTP/1.1\\r\\n\\r\\n\"))\n\terr := r.ContinueReadBodyStream(br, 0)\n\tr.SetHost(\"foobar\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\ts := r.String()\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected Content-Length\")\n\t}\n}\n\nfunc TestResponseWriteTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tr.SetBodyString(\"foobar\")\n\n\ts := r.String()\n\tvar buf bytebufferpool.ByteBuffer\n\tn, err := r.WriteTo(&buf)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != int64(len(s)) {\n\t\tt.Fatalf(\"unexpected response length %d. Expecting %d\", n, len(s))\n\t}\n\tif string(buf.B) != s {\n\t\tt.Fatalf(\"unexpected response %q. Expecting %q\", buf.B, s)\n\t}\n}\n\nfunc TestRequestWriteTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tr.SetRequestURI(\"http://foobar.com/aaa/bbb\")\n\n\ts := r.String()\n\tvar buf bytebufferpool.ByteBuffer\n\tn, err := r.WriteTo(&buf)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != int64(len(s)) {\n\t\tt.Fatalf(\"unexpected request length %d. Expecting %d\", n, len(s))\n\t}\n\tif string(buf.B) != s {\n\t\tt.Fatalf(\"unexpected request %q. Expecting %q\", buf.B, s)\n\t}\n}\n\nfunc TestResponseSkipBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\t// set StatusNotModified\n\tr.Header.SetStatusCode(StatusNotModified)\n\tr.SetBodyString(\"foobar\")\n\ts := r.String()\n\tif strings.Contains(s, \"\\r\\n\\r\\nfoobar\") {\n\t\tt.Fatalf(\"unexpected non-zero body in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected content-length in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Type: \") {\n\t\tt.Fatalf(\"unexpected content-type in response %q\", s)\n\t}\n\n\t// set StatusNoContent\n\tr.Header.SetStatusCode(StatusNoContent)\n\tr.SetBodyString(\"foobar\")\n\ts = r.String()\n\tif strings.Contains(s, \"\\r\\n\\r\\nfoobar\") {\n\t\tt.Fatalf(\"unexpected non-zero body in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected content-length in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Type: \") {\n\t\tt.Fatalf(\"unexpected content-type in response %q\", s)\n\t}\n\n\t// set StatusNoContent with statusMessage\n\tr.Header.SetStatusCode(StatusNoContent)\n\tr.Header.SetStatusMessage([]byte(\"NC\"))\n\tr.SetBodyString(\"foobar\")\n\ts = r.String()\n\tif strings.Contains(s, \"\\r\\n\\r\\nfoobar\") {\n\t\tt.Fatalf(\"unexpected non-zero body in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected content-length in response %q\", s)\n\t}\n\tif strings.Contains(s, \"Content-Type: \") {\n\t\tt.Fatalf(\"unexpected content-type in response %q\", s)\n\t}\n\tif !strings.HasPrefix(s, \"HTTP/1.1 204 NC\\r\\n\") {\n\t\tt.Fatalf(\"expecting non-default status line in response %q\", s)\n\t}\n\n\t// explicitly skip body\n\tr.Header.SetStatusCode(StatusOK)\n\tr.SkipBody = true\n\tr.SetBodyString(\"foobar\")\n\ts = r.String()\n\tif strings.Contains(s, \"\\r\\n\\r\\nfoobar\") {\n\t\tt.Fatalf(\"unexpected non-zero body in response %q\", s)\n\t}\n\tif !strings.Contains(s, \"Content-Length: 6\\r\\n\") {\n\t\tt.Fatalf(\"expecting content-length in response %q\", s)\n\t}\n\tif !strings.Contains(s, \"Content-Type: \") {\n\t\tt.Fatalf(\"expecting content-type in response %q\", s)\n\t}\n}\n\nfunc TestRequestNoContentLength(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tr.Header.SetMethod(MethodHead)\n\tr.Header.SetHost(\"foobar\")\n\n\ts := r.String()\n\tif strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"unexpected content-length in HEAD request %q\", s)\n\t}\n\n\tr.Header.SetMethod(MethodPost)\n\tfmt.Fprintf(r.BodyWriter(), \"foobar body\")\n\ts = r.String()\n\tif !strings.Contains(s, \"Content-Length: \") {\n\t\tt.Fatalf(\"missing content-length header in non-GET request %q\", s)\n\t}\n}\n\nfunc TestRequestReadGzippedBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tbodyOriginal := \"foo bar baz compress me better!\"\n\tbody := AppendGzipBytes(nil, []byte(bodyOriginal))\n\ts := fmt.Sprintf(\"POST /foobar HTTP/1.1\\r\\nContent-Type: foo/bar\\r\\nContent-Encoding: gzip\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tlen(body), body)\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif string(r.Header.ContentEncoding()) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected content-encoding: %q. Expecting %q\", r.Header.ContentEncoding(), \"gzip\")\n\t}\n\tif r.Header.ContentLength() != len(body) {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting %d\", r.Header.ContentLength(), len(body))\n\t}\n\tif !bytes.Equal(r.Body(), body) {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", r.Body(), body)\n\t}\n\n\tbodyGunzipped, err := AppendGunzipBytes(nil, r.Body())\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when uncompressing data: %v\", err)\n\t}\n\tif string(bodyGunzipped) != bodyOriginal {\n\t\tt.Fatalf(\"unexpected uncompressed body %q. Expecting %q\", bodyGunzipped, bodyOriginal)\n\t}\n}\n\nfunc TestRequestReadPostNoBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\ts := \"POST /foo/bar HTTP/1.1\\r\\nContent-Type: aaa/bbb\\r\\n\\r\\naaaa\"\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif string(r.Header.RequestURI()) != \"/foo/bar\" {\n\t\tt.Fatalf(\"unexpected request uri %q. Expecting %q\", r.Header.RequestURI(), \"/foo/bar\")\n\t}\n\tif string(r.Header.ContentType()) != \"aaa/bbb\" {\n\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", r.Header.ContentType(), \"aaa/bbb\")\n\t}\n\tif len(r.Body()) != 0 {\n\t\tt.Fatalf(\"unexpected body found %q. Expecting empty body\", r.Body())\n\t}\n\tif r.Header.ContentLength() != 0 {\n\t\tt.Fatalf(\"unexpected content-length: %d. Expecting 0\", r.Header.ContentLength())\n\t}\n\n\ttail, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(tail) != \"aaaa\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"aaaa\")\n\t}\n}\n\nfunc TestRequestContinueReadBody(t *testing.T) {\n\tt.Parallel()\n\n\ts := \"PUT /foo/bar HTTP/1.1\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: foo/bar\\r\\n\\r\\nabcdef4343\"\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\n\tvar r Request\n\tif err := r.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue must return true\")\n\t}\n\n\tif err := r.ContinueReadBody(br, 0, true); err != nil {\n\t\tt.Fatalf(\"error when reading request body: %v\", err)\n\t}\n\tbody := r.Body()\n\tif string(body) != \"abcde\" {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, \"abcde\")\n\t}\n\n\ttail, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(tail) != \"f4343\" {\n\t\tt.Fatalf(\"unexpected tail %q. Expecting %q\", tail, \"f4343\")\n\t}\n}\n\nfunc TestRequestContinueReadBodyDisablePrereadMultipartForm(t *testing.T) {\n\tt.Parallel()\n\n\tvar w bytes.Buffer\n\tmw := multipart.NewWriter(&w)\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tv := fmt.Sprintf(\"value_%d\", i)\n\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\tboundary := mw.Boundary()\n\tif err := mw.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tformData := w.Bytes()\n\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tboundary, len(formData), formData)\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\n\tvar r Request\n\n\tif err := r.Header.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error reading headers: %v\", err)\n\t}\n\n\tif err := r.readLimitBody(br, 10000, false, false); err != nil {\n\t\tt.Fatalf(\"unexpected error reading body: %v\", err)\n\t}\n\n\tif r.multipartForm != nil {\n\t\tt.Fatalf(\"The multipartForm of the Request must be nil\")\n\t}\n\n\tif !bytes.Equal(formData, r.Body()) {\n\t\tt.Fatalf(\"The body given must equal the body in the Request\")\n\t}\n}\n\nfunc TestRequestMayContinue(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\tif r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on empty request must return false\")\n\t}\n\n\tr.Header.Set(\"Expect\", \"123sdfds\")\n\tif r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on invalid Expect header must return false\")\n\t}\n\n\tr.Header.Set(\"Expect\", \"100-continue\")\n\tif !r.MayContinue() {\n\t\tt.Fatalf(\"MayContinue on 'Expect: 100-continue' header must return true\")\n\t}\n}\n\nfunc TestResponseGzipStream(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\tfmt.Fprintf(w, \"foo\")\n\t\tw.Flush()\n\t\ttime.Sleep(time.Millisecond)\n\t\t_, _ = w.WriteString(\"barbaz\")\n\t\t_ = w.Flush()\n\t\ttime.Sleep(time.Millisecond)\n\t\t_, _ = fmt.Fprintf(w, \"1234\")\n\t\tif err := w.Flush(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\ttestResponseGzipExt(t, &r, \"foobarbaz1234\")\n}\n\nfunc TestResponseDeflateStream(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tif r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tr.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\t_, _ = w.WriteString(\"foo\")\n\t\t_ = w.Flush()\n\t\t_, _ = fmt.Fprintf(w, \"barbaz\")\n\t\t_ = w.Flush()\n\t\t_, _ = w.WriteString(\"1234\")\n\t\tif err := w.Flush(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t})\n\tif !r.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\ttestResponseDeflateExt(t, &r, \"foobarbaz1234\")\n}\n\nfunc TestResponseDeflate(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, s := range compressTestcases {\n\t\ttestResponseDeflate(t, s)\n\t}\n}\n\nfunc TestResponseGzip(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, s := range compressTestcases {\n\t\ttestResponseGzip(t, s)\n\t}\n}\n\nfunc testResponseDeflate(t *testing.T, s string) {\n\tvar r Response\n\tr.SetBodyString(s)\n\ttestResponseDeflateExt(t, &r, s)\n\n\t// make sure the uncompressible Content-Type isn't compressed\n\tr.Reset()\n\tr.Header.SetContentType(\"image/jpeg\")\n\tr.SetBodyString(s)\n\ttestResponseDeflateExt(t, &r, s)\n}\n\nfunc testResponseDeflateExt(t *testing.T, r *Response, s string) {\n\tisCompressible := isCompressibleResponse(r, s)\n\n\tvar buf bytes.Buffer\n\tvar err error\n\tbw := bufio.NewWriter(&buf)\n\tif err = r.WriteDeflate(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err = bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar r1 Response\n\tbr := bufio.NewReader(&buf)\n\tif err = r1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := r1.Header.ContentEncoding()\n\tvar body []byte\n\tif isCompressible {\n\t\tif string(ce) != \"deflate\" {\n\t\t\tt.Fatalf(\"unexpected Content-Encoding %q. Expecting %q. len(s)=%d, Content-Type: %q\",\n\t\t\t\tce, \"deflate\", len(s), r.Header.ContentType())\n\t\t}\n\t\tbody, err = r1.BodyInflate()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t} else {\n\t\tif len(ce) > 0 {\n\t\t\tt.Fatalf(\"expecting empty Content-Encoding. Got %q\", ce)\n\t\t}\n\t\tbody = r1.Body()\n\t}\n\tif string(body) != s {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, s)\n\t}\n}\n\nfunc testResponseGzip(t *testing.T, s string) {\n\tvar r Response\n\tr.SetBodyString(s)\n\ttestResponseGzipExt(t, &r, s)\n\n\t// make sure the uncompressible Content-Type isn't compressed\n\tr.Reset()\n\tr.Header.SetContentType(\"image/jpeg\")\n\tr.SetBodyString(s)\n\ttestResponseGzipExt(t, &r, s)\n}\n\nfunc testResponseGzipExt(t *testing.T, r *Response, s string) {\n\tisCompressible := isCompressibleResponse(r, s)\n\n\tvar buf bytes.Buffer\n\tvar err error\n\tbw := bufio.NewWriter(&buf)\n\tif err = r.WriteGzip(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err = bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar r1 Response\n\tbr := bufio.NewReader(&buf)\n\tif err = r1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tce := r1.Header.ContentEncoding()\n\tvar body []byte\n\tif isCompressible {\n\t\tif string(ce) != \"gzip\" {\n\t\t\tt.Fatalf(\"unexpected Content-Encoding %q. Expecting %q. len(s)=%d, Content-Type: %q\",\n\t\t\t\tce, \"gzip\", len(s), r.Header.ContentType())\n\t\t}\n\t\tbody, err = r1.BodyGunzip()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t} else {\n\t\tif len(ce) > 0 {\n\t\t\tt.Fatalf(\"Expecting empty Content-Encoding. Got %q\", ce)\n\t\t}\n\t\tbody = r1.Body()\n\t}\n\tif string(body) != s {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, s)\n\t}\n}\n\nfunc isCompressibleResponse(r *Response, s string) bool {\n\tisCompressible := r.Header.isCompressibleContentType()\n\tif isCompressible && len(s) < minCompressLen && !r.IsBodyStream() {\n\t\tisCompressible = false\n\t}\n\treturn isCompressible\n}\n\nfunc TestRequestMultipartForm(t *testing.T) {\n\tt.Parallel()\n\n\tvar w bytes.Buffer\n\tmw := multipart.NewWriter(&w)\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tv := fmt.Sprintf(\"value_%d\", i)\n\t\tif err := mw.WriteField(k, v); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\tboundary := mw.Boundary()\n\tif err := mw.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tformData := w.Bytes()\n\tfor range 5 {\n\t\tformData = testRequestMultipartForm(t, boundary, formData, 10)\n\t}\n\n\t// verify request unmarshaling / marshaling\n\ts := \"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=foobar\\r\\nContent-Length: 213\\r\\n\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_0\\\"\\r\\n\\r\\nvalue_0\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_1\\\"\\r\\n\\r\\nvalue_1\\r\\n--foobar\\r\\nContent-Disposition: form-data; name=\\\"key_2\\\"\\r\\n\\r\\nvalue_2\\r\\n--foobar--\\r\\n\"\n\n\tvar req Request\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := req.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\ts = req.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := req.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\ttestRequestMultipartForm(t, \"foobar\", req.Body(), 3)\n}\n\nfunc testRequestMultipartForm(t *testing.T, boundary string, formData []byte, partsCount int) []byte {\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\tboundary, len(formData), formData)\n\n\tvar req Request\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := req.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tf, err := req.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer req.RemoveMultipartFormFiles()\n\n\tif len(f.File) > 0 {\n\t\tt.Fatalf(\"unexpected files found in the multipart form: %d\", len(f.File))\n\t}\n\n\tif len(f.Value) != partsCount {\n\t\tt.Fatalf(\"unexpected number of values found: %d. Expecting %d\", len(f.Value), partsCount)\n\t}\n\n\tfor k, vv := range f.Value {\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values found for key=%q: %d. Expecting 1\", k, len(vv))\n\t\t}\n\t\tif !strings.HasPrefix(k, \"key_\") {\n\t\t\tt.Fatalf(\"unexpected key prefix=%q. Expecting %q\", k, \"key_\")\n\t\t}\n\t\tv := vv[0]\n\t\tif !strings.HasPrefix(v, \"value_\") {\n\t\t\tt.Fatalf(\"unexpected value prefix=%q. expecting %q\", v, \"value_\")\n\t\t}\n\t\tif k[len(\"key_\"):] != v[len(\"value_\"):] {\n\t\t\tt.Fatalf(\"key and value suffixes don't match: %q vs %q\", k, v)\n\t\t}\n\t}\n\n\treturn req.Body()\n}\n\nfunc TestResponseReadLimitBody(t *testing.T) {\n\tt.Parallel()\n\n\t// response with content-length\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 10)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 100)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nContent-Length: 10\\r\\n\\r\\n9876543210\", 9, ErrBodyTooLarge)\n\n\t// chunked response\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 9)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\n\", 9)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 100)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 2, ErrBodyTooLarge)\n\n\t// identity response\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 6)\n\ttestResponseReadLimitBodySuccess(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 106)\n\ttestResponseReadLimitBodyError(t, \"HTTP/1.1 400 OK\\r\\nContent-Type: aa\\r\\n\\r\\n123456\", 5, ErrBodyTooLarge)\n}\n\nfunc TestRequestReadLimitBody(t *testing.T) {\n\tt.Parallel()\n\n\t// request with content-length\n\ttestRequestReadLimitBodySuccess(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 9)\n\ttestRequestReadLimitBodySuccess(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 92)\n\ttestRequestReadLimitBodyError(t, \"POST /foo HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 9\\r\\nContent-Type: aaa\\r\\n\\r\\n123456789\", 5, ErrBodyTooLarge)\n\n\t// chunked request\n\ttestRequestReadLimitBodySuccess(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 9)\n\ttestRequestReadLimitBodySuccess(t, \"POST /a HTTP/1.1\\nHost: a.com\\nTransfer-Encoding: chunked\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\nFoo: bar\\r\\n\\r\\n\", 9)\n\ttestRequestReadLimitBodySuccess(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 999)\n\ttestRequestReadLimitBodyError(t, \"POST /a HTTP/1.1\\r\\nHost: a.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa\\r\\n\\r\\n6\\r\\nfoobar\\r\\n3\\r\\nbaz\\r\\n0\\r\\n\\r\\n\", 8, ErrBodyTooLarge)\n}\n\nfunc testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) {\n\tvar resp Response\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\terr := resp.ReadLimitBody(br, maxBodySize)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error. s=%q, maxBodySize=%d\", s, maxBodySize)\n\t}\n\tif err != expectedErr {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v. s=%q, maxBodySize=%d\", err, expectedErr, s, maxBodySize)\n\t}\n}\n\nfunc testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {\n\tt.Helper()\n\n\tvar resp Response\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := resp.ReadLimitBody(br, maxBodySize); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. s=%q, maxBodySize=%d\", err, s, maxBodySize)\n\t}\n}\n\nfunc testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) {\n\tvar req Request\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\terr := req.ReadLimitBody(br, maxBodySize)\n\tif err == nil {\n\t\tt.Fatalf(\"expecting error. s=%q, maxBodySize=%d\", s, maxBodySize)\n\t}\n\tif err != expectedErr {\n\t\tt.Fatalf(\"unexpected error: %v. Expecting %v. s=%q, maxBodySize=%d\", err, expectedErr, s, maxBodySize)\n\t}\n}\n\nfunc testRequestReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {\n\tt.Helper()\n\n\tvar req Request\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := req.ReadLimitBody(br, maxBodySize); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v. s=%q, maxBodySize=%d\", err, s, maxBodySize)\n\t}\n}\n\nfunc TestRequestString(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\tr.SetRequestURI(\"http://foobar.com/aaa\")\n\ts := r.String()\n\texpectedS := \"GET /aaa HTTP/1.1\\r\\nHost: foobar.com\\r\\n\\r\\n\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected request: %q. Expecting %q\", s, expectedS)\n\t}\n}\n\nfunc TestRequestBodyWriter(t *testing.T) {\n\tvar r Request\n\tw := r.BodyWriter()\n\tfor i := range 10 {\n\t\tfmt.Fprintf(w, \"%d\", i)\n\t}\n\tif string(r.Body()) != \"0123456789\" {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", r.Body(), \"0123456789\")\n\t}\n}\n\nfunc TestResponseBodyWriter(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\tw := r.BodyWriter()\n\tfor i := range 10 {\n\t\tfmt.Fprintf(w, \"%d\", i)\n\t}\n\tif string(r.Body()) != \"0123456789\" {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", r.Body(), \"0123456789\")\n\t}\n}\n\nfunc TestRequestWriteRequestURINoHost(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\treq.Header.SetRequestURI(\"http://google.com/foo/bar?baz=aaa\")\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tif err := req.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar req1 Request\n\tbr := bufio.NewReader(&w)\n\tif err := req1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(req1.Header.Host()) != \"google.com\" {\n\t\tt.Fatalf(\"unexpected host: %q. Expecting %q\", req1.Header.Host(), \"google.com\")\n\t}\n\tif string(req.Header.RequestURI()) != \"/foo/bar?baz=aaa\" {\n\t\tt.Fatalf(\"unexpected requestURI: %q. Expecting %q\", req.Header.RequestURI(), \"/foo/bar?baz=aaa\")\n\t}\n\n\t// verify that Request.Write returns error on non-absolute RequestURI\n\treq.Reset()\n\treq.Header.SetRequestURI(\"/foo/bar\")\n\tw.Reset()\n\tbw.Reset(&w)\n\tif err := req.Write(bw); err == nil {\n\t\tt.Fatalf(\"expecting error\")\n\t}\n}\n\nfunc TestSetRequestBodyStreamFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetRequestBodyStream(t, \"a\")\n\ttestSetRequestBodyStream(t, string(createFixedBody(4097)))\n\ttestSetRequestBodyStream(t, string(createFixedBody(100500)))\n}\n\nfunc TestSetResponseBodyStreamFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetResponseBodyStream(t, \"a\")\n\ttestSetResponseBodyStream(t, string(createFixedBody(4097)))\n\ttestSetResponseBodyStream(t, string(createFixedBody(100500)))\n}\n\nfunc TestSetRequestBodyStreamChunked(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetRequestBodyStreamChunked(t, \"\", map[string]string{\"Foo\": \"bar\"})\n\n\tbody := \"foobar baz aaa bbb ccc\"\n\ttestSetRequestBodyStreamChunked(t, body, nil)\n\n\tbody = string(createFixedBody(10001))\n\ttestSetRequestBodyStreamChunked(t, body, map[string]string{\"Foo\": \"test\", \"Bar\": \"test\"})\n}\n\nfunc TestSetResponseBodyStreamChunked(t *testing.T) {\n\tt.Parallel()\n\n\ttestSetResponseBodyStreamChunked(t, \"\", map[string]string{\"Foo\": \"bar\"})\n\n\tbody := \"foobar baz aaa bbb ccc\"\n\ttestSetResponseBodyStreamChunked(t, body, nil)\n\n\tbody = string(createFixedBody(10001))\n\ttestSetResponseBodyStreamChunked(t, body, map[string]string{\"Foo\": \"test\", \"Bar\": \"test\"})\n}\n\nfunc testSetRequestBodyStream(t *testing.T, body string) {\n\tvar req Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.Header.SetMethod(MethodPost)\n\n\tbodySize := len(body)\n\tif req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\treq.SetBodyStream(bytes.NewBufferString(body), bodySize)\n\tif !req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tif err := req.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing request: %v. body=%q\", err, body)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing request: %v. body=%q\", err, body)\n\t}\n\n\tvar req1 Request\n\tbr := bufio.NewReader(&w)\n\tif err := req1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading request: %v. body=%q\", err, body)\n\t}\n\tif string(req1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", req1.Body(), body)\n\t}\n}\n\nfunc testSetRequestBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {\n\tvar req Request\n\treq.Header.SetHost(\"foobar.com\")\n\treq.Header.SetMethod(MethodPost)\n\n\tif req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\treq.SetBodyStream(bytes.NewBufferString(body), -1)\n\tif !req.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tfor k := range trailer {\n\t\terr := req.Header.AddTrailer(k)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\tif err := req.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing request: %v. body=%q\", err, body)\n\t}\n\tfor k, v := range trailer {\n\t\treq.Header.Set(k, v)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing request: %v. body=%q\", err, body)\n\t}\n\n\tvar req1 Request\n\tbr := bufio.NewReader(&w)\n\tif err := req1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading request: %v. body=%q\", err, body)\n\t}\n\tif string(req1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", req1.Body(), body)\n\t}\n\tfor k, v := range trailer {\n\t\tr := req.Header.Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer %q. Expecting %q. Got %q\", k, v, r)\n\t\t}\n\t}\n}\n\nfunc testSetResponseBodyStream(t *testing.T, body string) {\n\tvar resp Response\n\tbodySize := len(body)\n\tif resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tresp.SetBodyStream(bytes.NewBufferString(body), bodySize)\n\tif !resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tif err := resp.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing response: %v. body=%q\", err, body)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing response: %v. body=%q\", err, body)\n\t}\n\n\tvar resp1 Response\n\tbr := bufio.NewReader(&w)\n\tif err := resp1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading response: %v. body=%q\", err, body)\n\t}\n\tif string(resp1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", resp1.Body(), body)\n\t}\n}\n\nfunc testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {\n\tvar resp Response\n\tif resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return false\")\n\t}\n\tresp.SetBodyStream(bytes.NewBufferString(body), -1)\n\tif !resp.IsBodyStream() {\n\t\tt.Fatalf(\"IsBodyStream must return true\")\n\t}\n\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\tfor k := range trailer {\n\t\terr := resp.Header.AddTrailer(k)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\tif err := resp.Write(bw); err != nil {\n\t\tt.Fatalf(\"unexpected error when writing response: %v. body=%q\", err, body)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"unexpected error when flushing response: %v. body=%q\", err, body)\n\t}\n\tfor k, v := range trailer {\n\t\tresp.Header.Set(k, v)\n\t}\n\n\tvar resp1 Response\n\tbr := bufio.NewReader(&w)\n\tif err := resp1.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error when reading response: %v. body=%q\", err, body)\n\t}\n\tif string(resp1.Body()) != body {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", resp1.Body(), body)\n\t}\n\tfor k, v := range trailer {\n\t\tr := resp.Header.Peek(k)\n\t\tif string(r) != v {\n\t\t\tt.Fatalf(\"unexpected trailer %q. Expecting %q. Got %q\", k, v, r)\n\t\t}\n\t}\n}\n\nfunc TestRequestReadChunked(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\ts := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\nTrailer: Trail\\r\\n\\r\\n3\\r\\nabc\\r\\n5\\r\\n12345\\r\\n0\\r\\nTrail: test\\r\\n\\r\\n\"\n\tr := bytes.NewBufferString(s)\n\trb := bufio.NewReader(r)\n\terr := req.Read(rb)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %v\", err)\n\t}\n\texpectedBody := \"abc12345\"\n\tif string(req.Body()) != expectedBody {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", req.Body(), expectedBody)\n\t}\n\tverifyRequestHeader(t, &req.Header, -1, \"/foo\", \"google.com\", \"\", \"aa/bb\")\n\tverifyTrailer(t, &req.Header, map[string]string{\"Trail\": \"test\"})\n}\n\nfunc TestRequestChunkedEmpty(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\ts := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n0\\r\\n\\r\\n\"\n\tr := bytes.NewBufferString(s)\n\trb := bufio.NewReader(r)\n\terr := req.Read(rb)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %v\", err)\n\t}\n\texpectedBody := \"\"\n\tif string(req.Body()) != expectedBody {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", req.Body(), expectedBody)\n\t}\n\texpectRequestHeaderGet(t, &req.Header, HeaderTransferEncoding, \"\")\n}\n\n// See: https://github.com/erikdubbelboer/fasthttp/issues/34\nfunc TestRequestChunkedWhitespace(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\ts := \"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: aa/bb\\r\\n\\r\\n3  \\r\\nabc\\r\\n0\\r\\n\\r\\n\"\n\tr := bytes.NewBufferString(s)\n\trb := bufio.NewReader(r)\n\terr := req.Read(rb)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading chunked request: %v\", err)\n\t}\n\texpectedBody := \"abc\"\n\tif string(req.Body()) != expectedBody {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", req.Body(), expectedBody)\n\t}\n}\n\nfunc TestResponseReadWithoutBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 304 Not Modified\\r\\nContent-Type: aa\\r\\nContent-Length: 1235\\r\\n\\r\\n\", false,\n\t\t304, 1235, \"aa\")\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 204 Foo Bar\\r\\nContent-Type: aab\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\n\\r\\n\", false,\n\t\t204, -1, \"aab\")\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 123 AAA\\r\\nContent-Type: xxx\\r\\nContent-Length: 3434\\r\\n\\r\\n\", false,\n\t\t123, 3434, \"xxx\")\n\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP 200 OK\\r\\nContent-Type: text/xml\\r\\nContent-Length: 123\\r\\n\\r\\nfoobar\\r\\n\", true,\n\t\t200, 123, \"text/xml\")\n\n\t// '100 Continue' must be skipped.\n\ttestResponseReadWithoutBody(t, &resp, \"HTTP/1.1 100 Continue\\r\\nFoo-bar: baz\\r\\n\\r\\nHTTP/1.1 329 aaa\\r\\nContent-Type: qwe\\r\\nContent-Length: 894\\r\\n\\r\\n\", true,\n\t\t329, 894, \"qwe\")\n}\n\nfunc testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBody bool,\n\texpectedStatusCode, expectedContentLength int, expectedContentType string,\n) {\n\tt.Helper()\n\n\tr := bytes.NewBufferString(s)\n\trb := bufio.NewReader(r)\n\tresp.SkipBody = skipBody\n\terr := resp.Read(rb)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response without body: %v. response=%q\", err, s)\n\t}\n\tif len(resp.Body()) != 0 {\n\t\tt.Fatalf(\"Unexpected response body %q. Expected %q. response=%q\", resp.Body(), \"\", s)\n\t}\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, \"\")\n\n\t// verify that ordinal response is read after null-body response\n\tresp.SkipBody = false\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nContent-Length: 5\\r\\nContent-Type: bar\\r\\n\\r\\n56789aaa\",\n\t\t300, 5, \"bar\", \"56789\", nil)\n}\n\nfunc TestRequestSuccess(t *testing.T) {\n\tt.Parallel()\n\n\t// empty method, user-agent and body\n\ttestRequestSuccess(t, \"\", \"/foo/bar\", \"google.com\", \"\", \"\", MethodGet)\n\n\t// non-empty user-agent\n\ttestRequestSuccess(t, MethodGet, \"/foo/bar\", \"google.com\", \"MSIE\", \"\", MethodGet)\n\n\t// non-empty method\n\ttestRequestSuccess(t, MethodHead, \"/aaa\", \"fobar\", \"\", \"\", MethodHead)\n\n\t// POST method with body\n\ttestRequestSuccess(t, MethodPost, \"/bbb\", \"aaa.com\", \"Chrome aaa\", \"post body\", MethodPost)\n\n\t// PUT method with body\n\ttestRequestSuccess(t, MethodPut, \"/aa/bb\", \"a.com\", \"ome aaa\", \"put body\", MethodPut)\n\n\t// only host is set\n\ttestRequestSuccess(t, \"\", \"\", \"gooble.com\", \"\", \"\", MethodGet)\n\n\t// get with body\n\ttestRequestSuccess(t, MethodGet, \"/foo/bar\", \"aaa.com\", \"\", \"foobar\", MethodGet)\n}\n\nfunc TestResponseSuccess(t *testing.T) {\n\tt.Parallel()\n\n\t// 200 response\n\ttestResponseSuccess(t, 200, \"test/plain\", \"server\", \"foobar\",\n\t\t200, \"test/plain\", \"server\")\n\n\t// response with missing statusCode\n\ttestResponseSuccess(t, 0, \"text/plain\", \"server\", \"foobar\",\n\t\t200, \"text/plain\", \"server\")\n\n\t// response with missing server\n\ttestResponseSuccess(t, 500, \"aaa\", \"\", \"aaadfsd\",\n\t\t500, \"aaa\", \"\")\n\n\t// empty body\n\ttestResponseSuccess(t, 200, \"bbb\", \"qwer\", \"\",\n\t\t200, \"bbb\", \"qwer\")\n\n\t// missing content-type\n\ttestResponseSuccess(t, 200, \"\", \"asdfsd\", \"asdf\",\n\t\t200, string(defaultContentType), \"asdfsd\")\n}\n\nfunc testResponseSuccess(t *testing.T, statusCode int, contentType, serverName, body string,\n\texpectedStatusCode int, expectedContentType, expectedServerName string,\n) {\n\tvar resp Response\n\tresp.SetStatusCode(statusCode)\n\tresp.Header.Set(\"Content-Type\", contentType)\n\tresp.Header.Set(\"Server\", serverName)\n\tresp.SetBody([]byte(body))\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\terr := resp.Write(bw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Response.Write(): %v\", err)\n\t}\n\tif err = bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing bufio.Writer: %v\", err)\n\t}\n\n\tvar resp1 Response\n\tbr := bufio.NewReader(w)\n\tif err = resp1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Response.Read(): %v\", err)\n\t}\n\tif resp1.StatusCode() != expectedStatusCode {\n\t\tt.Fatalf(\"Unexpected status code: %d. Expected %d\", resp1.StatusCode(), expectedStatusCode)\n\t}\n\tif resp1.Header.ContentLength() != len(body) {\n\t\tt.Fatalf(\"Unexpected content-length: %d. Expected %d\", resp1.Header.ContentLength(), len(body))\n\t}\n\tif string(resp1.Header.Peek(HeaderContentType)) != expectedContentType {\n\t\tt.Fatalf(\"Unexpected content-type: %q. Expected %q\", resp1.Header.Peek(HeaderContentType), expectedContentType)\n\t}\n\tif string(resp1.Header.Peek(HeaderServer)) != expectedServerName {\n\t\tt.Fatalf(\"Unexpected server: %q. Expected %q\", resp1.Header.Peek(HeaderServer), expectedServerName)\n\t}\n\tif !bytes.Equal(resp1.Body(), []byte(body)) {\n\t\tt.Fatalf(\"Unexpected body: %q. Expected %q\", resp1.Body(), body)\n\t}\n}\n\nfunc TestRequestWriteError(t *testing.T) {\n\tt.Parallel()\n\n\t// no host\n\ttestRequestWriteError(t, \"\", \"/foo/bar\", \"\", \"\", \"\")\n}\n\nfunc testRequestWriteError(t *testing.T, method, requestURI, host, userAgent, body string) {\n\tvar req Request\n\n\treq.Header.SetMethod(method)\n\treq.Header.SetRequestURI(requestURI)\n\treq.Header.Set(HeaderHost, host)\n\treq.Header.Set(HeaderUserAgent, userAgent)\n\treq.SetBody([]byte(body))\n\n\tw := &bytebufferpool.ByteBuffer{}\n\tbw := bufio.NewWriter(w)\n\terr := req.Write(bw)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error when writing request=%#v\", &req)\n\t}\n}\n\nfunc testRequestSuccess(t *testing.T, method, requestURI, host, userAgent, body, expectedMethod string) {\n\tvar req Request\n\n\treq.Header.SetMethod(method)\n\treq.Header.SetRequestURI(requestURI)\n\treq.Header.Set(HeaderHost, host)\n\treq.Header.Set(HeaderUserAgent, userAgent)\n\treq.SetBody([]byte(body))\n\n\tcontentType := \"foobar\"\n\tif method == MethodPost {\n\t\treq.Header.Set(HeaderContentType, contentType)\n\t}\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\terr := req.Write(bw)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Request.Write(): %v\", err)\n\t}\n\tif err = bw.Flush(); err != nil {\n\t\tt.Fatalf(\"Unexpected error when flushing bufio.Writer: %v\", err)\n\t}\n\n\tvar req1 Request\n\tbr := bufio.NewReader(w)\n\tif err = req1.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when calling Request.Read(): %v\", err)\n\t}\n\tif string(req1.Header.Method()) != expectedMethod {\n\t\tt.Fatalf(\"Unexpected method: %q. Expected %q\", req1.Header.Method(), expectedMethod)\n\t}\n\tif requestURI == \"\" {\n\t\trequestURI = \"/\"\n\t}\n\tif string(req1.Header.RequestURI()) != requestURI {\n\t\tt.Fatalf(\"Unexpected RequestURI: %q. Expected %q\", req1.Header.RequestURI(), requestURI)\n\t}\n\tif string(req1.Header.Peek(HeaderHost)) != host {\n\t\tt.Fatalf(\"Unexpected host: %q. Expected %q\", req1.Header.Peek(HeaderHost), host)\n\t}\n\tif string(req1.Header.Peek(HeaderUserAgent)) != userAgent {\n\t\tt.Fatalf(\"Unexpected user-agent: %q. Expected %q\", req1.Header.Peek(HeaderUserAgent), userAgent)\n\t}\n\tif !bytes.Equal(req1.Body(), []byte(body)) {\n\t\tt.Fatalf(\"Unexpected body: %q. Expected %q\", req1.Body(), body)\n\t}\n\n\tif method == MethodPost && string(req1.Header.Peek(HeaderContentType)) != contentType {\n\t\tt.Fatalf(\"Unexpected content-type: %q. Expected %q\", req1.Header.Peek(HeaderContentType), contentType)\n\t}\n}\n\nfunc TestResponseReadSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &Response{}\n\n\t// usual response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Length: 10\\r\\nContent-Type: foo/bar\\r\\n\\r\\n0123456789\",\n\t\t200, 10, \"foo/bar\", \"0123456789\", nil)\n\n\t// zero response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 500 OK\\r\\nContent-Length: 0\\r\\nContent-Type: foo/bar\\r\\n\\r\\n\",\n\t\t500, 0, \"foo/bar\", \"\", nil)\n\n\t// response with trailer\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\t300, -1, \"bar\", \"56789\", map[string]string{\"Foo\": \"bar\"})\n\n\t// response with trailer disableNormalizing\n\tresp.Header.DisableNormalizing()\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 300 OK\\r\\nTransfer-Encoding: chunked\\r\\nContent-Type: bar\\r\\n\\r\\n5\\r\\n56789\\r\\n0\\r\\nfoo: bar\\r\\n\\r\\n\",\n\t\t300, -1, \"bar\", \"56789\", map[string]string{\"foo\": \"bar\"})\n\n\t// no content-length ('identity' transfer-encoding)\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: foobar\\r\\n\\r\\nzxxxx\",\n\t\t200, 5, \"foobar\", \"zxxxx\", nil)\n\n\t// explicitly stated 'Transfer-Encoding: identity'\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 234 ss\\r\\nContent-Type: xxx\\r\\n\\r\\nxag\",\n\t\t234, 3, \"xxx\", \"xag\", nil)\n\n\t// big 'identity' response\n\tbody := string(createFixedBody(100500))\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aa\\r\\n\\r\\n\"+body,\n\t\t200, 100500, \"aa\", body, nil)\n\n\t// chunked response\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\nqwer\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo2: bar2\\r\\n\\r\\n\",\n\t\t200, -1, \"text/html\", \"qwerty\", map[string]string{\"Foo2\": \"bar2\"})\n\n\t// chunked response with non-chunked Transfer-Encoding.\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 230 OK\\r\\nContent-Type: text\\r\\nTransfer-Encoding: aaabbb\\r\\n\\r\\n2\\r\\ner\\r\\n2\\r\\nty\\r\\n0\\r\\nFoo3: bar3\\r\\n\\r\\n\",\n\t\t230, -1, \"text\", \"erty\", map[string]string{\"Foo3\": \"bar3\"})\n\n\t// chunked response with content-length\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: foo/bar\\r\\nContent-Length: 123\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n4\\r\\ntest\\r\\n0\\r\\nFoo4:bar4\\r\\n\\r\\n\",\n\t\t200, -1, \"foo/bar\", \"test\", map[string]string{\"Foo4\": \"bar4\"})\n\n\t// chunked response with empty body\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n0\\r\\nFoo5: bar5\\r\\n\\r\\n\",\n\t\t200, -1, \"text/html\", \"\", map[string]string{\"Foo5\": \"bar5\"})\n\n\t// chunked response with chunk extension\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n3;ext\\r\\naaa\\r\\n0\\r\\nFoo6: bar6\\r\\n\\r\\n\",\n\t\t200, -1, \"text/html\", \"aaa\", map[string]string{\"Foo6\": \"bar6\"})\n}\n\nfunc TestResponseReadError(t *testing.T) {\n\tt.Parallel()\n\n\tresp := &Response{}\n\n\t// empty response\n\ttestResponseReadError(t, resp, \"\")\n\n\t// invalid header\n\ttestResponseReadError(t, resp, \"foobar\")\n\n\t// empty body\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 1234\\r\\n\\r\\n\")\n\n\t// invalid chunked body\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nContent-Length: 1234\\r\\n\\r\\nshort\")\n\n\t// chunked body without end chunk\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\nfoo\")\n\n\ttestResponseReadError(t, resp, \"HTTP/1.1 200 OK\\r\\nContent-Type: aaa\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n3\\r\\nfoo\")\n}\n\nfunc testResponseReadError(t *testing.T, resp *Response, response string) {\n\tr := bytes.NewBufferString(response)\n\trb := bufio.NewReader(r)\n\terr := resp.Read(rb)\n\tif err == nil {\n\t\tt.Fatalf(\"Expecting error for response=%q\", response)\n\t}\n\n\ttestResponseReadSuccess(t, resp, \"HTTP/1.1 303 Redisred sedfs sdf\\r\\nContent-Type: aaa\\r\\nContent-Length: 5\\r\\n\\r\\nHELLO\",\n\t\t303, 5, \"aaa\", \"HELLO\", nil)\n}\n\nfunc testResponseReadSuccess(t *testing.T, resp *Response, response string, expectedStatusCode, expectedContentLength int,\n\texpectedContentType, expectedBody string, expectedTrailer map[string]string,\n) {\n\tr := bytes.NewBufferString(response)\n\trb := bufio.NewReader(r)\n\terr := resp.Read(rb)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType, \"\")\n\tif !bytes.Equal(resp.Body(), []byte(expectedBody)) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), []byte(expectedBody))\n\t}\n\tverifyTrailer(t, &resp.Header, expectedTrailer)\n}\n\nfunc TestReadBodyFixedSize(t *testing.T) {\n\tt.Parallel()\n\n\t// zero-size body\n\ttestReadBodyFixedSize(t, 0)\n\n\t// small-size body\n\ttestReadBodyFixedSize(t, 3)\n\n\t// medium-size body\n\ttestReadBodyFixedSize(t, 1024)\n\n\t// large-size body\n\ttestReadBodyFixedSize(t, 1024*1024)\n\n\t// smaller body after big one\n\ttestReadBodyFixedSize(t, 34345)\n}\n\nfunc TestReadBodyChunked(t *testing.T) {\n\tt.Parallel()\n\n\t// zero-size body\n\ttestReadBodyChunked(t, 0)\n\n\t// small-size body\n\ttestReadBodyChunked(t, 5)\n\n\t// medium-size body\n\ttestReadBodyChunked(t, 43488)\n\n\t// big body\n\ttestReadBodyChunked(t, 3*1024*1024)\n\n\t// smaller body after big one\n\ttestReadBodyChunked(t, 12343)\n}\n\nfunc TestRequestURITLS(t *testing.T) {\n\tt.Parallel()\n\n\turiNoScheme := \"//foobar.com/baz/aa?bb=dd&dd#sdf\"\n\trequestURI := \"http:\" + uriNoScheme\n\trequestURITLS := \"https:\" + uriNoScheme\n\n\tvar req Request\n\n\treq.isTLS = true\n\treq.SetRequestURI(requestURI)\n\turi := req.URI().String()\n\tif uri != requestURITLS {\n\t\tt.Fatalf(\"unexpected request uri: %q. Expecting %q\", uri, requestURITLS)\n\t}\n\n\treq.Reset()\n\treq.SetRequestURI(requestURI)\n\turi = req.URI().String()\n\tif uri != requestURI {\n\t\tt.Fatalf(\"unexpected request uri: %q. Expecting %q\", uri, requestURI)\n\t}\n}\n\nfunc TestRequestURI(t *testing.T) {\n\tt.Parallel()\n\n\thost := \"foobar.com\"\n\trequestURI := \"/aaa/bb+b%20d?ccc=ddd&qqq#1334dfds&=d\"\n\texpectedPathOriginal := \"/aaa/bb+b%20d\"\n\texpectedPath := \"/aaa/bb+b d\"\n\texpectedQueryString := \"ccc=ddd&qqq\"\n\texpectedHash := \"1334dfds&=d\"\n\n\tvar req Request\n\treq.Header.Set(HeaderHost, host)\n\treq.Header.SetRequestURI(requestURI)\n\n\turi := req.URI()\n\tif string(uri.Host()) != host {\n\t\tt.Fatalf(\"Unexpected host %q. Expected %q\", uri.Host(), host)\n\t}\n\tif string(uri.PathOriginal()) != expectedPathOriginal {\n\t\tt.Fatalf(\"Unexpected source path %q. Expected %q\", uri.PathOriginal(), expectedPathOriginal)\n\t}\n\tif string(uri.Path()) != expectedPath {\n\t\tt.Fatalf(\"Unexpected path %q. Expected %q\", uri.Path(), expectedPath)\n\t}\n\tif string(uri.QueryString()) != expectedQueryString {\n\t\tt.Fatalf(\"Unexpected query string %q. Expected %q\", uri.QueryString(), expectedQueryString)\n\t}\n\tif string(uri.Hash()) != expectedHash {\n\t\tt.Fatalf(\"Unexpected hash %q. Expected %q\", uri.Hash(), expectedHash)\n\t}\n}\n\nfunc TestRequestPostArgsSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\ttestRequestPostArgsSuccess(t, &req, \"POST / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 0\\r\\n\\r\\n\", 0, \"foo=\", \"=\")\n\n\ttestRequestPostArgsSuccess(t, &req, \"POST / HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nContent-Length: 18\\r\\n\\r\\nfoo&b%20r=b+z=&qwe\", 3, \"foo=\", \"b r=b z=\", \"qwe=\")\n}\n\nfunc TestRequestPostArgsError(t *testing.T) {\n\tt.Parallel()\n\n\tvar req Request\n\n\t// non-post\n\ttestRequestPostArgsError(t, &req, \"GET /aa HTTP/1.1\\r\\nHost: aaa\\r\\n\\r\\n\")\n\n\t// invalid content-type\n\ttestRequestPostArgsError(t, &req, \"POST /aa HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: text/html\\r\\nContent-Length: 5\\r\\n\\r\\nabcde\")\n}\n\nfunc testRequestPostArgsError(t *testing.T, req *Request, s string) {\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\terr := req.Read(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading %q: %v\", s, err)\n\t}\n\tss := req.PostArgs().String()\n\tif ss != \"\" {\n\t\tt.Fatalf(\"unexpected post args: %q. Expecting empty post args\", ss)\n\t}\n}\n\nfunc testRequestPostArgsSuccess(t *testing.T, req *Request, s string, expectedArgsLen int, expectedArgs ...string) {\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\terr := req.Read(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading %q: %v\", s, err)\n\t}\n\n\targs := req.PostArgs()\n\tif args.Len() != expectedArgsLen {\n\t\tt.Fatalf(\"Unexpected args len %d. Expected %d for %q\", args.Len(), expectedArgsLen, s)\n\t}\n\tfor _, x := range expectedArgs {\n\t\ttmp := strings.SplitN(x, \"=\", 2)\n\t\tk := tmp[0]\n\t\tv := tmp[1]\n\t\tvv := string(args.Peek(k))\n\t\tif vv != v {\n\t\t\tt.Fatalf(\"Unexpected value for key %q: %q. Expected %q for %q\", k, vv, v, s)\n\t\t}\n\t}\n}\n\nfunc testReadBodyChunked(t *testing.T, bodySize int) {\n\tbody := createFixedBody(bodySize)\n\texpectedTrailer := map[string]string{\"Foo\": \"bar\"}\n\tchunkedBody := createChunkedBody(body, expectedTrailer, true)\n\n\tr := bytes.NewBuffer(chunkedBody)\n\tbr := bufio.NewReader(r)\n\tb, err := readBodyChunked(br, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error for bodySize=%d: %v. body=%q, chunkedBody=%q\", bodySize, err, body, chunkedBody)\n\t}\n\tif !bytes.Equal(b, body) {\n\t\tt.Fatalf(\"Unexpected response read for bodySize=%d: %q. Expected %q. chunkedBody=%q\", bodySize, b, body, chunkedBody)\n\t}\n}\n\nfunc testReadBodyFixedSize(t *testing.T, bodySize int) {\n\tbody := createFixedBody(bodySize)\n\tr := bytes.NewBuffer(body)\n\tbr := bufio.NewReader(r)\n\tb, err := readBody(br, bodySize, 0, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error in ReadResponseBody(%d): %v\", bodySize, err)\n\t}\n\tif !bytes.Equal(b, body) {\n\t\tt.Fatalf(\"Unexpected response read for bodySize=%d: %q. Expected %q\", bodySize, b, body)\n\t}\n}\n\nfunc createFixedBody(bodySize int) []byte {\n\tb := make([]byte, 0, bodySize)\n\tfor i := range bodySize {\n\t\tb = append(b, byte(i%10)+'0')\n\t}\n\treturn b\n}\n\nfunc createChunkedBody(body []byte, trailer map[string]string, withEnd bool) []byte {\n\tvar b []byte\n\tchunkSize := 1\n\tfor len(body) > 0 {\n\t\tif chunkSize > len(body) {\n\t\t\tchunkSize = len(body)\n\t\t}\n\t\tb = append(b, fmt.Appendf(nil, \"%x\\r\\n\", chunkSize)...)\n\t\tb = append(b, body[:chunkSize]...)\n\t\tb = append(b, []byte(\"\\r\\n\")...)\n\t\tbody = body[chunkSize:]\n\t\tchunkSize++\n\t}\n\tif withEnd {\n\t\tb = append(b, \"0\\r\\n\"...)\n\t\tfor k, v := range trailer {\n\t\t\tb = append(b, k...)\n\t\t\tb = append(b, \": \"...)\n\t\t\tb = append(b, v...)\n\t\t\tb = append(b, \"\\r\\n\"...)\n\t\t}\n\t\tb = append(b, \"\\r\\n\"...)\n\t}\n\treturn b\n}\n\nfunc TestWriteMultipartForm(t *testing.T) {\n\tt.Parallel()\n\n\tvar w bytes.Buffer\n\ts := strings.ReplaceAll(`--foo\nContent-Disposition: form-data; name=\"key\"\n\nvalue\n--foo\nContent-Disposition: form-data; name=\"file\"; filename=\"test.json\"\nContent-Type: application/json\n\n{\"foo\": \"bar\"}\n--foo--\n`, \"\\n\", \"\\r\\n\")\n\tmr := multipart.NewReader(strings.NewReader(s), \"foo\")\n\tform, err := mr.ReadForm(1024)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif err := WriteMultipartForm(&w, form, \"foo\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif w.String() != s {\n\t\tt.Fatalf(\"unexpected output %q\", w.Bytes())\n\t}\n}\n\nfunc TestResponseRawBodySet(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\texpectedS := \"test\"\n\tbody := []byte(expectedS)\n\tresp.SetBodyRaw(body)\n\n\ttestBodyWriteTo(t, &resp, expectedS, true)\n}\n\nfunc TestRequestRawBodySet(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\texpectedS := \"test\"\n\tbody := []byte(expectedS)\n\tr.SetBodyRaw(body)\n\n\ttestBodyWriteTo(t, &r, expectedS, true)\n}\n\nfunc TestResponseRawBodyReset(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\tbody := []byte(\"test\")\n\tresp.SetBodyRaw(body)\n\tresp.ResetBody()\n\n\ttestBodyWriteTo(t, &resp, \"\", true)\n}\n\nfunc TestRequestRawBodyReset(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Request\n\n\tbody := []byte(\"test\")\n\tr.SetBodyRaw(body)\n\tr.ResetBody()\n\n\ttestBodyWriteTo(t, &r, \"\", true)\n}\n\nfunc TestResponseRawBodyCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar resp Response\n\n\texpectedS := \"test\"\n\tbody := []byte(expectedS)\n\tresp.SetBodyRaw(body)\n\n\ttestResponseCopyTo(t, &resp)\n}\n\nfunc TestRequestRawBodyCopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar a Request\n\n\tbody := []byte(\"test\")\n\ta.SetBodyRaw(body)\n\n\tvar b Request\n\n\ta.CopyTo(&b)\n\n\ttestBodyWriteTo(t, &a, \"test\", true)\n\ttestBodyWriteTo(t, &b, \"test\", true)\n}\n\ntype testReader struct {\n\tread    chan int\n\tcb      chan struct{}\n\tonClose func() error\n}\n\nfunc (r *testReader) Read(b []byte) (int, error) {\n\tread := <-r.read\n\n\tif read == -1 {\n\t\treturn 0, io.EOF\n\t}\n\n\tr.cb <- struct{}{}\n\n\tfor i := range read {\n\t\tb[i] = 'x'\n\t}\n\n\treturn read, nil\n}\n\nfunc (r *testReader) Close() error {\n\tif r.onClose != nil {\n\t\treturn r.onClose()\n\t}\n\treturn nil\n}\n\nfunc TestResponseImmediateHeaderFlushRegressionFixedLength(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"aaabbbccc\"\n\tbuf := bytes.NewBufferString(expectedS)\n\tr.SetBodyStream(buf, len(expectedS))\n\tr.ImmediateHeaderFlush = true\n\n\ttestBodyWriteTo(t, &r, expectedS, false)\n}\n\nfunc TestResponseImmediateHeaderFlushRegressionChunked(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\texpectedS := \"aaabbbccc\"\n\tbuf := bytes.NewBufferString(expectedS)\n\tr.SetBodyStream(buf, -1)\n\tr.ImmediateHeaderFlush = true\n\n\ttestBodyWriteTo(t, &r, expectedS, false)\n}\n\nfunc TestResponseImmediateHeaderFlushFixedLength(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tr.ImmediateHeaderFlush = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, 3)\n\n\tw := &bytes.Buffer{}\n\tbb := bufio.NewWriter(w)\n\n\tbw := &r\n\n\twaitForIt := make(chan struct{})\n\n\tgo func() {\n\t\tif err := bw.Write(bb); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\twaitForIt <- struct{}{}\n\t}()\n\n\tch <- 3\n\n\tif !strings.Contains(w.String(), \"Content-Length: 3\") {\n\t\tt.Fatalf(\"Expected headers to be flushed\")\n\t}\n\n\tif strings.Contains(w.String(), \"xxx\") {\n\t\tt.Fatalf(\"Did not expect body to be written yet\")\n\t}\n\n\t<-cb\n\tch <- -1\n\n\t<-waitForIt\n}\n\nfunc TestResponseImmediateHeaderFlushFixedLengthSkipBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tr.ImmediateHeaderFlush = true\n\tr.SkipBody = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, 0)\n\n\tw := &bytes.Buffer{}\n\tbb := bufio.NewWriter(w)\n\n\tvar headersOnClose string\n\tbuf.onClose = func() error {\n\t\theadersOnClose = w.String()\n\t\treturn nil\n\t}\n\n\tbw := &r\n\n\tif err := bw.Write(bb); err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n\n\tif !strings.Contains(headersOnClose, \"Content-Length: 0\") {\n\t\tt.Fatalf(\"Expected headers to be eagerly flushed\")\n\t}\n}\n\nfunc TestResponseImmediateHeaderFlushChunked(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tr.ImmediateHeaderFlush = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, -1)\n\n\tw := &bytes.Buffer{}\n\tbb := bufio.NewWriter(w)\n\n\tbw := &r\n\n\twaitForIt := make(chan struct{})\n\n\tgo func() {\n\t\tif err := bw.Write(bb); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\twaitForIt <- struct{}{}\n\t}()\n\n\tch <- 3\n\n\tif !strings.Contains(w.String(), \"Transfer-Encoding: chunked\") {\n\t\tt.Fatalf(\"Expected headers to be flushed\")\n\t}\n\n\tif strings.Contains(w.String(), \"xxx\") {\n\t\tt.Fatalf(\"Did not expect body to be written yet\")\n\t}\n\n\t<-cb\n\tch <- -1\n\n\t<-waitForIt\n}\n\nfunc TestResponseImmediateHeaderFlushChunkedNoBody(t *testing.T) {\n\tt.Parallel()\n\n\tvar r Response\n\n\tr.ImmediateHeaderFlush = true\n\tr.SkipBody = true\n\n\tch := make(chan int)\n\tcb := make(chan struct{})\n\n\tbuf := &testReader{read: ch, cb: cb}\n\n\tr.SetBodyStream(buf, -1)\n\n\tw := &bytes.Buffer{}\n\tbb := bufio.NewWriter(w)\n\n\tvar headersOnClose string\n\tbuf.onClose = func() error {\n\t\theadersOnClose = w.String()\n\t\treturn nil\n\t}\n\n\tbw := &r\n\n\tif err := bw.Write(bb); err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n\n\tif !strings.Contains(headersOnClose, \"Transfer-Encoding: chunked\") {\n\t\tt.Fatalf(\"Expected headers to be eagerly flushed\")\n\t}\n}\n\ntype ErroneousBodyStream struct {\n\terrOnRead  bool\n\terrOnClose bool\n}\n\nfunc (ebs *ErroneousBodyStream) Read(p []byte) (n int, err error) {\n\tif ebs.errOnRead {\n\t\tpanic(\"reading erroneous body stream\")\n\t}\n\treturn 0, io.EOF\n}\n\nfunc (ebs *ErroneousBodyStream) Close() error {\n\tif ebs.errOnClose {\n\t\tpanic(\"closing erroneous body stream\")\n\t}\n\treturn nil\n}\n\nfunc TestResponseBodyStreamErrorOnPanicDuringRead(t *testing.T) {\n\tt.Parallel()\n\tvar resp Response\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\n\tebs := &ErroneousBodyStream{errOnRead: true, errOnClose: false}\n\tresp.SetBodyStream(ebs, 42)\n\terr := resp.Write(bw)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error when writing response.\")\n\t}\n\te, ok := err.(*ErrBodyStreamWritePanic)\n\tif !ok {\n\t\tt.Fatalf(\"expected error struct to be *ErrBodyStreamWritePanic, got: %+v.\", e)\n\t}\n\tif e.Error() != \"panic while writing body stream: reading erroneous body stream\" {\n\t\tt.Fatalf(\"unexpected error value, got: %+v.\", e.Error())\n\t}\n}\n\nfunc TestResponseBodyStreamErrorOnPanicDuringClose(t *testing.T) {\n\tt.Parallel()\n\tvar resp Response\n\tvar w bytes.Buffer\n\tbw := bufio.NewWriter(&w)\n\n\tebs := &ErroneousBodyStream{errOnRead: false, errOnClose: true}\n\tresp.SetBodyStream(ebs, 42)\n\terr := resp.Write(bw)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error when writing response.\")\n\t}\n\te, ok := err.(*ErrBodyStreamWritePanic)\n\tif !ok {\n\t\tt.Fatalf(\"expected error struct to be *ErrBodyStreamWritePanic, got: %+v.\", e)\n\t}\n\tif e.Error() != \"panic while writing body stream: closing erroneous body stream\" {\n\t\tt.Fatalf(\"unexpected error value, got: %+v.\", e.Error())\n\t}\n}\n\nfunc TestResponseBodyStream(t *testing.T) {\n\tt.Parallel()\n\tchunkedResp := \"HTTP/1.1 200 OK\\r\\n\" + \"Transfer-Encoding: chunked\\r\\n\" + \"\\r\\n\" + \"6\\r\\n123456\\r\\n\" + \"7\\r\\n1234567\\r\\n\" + \"0\\r\\n\\r\\n\"\n\tsimpleResp := \"HTTP/1.1 200 OK\\r\\n\" + \"Content-Length: 9\\r\\n\" + \"\\r\\n\" + \"123456789\"\n\tt.Run(\"read chunked response\", func(t *testing.T) {\n\t\tresponse := AcquireResponse()\n\t\tresponse.StreamBody = true\n\t\tif err := response.Read(bufio.NewReader(bytes.NewBufferString(chunkedResp))); err != nil {\n\t\t\tt.Fatalf(\"parse response find err: %v\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := response.closeBodyStream(nil); err != nil {\n\t\t\t\tt.Fatalf(\"close body stream err: %v\", err)\n\t\t\t}\n\t\t}()\n\t\tbody, err := io.ReadAll(response.bodyStream)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read body stream err: %v\", err)\n\t\t}\n\t\tif string(body) != \"1234561234567\" {\n\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(body), \"1234561234567\")\n\t\t}\n\t})\n\tt.Run(\"read simple response\", func(t *testing.T) {\n\t\tresp := AcquireResponse()\n\t\tresp.StreamBody = true\n\t\terr := resp.ReadLimitBody(bufio.NewReader(bytes.NewBufferString(simpleResp)), 8)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read limit body err: %v\", err)\n\t\t}\n\t\tbody := resp.BodyStream()\n\t\tdefer func() {\n\t\t\tif err := resp.CloseBodyStream(); err != nil {\n\t\t\t\tt.Fatalf(\"close body stream err: %v\", err)\n\t\t\t}\n\t\t}()\n\t\tcontent, err := io.ReadAll(body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read limit body err: %v\", err)\n\t\t}\n\t\tif string(content) != \"123456789\" {\n\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(content), \"123456789\")\n\t\t}\n\t})\n\tt.Run(\"http client\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tserver := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {\n\t\t\tif request.URL.Query().Get(\"chunked\") == \"true\" {\n\t\t\t\tfor x := range 10 {\n\t\t\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t\t\twriter.Write([]byte(strconv.Itoa(x))) //nolint:errcheck\n\t\t\t\t\twriter.(http.Flusher).Flush()\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\twriter.Write([]byte(`hello world`)) //nolint:errcheck\n\t\t}))\n\n\t\tt.Cleanup(server.Close)\n\n\t\tt.Run(\"normal request\", func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tclient := Client{StreamResponseBody: true}\n\t\t\tresp := AcquireResponse()\n\t\t\trequest := AcquireRequest()\n\t\t\trequest.SetRequestURI(server.URL)\n\t\t\tif err := client.Do(request, resp); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tstream := resp.BodyStream()\n\t\t\tdefer func() {\n\t\t\t\tReleaseResponse(resp)\n\t\t\t}()\n\t\t\tcontent, _ := io.ReadAll(stream)\n\t\t\tif string(content) != \"hello world\" {\n\t\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(content), \"hello world\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"limit response body size\", func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tclient := Client{StreamResponseBody: true, MaxResponseBodySize: 20}\n\t\t\tresp := AcquireResponse()\n\t\t\trequest := AcquireRequest()\n\t\t\trequest.SetRequestURI(server.URL)\n\t\t\tif err := client.Do(request, resp); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tstream := resp.BodyStream()\n\t\t\tdefer func() {\n\t\t\t\tif err := resp.CloseBodyStream(); err != nil {\n\t\t\t\t\tt.Fatalf(\"close stream err: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tcontent, _ := io.ReadAll(stream)\n\t\t\tif string(content) != \"hello world\" {\n\t\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(content), \"hello world\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"chunked\", func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tclient := Client{StreamResponseBody: true}\n\t\t\tresp := AcquireResponse()\n\t\t\trequest := AcquireRequest()\n\t\t\trequest.SetRequestURI(server.URL + \"?chunked=true\")\n\t\t\tif err := client.Do(request, resp); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tstream := resp.BodyStream()\n\t\t\tdefer func() {\n\t\t\t\tif err := resp.CloseBodyStream(); err != nil {\n\t\t\t\t\tt.Fatalf(\"close stream err: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tcontent, _ := io.ReadAll(stream)\n\t\t\tif string(content) != \"0123456789\" {\n\t\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(content), \"0123456789\")\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"identity\", func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tclient := Client{StreamResponseBody: true, DisablePathNormalizing: true}\n\t\t\tresp := AcquireResponse()\n\t\t\trequest := AcquireRequest()\n\t\t\trequest.SetRequestURI(server.URL + \"?400BadRequest\")\n\t\t\tif err := client.Do(request, resp); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tstream := resp.BodyStream()\n\t\t\tdefer func() {\n\t\t\t\tif err := resp.CloseBodyStream(); err != nil {\n\t\t\t\t\tt.Fatalf(\"close stream err: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tcontent, err := io.ReadAll(stream)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif string(content) != \"400 Bad Request\" {\n\t\t\t\tt.Fatalf(\"unexpected body content, got: %#v, want: %#v\", string(content), \"400 Bad Request\")\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestRequestMultipartFormPipeEmptyFormField(t *testing.T) {\n\tt.Parallel()\n\n\tpr, pw := io.Pipe()\n\tmw := multipart.NewWriter(pw)\n\n\terrs := make(chan error, 1)\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\terr := mw.Close()\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t\terr = pw.Close()\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t\tclose(errs)\n\t\t}()\n\n\t\tif err := mw.WriteField(\"emptyField\", \"\"); err != nil {\n\t\t\terrs <- err\n\t\t}\n\t}()\n\n\tvar b bytes.Buffer\n\tbw := bufio.NewWriter(&b)\n\terr := writeBodyChunked(bw, pr)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tfor e := range errs {\n\t\tt.Fatalf(\"unexpected error in goroutine multiwriter: %v\", e)\n\t}\n\n\ttestRequestMultipartFormPipeEmptyFormField(t, mw.Boundary(), b.Bytes(), 1)\n}\n\nfunc testRequestMultipartFormPipeEmptyFormField(t *testing.T, boundary string, formData []byte, partsCount int) []byte {\n\ts := fmt.Sprintf(\"POST / HTTP/1.1\\r\\nHost: aaa\\r\\nContent-Type: multipart/form-data; boundary=%s\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n%s\",\n\t\tboundary, formData)\n\n\tvar req Request\n\n\tr := bytes.NewBufferString(s)\n\tbr := bufio.NewReader(r)\n\tif err := req.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tf, err := req.MultipartForm()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer req.RemoveMultipartFormFiles()\n\n\tif len(f.File) > 0 {\n\t\tt.Fatalf(\"unexpected files found in the multipart form: %d\", len(f.File))\n\t}\n\n\tif len(f.Value) != partsCount {\n\t\tt.Fatalf(\"unexpected number of values found: %d. Expecting %d\", len(f.Value), partsCount)\n\t}\n\n\tfor k, vv := range f.Value {\n\t\tif len(vv) != 1 {\n\t\t\tt.Fatalf(\"unexpected number of values found for key=%q: %d. Expecting 1\", k, len(vv))\n\t\t}\n\t\tif k != \"emptyField\" {\n\t\t\tt.Fatalf(\"unexpected key=%q. Expecting %q\", k, \"emptyField\")\n\t\t}\n\n\t\tv := vv[0]\n\t\tif v != \"\" {\n\t\t\tt.Fatalf(\"unexpected value=%q. expecting %q\", v, \"\")\n\t\t}\n\t}\n\n\treturn req.Body()\n}\n\nfunc TestReqCopeToRace(t *testing.T) {\n\treq := AcquireRequest()\n\treqs := make([]*Request, 1000)\n\tfor i := range 1000 {\n\t\treq.SetBodyRaw([]byte(strconv.Itoa(i)))\n\t\ttmpReq := AcquireRequest()\n\t\treq.CopyTo(tmpReq)\n\t\treqs[i] = tmpReq\n\t}\n\tfor i := range 1000 {\n\t\tif strconv.Itoa(i) != string(reqs[i].Body()) {\n\t\t\tt.Fatalf(\"Unexpected req body %s. Expected %s\", string(reqs[i].Body()), strconv.Itoa(i))\n\t\t}\n\t}\n}\n\nfunc TestRespCopeToRace(t *testing.T) {\n\tresp := AcquireResponse()\n\tresps := make([]*Response, 1000)\n\tfor i := range 1000 {\n\t\tresp.SetBodyRaw([]byte(strconv.Itoa(i)))\n\t\ttmpResq := AcquireResponse()\n\t\tresp.CopyTo(tmpResq)\n\t\tresps[i] = tmpResq\n\t}\n\tfor i := range 1000 {\n\t\tif strconv.Itoa(i) != string(resps[i].Body()) {\n\t\t\tt.Fatalf(\"Unexpected resp body %s. Expected %s\", string(resps[i].Body()), strconv.Itoa(i))\n\t\t}\n\t}\n}\n\nfunc TestRequestGetTimeOut(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\ttimeout  time.Duration\n\t\texpected time.Duration\n\t}{\n\t\t{\"Timeout set to 0\", 0, 0},\n\t\t{\"Timeout set to 5s\", 5 * time.Second, 5 * time.Second},\n\t\t{\"Timeout set to 1m\", 1 * time.Minute, 1 * time.Minute},\n\t\t{\"Timeout set to 500ms\", 500 * time.Millisecond, 500 * time.Millisecond},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\treq := &Request{timeout: test.timeout}\n\n\t\t\tif got := req.GetTimeOut(); got != test.expected {\n\t\t\t\tt.Errorf(\"GetTimeOut() = %v, want %v\", got, test.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "http_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc BenchmarkCopyZeroAllocOSFileToBytesBuffer(b *testing.B) {\n\tr, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer r.Close()\n\n\tbuf := &bytes.Buffer{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tbuf.Reset()\n\t\t_, err = copyZeroAlloc(buf, r)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocBytesBufferToOSFile(b *testing.B) {\n\tf, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer f.Close()\n\n\tbuf := &bytes.Buffer{}\n\t_, err = io.Copy(buf, f)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\ttmp, err := os.CreateTemp(os.TempDir(), \"test_*\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer os.Remove(tmp.Name())\n\n\tw, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0o444)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer w.Close()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := w.Seek(0, 0)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = copyZeroAlloc(w, buf)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocOSFileToStringsBuilder(b *testing.B) {\n\tr, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatalf(\"Failed to open testing file: %v\", err)\n\t}\n\tdefer r.Close()\n\n\tw := &strings.Builder{}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tw.Reset()\n\t\t_, err = copyZeroAlloc(w, r)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocIOLimitedReaderToOSFile(b *testing.B) {\n\tf, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer f.Close()\n\n\tr := io.LimitReader(f, 1024)\n\n\ttmp, err := os.CreateTemp(os.TempDir(), \"test_*\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer os.Remove(tmp.Name())\n\n\tw, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0o444)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer w.Close()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := w.Seek(0, 0)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = copyZeroAlloc(w, r)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocOSFileToOSFile(b *testing.B) {\n\tr, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer r.Close()\n\n\tf, err := os.CreateTemp(os.TempDir(), \"test_*\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer os.Remove(f.Name())\n\n\tw, err := os.OpenFile(f.Name(), os.O_WRONLY, 0o444)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer w.Close()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := w.Seek(0, 0)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\t_, err = copyZeroAlloc(w, r)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocOSFileToNetConn(b *testing.B) {\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\taddr := ln.Addr().String()\n\tdefer ln.Close()\n\n\tdone := make(chan struct{})\n\tdefer close(done)\n\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tb.Error(err)\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\t_, err := io.Copy(io.Discard, conn)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer conn.Close()\n\n\tfile, err := os.Open(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer file.Close()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tif _, err := copyZeroAlloc(conn, file); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkCopyZeroAllocNetConnToOSFile(b *testing.B) {\n\tdata, err := os.ReadFile(\"./README.md\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\taddr := ln.Addr().String()\n\tdefer ln.Close()\n\n\tdone := make(chan struct{})\n\tdefer close(done)\n\n\twriteDone := make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tconn, err := ln.Accept()\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err = conn.Write(data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Error(err)\n\t\t\t\t}\n\t\t\t\tconn.Close()\n\t\t\t\twriteDone <- struct{}{}\n\t\t\t}\n\t\t}\n\t}()\n\n\ttmp, err := os.CreateTemp(os.TempDir(), \"test_*\")\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer os.Remove(tmp.Name())\n\n\tfile, err := os.OpenFile(tmp.Name(), os.O_WRONLY, 0o444)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer file.Close()\n\n\tconn, err := net.Dial(\"tcp\", addr)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tdefer conn.Close()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tb.StopTimer()\n\t\t<-writeDone\n\t\t_, err = file.Seek(0, 0)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tb.StartTimer()\n\t\t_, err = copyZeroAlloc(file, conn)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tb.StopTimer()\n\t\tconn, err = net.Dial(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ipv6.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n)\n\nvar (\n\terrInvalidIPv6Host    = errors.New(\"invalid IPv6 host\")\n\terrInvalidIPv6Zone    = errors.New(\"invalid IPv6 zone\")\n\terrInvalidIPv6Address = errors.New(\"invalid IPv6 address\")\n)\n\nfunc validateIPv6Literal(host []byte) error {\n\tif len(host) == 0 || host[0] != '[' {\n\t\treturn nil\n\t}\n\tend := bytes.IndexByte(host, ']')\n\tif end < 0 || end == 1 {\n\t\treturn errInvalidIPv6Host\n\t}\n\taddr := host[1:end]\n\n\t// Optional zone.\n\tif zi := bytes.IndexByte(addr, '%'); zi >= 0 {\n\t\tif zi == len(addr)-1 {\n\t\t\treturn errInvalidIPv6Zone\n\t\t}\n\t\taddr = addr[:zi]\n\t}\n\n\t// Must have a colon to be IPv6.\n\tif bytes.IndexByte(addr, ':') < 0 {\n\t\treturn errInvalidIPv6Address\n\t}\n\n\t// IPv4-embedded?\n\tif bytes.IndexByte(addr, '.') >= 0 {\n\t\tlastColon := bytes.LastIndexByte(addr, ':')\n\t\tif lastColon < 0 || lastColon == len(addr)-1 {\n\t\t\treturn errInvalidIPv6Address\n\t\t}\n\n\t\tipv4 := addr[lastColon+1:]\n\t\tif !validIPv4(ipv4) {\n\t\t\treturn errInvalidIPv6Address\n\t\t}\n\n\t\thead := addr[:lastColon]\n\t\tseenDoubleAtSplit := lastColon > 0 && addr[lastColon-1] == ':'\n\t\tif seenDoubleAtSplit {\n\t\t\thead = addr[:lastColon-1]\n\t\t}\n\n\t\thextets, seenDoubleHead, ok := parseIPv6Hextets(head, false)\n\t\tif !ok {\n\t\t\treturn errInvalidIPv6Address\n\t\t}\n\n\t\tif seenDoubleHead && seenDoubleAtSplit {\n\t\t\treturn errInvalidIPv6Address\n\t\t}\n\n\t\thextets += 2 // IPv4 tail = 2 hextets\n\t\tseenDouble := seenDoubleHead || seenDoubleAtSplit\n\n\t\t// '::' must compress at least one hextet.\n\t\tif (!seenDouble && hextets != 8) || (seenDouble && hextets >= 8) {\n\t\t\treturn errInvalidIPv6Address\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Pure IPv6\n\thextets, seenDouble, ok := parseIPv6Hextets(addr, false)\n\tif !ok {\n\t\treturn errInvalidIPv6Address\n\t}\n\tif (!seenDouble && hextets != 8) || (seenDouble && hextets >= 8) {\n\t\treturn errInvalidIPv6Address\n\t}\n\treturn nil\n}\n\nfunc parseIPv6Hextets(s []byte, allowTrailingColon bool) (groups int, seenDouble, ok bool) {\n\tn := len(s)\n\tif n == 0 {\n\t\treturn 0, false, true\n\t}\n\ti := 0\n\tjustSawDouble := false\n\n\tfor i < n {\n\t\tif s[i] == ':' {\n\t\t\tif i+1 < n && s[i+1] == ':' {\n\t\t\t\tif seenDouble || justSawDouble {\n\t\t\t\t\treturn 0, false, false\n\t\t\t\t}\n\t\t\t\tseenDouble = true\n\t\t\t\tjustSawDouble = true\n\t\t\t\ti += 2\n\t\t\t\tif i == n {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif i == 0 {\n\t\t\t\treturn 0, false, false\n\t\t\t}\n\t\t\tif justSawDouble {\n\t\t\t\treturn 0, false, false\n\t\t\t}\n\t\t\tif i == n-1 {\n\t\t\t\tif allowTrailingColon {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn 0, false, false\n\t\t\t}\n\t\t\tif !ishex(s[i+1]) {\n\t\t\t\treturn 0, false, false\n\t\t\t}\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\n\t\tjustSawDouble = false\n\t\tcnt := 0\n\t\tfor cnt < 4 && i < n && ishex(s[i]) {\n\t\t\ti++\n\t\t\tcnt++\n\t\t}\n\t\tif cnt == 0 {\n\t\t\treturn 0, false, false\n\t\t}\n\t\tgroups++\n\n\t\tif i < n && s[i] != ':' {\n\t\t\treturn 0, false, false\n\t\t}\n\t}\n\treturn groups, seenDouble, true\n}\n\n// validIPv4 validates a dotted-quad (exactly 4 parts, 0..255) with no leading zeros\n// unless the octet is exactly \"0\".\nfunc validIPv4(s []byte) bool {\n\tparts := 0\n\ti := 0\n\tn := len(s)\n\n\tfor parts < 4 {\n\t\tif i >= n {\n\t\t\treturn false\n\t\t}\n\n\t\tstart := i\n\t\tval := 0\n\t\tdigits := 0\n\n\t\tfor i < n {\n\t\t\tc := s[i]\n\t\t\tif c < '0' || c > '9' {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tval = val*10 + int(c-'0')\n\t\t\tif val > 255 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\ti++\n\t\t\tdigits++\n\t\t\tif digits > 3 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\tif digits == 0 {\n\t\t\treturn false\n\t\t}\n\n\t\t// Disallow leading zeros like \"00\", \"01\", \"001\".\n\t\t// Allowed: exactly \"0\" or any number that doesn't start with '0'.\n\t\tif digits > 1 && s[start] == '0' {\n\t\t\treturn false\n\t\t}\n\n\t\tparts++\n\t\tif parts == 4 {\n\t\t\treturn i == n // must consume all input\n\t\t}\n\t\tif i >= n || s[i] != '.' {\n\t\t\treturn false\n\t\t}\n\t\ti++ // skip dot\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "ipv6_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\t\"testing\"\n)\n\n// oracleValid replicates the original function's semantics using net.ParseIP:\n// - Input must start with '['\n// - There must be a closing ']' and a non-empty address between\n// - Optional %zone allowed but must not be empty\n// - Zone is stripped before checking with net.ParseIP\n// - Must contain a ':' to be IPv6 (prevents raw IPv4-in-brackets).\nfunc oracleValid(host []byte) bool {\n\tif len(host) == 0 || host[0] != '[' {\n\t\t// Original function: non-bracketed hosts return nil (treated as valid/no-op).\n\t\treturn true\n\t}\n\n\tend := bytes.IndexByte(host, ']')\n\tif end < 0 {\n\t\treturn false\n\t}\n\taddr := host[1:end]\n\tif len(addr) == 0 {\n\t\treturn false\n\t}\n\n\t// Split off %zone (if present).\n\tif zi := bytes.IndexByte(addr, '%'); zi >= 0 {\n\t\t// Zone must not be empty.\n\t\tif zi == len(addr)-1 {\n\t\t\treturn false\n\t\t}\n\t\taddr = addr[:zi]\n\t}\n\n\t// Must contain ':' to be IPv6.\n\tif bytes.IndexByte(addr, ':') < 0 {\n\t\treturn false\n\t}\n\n\t// Use net.ParseIP on the de-zoned address (this was the original check).\n\tif ip := net.ParseIP(string(addr)); ip == nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc FuzzValidateIPv6Literal(f *testing.F) {\n\tseeds := [][]byte{\n\t\t[]byte(\"\"),            // non-bracketed => valid (no-op)\n\t\t[]byte(\"example.com\"), // non-bracketed => valid (no-op)\n\t\t[]byte(\"[\"),           // unterminated\n\t\t[]byte(\"[]\"),          // empty\n\t\t[]byte(\"[::]\"),\n\t\t[]byte(\"[::1]\"),\n\t\t[]byte(\"[2001:db8::1]\"),\n\t\t[]byte(\"[2001:db8::]\"),\n\t\t[]byte(\"[::ffff:192.168.0.1]\"),\n\t\t[]byte(\"[fe80::1%eth0]\"),\n\t\t[]byte(\"[fe80::1%]\"),         // empty zone\n\t\t[]byte(\"[1234]\"),             // no colon\n\t\t[]byte(\"[2001:db8:zzzz::1]\"), // invalid hex\n\t\t[]byte(\"[::ffff:256.0.0.1]\"), // invalid v4 tail\n\t\t[]byte(\"[2001:db8:::1]\"),     // triple colon\n\t\t[]byte(\"[::1]:443\"),          // trailing port outside ']' is ignored by validator\n\t\t[]byte(\"[2001:db8:0:0:0:0:2:1]\"),\n\t\t[]byte(\"[2001:db8:0:0:0:0:2:1%en0]\"),\n\t}\n\tfor _, s := range seeds {\n\t\tf.Add(s)\n\t}\n\n\tf.Fuzz(func(t *testing.T, host []byte) {\n\t\tgotErr := validateIPv6Literal(host)\n\t\twantValid := oracleValid(host)\n\n\t\tif (gotErr == nil) != wantValid {\n\t\t\tt.Fatalf(\"mismatch for %q: validateIPv6Literal err=%v, oracleValid=%v\",\n\t\t\t\tb2s(host), gotErr, wantValid)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "lbclient.go",
    "content": "package fasthttp\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n// BalancingClient is the interface for clients, which may be passed\n// to LBClient.Clients.\ntype BalancingClient interface {\n\tDoDeadline(req *Request, resp *Response, deadline time.Time) error\n\tPendingRequests() int\n}\n\n// LBClient balances requests among available LBClient.Clients.\n//\n// It has the following features:\n//\n//   - Balances load among available clients using 'least loaded' + 'least total'\n//     hybrid technique.\n//   - Dynamically decreases load on unhealthy clients.\n//\n// It is forbidden copying LBClient instances. Create new instances instead.\n//\n// It is safe calling LBClient methods from concurrently running goroutines.\ntype LBClient struct {\n\tnoCopy noCopy\n\n\t// HealthCheck is a callback called after each request.\n\t//\n\t// The request, response and the error returned by the client\n\t// is passed to HealthCheck, so the callback may determine whether\n\t// the client is healthy.\n\t//\n\t// Load on the current client is decreased if HealthCheck returns false.\n\t//\n\t// By default HealthCheck returns false if err != nil.\n\tHealthCheck func(req *Request, resp *Response, err error) bool\n\n\t// Clients must contain non-zero clients list.\n\t// Incoming requests are balanced among these clients.\n\tClients []BalancingClient\n\n\tcs []*lbClient\n\n\t// Timeout is the request timeout used when calling LBClient.Do.\n\t//\n\t// DefaultLBClientTimeout is used by default.\n\tTimeout time.Duration\n\n\tmu sync.RWMutex\n\n\tonce sync.Once\n}\n\n// DefaultLBClientTimeout is the default request timeout used by LBClient\n// when calling LBClient.Do.\n//\n// The timeout may be overridden via LBClient.Timeout.\nconst DefaultLBClientTimeout = time.Second\n\n// DoDeadline calls DoDeadline on the least loaded client.\nfunc (cc *LBClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\treturn cc.get().DoDeadline(req, resp, deadline)\n}\n\n// DoTimeout calculates deadline and calls DoDeadline on the least loaded client.\nfunc (cc *LBClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {\n\tdeadline := time.Now().Add(timeout)\n\treturn cc.get().DoDeadline(req, resp, deadline)\n}\n\n// Do calculates timeout using LBClient.Timeout and calls DoTimeout\n// on the least loaded client.\nfunc (cc *LBClient) Do(req *Request, resp *Response) error {\n\ttimeout := cc.Timeout\n\tif timeout <= 0 {\n\t\ttimeout = DefaultLBClientTimeout\n\t}\n\treturn cc.DoTimeout(req, resp, timeout)\n}\n\nfunc (cc *LBClient) init() {\n\tcc.mu.Lock()\n\tdefer cc.mu.Unlock()\n\tif len(cc.Clients) == 0 {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: LBClient.Clients cannot be empty\")\n\t}\n\tfor _, c := range cc.Clients {\n\t\tcc.cs = append(cc.cs, &lbClient{\n\t\t\tc:           c,\n\t\t\thealthCheck: cc.HealthCheck,\n\t\t})\n\t}\n}\n\n// AddClient adds a new client to the balanced clients and\n// returns the new total number of clients.\nfunc (cc *LBClient) AddClient(c BalancingClient) int {\n\tcc.mu.Lock()\n\tcc.cs = append(cc.cs, &lbClient{\n\t\tc:           c,\n\t\thealthCheck: cc.HealthCheck,\n\t})\n\tcc.mu.Unlock()\n\treturn len(cc.cs)\n}\n\n// RemoveClients removes clients using the provided callback.\n// If rc returns true, the passed client will be removed.\n// Returns the new total number of clients.\nfunc (cc *LBClient) RemoveClients(rc func(BalancingClient) bool) int {\n\tcc.mu.Lock()\n\tn := 0\n\tfor idx, cs := range cc.cs {\n\t\tcc.cs[idx] = nil\n\t\tif rc(cs.c) {\n\t\t\tcontinue\n\t\t}\n\t\tcc.cs[n] = cs\n\t\tn++\n\t}\n\tcc.cs = cc.cs[:n]\n\n\tcc.mu.Unlock()\n\treturn len(cc.cs)\n}\n\nfunc (cc *LBClient) get() *lbClient {\n\tcc.once.Do(cc.init)\n\n\tcc.mu.RLock()\n\tcs := cc.cs\n\n\tminC := cs[0]\n\tminN := minC.PendingRequests()\n\tminT := atomic.LoadUint64(&minC.total)\n\tfor _, c := range cs[1:] {\n\t\tn := c.PendingRequests()\n\t\tt := atomic.LoadUint64(&c.total)\n\t\tif n < minN || (n == minN && t < minT) {\n\t\t\tminC = c\n\t\t\tminN = n\n\t\t\tminT = t\n\t\t}\n\t}\n\tcc.mu.RUnlock()\n\treturn minC\n}\n\ntype lbClient struct {\n\tc           BalancingClient\n\thealthCheck func(req *Request, resp *Response, err error) bool\n\tpenalty     uint32\n\n\t// total amount of requests handled.\n\ttotal uint64\n}\n\nfunc (c *lbClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {\n\terr := c.c.DoDeadline(req, resp, deadline)\n\tif !c.isHealthy(req, resp, err) && c.incPenalty() {\n\t\t// Penalize the client returning error, so the next requests\n\t\t// are routed to another clients.\n\t\ttime.AfterFunc(penaltyDuration, c.decPenalty)\n\t} else {\n\t\tatomic.AddUint64(&c.total, 1)\n\t}\n\treturn err\n}\n\nfunc (c *lbClient) PendingRequests() int {\n\tn := c.c.PendingRequests()\n\tm := atomic.LoadUint32(&c.penalty)\n\treturn n + int(m)\n}\n\nfunc (c *lbClient) isHealthy(req *Request, resp *Response, err error) bool {\n\tif c.healthCheck == nil {\n\t\treturn err == nil\n\t}\n\treturn c.healthCheck(req, resp, err)\n}\n\nfunc (c *lbClient) incPenalty() bool {\n\tm := atomic.AddUint32(&c.penalty, 1)\n\tif m > maxPenalty {\n\t\tc.decPenalty()\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (c *lbClient) decPenalty() {\n\tatomic.AddUint32(&c.penalty, ^uint32(0))\n}\n\nconst (\n\tmaxPenalty = 300\n\n\tpenaltyDuration = 3 * time.Second\n)\n"
  },
  {
    "path": "lbclient_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc ExampleLBClient() {\n\t// Requests will be spread among these servers.\n\tservers := []string{\n\t\t\"google.com:80\",\n\t\t\"foobar.com:8080\",\n\t\t\"127.0.0.1:123\",\n\t}\n\n\t// Prepare clients for each server\n\tvar lbc fasthttp.LBClient\n\tfor _, addr := range servers {\n\t\tc := &fasthttp.HostClient{\n\t\t\tAddr: addr,\n\t\t}\n\t\tlbc.Clients = append(lbc.Clients, c)\n\t}\n\n\t// Send requests to load-balanced servers\n\tvar req fasthttp.Request\n\tvar resp fasthttp.Response\n\tfor i := range 10 {\n\t\turl := fmt.Sprintf(\"http://abcedfg/foo/bar/%d\", i)\n\t\treq.SetRequestURI(url)\n\t\tif err := lbc.Do(&req, &resp); err != nil {\n\t\t\tlog.Fatalf(\"Error when sending request: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != fasthttp.StatusOK {\n\t\t\tlog.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), fasthttp.StatusOK)\n\t\t}\n\n\t\tuseResponseBody(resp.Body())\n\t}\n}\n"
  },
  {
    "path": "methods.go",
    "content": "package fasthttp\n\n// HTTP methods were copied from net/http.\nconst (\n\tMethodGet     = \"GET\"     // RFC 7231, 4.3.1\n\tMethodHead    = \"HEAD\"    // RFC 7231, 4.3.2\n\tMethodPost    = \"POST\"    // RFC 7231, 4.3.3\n\tMethodPut     = \"PUT\"     // RFC 7231, 4.3.4\n\tMethodPatch   = \"PATCH\"   // RFC 5789\n\tMethodDelete  = \"DELETE\"  // RFC 7231, 4.3.5\n\tMethodConnect = \"CONNECT\" // RFC 7231, 4.3.6\n\tMethodOptions = \"OPTIONS\" // RFC 7231, 4.3.7\n\tMethodTrace   = \"TRACE\"   // RFC 7231, 4.3.8\n)\n"
  },
  {
    "path": "nocopy.go",
    "content": "package fasthttp\n\n// Embed this type into a struct, which mustn't be copied,\n// so `go vet` gives a warning if this struct is copied.\n//\n// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.\n// and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example\ntype noCopy struct{}\n\nfunc (*noCopy) Lock()   {}\nfunc (*noCopy) Unlock() {}\n"
  },
  {
    "path": "peripconn.go",
    "content": "package fasthttp\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"net\"\n\t\"sync\"\n)\n\ntype perIPConnCounter struct {\n\tperIPConnPool    sync.Pool\n\tperIPTLSConnPool sync.Pool\n\tm                map[uint32]int\n\tlock             sync.Mutex\n}\n\nfunc (cc *perIPConnCounter) Register(ip uint32) int {\n\tcc.lock.Lock()\n\tif cc.m == nil {\n\t\tcc.m = make(map[uint32]int)\n\t}\n\tn := cc.m[ip] + 1\n\tcc.m[ip] = n\n\tcc.lock.Unlock()\n\treturn n\n}\n\nfunc (cc *perIPConnCounter) Unregister(ip uint32) {\n\tcc.lock.Lock()\n\tdefer cc.lock.Unlock()\n\tif cc.m == nil {\n\t\t// developer safeguard\n\t\tpanic(\"BUG: perIPConnCounter.Register() wasn't called\")\n\t}\n\tn := max(cc.m[ip]-1, 0)\n\tcc.m[ip] = n\n}\n\ntype perIPConn struct {\n\tnet.Conn\n\n\tperIPConnCounter *perIPConnCounter\n\n\tip   uint32\n\tlock sync.Mutex\n}\n\ntype perIPTLSConn struct {\n\t*tls.Conn\n\n\tperIPConnCounter *perIPConnCounter\n\n\tip   uint32\n\tlock sync.Mutex\n}\n\nfunc acquirePerIPConn(conn net.Conn, ip uint32, counter *perIPConnCounter) net.Conn {\n\tif tlsConn, ok := conn.(*tls.Conn); ok {\n\t\tv := counter.perIPTLSConnPool.Get()\n\t\tif v == nil {\n\t\t\treturn &perIPTLSConn{\n\t\t\t\tperIPConnCounter: counter,\n\t\t\t\tConn:             tlsConn,\n\t\t\t\tip:               ip,\n\t\t\t}\n\t\t}\n\t\tc := v.(*perIPTLSConn)\n\t\tc.Conn = tlsConn\n\t\tc.ip = ip\n\t\treturn c\n\t}\n\n\tv := counter.perIPConnPool.Get()\n\tif v == nil {\n\t\treturn &perIPConn{\n\t\t\tperIPConnCounter: counter,\n\t\t\tConn:             conn,\n\t\t\tip:               ip,\n\t\t}\n\t}\n\tc := v.(*perIPConn)\n\tc.Conn = conn\n\tc.ip = ip\n\treturn c\n}\n\nfunc (c *perIPConn) Close() error {\n\tc.lock.Lock()\n\tcc := c.Conn\n\tc.Conn = nil\n\tc.lock.Unlock()\n\n\tif cc == nil {\n\t\treturn nil\n\t}\n\n\terr := cc.Close()\n\tc.perIPConnCounter.Unregister(c.ip)\n\tc.perIPConnCounter.perIPConnPool.Put(c)\n\treturn err\n}\n\nfunc (c *perIPTLSConn) Close() error {\n\tc.lock.Lock()\n\tcc := c.Conn\n\tc.Conn = nil\n\tc.lock.Unlock()\n\n\tif cc == nil {\n\t\treturn nil\n\t}\n\n\terr := cc.Close()\n\tc.perIPConnCounter.Unregister(c.ip)\n\tc.perIPConnCounter.perIPTLSConnPool.Put(c)\n\treturn err\n}\n\nfunc getUint32IP(c net.Conn) uint32 {\n\treturn ip2uint32(getConnIP4(c))\n}\n\nfunc getConnIP4(c net.Conn) net.IP {\n\taddr := c.RemoteAddr()\n\tipAddr, ok := addr.(*net.TCPAddr)\n\tif !ok {\n\t\treturn net.IPv4zero\n\t}\n\treturn ipAddr.IP.To4()\n}\n\nfunc ip2uint32(ip net.IP) uint32 {\n\tif len(ip) != 4 {\n\t\treturn 0\n\t}\n\treturn uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])\n}\n\nfunc uint322ip(ip uint32) net.IP {\n\tb := make(net.IP, net.IPv4len)\n\tbinary.BigEndian.PutUint32(b, ip)\n\treturn b\n}\n"
  },
  {
    "path": "peripconn_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"testing\"\n)\n\nvar _ connTLSer = &perIPTLSConn{}\n\nfunc TestIPxUint32(t *testing.T) {\n\tt.Parallel()\n\n\ttestIPxUint32(t, 0)\n\ttestIPxUint32(t, 10)\n\ttestIPxUint32(t, 0x12892392)\n}\n\nfunc testIPxUint32(t *testing.T, n uint32) {\n\tip := uint322ip(n)\n\tnn := ip2uint32(ip)\n\tif n != nn {\n\t\tt.Fatalf(\"Unexpected value=%d for ip=%q. Expected %d\", nn, ip, n)\n\t}\n}\n\nfunc TestPerIPConnCounter(t *testing.T) {\n\tt.Parallel()\n\n\tvar cc perIPConnCounter\n\n\tfor i := 1; i < 100; i++ {\n\t\tif n := cc.Register(123); n != i {\n\t\t\tt.Fatalf(\"Unexpected counter value=%d. Expected %d\", n, i)\n\t\t}\n\t}\n\n\tn := cc.Register(456)\n\tif n != 1 {\n\t\tt.Fatalf(\"Unexpected counter value=%d. Expected 1\", n)\n\t}\n\n\tfor i := 1; i < 100; i++ {\n\t\tcc.Unregister(123)\n\t}\n\tcc.Unregister(456)\n\n\tn = cc.Register(123)\n\tif n != 1 {\n\t\tt.Fatalf(\"Unexpected counter value=%d. Expected 1\", n)\n\t}\n\tcc.Unregister(123)\n}\n"
  },
  {
    "path": "pprofhandler/pprof.go",
    "content": "// Package pprofhandler provides a fasthttp handler similar to net/http/pprof.\npackage pprofhandler\n\nimport (\n\t\"bytes\"\n\t\"net/http/pprof\"\n\trtp \"runtime/pprof\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"github.com/valyala/fasthttp/fasthttpadaptor\"\n)\n\nvar (\n\tcmdline = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Cmdline)\n\tprofile = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Profile)\n\tsymbol  = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Symbol)\n\ttrace   = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Trace)\n\tindex   = fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Index)\n)\n\n// PprofHandler serves server runtime profiling data in the format expected by the pprof visualization tool.\n//\n// See https://pkg.go.dev/net/http/pprof for details.\nfunc PprofHandler(ctx *fasthttp.RequestCtx) {\n\tctx.Response.Header.Set(\"Content-Type\", \"text/html\")\n\tswitch {\n\tcase bytes.HasPrefix(ctx.Path(), []byte(\"/debug/pprof/cmdline\")):\n\t\tcmdline(ctx)\n\tcase bytes.HasPrefix(ctx.Path(), []byte(\"/debug/pprof/profile\")):\n\t\tprofile(ctx)\n\tcase bytes.HasPrefix(ctx.Path(), []byte(\"/debug/pprof/symbol\")):\n\t\tsymbol(ctx)\n\tcase bytes.HasPrefix(ctx.Path(), []byte(\"/debug/pprof/trace\")):\n\t\ttrace(ctx)\n\tdefault:\n\t\tfor _, v := range rtp.Profiles() {\n\t\t\tppName := v.Name()\n\t\t\tif bytes.HasPrefix(ctx.Path(), []byte(\"/debug/pprof/\"+ppName)) {\n\t\t\t\tnamedHandler := fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler(ppName).ServeHTTP)\n\t\t\t\tnamedHandler(ctx)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tindex(ctx)\n\t}\n}\n"
  },
  {
    "path": "prefork/README.md",
    "content": "# Prefork\n\nServer prefork implementation.\n\nPreforks master process between several child processes increases performance, because Go doesn't have to share and manage memory between cores.\n\n**WARNING: using prefork prevents the use of any global state!. Things like in-memory caches won't work.**\n\n- How it works:\n\n```go\nimport (\n    \"github.com/valyala/fasthttp\"\n    \"github.com/valyala/fasthttp/prefork\"\n)\n\nserver := &fasthttp.Server{\n    // Your configuration\n}\n\n// Wraps the server with prefork\npreforkServer := prefork.New(server)\n\nif err := preforkServer.ListenAndServe(\":8080\"); err != nil {\n    panic(err)\n}\n```\n\n## Benchmarks\n\nEnvironment:\n\n- Machine: MacBook Pro 13-inch, 2017\n- OS: MacOS 10.15.3\n- Go: go1.13.6 darwin/amd64\n\nHandler code:\n\n```go\nfunc requestHandler(ctx *fasthttp.RequestCtx) {\n    // Simulates some hard work\n    time.Sleep(100 * time.Millisecond)\n}\n```\n\nTest command:\n\n```bash\n$ 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\n```\n\nResults:\n\n- prefork\n\n```bash\nRunning 15s test @ http://localhost:8080\n  4 threads and 512 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     4.75ms    4.27ms 126.24ms   97.45%\n    Req/Sec    26.46k     4.16k   71.18k    88.72%\n  Latency Distribution\n     50%    4.55ms\n     75%    4.82ms\n     90%    5.46ms\n     99%   15.49ms\n  1581916 requests in 15.09s, 140.30MB read\n  Socket errors: connect 0, read 318, write 0, timeout 0\nRequests/sec: 104861.58\nTransfer/sec:      9.30MB\n```\n\n- **non**-prefork\n\n```bash\nRunning 15s test @ http://localhost:8080\n  4 threads and 512 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     6.42ms   11.83ms 177.19ms   96.42%\n    Req/Sec    24.96k     5.83k   56.83k    82.93%\n  Latency Distribution\n     50%    4.53ms\n     75%    4.93ms\n     90%    6.94ms\n     99%   74.54ms\n  1472441 requests in 15.09s, 130.59MB read\n  Socket errors: connect 0, read 265, write 0, timeout 0\nRequests/sec:  97553.34\nTransfer/sec:      8.65MB\n```\n"
  },
  {
    "path": "prefork/prefork.go",
    "content": "// Package prefork provides a way to prefork a fasthttp server.\npackage prefork\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"github.com/valyala/fasthttp/reuseport\"\n)\n\nconst (\n\tpreforkChildEnvVariable = \"FASTHTTP_PREFORK_CHILD\"\n\tdefaultNetwork          = \"tcp4\"\n)\n\nvar (\n\tdefaultLogger = Logger(log.New(os.Stderr, \"\", log.LstdFlags))\n\t// ErrOverRecovery is returned when the times of starting over child prefork processes exceed\n\t// the threshold.\n\tErrOverRecovery = errors.New(\"exceeding the value of RecoverThreshold\")\n\n\t// ErrOnlyReuseportOnWindows is returned when Reuseport is false.\n\tErrOnlyReuseportOnWindows = errors.New(\"windows only supports Reuseport = true\")\n)\n\n// Logger is used for logging formatted messages.\ntype Logger interface {\n\t// Printf must have the same semantics as log.Printf.\n\tPrintf(format string, args ...any)\n}\n\n// Prefork implements fasthttp server prefork.\n//\n// Preforks master process (with all cores) between several child processes\n// increases performance significantly, because Go doesn't have to share\n// and manage memory between cores.\n//\n// WARNING: using prefork prevents the use of any global state!\n// Things like in-memory caches won't work.\ntype Prefork struct {\n\t// By default standard logger from log package is used.\n\tLogger Logger\n\n\tln net.Listener\n\n\tServeFunc         func(ln net.Listener) error\n\tServeTLSFunc      func(ln net.Listener, certFile, keyFile string) error\n\tServeTLSEmbedFunc func(ln net.Listener, certData, keyData []byte) error\n\n\t// The network must be \"tcp\", \"tcp4\" or \"tcp6\".\n\t//\n\t// By default is \"tcp4\"\n\tNetwork string\n\n\tfiles []*os.File\n\n\t// Child prefork processes may exit with failure and will be started over until the times reach\n\t// the value of RecoverThreshold, then it will return and terminate the server.\n\tRecoverThreshold int\n\n\t// Flag to use a listener with reuseport, if not a file Listener will be used\n\t// See: https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/\n\t//\n\t// It's disabled by default\n\tReuseport bool\n}\n\n// IsChild checks if the current thread/process is a child.\nfunc IsChild() bool {\n\treturn os.Getenv(preforkChildEnvVariable) == \"1\"\n}\n\n// New wraps the fasthttp server to run with preforked processes.\nfunc New(s *fasthttp.Server) *Prefork {\n\treturn &Prefork{\n\t\tNetwork:           defaultNetwork,\n\t\tRecoverThreshold:  runtime.GOMAXPROCS(0) / 2,\n\t\tLogger:            s.Logger,\n\t\tServeFunc:         s.Serve,\n\t\tServeTLSFunc:      s.ServeTLS,\n\t\tServeTLSEmbedFunc: s.ServeTLSEmbed,\n\t}\n}\n\nfunc (p *Prefork) logger() Logger {\n\tif p.Logger != nil {\n\t\treturn p.Logger\n\t}\n\treturn defaultLogger\n}\n\nfunc (p *Prefork) listen(addr string) (net.Listener, error) {\n\truntime.GOMAXPROCS(1)\n\n\tif p.Network == \"\" {\n\t\tp.Network = defaultNetwork\n\t}\n\n\tif p.Reuseport {\n\t\treturn reuseport.Listen(p.Network, addr)\n\t}\n\n\treturn net.FileListener(os.NewFile(3, \"\"))\n}\n\nfunc (p *Prefork) setTCPListenerFiles(addr string) error {\n\tif p.Network == \"\" {\n\t\tp.Network = defaultNetwork\n\t}\n\n\ttcpAddr, err := net.ResolveTCPAddr(p.Network, addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttcplistener, err := net.ListenTCP(p.Network, tcpAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.ln = tcplistener\n\n\tfl, err := tcplistener.File()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.files = []*os.File{fl}\n\n\treturn nil\n}\n\nfunc (p *Prefork) doCommand() (*exec.Cmd, error) {\n\texecutable, err := os.Executable()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\targs := make([]string, len(os.Args))\n\targs[0] = executable\n\tcopy(args[1:], os.Args[1:])\n\n\tcmd := &exec.Cmd{\n\t\tPath:       executable,\n\t\tArgs:       args,\n\t\tStdout:     os.Stdout,\n\t\tStderr:     os.Stderr,\n\t\tEnv:        append(os.Environ(), preforkChildEnvVariable+\"=1\"),\n\t\tExtraFiles: p.files,\n\t}\n\terr = cmd.Start()\n\treturn cmd, err\n}\n\nfunc (p *Prefork) prefork(addr string) (err error) {\n\tif !p.Reuseport {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\treturn ErrOnlyReuseportOnWindows\n\t\t}\n\n\t\tif err = p.setTCPListenerFiles(addr); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// defer for closing the net.Listener opened by setTCPListenerFiles.\n\t\tdefer func() {\n\t\t\te := p.ln.Close()\n\t\t\tif err == nil {\n\t\t\t\terr = e\n\t\t\t}\n\t\t}()\n\t}\n\n\ttype procSig struct {\n\t\terr error\n\t\tpid int\n\t}\n\n\tgoMaxProcs := runtime.GOMAXPROCS(0)\n\tsigCh := make(chan procSig, goMaxProcs)\n\tchildProcs := make(map[int]*exec.Cmd)\n\n\tdefer func() {\n\t\tfor _, proc := range childProcs {\n\t\t\t_ = proc.Process.Kill()\n\t\t}\n\t}()\n\n\tfor range goMaxProcs {\n\t\tvar cmd *exec.Cmd\n\t\tif cmd, err = p.doCommand(); err != nil {\n\t\t\tp.logger().Printf(\"failed to start a child prefork process, error: %v\\n\", err)\n\t\t\treturn err\n\t\t}\n\n\t\tchildProcs[cmd.Process.Pid] = cmd\n\t\tgo func() {\n\t\t\tsigCh <- procSig{pid: cmd.Process.Pid, err: cmd.Wait()}\n\t\t}()\n\t}\n\n\tvar exitedProcs int\n\tfor sig := range sigCh {\n\t\tdelete(childProcs, sig.pid)\n\n\t\tp.logger().Printf(\"one of the child prefork processes exited with \"+\n\t\t\t\"error: %v\", sig.err)\n\n\t\texitedProcs++\n\t\tif exitedProcs > p.RecoverThreshold {\n\t\t\tp.logger().Printf(\"child prefork processes exit too many times, \"+\n\t\t\t\t\"which exceeds the value of RecoverThreshold(%d), \"+\n\t\t\t\t\"exiting the master process.\\n\", exitedProcs)\n\t\t\terr = ErrOverRecovery\n\t\t\tbreak\n\t\t}\n\n\t\tvar cmd *exec.Cmd\n\t\tif cmd, err = p.doCommand(); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tchildProcs[cmd.Process.Pid] = cmd\n\t\tgo func() {\n\t\t\tsigCh <- procSig{pid: cmd.Process.Pid, err: cmd.Wait()}\n\t\t}()\n\t}\n\n\treturn err\n}\n\n// ListenAndServe serves HTTP requests from the given TCP addr.\nfunc (p *Prefork) ListenAndServe(addr string) error {\n\tif IsChild() {\n\t\tln, err := p.listen(addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tp.ln = ln\n\n\t\treturn p.ServeFunc(ln)\n\t}\n\n\treturn p.prefork(addr)\n}\n\n// ListenAndServeTLS serves HTTPS requests from the given TCP addr.\n//\n// certFile and keyFile are paths to TLS certificate and key files.\nfunc (p *Prefork) ListenAndServeTLS(addr, certKey, certFile string) error {\n\tif IsChild() {\n\t\tln, err := p.listen(addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tp.ln = ln\n\n\t\treturn p.ServeTLSFunc(ln, certFile, certKey)\n\t}\n\n\treturn p.prefork(addr)\n}\n\n// ListenAndServeTLSEmbed serves HTTPS requests from the given TCP addr.\n//\n// certData and keyData must contain valid TLS certificate and key data.\nfunc (p *Prefork) ListenAndServeTLSEmbed(addr string, certData, keyData []byte) error {\n\tif IsChild() {\n\t\tln, err := p.listen(addr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tp.ln = ln\n\n\t\treturn p.ServeTLSEmbedFunc(ln, certData, keyData)\n\t}\n\n\treturn p.prefork(addr)\n}\n"
  },
  {
    "path": "prefork/prefork_test.go",
    "content": "package prefork\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc setUp() {\n\tos.Setenv(preforkChildEnvVariable, \"1\")\n}\n\nfunc tearDown() {\n\tos.Unsetenv(preforkChildEnvVariable)\n}\n\nfunc getAddr() string {\n\treturn fmt.Sprintf(\"127.0.0.1:%d\", rand.Intn(9000-3000)+3000)\n}\n\nfunc Test_IsChild(t *testing.T) {\n\t// This test can't run parallel as it modifies os.Args.\n\n\tv := IsChild()\n\tif v {\n\t\tt.Errorf(\"IsChild() == %v, want %v\", v, false)\n\t}\n\n\tsetUp()\n\tdefer tearDown()\n\n\tv = IsChild()\n\tif !v {\n\t\tt.Errorf(\"IsChild() == %v, want %v\", v, true)\n\t}\n}\n\nfunc Test_New(t *testing.T) {\n\tt.Parallel()\n\n\ts := &fasthttp.Server{}\n\tp := New(s)\n\n\tif p.Network != defaultNetwork {\n\t\tt.Errorf(\"Prefork.Network == %q, want %q\", p.Network, defaultNetwork)\n\t}\n\n\tif reflect.ValueOf(p.ServeFunc).Pointer() != reflect.ValueOf(s.Serve).Pointer() {\n\t\tt.Errorf(\"Prefork.ServeFunc == %p, want %p\", p.ServeFunc, s.Serve)\n\t}\n\n\tif reflect.ValueOf(p.ServeTLSFunc).Pointer() != reflect.ValueOf(s.ServeTLS).Pointer() {\n\t\tt.Errorf(\"Prefork.ServeTLSFunc == %p, want %p\", p.ServeTLSFunc, s.ServeTLS)\n\t}\n\n\tif reflect.ValueOf(p.ServeTLSEmbedFunc).Pointer() != reflect.ValueOf(s.ServeTLSEmbed).Pointer() {\n\t\tt.Errorf(\"Prefork.ServeTLSFunc == %p, want %p\", p.ServeTLSEmbedFunc, s.ServeTLSEmbed)\n\t}\n}\n\nfunc Test_listen(t *testing.T) {\n\tt.Parallel()\n\n\tp := &Prefork{\n\t\tReuseport: true,\n\t}\n\taddr := getAddr()\n\n\tln, err := p.listen(addr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tln.Close()\n\n\tlnAddr := ln.Addr().String()\n\tif lnAddr != addr {\n\t\tt.Errorf(\"Prefork.Addr == %q, want %q\", lnAddr, addr)\n\t}\n\n\tif p.Network != defaultNetwork {\n\t\tt.Errorf(\"Prefork.Network == %q, want %q\", p.Network, defaultNetwork)\n\t}\n\n\tprocs := runtime.GOMAXPROCS(0)\n\tif procs != 1 {\n\t\tt.Errorf(\"GOMAXPROCS == %d, want %d\", procs, 1)\n\t}\n}\n\nfunc Test_setTCPListenerFiles(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tp := &Prefork{}\n\taddr := getAddr()\n\n\terr := p.setTCPListenerFiles(addr)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tif p.ln == nil {\n\t\tt.Fatal(\"Prefork.ln is nil\")\n\t}\n\n\tp.ln.Close()\n\n\tlnAddr := p.ln.Addr().String()\n\tif lnAddr != addr {\n\t\tt.Errorf(\"Prefork.Addr == %q, want %q\", lnAddr, addr)\n\t}\n\n\tif p.Network != defaultNetwork {\n\t\tt.Errorf(\"Prefork.Network == %q, want %q\", p.Network, defaultNetwork)\n\t}\n\n\tif len(p.files) != 1 {\n\t\tt.Errorf(\"Prefork.files == %d, want %d\", len(p.files), 1)\n\t}\n}\n\nfunc Test_ListenAndServe(t *testing.T) {\n\t// This test can't run parallel as it modifies os.Args.\n\n\tsetUp()\n\tdefer tearDown()\n\n\ts := &fasthttp.Server{}\n\tp := New(s)\n\tp.Reuseport = true\n\tp.ServeFunc = func(ln net.Listener) error {\n\t\treturn nil\n\t}\n\n\taddr := getAddr()\n\n\terr := p.ListenAndServe(addr)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\tp.ln.Close()\n\n\tlnAddr := p.ln.Addr().String()\n\tif lnAddr != addr {\n\t\tt.Errorf(\"Prefork.Addr == %q, want %q\", lnAddr, addr)\n\t}\n\n\tif p.ln == nil {\n\t\tt.Error(\"Prefork.ln is nil\")\n\t}\n}\n\nfunc Test_ListenAndServeTLS(t *testing.T) {\n\t// This test can't run parallel as it modifies os.Args.\n\n\tsetUp()\n\tdefer tearDown()\n\n\ts := &fasthttp.Server{}\n\tp := New(s)\n\tp.Reuseport = true\n\tp.ServeTLSFunc = func(ln net.Listener, certFile, keyFile string) error {\n\t\treturn nil\n\t}\n\n\taddr := getAddr()\n\n\terr := p.ListenAndServeTLS(addr, \"./key\", \"./cert\")\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\tp.ln.Close()\n\n\tlnAddr := p.ln.Addr().String()\n\tif lnAddr != addr {\n\t\tt.Errorf(\"Prefork.Addr == %q, want %q\", lnAddr, addr)\n\t}\n\n\tif p.ln == nil {\n\t\tt.Error(\"Prefork.ln is nil\")\n\t}\n}\n\nfunc Test_ListenAndServeTLSEmbed(t *testing.T) {\n\t// This test can't run parallel as it modifies os.Args.\n\n\tsetUp()\n\tdefer tearDown()\n\n\ts := &fasthttp.Server{}\n\tp := New(s)\n\tp.Reuseport = true\n\tp.ServeTLSEmbedFunc = func(ln net.Listener, certData, keyData []byte) error {\n\t\treturn nil\n\t}\n\n\taddr := getAddr()\n\n\terr := p.ListenAndServeTLSEmbed(addr, []byte(\"key\"), []byte(\"cert\"))\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t}\n\n\tp.ln.Close()\n\n\tlnAddr := p.ln.Addr().String()\n\tif lnAddr != addr {\n\t\tt.Errorf(\"Prefork.Addr == %q, want %q\", lnAddr, addr)\n\t}\n\n\tif p.ln == nil {\n\t\tt.Error(\"Prefork.ln is nil\")\n\t}\n}\n"
  },
  {
    "path": "requestctx_setbodystreamwriter_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc ExampleRequestCtx_SetBodyStreamWriter() {\n\t// Start fasthttp server for streaming responses.\n\tif err := fasthttp.ListenAndServe(\":8080\", responseStreamHandler); err != nil {\n\t\tlog.Fatalf(\"unexpected error in server: %v\", err)\n\t}\n}\n\nfunc responseStreamHandler(ctx *fasthttp.RequestCtx) {\n\t// Send the response in chunks and wait for a second between each chunk.\n\tctx.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\tfor i := range 10 {\n\t\t\tfmt.Fprintf(w, \"this is a message number %d\", i)\n\n\t\t\t// Do not forget flushing streamed data to the client.\n\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "reuseport/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Max Riveiro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "reuseport/reuseport.go",
    "content": "//go:build !windows && !aix && !solaris\n\n// Package reuseport provides TCP net.Listener with SO_REUSEPORT support.\n//\n// SO_REUSEPORT allows linear scaling server performance on multi-CPU servers.\n// See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for more details :)\n//\n// The package is based on https://github.com/kavu/go_reuseport .\npackage reuseport\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/valyala/fasthttp/tcplisten\"\n)\n\n// Listen returns TCP listener with SO_REUSEPORT option set.\n//\n// The returned listener tries enabling the following TCP options, which usually\n// have positive impact on performance:\n//\n//   - TCP_DEFER_ACCEPT. This option expects that the server reads from accepted\n//     connections before writing to them.\n//\n//   - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details.\n//\n// Only tcp4 and tcp6 networks are supported.\n//\n// ErrNoReusePort error is returned if the system doesn't support SO_REUSEPORT.\nfunc Listen(network, addr string) (net.Listener, error) {\n\tln, err := cfg.NewListener(network, addr)\n\tif err != nil && strings.Contains(err.Error(), \"SO_REUSEPORT\") {\n\t\treturn nil, &ErrNoReusePort{err: err}\n\t}\n\treturn ln, err\n}\n\nvar cfg = &tcplisten.Config{\n\tReusePort:   true,\n\tDeferAccept: true,\n\tFastOpen:    true,\n}\n"
  },
  {
    "path": "reuseport/reuseport_aix.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nvar listenConfig = net.ListenConfig{\n\tControl: func(network, address string, c syscall.RawConn) (err error) {\n\t\treturn c.Control(func(fd uintptr) {\n\t\t\terr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)\n\t\t\tif err == nil {\n\t\t\t\terr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)\n\t\t\t}\n\t\t})\n\t},\n}\n\n// Listen returns a TCP listener with the SO_REUSEADDR and SO_REUSEPORT options set.\nfunc Listen(network, addr string) (net.Listener, error) {\n\treturn listenConfig.Listen(context.Background(), network, addr)\n}\n"
  },
  {
    "path": "reuseport/reuseport_error.go",
    "content": "package reuseport\n\nimport (\n\t\"fmt\"\n)\n\n// ErrNoReusePort is returned if the OS doesn't support SO_REUSEPORT.\ntype ErrNoReusePort struct {\n\terr error\n}\n\n// Error implements error interface.\nfunc (e *ErrNoReusePort) Error() string {\n\treturn fmt.Sprintf(\"The OS doesn't support SO_REUSEPORT: %v\", e.err)\n}\n"
  },
  {
    "path": "reuseport/reuseport_example_test.go",
    "content": "package reuseport_test\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/valyala/fasthttp\"\n\t\"github.com/valyala/fasthttp/reuseport\"\n)\n\nfunc ExampleListen() {\n\tln, err := reuseport.Listen(\"tcp4\", \"localhost:12345\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error in reuseport listener: %v\", err)\n\t}\n\n\tif err = fasthttp.Serve(ln, requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in fasthttp Server: %v\", err)\n\t}\n}\n\nfunc requestHandler(ctx *fasthttp.RequestCtx) {\n\tfmt.Fprintf(ctx, \"Hello, world!\")\n}\n"
  },
  {
    "path": "reuseport/reuseport_solaris.go",
    "content": "//go:build solaris\n\npackage reuseport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\t_SO_REUSEPORT = 0x100e\n)\n\nvar listenConfig = net.ListenConfig{\n\tControl: func(network, address string, c syscall.RawConn) (err error) {\n\t\treturn c.Control(func(fd uintptr) {\n\t\t\terr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)\n\t\t\tif err == nil {\n\t\t\t\terr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, _SO_REUSEPORT, 1)\n\t\t\t}\n\t\t})\n\t},\n}\n\n// Listen returns a TCP listener with the SO_REUSEADDR and SO_REUSEPORT options set.\nfunc Listen(network, addr string) (net.Listener, error) {\n\treturn listenConfig.Listen(context.Background(), network, addr)\n}\n"
  },
  {
    "path": "reuseport/reuseport_test.go",
    "content": "package reuseport\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestTCP4(t *testing.T) {\n\tt.Parallel()\n\n\ttestNewListener(t, \"tcp4\", \"localhost:10081\")\n}\n\nfunc TestTCP6(t *testing.T) {\n\tt.Parallel()\n\n\t// Run this test only if tcp6 interface exists.\n\tif hasLocalIPv6(t) {\n\t\ttestNewListener(t, \"tcp6\", \"[::1]:10082\")\n\t}\n}\n\nfunc hasLocalIPv6(t *testing.T) bool {\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\tt.Fatalf(\"cannot obtain local interfaces: %v\", err)\n\t}\n\tfor _, a := range addrs {\n\t\tif a.String() == \"::1/128\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc testNewListener(t *testing.T, network, addr string) {\n\tln1, err := Listen(network, addr)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot create listener %v\", err)\n\t}\n\n\tln2, err := Listen(network, addr)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot create listener %v\", err)\n\t}\n\n\t_ = ln1.Close()\n\t_ = ln2.Close()\n}\n"
  },
  {
    "path": "reuseport/reuseport_windows.go",
    "content": "package reuseport\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nvar listenConfig = net.ListenConfig{\n\tControl: func(network, address string, c syscall.RawConn) (err error) {\n\t\treturn c.Control(func(fd uintptr) {\n\t\t\terr = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)\n\t\t})\n\t},\n}\n\n// Listen returns TCP listener with SO_REUSEADDR option set, SO_REUSEPORT is not supported on Windows, so it uses\n// SO_REUSEADDR as an alternative to achieve the same effect.\nfunc Listen(network, addr string) (net.Listener, error) {\n\treturn listenConfig.Listen(context.Background(), network, addr)\n}\n"
  },
  {
    "path": "round2_32.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64 && !ppc64le && !riscv64 && !s390x\n\npackage fasthttp\n\nimport \"math\"\n\nfunc roundUpForSliceCap(n int) int {\n\tif n <= 0 {\n\t\treturn 0\n\t}\n\n\t// Above 100MB, we don't round up as the overhead is too large.\n\tif n > 100*1024*1024 {\n\t\treturn n\n\t}\n\n\tx := uint32(n - 1)\n\tx |= x >> 1\n\tx |= x >> 2\n\tx |= x >> 4\n\tx |= x >> 8\n\tx |= x >> 16\n\n\t// Make sure we don't return 0 due to overflow, even on 32 bit systems\n\tif x >= uint32(math.MaxInt32) {\n\t\treturn math.MaxInt32\n\t}\n\n\treturn int(x + 1)\n}\n"
  },
  {
    "path": "round2_32_test.go",
    "content": "//go:build !amd64 && !arm64 && !ppc64 && !ppc64le && !riscv64 && !s390x\n\npackage fasthttp\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc TestRound2ForSliceCap(t *testing.T) {\n\tt.Parallel()\n\n\ttestRound2ForSliceCap(t, 0, 0)\n\ttestRound2ForSliceCap(t, 1, 1)\n\ttestRound2ForSliceCap(t, 2, 2)\n\ttestRound2ForSliceCap(t, 3, 4)\n\ttestRound2ForSliceCap(t, 4, 4)\n\ttestRound2ForSliceCap(t, 5, 8)\n\ttestRound2ForSliceCap(t, 7, 8)\n\ttestRound2ForSliceCap(t, 8, 8)\n\ttestRound2ForSliceCap(t, 9, 16)\n\ttestRound2ForSliceCap(t, 0x10001, 0x20000)\n\n\ttestRound2ForSliceCap(t, math.MaxInt32-1, math.MaxInt32-1)\n}\n\nfunc testRound2ForSliceCap(t *testing.T, n, expectedRound2 int) {\n\tif roundUpForSliceCap(n) != expectedRound2 {\n\t\tt.Fatalf(\"Unexpected round2(%d)=%d. Expected %d\", n, roundUpForSliceCap(n), expectedRound2)\n\t}\n}\n"
  },
  {
    "path": "round2_64.go",
    "content": "//go:build amd64 || arm64 || ppc64 || ppc64le || riscv64 || s390x\n\npackage fasthttp\n\nfunc roundUpForSliceCap(n int) int {\n\tif n <= 0 {\n\t\treturn 0\n\t}\n\n\t// Above 100MB, we don't round up as the overhead is too large.\n\tif n > 100*1024*1024 {\n\t\treturn n\n\t}\n\n\tx := uint64(n - 1) // #nosec G115\n\tx |= x >> 1\n\tx |= x >> 2\n\tx |= x >> 4\n\tx |= x >> 8\n\tx |= x >> 16\n\n\treturn int(x + 1) // #nosec G115\n}\n"
  },
  {
    "path": "round2_64_test.go",
    "content": "//go:build amd64 || arm64 || ppc64 || ppc64le || riscv64 || s390x\n\npackage fasthttp\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc TestRound2ForSliceCap(t *testing.T) {\n\tt.Parallel()\n\n\ttestRound2ForSliceCap(t, 0, 0)\n\ttestRound2ForSliceCap(t, 1, 1)\n\ttestRound2ForSliceCap(t, 2, 2)\n\ttestRound2ForSliceCap(t, 3, 4)\n\ttestRound2ForSliceCap(t, 4, 4)\n\ttestRound2ForSliceCap(t, 5, 8)\n\ttestRound2ForSliceCap(t, 7, 8)\n\ttestRound2ForSliceCap(t, 8, 8)\n\ttestRound2ForSliceCap(t, 9, 16)\n\ttestRound2ForSliceCap(t, 0x10001, 0x20000)\n\n\ttestRound2ForSliceCap(t, math.MaxInt32, math.MaxInt32)\n\ttestRound2ForSliceCap(t, math.MaxInt64-1, math.MaxInt64-1)\n}\n\nfunc testRound2ForSliceCap(t *testing.T, n, expectedRound2 int) {\n\tif roundUpForSliceCap(n) != expectedRound2 {\n\t\tt.Fatalf(\"Unexpected round2(%d)=%d. Expected %d\", n, roundUpForSliceCap(n), expectedRound2)\n\t}\n}\n"
  },
  {
    "path": "s2b.go",
    "content": "package fasthttp\n\nimport \"unsafe\"\n\n// s2b converts string to a byte slice without memory allocation.\nfunc s2b(s string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(s), len(s))\n}\n"
  },
  {
    "path": "server.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nvar errNoCertOrKeyProvided = errors.New(\"cert or key has not provided\")\n\n// ErrAlreadyServing is deprecated.\n//\n// Deprecated: ErrAlreadyServing is never returned from Serve. See issue #633.\nvar ErrAlreadyServing = errors.New(\"Server is already serving connections\")\n\n// ServeConn serves HTTP requests from the given connection\n// using the given handler.\n//\n// ServeConn returns nil if all requests from the c are successfully served.\n// It returns non-nil error otherwise.\n//\n// Connection c must immediately propagate all the data passed to Write()\n// to the client. Otherwise requests' processing may hang.\n//\n// ServeConn closes c before returning.\nfunc ServeConn(c net.Conn, handler RequestHandler) error {\n\tv := serverPool.Get()\n\tif v == nil {\n\t\tv = &Server{}\n\t}\n\ts := v.(*Server)\n\ts.Handler = handler\n\terr := s.ServeConn(c)\n\ts.Handler = nil\n\tserverPool.Put(v)\n\treturn err\n}\n\nvar serverPool sync.Pool\n\n// Serve serves incoming connections from the given listener\n// using the given handler.\n//\n// Serve blocks until the given listener returns permanent error.\nfunc Serve(ln net.Listener, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.Serve(ln)\n}\n\n// ServeTLS serves HTTPS requests from the given net.Listener\n// using the given handler.\n//\n// certFile and keyFile are paths to TLS certificate and key files.\nfunc ServeTLS(ln net.Listener, certFile, keyFile string, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ServeTLS(ln, certFile, keyFile)\n}\n\n// ServeTLSEmbed serves HTTPS requests from the given net.Listener\n// using the given handler.\n//\n// certData and keyData must contain valid TLS certificate and key data.\nfunc ServeTLSEmbed(ln net.Listener, certData, keyData []byte, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ServeTLSEmbed(ln, certData, keyData)\n}\n\n// ListenAndServe serves HTTP requests from the given TCP addr\n// using the given handler.\nfunc ListenAndServe(addr string, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ListenAndServe(addr)\n}\n\n// ListenAndServeUNIX serves HTTP requests from the given UNIX addr\n// using the given handler.\n//\n// The function deletes existing file at addr before starting serving.\n//\n// The server sets the given file mode for the UNIX addr.\nfunc ListenAndServeUNIX(addr string, mode os.FileMode, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ListenAndServeUNIX(addr, mode)\n}\n\n// ListenAndServeTLS serves HTTPS requests from the given TCP addr\n// using the given handler.\n//\n// certFile and keyFile are paths to TLS certificate and key files.\nfunc ListenAndServeTLS(addr, certFile, keyFile string, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ListenAndServeTLS(addr, certFile, keyFile)\n}\n\n// ListenAndServeTLSEmbed serves HTTPS requests from the given TCP addr\n// using the given handler.\n//\n// certData and keyData must contain valid TLS certificate and key data.\nfunc ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler RequestHandler) error {\n\ts := &Server{\n\t\tHandler: handler,\n\t}\n\treturn s.ListenAndServeTLSEmbed(addr, certData, keyData)\n}\n\n// RequestHandler must process incoming requests.\n//\n// RequestHandler must call ctx.TimeoutError() before returning\n// if it keeps references to ctx and/or its members after the return.\n// Consider wrapping RequestHandler into TimeoutHandler if response time\n// must be limited.\ntype RequestHandler func(ctx *RequestCtx)\n\n// ServeHandler must process tls.Config.NextProto negotiated requests.\ntype ServeHandler func(c net.Conn) error\n\n// Server implements HTTP server.\n//\n// Default Server settings should satisfy the majority of Server users.\n// Adjust Server settings only if you really understand the consequences.\n//\n// It is forbidden copying Server instances. Create new Server instances\n// instead.\n//\n// It is safe to call Server methods from concurrently running goroutines.\ntype Server struct {\n\tnoCopy noCopy\n\n\tperIPConnCounter perIPConnCounter\n\n\tctxPool        sync.Pool\n\treaderPool     sync.Pool\n\twriterPool     sync.Pool\n\thijackConnPool sync.Pool\n\n\t// Logger, which is used by RequestCtx.Logger().\n\t//\n\t// By default standard logger from log package is used.\n\tLogger Logger\n\n\t// Handler for processing incoming requests.\n\t//\n\t// Take into account that no `panic` recovery is done by `fasthttp` (thus any `panic` will take down the entire server).\n\t// Instead the user should use `recover` to handle these situations.\n\tHandler RequestHandler\n\n\t// ErrorHandler for returning a response in case of an error while receiving or parsing the request.\n\t//\n\t// The following is a non-exhaustive list of errors that can be expected as argument:\n\t//   * io.EOF\n\t//   * io.ErrUnexpectedEOF\n\t//   * ErrGetOnly\n\t//   * ErrSmallBuffer\n\t//   * ErrBodyTooLarge\n\t//   * ErrBrokenChunks\n\tErrorHandler func(ctx *RequestCtx, err error)\n\n\t// HeaderReceived is called after receiving the header.\n\t//\n\t// Non zero RequestConfig field values will overwrite the default configs\n\tHeaderReceived func(header *RequestHeader) RequestConfig\n\n\t// ContinueHandler is called after receiving the Expect 100 Continue Header.\n\t//\n\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3\n\t// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1\n\t// Using ContinueHandler a server can make decisioning on whether or not\n\t// to read a potentially large request body based on the headers.\n\t//\n\t// The default is to automatically read request bodies of Expect 100 Continue requests\n\t// like they are normal requests.\n\tContinueHandler func(header *RequestHeader) bool\n\n\t// ConnState specifies an optional callback function that is\n\t// called when a client connection changes state. See the\n\t// ConnState type and associated constants for details.\n\tConnState func(net.Conn, ConnState)\n\n\t// TLSConfig optionally provides a TLS configuration for use\n\t// by ServeTLS, ServeTLSEmbed, ListenAndServeTLS, ListenAndServeTLSEmbed,\n\t// AppendCert, AppendCertEmbed and NextProto.\n\t//\n\t// Note that this value is cloned by ServeTLS, ServeTLSEmbed, ListenAndServeTLS\n\t// and ListenAndServeTLSEmbed, so it's not possible to modify the configuration\n\t// with methods like tls.Config.SetSessionTicketKeys.\n\t// To use SetSessionTicketKeys, use Server.Serve with a TLS Listener\n\t// instead.\n\tTLSConfig *tls.Config\n\n\t// FormValueFunc customizes the behavior of RequestCtx.FormValue.\n\t//\n\t// For multipart requests, the default FormValue path calls MultipartForm()\n\t// without a body size limit. If you need a limit for multipart parsing,\n\t// provide a custom FormValueFunc and call MultipartFormWithLimit() there.\n\t//\n\t// NetHttpFormValueFunc gives a FormValueFunc implementation that is\n\t// consistent with net/http.\n\tFormValueFunc FormValueFunc\n\n\tnextProtos map[string]ServeHandler\n\n\tconcurrencyCh chan struct{}\n\n\tidleConns map[net.Conn]*atomic.Int64\n\tdone      chan struct{}\n\n\t// Server name for sending in response headers.\n\t//\n\t// Default server name is used if left blank.\n\tName string\n\n\t// We need to know our listeners and idle connections so we can close them in Shutdown().\n\tln []net.Listener\n\n\t// The maximum number of concurrent connections the server may serve.\n\t//\n\t// DefaultConcurrency is used if not set.\n\t//\n\t// Concurrency only works if you either call Serve once, or only ServeConn multiple times.\n\t// It works with ListenAndServe as well.\n\tConcurrency int\n\n\t// Per-connection buffer size for requests' reading.\n\t// This also limits the maximum header size.\n\t//\n\t// Increase this buffer if your clients send multi-KB RequestURIs\n\t// and/or multi-KB headers (for example, BIG cookies).\n\t//\n\t// Default buffer size is used if not set.\n\tReadBufferSize int\n\n\t// Per-connection buffer size for responses' writing.\n\t//\n\t// Default buffer size is used if not set.\n\tWriteBufferSize int\n\n\t// ReadTimeout is the amount of time allowed to read\n\t// the full request including body. The connection's read\n\t// deadline is reset when the connection opens, or for\n\t// keep-alive connections after the first byte has been read.\n\t//\n\t// By default request read timeout is unlimited.\n\tReadTimeout time.Duration\n\n\t// WriteTimeout is the maximum duration before timing out\n\t// writes of the response. It is reset after the request handler\n\t// has returned.\n\t//\n\t// By default response write timeout is unlimited.\n\tWriteTimeout time.Duration\n\n\t// IdleTimeout is the maximum amount of time to wait for the\n\t// next request when keep-alive is enabled. If IdleTimeout\n\t// is zero, the value of ReadTimeout is used.\n\tIdleTimeout time.Duration\n\n\t// Maximum number of concurrent client connections allowed per IP.\n\t//\n\t// By default unlimited number of concurrent connections\n\t// may be established to the server from a single IP address.\n\tMaxConnsPerIP int\n\n\t// Maximum number of requests served per connection.\n\t//\n\t// The server closes connection after the last request.\n\t// 'Connection: close' header is added to the last response.\n\t//\n\t// By default unlimited number of requests may be served per connection.\n\tMaxRequestsPerConn int\n\n\t// MaxKeepaliveDuration is a no-op and only left here for backwards compatibility.\n\t//\n\t// Deprecated: Use IdleTimeout instead.\n\tMaxKeepaliveDuration time.Duration\n\n\t// MaxIdleWorkerDuration is the maximum idle time of a single worker in the underlying\n\t// worker pool of the Server. Idle workers beyond this time will be cleared.\n\tMaxIdleWorkerDuration time.Duration\n\n\t// Period between tcp keep-alive messages.\n\t//\n\t// TCP keep-alive period is determined by operation system by default.\n\tTCPKeepalivePeriod time.Duration\n\n\t// Maximum request body size.\n\t//\n\t// The server rejects requests with bodies exceeding this limit.\n\t//\n\t// Request body size is limited by DefaultMaxRequestBodySize by default.\n\tMaxRequestBodySize int\n\n\t// SleepWhenConcurrencyLimitsExceeded is a duration to be slept of if\n\t// the concurrency limit in exceeded (default [when is 0]: don't sleep\n\t// and accept new connections immediately).\n\tSleepWhenConcurrencyLimitsExceeded time.Duration\n\n\tidleConnsMu sync.Mutex\n\n\tmu sync.Mutex\n\n\tconcurrency atomic.Uint32\n\topen        atomic.Int32\n\tstop        atomic.Int32\n\n\trejectedRequestsCount atomic.Uint32\n\n\t// Whether to disable keep-alive connections.\n\t//\n\t// The server will close all the incoming connections after sending\n\t// the first response to client if this option is set to true.\n\t//\n\t// By default keep-alive connections are enabled.\n\tDisableKeepalive bool\n\n\t// Whether to enable tcp keep-alive connections.\n\t//\n\t// Whether the operating system should send tcp keep-alive messages on the tcp connection.\n\t//\n\t// By default tcp keep-alive connections are disabled.\n\tTCPKeepalive bool\n\n\t// Aggressively reduces memory usage at the cost of higher CPU usage\n\t// if set to true.\n\t//\n\t// Try enabling this option only if the server consumes too much memory\n\t// serving mostly idle keep-alive connections. This may reduce memory\n\t// usage by more than 50%.\n\t//\n\t// Aggressive memory usage reduction is disabled by default.\n\tReduceMemoryUsage bool\n\n\t// Rejects all non-GET requests if set to true.\n\t//\n\t// This option is useful as anti-DoS protection for servers\n\t// accepting only GET requests and HEAD requests. The request size is limited\n\t// by ReadBufferSize if GetOnly is set.\n\t//\n\t// Server accepts all the requests by default.\n\tGetOnly bool\n\n\t// Will not pre parse Multipart Form data if set to true.\n\t//\n\t// This option is useful for servers that desire to treat\n\t// multipart form data as a binary blob, or choose when to parse the data.\n\t//\n\t// Server pre parses multipart form data by default.\n\tDisablePreParseMultipartForm bool\n\n\t// Logs all errors, including the most frequent\n\t// 'connection reset by peer', 'broken pipe' and 'connection timeout'\n\t// errors. Such errors are common in production serving real-world\n\t// clients.\n\t//\n\t// By default the most frequent errors such as\n\t// 'connection reset by peer', 'broken pipe' and 'connection timeout'\n\t// are suppressed in order to limit output log traffic.\n\tLogAllErrors bool\n\n\t// Will not log potentially sensitive content in error logs\n\t//\n\t// This option is useful for servers that handle sensitive data\n\t// in the request/response.\n\t//\n\t// Server logs all full errors by default.\n\tSecureErrorLogMessage bool\n\n\t// Header names are passed as-is without normalization\n\t// if this option is set.\n\t//\n\t// Disabled header names' normalization may be useful only for proxying\n\t// incoming requests to other servers expecting case-sensitive\n\t// header names. See https://github.com/valyala/fasthttp/issues/57\n\t// for details.\n\t//\n\t// By default request and response header names are normalized, i.e.\n\t// The first letter and the first letters following dashes\n\t// are uppercased, while all the other letters are lowercased.\n\t// Examples:\n\t//\n\t//     * HOST -> Host\n\t//     * content-type -> Content-Type\n\t//     * cONTENT-lenGTH -> Content-Length\n\tDisableHeaderNamesNormalizing bool\n\n\t// NoDefaultServerHeader, when set to true, causes the default Server header\n\t// to be excluded from the Response.\n\t//\n\t// The default Server header value is the value of the Name field or an\n\t// internal default value in its absence. With this option set to true,\n\t// the only time a Server header will be sent is if a non-zero length\n\t// value is explicitly provided during a request.\n\tNoDefaultServerHeader bool\n\n\t// NoDefaultDate, when set to true, causes the default Date\n\t// header to be excluded from the Response.\n\t//\n\t// The default Date header value is the current date value. When\n\t// set to true, the Date will not be present.\n\tNoDefaultDate bool\n\n\t// NoDefaultContentType, when set to true, causes the default Content-Type\n\t// header to be excluded from the Response.\n\t//\n\t// The default Content-Type header value is the internal default value. When\n\t// set to true, the Content-Type will not be present.\n\tNoDefaultContentType bool\n\n\t// KeepHijackedConns is an opt-in disable of connection\n\t// close by fasthttp after connections' HijackHandler returns.\n\t// This allows to save goroutines, e.g. when fasthttp used to upgrade\n\t// http connections to WS and connection goes to another handler,\n\t// which will close it when needed.\n\tKeepHijackedConns bool\n\n\t// CloseOnShutdown when true adds a `Connection: close` header when the server is shutting down.\n\tCloseOnShutdown bool\n\n\t// StreamRequestBody enables request body streaming,\n\t// and calls the handler sooner when given body is\n\t// larger than the current limit.\n\tStreamRequestBody bool\n}\n\n// TimeoutHandler creates RequestHandler, which returns StatusRequestTimeout\n// error with the given msg to the client if h didn't return during\n// the given duration.\n//\n// The returned handler may return StatusTooManyRequests error with the given\n// msg to the client if there are more than Server.Concurrency concurrent\n// handlers h are running at the moment.\nfunc TimeoutHandler(h RequestHandler, timeout time.Duration, msg string) RequestHandler {\n\treturn TimeoutWithCodeHandler(h, timeout, msg, StatusRequestTimeout)\n}\n\n// TimeoutWithCodeHandler creates RequestHandler, which returns an error with\n// the given msg and status code to the client  if h didn't return during\n// the given duration.\n//\n// The returned handler may return StatusTooManyRequests error with the given\n// msg to the client if there are more than Server.Concurrency concurrent\n// handlers h are running at the moment.\nfunc TimeoutWithCodeHandler(h RequestHandler, timeout time.Duration, msg string, statusCode int) RequestHandler {\n\tif timeout <= 0 {\n\t\treturn h\n\t}\n\n\treturn func(ctx *RequestCtx) {\n\t\tconcurrencyCh := ctx.s.concurrencyCh\n\t\tselect {\n\t\tcase concurrencyCh <- struct{}{}:\n\t\tdefault:\n\t\t\tctx.Error(msg, StatusTooManyRequests)\n\t\t\treturn\n\t\t}\n\n\t\tch := ctx.timeoutCh\n\t\tif ch == nil {\n\t\t\tch = make(chan struct{}, 1)\n\t\t\tctx.timeoutCh = ch\n\t\t}\n\t\tgo func() {\n\t\t\th(ctx)\n\t\t\tch <- struct{}{}\n\t\t\t<-concurrencyCh\n\t\t}()\n\t\tctx.timeoutTimer = initTimer(ctx.timeoutTimer, timeout)\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-ctx.timeoutTimer.C:\n\t\t\tctx.TimeoutErrorWithCode(msg, statusCode)\n\t\t}\n\t\tstopTimer(ctx.timeoutTimer)\n\t}\n}\n\n// RequestConfig configure the per request deadline and body limits.\ntype RequestConfig struct {\n\t// ReadTimeout is the maximum duration for reading the entire\n\t// request body.\n\t// A zero value means that default values will be honored.\n\tReadTimeout time.Duration\n\t// WriteTimeout is the maximum duration before timing out\n\t// writes of the response.\n\t// A zero value means that default values will be honored.\n\tWriteTimeout time.Duration\n\t// Maximum request body size.\n\t// A zero value means that default values will be honored.\n\tMaxRequestBodySize int\n}\n\n// CompressHandler returns RequestHandler that transparently compresses\n// response body generated by h if the request contains 'gzip' or 'deflate'\n// 'Accept-Encoding' header.\nfunc CompressHandler(h RequestHandler) RequestHandler {\n\treturn CompressHandlerLevel(h, CompressDefaultCompression)\n}\n\n// CompressHandlerLevel returns RequestHandler that transparently compresses\n// response body generated by h if the request contains a 'gzip' or 'deflate'\n// 'Accept-Encoding' header.\n//\n// Level is the desired compression level:\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc CompressHandlerLevel(h RequestHandler, level int) RequestHandler {\n\treturn func(ctx *RequestCtx) {\n\t\th(ctx)\n\t\tswitch {\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strGzip):\n\t\t\tctx.Response.gzipBody(level)\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strDeflate):\n\t\t\tctx.Response.deflateBody(level)\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strZstd):\n\t\t\tctx.Response.zstdBody(level)\n\t\t}\n\t}\n}\n\n// CompressHandlerBrotliLevel returns RequestHandler that transparently compresses\n// response body generated by h if the request contains a 'br', 'gzip' or 'deflate'\n// 'Accept-Encoding' header.\n//\n// brotliLevel is the desired compression level for brotli.\n//\n//   - CompressBrotliNoCompression\n//   - CompressBrotliBestSpeed\n//   - CompressBrotliBestCompression\n//   - CompressBrotliDefaultCompression\n//\n// otherLevel is the desired compression level for gzip and deflate.\n//\n//   - CompressNoCompression\n//   - CompressBestSpeed\n//   - CompressBestCompression\n//   - CompressDefaultCompression\n//   - CompressHuffmanOnly\nfunc CompressHandlerBrotliLevel(h RequestHandler, brotliLevel, otherLevel int) RequestHandler {\n\treturn func(ctx *RequestCtx) {\n\t\th(ctx)\n\t\tswitch {\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strBr):\n\t\t\tctx.Response.brotliBody(brotliLevel)\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strGzip):\n\t\t\tctx.Response.gzipBody(otherLevel)\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strDeflate):\n\t\t\tctx.Response.deflateBody(otherLevel)\n\t\tcase ctx.Request.Header.HasAcceptEncodingBytes(strZstd):\n\t\t\tctx.Response.zstdBody(otherLevel)\n\t\t}\n\t}\n}\n\n// RequestCtx contains incoming request and manages outgoing response.\n//\n// It is forbidden copying RequestCtx instances.\n//\n// RequestHandler should avoid holding references to incoming RequestCtx and/or\n// its members after the return.\n// If holding RequestCtx references after the return is unavoidable\n// (for instance, ctx is passed to a separate goroutine and ctx lifetime cannot\n// be controlled), then the RequestHandler MUST call ctx.TimeoutError()\n// before return.\n//\n// It is unsafe modifying/reading RequestCtx instance from concurrently\n// running goroutines. The only exception is TimeoutError*, which may be called\n// while other goroutines accessing RequestCtx.\ntype RequestCtx struct {\n\tnoCopy noCopy\n\n\t// Outgoing response.\n\t//\n\t// Copying Response by value is forbidden. Use pointer to Response instead.\n\tResponse Response\n\n\tconnTime time.Time\n\n\ttime time.Time\n\n\tlogger     ctxLogger\n\tremoteAddr net.Addr\n\n\tc net.Conn\n\ts *Server\n\n\ttimeoutResponse *Response\n\ttimeoutCh       chan struct{}\n\ttimeoutTimer    *time.Timer\n\n\thijackHandler HijackHandler\n\tformValueFunc FormValueFunc\n\tfbr           firstByteReader\n\n\t// Incoming request.\n\t//\n\t// Copying Request by value is forbidden. Use pointer to Request instead.\n\tRequest Request\n\n\tconnID           uint64\n\tconnRequestNum   uint64\n\thijackNoResponse bool\n}\n\n// EarlyHints allows the server to hint to the browser what resources a page would need\n// so the browser can preload them while waiting for the server's full response. Only Link\n// headers already written to the response will be transmitted as Early Hints.\n//\n// This is a HTTP/2+ feature but all browsers will either understand it or safely ignore it.\n//\n// NOTE: Older HTTP/1.1 non-browser clients may face compatibility issues.\n//\n// See: https://developer.chrome.com/docs/web-platform/early-hints and\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#syntax\n//\n// Example:\n//\n//\tfunc(ctx *fasthttp.RequestCtx) {\n//\t   ctx.Response.Header.Add(\"Link\", \"<https://fonts.google.com>; rel=preconnect\")\n//\t   ctx.EarlyHints()\n//\t   time.Sleep(5*time.Second) // some time-consuming task\n//\t   ctx.SetStatusCode(fasthttp.StatusOK)\n//\t   ctx.SetBody([]byte(\"<html><head></head><body><h1>Hello from Fasthttp</h1></body></html>\"))\n//\t}\nfunc (ctx *RequestCtx) EarlyHints() error {\n\tlinks := ctx.Response.Header.PeekAll(b2s(strLink))\n\tif len(links) > 0 {\n\t\tc := acquireWriter(ctx)\n\t\tdefer releaseWriter(ctx.s, c)\n\t\t_, err := c.Write(strEarlyHints)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, l := range links {\n\t\t\tif len(l) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = c.Write(strLink)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = c.Write(strColon)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = c.Write(strSpace)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = c.Write(l)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = c.Write(strCRLF)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t_, err = c.Write(strCRLF)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = c.Flush()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// HijackHandler must process the hijacked connection c.\n//\n// If KeepHijackedConns is disabled, which is by default,\n// the connection c is automatically closed after returning from HijackHandler.\n//\n// The connection c must not be used after returning from the handler, if KeepHijackedConns is disabled.\n//\n// When KeepHijackedConns enabled, fasthttp will not Close() the connection,\n// you must do it when you need it. You must not use c in any way after calling Close().\ntype HijackHandler func(c net.Conn)\n\n// Hijack registers the given handler for connection hijacking.\n//\n// The handler is called after returning from RequestHandler\n// and sending http response. The current connection is passed\n// to the handler. The connection is automatically closed after\n// returning from the handler.\n//\n// The server skips calling the handler in the following cases:\n//\n//   - 'Connection: close' header exists in either request or response.\n//   - Unexpected error during response writing to the connection.\n//\n// The server stops processing requests from hijacked connections.\n//\n// Server limits such as Concurrency, ReadTimeout, WriteTimeout, etc.\n// aren't applied to hijacked connections.\n//\n// The handler must not retain references to ctx members.\n//\n// Arbitrary 'Connection: Upgrade' protocols may be implemented\n// with HijackHandler. For instance,\n//\n//   - WebSocket ( https://en.wikipedia.org/wiki/WebSocket )\n//   - HTTP/2.0 ( https://en.wikipedia.org/wiki/HTTP/2 )\nfunc (ctx *RequestCtx) Hijack(handler HijackHandler) {\n\tctx.hijackHandler = handler\n}\n\n// HijackSetNoResponse changes the behavior of hijacking a request.\n// If HijackSetNoResponse is called with false fasthttp will send a response\n// to the client before calling the HijackHandler (default). If HijackSetNoResponse\n// is called with true no response is send back before calling the\n// HijackHandler supplied in the Hijack function.\nfunc (ctx *RequestCtx) HijackSetNoResponse(noResponse bool) {\n\tctx.hijackNoResponse = noResponse\n}\n\n// Hijacked returns true after Hijack is called.\nfunc (ctx *RequestCtx) Hijacked() bool {\n\treturn ctx.hijackHandler != nil\n}\n\n// SetUserValue stores the given value (arbitrary object)\n// under the given key in Request.\n//\n// The value stored in Request may be obtained by UserValue*.\n//\n// This functionality may be useful for passing arbitrary values between\n// functions involved in request processing.\n//\n// All the values are removed from Request after returning from the top\n// RequestHandler. Additionally, Close method is called on each value\n// implementing io.Closer before removing the value from Request.\nfunc (ctx *RequestCtx) SetUserValue(key, value any) {\n\tctx.Request.SetUserValue(key, value)\n}\n\n// SetUserValueBytes stores the given value (arbitrary object)\n// under the given key in Request.\n//\n// The value stored in Request may be obtained by UserValue*.\n//\n// This functionality may be useful for passing arbitrary values between\n// functions involved in request processing.\n//\n// All the values stored in Request are deleted after returning from RequestHandler.\nfunc (ctx *RequestCtx) SetUserValueBytes(key []byte, value any) {\n\tctx.Request.SetUserValueBytes(key, value)\n}\n\n// UserValue returns the value stored via SetUserValue* under the given key.\nfunc (ctx *RequestCtx) UserValue(key any) any {\n\treturn ctx.Request.UserValue(key)\n}\n\n// UserValueBytes returns the value stored via SetUserValue*\n// under the given key.\nfunc (ctx *RequestCtx) UserValueBytes(key []byte) any {\n\treturn ctx.Request.UserValueBytes(key)\n}\n\n// VisitUserValues calls visitor for each existing userValue with a key that is a string or []byte.\n//\n// visitor must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (ctx *RequestCtx) VisitUserValues(visitor func([]byte, any)) {\n\tctx.Request.VisitUserValues(visitor)\n}\n\n// VisitUserValuesAll calls visitor for each existing userValue.\n//\n// visitor must not retain references to key and value after returning.\n// Make key and/or value copies if you need storing them after returning.\nfunc (ctx *RequestCtx) VisitUserValuesAll(visitor func(any, any)) {\n\tctx.Request.VisitUserValuesAll(visitor)\n}\n\n// ResetUserValues allows to reset user values from Request.\nfunc (ctx *RequestCtx) ResetUserValues() {\n\tctx.Request.ResetUserValues()\n}\n\n// RemoveUserValue removes the given key and the value under it in Request.\nfunc (ctx *RequestCtx) RemoveUserValue(key any) {\n\tctx.Request.RemoveUserValue(key)\n}\n\n// RemoveUserValueBytes removes the given key and the value under it in Request.\nfunc (ctx *RequestCtx) RemoveUserValueBytes(key []byte) {\n\tctx.Request.RemoveUserValueBytes(key)\n}\n\ntype connTLSer interface {\n\tHandshake() error\n\tConnectionState() tls.ConnectionState\n}\n\n// IsTLS returns true if the underlying connection is tls.Conn.\n//\n// tls.Conn is an encrypted connection (aka SSL, HTTPS).\nfunc (ctx *RequestCtx) IsTLS() bool {\n\t// cast to (connTLSer) instead of (*tls.Conn), since it catches\n\t// cases with overridden tls.Conn such as:\n\t//\n\t// type customConn struct {\n\t//     *tls.Conn\n\t//\n\t//     // other custom fields here\n\t// }\n\n\t// perIPConn wraps the net.Conn in the Conn field\n\tif pic, ok := ctx.c.(*perIPConn); ok {\n\t\t_, ok := pic.Conn.(connTLSer)\n\t\treturn ok\n\t}\n\n\t_, ok := ctx.c.(connTLSer)\n\treturn ok\n}\n\n// TLSConnectionState returns TLS connection state.\n//\n// The function returns nil if the underlying connection isn't tls.Conn.\n//\n// The returned state may be used for verifying TLS version, client certificates,\n// etc.\nfunc (ctx *RequestCtx) TLSConnectionState() *tls.ConnectionState {\n\ttlsConn, ok := ctx.c.(connTLSer)\n\tif !ok {\n\t\treturn nil\n\t}\n\tstate := tlsConn.ConnectionState()\n\treturn &state\n}\n\n// Conn returns a reference to the underlying net.Conn.\n//\n// WARNING: Only use this method if you know what you are doing!\n//\n// Reading from or writing to the returned connection will end badly!\nfunc (ctx *RequestCtx) Conn() net.Conn {\n\treturn ctx.c\n}\n\nfunc (ctx *RequestCtx) reset() {\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.fbr.reset()\n\n\tctx.connID = 0\n\tctx.connRequestNum = 0\n\tctx.connTime = zeroTime\n\tctx.remoteAddr = nil\n\tctx.time = zeroTime\n\tctx.c = nil\n\n\t// Don't reset ctx.s!\n\t// We have a pool per server so the next time this ctx is used it\n\t// will be assigned the same value again.\n\t// ctx might still be in use for context.Done() and context.Err()\n\t// which are safe to use as they only use ctx.s and no other value.\n\n\tif ctx.timeoutResponse != nil {\n\t\tctx.timeoutResponse.Reset()\n\t}\n\n\tif ctx.timeoutTimer != nil {\n\t\tstopTimer(ctx.timeoutTimer)\n\t}\n\n\tctx.hijackHandler = nil\n\tctx.hijackNoResponse = false\n}\n\ntype firstByteReader struct {\n\tc        net.Conn\n\tch       byte\n\tbyteRead bool\n}\n\nfunc (r *firstByteReader) reset() {\n\tr.c = nil\n\tr.ch = 0\n\tr.byteRead = false\n}\n\nfunc (r *firstByteReader) Read(b []byte) (int, error) {\n\tif len(b) == 0 {\n\t\treturn 0, nil\n\t}\n\tnn := 0\n\tif !r.byteRead {\n\t\tb[0] = r.ch\n\t\tb = b[1:]\n\t\tr.byteRead = true\n\t\tnn = 1\n\t}\n\tn, err := r.c.Read(b)\n\treturn n + nn, err\n}\n\n// Logger is used for logging formatted messages.\ntype Logger interface {\n\t// Printf must have the same semantics as log.Printf.\n\tPrintf(format string, args ...any)\n}\n\nvar ctxLoggerLock sync.Mutex\n\ntype ctxLogger struct {\n\tctx    *RequestCtx\n\tlogger Logger\n}\n\nfunc (cl *ctxLogger) Printf(format string, args ...any) {\n\tmsg := fmt.Sprintf(format, args...)\n\tctxLoggerLock.Lock()\n\tcl.logger.Printf(\"%.3f %s - %s\", time.Since(cl.ctx.ConnTime()).Seconds(), cl.ctx.String(), msg)\n\tctxLoggerLock.Unlock()\n}\n\nvar zeroTCPAddr = &net.TCPAddr{\n\tIP: net.IPv4zero,\n}\n\n// String returns unique string representation of the ctx.\n//\n// The returned value may be useful for logging.\nfunc (ctx *RequestCtx) String() string {\n\treturn fmt.Sprintf(\"#%016X - %s<->%s - %s %s\", ctx.ID(), ctx.LocalAddr(), ctx.RemoteAddr(),\n\t\tctx.Request.Header.Method(), ctx.URI().FullURI())\n}\n\n// ID returns unique ID of the request.\nfunc (ctx *RequestCtx) ID() uint64 {\n\treturn (ctx.connID << 32) | ctx.connRequestNum\n}\n\n// ConnID returns unique connection ID.\n//\n// This ID may be used to match distinct requests to the same incoming\n// connection.\nfunc (ctx *RequestCtx) ConnID() uint64 {\n\treturn ctx.connID\n}\n\n// Time returns RequestHandler call time.\nfunc (ctx *RequestCtx) Time() time.Time {\n\treturn ctx.time\n}\n\n// ConnTime returns the time the server started serving the connection\n// the current request came from.\nfunc (ctx *RequestCtx) ConnTime() time.Time {\n\treturn ctx.connTime\n}\n\n// ConnRequestNum returns request sequence number\n// for the current connection.\n//\n// Sequence starts with 1.\nfunc (ctx *RequestCtx) ConnRequestNum() uint64 {\n\treturn ctx.connRequestNum\n}\n\n// SetConnectionClose sets 'Connection: close' response header and closes\n// connection after the RequestHandler returns.\nfunc (ctx *RequestCtx) SetConnectionClose() {\n\tctx.Response.SetConnectionClose()\n}\n\n// SetStatusCode sets response status code.\nfunc (ctx *RequestCtx) SetStatusCode(statusCode int) {\n\tctx.Response.SetStatusCode(statusCode)\n}\n\n// SetContentType sets response Content-Type.\nfunc (ctx *RequestCtx) SetContentType(contentType string) {\n\tctx.Response.Header.SetContentType(contentType)\n}\n\n// SetContentTypeBytes sets response Content-Type.\n//\n// It is safe modifying contentType buffer after function return.\nfunc (ctx *RequestCtx) SetContentTypeBytes(contentType []byte) {\n\tctx.Response.Header.SetContentTypeBytes(contentType)\n}\n\n// RequestURI returns RequestURI.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) RequestURI() []byte {\n\treturn ctx.Request.Header.RequestURI()\n}\n\n// URI returns requested uri.\n//\n// This uri is valid until your request handler returns.\nfunc (ctx *RequestCtx) URI() *URI {\n\treturn ctx.Request.URI()\n}\n\n// Referer returns request referer.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) Referer() []byte {\n\treturn ctx.Request.Header.Referer()\n}\n\n// UserAgent returns User-Agent header value from the request.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) UserAgent() []byte {\n\treturn ctx.Request.Header.UserAgent()\n}\n\n// Path returns requested path.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) Path() []byte {\n\treturn ctx.URI().Path()\n}\n\n// Host returns requested host.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) Host() []byte {\n\treturn ctx.URI().Host()\n}\n\n// QueryArgs returns query arguments from RequestURI.\n//\n// It doesn't return POST'ed arguments - use PostArgs() for this.\n//\n// See also PostArgs, FormValue and FormFile.\n//\n// These args are valid until your request handler returns.\nfunc (ctx *RequestCtx) QueryArgs() *Args {\n\treturn ctx.URI().QueryArgs()\n}\n\n// PostArgs returns POST arguments.\n//\n// It doesn't return query arguments from RequestURI - use QueryArgs for this.\n//\n// See also QueryArgs, FormValue and FormFile.\n//\n// These args are valid until your request handler returns.\nfunc (ctx *RequestCtx) PostArgs() *Args {\n\treturn ctx.Request.PostArgs()\n}\n\n// MultipartForm returns request's multipart form.\n//\n// Returns ErrNoMultipartForm if request's content-type\n// isn't 'multipart/form-data'.\n//\n// This method is equivalent to MultipartFormWithLimit(0), i.e. no body size\n// limit is applied during multipart parsing.\n//\n// All uploaded temporary files are automatically deleted after\n// returning from RequestHandler. Either move or copy uploaded files\n// into new place if you want retaining them.\n//\n// Use SaveMultipartFile function for permanently saving uploaded file.\n//\n// The returned form is valid until your request handler returns.\n//\n// See also FormFile and FormValue.\nfunc (ctx *RequestCtx) MultipartForm() (*multipart.Form, error) {\n\treturn ctx.Request.MultipartForm()\n}\n\n// MultipartFormWithLimit returns request's multipart form and limits the read\n// multipart body size to maxBodySize bytes.\n//\n// If maxBodySize <= 0, then no limit is applied.\n//\n// Call this method before FormValue/FormFile if you need a limit for\n// multipart parsing.\nfunc (ctx *RequestCtx) MultipartFormWithLimit(maxBodySize int) (*multipart.Form, error) {\n\treturn ctx.Request.MultipartFormWithLimit(maxBodySize)\n}\n\n// FormFile returns uploaded file associated with the given multipart form key.\n//\n// The file is automatically deleted after returning from RequestHandler,\n// so either move or copy uploaded file into new place if you want retaining it.\n//\n// Use SaveMultipartFile function for permanently saving uploaded file.\n//\n// The returned file header is valid until your request handler returns.\n//\n// For multipart requests with untrusted input, call MultipartFormWithLimit()\n// before FormFile.\nfunc (ctx *RequestCtx) FormFile(key string) (*multipart.FileHeader, error) {\n\tmf, err := ctx.MultipartForm()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif mf.File == nil {\n\t\treturn nil, err\n\t}\n\tfhh := mf.File[key]\n\tif fhh == nil {\n\t\treturn nil, ErrMissingFile\n\t}\n\treturn fhh[0], nil\n}\n\n// ErrMissingFile may be returned from FormFile when the is no uploaded file\n// associated with the given multipart form key.\nvar ErrMissingFile = errors.New(\"there is no uploaded file associated with the given key\")\n\n// SaveMultipartFile saves multipart file fh under the given filename path.\nfunc SaveMultipartFile(fh *multipart.FileHeader, path string) (err error) {\n\tvar (\n\t\tf  multipart.File\n\t\tff *os.File\n\t)\n\tf, err = fh.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ok bool\n\tif ff, ok = f.(*os.File); ok {\n\t\t// Windows can't rename files that are opened.\n\t\tif err = f.Close(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// If renaming fails we try the normal copying method.\n\t\t// Renaming could fail if the files are on different devices.\n\t\tif os.Rename(ff.Name(), path) == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Reopen f for the code below.\n\t\tif f, err = fh.Open(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdefer func() {\n\t\te := f.Close()\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t}()\n\n\tif ff, err = os.Create(path); err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\te := ff.Close()\n\t\tif err == nil {\n\t\t\terr = e\n\t\t}\n\t}()\n\t_, err = copyZeroAlloc(ff, f)\n\treturn err\n}\n\n// FormValue returns form value associated with the given key.\n//\n// The value is searched in the following places:\n//\n//   - Query string.\n//   - POST or PUT body.\n//\n// There are more fine-grained methods for obtaining form values:\n//\n//   - QueryArgs for obtaining values from query string.\n//   - PostArgs for obtaining values from POST or PUT body.\n//   - MultipartForm for obtaining values from multipart form.\n//   - FormFile for obtaining uploaded files.\n//\n// The returned value is valid until your request handler returns.\n//\n// For multipart requests with untrusted input, either call\n// MultipartFormWithLimit() before FormValue or provide a custom\n// Server.FormValueFunc that uses MultipartFormWithLimit().\nfunc (ctx *RequestCtx) FormValue(key string) []byte {\n\tif ctx.formValueFunc != nil {\n\t\treturn ctx.formValueFunc(ctx, key)\n\t}\n\treturn defaultFormValue(ctx, key)\n}\n\n// FormValueFunc customizes how RequestCtx.FormValue resolves a value.\ntype FormValueFunc func(*RequestCtx, string) []byte\n\nvar (\n\tdefaultFormValue = func(ctx *RequestCtx, key string) []byte {\n\t\tv := ctx.QueryArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\tv = ctx.PostArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\tmf, err := ctx.MultipartForm()\n\t\tif err == nil && mf.Value != nil {\n\t\t\tvv := mf.Value[key]\n\t\t\tif len(vv) > 0 {\n\t\t\t\treturn []byte(vv[0])\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// NetHttpFormValueFunc gives consistent behavior with net/http.\n\t// POST and PUT body parameters take precedence over URL query string values.\n\t//\n\t//nolint:staticcheck // backwards compatibility\n\tNetHttpFormValueFunc = func(ctx *RequestCtx, key string) []byte {\n\t\tv := ctx.PostArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\tmf, err := ctx.MultipartForm()\n\t\tif err == nil && mf.Value != nil {\n\t\t\tvv := mf.Value[key]\n\t\t\tif len(vv) > 0 {\n\t\t\t\treturn []byte(vv[0])\n\t\t\t}\n\t\t}\n\t\tv = ctx.QueryArgs().Peek(key)\n\t\tif len(v) > 0 {\n\t\t\treturn v\n\t\t}\n\t\treturn nil\n\t}\n)\n\n// IsGet returns true if request method is GET.\nfunc (ctx *RequestCtx) IsGet() bool {\n\treturn ctx.Request.Header.IsGet()\n}\n\n// IsPost returns true if request method is POST.\nfunc (ctx *RequestCtx) IsPost() bool {\n\treturn ctx.Request.Header.IsPost()\n}\n\n// IsPut returns true if request method is PUT.\nfunc (ctx *RequestCtx) IsPut() bool {\n\treturn ctx.Request.Header.IsPut()\n}\n\n// IsDelete returns true if request method is DELETE.\nfunc (ctx *RequestCtx) IsDelete() bool {\n\treturn ctx.Request.Header.IsDelete()\n}\n\n// IsConnect returns true if request method is CONNECT.\nfunc (ctx *RequestCtx) IsConnect() bool {\n\treturn ctx.Request.Header.IsConnect()\n}\n\n// IsOptions returns true if request method is OPTIONS.\nfunc (ctx *RequestCtx) IsOptions() bool {\n\treturn ctx.Request.Header.IsOptions()\n}\n\n// IsTrace returns true if request method is TRACE.\nfunc (ctx *RequestCtx) IsTrace() bool {\n\treturn ctx.Request.Header.IsTrace()\n}\n\n// IsPatch returns true if request method is PATCH.\nfunc (ctx *RequestCtx) IsPatch() bool {\n\treturn ctx.Request.Header.IsPatch()\n}\n\n// Method return request method.\n//\n// Returned value is valid until your request handler returns.\nfunc (ctx *RequestCtx) Method() []byte {\n\treturn ctx.Request.Header.Method()\n}\n\n// IsHead returns true if request method is HEAD.\nfunc (ctx *RequestCtx) IsHead() bool {\n\treturn ctx.Request.Header.IsHead()\n}\n\n// RemoteAddr returns client address for the given request.\n//\n// Always returns non-nil result.\nfunc (ctx *RequestCtx) RemoteAddr() net.Addr {\n\tif ctx.remoteAddr != nil {\n\t\treturn ctx.remoteAddr\n\t}\n\tif ctx.c == nil {\n\t\treturn zeroTCPAddr\n\t}\n\taddr := ctx.c.RemoteAddr()\n\tif addr == nil {\n\t\treturn zeroTCPAddr\n\t}\n\treturn addr\n}\n\n// SetRemoteAddr sets remote address to the given value.\n//\n// Set nil value to restore default behaviour for using\n// connection remote address.\nfunc (ctx *RequestCtx) SetRemoteAddr(remoteAddr net.Addr) {\n\tctx.remoteAddr = remoteAddr\n}\n\n// LocalAddr returns server address for the given request.\n//\n// Always returns non-nil result.\nfunc (ctx *RequestCtx) LocalAddr() net.Addr {\n\tif ctx.c == nil {\n\t\treturn zeroTCPAddr\n\t}\n\taddr := ctx.c.LocalAddr()\n\tif addr == nil {\n\t\treturn zeroTCPAddr\n\t}\n\treturn addr\n}\n\n// RemoteIP returns the client ip the request came from.\n//\n// Always returns non-nil result.\nfunc (ctx *RequestCtx) RemoteIP() net.IP {\n\treturn addrToIP(ctx.RemoteAddr())\n}\n\n// LocalIP returns the server ip the request came to.\n//\n// Always returns non-nil result.\nfunc (ctx *RequestCtx) LocalIP() net.IP {\n\treturn addrToIP(ctx.LocalAddr())\n}\n\nfunc addrToIP(addr net.Addr) net.IP {\n\tx, ok := addr.(*net.TCPAddr)\n\tif !ok {\n\t\treturn net.IPv4zero\n\t}\n\treturn x.IP\n}\n\n// Error sets response status code to the given value and sets response body\n// to the given message.\n//\n// Warning: this will reset the response headers and body already set!\nfunc (ctx *RequestCtx) Error(msg string, statusCode int) {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(statusCode)\n\tctx.SetContentTypeBytes(defaultContentType)\n\tctx.SetBodyString(msg)\n}\n\n// Success sets response Content-Type and body to the given values.\nfunc (ctx *RequestCtx) Success(contentType string, body []byte) {\n\tctx.SetContentType(contentType)\n\tctx.SetBody(body)\n}\n\n// SuccessString sets response Content-Type and body to the given values.\nfunc (ctx *RequestCtx) SuccessString(contentType, body string) {\n\tctx.SetContentType(contentType)\n\tctx.SetBodyString(body)\n}\n\n// Redirect sets 'Location: uri' response header and sets the given statusCode.\n//\n// statusCode must have one of the following values:\n//\n//   - StatusMovedPermanently (301)\n//   - StatusFound (302)\n//   - StatusSeeOther (303)\n//   - StatusTemporaryRedirect (307)\n//   - StatusPermanentRedirect (308)\n//\n// All other statusCode values are replaced by StatusFound (302).\n//\n// The redirect uri may be either absolute or relative to the current\n// request uri. Fasthttp will always send an absolute uri back to the client.\n// To send a relative uri you can use the following code:\n//\n//\tstrLocation = []byte(\"Location\") // Put this with your top level var () declarations.\n//\tctx.Response.Header.SetCanonical(strLocation, \"/relative?uri\")\n//\tctx.Response.SetStatusCode(fasthttp.StatusMovedPermanently)\nfunc (ctx *RequestCtx) Redirect(uri string, statusCode int) {\n\tu := AcquireURI()\n\tctx.URI().CopyTo(u)\n\tu.Update(uri)\n\tctx.redirect(u.FullURI(), statusCode)\n\tReleaseURI(u)\n}\n\n// RedirectBytes sets 'Location: uri' response header and sets\n// the given statusCode.\n//\n// statusCode must have one of the following values:\n//\n//   - StatusMovedPermanently (301)\n//   - StatusFound (302)\n//   - StatusSeeOther (303)\n//   - StatusTemporaryRedirect (307)\n//   - StatusPermanentRedirect (308)\n//\n// All other statusCode values are replaced by StatusFound (302).\n//\n// The redirect uri may be either absolute or relative to the current\n// request uri. Fasthttp will always send an absolute uri back to the client.\n// To send a relative uri you can use the following code:\n//\n//\tstrLocation = []byte(\"Location\") // Put this with your top level var () declarations.\n//\tctx.Response.Header.SetCanonical(strLocation, \"/relative?uri\")\n//\tctx.Response.SetStatusCode(fasthttp.StatusMovedPermanently)\nfunc (ctx *RequestCtx) RedirectBytes(uri []byte, statusCode int) {\n\ts := b2s(uri)\n\tctx.Redirect(s, statusCode)\n}\n\nfunc (ctx *RequestCtx) redirect(uri []byte, statusCode int) {\n\tctx.Response.Header.setNonSpecial(strLocation, uri)\n\tstatusCode = getRedirectStatusCode(statusCode)\n\tctx.Response.SetStatusCode(statusCode)\n}\n\nfunc getRedirectStatusCode(statusCode int) int {\n\tif statusCode == StatusMovedPermanently || statusCode == StatusFound ||\n\t\tstatusCode == StatusSeeOther || statusCode == StatusTemporaryRedirect ||\n\t\tstatusCode == StatusPermanentRedirect {\n\t\treturn statusCode\n\t}\n\treturn StatusFound\n}\n\n// SetBody sets response body to the given value.\n//\n// It is safe re-using body argument after the function returns.\nfunc (ctx *RequestCtx) SetBody(body []byte) {\n\tctx.Response.SetBody(body)\n}\n\n// SetBodyString sets response body to the given value.\nfunc (ctx *RequestCtx) SetBodyString(body string) {\n\tctx.Response.SetBodyString(body)\n}\n\n// ResetBody resets response body contents.\nfunc (ctx *RequestCtx) ResetBody() {\n\tctx.Response.ResetBody()\n}\n\n// SendFile sends local file contents from the given path as response body.\n//\n// This is a shortcut to ServeFile(ctx, path).\n//\n// SendFile logs all the errors via ctx.Logger.\n//\n// See also ServeFile, FSHandler and FS.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc (ctx *RequestCtx) SendFile(path string) {\n\tServeFile(ctx, path)\n}\n\n// SendFileBytes sends local file contents from the given path as response body.\n//\n// This is a shortcut to ServeFileBytes(ctx, path).\n//\n// SendFileBytes logs all the errors via ctx.Logger.\n//\n// See also ServeFileBytes, FSHandler and FS.\n//\n// WARNING: do not pass any user supplied paths to this function!\n// WARNING: if path is based on user input users will be able to request\n// any file on your filesystem! Use fasthttp.FS with a sane Root instead.\nfunc (ctx *RequestCtx) SendFileBytes(path []byte) {\n\tServeFileBytes(ctx, path)\n}\n\n// IfModifiedSince returns true if lastModified exceeds 'If-Modified-Since'\n// value from the request header.\n//\n// The function returns true also 'If-Modified-Since' request header is missing.\nfunc (ctx *RequestCtx) IfModifiedSince(lastModified time.Time) bool {\n\tifModStr := ctx.Request.Header.peek(strIfModifiedSince)\n\tif len(ifModStr) == 0 {\n\t\treturn true\n\t}\n\tifMod, err := ParseHTTPDate(ifModStr)\n\tif err != nil {\n\t\treturn true\n\t}\n\tlastModified = lastModified.Truncate(time.Second)\n\treturn ifMod.Before(lastModified)\n}\n\n// NotModified resets response and sets '304 Not Modified' response status code.\nfunc (ctx *RequestCtx) NotModified() {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(StatusNotModified)\n}\n\n// NotFound resets response and sets '404 Not Found' response status code.\nfunc (ctx *RequestCtx) NotFound() {\n\tctx.Response.Reset()\n\tctx.SetStatusCode(StatusNotFound)\n\tctx.SetBodyString(\"404 Page not found\")\n}\n\n// Write writes p into response body.\nfunc (ctx *RequestCtx) Write(p []byte) (int, error) {\n\tctx.Response.AppendBody(p)\n\treturn len(p), nil\n}\n\n// WriteString appends s to response body.\nfunc (ctx *RequestCtx) WriteString(s string) (int, error) {\n\tctx.Response.AppendBodyString(s)\n\treturn len(s), nil\n}\n\n// PostBody returns POST request body.\n//\n// The returned bytes are valid until your request handler returns.\nfunc (ctx *RequestCtx) PostBody() []byte {\n\treturn ctx.Request.Body()\n}\n\n// SetBodyStream sets response body stream and, optionally body size.\n//\n// bodyStream.Close() is called after finishing reading all body data\n// if it implements io.Closer.\n//\n// If bodySize is >= 0, then bodySize bytes must be provided by bodyStream\n// before returning io.EOF.\n//\n// If bodySize < 0, then bodyStream is read until io.EOF.\n//\n// See also SetBodyStreamWriter.\nfunc (ctx *RequestCtx) SetBodyStream(bodyStream io.Reader, bodySize int) {\n\tctx.Response.SetBodyStream(bodyStream, bodySize)\n}\n\n// SetBodyStreamWriter registers the given stream writer for populating\n// response body.\n//\n// Access to RequestCtx and/or its members is forbidden from sw.\n//\n// This function may be used in the following cases:\n//\n//   - if response body is too big (more than 10MB).\n//   - if response body is streamed from slow external sources.\n//   - if response body must be streamed to the client in chunks.\n//     (aka `http server push`).\nfunc (ctx *RequestCtx) SetBodyStreamWriter(sw StreamWriter) {\n\tctx.Response.SetBodyStreamWriter(sw)\n}\n\n// IsBodyStream returns true if response body is set via SetBodyStream*.\nfunc (ctx *RequestCtx) IsBodyStream() bool {\n\treturn ctx.Response.IsBodyStream()\n}\n\n// Logger returns logger, which may be used for logging arbitrary\n// request-specific messages inside RequestHandler.\n//\n// Each message logged via returned logger contains request-specific information\n// such as request id, request duration, local address, remote address,\n// request method and request url.\n//\n// It is safe re-using returned logger for logging multiple messages\n// for the current request.\n//\n// The returned logger is valid until your request handler returns.\nfunc (ctx *RequestCtx) Logger() Logger {\n\tif ctx.logger.ctx == nil {\n\t\tctx.logger.ctx = ctx\n\t}\n\tif ctx.logger.logger == nil {\n\t\tctx.logger.logger = ctx.s.logger()\n\t}\n\treturn &ctx.logger\n}\n\n// TimeoutError sets response status code to StatusRequestTimeout and sets\n// body to the given msg.\n//\n// All response modifications after TimeoutError call are ignored.\n//\n// TimeoutError MUST be called before returning from RequestHandler if there are\n// references to ctx and/or its members in other goroutines remain.\n//\n// Usage of this function is discouraged. Prefer eliminating ctx references\n// from pending goroutines instead of using this function.\nfunc (ctx *RequestCtx) TimeoutError(msg string) {\n\tctx.TimeoutErrorWithCode(msg, StatusRequestTimeout)\n}\n\n// TimeoutErrorWithCode sets response body to msg and response status\n// code to statusCode.\n//\n// All response modifications after TimeoutErrorWithCode call are ignored.\n//\n// TimeoutErrorWithCode MUST be called before returning from RequestHandler\n// if there are references to ctx and/or its members in other goroutines remain.\n//\n// Usage of this function is discouraged. Prefer eliminating ctx references\n// from pending goroutines instead of using this function.\nfunc (ctx *RequestCtx) TimeoutErrorWithCode(msg string, statusCode int) {\n\tvar resp Response\n\tresp.SetStatusCode(statusCode)\n\tresp.SetBodyString(msg)\n\tctx.TimeoutErrorWithResponse(&resp)\n}\n\n// TimeoutErrorWithResponse marks the ctx as timed out and sends the given\n// response to the client.\n//\n// All ctx modifications after TimeoutErrorWithResponse call are ignored.\n//\n// TimeoutErrorWithResponse MUST be called before returning from RequestHandler\n// if there are references to ctx and/or its members in other goroutines remain.\n//\n// Usage of this function is discouraged. Prefer eliminating ctx references\n// from pending goroutines instead of using this function.\nfunc (ctx *RequestCtx) TimeoutErrorWithResponse(resp *Response) {\n\trespCopy := &Response{}\n\tresp.CopyTo(respCopy)\n\tctx.timeoutResponse = respCopy\n}\n\n// NextProto adds nph to be processed when key is negotiated when TLS\n// connection is established.\n//\n// This function can only be called before the server is started.\nfunc (s *Server) NextProto(key string, nph ServeHandler) {\n\tif s.nextProtos == nil {\n\t\ts.nextProtos = make(map[string]ServeHandler)\n\t}\n\n\ts.configTLS()\n\ts.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, key)\n\ts.nextProtos[key] = nph\n}\n\nfunc (s *Server) getNextProto(c net.Conn) (string, error) {\n\tif tlsConn, ok := c.(connTLSer); ok {\n\t\tif s.ReadTimeout > 0 {\n\t\t\tif err := c.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\n\t\tif s.WriteTimeout > 0 {\n\t\t\tif err := c.SetWriteDeadline(time.Now().Add(s.WriteTimeout)); err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\n\t\terr := tlsConn.Handshake()\n\t\tif err == nil {\n\t\t\treturn tlsConn.ConnectionState().NegotiatedProtocol, nil\n\t\t}\n\t}\n\treturn \"\", nil\n}\n\n// ListenAndServe serves HTTP requests from the given TCP4 addr.\n//\n// Pass custom listener to Serve if you need listening on non-TCP4 media\n// such as IPv6.\n//\n// Accepted connections are configured to enable TCP keep-alives.\nfunc (s *Server) ListenAndServe(addr string) error {\n\tln, err := net.Listen(\"tcp4\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.Serve(ln)\n}\n\n// ListenAndServeUNIX serves HTTP requests from the given UNIX addr.\n//\n// The function deletes existing file at addr before starting serving.\n//\n// The server sets the given file mode for the UNIX addr.\nfunc (s *Server) ListenAndServeUNIX(addr string, mode os.FileMode) error {\n\tif err := os.Remove(addr); err != nil && !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"unexpected error when trying to remove unix socket file %q: %w\", addr, err)\n\t}\n\tln, err := net.Listen(\"unix\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = os.Chmod(addr, mode); err != nil {\n\t\treturn fmt.Errorf(\"cannot chmod %#o for %q: %w\", mode, addr, err)\n\t}\n\treturn s.Serve(ln)\n}\n\n// ListenAndServeTLS serves HTTPS requests from the given TCP4 addr.\n//\n// certFile and keyFile are paths to TLS certificate and key files.\n//\n// Pass custom listener to Serve if you need listening on non-TCP4 media\n// such as IPv6.\n//\n// If the certFile or keyFile has not been provided to the server structure,\n// the function will use the previously added TLS configuration.\n//\n// Accepted connections are configured to enable TCP keep-alives.\nfunc (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error {\n\tln, err := net.Listen(\"tcp4\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.ServeTLS(ln, certFile, keyFile)\n}\n\n// ListenAndServeTLSEmbed serves HTTPS requests from the given TCP4 addr.\n//\n// certData and keyData must contain valid TLS certificate and key data.\n//\n// Pass custom listener to Serve if you need listening on arbitrary media\n// such as IPv6.\n//\n// If the certFile or keyFile has not been provided the server structure,\n// the function will use previously added TLS configuration.\n//\n// Accepted connections are configured to enable TCP keep-alives.\nfunc (s *Server) ListenAndServeTLSEmbed(addr string, certData, keyData []byte) error {\n\tln, err := net.Listen(\"tcp4\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.ServeTLSEmbed(ln, certData, keyData)\n}\n\n// ServeTLS serves HTTPS requests from the given listener.\n//\n// certFile and keyFile are paths to TLS certificate and key files.\n//\n// If the certFile or keyFile has not been provided the server structure,\n// the function will use previously added TLS configuration.\nfunc (s *Server) ServeTLS(ln net.Listener, certFile, keyFile string) error {\n\ts.mu.Lock()\n\ts.configTLS()\n\tconfigHasCert := len(s.TLSConfig.Certificates) > 0 || s.TLSConfig.GetCertificate != nil\n\tif !configHasCert || certFile != \"\" || keyFile != \"\" {\n\t\tif err := s.AppendCert(certFile, keyFile); err != nil {\n\t\t\ts.mu.Unlock()\n\t\t\treturn err\n\t\t}\n\t}\n\n\ts.mu.Unlock()\n\n\treturn s.Serve(\n\t\ttls.NewListener(ln, s.TLSConfig.Clone()),\n\t)\n}\n\n// ServeTLSEmbed serves HTTPS requests from the given listener.\n//\n// certData and keyData must contain valid TLS certificate and key data.\n//\n// If the certFile or keyFile has not been provided the server structure,\n// the function will use previously added TLS configuration.\nfunc (s *Server) ServeTLSEmbed(ln net.Listener, certData, keyData []byte) error {\n\ts.mu.Lock()\n\ts.configTLS()\n\tconfigHasCert := len(s.TLSConfig.Certificates) > 0 || s.TLSConfig.GetCertificate != nil\n\tif !configHasCert || len(certData) != 0 || len(keyData) != 0 {\n\t\tif err := s.AppendCertEmbed(certData, keyData); err != nil {\n\t\t\ts.mu.Unlock()\n\t\t\treturn err\n\t\t}\n\t}\n\n\ts.mu.Unlock()\n\n\treturn s.Serve(\n\t\ttls.NewListener(ln, s.TLSConfig.Clone()),\n\t)\n}\n\n// AppendCert appends certificate and keyfile to TLS Configuration.\n//\n// This function allows programmer to handle multiple domains\n// in one server structure. See examples/multidomain.\nfunc (s *Server) AppendCert(certFile, keyFile string) error {\n\tif certFile == \"\" && keyFile == \"\" {\n\t\treturn errNoCertOrKeyProvided\n\t}\n\n\tcert, err := tls.LoadX509KeyPair(certFile, keyFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot load TLS key pair from certFile=%q and keyFile=%q: %w\", certFile, keyFile, err)\n\t}\n\n\ts.configTLS()\n\ts.TLSConfig.Certificates = append(s.TLSConfig.Certificates, cert)\n\n\treturn nil\n}\n\n// AppendCertEmbed does the same as AppendCert but using in-memory data.\nfunc (s *Server) AppendCertEmbed(certData, keyData []byte) error {\n\tif len(certData) == 0 && len(keyData) == 0 {\n\t\treturn errNoCertOrKeyProvided\n\t}\n\n\tcert, err := tls.X509KeyPair(certData, keyData)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot load TLS key pair from the provided certData(%d) and keyData(%d): %w\",\n\t\t\tlen(certData), len(keyData), err)\n\t}\n\n\ts.configTLS()\n\ts.TLSConfig.Certificates = append(s.TLSConfig.Certificates, cert)\n\n\treturn nil\n}\n\nfunc (s *Server) configTLS() {\n\tif s.TLSConfig == nil {\n\t\ts.TLSConfig = &tls.Config{}\n\t}\n}\n\n// DefaultConcurrency is the maximum number of concurrent connections\n// the Server may serve by default (i.e. if Server.Concurrency isn't set).\nconst DefaultConcurrency = 256 * 1024\n\n// Serve serves incoming connections from the given listener.\n//\n// Serve blocks until the given listener returns permanent error.\nfunc (s *Server) Serve(ln net.Listener) error {\n\tvar lastOverflowErrorTime time.Time\n\tvar lastPerIPErrorTime time.Time\n\n\tmaxWorkersCount := s.getConcurrency()\n\n\ts.mu.Lock()\n\ts.ln = append(s.ln, ln)\n\tif s.done == nil {\n\t\ts.done = make(chan struct{})\n\t}\n\tif s.concurrencyCh == nil {\n\t\ts.concurrencyCh = make(chan struct{}, maxWorkersCount)\n\t}\n\ts.mu.Unlock()\n\n\twp := &workerPool{\n\t\tWorkerFunc:            s.serveConn,\n\t\tMaxWorkersCount:       maxWorkersCount,\n\t\tLogAllErrors:          s.LogAllErrors,\n\t\tMaxIdleWorkerDuration: s.MaxIdleWorkerDuration,\n\t\tLogger:                s.logger(),\n\t\tconnState:             s.setState,\n\t}\n\twp.Start()\n\n\t// Count our waiting to accept a connection as an open connection.\n\t// This way we can't get into any weird state where just after accepting\n\t// a connection Shutdown is called which reads open as 0 because it isn't\n\t// incremented yet.\n\ts.open.Add(1)\n\tdefer s.open.Add(-1)\n\n\tfor {\n\t\tc, err := acceptConn(s, ln, &lastPerIPErrorTime)\n\t\tif err != nil {\n\t\t\twp.Stop()\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\ts.setState(c, StateNew)\n\t\ts.open.Add(1)\n\t\tif !wp.Serve(c) {\n\t\t\ts.open.Add(-1)\n\t\t\ts.rejectedRequestsCount.Add(1)\n\t\t\ts.writeFastError(c, StatusServiceUnavailable,\n\t\t\t\t\"The connection cannot be served because Server.Concurrency limit exceeded\")\n\t\t\tc.Close()\n\t\t\ts.setState(c, StateClosed)\n\t\t\tif time.Since(lastOverflowErrorTime) > time.Minute {\n\t\t\t\ts.logger().Printf(\"The incoming connection cannot be served, because %d concurrent connections are served. \"+\n\t\t\t\t\t\"Try increasing Server.Concurrency\", maxWorkersCount)\n\t\t\t\tlastOverflowErrorTime = time.Now()\n\t\t\t}\n\n\t\t\t// The current server reached concurrency limit,\n\t\t\t// so give other concurrently running servers a chance\n\t\t\t// accepting incoming connections on the same address.\n\t\t\t//\n\t\t\t// There is a hope other servers didn't reach their\n\t\t\t// concurrency limits yet :)\n\t\t\t//\n\t\t\t// See also: https://github.com/valyala/fasthttp/pull/485#discussion_r239994990\n\t\t\tif s.SleepWhenConcurrencyLimitsExceeded > 0 {\n\t\t\t\ttime.Sleep(s.SleepWhenConcurrencyLimitsExceeded)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Shutdown gracefully shuts down the server without interrupting any active connections.\n// Shutdown works by first closing all open listeners and then waiting indefinitely for all connections\n// to return to idle and then shut down.\n//\n// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.\n// Make sure the program doesn't exit and waits instead for Shutdown to return.\n//\n// Shutdown does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout to something else than 0.\nfunc (s *Server) Shutdown() error {\n\treturn s.ShutdownWithContext(context.Background())\n}\n\n// ShutdownWithContext gracefully shuts down the server without interrupting any active connections.\n// ShutdownWithContext works by first closing all open listeners and then waiting for all connections to return to idle\n// or context timeout and then shut down.\n//\n// When ShutdownWithContext is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.\n// Make sure the program doesn't exit and waits instead for Shutdown to return.\n//\n// ShutdownWithContext does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout\n// to something else than 0.\n//\n// When ShutdownWithContext returns errors, any operation to the Server is unavailable.\nfunc (s *Server) ShutdownWithContext(ctx context.Context) (err error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.stop.Store(1)\n\tdefer s.stop.Store(0)\n\n\tif s.ln == nil {\n\t\treturn nil\n\t}\n\n\tlnerr := s.closeListenersLocked()\n\n\tif s.done != nil {\n\t\tclose(s.done)\n\t}\n\n\t// Closing the listener will make Serve() call Stop on the worker pool.\n\t// Setting .stop to 1 will make serveConn() break out of its loop.\n\t// Now we just have to wait until all workers are done or timeout.\n\tticker := time.NewTicker(time.Millisecond * 100)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\ts.closeIdleConns()\n\n\t\tif open := s.open.Load(); open == 0 {\n\t\t\t// There may be a pending request to call ctx.Done(). Therefore, we only set it to nil when open == 0.\n\t\t\ts.done = nil\n\t\t\treturn lnerr\n\t\t}\n\t\t// This is not an optimal solution but using a sync.WaitGroup\n\t\t// here causes data races as it's hard to prevent Add() to be called\n\t\t// while Wait() is waiting.\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase <-ticker.C:\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\ntype connKeepAliveer interface {\n\tSetKeepAlive(keepalive bool) error\n\tSetKeepAlivePeriod(d time.Duration) error\n\tio.Closer\n}\n\nfunc acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.Conn, error) {\n\tfor {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tif netErr, ok := err.(net.Error); ok && netErr.Timeout() {\n\t\t\t\ts.logger().Printf(\"Timeout error when accepting new connections: %v\", netErr)\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != io.EOF && !strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\t\t\ts.logger().Printf(\"Permanent error when accepting new connections: %v\", err)\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn nil, io.EOF\n\t\t}\n\n\t\tif tc, ok := c.(connKeepAliveer); ok && s.TCPKeepalive {\n\t\t\tif err := tc.SetKeepAlive(s.TCPKeepalive); err != nil {\n\t\t\t\t_ = tc.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif s.TCPKeepalivePeriod > 0 {\n\t\t\t\tif err := tc.SetKeepAlivePeriod(s.TCPKeepalivePeriod); err != nil {\n\t\t\t\t\t_ = tc.Close()\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif s.MaxConnsPerIP > 0 {\n\t\t\tpic := wrapPerIPConn(s, c)\n\t\t\tif pic == nil {\n\t\t\t\tif time.Since(*lastPerIPErrorTime) > time.Minute {\n\t\t\t\t\ts.logger().Printf(\"The number of connections from %s exceeds MaxConnsPerIP=%d\",\n\t\t\t\t\t\tgetConnIP4(c), s.MaxConnsPerIP)\n\t\t\t\t\t*lastPerIPErrorTime = time.Now()\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tc = pic\n\t\t}\n\t\treturn c, nil\n\t}\n}\n\nfunc wrapPerIPConn(s *Server, c net.Conn) net.Conn {\n\tip := getUint32IP(c)\n\tif ip == 0 {\n\t\treturn c\n\t}\n\tn := s.perIPConnCounter.Register(ip)\n\tif n > s.MaxConnsPerIP {\n\t\ts.perIPConnCounter.Unregister(ip)\n\t\ts.writeFastError(c, StatusTooManyRequests, \"The number of connections from your ip exceeds MaxConnsPerIP\")\n\t\tc.Close()\n\t\treturn nil\n\t}\n\treturn acquirePerIPConn(c, ip, &s.perIPConnCounter)\n}\n\nvar defaultLogger = Logger(log.New(os.Stderr, \"\", log.LstdFlags))\n\nfunc (s *Server) logger() Logger {\n\tif s.Logger != nil {\n\t\treturn s.Logger\n\t}\n\treturn defaultLogger\n}\n\nvar (\n\t// ErrPerIPConnLimit may be returned from ServeConn if the number of connections\n\t// per ip exceeds Server.MaxConnsPerIP.\n\tErrPerIPConnLimit = errors.New(\"too many connections per ip\")\n\n\t// ErrConcurrencyLimit may be returned from ServeConn if the number\n\t// of concurrently served connections exceeds Server.Concurrency.\n\tErrConcurrencyLimit = errors.New(\"cannot serve the connection because Server.Concurrency concurrent connections are served\")\n)\n\n// ServeConn serves HTTP requests from the given connection.\n//\n// ServeConn returns nil if all requests from the c are successfully served.\n// It returns non-nil error otherwise.\n//\n// Connection c must immediately propagate all the data passed to Write()\n// to the client. Otherwise requests' processing may hang.\n//\n// ServeConn closes c before returning.\nfunc (s *Server) ServeConn(c net.Conn) error {\n\tif s.MaxConnsPerIP > 0 {\n\t\tpic := wrapPerIPConn(s, c)\n\t\tif pic == nil {\n\t\t\treturn ErrPerIPConnLimit\n\t\t}\n\t\tc = pic\n\t}\n\n\tn := int(s.concurrency.Add(1)) // #nosec G115\n\tif n > s.getConcurrency() {\n\t\ts.concurrency.Add(^uint32(0))\n\t\ts.writeFastError(c, StatusServiceUnavailable, \"The connection cannot be served because Server.Concurrency limit exceeded\")\n\t\tc.Close()\n\t\treturn ErrConcurrencyLimit\n\t}\n\n\ts.open.Add(1)\n\n\terr := s.serveConn(c)\n\n\ts.concurrency.Add(^uint32(0))\n\n\tif err != errHijacked {\n\t\terrc := c.Close()\n\t\ts.setState(c, StateClosed)\n\t\tif err == nil {\n\t\t\terr = errc\n\t\t}\n\t} else {\n\t\terr = nil\n\t\ts.setState(c, StateHijacked)\n\t}\n\treturn err\n}\n\nvar errHijacked = errors.New(\"connection has been hijacked\")\n\n// GetCurrentConcurrency returns a number of currently served\n// connections.\n//\n// This function is intended be used by monitoring systems.\nfunc (s *Server) GetCurrentConcurrency() uint32 {\n\treturn s.concurrency.Load()\n}\n\n// GetOpenConnectionsCount returns a number of opened connections.\n//\n// This function is intended be used by monitoring systems.\nfunc (s *Server) GetOpenConnectionsCount() int32 {\n\tif s.stop.Load() == 0 {\n\t\t// Decrement by one to avoid reporting the extra open value that gets\n\t\t// counted while the server is listening.\n\t\treturn s.open.Load() - 1\n\t}\n\t// This is not perfect, because s.stop could have changed to zero\n\t// before we load the value of s.open. However, in the common case\n\t// this avoids underreporting open connections by 1 during server shutdown.\n\treturn s.open.Load()\n}\n\n// GetRejectedConnectionsCount returns a number of rejected connections.\n//\n// This function is intended be used by monitoring systems.\nfunc (s *Server) GetRejectedConnectionsCount() uint32 {\n\treturn s.rejectedRequestsCount.Load()\n}\n\nfunc (s *Server) getConcurrency() int {\n\tn := s.Concurrency\n\tif n <= 0 {\n\t\tn = DefaultConcurrency\n\t}\n\treturn n\n}\n\nvar globalConnID uint64\n\nfunc nextConnID() uint64 {\n\treturn atomic.AddUint64(&globalConnID, 1)\n}\n\n// DefaultMaxRequestBodySize is the maximum request body size the server\n// reads by default.\n//\n// See Server.MaxRequestBodySize for details.\nconst DefaultMaxRequestBodySize = 4 * 1024 * 1024\n\nfunc (s *Server) idleTimeout() time.Duration {\n\tif s.IdleTimeout != 0 {\n\t\treturn s.IdleTimeout\n\t}\n\treturn s.ReadTimeout\n}\n\nfunc (s *Server) serveConnCleanup() {\n\ts.open.Add(-1)\n\ts.concurrency.Add(^uint32(0))\n}\n\nfunc (s *Server) serveConn(c net.Conn) error {\n\tdefer s.serveConnCleanup()\n\ts.concurrency.Add(1)\n\n\tproto, err := s.getNextProto(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif handler, ok := s.nextProtos[proto]; ok {\n\t\t// Remove read or write deadlines that might have previously been set.\n\t\t// The next handler is responsible for setting its own deadlines.\n\t\tif s.ReadTimeout > 0 || s.WriteTimeout > 0 {\n\t\t\tif err := c.SetDeadline(zeroTime); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn handler(c)\n\t}\n\n\ts.idleConnsMu.Lock()\n\tif s.idleConns == nil {\n\t\ts.idleConns = make(map[net.Conn]*atomic.Int64)\n\t}\n\tidleConnTime, ok := s.idleConns[c]\n\tif !ok {\n\t\tv := idleConnTimePool.Get()\n\t\tif v == nil {\n\t\t\tv = &atomic.Int64{}\n\t\t}\n\t\tidleConnTime = v.(*atomic.Int64)\n\t\ts.idleConns[c] = idleConnTime\n\t}\n\n\t// Count the connection as Idle after 5 seconds.\n\t// Same as net/http.Server:\n\t// https://github.com/golang/go/blob/85d7bab91d9a3ed1f76842e4328973ea75efef54/src/net/http/server.go#L2834-L2836\n\tidleConnTime.Store(time.Now().Add(time.Second * 5).Unix())\n\ts.idleConnsMu.Unlock()\n\n\tserverName := s.getServerName()\n\tconnRequestNum := uint64(0)\n\tconnID := nextConnID()\n\tconnTime := time.Now()\n\tmaxRequestBodySize := s.MaxRequestBodySize\n\tif maxRequestBodySize <= 0 {\n\t\tmaxRequestBodySize = DefaultMaxRequestBodySize\n\t}\n\twriteTimeout := s.WriteTimeout\n\tpreviousWriteTimeout := time.Duration(0)\n\n\tctx := s.acquireCtx(c)\n\tctx.connTime = connTime\n\tisTLS := ctx.IsTLS()\n\tvar (\n\t\tbr *bufio.Reader\n\t\tbw *bufio.Writer\n\n\t\ttimeoutResponse  *Response\n\t\thijackHandler    HijackHandler\n\t\thijackNoResponse bool\n\n\t\tconnectionClose bool\n\n\t\tcontinueReadingRequest = true\n\t)\n\tfor {\n\t\tconnRequestNum++\n\n\t\t// If this is a keep-alive connection set the idle timeout.\n\t\tif connRequestNum > 1 {\n\t\t\tif d := s.idleTimeout(); d > 0 {\n\t\t\t\tif err = c.SetReadDeadline(time.Now().Add(d)); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !s.ReduceMemoryUsage || br != nil {\n\t\t\tif br == nil {\n\t\t\t\tbr = acquireReader(ctx)\n\t\t\t}\n\n\t\t\t// If this is a keep-alive connection we want to try and read the first bytes\n\t\t\t// within the idle time.\n\t\t\tif connRequestNum > 1 {\n\t\t\t\tvar b []byte\n\t\t\t\tb, err = br.Peek(1)\n\t\t\t\tif len(b) == 0 {\n\t\t\t\t\t// If reading from a keep-alive connection returns nothing it means\n\t\t\t\t\t// the connection was closed (either timeout or from the other side).\n\t\t\t\t\tif err != io.EOF {\n\t\t\t\t\t\terr = ErrNothingRead{error: err}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// If this is a keep-alive connection acquireByteReader will try to peek\n\t\t\t// a couple of bytes already so the idle timeout will already be used.\n\t\t\tbr, err = acquireByteReader(&ctx)\n\t\t}\n\n\t\tctx.Request.isTLS = isTLS\n\t\tctx.Response.Header.noDefaultContentType = s.NoDefaultContentType\n\t\tctx.Response.Header.noDefaultDate = s.NoDefaultDate\n\n\t\t// Secure header error logs configuration\n\t\tctx.Request.Header.secureErrorLogMessage = s.SecureErrorLogMessage\n\t\tctx.Response.Header.secureErrorLogMessage = s.SecureErrorLogMessage\n\t\tctx.Request.secureErrorLogMessage = s.SecureErrorLogMessage\n\t\tctx.Response.secureErrorLogMessage = s.SecureErrorLogMessage\n\n\t\tif err == nil {\n\t\t\ts.setState(c, StateActive)\n\n\t\t\tidleConnTime.Store(0)\n\n\t\t\tif s.ReadTimeout > 0 {\n\t\t\t\tif err = c.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else if s.IdleTimeout > 0 && connRequestNum > 1 {\n\t\t\t\t// If this was an idle connection and the server has an IdleTimeout but\n\t\t\t\t// no ReadTimeout then we should remove the ReadTimeout.\n\t\t\t\tif err = c.SetReadDeadline(zeroTime); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif s.DisableHeaderNamesNormalizing {\n\t\t\t\tctx.Request.Header.DisableNormalizing()\n\t\t\t\tctx.Response.Header.DisableNormalizing()\n\t\t\t}\n\n\t\t\t// Reading Headers.\n\t\t\t//\n\t\t\t// If we have pipeline response in the outgoing buffer,\n\t\t\t// we only want to try and read the next headers once.\n\t\t\t// If we have to wait for the next request we flush the\n\t\t\t// outgoing buffer first so it doesn't have to wait.\n\t\t\tif bw != nil && bw.Buffered() > 0 {\n\t\t\t\terr = ctx.Request.Header.readLoop(br, false)\n\t\t\t\tif err == ErrNeedMore {\n\t\t\t\t\terr = bw.Flush()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\terr = ctx.Request.Header.Read(br)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terr = ctx.Request.Header.Read(br)\n\t\t\t}\n\n\t\t\tif err == nil {\n\t\t\t\tif onHdrRecv := s.HeaderReceived; onHdrRecv != nil {\n\t\t\t\t\treqConf := onHdrRecv(&ctx.Request.Header)\n\t\t\t\t\tif reqConf.ReadTimeout > 0 {\n\t\t\t\t\t\tdeadline := time.Now().Add(reqConf.ReadTimeout)\n\t\t\t\t\t\tif err = c.SetReadDeadline(deadline); err != nil {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase reqConf.MaxRequestBodySize > 0:\n\t\t\t\t\t\tmaxRequestBodySize = reqConf.MaxRequestBodySize\n\t\t\t\t\tcase s.MaxRequestBodySize > 0:\n\t\t\t\t\t\tmaxRequestBodySize = s.MaxRequestBodySize\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tmaxRequestBodySize = DefaultMaxRequestBodySize\n\t\t\t\t\t}\n\t\t\t\t\tif reqConf.WriteTimeout > 0 {\n\t\t\t\t\t\twriteTimeout = reqConf.WriteTimeout\n\t\t\t\t\t} else {\n\t\t\t\t\t\twriteTimeout = s.WriteTimeout\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tif err = ctx.Request.parseURI(); err != nil {\n\t\t\t\t\t\tbw = s.writeErrorResponse(bw, ctx, serverName, err)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err == nil {\n\t\t\t\t\t// read body\n\t\t\t\t\tif s.StreamRequestBody {\n\t\t\t\t\t\terr = ctx.Request.readBodyStream(br, maxRequestBodySize, s.GetOnly, !s.DisablePreParseMultipartForm)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = ctx.Request.readLimitBody(br, maxRequestBodySize, s.GetOnly, !s.DisablePreParseMultipartForm)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// When StreamRequestBody is set to true, we cannot safely release br.\n\t\t\t// For example, when using chunked encoding, it's possible that br has only read the request headers.\n\t\t\tif (!s.StreamRequestBody && s.ReduceMemoryUsage && br.Buffered() == 0) || err != nil {\n\t\t\t\treleaseReader(s, br)\n\t\t\t\tbr = nil\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\terr = nil\n\t\t\t} else if nr, ok := err.(ErrNothingRead); ok {\n\t\t\t\tif connRequestNum > 1 {\n\t\t\t\t\t// This is not the first request and we haven't read a single byte\n\t\t\t\t\t// of a new request yet. This means it's just a keep-alive connection\n\t\t\t\t\t// closing down either because the remote closed it or because\n\t\t\t\t\t// or a read timeout on our side. Either way just close the connection\n\t\t\t\t\t// and don't return any error response.\n\t\t\t\t\terr = nil\n\t\t\t\t} else {\n\t\t\t\t\terr = nr.error\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tbw = s.writeErrorResponse(bw, ctx, serverName, err)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\t// 'Expect: 100-continue' request handling.\n\t\t// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3 for details.\n\t\tif ctx.Request.MayContinue() {\n\t\t\t// Allow the ability to deny reading the incoming request body\n\t\t\tif s.ContinueHandler != nil {\n\t\t\t\tif continueReadingRequest = s.ContinueHandler(&ctx.Request.Header); !continueReadingRequest {\n\t\t\t\t\tif br != nil {\n\t\t\t\t\t\tbr.Reset(ctx.c)\n\t\t\t\t\t}\n\n\t\t\t\t\tctx.SetStatusCode(StatusExpectationFailed)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif continueReadingRequest {\n\t\t\t\tif bw == nil {\n\t\t\t\t\tbw = acquireWriter(ctx)\n\t\t\t\t}\n\n\t\t\t\t// Send 'HTTP/1.1 100 Continue' response.\n\t\t\t\t_, err = bw.Write(strResponseContinue)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = bw.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif s.ReduceMemoryUsage {\n\t\t\t\t\treleaseWriter(s, bw)\n\t\t\t\t\tbw = nil\n\t\t\t\t}\n\n\t\t\t\t// Read request body.\n\t\t\t\tif br == nil {\n\t\t\t\t\tbr = acquireReader(ctx)\n\t\t\t\t}\n\n\t\t\t\tif s.StreamRequestBody {\n\t\t\t\t\terr = ctx.Request.ContinueReadBodyStream(br, maxRequestBodySize, !s.DisablePreParseMultipartForm)\n\t\t\t\t} else {\n\t\t\t\t\terr = ctx.Request.ContinueReadBody(br, maxRequestBodySize, !s.DisablePreParseMultipartForm)\n\t\t\t\t}\n\t\t\t\tif (!s.StreamRequestBody && s.ReduceMemoryUsage && br.Buffered() == 0) || err != nil {\n\t\t\t\t\treleaseReader(s, br)\n\t\t\t\t\tbr = nil\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tbw = s.writeErrorResponse(bw, ctx, serverName, err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// store req.ConnectionClose so even if it was changed inside of handler\n\t\tconnectionClose = s.DisableKeepalive || ctx.Request.Header.ConnectionClose()\n\n\t\tif serverName != \"\" {\n\t\t\tctx.Response.Header.SetServer(serverName)\n\t\t}\n\t\tctx.connID = connID\n\t\tctx.connRequestNum = connRequestNum\n\t\tctx.time = time.Now()\n\n\t\t// If a client denies a request the handler should not be called\n\t\tif continueReadingRequest {\n\t\t\ts.Handler(ctx)\n\t\t}\n\n\t\ttimeoutResponse = ctx.timeoutResponse\n\t\tif timeoutResponse != nil {\n\t\t\t// Acquire a new ctx because the old one will still be in use by the timeout out handler.\n\t\t\tctx = s.acquireCtx(c)\n\t\t\ttimeoutResponse.CopyTo(&ctx.Response)\n\t\t}\n\n\t\tif ctx.IsHead() {\n\t\t\tctx.Response.SkipBody = true\n\t\t}\n\n\t\thijackHandler = ctx.hijackHandler\n\t\tctx.hijackHandler = nil\n\t\thijackNoResponse = ctx.hijackNoResponse && hijackHandler != nil\n\t\tctx.hijackNoResponse = false\n\n\t\tif writeTimeout > 0 {\n\t\t\tif err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpreviousWriteTimeout = writeTimeout\n\t\t} else if previousWriteTimeout > 0 {\n\t\t\t// We don't want a write timeout but we previously set one, remove it.\n\t\t\tif err = c.SetWriteDeadline(zeroTime); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tpreviousWriteTimeout = 0\n\t\t}\n\n\t\tconnectionClose = connectionClose ||\n\t\t\t(s.MaxRequestsPerConn > 0 && connRequestNum >= uint64(s.MaxRequestsPerConn)) || // #nosec G115\n\t\t\tctx.Response.Header.ConnectionClose() ||\n\t\t\t(s.CloseOnShutdown && s.stop.Load() == 1)\n\t\tif connectionClose {\n\t\t\tctx.Response.Header.SetConnectionClose()\n\t\t} else if !ctx.Request.Header.IsHTTP11() {\n\t\t\t// Set 'Connection: keep-alive' response header for HTTP/1.0 request.\n\t\t\t// There is no need in setting this header for http/1.1, since in http/1.1\n\t\t\t// connections are keep-alive by default.\n\t\t\tctx.Response.Header.setNonSpecial(strConnection, strKeepAlive)\n\t\t}\n\n\t\tif serverName != \"\" && len(ctx.Response.Header.Server()) == 0 {\n\t\t\tctx.Response.Header.SetServer(serverName)\n\t\t}\n\n\t\tif !hijackNoResponse {\n\t\t\tif bw == nil {\n\t\t\t\tbw = acquireWriter(ctx)\n\t\t\t}\n\t\t\tif err = writeResponse(ctx, bw); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Only flush the writer if we don't have another request in the pipeline.\n\t\t\t// This is a big of an ugly optimization for https://www.techempower.com/benchmarks/\n\t\t\t// This benchmark will send 16 pipelined requests. It is faster to pack as many responses\n\t\t\t// in a TCP packet and send it back at once than waiting for a flush every request.\n\t\t\t// In real world circumstances this behaviour could be argued as being wrong.\n\t\t\tif br == nil || br.Buffered() == 0 || connectionClose || (s.ReduceMemoryUsage && hijackHandler == nil) {\n\t\t\t\terr = bw.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif connectionClose {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif s.ReduceMemoryUsage && hijackHandler == nil {\n\t\t\t\treleaseWriter(s, bw)\n\t\t\t\tbw = nil\n\t\t\t}\n\t\t}\n\n\t\tif hijackHandler != nil {\n\t\t\tvar hjr io.Reader = c\n\t\t\tif br != nil {\n\t\t\t\thjr = br\n\t\t\t\tbr = nil\n\t\t\t}\n\t\t\tif bw != nil {\n\t\t\t\terr = bw.Flush()\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treleaseWriter(s, bw)\n\t\t\t\tbw = nil\n\t\t\t}\n\t\t\terr = c.SetDeadline(zeroTime)\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tgo hijackConnHandler(ctx, hjr, c, s, hijackHandler)\n\t\t\terr = errHijacked\n\t\t\tbreak\n\t\t}\n\n\t\tif ctx.Request.bodyStream != nil {\n\t\t\tif rs, ok := ctx.Request.bodyStream.(*requestStream); ok {\n\t\t\t\treleaseRequestStream(rs)\n\t\t\t}\n\t\t\tctx.Request.bodyStream = nil\n\t\t}\n\n\t\ts.setState(c, StateIdle)\n\t\tctx.Request.Reset()\n\t\tctx.Response.Reset()\n\n\t\tif s.stop.Load() == 1 {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\n\t\tidleConnTime.Store(time.Now().Unix())\n\t}\n\n\tif br != nil {\n\t\treleaseReader(s, br)\n\t}\n\tif bw != nil {\n\t\treleaseWriter(s, bw)\n\t}\n\tif hijackHandler == nil {\n\t\ts.releaseCtx(ctx)\n\t}\n\n\ts.idleConnsMu.Lock()\n\tic, ok := s.idleConns[c]\n\tif ok {\n\t\tidleConnTimePool.Put(ic)\n\t\tdelete(s.idleConns, c)\n\t}\n\ts.idleConnsMu.Unlock()\n\n\treturn err\n}\n\nfunc (s *Server) setState(nc net.Conn, state ConnState) {\n\tif hook := s.ConnState; hook != nil {\n\t\thook(nc, state)\n\t}\n}\n\nfunc hijackConnHandler(ctx *RequestCtx, r io.Reader, c net.Conn, s *Server, h HijackHandler) {\n\thjc := s.acquireHijackConn(r, c)\n\th(hjc)\n\n\tif br, ok := r.(*bufio.Reader); ok {\n\t\treleaseReader(s, br)\n\t}\n\tif !s.KeepHijackedConns {\n\t\tc.Close()\n\t\ts.releaseHijackConn(hjc)\n\t}\n\ts.releaseCtx(ctx)\n}\n\nfunc (s *Server) acquireHijackConn(r io.Reader, c net.Conn) *hijackConn {\n\tv := s.hijackConnPool.Get()\n\tif v == nil {\n\t\thjc := &hijackConn{\n\t\t\tConn: c,\n\t\t\tr:    r,\n\t\t\ts:    s,\n\t\t}\n\t\treturn hjc\n\t}\n\thjc := v.(*hijackConn)\n\thjc.Conn = c\n\thjc.r = r\n\treturn hjc\n}\n\nfunc (s *Server) releaseHijackConn(hjc *hijackConn) {\n\thjc.Conn = nil\n\thjc.r = nil\n\ts.hijackConnPool.Put(hjc)\n}\n\ntype hijackConn struct {\n\tnet.Conn\n\n\tr io.Reader\n\ts *Server\n}\n\nfunc (c *hijackConn) UnsafeConn() net.Conn {\n\treturn c.Conn\n}\n\nfunc (c *hijackConn) Read(p []byte) (int, error) {\n\treturn c.r.Read(p)\n}\n\nfunc (c *hijackConn) Close() error {\n\tif !c.s.KeepHijackedConns {\n\t\t// when we do not keep hijacked connections,\n\t\t// it is closed in hijackConnHandler.\n\t\treturn nil\n\t}\n\n\treturn c.Conn.Close()\n}\n\n// LastTimeoutErrorResponse returns the last timeout response set\n// via TimeoutError* call.\n//\n// This function is intended for custom server implementations.\nfunc (ctx *RequestCtx) LastTimeoutErrorResponse() *Response {\n\treturn ctx.timeoutResponse\n}\n\nfunc writeResponse(ctx *RequestCtx, w *bufio.Writer) error {\n\tif ctx.timeoutResponse != nil {\n\t\treturn errors.New(\"cannot write timed out response\")\n\t}\n\terr := ctx.Response.Write(w)\n\n\treturn err\n}\n\nconst (\n\tdefaultReadBufferSize  = 4096\n\tdefaultWriteBufferSize = 4096\n)\n\nfunc acquireByteReader(ctxP **RequestCtx) (*bufio.Reader, error) {\n\tctx := *ctxP\n\ts := ctx.s\n\tc := ctx.c\n\ts.releaseCtx(ctx)\n\n\t//nolint:wastedassign // Make GC happy, so it could garbage collect ctx while we wait for the\n\t// next request.\n\tctx = nil\n\t*ctxP = nil\n\n\tvar b [1]byte\n\tn, err := c.Read(b[:])\n\n\tctx = s.acquireCtx(c)\n\t*ctxP = ctx\n\tif err != nil {\n\t\t// Treat all errors as EOF on unsuccessful read\n\t\t// of the first request byte.\n\t\treturn nil, io.EOF\n\t}\n\tif n != 1 {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: Reader must return at least one byte\")\n\t}\n\n\tctx.fbr.c = c\n\tctx.fbr.ch = b[0] // #nosec G602\n\tctx.fbr.byteRead = false\n\tr := acquireReader(ctx)\n\tr.Reset(&ctx.fbr)\n\treturn r, nil\n}\n\nfunc acquireReader(ctx *RequestCtx) *bufio.Reader {\n\tv := ctx.s.readerPool.Get()\n\tif v == nil {\n\t\tn := ctx.s.ReadBufferSize\n\t\tif n <= 0 {\n\t\t\tn = defaultReadBufferSize\n\t\t}\n\t\treturn bufio.NewReaderSize(ctx.c, n)\n\t}\n\tr := v.(*bufio.Reader)\n\tr.Reset(ctx.c)\n\treturn r\n}\n\nfunc releaseReader(s *Server, r *bufio.Reader) {\n\ts.readerPool.Put(r)\n}\n\nfunc acquireWriter(ctx *RequestCtx) *bufio.Writer {\n\tv := ctx.s.writerPool.Get()\n\tif v == nil {\n\t\tn := ctx.s.WriteBufferSize\n\t\tif n <= 0 {\n\t\t\tn = defaultWriteBufferSize\n\t\t}\n\t\treturn bufio.NewWriterSize(ctx.c, n)\n\t}\n\tw := v.(*bufio.Writer)\n\tw.Reset(ctx.c)\n\treturn w\n}\n\nfunc releaseWriter(s *Server, w *bufio.Writer) {\n\ts.writerPool.Put(w)\n}\n\nfunc (s *Server) acquireCtx(c net.Conn) (ctx *RequestCtx) {\n\tv := s.ctxPool.Get()\n\tif v == nil {\n\t\tkeepBodyBuffer := !s.ReduceMemoryUsage\n\n\t\tctx = new(RequestCtx)\n\t\tctx.Request.keepBodyBuffer = keepBodyBuffer\n\t\tctx.Response.keepBodyBuffer = keepBodyBuffer\n\t\tctx.s = s\n\t} else {\n\t\tctx = v.(*RequestCtx)\n\t}\n\tif s.FormValueFunc != nil {\n\t\tctx.formValueFunc = s.FormValueFunc\n\t}\n\tctx.c = c\n\n\treturn ctx\n}\n\n// Init2 prepares ctx for passing to RequestHandler.\n//\n// conn is used only for determining local and remote addresses.\n//\n// This function is intended for custom Server implementations.\n// See https://github.com/valyala/httpteleport for details.\nfunc (ctx *RequestCtx) Init2(conn net.Conn, logger Logger, reduceMemoryUsage bool) {\n\tctx.c = conn\n\tctx.remoteAddr = nil\n\tctx.logger.logger = logger\n\tctx.connID = nextConnID()\n\tctx.s = fakeServer\n\tctx.connRequestNum = 0\n\tctx.connTime = time.Now()\n\n\tkeepBodyBuffer := !reduceMemoryUsage\n\tctx.Request.keepBodyBuffer = keepBodyBuffer\n\tctx.Response.keepBodyBuffer = keepBodyBuffer\n}\n\n// Init prepares ctx for passing to RequestHandler.\n//\n// remoteAddr and logger are optional. They are used by RequestCtx.Logger().\n//\n// This function is intended for custom Server implementations.\nfunc (ctx *RequestCtx) Init(req *Request, remoteAddr net.Addr, logger Logger) {\n\tif remoteAddr == nil {\n\t\tremoteAddr = zeroTCPAddr\n\t}\n\tc := &fakeAddrer{\n\t\tladdr: zeroTCPAddr,\n\t\traddr: remoteAddr,\n\t}\n\tif logger == nil {\n\t\tlogger = defaultLogger\n\t}\n\tctx.Init2(c, logger, true)\n\treq.CopyTo(&ctx.Request)\n}\n\n// Deadline returns the time when work done on behalf of this context\n// should be canceled. Deadline returns ok==false when no deadline is\n// set. Successive calls to Deadline return the same results.\n//\n// This method always returns 0, false and is only present to make\n// RequestCtx implement the context interface.\nfunc (ctx *RequestCtx) Deadline() (deadline time.Time, ok bool) {\n\treturn time.Time{}, false\n}\n\n// Done returns a channel that's closed when work done on behalf of this\n// context should be canceled. Done may return nil if this context can\n// never be canceled. Successive calls to Done return the same value.\n//\n// Note: Because creating a new channel for every request is just too expensive, so\n// RequestCtx.s.done is only closed when the server is shutting down.\nfunc (ctx *RequestCtx) Done() <-chan struct{} {\n\treturn ctx.s.done\n}\n\n// Err returns a non-nil error value after Done is closed,\n// successive calls to Err return the same error.\n// If Done is not yet closed, Err returns nil.\n// If Done is closed, Err returns a non-nil error explaining why:\n// Canceled if the context was canceled (via server Shutdown)\n// or DeadlineExceeded if the context's deadline passed.\n//\n// Note: Because creating a new channel for every request is just too expensive, so\n// RequestCtx.s.done is only closed when the server is shutting down.\nfunc (ctx *RequestCtx) Err() error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn context.Canceled\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// Value returns the value associated with this context for key, or nil\n// if no value is associated with key. Successive calls to Value with\n// the same key returns the same result.\n//\n// This method is present to make RequestCtx implement the context interface.\n// This method is the same as calling ctx.UserValue(key).\nfunc (ctx *RequestCtx) Value(key any) any {\n\treturn ctx.UserValue(key)\n}\n\nvar fakeServer = &Server{\n\tdone: make(chan struct{}),\n\t// Initialize concurrencyCh for TimeoutHandler\n\tconcurrencyCh: make(chan struct{}, DefaultConcurrency),\n}\n\ntype fakeAddrer struct {\n\tnet.Conn\n\n\tladdr net.Addr\n\traddr net.Addr\n}\n\nfunc (fa *fakeAddrer) RemoteAddr() net.Addr {\n\treturn fa.raddr\n}\n\nfunc (fa *fakeAddrer) LocalAddr() net.Addr {\n\treturn fa.laddr\n}\n\nfunc (fa *fakeAddrer) Read(p []byte) (int, error) {\n\t// developer sanity-check\n\tpanic(\"BUG: unexpected Read call\")\n}\n\nfunc (fa *fakeAddrer) Write(p []byte) (int, error) {\n\t// developer sanity-check\n\tpanic(\"BUG: unexpected Write call\")\n}\n\nfunc (fa *fakeAddrer) Close() error {\n\t// developer sanity-check\n\tpanic(\"BUG: unexpected Close call\")\n}\n\nfunc (s *Server) releaseCtx(ctx *RequestCtx) {\n\tif ctx.timeoutResponse != nil {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: cannot release timed out RequestCtx\")\n\t}\n\n\tctx.reset()\n\ts.ctxPool.Put(ctx)\n}\n\nfunc (s *Server) getServerName() string {\n\tserverName := s.Name\n\tif serverName == \"\" {\n\t\tif !s.NoDefaultServerHeader {\n\t\t\tserverName = defaultServerName\n\t\t}\n\t}\n\treturn serverName\n}\n\nfunc (s *Server) writeFastError(w io.Writer, statusCode int, msg string) {\n\tw.Write(formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))) //nolint:errcheck\n\n\tserver := s.getServerName()\n\tif server != \"\" {\n\t\tserver = fmt.Sprintf(\"Server: %s\\r\\n\", server)\n\t}\n\tdate := \"\"\n\tif !s.NoDefaultDate {\n\t\tserverDateOnce.Do(updateServerDate)\n\t\tdate = fmt.Sprintf(\"Date: %s\\r\\n\", serverDate.Load())\n\t}\n\n\tfmt.Fprintf(w, \"Connection: close\\r\\n\"+\n\t\tserver+\n\t\tdate+\n\t\t\"Content-Type: text/plain\\r\\n\"+\n\t\t\"Content-Length: %d\\r\\n\"+\n\t\t\"\\r\\n\"+\n\t\t\"%s\",\n\t\tlen(msg), msg)\n}\n\nfunc defaultErrorHandler(ctx *RequestCtx, err error) {\n\tif _, ok := err.(*ErrSmallBuffer); ok {\n\t\tctx.Error(\"Too big request header\", StatusRequestHeaderFieldsTooLarge)\n\t} else if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {\n\t\tctx.Error(\"Request timeout\", StatusRequestTimeout)\n\t} else {\n\t\tctx.Error(\"Error when parsing request\", StatusBadRequest)\n\t}\n}\n\nfunc (s *Server) writeErrorResponse(bw *bufio.Writer, ctx *RequestCtx, serverName string, err error) *bufio.Writer {\n\terrorHandler := defaultErrorHandler\n\tif s.ErrorHandler != nil {\n\t\terrorHandler = s.ErrorHandler\n\t}\n\n\terrorHandler(ctx, err)\n\n\tif serverName != \"\" {\n\t\tctx.Response.Header.SetServer(serverName)\n\t}\n\tctx.SetConnectionClose()\n\tif bw == nil {\n\t\tbw = acquireWriter(ctx)\n\t}\n\n\twriteResponse(ctx, bw) //nolint:errcheck\n\tctx.Response.Reset()\n\tbw.Flush()\n\n\treturn bw\n}\n\nvar idleConnTimePool sync.Pool\n\nfunc (s *Server) closeIdleConns() {\n\ts.idleConnsMu.Lock()\n\tnow := time.Now().Unix()\n\tfor c, ict := range s.idleConns {\n\t\tt := ict.Load()\n\t\tif t != 0 && now-t >= 0 {\n\t\t\t_ = c.Close()\n\t\t\tdelete(s.idleConns, c)\n\t\t\tidleConnTimePool.Put(ict)\n\t\t}\n\t}\n\ts.idleConnsMu.Unlock()\n}\n\nfunc (s *Server) closeListenersLocked() error {\n\tvar err error\n\tfor _, ln := range s.ln {\n\t\tif cerr := ln.Close(); cerr != nil && err == nil {\n\t\t\terr = cerr\n\t\t}\n\t}\n\ts.ln = nil\n\treturn err\n}\n\n// A ConnState represents the state of a client connection to a server.\n// It's used by the optional Server.ConnState hook.\ntype ConnState int\n\nconst (\n\t// StateNew represents a new connection that is expected to\n\t// send a request immediately. Connections begin at this\n\t// state and then transition to either StateActive or\n\t// StateClosed.\n\tStateNew ConnState = iota\n\n\t// StateActive represents a connection that has read 1 or more\n\t// bytes of a request. The Server.ConnState hook for\n\t// StateActive fires before the request has entered a handler\n\t// and doesn't fire again until the request has been\n\t// handled. After the request is handled, the state\n\t// transitions to StateClosed, StateHijacked, or StateIdle.\n\t// For HTTP/2, StateActive fires on the transition from zero\n\t// to one active request, and only transitions away once all\n\t// active requests are complete. That means that ConnState\n\t// cannot be used to do per-request work; ConnState only notes\n\t// the overall state of the connection.\n\tStateActive\n\n\t// StateIdle represents a connection that has finished\n\t// handling a request and is in the keep-alive state, waiting\n\t// for a new request. Connections transition from StateIdle\n\t// to either StateActive or StateClosed.\n\tStateIdle\n\n\t// StateHijacked represents a hijacked connection.\n\t// This is a terminal state. It does not transition to StateClosed.\n\tStateHijacked\n\n\t// StateClosed represents a closed connection.\n\t// This is a terminal state. Hijacked connections do not\n\t// transition to StateClosed.\n\tStateClosed\n)\n\nvar stateName = []string{\n\tStateNew:      \"new\",\n\tStateActive:   \"active\",\n\tStateIdle:     \"idle\",\n\tStateHijacked: \"hijacked\",\n\tStateClosed:   \"closed\",\n}\n\nfunc (c ConnState) String() string {\n\treturn stateName[c]\n}\n"
  },
  {
    "path": "server_example_test.go",
    "content": "package fasthttp_test\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp\"\n)\n\nfunc ExampleListenAndServe() {\n\t// The server will listen for incoming requests on this address.\n\tlistenAddr := \"127.0.0.1:80\"\n\n\t// This function will be called by the server for each incoming request.\n\t//\n\t// RequestCtx provides a lot of functionality related to http request\n\t// processing. See RequestCtx docs for details.\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\tfmt.Fprintf(ctx, \"Hello, world! Requested path is %q\", ctx.Path())\n\t}\n\n\t// Start the server with default settings.\n\t// Create Server instance for adjusting server settings.\n\t//\n\t// ListenAndServe returns only on error, so usually it blocks forever.\n\tif err := fasthttp.ListenAndServe(listenAddr, requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n\nfunc ExampleServe() {\n\t// Create network listener for accepting incoming requests.\n\t//\n\t// Note that you are not limited by TCP listener - arbitrary\n\t// net.Listener may be used by the server.\n\t// For example, unix socket listener or TLS listener.\n\tln, err := net.Listen(\"tcp4\", \"127.0.0.1:8080\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error in net.Listen: %v\", err)\n\t}\n\n\t// This function will be called by the server for each incoming request.\n\t//\n\t// RequestCtx provides a lot of functionality related to http request\n\t// processing. See RequestCtx docs for details.\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\tfmt.Fprintf(ctx, \"Hello, world! Requested path is %q\", ctx.Path())\n\t}\n\n\t// Start the server with default settings.\n\t// Create Server instance for adjusting server settings.\n\t//\n\t// Serve returns on ln.Close() or error, so usually it blocks forever.\n\tif err := fasthttp.Serve(ln, requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in Serve: %v\", err)\n\t}\n}\n\nfunc ExampleServer() {\n\t// This function will be called by the server for each incoming request.\n\t//\n\t// RequestCtx provides a lot of functionality related to http request\n\t// processing. See RequestCtx docs for details.\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\tfmt.Fprintf(ctx, \"Hello, world! Requested path is %q\", ctx.Path())\n\t}\n\n\t// Create custom server.\n\ts := &fasthttp.Server{\n\t\tHandler: requestHandler,\n\n\t\t// Every response will contain 'Server: My super server' header.\n\t\tName: \"My super server\",\n\n\t\t// Other Server settings may be set here.\n\t}\n\n\t// Start the server listening for incoming requests on the given address.\n\t//\n\t// ListenAndServe returns only on error, so usually it blocks forever.\n\tif err := s.ListenAndServe(\"127.0.0.1:80\"); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n\nfunc ExampleRequestCtx_Hijack() {\n\t// hijackHandler is called on hijacked connection.\n\thijackHandler := func(c net.Conn) {\n\t\tfmt.Fprintf(c, \"This message is sent over a hijacked connection to the client %s\\n\", c.RemoteAddr())\n\t\tfmt.Fprintf(c, \"Send me something and I'll echo it to you\\n\")\n\t\tvar buf [1]byte\n\t\tfor {\n\t\t\tif _, err := c.Read(buf[:]); err != nil {\n\t\t\t\tlog.Printf(\"error when reading from hijacked connection: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfmt.Fprintf(c, \"You sent me %q. Waiting for new data\\n\", buf[:])\n\t\t}\n\t}\n\n\t// requestHandler is called for each incoming request.\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\tpath := ctx.Path()\n\t\tswitch {\n\t\tcase string(path) == \"/hijack\":\n\t\t\t// Note that the connection is hijacked only after\n\t\t\t// returning from requestHandler and sending http response.\n\t\t\tctx.Hijack(hijackHandler)\n\n\t\t\t// The connection will be hijacked after sending this response.\n\t\t\tfmt.Fprintf(ctx, \"Hijacked the connection!\")\n\t\tcase string(path) == \"/\":\n\t\t\tfmt.Fprintf(ctx, \"Root directory requested\")\n\t\tdefault:\n\t\t\tfmt.Fprintf(ctx, \"Requested path is %q\", path)\n\t\t}\n\t}\n\n\tif err := fasthttp.ListenAndServe(\":80\", requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n\nfunc ExampleRequestCtx_TimeoutError() {\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\t// Emulate long-running task, which touches ctx.\n\t\tdoneCh := make(chan struct{})\n\t\tgo func() {\n\t\t\tworkDuration := time.Millisecond * time.Duration(rand.Intn(2000))\n\t\t\ttime.Sleep(workDuration)\n\n\t\t\tfmt.Fprintf(ctx, \"ctx has been accessed by long-running task\\n\")\n\t\t\tfmt.Fprintf(ctx, \"The requestHandler may be finished by this time.\\n\")\n\n\t\t\tclose(doneCh)\n\t\t}()\n\n\t\tselect {\n\t\tcase <-doneCh:\n\t\t\tfmt.Fprintf(ctx, \"The task has been finished in less than a second\")\n\t\tcase <-time.After(time.Second):\n\t\t\t// Since the long-running task is still running and may access ctx,\n\t\t\t// we must call TimeoutError before returning from requestHandler.\n\t\t\t//\n\t\t\t// Otherwise the program will suffer from data races.\n\t\t\tctx.TimeoutError(\"Timeout!\")\n\t\t}\n\t}\n\n\tif err := fasthttp.ListenAndServe(\":80\", requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n\nfunc ExampleRequestCtx_Logger() {\n\trequestHandler := func(ctx *fasthttp.RequestCtx) {\n\t\tif string(ctx.Path()) == \"/top-secret\" {\n\t\t\tctx.Logger().Printf(\"Alarm! Alien intrusion detected!\")\n\t\t\tctx.Error(\"Access denied!\", fasthttp.StatusForbidden)\n\t\t\treturn\n\t\t}\n\n\t\t// Logger may be cached in local variables.\n\t\tlogger := ctx.Logger()\n\n\t\tlogger.Printf(\"Good request from User-Agent %q\", ctx.Request.Header.UserAgent())\n\t\tfmt.Fprintf(ctx, \"Good request to %q\", ctx.Path())\n\t\tlogger.Printf(\"Multiple log messages may be written during a single request\")\n\t}\n\n\tif err := fasthttp.ListenAndServe(\":80\", requestHandler); err != nil {\n\t\tlog.Fatalf(\"error in ListenAndServe: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "server_race_test.go",
    "content": "//go:build race\n\npackage fasthttp\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nfunc TestServerDoneRace(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tfor i := 0; i < math.MaxInt; i++ {\n\t\t\t\tctx.Done()\n\t\t\t}\n\t\t},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer c.Close()\n\tif _, err = c.Write([]byte(\"POST / HTTP/1.1\\r\\nHost: go.dev\\r\\nContent-Length: 3\\r\\n\\r\\nABC\" +\n\t\t\"\\r\\n\\r\\n\" + // <-- this stuff is bogus, but we'll ignore it\n\t\t\"GET / HTTP/1.1\\r\\nHost: go.dev\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tctx, cancelFunc := context.WithCancel(t.Context())\n\tcancelFunc()\n\n\ts.ShutdownWithContext(ctx)\n}\n"
  },
  {
    "path": "server_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\n// Make sure RequestCtx implements context.Context.\nvar _ context.Context = &RequestCtx{}\n\ntype closerWithRequestCtx struct {\n\tctx       *RequestCtx\n\tcloseFunc func(ctx *RequestCtx) error\n}\n\nfunc (c *closerWithRequestCtx) Close() error {\n\treturn c.closeFunc(c.ctx)\n}\n\nfunc TestServerCRNLAfterPost_Pipeline(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t\tLogger: &testLogger{},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer c.Close()\n\tif _, err = c.Write([]byte(\"POST / HTTP/1.1\\r\\nHost: go.dev\\r\\nContent-Length: 3\\r\\n\\r\\nABC\" +\n\t\t\"\\r\\n\\r\\n\" + // <-- this stuff is bogus, but we'll ignore it\n\t\t\"GET / HTTP/1.1\\r\\nHost: go.dev\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbr := bufio.NewReader(c)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n}\n\nfunc TestServerCRNLAfterPost(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t\tLogger:      &testLogger{},\n\t\tReadTimeout: time.Millisecond * 100,\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\tdefer ln.Close()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer c.Close()\n\tif _, err = c.Write([]byte(\"POST / HTTP/1.1\\r\\nHost: go.dev\\r\\nContent-Length: 3\\r\\n\\r\\nABC\" +\n\t\t\"\\r\\n\\r\\n\", // <-- this stuff is bogus, but we'll ignore it\n\t)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbr := bufio.NewReader(c)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\tif err := resp.Read(br); err == nil {\n\t\tt.Fatal(\"expected error\") // We didn't send a request so we should get an error here.\n\t}\n}\n\nfunc TestServerPipelineFlush(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = c.Write([]byte(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Write a partial request.\n\tif _, err = c.Write([]byte(\"GET /foo1 HTTP/1.1\\r\\nHost: \")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgo func() {\n\t\t// Wait for 200ms to finish the request\n\t\ttime.Sleep(time.Millisecond * 200)\n\n\t\tif _, err = c.Write([]byte(\"google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tstart := time.Now()\n\tbr := bufio.NewReader(c)\n\tvar resp Response\n\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\t// Since the second request takes 200ms to finish we expect the first one to be flushed earlier.\n\td := time.Since(start)\n\tif d >= time.Millisecond*200 {\n\t\tt.Fatalf(\"had to wait for %v\", d)\n\t}\n\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n}\n\nfunc TestServerInvalidHeader(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif ctx.Request.Header.Peek(\"Foő\") != nil || ctx.Request.Header.Peek(\"Foő \") != nil {\n\t\t\t\tt.Error(\"expected Foő header\")\n\t\t\t}\n\t\t},\n\t\tLogger: &testLogger{},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = c.Write([]byte(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nFoő : bar\\r\\nContent-Length: 5\\r\\n\\r\\n12345\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbr := bufio.NewReader(c)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusBadRequest {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusBadRequest)\n\t}\n\n\tc, err = ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = c.Write([]byte(\"GET /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nFoő : bar\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbr = bufio.NewReader(c)\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif resp.StatusCode() != StatusBadRequest {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusBadRequest)\n\t}\n\n\tif err := c.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestServerConnState(t *testing.T) {\n\tt.Parallel()\n\n\tstates := make([]string, 0)\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {},\n\t\tConnState: func(_ net.Conn, state ConnState) {\n\t\t\tstates = append(states, state.String())\n\t\t},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c)\n\t\t// Send 2 requests on the same connection.\n\t\tfor range 2 {\n\t\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tvar resp Response\n\t\t\tif err := resp.Read(br); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\t\t}\n\t\tif err := c.Close(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\t// Give the server a little bit of time to transition the connection to the close state.\n\t\ttime.Sleep(time.Millisecond * 100)\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\t// 2 requests so we go to active and idle twice.\n\texpected := []string{\"new\", \"active\", \"idle\", \"active\", \"idle\", \"closed\"}\n\n\tif !reflect.DeepEqual(expected, states) {\n\t\tt.Fatalf(\"wrong state, expected %q, got %q\", expected, states)\n\t}\n}\n\nfunc TestSaveMultipartFile(t *testing.T) {\n\tt.Parallel()\n\n\tfilea := \"This is a test file.\"\n\tfileb := strings.Repeat(\"test\", 64)\n\n\tmr := multipart.NewReader(strings.NewReader(\"\"+\n\t\t\"--foo\\r\\n\"+\n\t\t\"Content-Disposition: form-data; name=\\\"filea\\\"; filename=\\\"filea.txt\\\"\\r\\n\"+\n\t\t\"Content-Type: text/plain\\r\\n\"+\n\t\t\"\\r\\n\"+\n\t\tfilea+\"\\r\\n\"+\n\t\t\"--foo\\r\\n\"+\n\t\t\"Content-Disposition: form-data; name=\\\"fileb\\\"; filename=\\\"fileb.txt\\\"\\r\\n\"+\n\t\t\"Content-Type: text/plain\\r\\n\"+\n\t\t\"\\r\\n\"+\n\t\tfileb+\"\\r\\n\"+\n\t\t\"--foo--\\r\\n\",\n\t), \"foo\")\n\n\tf, err := mr.ReadForm(64)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := SaveMultipartFile(f.File[\"filea\"][0], \"filea.txt\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(\"filea.txt\")\n\n\tif c, err := os.ReadFile(\"filea.txt\"); err != nil {\n\t\tt.Fatal(err)\n\t} else if string(c) != filea {\n\t\tt.Fatalf(\"filea changed expected %q got %q\", filea, c)\n\t}\n\n\t// Make sure fileb was saved to a file.\n\tif ff, err := f.File[\"fileb\"][0].Open(); err != nil {\n\t\tt.Fatal(\"expected FileHeader.Open to work\")\n\t} else if _, ok := ff.(*os.File); !ok {\n\t\tt.Fatal(\"expected fileb to be an os.File\")\n\t} else {\n\t\tff.Close()\n\t}\n\n\tif err := SaveMultipartFile(f.File[\"fileb\"][0], \"fileb.txt\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(\"fileb.txt\")\n\n\tif c, err := os.ReadFile(\"fileb.txt\"); err != nil {\n\t\tt.Fatal(err)\n\t} else if string(c) != fileb {\n\t\tt.Fatalf(\"fileb changed expected %q got %q\", fileb, c)\n\t}\n}\n\nfunc TestServerName(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\n\tgetResponse := func() []byte {\n\t\trw := &readWriter{}\n\t\trw.r.WriteString(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\t\tif err := s.ServeConn(rw); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\n\t\tresp, err := io.ReadAll(&rw.w)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error from ReadAll: %v\", err)\n\t\t}\n\n\t\treturn resp\n\t}\n\n\tresp := getResponse()\n\tif !bytes.Contains(resp, []byte(\"\\r\\nServer: \"+defaultServerName+\"\\r\\n\")) {\n\t\tt.Fatalf(\"Unexpected response %q expected Server: \"+defaultServerName, resp)\n\t}\n\n\t// We can't just overwrite s.Name as fasthttp caches the name in an atomic.Value\n\ts = &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t\tName: \"foobar\",\n\t}\n\n\tresp = getResponse()\n\tif !bytes.Contains(resp, []byte(\"\\r\\nServer: foobar\\r\\n\")) {\n\t\tt.Fatalf(\"Unexpected response %q expected Server: foobar\", resp)\n\t}\n\n\ts = &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t\tNoDefaultServerHeader: true,\n\t\tNoDefaultContentType:  true,\n\t\tNoDefaultDate:         true,\n\t}\n\n\tresp = getResponse()\n\tif bytes.Contains(resp, []byte(\"\\r\\nServer: \")) {\n\t\tt.Fatalf(\"Unexpected response %q expected no Server header\", resp)\n\t}\n\n\tif bytes.Contains(resp, []byte(\"\\r\\nContent-Type: \")) {\n\t\tt.Fatalf(\"Unexpected response %q expected no Content-Type header\", resp)\n\t}\n\n\tif bytes.Contains(resp, []byte(\"\\r\\nDate: \")) {\n\t\tt.Fatalf(\"Unexpected response %q expected no Date header\", resp)\n\t}\n}\n\nfunc TestRequestCtxString(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\n\ts := ctx.String()\n\texpectedS := \"#0000000000000000 - 0.0.0.0:0<->0.0.0.0:0 - GET http:///\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected ctx.String: %q. Expecting %q\", s, expectedS)\n\t}\n\n\tctx.Request.SetRequestURI(\"https://foobar.com/aaa?bb=c\")\n\ts = ctx.String()\n\texpectedS = \"#0000000000000000 - 0.0.0.0:0<->0.0.0.0:0 - GET https://foobar.com/aaa?bb=c\"\n\tif s != expectedS {\n\t\tt.Fatalf(\"unexpected ctx.String: %q. Expecting %q\", s, expectedS)\n\t}\n}\n\nfunc TestServerErrSmallBuffer(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"shouldn't be never called\") //nolint:errcheck\n\t\t},\n\t\tReadBufferSize: 20,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET / HTTP/1.1\\r\\nHost: aabb.com\\r\\nVERY-long-Header: sdfdfsd dsf dsaf dsf df fsd\\r\\n\\r\\n\")\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(rw)\n\t}()\n\n\tvar serverErr error\n\tselect {\n\tcase serverErr = <-ch:\n\tcase <-time.After(200 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif serverErr == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tstatusCode := resp.StatusCode()\n\tif statusCode != StatusRequestHeaderFieldsTooLarge {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusRequestHeaderFieldsTooLarge)\n\t}\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"missing 'Connection: close' response header\")\n\t}\n\n\texpectedErr := ErrSmallReadBuffer.Error()\n\tif !strings.Contains(serverErr.Error(), expectedErr) {\n\t\tt.Fatalf(\"unexpected log output: %v. Expecting %q\", serverErr, expectedErr)\n\t}\n}\n\nfunc TestRequestCtxIsTLS(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\n\t// tls.Conn\n\tctx.c = &tls.Conn{}\n\tif !ctx.IsTLS() {\n\t\tt.Fatal(\"IsTLS must return true\")\n\t}\n\n\t// non-tls.Conn\n\tctx.c = &readWriter{}\n\tif ctx.IsTLS() {\n\t\tt.Fatal(\"IsTLS must return false\")\n\t}\n\n\t// overridden tls.Conn\n\tctx.c = &struct {\n\t\t*tls.Conn\n\n\t\tfooBar bool\n\t}{}\n\tif !ctx.IsTLS() {\n\t\tt.Fatal(\"IsTLS must return true\")\n\t}\n\n\tctx.c = &perIPConn{Conn: &tls.Conn{}}\n\tif !ctx.IsTLS() {\n\t\tt.Fatal(\"IsTLS must return true\")\n\t}\n}\n\nfunc TestRequestCtxRedirectHTTPSSchemeless(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\n\ts := \"GET /foo/bar?baz HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\"\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := ctx.Request.Read(br); err != nil {\n\t\tt.Fatalf(\"cannot read request: %v\", err)\n\t}\n\tctx.Request.isTLS = true\n\n\tctx.Redirect(\"//foobar.com/aa/bbb\", StatusFound)\n\tlocation := ctx.Response.Header.Peek(HeaderLocation)\n\texpectedLocation := \"https://foobar.com/aa/bbb\"\n\tif string(location) != expectedLocation {\n\t\tt.Fatalf(\"Unexpected location: %q. Expecting %q\", location, expectedLocation)\n\t}\n}\n\nfunc TestRequestCtxRedirect(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestCtxRedirect(t, \"http://qqq/\", \"\", \"http://qqq/\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"\", \"http://qqq/foo/bar?baz=111\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"#aaa\", \"http://qqq/foo/bar?baz=111#aaa\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"?abc=de&f\", \"http://qqq/foo/bar?abc=de&f\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"?abc=de&f#sf\", \"http://qqq/foo/bar?abc=de&f#sf\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"x.html\", \"http://qqq/foo/x.html\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"x.html?a=1\", \"http://qqq/foo/x.html?a=1\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"x.html#aaa=bbb&cc=ddd\", \"http://qqq/foo/x.html#aaa=bbb&cc=ddd\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"x.html?b=1#aaa=bbb&cc=ddd\", \"http://qqq/foo/x.html?b=1#aaa=bbb&cc=ddd\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"/x.html\", \"http://qqq/x.html\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"/x.html#aaa=bbb&cc=ddd\", \"http://qqq/x.html#aaa=bbb&cc=ddd\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"http://foo.bar/baz\", \"http://foo.bar/baz\")\n\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"https://foo.bar/baz\", \"https://foo.bar/baz\")\n\ttestRequestCtxRedirect(t, \"https://foo.com/bar?aaa\", \"//google.com/aaa?bb\", \"https://google.com/aaa?bb\")\n\n\tif runtime.GOOS != \"windows\" {\n\t\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"../x.html\", \"http://qqq/x.html\")\n\t\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"../../x.html\", \"http://qqq/x.html\")\n\t\ttestRequestCtxRedirect(t, \"http://qqq/foo/bar?baz=111\", \"./.././../x.html\", \"http://qqq/x.html\")\n\t}\n}\n\nfunc testRequestCtxRedirect(t *testing.T, origURL, redirectURL, expectedURL string) {\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(origURL)\n\tctx.Init(&req, nil, nil)\n\n\tctx.Redirect(redirectURL, StatusFound)\n\tloc := ctx.Response.Header.Peek(HeaderLocation)\n\tif string(loc) != expectedURL {\n\t\tt.Fatalf(\"unexpected redirect url %q. Expecting %q. origURL=%q, redirectURL=%q\", loc, expectedURL, origURL, redirectURL)\n\t}\n}\n\nfunc TestServerResponseServerHeader(t *testing.T) {\n\tt.Parallel()\n\n\tserverName := \"foobar serv\"\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tname := ctx.Response.Header.Server()\n\t\t\tif string(name) != serverName {\n\t\t\t\tfmt.Fprintf(ctx, \"unexpected server name: %q. Expecting %q\", name, serverName)\n\t\t\t} else {\n\t\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t\t}\n\n\t\t\t// make sure the server name is sent to the client after ctx.Response.Reset()\n\t\t\tctx.NotFound()\n\t\t},\n\t\tName: serverName,\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c)\n\t\tvar resp Response\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif resp.StatusCode() != StatusNotFound {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusNotFound)\n\t\t}\n\t\tif string(resp.Body()) != \"404 Page not found\" {\n\t\t\tt.Errorf(\"unexpected body: %q. Expecting %q\", resp.Body(), \"404 Page not found\")\n\t\t}\n\t\tif string(resp.Header.Server()) != serverName {\n\t\t\tt.Errorf(\"unexpected server header: %q. Expecting %q\", resp.Header.Server(), serverName)\n\t\t}\n\t\tif err = c.Close(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestServerResponseBodyStream(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\treadyCh := make(chan struct{})\n\th := func(ctx *RequestCtx) {\n\t\tctx.SetConnectionClose()\n\t\tif ctx.IsBodyStream() {\n\t\t\tt.Fatal(\"IsBodyStream must return false\")\n\t\t}\n\t\tctx.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\t\tfmt.Fprintf(w, \"first\")\n\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t<-readyCh\n\t\t\tfmt.Fprintf(w, \"second\")\n\t\t\t// there is no need to flush w here, since it will\n\t\t\t// be flushed automatically after returning from StreamWriter.\n\t\t})\n\t\tif !ctx.IsBodyStream() {\n\t\t\tt.Fatal(\"IsBodyStream must return true\")\n\t\t}\n\t}\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := Serve(ln, h); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c)\n\t\tvar respH ResponseHeader\n\t\tif err = respH.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif respH.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", respH.StatusCode(), StatusOK)\n\t\t}\n\n\t\tbuf := make([]byte, 1024)\n\t\tn, err := br.Read(buf)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tb := buf[:n]\n\t\tif string(b) != \"5\\r\\nfirst\\r\\n\" {\n\t\t\tt.Errorf(\"unexpected result %q. Expecting %q\", b, \"5\\r\\nfirst\\r\\n\")\n\t\t}\n\t\tclose(readyCh)\n\n\t\ttail, err := io.ReadAll(br)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif string(tail) != \"6\\r\\nsecond\\r\\n0\\r\\n\\r\\n\" {\n\t\t\tt.Errorf(\"unexpected tail %q. Expecting %q\", tail, \"6\\r\\nsecond\\r\\n0\\r\\n\\r\\n\")\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestServerDisableKeepalive(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t},\n\t\tDisableKeepalive: true,\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = c.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c)\n\t\tvar resp Response\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\t\tif !resp.ConnectionClose() {\n\t\t\tt.Error(\"expecting 'Connection: close' response header\")\n\t\t}\n\t\tif string(resp.Body()) != \"OK\" {\n\t\t\tt.Errorf(\"unexpected body: %q. Expecting %q\", resp.Body(), \"OK\")\n\t\t}\n\n\t\t// make sure the connection is closed\n\t\tdata, err := io.ReadAll(br)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif len(data) > 0 {\n\t\t\tt.Errorf(\"unexpected data read from the connection: %q. Expecting empty data\", data)\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestServerMaxConnsPerIPLimit(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t},\n\t\tMaxConnsPerIP: 1,\n\t\tLogger:        &testLogger{},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tfakeLN := &fakeIPListener{\n\t\t\tListener: ln,\n\t\t}\n\t\tif err := s.Serve(fakeLN); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc1, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tc2, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c2)\n\t\tvar resp Response\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusTooManyRequests {\n\t\t\tt.Errorf(\"unexpected status code for the second connection: %d. Expecting %d\",\n\t\t\t\tresp.StatusCode(), StatusTooManyRequests)\n\t\t}\n\n\t\tif _, err = c1.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error when writing to the first connection: %v\", err)\n\t\t}\n\t\tbr = bufio.NewReader(c1)\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code for the first connection: %d. Expecting %d\",\n\t\t\t\tresp.StatusCode(), StatusOK)\n\t\t}\n\t\tif string(resp.Body()) != \"OK\" {\n\t\t\tt.Errorf(\"unexpected body for the first connection: %q. Expecting %q\", resp.Body(), \"OK\")\n\t\t}\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\ntype fakeIPListener struct {\n\tnet.Listener\n}\n\nfunc (ln *fakeIPListener) Accept() (net.Conn, error) {\n\tconn, err := ln.Listener.Accept()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fakeIPConn{\n\t\tConn: conn,\n\t}, nil\n}\n\ntype fakeIPConn struct {\n\tnet.Conn\n}\n\nfunc (conn *fakeIPConn) RemoteAddr() net.Addr {\n\taddr, err := net.ResolveTCPAddr(\"tcp4\", \"1.2.3.4:5789\")\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"BUG: unexpected error: %v\", err))\n\t}\n\treturn addr\n}\n\nfunc TestServerConcurrencyLimit(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t},\n\t\tConcurrency: 1,\n\t\tLogger:      &testLogger{},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tc1, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tc2, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(c2)\n\t\tvar resp Response\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusServiceUnavailable {\n\t\t\tt.Errorf(\"unexpected status code for the second connection: %d. Expecting %d\",\n\t\t\t\tresp.StatusCode(), StatusServiceUnavailable)\n\t\t}\n\n\t\tif _, err = c1.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aa\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error when writing to the first connection: %v\", err)\n\t\t}\n\t\tbr = bufio.NewReader(c1)\n\t\tif err = resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code for the first connection: %d. Expecting %d\",\n\t\t\t\tresp.StatusCode(), StatusOK)\n\t\t}\n\t\tif string(resp.Body()) != \"OK\" {\n\t\t\tt.Errorf(\"unexpected body for the first connection: %q. Expecting %q\", resp.Body(), \"OK\")\n\t\t}\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestRejectedRequestsCount(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"OK\") //nolint:errcheck\n\t\t},\n\t\tConcurrency: 1,\n\t\tLogger:      &testLogger{},\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tclientCh := make(chan struct{})\n\texpectedCount := 5\n\tgo func() {\n\t\tfor i := 0; i < expectedCount+1; i++ {\n\t\t\t_, err := ln.Dial()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\t// The server's worker pool is a separate goroutine, give it\n\t\t// a little bit of time to process the failed connection,\n\t\t// otherwise the test may fail from time to time.\n\t\ttime.Sleep(time.Millisecond * 10)\n\n\t\tif cnt := s.GetRejectedConnectionsCount(); cnt != uint32(expectedCount) {\n\t\t\tt.Errorf(\"unexpected rejected connections count: %d. Expecting %d\",\n\t\t\t\tcnt, expectedCount)\n\t\t}\n\n\t\tclose(clientCh)\n\t}()\n\n\tselect {\n\tcase <-clientCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestServerWriteFastError(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tName: \"foobar\",\n\t}\n\tvar buf bytes.Buffer\n\texpectedBody := \"access denied\"\n\ts.writeFastError(&buf, StatusForbidden, expectedBody)\n\n\tbr := bufio.NewReader(&buf)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusForbidden {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusForbidden)\n\t}\n\tbody := resp.Body()\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", body, expectedBody)\n\t}\n\tserver := string(resp.Header.Server())\n\tif server != s.Name {\n\t\tt.Fatalf(\"unexpected server: %q. Expecting %q\", server, s.Name)\n\t}\n\tcontentType := string(resp.Header.ContentType())\n\tif contentType != \"text/plain\" {\n\t\tt.Fatalf(\"unexpected content-type: %q. Expecting %q\", contentType, \"text/plain\")\n\t}\n\tif !resp.Header.ConnectionClose() {\n\t\tt.Fatal(\"expecting 'Connection: close' response header\")\n\t}\n}\n\nfunc TestServerTLS(t *testing.T) {\n\tt.Parallel()\n\n\ttext := []byte(\"Make fasthttp great again\")\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Write(text) //nolint:errcheck\n\t\t},\n\t}\n\n\tcertData, keyData, err := GenerateTestCertificate(\"localhost\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = s.AppendCertEmbed(certData, keyData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgo func() {\n\t\terr = s.ServeTLS(ln, \"\", \"\")\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tc := &Client{\n\t\tReadTimeout: time.Second * 2,\n\t\tDial: func(addr string) (net.Conn, error) {\n\t\t\treturn ln.Dial()\n\t\t},\n\t\tTLSConfig: &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t}\n\n\treq, res := AcquireRequest(), AcquireResponse()\n\treq.SetRequestURI(\"https://some.url\")\n\n\terr = c.Do(req, res)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !bytes.Equal(text, res.Body()) {\n\t\tt.Fatal(\"error transmitting information\")\n\t}\n}\n\nfunc TestServerTLSReadTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tReadTimeout: time.Millisecond * 500,\n\t\tLogger:      &testLogger{}, // Ignore log output.\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t},\n\t}\n\n\tcertData, keyData, err := GenerateTestCertificate(\"localhost\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = s.AppendCertEmbed(certData, keyData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgo func() {\n\t\terr = s.ServeTLS(ln, \"\", \"\")\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tr := make(chan error)\n\n\tgo func() {\n\t\tb := make([]byte, 1)\n\t\t_, err := c.Read(b)\n\t\tc.Close()\n\t\tr <- err\n\t}()\n\n\tselect {\n\tcase err = <-r:\n\tcase <-time.After(time.Second * 2):\n\t}\n\n\tif err == nil {\n\t\tt.Error(\"server didn't close connection after timeout\")\n\t}\n}\n\nfunc TestServerServeTLSEmbed(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tcertData, keyData, err := GenerateTestCertificate(\"localhost\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// start the server\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := ServeTLSEmbed(ln, certData, keyData, func(ctx *RequestCtx) {\n\t\t\tif !ctx.IsTLS() {\n\t\t\t\tctx.Error(\"expecting tls\", StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !ctx.URI().isHTTPS() {\n\t\t\t\tctx.Error(fmt.Sprintf(\"unexpected scheme=%q. Expecting %q\", ctx.URI().Scheme(), \"https\"), StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tctx.WriteString(\"success\") //nolint:errcheck\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\t// establish connection to the server\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\ttlsConn := tls.Client(conn, &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t})\n\n\t// send request\n\tif _, err = tlsConn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: aaa\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// read response\n\trespCh := make(chan struct{})\n\tgo func() {\n\t\tbr := bufio.NewReader(tlsConn)\n\t\tvar resp Response\n\t\tif err := resp.Read(br); err != nil {\n\t\t\tt.Error(\"unexpected error\")\n\t\t}\n\t\tbody := resp.Body()\n\t\tif string(body) != \"success\" {\n\t\t\tt.Errorf(\"unexpected response body %q. Expecting %q\", body, \"success\")\n\t\t}\n\t\tclose(respCh)\n\t}()\n\tselect {\n\tcase <-respCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\t// close the server\n\tif err = ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestServerMultipartFormDataRequest(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, test := range []struct {\n\t\tStreamRequestBody            bool\n\t\tDisablePreParseMultipartForm bool\n\t}{\n\t\t{StreamRequestBody: false, DisablePreParseMultipartForm: false},\n\t\t{StreamRequestBody: false, DisablePreParseMultipartForm: true},\n\t\t{StreamRequestBody: true, DisablePreParseMultipartForm: false},\n\t\t{StreamRequestBody: true, DisablePreParseMultipartForm: true},\n\t} {\n\t\tb := strings.ReplaceAll(`------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"f1\"\n\nvalue1\n------WebKitFormBoundaryJwfATyF8tmxSJnLg\nContent-Disposition: form-data; name=\"fileaaa\"; filename=\"TODO\"\nContent-Type: application/octet-stream\n\n- SessionClient with referer and cookies support.\n- Client with requests' pipelining support.\n- ProxyHandler similar to FSHandler.\n- WebSockets. See https://tools.ietf.org/html/rfc6455 .\n- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .\n\n------WebKitFormBoundaryJwfATyF8tmxSJnLg--\n`, \"\\n\", \"\\r\\n\")\n\n\t\treqS := fmt.Sprintf(strings.ReplaceAll(`POST /upload HTTP/1.1\nHost: qwerty.com\nContent-Length: %d\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJwfATyF8tmxSJnLg\n\n%s\n\nGET / HTTP/1.1\nHost: asbd\nConnection: close\n\n`, \"\\n\", \"\\r\\n\"), len(b), b)\n\n\t\tln := fasthttputil.NewInmemoryListener()\n\n\t\ts := &Server{\n\t\t\tStreamRequestBody:            test.StreamRequestBody,\n\t\t\tDisablePreParseMultipartForm: test.DisablePreParseMultipartForm,\n\t\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t\tswitch string(ctx.Path()) {\n\t\t\t\tcase \"/upload\":\n\t\t\t\t\tf, err := ctx.MultipartForm()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif len(f.Value) != 1 {\n\t\t\t\t\t\tt.Errorf(\"unexpected values %d. Expecting %d\", len(f.Value), 1)\n\t\t\t\t\t}\n\t\t\t\t\tif len(f.File) != 1 {\n\t\t\t\t\t\tt.Errorf(\"unexpected file values %d. Expecting %d\", len(f.File), 1)\n\t\t\t\t\t}\n\t\t\t\t\tfv := ctx.FormValue(\"f1\")\n\t\t\t\t\tif string(fv) != \"value1\" {\n\t\t\t\t\t\tt.Errorf(\"unexpected form value: %q. Expecting %q\", fv, \"value1\")\n\t\t\t\t\t}\n\t\t\t\t\tctx.Redirect(\"/\", StatusSeeOther)\n\t\t\t\tdefault:\n\t\t\t\t\tctx.WriteString(\"non-upload\") //nolint:errcheck\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\n\t\tch := make(chan struct{})\n\t\tgo func() {\n\t\t\tif err := s.Serve(ln); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tclose(ch)\n\t\t}()\n\n\t\tconn, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = conn.Write([]byte(reqS)); err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tvar resp Response\n\t\tbr := bufio.NewReader(conn)\n\t\trespCh := make(chan struct{})\n\t\tgo func() {\n\t\t\tif err := resp.Read(br); err != nil {\n\t\t\t\tt.Errorf(\"error when reading response: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode() != StatusSeeOther {\n\t\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusSeeOther)\n\t\t\t}\n\t\t\tloc := resp.Header.Peek(HeaderLocation)\n\t\t\tif string(loc) != \"http://qwerty.com/\" {\n\t\t\t\tt.Errorf(\"unexpected location %q. Expecting %q\", loc, \"http://qwerty.com/\")\n\t\t\t}\n\n\t\t\tif err := resp.Read(br); err != nil {\n\t\t\t\tt.Errorf(\"error when reading the second response: %v\", err)\n\t\t\t}\n\t\t\tif resp.StatusCode() != StatusOK {\n\t\t\t\tt.Errorf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t\t}\n\t\t\tbody := resp.Body()\n\t\t\tif string(body) != \"non-upload\" {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", body, \"non-upload\")\n\t\t\t}\n\t\t\tclose(respCh)\n\t\t}()\n\n\t\tselect {\n\t\tcase <-respCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\n\t\tif err := ln.Close(); err != nil {\n\t\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t\t}\n\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t\t}\n\t}\n}\n\nfunc TestServerGetWithContent(t *testing.T) {\n\tt.Parallel()\n\n\th := func(ctx *RequestCtx) {\n\t\tctx.Success(\"foo/bar\", []byte(\"success\"))\n\t}\n\ts := &Server{\n\t\tHandler: h,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET / HTTP/1.1\\r\\nHost: mm.com\\r\\nContent-Length: 5\\r\\n\\r\\nabcde\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tresp := rw.w.String()\n\tif !strings.HasSuffix(resp, \"success\") {\n\t\tt.Fatalf(\"unexpected response %q.\", resp)\n\t}\n}\n\nfunc TestServerDisableHeaderNamesNormalizing(t *testing.T) {\n\tt.Parallel()\n\n\theaderName := \"CASE-senSITive-HEAder-NAME\"\n\theaderNameLower := strings.ToLower(headerName)\n\theaderValue := \"foobar baz\"\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\thv := ctx.Request.Header.Peek(headerName)\n\t\t\tif string(hv) != headerValue {\n\t\t\t\tt.Errorf(\"unexpected header value for %q: %q. Expecting %q\", headerName, hv, headerValue)\n\t\t\t}\n\t\t\thv = ctx.Request.Header.Peek(headerNameLower)\n\t\t\tif len(hv) > 0 {\n\t\t\t\tt.Errorf(\"unexpected header value for %q: %q. Expecting empty value\", headerNameLower, hv)\n\t\t\t}\n\t\t\tctx.Response.Header.Set(headerName, headerValue)\n\t\t\tctx.WriteString(\"ok\") //nolint:errcheck\n\t\t\tctx.SetContentType(\"aaa\")\n\t\t},\n\t\tDisableHeaderNamesNormalizing: true,\n\t}\n\n\trw := &readWriter{}\n\tfmt.Fprintf(&rw.r, \"GET / HTTP/1.1\\r\\n%s: %s\\r\\nHost: google.com\\r\\n\\r\\n\", headerName, headerValue)\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tresp.Header.DisableNormalizing()\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\thv := resp.Header.Peek(headerName)\n\tif string(hv) != headerValue {\n\t\tt.Fatalf(\"unexpected header value for %q: %q. Expecting %q\", headerName, hv, headerValue)\n\t}\n\thv = resp.Header.Peek(headerNameLower)\n\tif len(hv) > 0 {\n\t\tt.Fatalf(\"unexpected header value for %q: %q. Expecting empty value\", headerNameLower, hv)\n\t}\n}\n\nfunc TestServerReduceMemoryUsageSerial(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler:           func(ctx *RequestCtx) {},\n\t\tReduceMemoryUsage: true,\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\ttestServerRequests(t, ln)\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t}\n}\n\nfunc TestServerReduceMemoryUsageConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tHandler:           func(ctx *RequestCtx) {},\n\t\tReduceMemoryUsage: true,\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tgCh := make(chan struct{})\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestServerRequests(t, ln)\n\t\t\tgCh <- struct{}{}\n\t\t}()\n\t}\n\tfor i := range 10 {\n\t\tselect {\n\t\tcase <-gCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout on goroutine %d\", i)\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t}\n}\n\nfunc testServerRequests(t *testing.T, ln *fasthttputil.InmemoryListener) {\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(conn)\n\tvar resp Response\n\tfor i := range 10 {\n\t\tif _, err = fmt.Fprintf(conn, \"GET / HTTP/1.1\\r\\nHost: aaa\\r\\n\\r\\n\"); err != nil {\n\t\t\tt.Fatalf(\"unexpected error on iteration %d: %v\", i, err)\n\t\t}\n\n\t\trespCh := make(chan struct{})\n\t\tgo func() {\n\t\t\tif err = resp.Read(br); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error when reading response on iteration %d: %v\", i, err)\n\t\t\t}\n\t\t\tclose(respCh)\n\t\t}()\n\t\tselect {\n\t\tcase <-respCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout on iteration %d\", i)\n\t\t}\n\t}\n\n\tif err = conn.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing the connection: %v\", err)\n\t}\n}\n\nfunc TestServerHTTP10ConnectionKeepAlive(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := Serve(ln, func(ctx *RequestCtx) {\n\t\t\tif string(ctx.Path()) == \"/close\" {\n\t\t\t\tctx.SetConnectionClose()\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\t_, err = fmt.Fprintf(conn, \"%s\", \"GET / HTTP/1.0\\r\\nHost: aaa\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n\tif err != nil {\n\t\tt.Fatalf(\"error when writing request: %v\", err)\n\t}\n\t_, err = fmt.Fprintf(conn, \"%s\", \"GET /close HTTP/1.0\\r\\nHost: aaa\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n\tif err != nil {\n\t\tt.Fatalf(\"error when writing request: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(conn)\n\tvar resp Response\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\tif resp.ConnectionClose() {\n\t\tt.Fatal(\"response mustn't have 'Connection: close' header\")\n\t}\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"response must have 'Connection: close' header\")\n\t}\n\n\ttailCh := make(chan struct{})\n\tgo func() {\n\t\ttail, err := io.ReadAll(br)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error when reading tail: %v\", err)\n\t\t}\n\t\tif len(tail) > 0 {\n\t\t\tt.Errorf(\"unexpected non-zero tail %q\", tail)\n\t\t}\n\t\tclose(tailCh)\n\t}()\n\n\tselect {\n\tcase <-tailCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when reading tail\")\n\t}\n\n\tif err = conn.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing the connection: %v\", err)\n\t}\n\n\tif err = ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t}\n}\n\nfunc TestServerHTTP10ConnectionClose(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := Serve(ln, func(ctx *RequestCtx) {\n\t\t\t// The server must close the connection irregardless\n\t\t\t// of request and response state set inside request\n\t\t\t// handler, since the HTTP/1.0 request\n\t\t\t// had no 'Connection: keep-alive' header.\n\t\t\tctx.Request.Header.ResetConnectionClose()\n\t\t\tctx.Request.Header.Set(HeaderConnection, \"keep-alive\")\n\t\t\tctx.Response.Header.ResetConnectionClose()\n\t\t\tctx.Response.Header.Set(HeaderConnection, \"keep-alive\")\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\t_, err = fmt.Fprintf(conn, \"%s\", \"GET / HTTP/1.0\\r\\nHost: aaa\\r\\n\\r\\n\")\n\tif err != nil {\n\t\tt.Fatalf(\"error when writing request: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(conn)\n\tvar resp Response\n\tif err = resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"HTTP1.0 response must have 'Connection: close' header\")\n\t}\n\n\ttailCh := make(chan struct{})\n\tgo func() {\n\t\ttail, err := io.ReadAll(br)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error when reading tail: %v\", err)\n\t\t}\n\t\tif len(tail) > 0 {\n\t\t\tt.Errorf(\"unexpected non-zero tail %q\", tail)\n\t\t}\n\t\tclose(tailCh)\n\t}()\n\n\tselect {\n\tcase <-tailCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when reading tail\")\n\t}\n\n\tif err = conn.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing the connection: %v\", err)\n\t}\n\n\tif err = ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t}\n}\n\nfunc TestRequestCtxFormValue(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"/foo/bar?baz=123&aaa=bbb\")\n\treq.SetBodyString(\"qqq=port&mmm=sddd\")\n\treq.Header.SetContentType(\"application/x-www-form-urlencoded\")\n\n\tctx.Init(&req, nil, nil)\n\n\tv := ctx.FormValue(\"baz\")\n\tif string(v) != \"123\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"123\")\n\t}\n\tv = ctx.FormValue(\"mmm\")\n\tif string(v) != \"sddd\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"sddd\")\n\t}\n\tv = ctx.FormValue(\"aaaasdfsdf\")\n\tif len(v) > 0 {\n\t\tt.Fatalf(\"unexpected value for unknown key %q\", v)\n\t}\n}\n\nfunc TestSetStandardFormValueFunc(t *testing.T) {\n\tt.Parallel()\n\tvar ctx RequestCtx\n\tvar req Request\n\treq.SetRequestURI(\"/foo/bar?aaa=bbb\")\n\treq.SetBodyString(\"aaa=port\")\n\treq.Header.SetContentType(\"application/x-www-form-urlencoded\")\n\tctx.Init(&req, nil, nil)\n\tctx.formValueFunc = NetHttpFormValueFunc\n\tv := ctx.FormValue(\"aaa\")\n\tif string(v) != \"port\" {\n\t\tt.Fatalf(\"unexpected value %q. Expecting %q\", v, \"port\")\n\t}\n}\n\nfunc TestRequestCtxUserValue(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\n\tfor i := range 5 {\n\t\tk := fmt.Sprintf(\"key-%d\", i)\n\t\tctx.SetUserValue(k, i)\n\t}\n\tfor i := 5; i < 10; i++ {\n\t\tk := fmt.Sprintf(\"key-%d\", i)\n\t\tctx.SetUserValueBytes([]byte(k), i)\n\t}\n\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key-%d\", i)\n\t\tv := ctx.UserValue(k)\n\t\tn, ok := v.(int)\n\t\tif !ok || n != i {\n\t\t\tt.Fatalf(\"unexpected value obtained for key %q: %v. Expecting %d\", k, v, i)\n\t\t}\n\t}\n\tvlen := 0\n\tctx.VisitUserValues(func(key []byte, value any) {\n\t\tvlen++\n\t\tv := ctx.UserValue(key)\n\t\tif v != value {\n\t\t\tt.Fatalf(\"unexpected value obtained from VisitUserValues for key: %q, expecting: %#v but got: %#v\", key, v, value)\n\t\t}\n\t})\n\tif len(ctx.Request.userValues) != vlen {\n\t\tt.Fatalf(\"the length of user values returned from VisitUserValues is not equal to the length of the userValues, expecting: %d but got: %d\", len(ctx.Request.userValues), vlen)\n\t}\n\n\tctx.ResetUserValues()\n\tfor i := range 10 {\n\t\tk := fmt.Sprintf(\"key-%d\", i)\n\t\tv := ctx.UserValue(k)\n\t\tif v != nil {\n\t\t\tt.Fatalf(\"unexpected value obtained for key %q: %v. Expecting nil\", k, v)\n\t\t}\n\t}\n}\n\nfunc TestServerHeadRequest(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tfmt.Fprintf(ctx, \"Request method is %q\", ctx.Method())\n\t\t\tctx.SetContentType(\"aaa/bbb\")\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"HEAD /foobar HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tresp.SkipBody = true\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\tif resp.Header.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.Header.StatusCode(), StatusOK)\n\t}\n\tif len(resp.Body()) > 0 {\n\t\tt.Fatalf(\"Unexpected non-zero body %q\", resp.Body())\n\t}\n\tif resp.Header.ContentLength() != 24 {\n\t\tt.Fatalf(\"unexpected content-length %d. Expecting %d\", resp.Header.ContentLength(), 24)\n\t}\n\tif string(resp.Header.ContentType()) != \"aaa/bbb\" {\n\t\tt.Fatalf(\"unexpected content-type %q. Expecting %q\", resp.Header.ContentType(), \"aaa/bbb\")\n\t}\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) > 0 {\n\t\tt.Fatalf(\"unexpected remaining data %q\", data)\n\t}\n}\n\nfunc TestServerRejectsBackslashInAbsoluteURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar handlerCalled atomic.Bool\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\thandlerCalled.Store(true)\n\t\t\tctx.Success(\"text/plain\", []byte(\"ok\"))\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET http://vulndetector.com\\\\\\\\admin\\\\\\\\api HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n\n\t_ = s.ServeConn(rw)\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, StatusBadRequest, string(defaultContentType), \"Error when parsing request\")\n\n\tif handlerCalled.Load() {\n\t\tt.Fatal(\"handler should not run for invalid absolute URI\")\n\t}\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) > 0 {\n\t\tt.Fatalf(\"unexpected remaining data %q\", data)\n\t}\n}\n\nfunc TestServerExpect100Continue(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif !ctx.IsPost() {\n\t\t\t\tt.Errorf(\"unexpected method %q. Expecting POST\", ctx.Method())\n\t\t\t}\n\t\t\tif string(ctx.Path()) != \"/foo\" {\n\t\t\t\tt.Errorf(\"unexpected path %q. Expecting %q\", ctx.Path(), \"/foo\")\n\t\t\t}\n\t\t\tct := ctx.Request.Header.ContentType()\n\t\t\tif string(ct) != \"a/b\" {\n\t\t\t\tt.Errorf(\"unexpected content-type: %q. Expecting %q\", ct, \"a/b\")\n\t\t\t}\n\t\t\tif string(ctx.PostBody()) != \"12345\" {\n\t\t\t\tt.Errorf(\"unexpected body: %q. Expecting %q\", ctx.PostBody(), \"12345\")\n\t\t\t}\n\t\t\tctx.WriteString(\"foobar\") //nolint:errcheck\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, StatusOK, string(defaultContentType), \"foobar\")\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) > 0 {\n\t\tt.Fatalf(\"unexpected remaining data %q\", data)\n\t}\n}\n\nfunc TestServerExpect103EarlyHints(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tNoDefaultContentType:  true,\n\t\tNoDefaultDate:         true,\n\t\tNoDefaultServerHeader: true,\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Response.Header.Add(\"Link\", \"<https://cdn.com>; rel=preload; as=script\")\n\t\t\tctx.EarlyHints() //nolint:errcheck\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tscanner := bufio.NewScanner(&rw.w)\n\texpected := []string{\n\t\t\"HTTP/1.1 103 Early Hints\",\n\t\t\"Link: <https://cdn.com>; rel=preload; as=script\",\n\t\t\"\",\n\t\t\"HTTP/1.1 200 OK\",\n\t\t\"Content-Length: 0\",\n\t\t\"Link: <https://cdn.com>; rel=preload; as=script\",\n\t}\n\n\ti := 0\n\tfor scanner.Scan() {\n\t\tif i >= len(expected) {\n\t\t\tbreak\n\t\t}\n\t\tline := scanner.Text()\n\t\tif line != expected[i] {\n\t\t\tt.Fatalf(\"unexpected data: %s. Expecting %s\", line, expected[i])\n\t\t}\n\t\ti++\n\t}\n}\n\nfunc TestServerContinueHandler(t *testing.T) {\n\tt.Parallel()\n\n\tacceptContentLength := 5\n\ts := &Server{\n\t\tContinueHandler: func(headers *RequestHeader) bool {\n\t\t\tif !headers.IsPost() {\n\t\t\t\tt.Errorf(\"unexpected method %q. Expecting POST\", headers.Method())\n\t\t\t}\n\n\t\t\tct := headers.ContentType()\n\t\t\tif string(ct) != \"a/b\" {\n\t\t\t\tt.Errorf(\"unexpected content-type: %q. Expecting %q\", ct, \"a/b\")\n\t\t\t}\n\n\t\t\t// Pass on any request that isn't the accepted content length\n\t\t\treturn headers.contentLength == acceptContentLength\n\t\t},\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif ctx.Request.Header.contentLength != acceptContentLength {\n\t\t\t\tt.Errorf(\"all requests with content-length: other than %d, should be denied\", acceptContentLength)\n\t\t\t}\n\t\t\tif !ctx.IsPost() {\n\t\t\t\tt.Errorf(\"unexpected method %q. Expecting POST\", ctx.Method())\n\t\t\t}\n\t\t\tif string(ctx.Path()) != \"/foo\" {\n\t\t\t\tt.Errorf(\"unexpected path %q. Expecting %q\", ctx.Path(), \"/foo\")\n\t\t\t}\n\t\t\tct := ctx.Request.Header.ContentType()\n\t\t\tif string(ct) != \"a/b\" {\n\t\t\t\tt.Errorf(\"unexpected content-type: %q. Expecting %q\", ct, \"a/b\")\n\t\t\t}\n\t\t\tif string(ctx.PostBody()) != \"12345\" {\n\t\t\t\tt.Errorf(\"unexpected body: %q. Expecting %q\", ctx.PostBody(), \"12345\")\n\t\t\t}\n\t\t\tctx.WriteString(\"foobar\") //nolint:errcheck\n\t\t},\n\t}\n\n\tsendRequest := func(rw *readWriter, expectedStatusCode int, expectedResponse string) {\n\t\tif err := s.ServeConn(rw); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\n\t\tbr := bufio.NewReader(&rw.w)\n\t\tverifyResponse(t, br, expectedStatusCode, string(defaultContentType), expectedResponse)\n\n\t\tdata, err := io.ReadAll(br)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t\t}\n\t\tif len(data) > 0 {\n\t\t\tt.Fatalf(\"unexpected remaining data %q\", data)\n\t\t}\n\t}\n\n\t// The same server should not fail when handling the three different types of requests\n\t// Regular requests\n\t// Expect 100 continue accepted\n\t// Expect 100 continue denied\n\trw := &readWriter{}\n\tfor range 25 {\n\t\t// Regular requests without Expect 100 continue header\n\t\trw.r.Reset()\n\t\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\t\tsendRequest(rw, StatusOK, \"foobar\")\n\n\t\t// Regular Expect 100 continue requests that are accepted\n\t\trw.r.Reset()\n\t\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nExpect: 100-continue\\r\\nContent-Length: 5\\r\\nContent-Type: a/b\\r\\n\\r\\n12345\")\n\t\tsendRequest(rw, StatusOK, \"foobar\")\n\n\t\t// Requests being denied\n\t\trw.r.Reset()\n\t\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: gle.com\\r\\nExpect: 100-continue\\r\\nContent-Length: 6\\r\\nContent-Type: a/b\\r\\n\\r\\n123456\")\n\t\tsendRequest(rw, StatusExpectationFailed, \"\")\n\t}\n}\n\nfunc TestCompressHandler(t *testing.T) {\n\tt.Parallel()\n\n\texpectedBody := string(createFixedBody(2e4))\n\th := CompressHandler(func(ctx *RequestCtx) {\n\t\tctx.WriteString(expectedBody) //nolint:errcheck\n\t})\n\n\tvar ctx RequestCtx\n\tvar resp Response\n\n\t// verify uncompressed response\n\th(&ctx)\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) != 0 {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"\")\n\t}\n\tbody := resp.Body()\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify gzip-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"gzip\")\n\t}\n\tbody, err := resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// an attempt to compress already compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc\")\n\thh := CompressHandler(h)\n\thh(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"gzip\")\n\t}\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify deflate-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"foobar, deflate, sdhc\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"deflate\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"deflate\")\n\t}\n\tbody, err = resp.BodyInflate()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n}\n\nfunc TestCompressHandlerVary(t *testing.T) {\n\tt.Parallel()\n\n\texpectedBody := string(createFixedBody(2e4))\n\n\th := CompressHandlerBrotliLevel(func(ctx *RequestCtx) {\n\t\tctx.WriteString(expectedBody) //nolint:errcheck\n\t}, CompressBrotliBestSpeed, CompressBestSpeed)\n\n\tvar ctx RequestCtx\n\tvar resp Response\n\n\t// verify uncompressed response\n\th(&ctx)\n\ts := ctx.Response.String()\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce := resp.Header.ContentEncoding()\n\tif len(ce) != 0 {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"\")\n\t}\n\tvary := resp.Header.Peek(\"Vary\")\n\tif len(vary) != 0 {\n\t\tt.Fatalf(\"unexpected Vary: %q. Expecting %q\", vary, \"\")\n\t}\n\tbody := resp.Body()\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify gzip-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"gzip\")\n\t}\n\tvary = resp.Header.Peek(\"Vary\")\n\tif string(vary) != \"Accept-Encoding\" {\n\t\tt.Fatalf(\"unexpected Vary: %q. Expecting %q\", vary, \"Accept-Encoding\")\n\t}\n\tbody, err := resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// an attempt to compress already compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(\"Accept-Encoding\", \"gzip, deflate, sdhc\")\n\thh := CompressHandler(h)\n\thh(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"gzip\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"gzip\")\n\t}\n\tvary = resp.Header.Peek(\"Vary\")\n\tif string(vary) != \"Accept-Encoding\" {\n\t\tt.Fatalf(\"unexpected Vary: %q. Expecting %q\", vary, \"Accept-Encoding\")\n\t}\n\tbody, err = resp.BodyGunzip()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify deflate-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"foobar, deflate, sdhc\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"deflate\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"deflate\")\n\t}\n\tvary = resp.Header.Peek(\"Vary\")\n\tif string(vary) != \"Accept-Encoding\" {\n\t\tt.Fatalf(\"unexpected Vary: %q. Expecting %q\", vary, \"Accept-Encoding\")\n\t}\n\tbody, err = resp.BodyInflate()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n\n\t// verify br-compressed response\n\tctx.Request.Reset()\n\tctx.Response.Reset()\n\tctx.Request.Header.Set(HeaderAcceptEncoding, \"gzip, deflate, br\")\n\n\th(&ctx)\n\ts = ctx.Response.String()\n\tbr = bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tce = resp.Header.ContentEncoding()\n\tif string(ce) != \"br\" {\n\t\tt.Fatalf(\"unexpected Content-Encoding: %q. Expecting %q\", ce, \"br\")\n\t}\n\tvary = resp.Header.Peek(\"Vary\")\n\tif string(vary) != \"Accept-Encoding\" {\n\t\tt.Fatalf(\"unexpected Vary: %q. Expecting %q\", vary, \"Accept-Encoding\")\n\t}\n\tbody, err = resp.BodyUnbrotli()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif string(body) != expectedBody {\n\t\tt.Fatalf(\"unexpected body %q. Expecting %q\", body, expectedBody)\n\t}\n}\n\nfunc TestRequestCtxWriteString(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tn, err := ctx.WriteString(\"foo\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != 3 {\n\t\tt.Fatalf(\"unexpected n %d. Expecting 3\", n)\n\t}\n\tn, err = ctx.WriteString(\"привет\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != 12 {\n\t\tt.Fatalf(\"unexpected n=%d. Expecting 12\", n)\n\t}\n\n\ts := ctx.Response.Body()\n\tif string(s) != \"fooпривет\" {\n\t\tt.Fatalf(\"unexpected response body %q. Expecting %q\", s, \"fooпривет\")\n\t}\n}\n\nfunc TestServeConnKeepRequestAndResponseUntilResetUserValues(t *testing.T) {\n\tt.Parallel()\n\n\treqStr := \"POST /foo HTTP/1.0\\r\\nHost: google.com\\r\\nContent-Type: application/octet-stream\\r\\nContent-Length: 0\\r\\nConnection: keep-alive\\r\\n\\r\\n\"\n\trespRegex := regexp.MustCompile(\"HTTP/1.1 308 Permanent Redirect\\r\\nServer: fasthttp\\r\\nDate: (.*)\\r\\nContent-Length: 0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n\n\trw := &readWriter{}\n\trw.r.WriteString(reqStr)\n\n\tvar resultReqStr, resultRespStr string\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := ServeConn(rw, func(ctx *RequestCtx) {\n\t\t\tctx.Response.SetStatusCode(StatusPermanentRedirect)\n\n\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\n\t\t\t\tctx: ctx,\n\t\t\t\tcloseFunc: func(closerCtx *RequestCtx) error {\n\t\t\t\t\tresultReqStr = closerCtx.Request.String()\n\t\t\t\t\tresultRespStr = closerCtx.Response.String()\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error in ServeConn: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif resultReqStr != reqStr {\n\t\tt.Errorf(\"Request == %q, want %q\", resultReqStr, reqStr)\n\t}\n\n\tif !respRegex.MatchString(resultRespStr) {\n\t\tt.Errorf(\"Response == %q, want regex %q\", resultRespStr, respRegex)\n\t}\n}\n\n// TestServerErrorHandler tests unexpected cases the for loop will break\n// before request/response reset call. in such cases, call it before\n// release to fix #548.\nfunc TestServerErrorHandler(t *testing.T) {\n\tt.Parallel()\n\n\tvar resultReqStr, resultRespStr string\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {},\n\t\tErrorHandler: func(ctx *RequestCtx, _ error) {\n\t\t\tresultReqStr = ctx.Request.String()\n\t\t\tresultRespStr = ctx.Response.String()\n\t\t},\n\t\tMaxRequestBodySize: 10,\n\t}\n\n\treqStrTpl := \"POST %s HTTP/1.1\\r\\nHost: example.com\\r\\nContent-Type: application/octet-stream\\r\\nContent-Length: %d\\r\\nConnection: keep-alive\\r\\n\\r\\n\"\n\trespRegex := regexp.MustCompile(\"HTTP/1.1 200 OK\\r\\nDate: (.*)\\r\\nContent-Length: 0\\r\\n\\r\\n\")\n\n\trw := &readWriter{}\n\n\tfor i := range 100 {\n\t\tbody := strings.Repeat(\"@\", s.MaxRequestBodySize+1)\n\t\tpath := fmt.Sprintf(\"/%d\", i)\n\n\t\treqStr := fmt.Sprintf(reqStrTpl, path, len(body))\n\t\texpectedReqStr := fmt.Sprintf(reqStrTpl, path, 0)\n\n\t\trw.r.WriteString(reqStr)\n\t\trw.r.WriteString(body)\n\n\t\tch := make(chan struct{})\n\t\tgo func() {\n\t\t\terr := s.ServeConn(rw)\n\t\t\tif err != nil && !errors.Is(err, ErrBodyTooLarge) {\n\t\t\t\tt.Errorf(\"unexpected error in ServeConn: %v\", err)\n\t\t\t}\n\t\t\tclose(ch)\n\t\t}()\n\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\n\t\tif resultReqStr != expectedReqStr {\n\t\t\tt.Errorf(\"[iter: %d] Request == %q, want %s\", i, resultReqStr, reqStr)\n\t\t}\n\n\t\tif !respRegex.MatchString(resultRespStr) {\n\t\t\tt.Errorf(\"[iter: %d] Response == %q, want regex %q\", i, resultRespStr, respRegex)\n\t\t}\n\t}\n}\n\nfunc TestServeConnHijackResetUserValues(t *testing.T) {\n\tt.Parallel()\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.0\\r\\nConnection: keep-alive\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"\")\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := ServeConn(rw, func(ctx *RequestCtx) {\n\t\t\tctx.Hijack(func(c net.Conn) {})\n\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\n\t\t\t\tcloseFunc: func(_ *RequestCtx) error {\n\t\t\t\t\tclose(ch)\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t)\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error in ServeConn: %v\", err)\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Errorf(\"Timeout: UserValues should be reset\")\n\t}\n}\n\nfunc TestServeConnNonHTTP11KeepAlive(t *testing.T) {\n\tt.Parallel()\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.0\\r\\nConnection: keep-alive\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /bar HTTP/1.0\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /must/be/ignored HTTP/1.0\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\trequestsServed := 0\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\terr := ServeConn(rw, func(ctx *RequestCtx) {\n\t\t\trequestsServed++\n\t\t\tctx.SuccessString(\"aaa/bbb\", \"foobar\")\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error in ServeConn: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\n\tvar resp Response\n\n\t// verify the first response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\tif string(resp.Header.Peek(HeaderConnection)) != \"keep-alive\" {\n\t\tt.Fatalf(\"unexpected Connection header %q. Expecting %q\", resp.Header.Peek(HeaderConnection), \"keep-alive\")\n\t}\n\tif resp.Header.ConnectionClose() {\n\t\tt.Fatal(\"unexpected Connection: close\")\n\t}\n\n\t// verify the second response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\tif string(resp.Header.Peek(HeaderConnection)) != \"close\" {\n\t\tt.Fatalf(\"unexpected Connection header %q. Expecting %q\", resp.Header.Peek(HeaderConnection), \"close\")\n\t}\n\tif !resp.Header.ConnectionClose() {\n\t\tt.Fatal(\"expecting Connection: close\")\n\t}\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after responses %q\", data)\n\t}\n\n\tif requestsServed != 2 {\n\t\tt.Fatalf(\"unexpected number of requests served: %d. Expecting 2\", requestsServed)\n\t}\n}\n\nfunc TestRequestCtxSetBodyStreamWriter(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\n\tif ctx.IsBodyStream() {\n\t\tt.Fatal(\"IsBodyStream must return false\")\n\t}\n\tctx.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\tfmt.Fprintf(w, \"body writer line 1\\n\")\n\t\tif err := w.Flush(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tfmt.Fprintf(w, \"body writer line 2\\n\")\n\t})\n\tif !ctx.IsBodyStream() {\n\t\tt.Fatal(\"IsBodyStream must return true\")\n\t}\n\n\ts := ctx.Response.String()\n\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Error when reading response: %v\", err)\n\t}\n\n\tbody := string(resp.Body())\n\texpectedBody := \"body writer line 1\\nbody writer line 2\\n\"\n\tif body != expectedBody {\n\t\tt.Fatalf(\"unexpected body: %q. Expecting %q\", body, expectedBody)\n\t}\n}\n\nfunc TestRequestCtxIfModifiedSince(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\n\tlastModified := time.Now().Add(-time.Hour)\n\n\tif !ctx.IfModifiedSince(lastModified) {\n\t\tt.Fatal(\"IfModifiedSince must return true for non-existing If-Modified-Since header\")\n\t}\n\n\tctx.Request.Header.Set(\"If-Modified-Since\", string(AppendHTTPDate(nil, lastModified)))\n\n\tif ctx.IfModifiedSince(lastModified) {\n\t\tt.Fatal(\"If-Modified-Since current time must return false\")\n\t}\n\n\tpast := lastModified.Add(-time.Hour)\n\tif ctx.IfModifiedSince(past) {\n\t\tt.Fatal(\"If-Modified-Since past time must return false\")\n\t}\n\n\tfuture := lastModified.Add(time.Hour)\n\tif !ctx.IfModifiedSince(future) {\n\t\tt.Fatal(\"If-Modified-Since future time must return true\")\n\t}\n}\n\nfunc TestRequestCtxSendFileNotModified(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\n\tfilePath := \"./server_test.go\"\n\tlastModified, err := FileLastModified(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tctx.Request.Header.Set(\"If-Modified-Since\", string(AppendHTTPDate(nil, lastModified)))\n\n\tctx.SendFile(filePath)\n\n\ts := ctx.Response.String()\n\n\tvar resp Response\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusNotModified {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusNotModified)\n\t}\n\tif len(resp.Body()) > 0 {\n\t\tt.Fatalf(\"unexpected non-zero response body: %q\", resp.Body())\n\t}\n}\n\nfunc TestRequestCtxSendFileModified(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\n\tfilePath := \"./server_test.go\"\n\tlastModified, err := FileLastModified(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tlastModified = lastModified.Add(-time.Hour)\n\tctx.Request.Header.Set(\"If-Modified-Since\", string(AppendHTTPDate(nil, lastModified)))\n\n\tctx.SendFile(filePath)\n\n\ts := ctx.Response.String()\n\n\tvar resp Response\n\tbr := bufio.NewReader(bytes.NewBufferString(s))\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot open file: %v\", err)\n\t}\n\tbody, err := io.ReadAll(f)\n\tf.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"error when reading file: %v\", err)\n\t}\n\n\tif !bytes.Equal(resp.Body(), body) {\n\t\tt.Fatalf(\"unexpected response body: %q. Expecting %q\", resp.Body(), body)\n\t}\n}\n\nfunc TestRequestCtxSendFile(t *testing.T) {\n\tt.Parallel()\n\n\tvar ctx RequestCtx\n\tvar req Request\n\tctx.Init(&req, nil, defaultLogger)\n\n\tfilePath := \"./server_test.go\"\n\tctx.SendFile(filePath)\n\n\tw := &bytes.Buffer{}\n\tbw := bufio.NewWriter(w)\n\tif err := ctx.Response.Write(bw); err != nil {\n\t\tt.Fatalf(\"error when writing response: %v\", err)\n\t}\n\tif err := bw.Flush(); err != nil {\n\t\tt.Fatalf(\"error when flushing response: %v\", err)\n\t}\n\n\tvar resp Response\n\tbr := bufio.NewReader(w)\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"error when reading response: %v\", err)\n\t}\n\tif resp.StatusCode() != StatusOK {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t}\n\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot open file: %v\", err)\n\t}\n\tbody, err := io.ReadAll(f)\n\tf.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"error when reading file: %v\", err)\n\t}\n\n\tif !bytes.Equal(resp.Body(), body) {\n\t\tt.Fatalf(\"unexpected response body: %q. Expecting %q\", resp.Body(), body)\n\t}\n}\n\nfunc testRequestCtxHijack(t *testing.T, s *Server) {\n\tt.Helper()\n\n\ttype hijackSignal struct {\n\t\trw *readWriter\n\t\tid int\n\t}\n\n\twg := sync.WaitGroup{}\n\ttotalConns := 100\n\thijackStartCh := make(chan *hijackSignal, totalConns)\n\thijackStopCh := make(chan *hijackSignal, totalConns)\n\n\ts.Handler = func(ctx *RequestCtx) {\n\t\tif ctx.Hijacked() {\n\t\t\tt.Error(\"connection mustn't be hijacked\")\n\t\t}\n\n\t\tctx.Hijack(func(c net.Conn) {\n\t\t\tsignal := <-hijackStartCh\n\t\t\tdefer func() {\n\t\t\t\thijackStopCh <- signal\n\t\t\t\twg.Done()\n\t\t\t}()\n\n\t\t\tb := make([]byte, 1)\n\t\t\tstop := false\n\n\t\t\t// ping-pong echo via hijacked conn\n\t\t\tfor !stop {\n\t\t\t\tn, err := c.Read(b)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\t\tstop = true\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tt.Errorf(\"unexpected read error: %v\", err)\n\t\t\t\t} else if n != 1 {\n\t\t\t\t\tt.Errorf(\"unexpected number of bytes read: %d. Expecting 1\", n)\n\t\t\t\t}\n\n\t\t\t\tif _, err = c.Write(b); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error when writing data: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tif !ctx.Hijacked() {\n\t\t\tt.Error(\"connection must be hijacked\")\n\t\t}\n\n\t\tctx.Success(\"foo/bar\", []byte(\"hijack it!\"))\n\t}\n\n\thijackedString := \"foobar baz hijacked!!!\"\n\n\tfor i := range totalConns {\n\t\twg.Add(1)\n\n\t\tgo func(t *testing.T, id int) {\n\t\t\tt.Helper()\n\n\t\t\trw := new(readWriter)\n\t\t\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\t\t\trw.r.WriteString(hijackedString)\n\n\t\t\tif err := s.ServeConn(rw); err != nil {\n\t\t\t\tt.Errorf(\"[iter: %d] Unexpected error from serveConn: %v\", id, err)\n\t\t\t}\n\n\t\t\thijackStartCh <- &hijackSignal{id: id, rw: rw}\n\t\t}(t, i)\n\t}\n\n\twg.Wait()\n\n\tcount := 0\n\tfor count != totalConns {\n\t\tselect {\n\t\tcase signal := <-hijackStopCh:\n\t\t\tcount++\n\n\t\t\tid := signal.id\n\t\t\trw := signal.rw\n\n\t\t\tbr := bufio.NewReader(&rw.w)\n\t\t\tverifyResponse(t, br, StatusOK, \"foo/bar\", \"hijack it!\")\n\n\t\t\tdata, err := io.ReadAll(br)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"[iter: %d] Unexpected error when reading remaining data: %v\", id, err)\n\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif string(data) != hijackedString {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"[iter: %d] Unexpected response %q. Expecting %q\",\n\t\t\t\t\tid, data, hijackedString,\n\t\t\t\t)\n\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\tt.Errorf(\"timeout\")\n\t\t}\n\t}\n\n\tclose(hijackStartCh)\n\tclose(hijackStopCh)\n}\n\nfunc TestRequestCtxHijack(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestCtxHijack(t, &Server{})\n}\n\nfunc TestRequestCtxHijackReduceMemoryUsage(t *testing.T) {\n\tt.Parallel()\n\n\ttestRequestCtxHijack(t, &Server{\n\t\tReduceMemoryUsage: true,\n\t})\n}\n\nfunc TestRequestCtxHijackNoResponse(t *testing.T) {\n\tt.Parallel()\n\n\thijackDone := make(chan error)\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Hijack(func(c net.Conn) {\n\t\t\t\t_, err := c.Write([]byte(\"test\"))\n\t\t\t\thijackDone <- err\n\t\t\t})\n\t\t\tctx.HijackSetNoResponse(true)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\nContent-Length: 0\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tselect {\n\tcase err := <-hijackDone:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error from hijack: %v\", err)\n\t\t}\n\tcase <-time.After(100 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif got := rw.w.String(); got != \"test\" {\n\t\tt.Errorf(`expected \"test\", got %q`, got)\n\t}\n}\n\nfunc TestRequestCtxNoHijackNoResponse(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.WriteString(\"test\") //nolint:errcheck\n\t\t\tctx.HijackSetNoResponse(true)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\nContent-Length: 0\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbf := bufio.NewReader(\n\t\tstrings.NewReader(rw.w.String()),\n\t)\n\tresp := AcquireResponse()\n\tresp.Read(bf) //nolint:errcheck\n\tif got := string(resp.Body()); got != \"test\" {\n\t\tt.Errorf(`expected \"test\", got %q`, got)\n\t}\n}\n\nfunc TestRequestCtxInit(t *testing.T) {\n\t// This test can't run parallel as it modifies globalConnID.\n\n\tvar ctx RequestCtx\n\tvar logger testLogger\n\tglobalConnID = 0x123456\n\tctx.Init(&ctx.Request, zeroTCPAddr, &logger)\n\tip := ctx.RemoteIP()\n\tif !ip.IsUnspecified() {\n\t\tt.Fatalf(\"unexpected ip for bare RequestCtx: %q. Expected 0.0.0.0\", ip)\n\t}\n\tctx.Logger().Printf(\"foo bar %d\", 10)\n\n\texpectedLog := \"#0012345700000000 - 0.0.0.0:0<->0.0.0.0:0 - GET http:/// - foo bar 10\\n\"\n\tif logger.out != expectedLog {\n\t\tt.Fatalf(\"Unexpected log output: %q. Expected %q\", logger.out, expectedLog)\n\t}\n}\n\nfunc TestTimeoutHandlerSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\th := func(ctx *RequestCtx) {\n\t\tif string(ctx.Path()) == \"/\" {\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t}\n\t}\n\ts := &Server{\n\t\tHandler: TimeoutHandler(h, 10*time.Second, \"timeout!!!\"),\n\t}\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tconcurrency := 20\n\tclientCh := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tconn, err := ln.Dial()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tbr := bufio.NewReader(conn)\n\t\t\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\t\t\tclientCh <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-clientCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestTimeoutHandlerTimeout(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\treadyCh := make(chan struct{})\n\tdoneCh := make(chan struct{})\n\th := func(ctx *RequestCtx) {\n\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t<-readyCh\n\t\tdoneCh <- struct{}{}\n\t}\n\ts := &Server{\n\t\tHandler: TimeoutHandler(h, 20*time.Millisecond, \"timeout!!!\"),\n\t}\n\tserverCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(serverCh)\n\t}()\n\n\tconcurrency := 20\n\tclientCh := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tconn, err := ln.Dial()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tbr := bufio.NewReader(conn)\n\t\t\tverifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), \"timeout!!!\")\n\t\t\tclientCh <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-clientCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t}\n\n\tclose(readyCh)\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-doneCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase <-serverCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n}\n\nfunc TestTimeoutHandlerTimeoutReuse(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\th := func(ctx *RequestCtx) {\n\t\tif string(ctx.Path()) == \"/timeout\" {\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t\tctx.SetBodyString(\"ok\")\n\t}\n\ts := &Server{\n\t\tHandler: TimeoutHandler(h, 500*time.Millisecond, \"timeout!!!\"),\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tbr := bufio.NewReader(conn)\n\tif _, err = conn.Write([]byte(\"GET /timeout HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tverifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), \"timeout!!!\")\n\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tverifyResponse(t, br, StatusOK, string(defaultContentType), \"ok\")\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestServerGetOnly(t *testing.T) {\n\tt.Parallel()\n\n\th := func(ctx *RequestCtx) {\n\t\tif !ctx.IsGet() {\n\t\t\tt.Errorf(\"non-get request: %q\", ctx.Method())\n\t\t}\n\t\tctx.Success(\"foo/bar\", []byte(\"success\"))\n\t}\n\ts := &Server{\n\t\tHandler: h,\n\t\tGetOnly: true,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nContent-Length: 5\\r\\nContent-Type: aaa\\r\\n\\r\\n12345\")\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(rw)\n\t}()\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expecting error\")\n\t\t}\n\t\tif err != ErrGetOnly {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v. Expecting %v\", err, ErrGetOnly)\n\t\t}\n\tcase <-time.After(100 * time.Millisecond):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tstatusCode := resp.StatusCode()\n\tif statusCode != StatusBadRequest {\n\t\tt.Fatalf(\"unexpected status code: %d. Expecting %d\", statusCode, StatusBadRequest)\n\t}\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"missing 'Connection: close' response header\")\n\t}\n}\n\nfunc TestServerTimeoutErrorWithResponse(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tgo func() {\n\t\t\t\tctx.Success(\"aaa/bbb\", []byte(\"xxxyyy\"))\n\t\t\t}()\n\n\t\t\tvar resp Response\n\n\t\t\tresp.SetStatusCode(123)\n\t\t\tresp.SetBodyString(\"foobar. Should be ignored\")\n\t\t\tctx.TimeoutErrorWithResponse(&resp)\n\n\t\t\tresp.SetStatusCode(456)\n\t\t\tresp.ResetBody()\n\t\t\tfmt.Fprintf(resp.BodyWriter(), \"path=%s\", ctx.Path())\n\t\t\tresp.Header.SetContentType(\"foo/bar\")\n\t\t\tctx.TimeoutErrorWithResponse(&resp)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /bar HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 456, \"foo/bar\", \"path=/foo\")\n\tverifyResponse(t, br, 456, \"foo/bar\", \"path=/bar\")\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, \"\")\n\t}\n}\n\nfunc TestServerTimeoutErrorWithCode(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tgo func() {\n\t\t\t\tctx.Success(\"aaa/bbb\", []byte(\"xxxyyy\"))\n\t\t\t}()\n\t\t\tctx.TimeoutErrorWithCode(\"should be ignored\", 234)\n\t\t\tctx.TimeoutErrorWithCode(\"stolen ctx\", StatusBadRequest)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, StatusBadRequest, string(defaultContentType), \"stolen ctx\")\n\tverifyResponse(t, br, StatusBadRequest, string(defaultContentType), \"stolen ctx\")\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, \"\")\n\t}\n}\n\nfunc TestServerTimeoutError(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tgo func() {\n\t\t\t\tctx.Success(\"aaa/bbb\", []byte(\"xxxyyy\"))\n\t\t\t}()\n\t\t\tctx.TimeoutError(\"should be ignored\")\n\t\t\tctx.TimeoutError(\"stolen ctx\")\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), \"stolen ctx\")\n\tverifyResponse(t, br, StatusRequestTimeout, string(defaultContentType), \"stolen ctx\")\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, \"\")\n\t}\n}\n\nfunc TestServerMaxRequestsPerConn(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler:            func(ctx *RequestCtx) {},\n\t\tMaxRequestsPerConn: 1,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /bar HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"Response must have 'connection: close' header\")\n\t}\n\tverifyResponseHeader(t, &resp.Header, 200, 0, string(defaultContentType), \"\")\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, \"\")\n\t}\n}\n\nfunc TestServerConnectionClose(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.SetConnectionClose()\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /must/be/ignored HTTP/1.1\\r\\nHost: aaa.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\tif !resp.ConnectionClose() {\n\t\tt.Fatal(\"expecting Connection: close header\")\n\t}\n\n\tdata, err := io.ReadAll(br)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading remaining data: %v\", err)\n\t}\n\tif len(data) != 0 {\n\t\tt.Fatalf(\"Unexpected data read after the first response %q. Expecting %q\", data, \"\")\n\t}\n}\n\nfunc TestServerRequestNumAndTime(t *testing.T) {\n\tt.Parallel()\n\n\tn := uint64(0)\n\tvar connT time.Time\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tn++\n\t\t\tif ctx.ConnRequestNum() != n {\n\t\t\t\tt.Errorf(\"unexpected request number: %d. Expecting %d\", ctx.ConnRequestNum(), n)\n\t\t\t}\n\t\t\tif connT.IsZero() {\n\t\t\t\tconnT = ctx.ConnTime()\n\t\t\t}\n\t\t\tif ctx.ConnTime() != connT {\n\t\t\t\tt.Errorf(\"unexpected serve conn time: %q. Expecting %q\", ctx.ConnTime(), connT)\n\t\t\t}\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /bar HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"GET /baz HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tif n != 3 {\n\t\tt.Fatalf(\"unexpected number of requests served: %d. Expecting %d\", n, 3)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, string(defaultContentType), \"\")\n}\n\nfunc TestServerEmptyResponse(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t// do nothing :)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, string(defaultContentType), \"\")\n}\n\nfunc TestServerLogger(t *testing.T) {\n\t// This test can't run parallel as it modifies globalConnID.\n\n\tcl := &testLogger{}\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tlogger := ctx.Logger()\n\t\t\th := &ctx.Request.Header\n\t\t\tlogger.Printf(\"begin\")\n\t\t\tctx.Success(\"text/html\", fmt.Appendf(nil, \"requestURI=%s, body=%q, remoteAddr=%s\",\n\t\t\t\th.RequestURI(), ctx.Request.Body(), ctx.RemoteAddr()))\n\t\t\tlogger.Printf(\"end\")\n\t\t},\n\t\tLogger: cl,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\trw.r.WriteString(\"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: 5\\r\\nContent-Type: aa\\r\\n\\r\\nabcde\")\n\n\trwx := &readWriterRemoteAddr{\n\t\trw: rw,\n\t\taddr: &net.TCPAddr{\n\t\t\tIP:   []byte{1, 2, 3, 4},\n\t\t\tPort: 8765,\n\t\t},\n\t}\n\n\tglobalConnID = 0\n\n\tif err := s.ServeConn(rwx); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"text/html\", \"requestURI=/foo1, body=\\\"\\\", remoteAddr=1.2.3.4:8765\")\n\tverifyResponse(t, br, 200, \"text/html\", \"requestURI=/foo2, body=\\\"abcde\\\", remoteAddr=1.2.3.4:8765\")\n\n\texpectedLogOut := `#0000000100000001 - 1.2.3.4:8765<->1.2.3.4:8765 - GET http://google.com/foo1 - begin\n#0000000100000001 - 1.2.3.4:8765<->1.2.3.4:8765 - GET http://google.com/foo1 - end\n#0000000100000002 - 1.2.3.4:8765<->1.2.3.4:8765 - POST http://aaa.com/foo2 - begin\n#0000000100000002 - 1.2.3.4:8765<->1.2.3.4:8765 - POST http://aaa.com/foo2 - end\n`\n\tif cl.out != expectedLogOut {\n\t\tt.Fatalf(\"Unexpected logger output: %q. Expected %q\", cl.out, expectedLogOut)\n\t}\n}\n\nfunc TestServerRemoteAddr(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\th := &ctx.Request.Header\n\t\t\tctx.Success(\"text/html\", fmt.Appendf(nil, \"requestURI=%s, remoteAddr=%s, remoteIP=%s\",\n\t\t\t\th.RequestURI(), ctx.RemoteAddr(), ctx.RemoteIP()))\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\trwx := &readWriterRemoteAddr{\n\t\trw: rw,\n\t\taddr: &net.TCPAddr{\n\t\t\tIP:   []byte{1, 2, 3, 4},\n\t\t\tPort: 8765,\n\t\t},\n\t}\n\n\tif err := s.ServeConn(rwx); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"text/html\", \"requestURI=/foo1, remoteAddr=1.2.3.4:8765, remoteIP=1.2.3.4\")\n}\n\nfunc TestServerCustomRemoteAddr(t *testing.T) {\n\tt.Parallel()\n\n\tcustomRemoteAddrHandler := func(h RequestHandler) RequestHandler {\n\t\treturn func(ctx *RequestCtx) {\n\t\t\tctx.SetRemoteAddr(&net.TCPAddr{\n\t\t\t\tIP:   []byte{1, 2, 3, 5},\n\t\t\t\tPort: 0,\n\t\t\t})\n\t\t\th(ctx)\n\t\t}\n\t}\n\n\ts := &Server{\n\t\tHandler: customRemoteAddrHandler(func(ctx *RequestCtx) {\n\t\t\th := &ctx.Request.Header\n\t\t\tctx.Success(\"text/html\", fmt.Appendf(nil, \"requestURI=%s, remoteAddr=%s, remoteIP=%s\",\n\t\t\t\th.RequestURI(), ctx.RemoteAddr(), ctx.RemoteIP()))\n\t\t}),\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo1 HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\trwx := &readWriterRemoteAddr{\n\t\trw: rw,\n\t\taddr: &net.TCPAddr{\n\t\t\tIP:   []byte{1, 2, 3, 4},\n\t\t\tPort: 8765,\n\t\t},\n\t}\n\n\tif err := s.ServeConn(rwx); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"text/html\", \"requestURI=/foo1, remoteAddr=1.2.3.5:0, remoteIP=1.2.3.5\")\n}\n\ntype readWriterRemoteAddr struct {\n\tnet.Conn\n\n\trw   io.ReadWriteCloser\n\taddr net.Addr\n}\n\nfunc (rw *readWriterRemoteAddr) Close() error {\n\treturn rw.rw.Close()\n}\n\nfunc (rw *readWriterRemoteAddr) Read(b []byte) (int, error) {\n\treturn rw.rw.Read(b)\n}\n\nfunc (rw *readWriterRemoteAddr) Write(b []byte) (int, error) {\n\treturn rw.rw.Write(b)\n}\n\nfunc (rw *readWriterRemoteAddr) RemoteAddr() net.Addr {\n\treturn rw.addr\n}\n\nfunc (rw *readWriterRemoteAddr) LocalAddr() net.Addr {\n\treturn rw.addr\n}\n\nfunc TestServerConnError(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Error(\"foobar\", 423)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo/bar?baz HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response: %v\", err)\n\t}\n\tif resp.Header.StatusCode() != 423 {\n\t\tt.Fatalf(\"Unexpected status code %d. Expected %d\", resp.Header.StatusCode(), 423)\n\t}\n\tif resp.Header.ContentLength() != 6 {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", resp.Header.ContentLength(), 6)\n\t}\n\tif !bytes.Equal(resp.Header.Peek(HeaderContentType), defaultContentType) {\n\t\tt.Fatalf(\"Unexpected Content-Type %q. Expected %q\", resp.Header.Peek(HeaderContentType), defaultContentType)\n\t}\n\tif !bytes.Equal(resp.Body(), []byte(\"foobar\")) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), \"foobar\")\n\t}\n}\n\nfunc TestServeConnSingleRequest(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\th := &ctx.Request.Header\n\t\t\tctx.Success(\"aaa\", fmt.Appendf(nil, \"requestURI=%s, host=%s\", h.RequestURI(), h.Peek(HeaderHost)))\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo/bar?baz HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"aaa\", \"requestURI=/foo/bar?baz, host=google.com\")\n}\n\nfunc TestServerSetFormValueFunc(t *testing.T) {\n\tt.Parallel()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Success(\"aaa\", ctx.FormValue(\"aaa\"))\n\t\t},\n\t\tFormValueFunc: func(ctx *RequestCtx, s string) []byte {\n\t\t\treturn []byte(s)\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo/bar?baz HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"aaa\", \"aaa\")\n}\n\nfunc TestServeConnMultiRequests(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\th := &ctx.Request.Header\n\t\t\tctx.Success(\"aaa\", fmt.Appendf(nil, \"requestURI=%s, host=%s\", h.RequestURI(), h.Peek(HeaderHost)))\n\t\t},\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET /foo/bar?baz HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\nGET /abc HTTP/1.1\\r\\nHost: foobar.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tverifyResponse(t, br, 200, \"aaa\", \"requestURI=/foo/bar?baz, host=google.com\")\n\tverifyResponse(t, br, 200, \"aaa\", \"requestURI=/abc, host=foobar.com\")\n}\n\nfunc TestShutdown(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\ttime.Sleep(time.Millisecond * 500)\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\tserveCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\t_, err := ln.Dial()\n\t\tif err == nil {\n\t\t\tt.Error(\"server is still listening\")\n\t\t}\n\t\tserveCh <- struct{}{}\n\t}()\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tconn, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(conn)\n\t\tresp := verifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\t\tverifyResponseHeaderConnection(t, &resp.Header, \"\")\n\t\tclientCh <- struct{}{}\n\t}()\n\ttime.Sleep(time.Millisecond * 100)\n\tshutdownCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Shutdown(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tshutdownCh <- struct{}{}\n\t}()\n\tdone := 0\n\tfor {\n\t\tselect {\n\t\tcase <-time.After(time.Second * 2):\n\t\t\tt.Fatal(\"shutdown took too long\")\n\t\tcase <-serveCh:\n\t\t\tdone++\n\t\tcase <-clientCh:\n\t\t\tdone++\n\t\tcase <-shutdownCh:\n\t\t\tdone++\n\t\t}\n\t\tif done == 3 {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestCloseOnShutdown(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\ttime.Sleep(time.Millisecond * 500)\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t\tCloseOnShutdown: true,\n\t}\n\tserveCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\t_, err := ln.Dial()\n\t\tif err == nil {\n\t\t\tt.Error(\"server is still listening\")\n\t\t}\n\t\tserveCh <- struct{}{}\n\t}()\n\tclientCh := make(chan struct{})\n\tgo func() {\n\t\tconn, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(conn)\n\t\tresp := verifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\t\tverifyResponseHeaderConnection(t, &resp.Header, \"close\")\n\t\tclientCh <- struct{}{}\n\t}()\n\ttime.Sleep(time.Millisecond * 100)\n\tshutdownCh := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Shutdown(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tshutdownCh <- struct{}{}\n\t}()\n\tdone := 0\n\tfor {\n\t\tselect {\n\t\tcase <-time.After(time.Second * 2):\n\t\t\tt.Fatal(\"shutdown took too long\")\n\t\tcase <-serveCh:\n\t\t\tdone++\n\t\tcase <-clientCh:\n\t\t\tdone++\n\t\tcase <-shutdownCh:\n\t\t\tdone++\n\t\t}\n\t\tif done == 3 {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestShutdownReuse(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t\tReadTimeout: time.Millisecond * 100,\n\t\tLogger:      &testLogger{}, // Ignore log output.\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tbr := bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\tif err := s.Shutdown(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tln = fasthttputil.NewInmemoryListener()\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err = ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tbr = bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\tif err := s.Shutdown(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestShutdownDone(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t<-ctx.Done()\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tgo func() {\n\t\t// Shutdown won't return if the connection doesn't close,\n\t\t// which doesn't happen until we read the response.\n\t\tif err := s.Shutdown(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\t// We can only reach this point and get a valid response\n\t// if reading from ctx.Done() returned.\n\tbr := bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n}\n\nfunc TestShutdownErr(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t// This will panic, but I was not able to intercept with recover()\n\t\t\tc, cancel := context.WithCancel(ctx)\n\t\t\tdefer cancel()\n\t\t\t<-c.Done()\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tgo func() {\n\t\t// Shutdown won't return if the connection doesn't close,\n\t\t// which doesn't happen until we read the response.\n\t\tif err := s.Shutdown(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\t// We can only reach this point and get a valid response\n\t// if reading from ctx.Done() returned.\n\tbr := bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n}\n\nfunc TestShutdownCloseIdleConns(t *testing.T) {\n\tt.Parallel()\n\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n\tbr := bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\n\tshutdownErr := make(chan error)\n\tgo func() {\n\t\tshutdownErr <- s.Shutdown()\n\t}()\n\n\ttimer := time.NewTimer(time.Second)\n\tselect {\n\tcase <-timer.C:\n\t\tt.Fatal(\"idle connections not closed on shutdown\")\n\tcase err = <-shutdownErr:\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err := conn.Read(make([]byte, 1)); err == nil {\n\t\t\tt.Fatal(\"connection not closed\")\n\t\t}\n\t}\n}\n\nfunc TestShutdownWithContext(t *testing.T) {\n\tt.Parallel()\n\tdone := make(chan struct{})\n\tdefer close(done)\n\tln := fasthttputil.NewInmemoryListener()\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t<-done\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\ttime.Sleep(1 * time.Millisecond * 500)\n\tgo func() {\n\t\tconn, err := ln.Dial()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tbr := bufio.NewReader(conn)\n\t\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\t}()\n\n\ttime.Sleep(1 * time.Millisecond * 500)\n\tctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond)\n\tdefer cancel()\n\tshutdownErr := make(chan error)\n\tgo func() {\n\t\tshutdownErr <- s.ShutdownWithContext(ctx)\n\t}()\n\n\ttimer := time.NewTimer(time.Second)\n\tselect {\n\tcase <-timer.C:\n\t\tt.Fatal(\"idle connections not closed on shutdown\")\n\tcase err := <-shutdownErr:\n\t\tif err == nil || err != context.DeadlineExceeded {\n\t\t\tt.Fatalf(\"unexpected err %v. Expecting %v\", err, context.DeadlineExceeded)\n\t\t}\n\t}\n\tif o := s.open.Load(); o != 1 {\n\t\tt.Fatalf(\"unexpected open connection num: %#v. Expecting %#v\", o, 1)\n\t}\n}\n\nfunc TestMultipleServe(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Success(\"aaa/bbb\", []byte(\"real response\"))\n\t\t},\n\t}\n\n\tln1 := fasthttputil.NewInmemoryListener()\n\tln2 := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\tif err := s.Serve(ln1); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tgo func() {\n\t\tif err := s.Serve(ln2); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\n\tconn, err := ln1.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tbr := bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n\n\tconn, err = ln2.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tbr = bufio.NewReader(conn)\n\tverifyResponse(t, br, StatusOK, \"aaa/bbb\", \"real response\")\n}\n\nfunc TestMaxBodySizePerRequest(t *testing.T) {\n\tt.Parallel()\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\t// do nothing :)\n\t\t},\n\t\tHeaderReceived: func(header *RequestHeader) RequestConfig {\n\t\t\treturn RequestConfig{\n\t\t\t\tMaxRequestBodySize: 5 << 10,\n\t\t\t}\n\t\t},\n\t\tReadTimeout:        time.Second * 5,\n\t\tWriteTimeout:       time.Second * 5,\n\t\tMaxRequestBodySize: 1 << 20,\n\t}\n\n\trw := &readWriter{}\n\tfmt.Fprintf(&rw.r, \"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: %d\\r\\nContent-Type: aa\\r\\n\\r\\n%s\", (5<<10)+1, strings.Repeat(\"a\", (5<<10)+1))\n\n\tif err := s.ServeConn(rw); err != ErrBodyTooLarge {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n}\n\nfunc TestStreamRequestBody(t *testing.T) {\n\tt.Parallel()\n\n\tpart1 := strings.Repeat(\"1\", 1<<15)\n\tpart2 := strings.Repeat(\"2\", 1<<16)\n\tcontentLength := len(part1) + len(part2)\n\tnext := make(chan struct{})\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tcheckReader(t, ctx.RequestBodyStream(), part1)\n\t\t\tclose(next)\n\t\t\tcheckReader(t, ctx.RequestBodyStream(), part2)\n\t\t},\n\t\tStreamRequestBody: true,\n\t\tLogger:            &testLogger{},\n\t}\n\n\tpipe := fasthttputil.NewPipeConns()\n\tcc, sc := pipe.Conn1(), pipe.Conn2()\n\t// write headers and part1 body\n\tif _, err := fmt.Fprintf(cc, \"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: %d\\r\\nContent-Type: aa\\r\\n\\r\\n\", contentLength); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := cc.Write([]byte(part1)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(sc)\n\t}()\n\n\tselect {\n\tcase <-next:\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"part1 timeout\")\n\t}\n\n\tif _, err := cc.Write([]byte(part2)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := sc.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err != nil && err.Error() != \"connection closed\" { // fasthttputil.errConnectionClosed is private so do a string match.\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"part2 timeout\")\n\t}\n}\n\nfunc TestStreamRequestBodyExceedMaxSize(t *testing.T) {\n\tpart1 := strings.Repeat(\"1\", 1<<18)\n\tpart2 := strings.Repeat(\"2\", 1<<20-1<<18)\n\tcontentLength := len(part1) + len(part2)\n\tnext := make(chan struct{})\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tcheckReader(t, ctx.RequestBodyStream(), part1)\n\t\t\tclose(next)\n\t\t\tcheckReader(t, ctx.RequestBodyStream(), part2)\n\t\t},\n\t\tDisableKeepalive:   true,\n\t\tStreamRequestBody:  true,\n\t\tMaxRequestBodySize: 1,\n\t}\n\n\tpipe := fasthttputil.NewPipeConns()\n\tcc, sc := pipe.Conn1(), pipe.Conn2()\n\t// write headers and part1 body\n\tif _, err := fmt.Fprintf(cc, \"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: %d\\r\\nContent-Type: aa\\r\\n\\r\\n%s\", contentLength, part1); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(sc)\n\t}()\n\n\tselect {\n\tcase <-next:\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"part1 timeout\")\n\t}\n\n\tif _, err := cc.Write([]byte(part2)); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"part2 timeout\")\n\t}\n}\n\nfunc TestStreamBodyRequestContentLength(t *testing.T) {\n\tcontent := strings.Repeat(\"1\", 1<<15) // 32K\n\tcontentLength := len(content)\n\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\trealContentLength := ctx.Request.Header.ContentLength()\n\t\t\tif realContentLength != contentLength {\n\t\t\t\tt.Fatal(\"incorrect content length\")\n\t\t\t}\n\t\t},\n\t\tMaxRequestBodySize: 1 * 1024 * 1024, // 1M\n\t\tStreamRequestBody:  true,\n\t}\n\n\tpipe := fasthttputil.NewPipeConns()\n\tcc, sc := pipe.Conn1(), pipe.Conn2()\n\tif _, err := fmt.Fprintf(cc, \"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: %d\\r\\nContent-Type: aa\\r\\n\\r\\n%s\", contentLength, content); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(sc)\n\t}()\n\n\tif err := sc.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err == nil || err.Error() != \"connection closed\" { // fasthttputil.errConnectionClosed is private so do a string match.\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"test timeout\")\n\t}\n}\n\nfunc checkReader(t *testing.T, r io.Reader, expected string) {\n\tb := make([]byte, len(expected))\n\tif _, err := io.ReadFull(r, b); err != nil {\n\t\tt.Fatalf(\"Unexpected error from reader: %v\", err)\n\t}\n\tif string(b) != expected {\n\t\tt.Fatal(\"incorrect request body\")\n\t}\n}\n\nfunc TestMaxReadTimeoutPerRequest(t *testing.T) {\n\tt.Parallel()\n\n\theaders := fmt.Appendf(nil, \"POST /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Length: %d\\r\\nContent-Type: aa\\r\\n\\r\\n\", 5*1024)\n\ts := &Server{\n\t\tHandler: func(_ *RequestCtx) {\n\t\t\tt.Error(\"shouldn't reach handler\")\n\t\t},\n\t\tHeaderReceived: func(header *RequestHeader) RequestConfig {\n\t\t\treturn RequestConfig{\n\t\t\t\tReadTimeout: time.Millisecond,\n\t\t\t}\n\t\t},\n\t\tReadBufferSize: len(headers),\n\t\tReadTimeout:    time.Second * 5,\n\t\tWriteTimeout:   time.Second * 5,\n\t}\n\n\tpipe := fasthttputil.NewPipeConns()\n\tcc, sc := pipe.Conn1(), pipe.Conn2()\n\tgo func() {\n\t\t// write headers\n\t\t_, err := cc.Write(headers)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\t// write body\n\t\tfor range 5 * 1024 {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t_, err = cc.Write([]byte{'a'})\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(sc)\n\t}()\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err == nil || !strings.EqualFold(err.Error(), \"timeout\") {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"test timeout\")\n\t}\n}\n\nfunc TestMaxWriteTimeoutPerRequest(t *testing.T) {\n\tt.Parallel()\n\n\theaders := []byte(\"GET /foo2 HTTP/1.1\\r\\nHost: aaa.com\\r\\nContent-Type: aa\\r\\n\\r\\n\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\t\t\tvar buf [192]byte\n\t\t\t\tfor {\n\t\t\t\t\t_, err := w.Write(buf[:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tHeaderReceived: func(header *RequestHeader) RequestConfig {\n\t\t\treturn RequestConfig{\n\t\t\t\tWriteTimeout: time.Millisecond,\n\t\t\t}\n\t\t},\n\t\tReadBufferSize: 192,\n\t\tReadTimeout:    time.Second * 5,\n\t\tWriteTimeout:   time.Second * 5,\n\t}\n\n\tpipe := fasthttputil.NewPipeConns()\n\tcc, sc := pipe.Conn1(), pipe.Conn2()\n\n\tvar resp Response\n\tgo func() {\n\t\t// write headers\n\t\t_, err := cc.Write(headers)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tbr := bufio.NewReaderSize(cc, 192)\n\t\terr = resp.Header.Read(br)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tvar chunk [192]byte\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\t_, err = br.Read(chunk[:])\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(sc)\n\t}()\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err == nil || !strings.EqualFold(err.Error(), \"timeout\") {\n\t\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"test timeout\")\n\t}\n}\n\nfunc TestIncompleteBodyReturnsUnexpectedEOF(t *testing.T) {\n\tt.Parallel()\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"POST /foo HTTP/1.1\\r\\nHost: google.com\\r\\nContent-Length: 5\\r\\n\\r\\n123\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {},\n\t}\n\tch := make(chan error)\n\tgo func() {\n\t\tch <- s.ServeConn(rw)\n\t}()\n\tif err := <-ch; err == nil || err.Error() != \"unexpected EOF\" {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestServerChunkedResponse(t *testing.T) {\n\tt.Parallel()\n\n\ttrailer := map[string]string{\n\t\t\"AtEnd1\": \"1111\",\n\t\t\"AtEnd2\": \"2222\",\n\t\t\"AtEnd3\": \"3333\",\n\t}\n\n\th := func(ctx *RequestCtx) {\n\t\tctx.Response.Header.DisableNormalizing()\n\t\tctx.Response.Header.Set(\"Transfer-Encoding\", \"chunked\")\n\t\tfor k := range trailer {\n\t\t\terr := ctx.Response.Header.AddTrailer(k)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t}\n\t\tctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) {\n\t\t\tfor i := range 3 {\n\t\t\t\tfmt.Fprintf(w, \"message %d\", i)\n\t\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t\ttime.Sleep(time.Millisecond * 100)\n\t\t\t}\n\t\t})\n\t\tfor k, v := range trailer {\n\t\t\tctx.Response.Header.Set(k, v)\n\t\t}\n\t}\n\ts := &Server{\n\t\tHandler: h,\n\t}\n\n\trw := &readWriter{}\n\trw.r.WriteString(\"GET / HTTP/1.1\\r\\nHost: test.com\\r\\n\\r\\n\")\n\n\tif err := s.ServeConn(rw); err != nil {\n\t\tt.Fatalf(\"Unexpected error from serveConn: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(&rw.w)\n\tvar resp Response\n\tif err := resp.Read(br); err != nil {\n\t\tt.Fatalf(\"Unexpected error when reading response: %v\", err)\n\t}\n\tif resp.Header.ContentLength() != -1 {\n\t\tt.Fatalf(\"Unexpected Content-Length %d. Expected %d\", resp.Header.ContentLength(), -1)\n\t}\n\tif !bytes.Equal(resp.Body(), []byte(\"message 0\"+\"message 1\"+\"message 2\")) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), \"foobar\")\n\t}\n\tfor k, v := range trailer {\n\t\th := resp.Header.Peek(k)\n\t\tif !bytes.Equal(h, []byte(v)) {\n\t\t\tt.Fatalf(\"Unexpected trailer %q. Expected %q. Got %q\", k, v, h)\n\t\t}\n\t}\n}\n\nfunc verifyResponse(t *testing.T, r *bufio.Reader, expectedStatusCode int, expectedContentType, expectedBody string) *Response {\n\tt.Helper()\n\n\tvar resp Response\n\tif err := resp.Read(r); err != nil {\n\t\tt.Fatalf(\"Unexpected error when parsing response: %v\", err)\n\t}\n\n\tif !bytes.Equal(resp.Body(), []byte(expectedBody)) {\n\t\tt.Fatalf(\"Unexpected body %q. Expected %q\", resp.Body(), []byte(expectedBody))\n\t}\n\tverifyResponseHeader(t, &resp.Header, expectedStatusCode, len(resp.Body()), expectedContentType, \"\")\n\treturn &resp\n}\n\ntype readWriter struct {\n\tnet.Conn\n\n\tr bytes.Buffer\n\tw bytes.Buffer\n}\n\nfunc (rw *readWriter) Close() error {\n\treturn nil\n}\n\nfunc (rw *readWriter) Read(b []byte) (int, error) {\n\treturn rw.r.Read(b)\n}\n\nfunc (rw *readWriter) Write(b []byte) (int, error) {\n\treturn rw.w.Write(b)\n}\n\nfunc (rw *readWriter) RemoteAddr() net.Addr {\n\treturn zeroTCPAddr\n}\n\nfunc (rw *readWriter) LocalAddr() net.Addr {\n\treturn zeroTCPAddr\n}\n\nfunc (rw *readWriter) SetDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (rw *readWriter) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (rw *readWriter) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n\ntype testLogger struct {\n\tout  string\n\tlock sync.Mutex\n}\n\nfunc (cl *testLogger) Printf(format string, args ...any) {\n\tcl.lock.Lock()\n\tline := fmt.Sprintf(format, args...)\n\tspace := strings.IndexByte(line, ' ') + 1\n\tcl.out += line[space:] + \"\\n\"\n\tcl.lock.Unlock()\n}\n\nfunc TestRequestBodyStreamReadIssue1816(t *testing.T) {\n\tpcs := fasthttputil.NewPipeConns()\n\tcliCon, serverCon := pcs.Conn1(), pcs.Conn2()\n\tgo func() {\n\t\treq := AcquireRequest()\n\t\tdefer ReleaseRequest(req)\n\t\treq.Header.SetContentLength(10)\n\t\treq.Header.SetMethod(\"POST\")\n\t\treq.SetRequestURI(\"http://localhsot:8080\")\n\t\treq.SetBodyRaw(bytes.Repeat([]byte{'1'}, 10))\n\t\treqBody := req.String()\n\t\tpipelineReqBody := make([]byte, 0, len(reqBody)*2)\n\t\tpipelineReqBody = append(pipelineReqBody, reqBody...)\n\t\tpipelineReqBody = append(pipelineReqBody, reqBody...)\n\t\t_, err := cliCon.Write(pipelineReqBody)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tresp := AcquireResponse()\n\t\terr = resp.Read(bufio.NewReader(cliCon))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\terr = cliCon.Close()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\tserver := Server{StreamRequestBody: true, MaxRequestBodySize: 5, Handler: func(ctx *RequestCtx) {\n\t\tr := ctx.RequestBodyStream()\n\t\tp := make([]byte, 1300)\n\t\tfor {\n\t\t\t_, err := r.Read(p)\n\t\t\tif err != nil {\n\t\t\t\tif err != io.EOF {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}}\n\terr := server.serveConn(serverCon)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestRequestCtxInitShouldNotBeCanceledIssue1879(t *testing.T) {\n\tvar r Request\n\tvar requestCtx RequestCtx\n\trequestCtx.Init(&r, nil, nil)\n\terr := requestCtx.Err()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "server_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar defaultClientsCount = runtime.NumCPU()\n\nfunc BenchmarkRequestCtxRedirect(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar ctx RequestCtx\n\t\tfor pb.Next() {\n\t\t\tctx.Request.SetRequestURI(\"http://aaa.com/fff/ss.html?sdf\")\n\t\t\tctx.Redirect(\"/foo/bar?baz=111\", StatusFound)\n\t\t}\n\t})\n}\n\nfunc BenchmarkServerGet1ReqPerConn(b *testing.B) {\n\tbenchmarkServerGet(b, defaultClientsCount, 1)\n}\n\nfunc BenchmarkServerGet2ReqPerConn(b *testing.B) {\n\tbenchmarkServerGet(b, defaultClientsCount, 2)\n}\n\nfunc BenchmarkServerGet10ReqPerConn(b *testing.B) {\n\tbenchmarkServerGet(b, defaultClientsCount, 10)\n}\n\nfunc BenchmarkServerGet10KReqPerConn(b *testing.B) {\n\tbenchmarkServerGet(b, defaultClientsCount, 10000)\n}\n\nfunc BenchmarkNetHTTPServerGet1ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, defaultClientsCount, 1)\n}\n\nfunc BenchmarkNetHTTPServerGet2ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, defaultClientsCount, 2)\n}\n\nfunc BenchmarkNetHTTPServerGet10ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, defaultClientsCount, 10)\n}\n\nfunc BenchmarkNetHTTPServerGet10KReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, defaultClientsCount, 10000)\n}\n\nfunc BenchmarkServerPost1ReqPerConn(b *testing.B) {\n\tbenchmarkServerPost(b, defaultClientsCount, 1)\n}\n\nfunc BenchmarkServerPost2ReqPerConn(b *testing.B) {\n\tbenchmarkServerPost(b, defaultClientsCount, 2)\n}\n\nfunc BenchmarkServerPost10ReqPerConn(b *testing.B) {\n\tbenchmarkServerPost(b, defaultClientsCount, 10)\n}\n\nfunc BenchmarkServerPost10KReqPerConn(b *testing.B) {\n\tbenchmarkServerPost(b, defaultClientsCount, 10000)\n}\n\nfunc BenchmarkNetHTTPServerPost1ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerPost(b, defaultClientsCount, 1)\n}\n\nfunc BenchmarkNetHTTPServerPost2ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerPost(b, defaultClientsCount, 2)\n}\n\nfunc BenchmarkNetHTTPServerPost10ReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerPost(b, defaultClientsCount, 10)\n}\n\nfunc BenchmarkNetHTTPServerPost10KReqPerConn(b *testing.B) {\n\tbenchmarkNetHTTPServerPost(b, defaultClientsCount, 10000)\n}\n\nfunc BenchmarkServerGet1ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkServerGet(b, 10000, 1)\n}\n\nfunc BenchmarkServerGet2ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkServerGet(b, 10000, 2)\n}\n\nfunc BenchmarkServerGet10ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkServerGet(b, 10000, 10)\n}\n\nfunc BenchmarkServerGet100ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkServerGet(b, 10000, 100)\n}\n\nfunc BenchmarkNetHTTPServerGet1ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, 10000, 1)\n}\n\nfunc BenchmarkNetHTTPServerGet2ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, 10000, 2)\n}\n\nfunc BenchmarkNetHTTPServerGet10ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, 10000, 10)\n}\n\nfunc BenchmarkNetHTTPServerGet100ReqPerConn10KClients(b *testing.B) {\n\tbenchmarkNetHTTPServerGet(b, 10000, 100)\n}\n\nfunc BenchmarkServerHijack(b *testing.B) {\n\tclientsCount := 1000\n\trequestsPerConn := 10000\n\tch := make(chan struct{}, b.N)\n\tresponseBody := []byte(\"123\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Hijack(func(c net.Conn) {\n\t\t\t\t// emulate server loop :)\n\t\t\t\terr := ServeConn(c, func(ctx *RequestCtx) {\n\t\t\t\t\tctx.Success(\"foobar\", responseBody)\n\t\t\t\t\tregisterServedRequest(b, ch)\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatalf(\"error when serving connection\")\n\t\t\t\t}\n\t\t\t})\n\t\t\tctx.Success(\"foobar\", responseBody)\n\t\t\tregisterServedRequest(b, ch)\n\t\t},\n\t\tConcurrency: 16 * clientsCount,\n\t}\n\treq := \"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\"\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, req)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc BenchmarkServerMaxConnsPerIP(b *testing.B) {\n\tclientsCount := 1000\n\trequestsPerConn := 10\n\tch := make(chan struct{}, b.N)\n\tresponseBody := []byte(\"123\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tctx.Success(\"foobar\", responseBody)\n\t\t\tregisterServedRequest(b, ch)\n\t\t},\n\t\tMaxConnsPerIP: clientsCount * 2,\n\t\tConcurrency:   16 * clientsCount,\n\t}\n\treq := \"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\"\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, req)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc BenchmarkServerTimeoutError(b *testing.B) {\n\tclientsCount := 10\n\trequestsPerConn := 1\n\tch := make(chan struct{}, b.N)\n\tvar n atomic.Uint32\n\tresponseBody := []byte(\"123\")\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif n.Add(1)&7 == 0 {\n\t\t\t\tctx.TimeoutError(\"xxx\")\n\t\t\t\tgo func() {\n\t\t\t\t\tctx.Success(\"foobar\", responseBody)\n\t\t\t\t}()\n\t\t\t} else {\n\t\t\t\tctx.Success(\"foobar\", responseBody)\n\t\t\t}\n\t\t\tregisterServedRequest(b, ch)\n\t\t},\n\t\tConcurrency: 16 * clientsCount,\n\t}\n\treq := \"GET /foo HTTP/1.1\\r\\nHost: google.com\\r\\n\\r\\n\"\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, req)\n\tverifyRequestsServed(b, ch)\n}\n\ntype fakeServerConn struct {\n\tnet.TCPConn\n\n\tln            *fakeListener\n\trequestsCount int\n\tpos           int\n\tclosed        atomic.Bool\n}\n\nfunc (c *fakeServerConn) Read(b []byte) (int, error) {\n\tnn := 0\n\treqLen := len(c.ln.request)\n\tfor len(b) > 0 {\n\t\tif c.requestsCount == 0 {\n\t\t\tif nn == 0 {\n\t\t\t\treturn 0, io.EOF\n\t\t\t}\n\t\t\treturn nn, nil\n\t\t}\n\t\tpos := c.pos % reqLen\n\t\tn := copy(b, c.ln.request[pos:])\n\t\tb = b[n:]\n\t\tnn += n\n\t\tc.pos += n\n\t\tif n+pos == reqLen {\n\t\t\tc.requestsCount--\n\t\t}\n\t}\n\treturn nn, nil\n}\n\nfunc (c *fakeServerConn) Write(b []byte) (int, error) {\n\treturn len(b), nil\n}\n\nvar fakeAddr = net.TCPAddr{\n\tIP:   []byte{1, 2, 3, 4},\n\tPort: 12345,\n}\n\nfunc (c *fakeServerConn) RemoteAddr() net.Addr {\n\treturn &fakeAddr\n}\n\nfunc (c *fakeServerConn) Close() error {\n\tif c.closed.CompareAndSwap(false, true) {\n\t\tc.ln.ch <- c\n\t}\n\treturn nil\n}\n\nfunc (c *fakeServerConn) SetReadDeadline(t time.Time) error {\n\treturn nil\n}\n\nfunc (c *fakeServerConn) SetWriteDeadline(t time.Time) error {\n\treturn nil\n}\n\ntype fakeListener struct {\n\tch              chan *fakeServerConn\n\tdone            chan struct{}\n\trequest         []byte\n\trequestsCount   int\n\trequestsPerConn int\n\tlock            sync.Mutex\n\tclosed          bool\n}\n\nfunc (ln *fakeListener) Accept() (net.Conn, error) {\n\tln.lock.Lock()\n\tif ln.requestsCount == 0 {\n\t\tln.lock.Unlock()\n\t\tfor len(ln.ch) < cap(ln.ch) {\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t\tln.lock.Lock()\n\t\tif !ln.closed {\n\t\t\tclose(ln.done)\n\t\t\tln.closed = true\n\t\t}\n\t\tln.lock.Unlock()\n\t\treturn nil, io.EOF\n\t}\n\trequestsCount := min(ln.requestsPerConn, ln.requestsCount)\n\tln.requestsCount -= requestsCount\n\tln.lock.Unlock()\n\n\tc := <-ln.ch\n\tc.requestsCount = requestsCount\n\tc.closed.Store(false)\n\tc.pos = 0\n\n\treturn c, nil\n}\n\nfunc (ln *fakeListener) Close() error {\n\treturn nil\n}\n\nfunc (ln *fakeListener) Addr() net.Addr {\n\treturn &fakeAddr\n}\n\nfunc newFakeListener(requestsCount, clientsCount, requestsPerConn int, request string) *fakeListener {\n\tln := &fakeListener{\n\t\trequestsCount:   requestsCount,\n\t\trequestsPerConn: requestsPerConn,\n\t\trequest:         []byte(request),\n\t\tch:              make(chan *fakeServerConn, clientsCount),\n\t\tdone:            make(chan struct{}),\n\t}\n\tfor range clientsCount {\n\t\tln.ch <- &fakeServerConn{\n\t\t\tln: ln,\n\t\t}\n\t}\n\treturn ln\n}\n\nvar (\n\tfakeResponse = []byte(\"Hello, world!\")\n\tgetRequest   = \"GET /foobar?baz HTTP/1.1\\r\\nHost: google.com\\r\\nUser-Agent: aaa/bbb/ccc/ddd/eee Firefox Chrome MSIE Opera\\r\\n\" +\n\t\t\"Referer: http://example.com/aaa?bbb=ccc\\r\\nCookie: foo=bar; baz=baraz; aa=aakslsdweriwereowriewroire\\r\\n\\r\\n\"\n\tpostRequest = fmt.Sprintf(\"POST /foobar?baz HTTP/1.1\\r\\nHost: google.com\\r\\nContent-Type: foo/bar\\r\\nContent-Length: %d\\r\\n\"+\n\t\t\"User-Agent: Opera Chrome MSIE Firefox and other/1.2.34\\r\\nReferer: http://google.com/aaaa/bbb/ccc\\r\\n\"+\n\t\t\"Cookie: foo=bar; baz=baraz; aa=aakslsdweriwereowriewroire\\r\\n\\r\\n%s\",\n\t\tlen(fakeResponse), fakeResponse)\n)\n\nfunc benchmarkServerGet(b *testing.B, clientsCount, requestsPerConn int) {\n\tch := make(chan struct{}, b.N)\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif !ctx.IsGet() {\n\t\t\t\tb.Fatalf(\"Unexpected request method: %q\", ctx.Method())\n\t\t\t}\n\t\t\tctx.Success(\"text/plain\", fakeResponse)\n\t\t\tif requestsPerConn == 1 {\n\t\t\t\tctx.SetConnectionClose()\n\t\t\t}\n\t\t\tregisterServedRequest(b, ch)\n\t\t},\n\t\tConcurrency: 16 * clientsCount,\n\t}\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, getRequest)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc benchmarkNetHTTPServerGet(b *testing.B, clientsCount, requestsPerConn int) {\n\tch := make(chan struct{}, b.N)\n\ts := &http.Server{\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\t\tif req.Method != MethodGet {\n\t\t\t\tb.Fatalf(\"Unexpected request method: %q\", req.Method)\n\t\t\t}\n\t\t\th := w.Header()\n\t\t\th.Set(\"Content-Type\", \"text/plain\")\n\t\t\tif requestsPerConn == 1 {\n\t\t\t\th.Set(HeaderConnection, \"close\")\n\t\t\t}\n\t\t\tw.Write(fakeResponse) //nolint:errcheck\n\t\t\tregisterServedRequest(b, ch)\n\t\t}),\n\t}\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, getRequest)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc benchmarkServerPost(b *testing.B, clientsCount, requestsPerConn int) {\n\tch := make(chan struct{}, b.N)\n\ts := &Server{\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tif !ctx.IsPost() {\n\t\t\t\tb.Fatalf(\"Unexpected request method: %q\", ctx.Method())\n\t\t\t}\n\t\t\tbody := ctx.Request.Body()\n\t\t\tif !bytes.Equal(body, fakeResponse) {\n\t\t\t\tb.Fatalf(\"Unexpected body %q. Expected %q\", body, fakeResponse)\n\t\t\t}\n\t\t\tctx.Success(\"text/plain\", body)\n\t\t\tif requestsPerConn == 1 {\n\t\t\t\tctx.SetConnectionClose()\n\t\t\t}\n\t\t\tregisterServedRequest(b, ch)\n\t\t},\n\t\tConcurrency: 16 * clientsCount,\n\t}\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, postRequest)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc benchmarkNetHTTPServerPost(b *testing.B, clientsCount, requestsPerConn int) {\n\tch := make(chan struct{}, b.N)\n\ts := &http.Server{\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\t\tif req.Method != MethodPost {\n\t\t\t\tb.Fatalf(\"Unexpected request method: %q\", req.Method)\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(req.Body)\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t}\n\t\t\treq.Body.Close()\n\t\t\tif !bytes.Equal(body, fakeResponse) {\n\t\t\t\tb.Fatalf(\"Unexpected body %q. Expected %q\", body, fakeResponse)\n\t\t\t}\n\t\t\th := w.Header()\n\t\t\th.Set(\"Content-Type\", \"text/plain\")\n\t\t\tif requestsPerConn == 1 {\n\t\t\t\th.Set(HeaderConnection, \"close\")\n\t\t\t}\n\t\t\tw.Write(body) //nolint:errcheck\n\t\t\tregisterServedRequest(b, ch)\n\t\t}),\n\t}\n\tbenchmarkServer(b, s, clientsCount, requestsPerConn, postRequest)\n\tverifyRequestsServed(b, ch)\n}\n\nfunc registerServedRequest(b *testing.B, ch chan<- struct{}) {\n\tselect {\n\tcase ch <- struct{}{}:\n\tdefault:\n\t\tb.Fatalf(\"More than %d requests served\", cap(ch))\n\t}\n}\n\nfunc verifyRequestsServed(b *testing.B, ch <-chan struct{}) {\n\trequestsServed := 0\n\tfor len(ch) > 0 {\n\t\t<-ch\n\t\trequestsServed++\n\t}\n\trequestsSent := b.N\n\tfor requestsServed < requestsSent {\n\t\tselect {\n\t\tcase <-ch:\n\t\t\trequestsServed++\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t\tb.Fatalf(\"Unexpected number of requests served %d. Expected %d\", requestsServed, requestsSent)\n\t\t}\n\t}\n}\n\ntype realServer interface {\n\tServe(ln net.Listener) error\n}\n\nfunc benchmarkServer(b *testing.B, s realServer, clientsCount, requestsPerConn int, request string) {\n\tln := newFakeListener(b.N, clientsCount, requestsPerConn, request)\n\tch := make(chan struct{})\n\tgo func() {\n\t\ts.Serve(ln) //nolint:errcheck\n\t\tch <- struct{}{}\n\t}()\n\n\t<-ln.done\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(10 * time.Second):\n\t\tb.Fatalf(\"Server.Serve() didn't stop\")\n\t}\n}\n"
  },
  {
    "path": "stackless/doc.go",
    "content": "// Package stackless provides functionality that may save stack space\n// for high number of concurrently running goroutines.\npackage stackless\n"
  },
  {
    "path": "stackless/func.go",
    "content": "package stackless\n\nimport (\n\t\"runtime\"\n\t\"sync\"\n)\n\n// NewFunc returns stackless wrapper for the function f.\n//\n// Unlike f, the returned stackless wrapper doesn't use stack space\n// on the goroutine that calls it.\n// The wrapper may save a lot of stack space if the following conditions\n// are met:\n//\n//   - f doesn't contain blocking calls on network, I/O or channels;\n//   - f uses a lot of stack space;\n//   - the wrapper is called from high number of concurrent goroutines.\n//\n// The stackless wrapper returns false if the call cannot be processed\n// at the moment due to high load.\nfunc NewFunc(f func(ctx any)) func(ctx any) bool {\n\tif f == nil {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: f cannot be nil\")\n\t}\n\n\tfuncWorkCh := make(chan *funcWork, runtime.GOMAXPROCS(-1)*2048)\n\tonceInit := func() {\n\t\tn := runtime.GOMAXPROCS(-1)\n\t\tfor range n {\n\t\t\tgo funcWorker(funcWorkCh, f)\n\t\t}\n\t}\n\tvar once sync.Once\n\n\treturn func(ctx any) bool {\n\t\tonce.Do(onceInit)\n\t\tfw := getFuncWork()\n\t\tfw.ctx = ctx\n\n\t\tselect {\n\t\tcase funcWorkCh <- fw:\n\t\tdefault:\n\t\t\tputFuncWork(fw)\n\t\t\treturn false\n\t\t}\n\t\t<-fw.done\n\t\tputFuncWork(fw)\n\t\treturn true\n\t}\n}\n\nfunc funcWorker(funcWorkCh <-chan *funcWork, f func(ctx any)) {\n\tfor fw := range funcWorkCh {\n\t\tf(fw.ctx)\n\t\tfw.done <- struct{}{}\n\t}\n}\n\nfunc getFuncWork() *funcWork {\n\tv := funcWorkPool.Get()\n\tif v == nil {\n\t\tv = &funcWork{\n\t\t\tdone: make(chan struct{}, 1),\n\t\t}\n\t}\n\treturn v.(*funcWork)\n}\n\nfunc putFuncWork(fw *funcWork) {\n\tfw.ctx = nil\n\tfuncWorkPool.Put(fw)\n}\n\nvar funcWorkPool sync.Pool\n\ntype funcWork struct {\n\tctx  any\n\tdone chan struct{}\n}\n"
  },
  {
    "path": "stackless/func_test.go",
    "content": "package stackless\n\nimport (\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewFuncSimple(t *testing.T) {\n\tt.Parallel()\n\n\tvar n atomic.Uint64\n\tf := NewFunc(func(ctx any) {\n\t\tn.Add(uint64(ctx.(int)))\n\t})\n\n\titerations := 4 * 1024\n\tfor range iterations {\n\t\tif !f(2) {\n\t\t\tt.Fatalf(\"f mustn't return false\")\n\t\t}\n\t}\n\tif got := n.Load(); got != uint64(2*iterations) {\n\t\tt.Fatalf(\"Unexpected n: %d. Expecting %d\", got, 2*iterations)\n\t}\n}\n\nfunc TestNewFuncMulti(t *testing.T) {\n\tt.Parallel()\n\n\tvar n1, n2 atomic.Uint64\n\tf1 := NewFunc(func(ctx any) {\n\t\tn1.Add(uint64(ctx.(int)))\n\t})\n\tf2 := NewFunc(func(ctx any) {\n\t\tn2.Add(uint64(ctx.(int)))\n\t})\n\n\titerations := 4 * 1024\n\n\tf1Done := make(chan error, 1)\n\tgo func() {\n\t\tvar err error\n\t\tfor range iterations {\n\t\t\tif !f1(3) {\n\t\t\t\terr = errors.New(\"f1 mustn't return false\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tf1Done <- err\n\t}()\n\n\tf2Done := make(chan error, 1)\n\tgo func() {\n\t\tvar err error\n\t\tfor range iterations {\n\t\t\tif !f2(5) {\n\t\t\t\terr = errors.New(\"f2 mustn't return false\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tf2Done <- err\n\t}()\n\n\tselect {\n\tcase err := <-f1Done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tselect {\n\tcase err := <-f2Done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n\n\tif got1 := n1.Load(); got1 != uint64(3*iterations) {\n\t\tt.Fatalf(\"unexpected n1: %d. Expecting %d\", got1, 3*iterations)\n\t}\n\tif got2 := n2.Load(); got2 != uint64(5*iterations) {\n\t\tt.Fatalf(\"unexpected n2: %d. Expecting %d\", got2, 5*iterations)\n\t}\n}\n"
  },
  {
    "path": "stackless/func_timing_test.go",
    "content": "package stackless\n\nimport (\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc BenchmarkFuncOverhead(b *testing.B) {\n\tvar n atomic.Uint64\n\tf := NewFunc(func(ctx any) {\n\t\tn.Add(*(ctx.(*uint64)))\n\t})\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tx := uint64(1)\n\t\tfor pb.Next() {\n\t\t\tif !f(&x) {\n\t\t\t\tb.Fatalf(\"f mustn't return false\")\n\t\t\t}\n\t\t}\n\t})\n\tif got := n.Load(); got != uint64(b.N) {\n\t\tb.Fatalf(\"unexpected n: %d. Expecting %d\", got, b.N)\n\t}\n}\n\nfunc BenchmarkFuncPure(b *testing.B) {\n\tvar n atomic.Uint64\n\tf := func(x *uint64) {\n\t\tn.Add(*x)\n\t}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tx := uint64(1)\n\t\tfor pb.Next() {\n\t\t\tf(&x)\n\t\t}\n\t})\n\tif got := n.Load(); got != uint64(b.N) {\n\t\tb.Fatalf(\"unexpected n: %d. Expecting %d\", got, b.N)\n\t}\n}\n"
  },
  {
    "path": "stackless/s2b.go",
    "content": "package stackless\n\nimport \"unsafe\"\n\n// s2b converts string to a byte slice without memory allocation.\nfunc s2b(s string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(s), len(s))\n}\n"
  },
  {
    "path": "stackless/writer.go",
    "content": "package stackless\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\n// Writer is an interface stackless writer must conform to.\n//\n// The interface contains common subset for Writers from compress/* packages.\ntype Writer interface {\n\tWrite(p []byte) (int, error)\n\tFlush() error\n\tClose() error\n\tReset(w io.Writer)\n}\n\n// NewWriterFunc must return new writer that will be wrapped into\n// stackless writer.\ntype NewWriterFunc func(w io.Writer) Writer\n\n// NewWriter creates a stackless writer around a writer returned\n// from newWriter.\n//\n// The returned writer writes data to dstW.\n//\n// Writers that use a lot of stack space may be wrapped into stackless writer,\n// thus saving stack space for high number of concurrently running goroutines.\nfunc NewWriter(dstW io.Writer, newWriter NewWriterFunc) Writer {\n\tw := &writer{\n\t\tdstW: dstW,\n\t}\n\tw.zw = newWriter(&w.xw)\n\treturn w\n}\n\ntype writer struct {\n\tdstW io.Writer\n\tzw   Writer\n\n\terr error\n\txw  xWriter\n\n\tp []byte\n\tn int\n\n\top op\n}\n\ntype op int\n\nconst (\n\topWrite op = iota\n\topFlush\n\topClose\n\topReset\n)\n\nfunc (w *writer) Write(p []byte) (int, error) {\n\tw.p = p\n\terr := w.do(opWrite)\n\tw.p = nil\n\treturn w.n, err\n}\n\nfunc (w *writer) WriteString(s string) (int, error) {\n\tw.p = s2b(s)\n\terr := w.do(opWrite)\n\tw.p = nil\n\treturn w.n, err\n}\n\nfunc (w *writer) Flush() error {\n\treturn w.do(opFlush)\n}\n\nfunc (w *writer) Close() error {\n\treturn w.do(opClose)\n}\n\nfunc (w *writer) Reset(dstW io.Writer) {\n\tw.xw.Reset()\n\tw.do(opReset) //nolint:errcheck\n\tw.dstW = dstW\n}\n\nfunc (w *writer) do(op op) error {\n\tw.op = op\n\tif !stacklessWriterFunc(w) {\n\t\treturn errHighLoad\n\t}\n\terr := w.err\n\tif err != nil {\n\t\treturn err\n\t}\n\tif w.xw.bb != nil && len(w.xw.bb.B) > 0 {\n\t\t_, err = w.dstW.Write(w.xw.bb.B)\n\t}\n\tw.xw.Reset()\n\n\treturn err\n}\n\nvar errHighLoad = errors.New(\"cannot compress data due to high load\")\n\nvar (\n\tstacklessWriterFuncOnce sync.Once\n\tstacklessWriterFuncFunc func(ctx any) bool\n)\n\nfunc stacklessWriterFunc(ctx any) bool {\n\tstacklessWriterFuncOnce.Do(func() {\n\t\tstacklessWriterFuncFunc = NewFunc(writerFunc)\n\t})\n\treturn stacklessWriterFuncFunc(ctx)\n}\n\nfunc writerFunc(ctx any) {\n\tw := ctx.(*writer)\n\tswitch w.op {\n\tcase opWrite:\n\t\tw.n, w.err = w.zw.Write(w.p)\n\tcase opFlush:\n\t\tw.err = w.zw.Flush()\n\tcase opClose:\n\t\tw.err = w.zw.Close()\n\tcase opReset:\n\t\tw.zw.Reset(&w.xw)\n\t\tw.err = nil\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"BUG: unexpected op: %d\", w.op))\n\t}\n}\n\ntype xWriter struct {\n\tbb *bytebufferpool.ByteBuffer\n}\n\nfunc (w *xWriter) Write(p []byte) (int, error) {\n\tif w.bb == nil {\n\t\tw.bb = bufferPool.Get()\n\t}\n\treturn w.bb.Write(p)\n}\n\nfunc (w *xWriter) Reset() {\n\tif w.bb != nil {\n\t\tbufferPool.Put(w.bb)\n\t\tw.bb = nil\n\t}\n}\n\nvar bufferPool bytebufferpool.Pool\n"
  },
  {
    "path": "stackless/writer_test.go",
    "content": "package stackless\n\nimport (\n\t\"bytes\"\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCompressFlateSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testCompressFlate(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestCompressFlateConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(testCompressFlate, 10); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc testCompressFlate() error {\n\treturn testWriter(func(w io.Writer) Writer {\n\t\tzw, err := flate.NewWriter(w, flate.DefaultCompression)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"BUG: unexpected error: %v\", err))\n\t\t}\n\t\treturn zw\n\t}, func(r io.Reader) io.Reader {\n\t\treturn flate.NewReader(r)\n\t})\n}\n\nfunc TestCompressGzipSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testCompressGzip(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestCompressGzipConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(testCompressGzip, 10); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc testCompressGzip() error {\n\treturn testWriter(func(w io.Writer) Writer {\n\t\treturn gzip.NewWriter(w)\n\t}, func(r io.Reader) io.Reader {\n\t\tzr, err := gzip.NewReader(r)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"BUG: cannot create gzip reader: %v\", err))\n\t\t}\n\t\treturn zr\n\t})\n}\n\nfunc testWriter(newWriter NewWriterFunc, newReader func(io.Reader) io.Reader) error {\n\tdstW := &bytes.Buffer{}\n\tw := NewWriter(dstW, newWriter)\n\n\tfor i := range 5 {\n\t\tif err := testWriterReuse(w, dstW, newReader); err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected error when re-using writer on iteration %d: %w\", i, err)\n\t\t}\n\t\tdstW = &bytes.Buffer{}\n\t\tw.Reset(dstW)\n\t}\n\n\treturn nil\n}\n\nfunc testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader) error {\n\twantW := &bytes.Buffer{}\n\tmw := io.MultiWriter(w, wantW)\n\tfor i := range 30 {\n\t\tfmt.Fprintf(mw, \"foobar %d\\n\", i)\n\t\tif i%13 == 0 {\n\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error on flush: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\tw.Close()\n\n\tzr := newReader(r)\n\tdata, err := io.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w, data=%q\", err, data)\n\t}\n\n\twantData := wantW.Bytes()\n\tif !bytes.Equal(data, wantData) {\n\t\treturn fmt.Errorf(\"unexpected data: %q. Expecting %q\", data, wantData)\n\t}\n\n\treturn nil\n}\n\nfunc testConcurrent(testFunc func() error, concurrency int) error {\n\tch := make(chan error, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\tch <- testFunc()\n\t\t}()\n\t}\n\tfor i := range concurrency {\n\t\tselect {\n\t\tcase err := <-ch:\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unexpected error on goroutine %d: %w\", i, err)\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\treturn fmt.Errorf(\"timeout on goroutine %d\", i)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "status.go",
    "content": "package fasthttp\n\nimport (\n\t\"strconv\"\n)\n\nconst (\n\tstatusMessageMin = 100\n\tstatusMessageMax = 511\n)\n\n// HTTP status codes were stolen from net/http.\nconst (\n\tStatusContinue           = 100 // RFC 7231, 6.2.1\n\tStatusSwitchingProtocols = 101 // RFC 7231, 6.2.2\n\tStatusProcessing         = 102 // RFC 2518, 10.1\n\tStatusEarlyHints         = 103 // RFC 8297\n\n\tStatusOK                   = 200 // RFC 7231, 6.3.1\n\tStatusCreated              = 201 // RFC 7231, 6.3.2\n\tStatusAccepted             = 202 // RFC 7231, 6.3.3\n\tStatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4\n\tStatusNoContent            = 204 // RFC 7231, 6.3.5\n\tStatusResetContent         = 205 // RFC 7231, 6.3.6\n\tStatusPartialContent       = 206 // RFC 7233, 4.1\n\tStatusMultiStatus          = 207 // RFC 4918, 11.1\n\tStatusAlreadyReported      = 208 // RFC 5842, 7.1\n\tStatusIMUsed               = 226 // RFC 3229, 10.4.1\n\n\tStatusMultipleChoices   = 300 // RFC 7231, 6.4.1\n\tStatusMovedPermanently  = 301 // RFC 7231, 6.4.2\n\tStatusFound             = 302 // RFC 7231, 6.4.3\n\tStatusSeeOther          = 303 // RFC 7231, 6.4.4\n\tStatusNotModified       = 304 // RFC 7232, 4.1\n\tStatusUseProxy          = 305 // RFC 7231, 6.4.5\n\t_                       = 306 // RFC 7231, 6.4.6 (Unused)\n\tStatusTemporaryRedirect = 307 // RFC 7231, 6.4.7\n\tStatusPermanentRedirect = 308 // RFC 7538, 3\n\n\tStatusBadRequest                   = 400 // RFC 7231, 6.5.1\n\tStatusUnauthorized                 = 401 // RFC 7235, 3.1\n\tStatusPaymentRequired              = 402 // RFC 7231, 6.5.2\n\tStatusForbidden                    = 403 // RFC 7231, 6.5.3\n\tStatusNotFound                     = 404 // RFC 7231, 6.5.4\n\tStatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5\n\tStatusNotAcceptable                = 406 // RFC 7231, 6.5.6\n\tStatusProxyAuthRequired            = 407 // RFC 7235, 3.2\n\tStatusRequestTimeout               = 408 // RFC 7231, 6.5.7\n\tStatusConflict                     = 409 // RFC 7231, 6.5.8\n\tStatusGone                         = 410 // RFC 7231, 6.5.9\n\tStatusLengthRequired               = 411 // RFC 7231, 6.5.10\n\tStatusPreconditionFailed           = 412 // RFC 7232, 4.2\n\tStatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11\n\tStatusRequestURITooLong            = 414 // RFC 7231, 6.5.12\n\tStatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13\n\tStatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4\n\tStatusExpectationFailed            = 417 // RFC 7231, 6.5.14\n\tStatusTeapot                       = 418 // RFC 7168, 2.3.3\n\tStatusMisdirectedRequest           = 421 // RFC 7540, 9.1.2\n\tStatusUnprocessableEntity          = 422 // RFC 4918, 11.2\n\tStatusLocked                       = 423 // RFC 4918, 11.3\n\tStatusFailedDependency             = 424 // RFC 4918, 11.4\n\tStatusUpgradeRequired              = 426 // RFC 7231, 6.5.15\n\tStatusPreconditionRequired         = 428 // RFC 6585, 3\n\tStatusTooManyRequests              = 429 // RFC 6585, 4\n\tStatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5\n\tStatusUnavailableForLegalReasons   = 451 // RFC 7725, 3\n\n\tStatusInternalServerError           = 500 // RFC 7231, 6.6.1\n\tStatusNotImplemented                = 501 // RFC 7231, 6.6.2\n\tStatusBadGateway                    = 502 // RFC 7231, 6.6.3\n\tStatusServiceUnavailable            = 503 // RFC 7231, 6.6.4\n\tStatusGatewayTimeout                = 504 // RFC 7231, 6.6.5\n\tStatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6\n\tStatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1\n\tStatusInsufficientStorage           = 507 // RFC 4918, 11.5\n\tStatusLoopDetected                  = 508 // RFC 5842, 7.2\n\tStatusNotExtended                   = 510 // RFC 2774, 7\n\tStatusNetworkAuthenticationRequired = 511 // RFC 6585, 6\n)\n\nvar (\n\tunknownStatusCode = \"Unknown Status Code\"\n\n\tstatusMessages = []string{\n\t\tStatusContinue:           \"Continue\",\n\t\tStatusSwitchingProtocols: \"Switching Protocols\",\n\t\tStatusProcessing:         \"Processing\",\n\t\tStatusEarlyHints:         \"Early Hints\",\n\n\t\tStatusOK:                   \"OK\",\n\t\tStatusCreated:              \"Created\",\n\t\tStatusAccepted:             \"Accepted\",\n\t\tStatusNonAuthoritativeInfo: \"Non-Authoritative Information\",\n\t\tStatusNoContent:            \"No Content\",\n\t\tStatusResetContent:         \"Reset Content\",\n\t\tStatusPartialContent:       \"Partial Content\",\n\t\tStatusMultiStatus:          \"Multi-Status\",\n\t\tStatusAlreadyReported:      \"Already Reported\",\n\t\tStatusIMUsed:               \"IM Used\",\n\n\t\tStatusMultipleChoices:   \"Multiple Choices\",\n\t\tStatusMovedPermanently:  \"Moved Permanently\",\n\t\tStatusFound:             \"Found\",\n\t\tStatusSeeOther:          \"See Other\",\n\t\tStatusNotModified:       \"Not Modified\",\n\t\tStatusUseProxy:          \"Use Proxy\",\n\t\tStatusTemporaryRedirect: \"Temporary Redirect\",\n\t\tStatusPermanentRedirect: \"Permanent Redirect\",\n\n\t\tStatusBadRequest:                   \"Bad Request\",\n\t\tStatusUnauthorized:                 \"Unauthorized\",\n\t\tStatusPaymentRequired:              \"Payment Required\",\n\t\tStatusForbidden:                    \"Forbidden\",\n\t\tStatusNotFound:                     \"Not Found\",\n\t\tStatusMethodNotAllowed:             \"Method Not Allowed\",\n\t\tStatusNotAcceptable:                \"Not Acceptable\",\n\t\tStatusProxyAuthRequired:            \"Proxy Authentication Required\",\n\t\tStatusRequestTimeout:               \"Request Timeout\",\n\t\tStatusConflict:                     \"Conflict\",\n\t\tStatusGone:                         \"Gone\",\n\t\tStatusLengthRequired:               \"Length Required\",\n\t\tStatusPreconditionFailed:           \"Precondition Failed\",\n\t\tStatusRequestEntityTooLarge:        \"Request Entity Too Large\",\n\t\tStatusRequestURITooLong:            \"Request URI Too Long\",\n\t\tStatusUnsupportedMediaType:         \"Unsupported Media Type\",\n\t\tStatusRequestedRangeNotSatisfiable: \"Requested Range Not Satisfiable\",\n\t\tStatusExpectationFailed:            \"Expectation Failed\",\n\t\tStatusTeapot:                       \"I'm a teapot\",\n\t\tStatusMisdirectedRequest:           \"Misdirected Request\",\n\t\tStatusUnprocessableEntity:          \"Unprocessable Entity\",\n\t\tStatusLocked:                       \"Locked\",\n\t\tStatusFailedDependency:             \"Failed Dependency\",\n\t\tStatusUpgradeRequired:              \"Upgrade Required\",\n\t\tStatusPreconditionRequired:         \"Precondition Required\",\n\t\tStatusTooManyRequests:              \"Too Many Requests\",\n\t\tStatusRequestHeaderFieldsTooLarge:  \"Request Header Fields Too Large\",\n\t\tStatusUnavailableForLegalReasons:   \"Unavailable For Legal Reasons\",\n\n\t\tStatusInternalServerError:           \"Internal Server Error\",\n\t\tStatusNotImplemented:                \"Not Implemented\",\n\t\tStatusBadGateway:                    \"Bad Gateway\",\n\t\tStatusServiceUnavailable:            \"Service Unavailable\",\n\t\tStatusGatewayTimeout:                \"Gateway Timeout\",\n\t\tStatusHTTPVersionNotSupported:       \"HTTP Version Not Supported\",\n\t\tStatusVariantAlsoNegotiates:         \"Variant Also Negotiates\",\n\t\tStatusInsufficientStorage:           \"Insufficient Storage\",\n\t\tStatusLoopDetected:                  \"Loop Detected\",\n\t\tStatusNotExtended:                   \"Not Extended\",\n\t\tStatusNetworkAuthenticationRequired: \"Network Authentication Required\",\n\t}\n)\n\n// StatusMessage returns HTTP status message for the given status code.\nfunc StatusMessage(statusCode int) string {\n\tif statusCode < statusMessageMin || statusCode > statusMessageMax {\n\t\treturn unknownStatusCode\n\t}\n\n\tif s := statusMessages[statusCode]; s != \"\" {\n\t\treturn s\n\t}\n\treturn unknownStatusCode\n}\n\nfunc formatStatusLine(dst, protocol []byte, statusCode int, statusText []byte) []byte {\n\tdst = append(dst, protocol...)\n\tdst = append(dst, ' ')\n\tdst = strconv.AppendInt(dst, int64(statusCode), 10)\n\tdst = append(dst, ' ')\n\tif len(statusText) == 0 {\n\t\tdst = append(dst, s2b(StatusMessage(statusCode))...)\n\t} else {\n\t\tdst = append(dst, statusText...)\n\t}\n\treturn append(dst, strCRLF...)\n}\n"
  },
  {
    "path": "status_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestStatusLine(t *testing.T) {\n\tt.Parallel()\n\n\ttestStatusLine(t, -1, []byte(\"HTTP/1.1 -1 Unknown Status Code\\r\\n\"))\n\ttestStatusLine(t, 99, []byte(\"HTTP/1.1 99 Unknown Status Code\\r\\n\"))\n\ttestStatusLine(t, 200, []byte(\"HTTP/1.1 200 OK\\r\\n\"))\n\ttestStatusLine(t, 512, []byte(\"HTTP/1.1 512 Unknown Status Code\\r\\n\"))\n\ttestStatusLine(t, 512, []byte(\"HTTP/1.1 512 Unknown Status Code\\r\\n\"))\n\ttestStatusLine(t, 520, []byte(\"HTTP/1.1 520 Unknown Status Code\\r\\n\"))\n}\n\nfunc testStatusLine(t *testing.T, statusCode int, expected []byte) {\n\tline := formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))\n\tif !bytes.Equal(expected, line) {\n\t\tt.Fatalf(\"unexpected status line %q. Expecting %q\", string(line), string(expected))\n\t}\n}\n"
  },
  {
    "path": "status_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc BenchmarkStatusLine99(b *testing.B) {\n\tbenchmarkStatusLine(b, 99, []byte(\"HTTP/1.1 99 Unknown Status Code\\r\\n\"))\n}\n\nfunc BenchmarkStatusLine200(b *testing.B) {\n\tbenchmarkStatusLine(b, 200, []byte(\"HTTP/1.1 200 OK\\r\\n\"))\n}\n\nfunc BenchmarkStatusLine512(b *testing.B) {\n\tbenchmarkStatusLine(b, 512, []byte(\"HTTP/1.1 512 Unknown Status Code\\r\\n\"))\n}\n\nfunc benchmarkStatusLine(b *testing.B, statusCode int, expected []byte) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tline := formatStatusLine(nil, strHTTP11, statusCode, s2b(StatusMessage(statusCode)))\n\t\t\tif !bytes.Equal(expected, line) {\n\t\t\t\tb.Fatalf(\"unexpected status line %q. Expecting %q\", string(line), string(expected))\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "stream.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\n// StreamWriter must write data to w.\n//\n// Usually StreamWriter writes data to w in a loop (aka 'data streaming').\n//\n// StreamWriter must return immediately if w returns error.\n//\n// Since the written data is buffered, do not forget calling w.Flush\n// when the data must be propagated to reader.\ntype StreamWriter func(w *bufio.Writer)\n\n// NewStreamReader returns a reader, which replays all the data generated by sw.\n//\n// The returned reader may be passed to Response.SetBodyStream.\n//\n// Close must be called on the returned reader after all the required data\n// has been read. Otherwise goroutine leak may occur.\n//\n// See also Response.SetBodyStreamWriter.\nfunc NewStreamReader(sw StreamWriter) io.ReadCloser {\n\tpc := fasthttputil.NewPipeConns()\n\tpw := pc.Conn1()\n\tpr := pc.Conn2()\n\n\tvar bw *bufio.Writer\n\tv := streamWriterBufPool.Get()\n\tif v == nil {\n\t\tbw = bufio.NewWriter(pw)\n\t} else {\n\t\tbw = v.(*bufio.Writer)\n\t\tbw.Reset(pw)\n\t}\n\n\tgo func() {\n\t\tsw(bw)\n\t\tbw.Flush()\n\t\tpw.Close()\n\n\t\tstreamWriterBufPool.Put(bw)\n\t}()\n\n\treturn pr\n}\n\nvar streamWriterBufPool sync.Pool\n"
  },
  {
    "path": "stream_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestNewStreamReader(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan struct{})\n\tr := NewStreamReader(func(w *bufio.Writer) {\n\t\tfmt.Fprintf(w, \"Hello, world\\n\")\n\t\tfmt.Fprintf(w, \"Line #2\\n\")\n\t\tclose(ch)\n\t})\n\n\tdata, err := io.ReadAll(r)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\texpectedData := \"Hello, world\\nLine #2\\n\"\n\tif string(data) != expectedData {\n\t\tt.Fatalf(\"unexpected data %q. Expecting %q\", data, expectedData)\n\t}\n\n\tif err = r.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error\")\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout\")\n\t}\n}\n\nfunc TestStreamReaderClose(t *testing.T) {\n\tt.Parallel()\n\n\tfirstLine := \"the first line must pass\"\n\tch := make(chan error, 1)\n\tr := NewStreamReader(func(w *bufio.Writer) {\n\t\tfmt.Fprintf(w, \"%s\", firstLine)\n\t\tif err := w.Flush(); err != nil {\n\t\t\tch <- fmt.Errorf(\"unexpected error on first flush: %w\", err)\n\t\t\treturn\n\t\t}\n\n\t\tdata := createFixedBody(4000)\n\t\tfor range 100 {\n\t\t\tw.Write(data) //nolint:errcheck\n\t\t}\n\t\tif err := w.Flush(); err == nil {\n\t\t\tch <- errors.New(\"expecting error on the second flush\")\n\t\t}\n\t\tch <- nil\n\t})\n\n\tbuf := make([]byte, len(firstLine))\n\tn, err := io.ReadFull(r, buf)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif n != len(buf) {\n\t\tt.Fatalf(\"unexpected number of bytes read: %d. Expecting %d\", n, len(buf))\n\t}\n\tif string(buf) != firstLine {\n\t\tt.Fatalf(\"unexpected result: %q. Expecting %q\", buf, firstLine)\n\t}\n\n\tif err := r.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error returned from stream reader: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout when waiting for stream reader\")\n\t}\n\n\t// read trailing data\n\tgo func() {\n\t\tif _, err := io.ReadAll(r); err != nil {\n\t\t\tch <- fmt.Errorf(\"unexpected error when reading trailing data: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tch <- nil\n\t}()\n\n\tselect {\n\tcase err := <-ch:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error returned when reading tail data: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"timeout when reading tail data\")\n\t}\n}\n"
  },
  {
    "path": "stream_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc BenchmarkStreamReader1(b *testing.B) {\n\tbenchmarkStreamReader(b, 1)\n}\n\nfunc BenchmarkStreamReader10(b *testing.B) {\n\tbenchmarkStreamReader(b, 10)\n}\n\nfunc BenchmarkStreamReader100(b *testing.B) {\n\tbenchmarkStreamReader(b, 100)\n}\n\nfunc BenchmarkStreamReader1K(b *testing.B) {\n\tbenchmarkStreamReader(b, 1000)\n}\n\nfunc BenchmarkStreamReader10K(b *testing.B) {\n\tbenchmarkStreamReader(b, 10000)\n}\n\nfunc benchmarkStreamReader(b *testing.B, size int) {\n\tsrc := createFixedBody(size)\n\tb.SetBytes(int64(size))\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tdst := make([]byte, size)\n\t\tch := make(chan error, 1)\n\t\tsr := NewStreamReader(func(w *bufio.Writer) {\n\t\t\tfor pb.Next() {\n\t\t\t\tif _, err := w.Write(src); err != nil {\n\t\t\t\t\tch <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err := w.Flush(); err != nil {\n\t\t\t\t\tch <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tch <- nil\n\t\t})\n\t\tfor {\n\t\t\tif _, err := sr.Read(dst); err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tb.Fatalf(\"unexpected error when reading from stream reader: %v\", err)\n\t\t\t}\n\t\t}\n\t\tif err := sr.Close(); err != nil {\n\t\t\tb.Fatalf(\"unexpected error when closing stream reader: %v\", err)\n\t\t}\n\t\tselect {\n\t\tcase err := <-ch:\n\t\t\tif err != nil {\n\t\t\t\tb.Fatalf(\"unexpected error from stream reader: %v\", err)\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\tb.Fatalf(\"timeout\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "streaming.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/valyala/bytebufferpool\"\n)\n\ntype headerInterface interface {\n\tContentLength() int\n\tReadTrailer(r *bufio.Reader) error\n}\n\ntype requestStream struct {\n\theader          headerInterface\n\tprefetchedBytes *bytes.Reader\n\treader          *bufio.Reader\n\ttotalBytesRead  int\n\tchunkLeft       int\n}\n\nfunc (rs *requestStream) Read(p []byte) (int, error) {\n\tvar (\n\t\tn   int\n\t\terr error\n\t)\n\tif rs.header.ContentLength() == -1 {\n\t\tif rs.chunkLeft == 0 {\n\t\t\tchunkSize, err := parseChunkSize(rs.reader)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tif chunkSize == 0 {\n\t\t\t\terr = rs.header.ReadTrailer(rs.reader)\n\t\t\t\tif err != nil && err != io.EOF {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\treturn 0, io.EOF\n\t\t\t}\n\t\t\trs.chunkLeft = chunkSize\n\t\t}\n\t\tbytesToRead := min(rs.chunkLeft, len(p))\n\t\tn, err = rs.reader.Read(p[:bytesToRead])\n\t\trs.totalBytesRead += n\n\t\trs.chunkLeft -= n\n\t\tif err == io.EOF {\n\t\t\terr = io.ErrUnexpectedEOF\n\t\t}\n\t\tif err == nil && rs.chunkLeft == 0 {\n\t\t\terr = readCrLf(rs.reader)\n\t\t}\n\t\treturn n, err\n\t}\n\tif rs.totalBytesRead == rs.header.ContentLength() {\n\t\treturn 0, io.EOF\n\t}\n\tprefetchedSize := int(rs.prefetchedBytes.Size())\n\tif prefetchedSize > rs.totalBytesRead {\n\t\tleft := prefetchedSize - rs.totalBytesRead\n\t\tif len(p) > left {\n\t\t\tp = p[:left]\n\t\t}\n\t\tn, err := rs.prefetchedBytes.Read(p)\n\t\trs.totalBytesRead += n\n\t\tif n == rs.header.ContentLength() {\n\t\t\treturn n, io.EOF\n\t\t}\n\t\treturn n, err\n\t}\n\tleft := rs.header.ContentLength() - rs.totalBytesRead\n\tif left > 0 && len(p) > left {\n\t\tp = p[:left]\n\t}\n\tn, err = rs.reader.Read(p)\n\trs.totalBytesRead += n\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\tif rs.totalBytesRead == rs.header.ContentLength() {\n\t\terr = io.EOF\n\t}\n\treturn n, err\n}\n\nfunc acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, h headerInterface) *requestStream {\n\trs := requestStreamPool.Get().(*requestStream)\n\trs.prefetchedBytes = bytes.NewReader(b.B)\n\trs.reader = r\n\trs.header = h\n\treturn rs\n}\n\nfunc releaseRequestStream(rs *requestStream) {\n\trs.prefetchedBytes = nil\n\trs.totalBytesRead = 0\n\trs.chunkLeft = 0\n\trs.reader = nil\n\trs.header = nil\n\trequestStreamPool.Put(rs)\n}\n\nvar requestStreamPool = sync.Pool{\n\tNew: func() any {\n\t\treturn &requestStream{}\n\t},\n}\n"
  },
  {
    "path": "streaming_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nfunc TestStreamingPipeline(t *testing.T) {\n\tt.Parallel()\n\n\treqS := strings.ReplaceAll(`POST /one HTTP/1.1\nHost: example.com\nContent-Length: 10\n\naaaaaaaaaa\nPOST /two HTTP/1.1\nHost: example.com\nContent-Length: 10\n\naaaaaaaaaa`, \"\\n\", \"\\r\\n\")\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\ts := &Server{\n\t\tStreamRequestBody: true,\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tvar body string\n\t\t\texpected := \"aaaaaaaaaa\"\n\t\t\tif string(ctx.Path()) == \"/one\" {\n\t\t\t\tbody = string(ctx.PostBody())\n\t\t\t} else {\n\t\t\t\tall, err := io.ReadAll(ctx.RequestBodyStream())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t\tbody = string(all)\n\t\t\t}\n\t\t\tif body != expected {\n\t\t\t\tt.Errorf(\"expected %q got %q\", expected, body)\n\t\t\t}\n\t\t},\n\t}\n\n\tch := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(reqS)); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tvar resp Response\n\tbr := bufio.NewReader(conn)\n\trespCh := make(chan struct{})\n\tgo func() {\n\t\tif err := resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"error when reading response: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\n\t\tif err := resp.Read(br); err != nil {\n\t\t\tt.Errorf(\"error when reading response: %v\", err)\n\t\t}\n\t\tif resp.StatusCode() != StatusOK {\n\t\t\tt.Errorf(\"unexpected status code %d. Expecting %d\", resp.StatusCode(), StatusOK)\n\t\t}\n\t\tclose(respCh)\n\t}()\n\n\tselect {\n\tcase <-respCh:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout\")\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t}\n}\n\nfunc getChunkedTestEnv(t testing.TB) (*fasthttputil.InmemoryListener, []byte) {\n\tbody := createFixedBody(128 * 1024)\n\tchunkedBody := createChunkedBody(body, nil, true)\n\n\ttestHandler := func(ctx *RequestCtx) {\n\t\tbodyBytes, err := io.ReadAll(ctx.RequestBodyStream())\n\t\tif err != nil {\n\t\t\tt.Logf(\"io read returned err=%v\", err)\n\t\t\tt.Error(\"unexpected error while reading request body stream\")\n\t\t}\n\n\t\tif !bytes.Equal(body, bodyBytes) {\n\t\t\tt.Errorf(\"unexpected request body, expected %q, got %q\", body, bodyBytes)\n\t\t}\n\t}\n\ts := &Server{\n\t\tHandler:            testHandler,\n\t\tStreamRequestBody:  true,\n\t\tMaxRequestBodySize: 1, // easier to test with small limit\n\t}\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tgo func() {\n\t\terr := s.Serve(ln)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"could not serve listener: %v\", err)\n\t\t}\n\t}()\n\n\treq := Request{}\n\treq.SetHost(\"localhost\")\n\treq.Header.SetMethod(\"POST\")\n\treq.Header.Set(\"transfer-encoding\", \"chunked\")\n\treq.Header.SetContentLength(-1)\n\n\tformattedRequest := req.Header.Header()\n\tformattedRequest = append(formattedRequest, chunkedBody...)\n\n\treturn ln, formattedRequest\n}\n\nfunc TestRequestStreamChunkedWithTrailer(t *testing.T) {\n\tt.Parallel()\n\n\tbody := createFixedBody(10)\n\texpectedTrailer := map[string]string{\n\t\t\"Foo\": \"footest\",\n\t\t\"Bar\": \"bartest\",\n\t}\n\tchunkedBody := createChunkedBody(body, expectedTrailer, true)\n\treq := fmt.Sprintf(strings.ReplaceAll(`POST / HTTP/1.1\nHost: example.com\nTransfer-Encoding: chunked\nTrailer: Foo, Bar\n\n%s\n`, \"\\n\", \"\\r\\n\"), chunkedBody)\n\n\tln := fasthttputil.NewInmemoryListener()\n\tch := make(chan struct{})\n\ts := &Server{\n\t\tStreamRequestBody: true,\n\t\tHandler: func(ctx *RequestCtx) {\n\t\t\tall, err := io.ReadAll(ctx.RequestBodyStream())\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif !bytes.Equal(all, body) {\n\t\t\t\tt.Errorf(\"unexpected body %q. Expecting %q\", all, body)\n\t\t\t}\n\n\t\t\tfor k, v := range expectedTrailer {\n\t\t\t\tr := ctx.Request.Header.Peek(k)\n\t\t\t\tif string(r) != v {\n\t\t\t\t\tt.Errorf(\"unexpected trailer %q. Expecting %q. Got %q\", k, v, r)\n\t\t\t\t}\n\t\t\t}\n\t\t\tclose(ch)\n\t\t},\n\t}\n\n\tch2 := make(chan struct{})\n\tgo func() {\n\t\tif err := s.Serve(ln); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tclose(ch2)\n\t}()\n\n\tconn, err := ln.Dial()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif _, err = conn.Write([]byte(req)); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"error when closing listener: %v\", err)\n\t}\n\n\tselect {\n\tcase <-ch:\n\t\tselect {\n\t\tcase <-ch2:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatal(\"timeout when waiting for the server to stop\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"timeout when waiting for the request to be processed\")\n\t}\n}\n\nfunc TestRequestStream(t *testing.T) {\n\tt.Parallel()\n\n\tln, formattedRequest := getChunkedTestEnv(t)\n\n\tc, err := ln.Dial()\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error while dialing: %v\", err)\n\t}\n\tif _, err = c.Write(formattedRequest); err != nil {\n\t\tt.Errorf(\"unexpected error while writing request: %v\", err)\n\t}\n\n\tbr := bufio.NewReader(c)\n\tvar respH ResponseHeader\n\tif err = respH.Read(br); err != nil {\n\t\tt.Errorf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc BenchmarkRequestStreamE2E(b *testing.B) {\n\tln, formattedRequest := getChunkedTestEnv(b)\n\n\twg := &sync.WaitGroup{}\n\twg.Add(4)\n\tfor range 4 {\n\t\tgo func(wg *sync.WaitGroup) {\n\t\t\tfor i := 0; i < b.N/4; i++ {\n\t\t\t\tc, err := ln.Dial()\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Errorf(\"unexpected error while dialing: %v\", err)\n\t\t\t\t}\n\t\t\t\tif _, err = c.Write(formattedRequest); err != nil {\n\t\t\t\t\tb.Errorf(\"unexpected error while writing request: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tbr := bufio.NewReaderSize(c, 128)\n\t\t\t\tvar respH ResponseHeader\n\t\t\t\tif err = respH.Read(br); err != nil {\n\t\t\t\t\tb.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t\tc.Close()\n\t\t\t}\n\t\t\twg.Done()\n\t\t}(wg)\n\t}\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "strings.go",
    "content": "package fasthttp\n\nvar (\n\tdefaultServerName  = \"fasthttp\"\n\tdefaultUserAgent   = \"fasthttp\"\n\tdefaultContentType = []byte(\"text/plain; charset=utf-8\")\n)\n\nvar (\n\tstrSlash                    = []byte(\"/\")\n\tstrSlashSlash               = []byte(\"//\")\n\tstrSlashDotDot              = []byte(\"/..\")\n\tstrSlashDotSlash            = []byte(\"/./\")\n\tstrSlashDotDotSlash         = []byte(\"/../\")\n\tstrBackSlashDotDot          = []byte(`\\..`)\n\tstrBackSlashDotBackSlash    = []byte(`\\.\\`)\n\tstrSlashDotDotBackSlash     = []byte(`/..\\`)\n\tstrBackSlashDotDotBackSlash = []byte(`\\..\\`)\n\tstrCRLF                     = []byte(\"\\r\\n\")\n\tstrCRLFCRLF                 = []byte(\"\\r\\n\\r\\n\")\n\tstrHTTP                     = []byte(\"http\")\n\tstrHTTPS                    = []byte(\"https\")\n\tstrHTTP11                   = []byte(\"HTTP/1.1\")\n\tstrColon                    = []byte(\":\")\n\tstrColonSlashSlash          = []byte(\"://\")\n\tstrColonSpace               = []byte(\": \")\n\tstrCommaSpace               = []byte(\", \")\n\tstrGMT                      = []byte(\"GMT\")\n\tstrSpace                    = []byte(\" \")\n\n\tstrResponseContinue = []byte(\"HTTP/1.1 100 Continue\\r\\n\\r\\n\")\n\tstrEarlyHints       = []byte(\"HTTP/1.1 103 Early Hints\\r\\n\")\n\n\tstrExpect             = []byte(HeaderExpect)\n\tstrConnection         = []byte(HeaderConnection)\n\tstrContentLength      = []byte(HeaderContentLength)\n\tstrContentType        = []byte(HeaderContentType)\n\tstrDate               = []byte(HeaderDate)\n\tstrHost               = []byte(HeaderHost)\n\tstrReferer            = []byte(HeaderReferer)\n\tstrServer             = []byte(HeaderServer)\n\tstrTransferEncoding   = []byte(HeaderTransferEncoding)\n\tstrContentEncoding    = []byte(HeaderContentEncoding)\n\tstrAcceptEncoding     = []byte(HeaderAcceptEncoding)\n\tstrUserAgent          = []byte(HeaderUserAgent)\n\tstrCookie             = []byte(HeaderCookie)\n\tstrSetCookie          = []byte(HeaderSetCookie)\n\tstrLocation           = []byte(HeaderLocation)\n\tstrIfModifiedSince    = []byte(HeaderIfModifiedSince)\n\tstrLastModified       = []byte(HeaderLastModified)\n\tstrAcceptRanges       = []byte(HeaderAcceptRanges)\n\tstrRange              = []byte(HeaderRange)\n\tstrContentRange       = []byte(HeaderContentRange)\n\tstrAuthorization      = []byte(HeaderAuthorization)\n\tstrTE                 = []byte(HeaderTE)\n\tstrTrailer            = []byte(HeaderTrailer)\n\tstrMaxForwards        = []byte(HeaderMaxForwards)\n\tstrProxyConnection    = []byte(HeaderProxyConnection)\n\tstrProxyAuthenticate  = []byte(HeaderProxyAuthenticate)\n\tstrProxyAuthorization = []byte(HeaderProxyAuthorization)\n\tstrWWWAuthenticate    = []byte(HeaderWWWAuthenticate)\n\tstrVary               = []byte(HeaderVary)\n\n\tstrCookieExpires        = []byte(\"expires\")\n\tstrCookieDomain         = []byte(\"domain\")\n\tstrCookiePath           = []byte(\"path\")\n\tstrCookieHTTPOnly       = []byte(\"HttpOnly\")\n\tstrCookieSecure         = []byte(\"secure\")\n\tstrCookiePartitioned    = []byte(\"Partitioned\")\n\tstrCookieMaxAge         = []byte(\"max-age\")\n\tstrCookieSameSite       = []byte(\"SameSite\")\n\tstrCookieSameSiteLax    = []byte(\"Lax\")\n\tstrCookieSameSiteStrict = []byte(\"Strict\")\n\tstrCookieSameSiteNone   = []byte(\"None\")\n\n\tstrClose               = []byte(\"close\")\n\tstrGzip                = []byte(\"gzip\")\n\tstrBr                  = []byte(\"br\")\n\tstrZstd                = []byte(\"zstd\")\n\tstrDeflate             = []byte(\"deflate\")\n\tstrKeepAlive           = []byte(\"keep-alive\")\n\tstrUpgrade             = []byte(\"Upgrade\")\n\tstrChunked             = []byte(\"chunked\")\n\tstrIdentity            = []byte(\"identity\")\n\tstr100Continue         = []byte(\"100-continue\")\n\tstrPostArgsContentType = []byte(\"application/x-www-form-urlencoded\")\n\tstrDefaultContentType  = []byte(\"application/octet-stream\")\n\tstrMultipartFormData   = []byte(\"multipart/form-data\")\n\tstrBoundary            = []byte(\"boundary\")\n\tstrBytes               = []byte(\"bytes\")\n\tstrBasicSpace          = []byte(\"Basic \")\n\tstrLink                = []byte(\"Link\")\n\n\tstrApplicationSlash = []byte(\"application/\")\n\tstrImageSVG         = []byte(\"image/svg\")\n\tstrImageIcon        = []byte(\"image/x-icon\")\n\tstrFontSlash        = []byte(\"font/\")\n\tstrMultipartSlash   = []byte(\"multipart/\")\n\tstrTextSlash        = []byte(\"text/\")\n)\n"
  },
  {
    "path": "tcpdialer.go",
    "content": "package fasthttp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\n// Dial dials the given TCP addr using tcp4.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//   - It returns ErrDialTimeout if connection cannot be established during\n//     DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.Dial or HostClient.Dial.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc Dial(addr string) (net.Conn, error) {\n\treturn defaultDialer.Dial(addr)\n}\n\n// DialTimeout dials the given TCP addr using tcp4 using the given timeout.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.DialTimeout or HostClient.DialTimeout.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {\n\treturn defaultDialer.DialTimeout(addr, timeout)\n}\n\n// DialDualStack dials the given TCP addr using both tcp4 and tcp6.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//   - It returns ErrDialTimeout if connection cannot be established during\n//     DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial\n//     timeout.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.Dial or HostClient.Dial.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc DialDualStack(addr string) (net.Conn, error) {\n\treturn defaultDialer.DialDualStack(addr)\n}\n\n// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6\n// using the given timeout.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.DialTimeout or HostClient.DialTimeout.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {\n\treturn defaultDialer.DialDualStackTimeout(addr, timeout)\n}\n\nvar defaultDialer = &TCPDialer{Concurrency: 1000}\n\n// Resolver represents interface of the tcp resolver.\ntype Resolver interface {\n\tLookupIPAddr(context.Context, string) (names []net.IPAddr, err error)\n}\n\n// TCPDialer contains options to control a group of Dial calls.\ntype TCPDialer struct {\n\t// This may be used to override DNS resolving policy, like this:\n\t// var dialer = &fasthttp.TCPDialer{\n\t// \tResolver: &net.Resolver{\n\t// \t\tPreferGo:     true,\n\t// \t\tStrictErrors: false,\n\t// \t\tDial: func (ctx context.Context, network, address string) (net.Conn, error) {\n\t// \t\t\td := net.Dialer{}\n\t// \t\t\treturn d.DialContext(ctx, \"udp\", \"8.8.8.8:53\")\n\t// \t\t},\n\t// \t},\n\t// }\n\tResolver Resolver\n\n\t// LocalAddr is the local address to use when dialing an\n\t// address.\n\t// If nil, a local address is automatically chosen.\n\tLocalAddr *net.TCPAddr\n\n\tconcurrencyCh chan struct{}\n\n\ttcpAddrsMap sync.Map\n\n\t// Concurrency controls the maximum number of concurrent Dials\n\t// that can be performed using this object.\n\t// Setting this to 0 means unlimited.\n\t//\n\t// WARNING: This can only be changed before the first Dial.\n\t// Changes made after the first Dial will not affect anything.\n\tConcurrency int\n\n\t// DNSCacheDuration may be used to override the default DNS cache duration (DefaultDNSCacheDuration)\n\tDNSCacheDuration time.Duration\n\n\tonce sync.Once\n\n\t// DisableDNSResolution may be used to disable DNS resolution\n\tDisableDNSResolution bool\n}\n\n// Dial dials the given TCP addr using tcp4.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//   - It returns ErrDialTimeout if connection cannot be established during\n//     DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.Dial or HostClient.Dial.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc (d *TCPDialer) Dial(addr string) (net.Conn, error) {\n\treturn d.dial(addr, false, DefaultDialTimeout)\n}\n\n// DialTimeout dials the given TCP addr using tcp4 using the given timeout.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.DialTimeout or HostClient.DialTimeout.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc (d *TCPDialer) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {\n\treturn d.dial(addr, false, timeout)\n}\n\n// DialDualStack dials the given TCP addr using both tcp4 and tcp6.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//   - It returns ErrDialTimeout if connection cannot be established during\n//     DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial\n//     timeout.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.Dial or HostClient.Dial.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc (d *TCPDialer) DialDualStack(addr string) (net.Conn, error) {\n\treturn d.dial(addr, true, DefaultDialTimeout)\n}\n\n// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6\n// using the given timeout.\n//\n// This function has the following additional features comparing to net.Dial:\n//\n//   - It reduces load on DNS resolver by caching resolved TCP addressed\n//     for DNSCacheDuration.\n//   - It dials all the resolved TCP addresses in round-robin manner until\n//     connection is established. This may be useful if certain addresses\n//     are temporarily unreachable.\n//\n// This dialer is intended for custom code wrapping before passing\n// to Client.DialTimeout or HostClient.DialTimeout.\n//\n// For instance, per-host counters and/or limits may be implemented\n// by such wrappers.\n//\n// The addr passed to the function must contain port. Example addr values:\n//\n//   - foobar.baz:443\n//   - foo.bar:80\n//   - aaa.com:8080\nfunc (d *TCPDialer) DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {\n\treturn d.dial(addr, true, timeout)\n}\n\n// FlushDNSCache clears all cached DNS entries, forcing fresh DNS lookups on subsequent dials.\n// This is useful when you want to ensure fresh DNS resolution, for example after network changes.\nfunc (d *TCPDialer) FlushDNSCache() {\n\td.tcpAddrsMap.Range(func(k, v any) bool {\n\t\td.tcpAddrsMap.Delete(k)\n\t\treturn true\n\t})\n}\n\n// FlushDNSCache clears all cached DNS entries for the default dialer,\n// forcing fresh DNS lookups on subsequent Dial* calls.\n// This is useful when you want to ensure fresh DNS resolution, for example after network changes.\nfunc FlushDNSCache() {\n\tdefaultDialer.FlushDNSCache()\n}\n\nfunc (d *TCPDialer) dial(addr string, dualStack bool, timeout time.Duration) (net.Conn, error) {\n\td.once.Do(func() {\n\t\tif d.Concurrency > 0 {\n\t\t\td.concurrencyCh = make(chan struct{}, d.Concurrency)\n\t\t}\n\n\t\tif d.DNSCacheDuration == 0 {\n\t\t\td.DNSCacheDuration = DefaultDNSCacheDuration\n\t\t}\n\n\t\tif !d.DisableDNSResolution {\n\t\t\tgo d.tcpAddrsClean()\n\t\t}\n\t})\n\tdeadline := time.Now().Add(timeout)\n\tnetwork := \"tcp4\"\n\tif dualStack {\n\t\tnetwork = \"tcp\"\n\t}\n\tif d.DisableDNSResolution {\n\t\treturn d.tryDial(network, addr, deadline, d.concurrencyCh)\n\t}\n\taddrs, idx, err := d.getTCPAddrs(addr, dualStack, deadline)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar conn net.Conn\n\tn := uint32(len(addrs)) // #nosec G115\n\tfor range n {\n\t\tconn, err = d.tryDial(network, addrs[idx%n].String(), deadline, d.concurrencyCh)\n\t\tif err == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\tif errors.Is(err, ErrDialTimeout) {\n\t\t\treturn nil, err\n\t\t}\n\t\tidx++\n\t}\n\treturn nil, err\n}\n\nfunc (d *TCPDialer) tryDial(\n\tnetwork string, addr string, deadline time.Time, concurrencyCh chan struct{},\n) (net.Conn, error) {\n\ttimeout := time.Until(deadline)\n\tif timeout <= 0 {\n\t\treturn nil, wrapDialWithUpstream(ErrDialTimeout, addr)\n\t}\n\n\tif concurrencyCh != nil {\n\t\tselect {\n\t\tcase concurrencyCh <- struct{}{}:\n\t\tdefault:\n\t\t\ttc := AcquireTimer(timeout)\n\t\t\tisTimeout := false\n\t\t\tselect {\n\t\t\tcase concurrencyCh <- struct{}{}:\n\t\t\tcase <-tc.C:\n\t\t\t\tisTimeout = true\n\t\t\t}\n\t\t\tReleaseTimer(tc)\n\t\t\tif isTimeout {\n\t\t\t\treturn nil, wrapDialWithUpstream(ErrDialTimeout, addr)\n\t\t\t}\n\t\t}\n\t\tdefer func() { <-concurrencyCh }()\n\t}\n\n\tdialer := net.Dialer{}\n\tif d.LocalAddr != nil {\n\t\tdialer.LocalAddr = d.LocalAddr\n\t}\n\n\tctx, cancelCtx := context.WithDeadline(context.Background(), deadline)\n\tdefer cancelCtx()\n\tconn, err := dialer.DialContext(ctx, network, addr)\n\tif err != nil {\n\t\tif ctx.Err() == context.DeadlineExceeded {\n\t\t\treturn nil, wrapDialWithUpstream(ErrDialTimeout, addr)\n\t\t}\n\t\treturn nil, wrapDialWithUpstream(err, addr)\n\t}\n\treturn conn, nil\n}\n\n// ErrDialTimeout is returned when TCP dialing is timed out.\nvar ErrDialTimeout = errors.New(\"dialing to the given TCP address timed out\")\n\n// ErrDialWithUpstream wraps dial error with upstream info.\n//\n// Should use errors.As to get upstream information from error:\n//\n//\thc := fasthttp.HostClient{Addr: \"foo.com,bar.com\"}\n//\terr := hc.Do(req, res)\n//\n//\tvar dialErr *fasthttp.ErrDialWithUpstream\n//\tif errors.As(err, &dialErr) {\n//\t\tupstream = dialErr.Upstream // 34.206.39.153:80\n//\t}\ntype ErrDialWithUpstream struct {\n\twrapErr  error\n\tUpstream string\n}\n\nfunc (e *ErrDialWithUpstream) Error() string {\n\treturn fmt.Sprintf(\"error when dialing %s: %s\", e.Upstream, e.wrapErr.Error())\n}\n\nfunc (e *ErrDialWithUpstream) Unwrap() error {\n\treturn e.wrapErr\n}\n\nfunc wrapDialWithUpstream(err error, upstream string) error {\n\treturn &ErrDialWithUpstream{\n\t\tUpstream: upstream,\n\t\twrapErr:  err,\n\t}\n}\n\n// DefaultDialTimeout is timeout used by Dial and DialDualStack\n// for establishing TCP connections.\nconst DefaultDialTimeout = 3 * time.Second\n\ntype tcpAddrEntry struct {\n\tresolveTime time.Time\n\taddrs       []net.TCPAddr\n\taddrsIdx    uint32\n\n\tpending int32\n}\n\n// DefaultDNSCacheDuration is the duration for caching resolved TCP addresses\n// by Dial* functions.\nconst DefaultDNSCacheDuration = time.Minute\n\n// cleanExpiredDNSEntries removes expired DNS cache entries based on DNSCacheDuration.\n// This is the core cleanup logic used by both the background cleaner and manual cleanup.\nfunc (d *TCPDialer) cleanExpiredDNSEntries() {\n\texpireDuration := 2 * d.DNSCacheDuration\n\n\tt := time.Now()\n\td.tcpAddrsMap.Range(func(k, v any) bool {\n\t\tif e, ok := v.(*tcpAddrEntry); ok && t.Sub(e.resolveTime) > expireDuration {\n\t\t\td.tcpAddrsMap.Delete(k)\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc (d *TCPDialer) tcpAddrsClean() {\n\tfor {\n\t\ttime.Sleep(time.Second)\n\t\td.cleanExpiredDNSEntries()\n\t}\n}\n\nfunc (d *TCPDialer) getTCPAddrs(addr string, dualStack bool, deadline time.Time) ([]net.TCPAddr, uint32, error) {\n\titem, exist := d.tcpAddrsMap.Load(addr)\n\te, ok := item.(*tcpAddrEntry)\n\tif exist && ok && e != nil && time.Since(e.resolveTime) > d.DNSCacheDuration {\n\t\t// Only let one goroutine re-resolve at a time.\n\t\tif atomic.SwapInt32(&e.pending, 1) == 0 {\n\t\t\te = nil\n\t\t}\n\t}\n\n\tif e == nil {\n\t\taddrs, err := resolveTCPAddrs(addr, dualStack, d.Resolver, deadline)\n\t\tif err != nil {\n\t\t\titem, exist := d.tcpAddrsMap.Load(addr)\n\t\t\te, ok = item.(*tcpAddrEntry)\n\t\t\tif exist && ok && e != nil {\n\t\t\t\t// Set pending to 0 so another goroutine can retry.\n\t\t\t\tatomic.StoreInt32(&e.pending, 0)\n\t\t\t}\n\t\t\treturn nil, 0, err\n\t\t}\n\n\t\te = &tcpAddrEntry{\n\t\t\taddrs:       addrs,\n\t\t\tresolveTime: time.Now(),\n\t\t}\n\t\td.tcpAddrsMap.Store(addr, e)\n\t}\n\n\tidx := atomic.AddUint32(&e.addrsIdx, 1)\n\treturn e.addrs, idx, nil\n}\n\nfunc resolveTCPAddrs(addr string, dualStack bool, resolver Resolver, deadline time.Time) ([]net.TCPAddr, error) {\n\thost, portS, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tport, err := strconv.Atoi(portS)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resolver == nil {\n\t\tresolver = net.DefaultResolver\n\t}\n\n\tctx, cancel := context.WithDeadline(context.Background(), deadline)\n\tdefer cancel()\n\tipaddrs, err := resolver.LookupIPAddr(ctx, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn := len(ipaddrs)\n\taddrs := make([]net.TCPAddr, 0, n)\n\tfor i := range n {\n\t\tip := ipaddrs[i]\n\t\tif !dualStack && ip.IP.To4() == nil {\n\t\t\tcontinue\n\t\t}\n\t\taddrs = append(addrs, net.TCPAddr{\n\t\t\tIP:   ip.IP,\n\t\t\tPort: port,\n\t\t\tZone: ip.Zone,\n\t\t})\n\t}\n\tif len(addrs) == 0 {\n\t\treturn nil, errNoDNSEntries\n\t}\n\treturn addrs, nil\n}\n\nvar errNoDNSEntries = errors.New(\"couldn't find DNS entries for the given domain. Try using DialDualStack\")\n"
  },
  {
    "path": "tcplisten/README.md",
    "content": "# TCPListen\n\nPackage tcplisten provides customizable TCP net.Listener with various\nperformance-related options:\n\n * SO_REUSEPORT. This option allows linear scaling server performance\n   on multi-CPU servers.\n   See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for details.\n\n * TCP_DEFER_ACCEPT. This option expects the server reads from the accepted\n   connection before writing to them.\n\n * TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details.\n\nThe package is derived from [go_reuseport](https://github.com/valyala/tcplisten).\n"
  },
  {
    "path": "tcplisten/socket.go",
    "content": "//go:build !js && !wasm && (linux || darwin || dragonfly || freebsd || netbsd || openbsd || rumprun || (zos && s390x))\n\npackage tcplisten\n\nimport (\n\t\"fmt\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc newSocketCloexecOld(domain, typ, proto int) (int, error) {\n\tsyscall.ForkLock.RLock()\n\tfd, err := unix.Socket(domain, typ, proto)\n\tif err == nil {\n\t\tunix.CloseOnExec(fd)\n\t}\n\tsyscall.ForkLock.RUnlock()\n\tif err != nil {\n\t\treturn -1, fmt.Errorf(\"cannot create listening socket: %w\", err)\n\t}\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\tunix.Close(fd)\n\t\treturn -1, fmt.Errorf(\"cannot make non-blocked listening socket: %w\", err)\n\t}\n\treturn fd, nil\n}\n"
  },
  {
    "path": "tcplisten/socket_darwin.go",
    "content": "//go:build darwin\n\npackage tcplisten\n\nvar newSocketCloexec = newSocketCloexecOld\n"
  },
  {
    "path": "tcplisten/socket_other.go",
    "content": "//go:build !js && !wasm && (linux || dragonfly || freebsd || netbsd || openbsd || rumprun)\n\npackage tcplisten\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc newSocketCloexec(domain, typ, proto int) (int, error) {\n\tfd, err := unix.Socket(domain, typ|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, proto)\n\tif err == nil {\n\t\treturn fd, nil\n\t}\n\n\tif err == unix.EPROTONOSUPPORT || err == unix.EINVAL {\n\t\treturn newSocketCloexecOld(domain, typ, proto)\n\t}\n\n\treturn -1, fmt.Errorf(\"cannot create listening unblocked socket: %w\", err)\n}\n"
  },
  {
    "path": "tcplisten/socket_zos_s390x.go",
    "content": "//go:build zos && s390x\n\npackage tcplisten\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc newSocketCloexec(domain, typ, proto int) (int, error) {\n\tfd, err := unix.Socket(domain, typ, proto)\n\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC)\n\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFL, unix.O_NONBLOCK)\n\tif err == nil {\n\t\treturn fd, nil\n\t}\n\treturn -1, fmt.Errorf(\"cannot create listening unblocked socket: %s\", err)\n}\n"
  },
  {
    "path": "tcplisten/tcplisten.go",
    "content": "//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || rumprun || (zos && s390x)\n\n// Package tcplisten provides customizable TCP net.Listener with various\n// performance-related options:\n//\n//   - SO_REUSEPORT. This option allows linear scaling server performance\n//     on multi-CPU servers.\n//     See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for details.\n//\n//   - TCP_DEFER_ACCEPT. This option expects the server reads from the accepted\n//     connection before writing to them.\n//\n//   - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details.\n//\n// The package is derived from https://github.com/valyala/tcplisten\npackage tcplisten\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// Config provides options to enable on the returned listener.\ntype Config struct {\n\t// ReusePort enables SO_REUSEPORT.\n\tReusePort bool\n\n\t// DeferAccept enables TCP_DEFER_ACCEPT.\n\tDeferAccept bool\n\n\t// FastOpen enables TCP_FASTOPEN.\n\tFastOpen bool\n\n\t// Backlog is the maximum number of pending TCP connections the listener\n\t// may queue before passing them to Accept.\n\t// See man 2 listen for details.\n\t//\n\t// By default system-level backlog value is used.\n\tBacklog int\n}\n\n// NewListener returns TCP listener with options set in the Config.\n//\n// The function may be called many times for creating distinct listeners\n// with the given config.\n//\n// Only tcp4 and tcp6 networks are supported.\nfunc (cfg *Config) NewListener(network, addr string) (net.Listener, error) {\n\tsa, soType, err := getSockaddr(network, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfd, err := newSocketCloexec(soType, unix.SOCK_STREAM, unix.IPPROTO_TCP)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = cfg.fdSetup(fd, sa, addr); err != nil {\n\t\tunix.Close(fd)\n\t\treturn nil, err\n\t}\n\n\tfdUintptr, err := safeIntToUintptr(fd)\n\tif err != nil {\n\t\tunix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"unexpected convert socket fd int to uintptr: %w\", err)\n\t}\n\n\tname := fmt.Sprintf(\"reuseport.%d.%s.%s\", os.Getpid(), network, addr)\n\tfile := os.NewFile(fdUintptr, name)\n\tln, err := net.FileListener(file)\n\tif err != nil {\n\t\tfile.Close()\n\t\treturn nil, err\n\t}\n\n\tif err = file.Close(); err != nil {\n\t\tln.Close()\n\t\treturn nil, err\n\t}\n\n\treturn ln, nil\n}\n\nfunc (cfg *Config) fdSetup(fd int, sa unix.Sockaddr, addr string) error {\n\tvar err error\n\n\tif err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil {\n\t\treturn fmt.Errorf(\"cannot enable SO_REUSEADDR: %w\", err)\n\t}\n\n\t// This should disable Nagle's algorithm in all accepted sockets by default.\n\t// Users may enable it with net.TCPConn.SetNoDelay(false).\n\tif err = unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1); err != nil {\n\t\treturn fmt.Errorf(\"cannot disable Nagle's algorithm: %w\", err)\n\t}\n\n\tif cfg.ReusePort {\n\t\tif err = unix.SetsockoptInt(fd, unix.SOL_SOCKET, soReusePort, 1); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot enable SO_REUSEPORT: %w\", err)\n\t\t}\n\t}\n\n\tif cfg.DeferAccept {\n\t\tif err = enableDeferAccept(fd); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif cfg.FastOpen {\n\t\tif err = enableFastOpen(fd); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err = unix.Bind(fd, sa); err != nil {\n\t\treturn fmt.Errorf(\"cannot bind to %q: %w\", addr, err)\n\t}\n\n\tbacklog := cfg.Backlog\n\tif backlog <= 0 {\n\t\tif backlog, err = soMaxConn(); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot determine backlog to pass to listen(2): %w\", err)\n\t\t}\n\t}\n\tif err = unix.Listen(fd, backlog); err != nil {\n\t\treturn fmt.Errorf(\"cannot listen on %q: %w\", addr, err)\n\t}\n\n\treturn nil\n}\n\nfunc getSockaddr(network, addr string) (sa unix.Sockaddr, soType int, err error) {\n\ttcpAddr, err := net.ResolveTCPAddr(network, addr)\n\tif err != nil {\n\t\treturn nil, -1, err\n\t}\n\n\tswitch network {\n\tcase \"tcp4\":\n\t\tvar sa4 unix.SockaddrInet4\n\t\tsa4.Port = tcpAddr.Port\n\t\tcopy(sa4.Addr[:], tcpAddr.IP.To4())\n\t\treturn &sa4, unix.AF_INET, nil\n\tcase \"tcp6\":\n\t\tvar sa6 unix.SockaddrInet6\n\t\tsa6.Port = tcpAddr.Port\n\t\tcopy(sa6.Addr[:], tcpAddr.IP.To16())\n\t\tif tcpAddr.Zone != \"\" {\n\t\t\tifi, err := net.InterfaceByName(tcpAddr.Zone)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, -1, err\n\t\t\t}\n\t\t\tsa6.ZoneId, err = safeIntToUint32(ifi.Index)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, -1, fmt.Errorf(\"unexpected convert net interface index int to uint32: %w\", err)\n\t\t\t}\n\t\t}\n\t\treturn &sa6, unix.AF_INET6, nil\n\tcase \"tcp\":\n\t\tvar sa6 unix.SockaddrInet6\n\t\tsa6.Port = tcpAddr.Port\n\t\tif tcpAddr.IP == nil {\n\t\t\ttcpAddr.IP = net.IPv4(0, 0, 0, 0)\n\t\t}\n\t\tcopy(sa6.Addr[:], tcpAddr.IP.To16())\n\t\tif tcpAddr.Zone != \"\" {\n\t\t\tifi, err := net.InterfaceByName(tcpAddr.Zone)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, -1, err\n\t\t\t}\n\t\t\tsa6.ZoneId, err = safeIntToUint32(ifi.Index)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, -1, fmt.Errorf(\"unexpected convert net interface index int to uint32: %w\", err)\n\t\t\t}\n\t\t}\n\t\treturn &sa6, unix.AF_INET6, nil\n\tdefault:\n\t\treturn nil, -1, errors.New(\"only tcp, tcp4, or tcp6 is supported \" + network)\n\t}\n}\n\nfunc safeIntToUint32(i int) (uint32, error) {\n\tif i < 0 {\n\t\treturn 0, errors.New(\"value is negative, cannot convert to uint32\")\n\t}\n\tui := uint64(i)\n\tif ui > math.MaxUint32 {\n\t\treturn 0, errors.New(\"value exceeds uint32 max value\")\n\t}\n\treturn uint32(ui), nil\n}\n\nfunc safeIntToUintptr(i int) (uintptr, error) {\n\tif i < 0 {\n\t\treturn 0, errors.New(\"value is negative, cannot convert to uintptr\")\n\t}\n\treturn uintptr(i), nil\n}\n"
  },
  {
    "path": "tcplisten/tcplisten_js_wasm.go",
    "content": "package tcplisten\n\nimport (\n\t\"net\"\n)\n\n// A dummy implementation for js,wasm\ntype Config struct {\n\tReusePort   bool\n\tDeferAccept bool\n\tFastOpen    bool\n\tBacklog     int\n}\n\nfunc (cfg *Config) NewListener(network, addr string) (net.Listener, error) {\n\treturn net.Listen(network, addr)\n}\n"
  },
  {
    "path": "tcplisten/tcplisten_linux.go",
    "content": "//go:build linux\n\npackage tcplisten\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\tsoReusePort = 0x0F\n\ttcpFastOpen = 0x17\n)\n\nfunc enableDeferAccept(fd int) error {\n\tif err := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_DEFER_ACCEPT, 1); err != nil {\n\t\treturn fmt.Errorf(\"cannot enable TCP_DEFER_ACCEPT: %s\", err)\n\t}\n\treturn nil\n}\n\nfunc enableFastOpen(fd int) error {\n\tif err := unix.SetsockoptInt(fd, unix.SOL_TCP, tcpFastOpen, fastOpenQlen); err != nil {\n\t\treturn fmt.Errorf(\"cannot enable TCP_FASTOPEN(qlen=%d): %s\", fastOpenQlen, err)\n\t}\n\treturn nil\n}\n\nconst fastOpenQlen = 16 * 1024\n\nfunc soMaxConn() (int, error) {\n\tdata, err := os.ReadFile(soMaxConnFilePath)\n\tif err != nil {\n\t\t// This error may trigger on travis build. Just use SOMAXCONN\n\t\tif os.IsNotExist(err) {\n\t\t\treturn unix.SOMAXCONN, nil\n\t\t}\n\t\treturn -1, err\n\t}\n\ts := strings.TrimSpace(string(data))\n\tn, err := strconv.Atoi(s)\n\tif err != nil || n <= 0 {\n\t\treturn -1, fmt.Errorf(\"cannot parse somaxconn %q read from %s: %s\", s, soMaxConnFilePath, err)\n\t}\n\n\t// Linux stores the backlog in a uint16.\n\t// Truncate number to avoid wrapping.\n\t// See https://github.com/golang/go/issues/5030 .\n\tif n > 1<<16-1 {\n\t\tn = maxAckBacklog(n)\n\t}\n\treturn n, nil\n}\n\nfunc kernelVersion() (int, int) {\n\tvar uname unix.Utsname\n\tif err := unix.Uname(&uname); err != nil {\n\t\treturn 0, 0\n\t}\n\n\trl := uname.Release\n\tvar values [2]int\n\tvi := 0\n\tvalue := 0\n\tfor _, c := range rl {\n\t\tif c >= '0' && c <= '9' {\n\t\t\tvalue = (value * 10) + int(c-'0')\n\t\t} else {\n\t\t\t// Note that we're assuming N.N.N here.  If we see anything else we are likely to\n\t\t\t// mis-parse it.\n\t\t\tvalues[vi] = value // #nosec G602\n\t\t\tvi++\n\t\t\tif vi >= len(values) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tswitch vi {\n\tcase 0:\n\t\treturn 0, 0\n\tcase 1:\n\t\treturn values[0], 0\n\tcase 2:\n\t\treturn values[0], values[1] // #nosec G602\n\t}\n\treturn 0, 0\n}\n\n// Linux stores the backlog as:\n//\n//   - uint16 in kernel version < 4.1,\n//   - uint32 in kernel version >= 4.1\n//\n// Truncate number to avoid wrapping.\n//\n// See issue 5 or\n// https://github.com/golang/go/issues/5030.\n// https://github.com/golang/go/issues/41470.\nfunc maxAckBacklog(n int) int {\n\tmajor, minor := kernelVersion()\n\tsize := 16\n\tif major > 4 || (major == 4 && minor >= 1) {\n\t\tsize = 32\n\t}\n\n\tu := 1<<size - 1\n\tif n > u {\n\t\tn = u\n\t}\n\treturn n\n}\n\nconst soMaxConnFilePath = \"/proc/sys/net/core/somaxconn\"\n"
  },
  {
    "path": "tcplisten/tcplisten_other.go",
    "content": "//go:build darwin || dragonfly || freebsd || netbsd || openbsd || rumprun || (zos && s390x)\n\npackage tcplisten\n\nimport \"golang.org/x/sys/unix\"\n\nconst soReusePort = unix.SO_REUSEPORT\n\nfunc enableDeferAccept(fd int) error {\n\t// TODO: implement SO_ACCEPTFILTER:dataready here\n\treturn nil\n}\n\nfunc enableFastOpen(fd int) error {\n\t// TODO: implement TCP_FASTOPEN when it will be ready\n\treturn nil\n}\n\nfunc soMaxConn() (int, error) {\n\t// TODO: properly implement it\n\treturn unix.SOMAXCONN, nil\n}\n"
  },
  {
    "path": "tcplisten/tcplisten_test.go",
    "content": "//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || rumprun\n\npackage tcplisten\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestConfigDeferAccept(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skip()\n\t}\n\ttestConfig(t, Config{DeferAccept: true})\n}\n\nfunc TestConfigReusePort(t *testing.T) {\n\ttestConfig(t, Config{ReusePort: true})\n}\n\nfunc TestConfigFastOpen(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skip()\n\t}\n\ttestConfig(t, Config{FastOpen: true})\n}\n\nfunc TestConfigAll(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skip()\n\t}\n\tcfg := Config{\n\t\tReusePort:   true,\n\t\tDeferAccept: true,\n\t\tFastOpen:    true,\n\t}\n\ttestConfig(t, cfg)\n}\n\nfunc TestConfigBacklog(t *testing.T) {\n\tcfg := Config{\n\t\tBacklog: 32,\n\t}\n\ttestConfig(t, cfg)\n}\n\nfunc testConfig(t *testing.T, cfg Config) {\n\ttestConfigV(t, cfg, \"tcp\", \"localhost:10083\")\n\ttestConfigV(t, cfg, \"tcp\", \"[::1]:10083\")\n\ttestConfigV(t, cfg, \"tcp4\", \"localhost:10083\")\n\ttestConfigV(t, cfg, \"tcp6\", \"[::1]:10083\")\n}\n\nfunc testConfigV(t *testing.T, cfg Config, network, addr string) {\n\tconst requestsCount = 1000\n\tserversCount := 1\n\tif cfg.ReusePort {\n\t\tserversCount = 10\n\t}\n\tdoneCh := make(chan struct{}, serversCount)\n\n\tvar lns []net.Listener\n\tfor i := 0; i < serversCount; i++ {\n\t\tln, err := cfg.NewListener(network, addr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"cannot create listener %d using Config %#v: %s\", i, &cfg, err)\n\t\t}\n\t\tgo func() {\n\t\t\tserveEcho(t, ln)\n\t\t\tdoneCh <- struct{}{}\n\t\t}()\n\t\tlns = append(lns, ln)\n\t}\n\n\tfor i := range requestsCount {\n\t\tc, err := net.Dial(network, addr)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%d. unexpected error when dialing: %s\", i, err)\n\t\t}\n\t\treq := fmt.Sprintf(\"request number %d\", i)\n\t\tif _, err = c.Write([]byte(req)); err != nil {\n\t\t\tt.Fatalf(\"%d. unexpected error when writing request: %s\", i, err)\n\t\t}\n\t\tif err = c.(*net.TCPConn).CloseWrite(); err != nil {\n\t\t\tt.Fatalf(\"%d. unexpected error when closing write end of the connection: %s\", i, err)\n\t\t}\n\n\t\tvar resp []byte\n\t\tch := make(chan error)\n\t\tgo func() {\n\t\t\tresp, err = io.ReadAll(c)\n\t\t\tch <- err\n\t\t\tclose(ch)\n\t\t}()\n\t\tselect {\n\t\tcase err := <-ch:\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"%d. unexpected error when reading response: %s\", i, err)\n\t\t\t}\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\tt.Fatalf(\"%d. timeout when waiting for response: %s\", i, err)\n\t\t}\n\n\t\tif string(resp) != req {\n\t\t\tt.Fatalf(\"%d. unexpected response %q. Expecting %q\", i, resp, req)\n\t\t}\n\t\tif err = c.Close(); err != nil {\n\t\t\tt.Fatalf(\"%d. unexpected error when closing connection: %s\", i, err)\n\t\t}\n\t}\n\n\tfor _, ln := range lns {\n\t\tif err := ln.Close(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error when closing listener: %s\", err)\n\t\t}\n\t}\n\n\tfor i := 0; i < serversCount; i++ {\n\t\tselect {\n\t\tcase <-doneCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout when waiting for servers to be closed\")\n\t\t}\n\t}\n}\n\nfunc serveEcho(t *testing.T, ln net.Listener) {\n\tfor {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\treq, err := io.ReadAll(c)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when reading request: %s\", err)\n\t\t}\n\t\tif _, err = c.Write(req); err != nil {\n\t\t\tt.Fatalf(\"unexpected error when writing response: %s\", err)\n\t\t}\n\t\tif err = c.Close(); err != nil {\n\t\t\tt.Fatalf(\"unexpected error when closing connection: %s\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "timer.go",
    "content": "package fasthttp\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nfunc initTimer(t *time.Timer, timeout time.Duration) *time.Timer {\n\tif t == nil {\n\t\treturn time.NewTimer(timeout)\n\t}\n\tif t.Reset(timeout) {\n\t\t// developer sanity-check\n\t\tpanic(\"BUG: active timer trapped into initTimer()\")\n\t}\n\treturn t\n}\n\nfunc stopTimer(t *time.Timer) {\n\tif !t.Stop() {\n\t\t// Collect possibly added time from the channel\n\t\t// if timer has been stopped and nobody collected its value.\n\t\tselect {\n\t\tcase <-t.C:\n\t\tdefault:\n\t\t}\n\t}\n}\n\n// AcquireTimer returns a time.Timer from the pool and updates it to\n// send the current time on its channel after at least timeout.\n//\n// The returned Timer may be returned to the pool with ReleaseTimer\n// when no longer needed. This allows reducing GC load.\nfunc AcquireTimer(timeout time.Duration) *time.Timer {\n\tv := timerPool.Get()\n\tif v == nil {\n\t\treturn time.NewTimer(timeout)\n\t}\n\tt := v.(*time.Timer)\n\tinitTimer(t, timeout)\n\treturn t\n}\n\n// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool\n// and prevents the Timer from firing.\n//\n// Do not access the released time.Timer or read from its channel otherwise\n// data races may occur.\nfunc ReleaseTimer(t *time.Timer) {\n\tstopTimer(t)\n\ttimerPool.Put(t)\n}\n\nvar timerPool sync.Pool\n"
  },
  {
    "path": "tls.go",
    "content": "package fasthttp\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"time\"\n)\n\n// GenerateTestCertificate generates a test certificate and private key based on the given host.\nfunc GenerateTestCertificate(host string) ([]byte, []byte, error) {\n\tpriv, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcert := &x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"fasthttp test\"},\n\t\t},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(365 * 24 * time.Hour),\n\t\tKeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},\n\t\tSignatureAlgorithm:    x509.SHA256WithRSA,\n\t\tDNSNames:              []string{host},\n\t\tBasicConstraintsValid: true,\n\t\tIsCA:                  true,\n\t}\n\n\tcertBytes, err := x509.CreateCertificate(\n\t\trand.Reader, cert, cert, &priv.PublicKey, priv,\n\t)\n\n\tp := pem.EncodeToMemory(\n\t\t&pem.Block{\n\t\t\tType:  \"PRIVATE KEY\",\n\t\t\tBytes: x509.MarshalPKCS1PrivateKey(priv),\n\t\t},\n\t)\n\n\tb := pem.EncodeToMemory(\n\t\t&pem.Block{\n\t\t\tType:  \"CERTIFICATE\",\n\t\t\tBytes: certBytes,\n\t\t},\n\t)\n\n\treturn b, p, err\n}\n"
  },
  {
    "path": "uri.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"sync\"\n)\n\n// AcquireURI returns an empty URI instance from the pool.\n//\n// Release the URI with ReleaseURI after the URI is no longer needed.\n// This allows reducing GC load.\nfunc AcquireURI() *URI {\n\treturn uriPool.Get().(*URI)\n}\n\n// ReleaseURI releases the URI acquired via AcquireURI.\n//\n// The released URI mustn't be used after releasing it, otherwise data races\n// may occur.\nfunc ReleaseURI(u *URI) {\n\tu.Reset()\n\turiPool.Put(u)\n}\n\nvar uriPool = &sync.Pool{\n\tNew: func() any {\n\t\treturn &URI{}\n\t},\n}\n\n// URI represents URI :) .\n//\n// It is forbidden copying URI instances. Create new instance and use CopyTo\n// instead.\n//\n// URI instance MUST NOT be used from concurrently running goroutines.\ntype URI struct {\n\tnoCopy noCopy\n\n\tqueryArgs Args\n\n\tpathOriginal []byte\n\tscheme       []byte\n\tpath         []byte\n\tqueryString  []byte\n\thash         []byte\n\thost         []byte\n\n\tfullURI    []byte\n\trequestURI []byte\n\n\tusername        []byte\n\tpassword        []byte\n\tparsedQueryArgs bool\n\n\t// Path values are sent as-is without normalization.\n\t//\n\t// Disabled path normalization may be useful for proxying incoming requests\n\t// to servers that are expecting paths to be forwarded as-is.\n\t//\n\t// By default path values are normalized, i.e.\n\t// extra slashes are removed, special characters are encoded.\n\tDisablePathNormalizing bool\n}\n\n// CopyTo copies uri contents to dst.\nfunc (u *URI) CopyTo(dst *URI) {\n\tdst.Reset()\n\tdst.pathOriginal = append(dst.pathOriginal, u.pathOriginal...)\n\tdst.scheme = append(dst.scheme, u.scheme...)\n\tdst.path = append(dst.path, u.path...)\n\tdst.queryString = append(dst.queryString, u.queryString...)\n\tdst.hash = append(dst.hash, u.hash...)\n\tdst.host = append(dst.host, u.host...)\n\tdst.username = append(dst.username, u.username...)\n\tdst.password = append(dst.password, u.password...)\n\n\tu.queryArgs.CopyTo(&dst.queryArgs)\n\tdst.parsedQueryArgs = u.parsedQueryArgs\n\tdst.DisablePathNormalizing = u.DisablePathNormalizing\n\n\t// fullURI and requestURI shouldn't be copied, since they are created\n\t// from scratch on each FullURI() and RequestURI() call.\n}\n\n// Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Hash() []byte {\n\treturn u.hash\n}\n\n// SetHash sets URI hash.\nfunc (u *URI) SetHash(hash string) {\n\tu.hash = append(u.hash[:0], hash...)\n}\n\n// SetHashBytes sets URI hash.\nfunc (u *URI) SetHashBytes(hash []byte) {\n\tu.hash = append(u.hash[:0], hash...)\n}\n\n// Username returns URI username\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Username() []byte {\n\treturn u.username\n}\n\n// SetUsername sets URI username.\nfunc (u *URI) SetUsername(username string) {\n\tu.username = append(u.username[:0], username...)\n}\n\n// SetUsernameBytes sets URI username.\nfunc (u *URI) SetUsernameBytes(username []byte) {\n\tu.username = append(u.username[:0], username...)\n}\n\n// Password returns URI password.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Password() []byte {\n\treturn u.password\n}\n\n// SetPassword sets URI password.\nfunc (u *URI) SetPassword(password string) {\n\tu.password = append(u.password[:0], password...)\n}\n\n// SetPasswordBytes sets URI password.\nfunc (u *URI) SetPasswordBytes(password []byte) {\n\tu.password = append(u.password[:0], password...)\n}\n\n// QueryString returns URI query string,\n// i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) QueryString() []byte {\n\treturn u.queryString\n}\n\n// SetQueryString sets URI query string.\nfunc (u *URI) SetQueryString(queryString string) {\n\tu.queryString = append(u.queryString[:0], queryString...)\n\tu.parsedQueryArgs = false\n}\n\n// SetQueryStringBytes sets URI query string.\nfunc (u *URI) SetQueryStringBytes(queryString []byte) {\n\tu.queryString = append(u.queryString[:0], queryString...)\n\tu.parsedQueryArgs = false\n}\n\n// Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// The returned path is always urldecoded and normalized,\n// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Path() []byte {\n\tpath := u.path\n\tif len(path) == 0 {\n\t\tpath = strSlash\n\t}\n\treturn path\n}\n\n// SetPath sets URI path.\nfunc (u *URI) SetPath(path string) {\n\tu.pathOriginal = append(u.pathOriginal[:0], path...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n}\n\n// SetPathBytes sets URI path.\nfunc (u *URI) SetPathBytes(path []byte) {\n\tu.pathOriginal = append(u.pathOriginal[:0], path...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n}\n\n// PathOriginal returns the original path from requestURI passed to URI.Parse().\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) PathOriginal() []byte {\n\treturn u.pathOriginal\n}\n\n// Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// Returned scheme is always lowercased.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Scheme() []byte {\n\tscheme := u.scheme\n\tif len(scheme) == 0 {\n\t\tscheme = strHTTP\n\t}\n\treturn scheme\n}\n\n// SetScheme sets URI scheme, i.e. http, https, ftp, etc.\nfunc (u *URI) SetScheme(scheme string) {\n\tu.scheme = append(u.scheme[:0], scheme...)\n\tlowercaseBytes(u.scheme)\n}\n\n// SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.\nfunc (u *URI) SetSchemeBytes(scheme []byte) {\n\tu.scheme = append(u.scheme[:0], scheme...)\n\tlowercaseBytes(u.scheme)\n}\n\nfunc (u *URI) isHTTPS() bool {\n\treturn bytes.Equal(u.scheme, strHTTPS)\n}\n\nfunc (u *URI) isHTTP() bool {\n\treturn len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)\n}\n\n// Reset clears uri.\nfunc (u *URI) Reset() {\n\tu.pathOriginal = u.pathOriginal[:0]\n\tu.scheme = u.scheme[:0]\n\tu.path = u.path[:0]\n\tu.queryString = u.queryString[:0]\n\tu.hash = u.hash[:0]\n\tu.username = u.username[:0]\n\tu.password = u.password[:0]\n\n\tu.host = u.host[:0]\n\tu.queryArgs.Reset()\n\tu.parsedQueryArgs = false\n\tu.DisablePathNormalizing = false\n\n\t// There is no need in u.fullURI = u.fullURI[:0], since full uri\n\t// is calculated on each call to FullURI().\n\n\t// There is no need in u.requestURI = u.requestURI[:0], since requestURI\n\t// is calculated on each call to RequestURI().\n}\n\n// Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .\n//\n// Host is always lowercased.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) Host() []byte {\n\treturn u.host\n}\n\n// SetHost sets host for the uri.\nfunc (u *URI) SetHost(host string) {\n\tu.host = append(u.host[:0], host...)\n\tlowercaseBytes(u.host)\n}\n\n// SetHostBytes sets host for the uri.\nfunc (u *URI) SetHostBytes(host []byte) {\n\tu.host = append(u.host[:0], host...)\n\tlowercaseBytes(u.host)\n}\n\nvar ErrorInvalidURI = errors.New(\"invalid uri\")\n\n// Parse initializes URI from the given host and uri.\n//\n// host may be nil. In this case uri must contain fully qualified uri,\n// i.e. with scheme and host. http is assumed if scheme is omitted.\n//\n// uri may contain e.g. RequestURI without scheme and host if host is non-empty.\nfunc (u *URI) Parse(host, uri []byte) error {\n\treturn u.parse(host, uri, false)\n}\n\nfunc (u *URI) parse(host, uri []byte, isTLS bool) error {\n\tu.Reset()\n\n\tif stringContainsCTLByte(uri) {\n\t\treturn ErrorInvalidURI\n\t}\n\n\tif len(host) == 0 || bytes.Contains(uri, strColonSlashSlash) {\n\t\tscheme, newHost, newURI := splitHostURI(host, uri)\n\t\tif len(scheme) > 0 && !isValidScheme(scheme) {\n\t\t\treturn fmt.Errorf(\"invalid scheme %q\", scheme)\n\t\t}\n\t\tu.SetSchemeBytes(scheme)\n\t\thost = newHost\n\t\turi = newURI\n\t}\n\n\tif isTLS {\n\t\tu.SetSchemeBytes(strHTTPS)\n\t}\n\n\tif n := bytes.LastIndexByte(host, '@'); n >= 0 {\n\t\tauth := host[:n]\n\t\tif !validUserinfo(auth) {\n\t\t\treturn ErrorInvalidURI\n\t\t}\n\t\thost = host[n+1:]\n\n\t\tif before, after, ok := bytes.Cut(auth, []byte{':'}); ok {\n\t\t\tu.username = append(u.username[:0], before...)\n\t\t\tu.password = append(u.password[:0], after...)\n\t\t} else {\n\t\t\tu.username = append(u.username[:0], auth...)\n\t\t\tu.password = u.password[:0]\n\t\t}\n\t}\n\n\tu.host = append(u.host, host...)\n\tparsedHost, err := parseHost(u.host)\n\tif err != nil {\n\t\treturn err\n\t}\n\tu.host = parsedHost\n\tlowercaseBytes(u.host)\n\n\tb := uri\n\tqueryIndex := bytes.IndexByte(b, '?')\n\tfragmentIndex := bytes.IndexByte(b, '#')\n\t// Ignore query in fragment part\n\tif fragmentIndex >= 0 && queryIndex > fragmentIndex {\n\t\tqueryIndex = -1\n\t}\n\n\tif queryIndex < 0 && fragmentIndex < 0 {\n\t\tu.pathOriginal = append(u.pathOriginal, b...)\n\t\tu.path = normalizePath(u.path, u.pathOriginal)\n\t\treturn nil\n\t}\n\n\tif queryIndex >= 0 {\n\t\t// Path is everything up to the start of the query\n\t\tu.pathOriginal = append(u.pathOriginal, b[:queryIndex]...)\n\t\tu.path = normalizePath(u.path, u.pathOriginal)\n\n\t\tif fragmentIndex < 0 {\n\t\t\tu.queryString = append(u.queryString, b[queryIndex+1:]...)\n\t\t} else {\n\t\t\tu.queryString = append(u.queryString, b[queryIndex+1:fragmentIndex]...)\n\t\t\tu.hash = append(u.hash, b[fragmentIndex+1:]...)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// fragmentIndex >= 0 && queryIndex < 0\n\t// Path is up to the start of fragment\n\tu.pathOriginal = append(u.pathOriginal, b[:fragmentIndex]...)\n\tu.path = normalizePath(u.path, u.pathOriginal)\n\tu.hash = append(u.hash, b[fragmentIndex+1:]...)\n\n\treturn nil\n}\n\nfunc validUserinfo(userinfo []byte) bool {\n\tfor _, c := range userinfo {\n\t\tswitch {\n\t\tcase 'A' <= c && c <= 'Z':\n\t\t\tcontinue\n\t\tcase 'a' <= c && c <= 'z':\n\t\t\tcontinue\n\t\tcase '0' <= c && c <= '9':\n\t\t\tcontinue\n\t\t}\n\t\tswitch c {\n\t\tcase '-', '.', '_', ':', '~', '!', '$', '&', '\\'', '(', ')', '*', '+', ',', ';', '=', '%', '@':\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc isValidScheme(scheme []byte) bool {\n\tif len(scheme) == 0 {\n\t\treturn false\n\t}\n\tfirst := scheme[0]\n\tif (first < 'a' || first > 'z') && (first < 'A' || first > 'Z') {\n\t\treturn false\n\t}\n\tfor i := 1; i < len(scheme); i++ {\n\t\tc := scheme[i]\n\t\tif ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') {\n\t\t\tcontinue\n\t\t}\n\t\tswitch c {\n\t\tcase '+', '-', '.':\n\t\t\tcontinue\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\n// parseHost parses host as an authority without user\n// information. That is, as host[:port].\n//\n// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L619\n//\n// The host is parsed and unescaped in place overwriting the contents of the host parameter.\nfunc parseHost(host []byte) ([]byte, error) {\n\tif len(host) > 0 && host[0] == '[' {\n\t\t// Parse an IP-Literal in RFC 3986 and RFC 6874.\n\t\t// E.g., \"[fe80::1]\", \"[fe80::1%25en0]\", \"[fe80::1]:80\".\n\t\ti := bytes.LastIndexByte(host, ']')\n\t\tif i < 0 {\n\t\t\treturn nil, errors.New(\"missing ']' in host\")\n\t\t}\n\t\tcolonPort := host[i+1:]\n\t\tif !validOptionalPort(colonPort) {\n\t\t\treturn nil, fmt.Errorf(\"invalid port %q after host\", colonPort)\n\t\t}\n\n\t\t// RFC 6874 defines that %25 (%-encoded percent) introduces\n\t\t// the zone identifier, and the zone identifier can use basically\n\t\t// any %-encoding it likes. That's different from the host, which\n\t\t// can only %-encode non-ASCII bytes.\n\t\t// We do impose some restrictions on the zone, to avoid stupidity\n\t\t// like newlines.\n\t\tzone := bytes.Index(host[:i], []byte(\"%25\"))\n\t\tif zone >= 0 {\n\t\t\thost1, err := unescape(host[:zone], encodeHost)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\thost2, err := unescape(host[zone:i], encodeZone)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\thost3, err := unescape(host[i:], encodeHost)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn append(host1, append(host2, host3...)...), nil\n\t\t}\n\t} else {\n\t\tif bytes.ContainsAny(host, \"[]\") {\n\t\t\treturn nil, fmt.Errorf(\"invalid host %q\", host)\n\t\t}\n\n\t\tif i := bytes.LastIndexByte(host, ':'); i != -1 {\n\t\t\tif bytes.IndexByte(host[:i], ':') != -1 {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid host %q with multiple port delimiters\", host)\n\t\t\t}\n\n\t\t\tcolonPort := host[i:]\n\t\t\tif !validOptionalPort(colonPort) {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid port %q after host\", colonPort)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar err error\n\tif host, err = unescape(host, encodeHost); err != nil {\n\t\treturn nil, err\n\t}\n\tif err = validateIPv6Literal(host); err != nil {\n\t\treturn nil, err\n\t}\n\treturn host, nil\n}\n\ntype encoding int\n\nconst (\n\tencodeHost encoding = 1 + iota\n\tencodeZone\n)\n\ntype EscapeError string\n\nfunc (e EscapeError) Error() string {\n\treturn \"invalid URL escape \" + strconv.Quote(string(e))\n}\n\ntype InvalidHostError string\n\nfunc (e InvalidHostError) Error() string {\n\treturn \"invalid character \" + strconv.Quote(string(e)) + \" in host name\"\n}\n\n// unescape unescapes a string; the mode specifies\n// which section of the URL string is being unescaped.\n//\n// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L199\n//\n// Unescapes in place overwriting the contents of s and returning it.\nfunc unescape(s []byte, mode encoding) ([]byte, error) {\n\t// Count %, check that they're well-formed.\n\tn := 0\n\tfor i := 0; i < len(s); {\n\t\tswitch s[i] {\n\t\tcase '%':\n\t\t\tn++\n\t\t\tif i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {\n\t\t\t\ts = s[i:]\n\t\t\t\tif len(s) > 3 {\n\t\t\t\t\ts = s[:3]\n\t\t\t\t}\n\t\t\t\treturn nil, EscapeError(s)\n\t\t\t}\n\t\t\t// Per https://tools.ietf.org/html/rfc3986#page-21\n\t\t\t// in the host component %-encoding can only be used\n\t\t\t// for non-ASCII bytes.\n\t\t\t// But https://tools.ietf.org/html/rfc6874#section-2\n\t\t\t// introduces %25 being allowed to escape a percent sign\n\t\t\t// in IPv6 scoped-address literals. Yay.\n\t\t\tif mode == encodeHost && unhex(s[i+1]) < 8 && !bytes.Equal(s[i:i+3], []byte(\"%25\")) {\n\t\t\t\treturn nil, EscapeError(s[i : i+3])\n\t\t\t}\n\t\t\tif mode == encodeZone {\n\t\t\t\t// RFC 6874 says basically \"anything goes\" for zone identifiers\n\t\t\t\t// and that even non-ASCII can be redundantly escaped,\n\t\t\t\t// but it seems prudent to restrict %-escaped bytes here to those\n\t\t\t\t// that are valid host name bytes in their unescaped form.\n\t\t\t\t// That is, you can use escaping in the zone identifier but not\n\t\t\t\t// to introduce bytes you couldn't just write directly.\n\t\t\t\t// But Windows puts spaces here! Yay.\n\t\t\t\tv := unhex(s[i+1])<<4 | unhex(s[i+2])\n\t\t\t\tif !bytes.Equal(s[i:i+3], []byte(\"%25\")) && v != ' ' && shouldEscape(v, encodeHost) {\n\t\t\t\t\treturn nil, EscapeError(s[i : i+3])\n\t\t\t\t}\n\t\t\t}\n\t\t\ti += 3\n\t\tdefault:\n\t\t\tif (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {\n\t\t\t\treturn nil, InvalidHostError(s[i : i+1])\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t}\n\n\tif n == 0 {\n\t\treturn s, nil\n\t}\n\n\tt := s[:0]\n\tfor i := 0; i < len(s); i++ {\n\t\tswitch s[i] {\n\t\tcase '%':\n\t\t\tt = append(t, unhex(s[i+1])<<4|unhex(s[i+2]))\n\t\t\ti += 2\n\t\tdefault:\n\t\t\tt = append(t, s[i])\n\t\t}\n\t}\n\treturn t, nil\n}\n\n// Return true if the specified character should be escaped when\n// appearing in a URL string, according to RFC 3986.\n//\n// Please be informed that for now shouldEscape does not check all\n// reserved characters correctly. See https://github.com/golang/go/issues/5684.\n//\n// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100\nfunc shouldEscape(c byte, mode encoding) bool {\n\t// §2.3 Unreserved characters (alphanum)\n\tif 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {\n\t\treturn false\n\t}\n\n\tif mode == encodeHost || mode == encodeZone {\n\t\t// §3.2.2 Host allows\n\t\t//\tsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n\t\t// as part of reg-name.\n\t\t// We add : because we include :port as part of host.\n\t\t// We add [ ] because we include [ipv6]:port as part of host.\n\t\t// We add < > because they're the only characters left that\n\t\t// we could possibly allow, and Parse will reject them if we\n\t\t// escape them (because hosts can't use %-encoding for\n\t\t// ASCII bytes).\n\t\tswitch c {\n\t\tcase '!', '$', '&', '\\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '\"':\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif c == '-' || c == '_' || c == '.' || c == '~' { // §2.3 Unreserved characters (mark)\n\t\treturn false\n\t}\n\n\t// Everything else must be escaped.\n\treturn true\n}\n\nfunc ishex(c byte) bool {\n\treturn hex2intTable[c] < 16\n}\n\nfunc unhex(c byte) byte {\n\treturn hex2intTable[c] & 15\n}\n\n// validOptionalPort reports whether port is either an empty string\n// or matches /^:\\d*$/.\nfunc validOptionalPort(port []byte) bool {\n\tif len(port) == 0 {\n\t\treturn true\n\t}\n\tif port[0] != ':' {\n\t\treturn false\n\t}\n\tfor _, b := range port[1:] {\n\t\tif b < '0' || b > '9' {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc normalizePath(dst, src []byte) []byte {\n\tdst = dst[:0]\n\tdst = addLeadingSlash(dst, src)\n\tdst = decodeArgAppendNoPlus(dst, src)\n\n\t// remove duplicate slashes\n\tb := dst\n\tbSize := len(b)\n\tfor {\n\t\tn := bytes.Index(b, strSlashSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tb = b[n:]\n\t\tcopy(b, b[1:])\n\t\tb = b[:len(b)-1]\n\t\tbSize--\n\t}\n\tdst = dst[:bSize]\n\n\t// remove /./ parts\n\tb = dst\n\tfor {\n\t\tn := bytes.Index(b, strSlashDotSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tnn := n + len(strSlashDotSlash) - 1\n\t\tcopy(b[n:], b[nn:])\n\t\tb = b[:len(b)-nn+n]\n\t}\n\n\t// remove /foo/../ parts\n\tfor {\n\t\tn := bytes.Index(b, strSlashDotDotSlash)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tnn := max(bytes.LastIndexByte(b[:n], '/'), 0)\n\t\tn += len(strSlashDotDotSlash) - 1\n\t\tcopy(b[nn:], b[n:])\n\t\tb = b[:len(b)-n+nn]\n\t}\n\n\t// remove trailing /foo/..\n\tn := bytes.LastIndex(b, strSlashDotDot)\n\tif n >= 0 && n+len(strSlashDotDot) == len(b) {\n\t\tnn := bytes.LastIndexByte(b[:n], '/')\n\t\tif nn < 0 {\n\t\t\treturn append(dst[:0], strSlash...)\n\t\t}\n\t\tb = b[:nn+1]\n\t}\n\n\tif filepath.Separator == '\\\\' {\n\t\t// remove \\.\\ parts\n\t\tfor {\n\t\t\tn := bytes.Index(b, strBackSlashDotBackSlash)\n\t\t\tif n < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tnn := n + len(strSlashDotSlash) - 1\n\t\t\tcopy(b[n:], b[nn:])\n\t\t\tb = b[:len(b)-nn+n]\n\t\t}\n\n\t\t// remove /foo/..\\ parts\n\t\tfor {\n\t\t\tn := bytes.Index(b, strSlashDotDotBackSlash)\n\t\t\tif n < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tnn := max(bytes.LastIndexByte(b[:n], '/'), 0)\n\t\t\tnn++\n\t\t\tn += len(strSlashDotDotBackSlash)\n\t\t\tcopy(b[nn:], b[n:])\n\t\t\tb = b[:len(b)-n+nn]\n\t\t}\n\n\t\t// remove /foo\\..\\ parts\n\t\tfor {\n\t\t\tn := bytes.Index(b, strBackSlashDotDotBackSlash)\n\t\t\tif n < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tnn := max(bytes.LastIndexByte(b[:n], '/'), 0)\n\t\t\tn += len(strBackSlashDotDotBackSlash) - 1\n\t\t\tcopy(b[nn:], b[n:])\n\t\t\tb = b[:len(b)-n+nn]\n\t\t}\n\n\t\t// remove trailing \\foo\\..\n\t\tn := bytes.LastIndex(b, strBackSlashDotDot)\n\t\tif n >= 0 && n+len(strSlashDotDot) == len(b) {\n\t\t\tnn := bytes.LastIndexByte(b[:n], '/')\n\t\t\tif nn < 0 {\n\t\t\t\treturn append(dst[:0], strSlash...)\n\t\t\t}\n\t\t\tb = b[:nn+1]\n\t\t}\n\t}\n\n\treturn b\n}\n\n// RequestURI returns RequestURI - i.e. URI without Scheme and Host.\nfunc (u *URI) RequestURI() []byte {\n\tvar dst []byte\n\tif u.DisablePathNormalizing {\n\t\tdst = u.requestURI[:0]\n\t\tdst = append(dst, u.PathOriginal()...)\n\t} else {\n\t\tdst = appendQuotedPath(u.requestURI[:0], u.Path())\n\t}\n\tif u.parsedQueryArgs && u.queryArgs.Len() > 0 {\n\t\tdst = append(dst, '?')\n\t\tdst = u.queryArgs.AppendBytes(dst)\n\t} else if len(u.queryString) > 0 {\n\t\tdst = append(dst, '?')\n\t\tdst = append(dst, u.queryString...)\n\t}\n\tu.requestURI = dst\n\treturn u.requestURI\n}\n\n// LastPathSegment returns the last part of uri path after '/'.\n//\n// Examples:\n//\n//   - For /foo/bar/baz.html path returns baz.html.\n//   - For /foo/bar/ returns empty byte slice.\n//   - For /foobar.js returns foobar.js.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) LastPathSegment() []byte {\n\tpath := u.Path()\n\tn := bytes.LastIndexByte(path, '/')\n\tif n < 0 {\n\t\treturn path\n\t}\n\treturn path[n+1:]\n}\n\n// Update updates uri.\n//\n// The following newURI types are accepted:\n//\n//   - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original\n//     uri is replaced by newURI.\n//   - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case\n//     the original scheme is preserved.\n//   - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part\n//     of the original uri is replaced.\n//   - Relative path, i.e.  xx?yy=abc . In this case the original RequestURI\n//     is updated according to the new relative path.\nfunc (u *URI) Update(newURI string) {\n\tu.UpdateBytes(s2b(newURI))\n}\n\n// UpdateBytes updates uri.\n//\n// The following newURI types are accepted:\n//\n//   - Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original\n//     uri is replaced by newURI.\n//   - Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case\n//     the original scheme is preserved.\n//   - Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part\n//     of the original uri is replaced.\n//   - Relative path, i.e.  xx?yy=abc . In this case the original RequestURI\n//     is updated according to the new relative path.\nfunc (u *URI) UpdateBytes(newURI []byte) {\n\tu.requestURI = u.updateBytes(newURI, u.requestURI)\n}\n\nfunc (u *URI) updateBytes(newURI, buf []byte) []byte {\n\tif len(newURI) == 0 {\n\t\treturn buf\n\t}\n\n\tn := bytes.Index(newURI, strSlashSlash)\n\tif n >= 0 {\n\t\t// absolute uri\n\t\tvar b [32]byte\n\t\tschemeOriginal := b[:0]\n\t\tif len(u.scheme) > 0 {\n\t\t\tschemeOriginal = append([]byte(nil), u.scheme...)\n\t\t}\n\t\tif err := u.Parse(nil, newURI); err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tif len(schemeOriginal) > 0 && len(u.scheme) == 0 {\n\t\t\tu.scheme = append(u.scheme[:0], schemeOriginal...)\n\t\t}\n\t\treturn buf\n\t}\n\n\tif newURI[0] == '/' {\n\t\t// uri without host\n\t\tbuf = u.appendSchemeHost(buf[:0])\n\t\tbuf = append(buf, newURI...)\n\t\tif err := u.Parse(nil, buf); err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn buf\n\t}\n\n\t// relative path\n\tswitch newURI[0] {\n\tcase '?':\n\t\t// query string only update\n\t\tu.SetQueryStringBytes(newURI[1:])\n\t\treturn append(buf[:0], u.FullURI()...)\n\tcase '#':\n\t\t// update only hash\n\t\tu.SetHashBytes(newURI[1:])\n\t\treturn append(buf[:0], u.FullURI()...)\n\tdefault:\n\t\t// update the last path part after the slash\n\t\tpath := u.Path()\n\t\tn = bytes.LastIndexByte(path, '/')\n\t\tif n < 0 {\n\t\t\tpanic(fmt.Sprintf(\"BUG: path must contain at least one slash: %q %q\", u.Path(), newURI))\n\t\t}\n\t\tbuf = u.appendSchemeHost(buf[:0])\n\t\tbuf = appendQuotedPath(buf, path[:n+1])\n\t\tbuf = append(buf, newURI...)\n\t\tif err := u.Parse(nil, buf); err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn buf\n\t}\n}\n\n// FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.\n//\n// The returned bytes are valid until the next URI method call.\nfunc (u *URI) FullURI() []byte {\n\tu.fullURI = u.AppendBytes(u.fullURI[:0])\n\treturn u.fullURI\n}\n\n// AppendBytes appends full uri to dst and returns the extended dst.\nfunc (u *URI) AppendBytes(dst []byte) []byte {\n\tdst = u.appendSchemeHost(dst)\n\tdst = append(dst, u.RequestURI()...)\n\tif len(u.hash) > 0 {\n\t\tdst = append(dst, '#')\n\t\tdst = append(dst, u.hash...)\n\t}\n\treturn dst\n}\n\nfunc (u *URI) appendSchemeHost(dst []byte) []byte {\n\tdst = append(dst, u.Scheme()...)\n\tdst = append(dst, strColonSlashSlash...)\n\treturn append(dst, u.Host()...)\n}\n\n// WriteTo writes full uri to w.\n//\n// WriteTo implements io.WriterTo interface.\nfunc (u *URI) WriteTo(w io.Writer) (int64, error) {\n\tn, err := w.Write(u.FullURI())\n\treturn int64(n), err\n}\n\n// String returns full uri.\nfunc (u *URI) String() string {\n\treturn string(u.FullURI())\n}\n\nfunc splitHostURI(host, uri []byte) ([]byte, []byte, []byte) {\n\tn := bytes.Index(uri, strSlashSlash)\n\tif n < 0 {\n\t\treturn strHTTP, host, uri\n\t}\n\tscheme := uri[:n]\n\tif bytes.IndexByte(scheme, '/') >= 0 {\n\t\treturn strHTTP, host, uri\n\t}\n\tif len(scheme) > 0 && scheme[len(scheme)-1] == ':' {\n\t\tscheme = scheme[:len(scheme)-1]\n\t}\n\tn += len(strSlashSlash)\n\turi = uri[n:]\n\tn = bytes.IndexByte(uri, '/')\n\tnq := bytes.IndexByte(uri, '?')\n\tif nq >= 0 && (n < 0 || nq < n) {\n\t\t// A hack for urls like foobar.com?a=b/xyz\n\t\tn = nq\n\t}\n\tnh := bytes.IndexByte(uri, '#')\n\tif nh >= 0 && (n < 0 || nh < n) {\n\t\t// A hack for urls like foobar.com#abc.com\n\t\tn = nh\n\t}\n\tif n < 0 {\n\t\treturn scheme, uri, strSlash\n\t}\n\treturn scheme, uri[:n], uri[n:]\n}\n\n// QueryArgs returns query args.\n//\n// The returned args are valid until the next URI method call.\nfunc (u *URI) QueryArgs() *Args {\n\tu.parseQueryArgs()\n\treturn &u.queryArgs\n}\n\nfunc (u *URI) parseQueryArgs() {\n\tif u.parsedQueryArgs {\n\t\treturn\n\t}\n\tu.queryArgs.ParseBytes(u.queryString)\n\tu.parsedQueryArgs = true\n}\n\n// stringContainsCTLByte reports whether s contains any ASCII control character.\nfunc stringContainsCTLByte(s []byte) bool {\n\tfor i := range s {\n\t\tb := s[i]\n\t\tif b < ' ' || b == 0x7f {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "uri_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestURICopyToQueryArgs(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\ta := u.QueryArgs()\n\ta.Set(\"foo\", \"bar\")\n\n\tvar u1 URI\n\tu.CopyTo(&u1)\n\ta1 := u1.QueryArgs()\n\n\tif string(a1.Peek(\"foo\")) != \"bar\" {\n\t\tt.Fatalf(\"unexpected query args value %q. Expecting %q\", a1.Peek(\"foo\"), \"bar\")\n\t}\n}\n\nfunc TestURIAcquireReleaseSequential(t *testing.T) {\n\tt.Parallel()\n\n\ttestURIAcquireRelease(t)\n}\n\nfunc TestURIAcquireReleaseConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan struct{}, 10)\n\tfor range 10 {\n\t\tgo func() {\n\t\t\ttestURIAcquireRelease(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\n\tfor range 10 {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testURIAcquireRelease(t *testing.T) {\n\tfor i := range 10 {\n\t\tu := AcquireURI()\n\t\thost := fmt.Sprintf(\"host.%d.com\", i*23)\n\t\tpath := fmt.Sprintf(\"/foo/%d/bar\", i*17)\n\t\tqueryArgs := \"?foo=bar&baz=aass\"\n\t\tu.Parse([]byte(host), []byte(path+queryArgs)) //nolint:errcheck\n\t\tif string(u.Host()) != host {\n\t\t\tt.Fatalf(\"unexpected host %q. Expecting %q\", u.Host(), host)\n\t\t}\n\t\tif string(u.Path()) != path {\n\t\t\tt.Fatalf(\"unexpected path %q. Expecting %q\", u.Path(), path)\n\t\t}\n\t\tReleaseURI(u)\n\t}\n}\n\nfunc TestURILastPathSegment(t *testing.T) {\n\tt.Parallel()\n\n\ttestURILastPathSegment(t, \"\", \"\")\n\ttestURILastPathSegment(t, \"/\", \"\")\n\ttestURILastPathSegment(t, \"/foo/bar/\", \"\")\n\ttestURILastPathSegment(t, \"/foobar.js\", \"foobar.js\")\n\ttestURILastPathSegment(t, \"/foo/bar/baz.html\", \"baz.html\")\n}\n\nfunc testURILastPathSegment(t *testing.T, path, expectedSegment string) {\n\tvar u URI\n\tu.SetPath(path)\n\tsegment := u.LastPathSegment()\n\tif string(segment) != expectedSegment {\n\t\tt.Fatalf(\"unexpected last path segment for path %q: %q. Expecting %q\", path, segment, expectedSegment)\n\t}\n}\n\nfunc TestURIPathEscape(t *testing.T) {\n\tt.Parallel()\n\n\ttestURIPathEscape(t, \"/foo/bar\", \"/foo/bar\")\n\ttestURIPathEscape(t, \"/f_o-o=b:ar,b.c&q\", \"/f_o-o=b:ar,b.c&q\")\n\ttestURIPathEscape(t, \"/aa?bb.тест~qq\", \"/aa%3Fbb.%D1%82%D0%B5%D1%81%D1%82~qq\")\n}\n\nfunc testURIPathEscape(t *testing.T, path, expectedRequestURI string) {\n\tvar u URI\n\tu.SetPath(path)\n\trequestURI := u.RequestURI()\n\tif string(requestURI) != expectedRequestURI {\n\t\tt.Fatalf(\"unexpected requestURI %q. Expecting %q. path %q\", requestURI, expectedRequestURI, path)\n\t}\n}\n\nfunc TestURIRejectInvalidIPv6(t *testing.T) {\n\tt.Parallel()\n\n\tfor _, raw := range []string{\n\t\t\"http://[0:0::vulndetector.com]:80\",\n\t\t\"http://[2001:db8::vulndetector.com]/\",\n\t\t\"http://[vulndetector.com]/\",\n\t\t\"http://[::ffff:192.0.2.300]/\",\n\t} {\n\t\tvar u URI\n\t\tif err := u.Parse(nil, []byte(raw)); err == nil {\n\t\t\tt.Errorf(\"expected Parse to fail for %q\", raw)\n\t\t}\n\t}\n\n\tfor _, raw := range []string{\n\t\t\"http://[2001:db8::1]/\",\n\t\t\"http://[fe80::1%25en0]/\",\n\t\t\"http://[::ffff:192.0.2.1]/\",\n\t} {\n\t\tvar u URI\n\t\tif err := u.Parse(nil, []byte(raw)); err != nil {\n\t\t\tt.Errorf(\"unexpected error for %q: %v\", raw, err)\n\t\t}\n\t}\n}\n\nfunc TestURIRejectInvalidScheme(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tif err := u.Parse(nil, []byte(\"https>://vulndetector.com/path\")); err == nil {\n\t\tt.Fatalf(\"expected invalid scheme error, got nil\")\n\t}\n\n\tvar relative URI\n\tif err := relative.Parse(nil, []byte(\"/relative\")); err != nil {\n\t\tt.Fatalf(\"unexpected error for relative path: %v\", err)\n\t}\n}\n\nfunc TestURIRejectMultiplePorts(t *testing.T) {\n\tt.Parallel()\n\n\ttestcases := []string{\n\t\t\"http://192.168.1.1:1111:2222/\",\n\t\t\"http://example.com:80:8080/\",\n\t}\n\n\tfor _, raw := range testcases {\n\t\tvar u URI\n\t\tif err := u.Parse(nil, []byte(raw)); err == nil {\n\t\t\tt.Fatalf(\"expected Parse to fail for %q\", raw)\n\t\t}\n\t}\n\n\tvar valid URI\n\tif err := valid.Parse(nil, []byte(\"http://192.168.1.1:1111/\")); err != nil {\n\t\tt.Fatalf(\"unexpected error for valid uri: %v\", err)\n\t}\n}\n\nfunc TestURIUpdate(t *testing.T) {\n\tt.Parallel()\n\n\t// full uri\n\ttestURIUpdate(t, \"http://example.net/dir/path1.html?param1=val1#fragment1\", \"https://example.com/dir/path2.html\", \"https://example.com/dir/path2.html\")\n\n\t// empty uri\n\ttestURIUpdate(t, \"http://example.com/dir/path1.html?param1=val1#fragment1\", \"\", \"http://example.com/dir/path1.html?param1=val1#fragment1\")\n\n\t// request uri\n\ttestURIUpdate(t, \"http://example.com/dir/path1.html?param1=val1#fragment1\", \"/dir/path2.html?param2=val2#fragment2\", \"http://example.com/dir/path2.html?param2=val2#fragment2\")\n\n\t// schema\n\ttestURIUpdate(t, \"http://example.com/dir/path1.html?param1=val1#fragment1\", \"https://example.com/dir/path1.html?param1=val1#fragment1\", \"https://example.com/dir/path1.html?param1=val1#fragment1\")\n\n\t// relative uri\n\ttestURIUpdate(t, \"http://example.com/baz/xxx.html?aaa=22#aaa\", \"bb.html?xx=12#pp\", \"http://example.com/baz/bb.html?xx=12#pp\")\n\n\ttestURIUpdate(t, \"http://example.com/aaa.html?foo=bar\", \"?baz=434&aaa#xcv\", \"http://example.com/aaa.html?baz=434&aaa#xcv\")\n\ttestURIUpdate(t, \"http://example.com/baz\", \"~a/%20b=c,тест?йцу=ке\", \"http://example.com/~a/%20b=c,%D1%82%D0%B5%D1%81%D1%82?йцу=ке\")\n\ttestURIUpdate(t, \"http://example.com/baz\", \"/qwe#fragment\", \"http://example.com/qwe#fragment\")\n\ttestURIUpdate(t, \"http://example.com/baz/xxx\", \"aaa.html#bb?cc=dd&ee=dfd\", \"http://example.com/baz/aaa.html#bb?cc=dd&ee=dfd\")\n\n\tif runtime.GOOS != \"windows\" {\n\t\ttestURIUpdate(t, \"http://example.com/a/b/c/d\", \"../qwe/p?zx=34\", \"http://example.com/a/b/qwe/p?zx=34\")\n\t}\n\n\t// hash\n\ttestURIUpdate(t, \"http://example.com/#fragment1\", \"#fragment2\", \"http://example.com/#fragment2\")\n\n\t// uri without scheme\n\ttestURIUpdate(t, \"https://example.net/dir/path1.html\", \"//example.com/dir/path2.html\", \"https://example.com/dir/path2.html\")\n\ttestURIUpdate(t, \"http://example.net/dir/path1.html\", \"//example.com/dir/path2.html\", \"http://example.com/dir/path2.html\")\n\t// host with port\n\ttestURIUpdate(t, \"http://example.net/\", \"//example.com:8080/\", \"http://example.com:8080/\")\n}\n\nfunc TestURIRejectsMixedBracketHost(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []string{\n\t\t\"http://127.0.0.1[192.168.0.1]/\",\n\t\t\"http://example.com[fd00::1]/\",\n\t}\n\n\tfor _, raw := range tests {\n\t\tvar u URI\n\t\tif err := u.Parse(nil, []byte(raw)); err == nil {\n\t\t\tt.Fatalf(\"expected error for %q\", raw)\n\t\t}\n\t}\n}\n\nfunc testURIUpdate(t *testing.T, base, update, result string) {\n\tvar u URI\n\tu.Parse(nil, []byte(base)) //nolint:errcheck\n\tu.Update(update)\n\ts := u.String()\n\tif s != result {\n\t\tt.Fatalf(\"unexpected result %q. Expecting %q. base=%q, update=%q\", s, result, base, update)\n\t}\n}\n\nfunc TestURIRejectInvalidUserinfo(t *testing.T) {\n\tt.Parallel()\n\n\tbad := []string{\n\t\t\"http://[normal.com@]vulndetector.com/\",\n\t\t\"http://normal.com[user@vulndetector].com/\",\n\t\t\"http://normal.com[@]vulndetector.com/\",\n\t}\n\n\tfor _, raw := range bad {\n\t\tvar u URI\n\t\tif err := u.Parse(nil, []byte(raw)); err == nil {\n\t\t\tt.Fatalf(\"expected error parsing %q\", raw)\n\t\t}\n\t}\n}\n\nfunc TestURIAllowAtInUserinfo(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tif err := u.Parse(nil, []byte(\"http://user:p@ss@example.com/\")); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif got := string(u.Host()); got != \"example.com\" {\n\t\tt.Fatalf(\"unexpected host %q\", got)\n\t}\n\n\tif got := string(u.Username()); got != \"user\" {\n\t\tt.Fatalf(\"unexpected username %q\", got)\n\t}\n\n\tif got := string(u.Password()); got != \"p@ss\" {\n\t\tt.Fatalf(\"unexpected password %q\", got)\n\t}\n}\n\nfunc TestURIPathNormalize(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.SkipNow()\n\t}\n\n\tt.Parallel()\n\n\tvar u URI\n\n\t// double slash\n\ttestURIPathNormalize(t, &u, \"/aa//bb\", \"/aa/bb\")\n\n\t// triple slash\n\ttestURIPathNormalize(t, &u, \"/x///y/\", \"/x/y/\")\n\n\t// multi slashes\n\ttestURIPathNormalize(t, &u, \"/abc//de///fg////\", \"/abc/de/fg/\")\n\n\t// encoded slashes\n\ttestURIPathNormalize(t, &u, \"/xxxx%2fyyy%2f%2F%2F\", \"/xxxx/yyy/\")\n\n\t// dotdot\n\ttestURIPathNormalize(t, &u, \"/aaa/..\", \"/\")\n\n\t// dotdot with trailing slash\n\ttestURIPathNormalize(t, &u, \"/xxx/yyy/../\", \"/xxx/\")\n\n\t// multi dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa/bbb/ccc/../../ddd\", \"/aaa/ddd\")\n\n\t// dotdots separated by other data\n\ttestURIPathNormalize(t, &u, \"/a/b/../c/d/../e/..\", \"/a/c/\")\n\n\t// too many dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa/../../../../xxx\", \"/xxx\")\n\ttestURIPathNormalize(t, &u, \"/../../../../../..\", \"/\")\n\ttestURIPathNormalize(t, &u, \"/../../../../../../\", \"/\")\n\n\t// encoded dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa%2Fbbb%2F%2E.%2Fxxx\", \"/aaa/xxx\")\n\n\t// double slash with dotdots\n\ttestURIPathNormalize(t, &u, \"/aaa////..//b\", \"/b\")\n\n\t// fake dotdot\n\ttestURIPathNormalize(t, &u, \"/aaa/..bbb/ccc/..\", \"/aaa/..bbb/\")\n\n\t// single dot\n\ttestURIPathNormalize(t, &u, \"/a/./b/././c/./d.html\", \"/a/b/c/d.html\")\n\ttestURIPathNormalize(t, &u, \"./foo/\", \"/foo/\")\n\ttestURIPathNormalize(t, &u, \"./../.././../../aaa/bbb/../../../././../\", \"/\")\n\ttestURIPathNormalize(t, &u, \"./a/./.././../b/./foo.html\", \"/b/foo.html\")\n}\n\nfunc testURIPathNormalize(t *testing.T, u *URI, requestURI, expectedPath string) {\n\tu.Parse(nil, []byte(requestURI)) //nolint:errcheck\n\tif string(u.Path()) != expectedPath {\n\t\tt.Fatalf(\"Unexpected path %q. Expected %q. requestURI=%q\", u.Path(), expectedPath, requestURI)\n\t}\n}\n\nfunc TestURINoNormalization(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tirregularPath := \"/aaa%2Fbbb%2F%2E.%2Fxxx\"\n\tu.Parse(nil, []byte(irregularPath)) //nolint:errcheck\n\tu.DisablePathNormalizing = true\n\tif string(u.RequestURI()) != irregularPath {\n\t\tt.Fatalf(\"Unexpected path %q. Expected %q.\", u.Path(), irregularPath)\n\t}\n}\n\nfunc TestURICopyTo(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tvar copyU URI\n\tu.CopyTo(&copyU)\n\tif !reflect.DeepEqual(&u, &copyU) {\n\t\tt.Fatalf(\"URICopyTo fail, u: \\n%+v\\ncopyu: \\n%+v\\n\", &u, &copyU)\n\t}\n\n\tu.UpdateBytes([]byte(\"https://example.com/foo?bar=baz&baraz#qqqq\"))\n\tu.CopyTo(&copyU)\n\tif !reflect.DeepEqual(&u, &copyU) {\n\t\tt.Fatalf(\"URICopyTo fail, u: \\n%+v\\ncopyu: \\n%+v\\n\", &u, &copyU)\n\t}\n}\n\nfunc TestURIFullURI(t *testing.T) {\n\tt.Parallel()\n\n\tvar args Args\n\n\t// empty scheme, path and hash\n\ttestURIFullURI(t, \"\", \"example.com\", \"\", \"\", &args, \"http://example.com/\")\n\n\t// empty scheme and hash\n\ttestURIFullURI(t, \"\", \"example.com\", \"/foo/bar\", \"\", &args, \"http://example.com/foo/bar\")\n\n\t// empty hash\n\ttestURIFullURI(t, \"fTP\", \"example.com\", \"/foo\", \"\", &args, \"ftp://example.com/foo\")\n\n\t// empty args\n\ttestURIFullURI(t, \"https\", \"example.com\", \"/\", \"aaa\", &args, \"https://example.com/#aaa\")\n\n\t// non-empty args and non-ASCII path\n\targs.Set(\"foo\", \"bar\")\n\targs.Set(\"xxx\", \"йух\")\n\ttestURIFullURI(t, \"\", \"example.com\", \"/тест123\", \"2er\", &args, \"http://example.com/%D1%82%D0%B5%D1%81%D1%82123?foo=bar&xxx=%D0%B9%D1%83%D1%85#2er\")\n\n\t// test with empty args and non-empty query string\n\tvar u URI\n\tu.Parse([]byte(\"example.com\"), []byte(\"/foo?bar=baz&baraz#qqqq\")) //nolint:errcheck\n\turi := u.FullURI()\n\texpectedURI := \"http://example.com/foo?bar=baz&baraz#qqqq\"\n\tif string(uri) != expectedURI {\n\t\tt.Fatalf(\"Unexpected URI: %q. Expected %q\", uri, expectedURI)\n\t}\n}\n\nfunc testURIFullURI(t *testing.T, scheme, host, path, hash string, args *Args, expectedURI string) {\n\tvar u URI\n\n\tu.SetScheme(scheme)\n\tu.SetHost(host)\n\tu.SetPath(path)\n\tu.SetHash(hash)\n\targs.CopyTo(u.QueryArgs())\n\n\turi := u.FullURI()\n\tif string(uri) != expectedURI {\n\t\tt.Fatalf(\"Unexpected URI: %q. Expected %q\", uri, expectedURI)\n\t}\n}\n\nfunc TestURIParseNilHost(t *testing.T) {\n\tt.Parallel()\n\n\ttestURIParseScheme(t, \"http://example.com/foo?bar#baz\", \"http\", \"example.com\", \"/foo?bar\", \"baz\")\n\ttestURIParseScheme(t, \"HTtP://example.com/\", \"http\", \"example.com\", \"/\", \"\")\n\ttestURIParseScheme(t, \"://example.com/xyz\", \"http\", \"example.com\", \"/xyz\", \"\")\n\ttestURIParseScheme(t, \"//example.com/foobar\", \"http\", \"example.com\", \"/foobar\", \"\")\n\ttestURIParseScheme(t, \"fTP://example.com\", \"ftp\", \"example.com\", \"/\", \"\")\n\ttestURIParseScheme(t, \"httPS://example.com\", \"https\", \"example.com\", \"/\", \"\")\n\n\t// missing slash after hostname\n\ttestURIParseScheme(t, \"http://example.com?baz=111\", \"http\", \"example.com\", \"/?baz=111\", \"\")\n\n\t// slash in args\n\ttestURIParseScheme(t, \"http://example.com?baz=111/222/xyz\", \"http\", \"example.com\", \"/?baz=111/222/xyz\", \"\")\n\ttestURIParseScheme(t, \"http://example.com?111/222/xyz\", \"http\", \"example.com\", \"/?111/222/xyz\", \"\")\n}\n\nfunc testURIParseScheme(t *testing.T, uri, expectedScheme, expectedHost, expectedRequestURI, expectedHash string) {\n\tvar u URI\n\tu.Parse(nil, []byte(uri)) //nolint:errcheck\n\tif string(u.Scheme()) != expectedScheme {\n\t\tt.Fatalf(\"Unexpected scheme %q. Expecting %q for uri %q\", u.Scheme(), expectedScheme, uri)\n\t}\n\tif string(u.Host()) != expectedHost {\n\t\tt.Fatalf(\"Unexpected host %q. Expecting %q for uri %q\", u.Host(), expectedHost, uri)\n\t}\n\tif string(u.RequestURI()) != expectedRequestURI {\n\t\tt.Fatalf(\"Unexpected requestURI %q. Expecting %q for uri %q\", u.RequestURI(), expectedRequestURI, uri)\n\t}\n\tif string(u.hash) != expectedHash {\n\t\tt.Fatalf(\"Unexpected hash %q. Expecting %q for uri %q\", u.hash, expectedHash, uri)\n\t}\n}\n\nfunc TestIsHttp(t *testing.T) {\n\tvar u URI\n\tif !u.isHTTP() || u.isHTTPS() {\n\t\tt.Fatalf(\"http scheme is assumed by default and not https\")\n\t}\n\tu.SetSchemeBytes([]byte{})\n\tif !u.isHTTP() || u.isHTTPS() {\n\t\tt.Fatalf(\"empty scheme must be threaten as http and not https\")\n\t}\n\tu.SetScheme(\"http\")\n\tif !u.isHTTP() || u.isHTTPS() {\n\t\tt.Fatalf(\"scheme must be threaten as http and not https\")\n\t}\n\tu.SetScheme(\"https\")\n\tif !u.isHTTPS() || u.isHTTP() {\n\t\tt.Fatalf(\"scheme must be threaten as https and not http\")\n\t}\n\tu.SetScheme(\"dav\")\n\tif u.isHTTPS() || u.isHTTP() {\n\t\tt.Fatalf(\"scheme must be threaten as not http and not https\")\n\t}\n}\n\nfunc TestURIParse(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\n\t// no args\n\ttestURIParse(t, &u, \"example.com\", \"sdfdsf\",\n\t\t\"http://example.com/sdfdsf\", \"example.com\", \"/sdfdsf\", \"sdfdsf\", \"\", \"\")\n\n\t// args\n\ttestURIParse(t, &u, \"example.com\", \"/aa?ss\",\n\t\t\"http://example.com/aa?ss\", \"example.com\", \"/aa\", \"/aa\", \"ss\", \"\")\n\n\t// args and hash\n\ttestURIParse(t, &u, \"example.com\", \"/a.b.c?def=gkl#mnop\",\n\t\t\"http://example.com/a.b.c?def=gkl#mnop\", \"example.com\", \"/a.b.c\", \"/a.b.c\", \"def=gkl\", \"mnop\")\n\n\t// '?' and '#' in hash\n\ttestURIParse(t, &u, \"example.com\", \"/foo#bar?baz=aaa#bbb\",\n\t\t\"http://example.com/foo#bar?baz=aaa#bbb\", \"example.com\", \"/foo\", \"/foo\", \"\", \"bar?baz=aaa#bbb\")\n\n\t// encoded path\n\ttestURIParse(t, &u, \"example.com\", \"/Test%20+%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf\",\n\t\t\"http://example.com/Test%20+%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf\", \"example.com\", \"/Test + при\", \"/Test%20+%20%D0%BF%D1%80%D0%B8\", \"asdf=%20%20&s=12\", \"sdf\")\n\n\t// host in uppercase\n\ttestURIParse(t, &u, \"example.com\", \"/bC?De=F#Gh\",\n\t\t\"http://example.com/bC?De=F#Gh\", \"example.com\", \"/bC\", \"/bC\", \"De=F\", \"Gh\")\n\n\t// uri with hostname\n\ttestURIParse(t, &u, \"example.com\", \"http://example.com/foo/bar?baz=aaa#ddd\",\n\t\t\"http://example.com/foo/bar?baz=aaa#ddd\", \"example.com\", \"/foo/bar\", \"/foo/bar\", \"baz=aaa\", \"ddd\")\n\ttestURIParse(t, &u, \"example.net\", \"https://example.com/f/b%20r?baz=aaa#ddd\",\n\t\t\"https://example.com/f/b%20r?baz=aaa#ddd\", \"example.com\", \"/f/b r\", \"/f/b%20r\", \"baz=aaa\", \"ddd\")\n\n\t// no slash after hostname in uri\n\ttestURIParse(t, &u, \"example.com\", \"http://example.com\",\n\t\t\"http://example.com/\", \"example.com\", \"/\", \"/\", \"\", \"\")\n\n\t// uppercase hostname in uri\n\ttestURIParse(t, &u, \"example.net\", \"http://EXAMPLE.COM/aaa\",\n\t\t\"http://example.com/aaa\", \"example.com\", \"/aaa\", \"/aaa\", \"\", \"\")\n\n\t// http:// in query params\n\ttestURIParse(t, &u, \"example.com\", \"/foo?bar=http://example.org\",\n\t\t\"http://example.com/foo?bar=http://example.org\", \"example.com\", \"/foo\", \"/foo\", \"bar=http://example.org\", \"\")\n\n\ttestURIParse(t, &u, \"example.com\", \"//relative\",\n\t\t\"http://example.com/relative\", \"example.com\", \"/relative\", \"//relative\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"//example.com//absolute\",\n\t\t\"http://example.com/absolute\", \"example.com\", \"/absolute\", \"//absolute\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"//example.com\\r\\n\\r\\nGET x\",\n\t\t\"http:///\", \"\", \"/\", \"\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"http://[fe80::1%25en0]/\",\n\t\t\"http://[fe80::1%en0]/\", \"[fe80::1%en0]\", \"/\", \"/\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"http://[fe80::1%25en0]:8080/\",\n\t\t\"http://[fe80::1%en0]:8080/\", \"[fe80::1%en0]:8080\", \"/\", \"/\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"http://hello.世界.com/foo\",\n\t\t\"http://hello.世界.com/foo\", \"hello.世界.com\", \"/foo\", \"/foo\", \"\", \"\")\n\n\ttestURIParse(t, &u, \"\", \"http://hello.%e4%b8%96%e7%95%8c.com/foo\",\n\t\t\"http://hello.世界.com/foo\", \"hello.世界.com\", \"/foo\", \"/foo\", \"\", \"\")\n}\n\nfunc testURIParse(t *testing.T, u *URI, host, uri,\n\texpectedURI, expectedHost, expectedPath, expectedPathOriginal, expectedArgs, expectedHash string,\n) {\n\tu.Parse([]byte(host), []byte(uri)) //nolint:errcheck\n\n\tif !bytes.Equal(u.FullURI(), []byte(expectedURI)) {\n\t\tt.Fatalf(\"Unexpected uri %q. Expected %q. host=%q, uri=%q\", u.FullURI(), expectedURI, host, uri)\n\t}\n\tif !bytes.Equal(u.Host(), []byte(expectedHost)) {\n\t\tt.Fatalf(\"Unexpected host %q. Expected %q. host=%q, uri=%q\", u.Host(), expectedHost, host, uri)\n\t}\n\tif !bytes.Equal(u.PathOriginal(), []byte(expectedPathOriginal)) {\n\t\tt.Fatalf(\"Unexpected original path %q. Expected %q. host=%q, uri=%q\", u.PathOriginal(), expectedPathOriginal, host, uri)\n\t}\n\tif !bytes.Equal(u.Path(), []byte(expectedPath)) {\n\t\tt.Fatalf(\"Unexpected path %q. Expected %q. host=%q, uri=%q\", u.Path(), expectedPath, host, uri)\n\t}\n\tif !bytes.Equal(u.QueryString(), []byte(expectedArgs)) {\n\t\tt.Fatalf(\"Unexpected args %q. Expected %q. host=%q, uri=%q\", u.QueryString(), expectedArgs, host, uri)\n\t}\n\tif !bytes.Equal(u.Hash(), []byte(expectedHash)) {\n\t\tt.Fatalf(\"Unexpected hash %q. Expected %q. host=%q, uri=%q\", u.Hash(), expectedHash, host, uri)\n\t}\n}\n\nfunc TestURIWithQuerystringOverride(t *testing.T) {\n\tt.Parallel()\n\n\tvar u URI\n\tu.SetQueryString(\"q1=foo&q2=bar\")\n\tu.QueryArgs().Add(\"q3\", \"baz\")\n\tu.SetQueryString(\"q1=foo&q2=bar&q4=quux\")\n\turiString := string(u.RequestURI())\n\n\tif uriString != \"/?q1=foo&q2=bar&q4=quux\" {\n\t\tt.Fatalf(\"Expected Querystring to be overridden but was %q \", uriString)\n\t}\n}\n\nfunc TestInvalidUrl(t *testing.T) {\n\turl := `https://.çèéà@&~!&:=\\\\/\\\"'~<>|+-*()[]{}%$;,¥&&$22|||<>< 4ly8lzjmoNx233AXELDtyaFQiiUH-fd8c-CnXUJVYnGIs4Uwr-bptom5GCnWtsGMQxeM2ZhoKE973eKgs2Sjh6RePnyaLpCi6SiNSLevcMoraARrp88L-SgtKqd-XHAtSI8hiPRiXPQmDIA4BGhSgoc0nfn1PoYuGKKmDcZ04tANRc3iz4aF4-A1UrO8bLHTH7MEJvzx.someqa.fr/A/?&QS_BEGIN<&8{b'Ob=p*f> QS_END`\n\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\tif err := u.Parse(nil, []byte(url)); err == nil {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestNoOverwriteInput(t *testing.T) {\n\tstr := `//%AA`\n\turl := []byte(str)\n\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\tif err := u.Parse(nil, url); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif string(url) != str {\n\t\tt.Error()\n\t}\n\n\tif u.String() != \"http://\\xaa/\" {\n\t\tt.Errorf(\"%q\", u.String())\n\t}\n}\n\nfunc TestFragmentInHost(t *testing.T) {\n\turl := \"http://google.com#@github.com\"\n\tu := AcquireURI()\n\tdefer ReleaseURI(u)\n\n\tif err := u.Parse(nil, []byte(url)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got := string(u.Host()); got != \"google.com\" {\n\t\tt.Fatalf(\"Unexpected host %q. Expected %q\", got, \"google.com\")\n\t}\n}\n"
  },
  {
    "path": "uri_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"testing\"\n)\n\nfunc BenchmarkURIParsePath(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar\")\n}\n\nfunc BenchmarkURIParsePathQueryString(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar?query=string&other=value\")\n}\n\nfunc BenchmarkURIParsePathQueryStringHash(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"/foo/bar?query=string&other=value#hashstring\")\n}\n\nfunc BenchmarkURIParseHostname(b *testing.B) {\n\tbenchmarkURIParse(b, \"google.com\", \"http://foobar.com/foo/bar?query=string&other=value#hashstring\")\n}\n\nfunc BenchmarkURIFullURI(b *testing.B) {\n\thost := []byte(\"foobar.com\")\n\trequestURI := []byte(\"/foobar/baz?aaa=bbb&ccc=ddd\")\n\turiLen := len(host) + len(requestURI) + 7\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar u URI\n\t\tu.Parse(host, requestURI) //nolint:errcheck\n\t\tfor pb.Next() {\n\t\t\turi := u.FullURI()\n\t\t\tif len(uri) != uriLen {\n\t\t\t\tb.Fatalf(\"unexpected uri len %d. Expecting %d\", len(uri), uriLen)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc benchmarkURIParse(b *testing.B, host, uri string) {\n\tstrHost, strURI := []byte(host), []byte(uri)\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar u URI\n\t\tfor pb.Next() {\n\t\t\tu.Parse(strHost, strURI) //nolint:errcheck\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "uri_unix.go",
    "content": "//go:build !windows\n\npackage fasthttp\n\nfunc addLeadingSlash(dst, src []byte) []byte {\n\t// add leading slash for unix paths\n\tif len(src) == 0 || src[0] != '/' {\n\t\tdst = append(dst, '/')\n\t}\n\n\treturn dst\n}\n"
  },
  {
    "path": "uri_windows.go",
    "content": "package fasthttp\n\nfunc addLeadingSlash(dst, src []byte) []byte {\n\t// zero length 、\"C:/\" and \"a\" case\n\tisDisk := len(src) > 2 && src[1] == ':'\n\tif len(src) == 0 || (!isDisk && src[0] != '/') {\n\t\tdst = append(dst, '/')\n\t}\n\n\treturn dst\n}\n"
  },
  {
    "path": "uri_windows_test.go",
    "content": "package fasthttp\n\nimport \"testing\"\n\nfunc TestURIPathNormalizeIssue86(t *testing.T) {\n\tt.Parallel()\n\n\t// see https://github.com/valyala/fasthttp/issues/86\n\tvar u URI\n\n\ttestURIPathNormalize(t, &u, `C:\\a\\b\\c\\fs.go`, `C:\\a\\b\\c\\fs.go`)\n\n\ttestURIPathNormalize(t, &u, `a`, `/a`)\n\n\ttestURIPathNormalize(t, &u, \"/../../../../../foo\", \"/foo\")\n\n\ttestURIPathNormalize(t, &u, \"/..\\\\..\\\\..\\\\..\\\\..\\\\\", \"/\")\n\n\ttestURIPathNormalize(t, &u, \"/..%5c..%5cfoo\", \"/foo\")\n}\n"
  },
  {
    "path": "userdata.go",
    "content": "package fasthttp\n\nimport (\n\t\"io\"\n)\n\ntype userDataKV struct {\n\tkey   any\n\tvalue any\n}\n\ntype userData []userDataKV\n\nfunc (d *userData) Set(key, value any) {\n\tif b, ok := key.([]byte); ok {\n\t\tkey = string(b)\n\t}\n\targs := *d\n\tn := len(args)\n\tfor i := range n {\n\t\tkv := &args[i]\n\t\tif kv.key == key {\n\t\t\tkv.value = value\n\t\t\treturn\n\t\t}\n\t}\n\n\tif value == nil {\n\t\treturn\n\t}\n\n\tc := cap(args)\n\tif c > n {\n\t\targs = args[:n+1]\n\t\tkv := &args[n]\n\t\tkv.key = key\n\t\tkv.value = value\n\t\t*d = args\n\t\treturn\n\t}\n\n\tkv := userDataKV{}\n\tkv.key = key\n\tkv.value = value\n\targs = append(args, kv)\n\t*d = args\n}\n\nfunc (d *userData) SetBytes(key []byte, value any) {\n\td.Set(key, value)\n}\n\nfunc (d *userData) Get(key any) any {\n\tif b, ok := key.([]byte); ok {\n\t\tkey = b2s(b)\n\t}\n\targs := *d\n\tn := len(args)\n\tfor i := range n {\n\t\tkv := &args[i]\n\t\tif kv.key == key {\n\t\t\treturn kv.value\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (d *userData) GetBytes(key []byte) any {\n\treturn d.Get(key)\n}\n\nfunc (d *userData) Reset() {\n\targs := *d\n\tn := len(args)\n\tfor i := range n {\n\t\tv := args[i].value\n\t\tif vc, ok := v.(io.Closer); ok {\n\t\t\tvc.Close()\n\t\t}\n\t\t(*d)[i].value = nil\n\t\t(*d)[i].key = nil\n\t}\n\t*d = (*d)[:0]\n}\n\nfunc (d *userData) Remove(key any) {\n\tif b, ok := key.([]byte); ok {\n\t\tkey = b2s(b)\n\t}\n\targs := *d\n\tn := len(args)\n\tfor i := 0; i < n; i++ {\n\t\tkv := &args[i]\n\t\tif kv.key == key {\n\t\t\tn--\n\t\t\targs[i], args[n] = args[n], args[i]\n\t\t\targs[n].key = nil\n\t\t\targs[n].value = nil\n\t\t\targs = args[:n]\n\t\t\t*d = args\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (d *userData) RemoveBytes(key []byte) {\n\td.Remove(key)\n}\n"
  },
  {
    "path": "userdata_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestUserData(t *testing.T) {\n\tt.Parallel()\n\n\tvar u userData\n\n\tfor i := range 10 {\n\t\tkey := fmt.Appendf(nil, \"key_%d\", i)\n\t\tu.SetBytes(key, i+5)\n\t\ttestUserDataGet(t, &u, key, i+5)\n\t\tu.SetBytes(key, i)\n\t\ttestUserDataGet(t, &u, key, i)\n\t}\n\n\tfor i := range 10 {\n\t\tkey := fmt.Appendf(nil, \"key_%d\", i)\n\t\ttestUserDataGet(t, &u, key, i)\n\t}\n\n\tu.Reset()\n\n\tfor i := range 10 {\n\t\tkey := fmt.Appendf(nil, \"key_%d\", i)\n\t\ttestUserDataGet(t, &u, key, nil)\n\t}\n}\n\nfunc testUserDataGet(t *testing.T, u *userData, key []byte, value any) {\n\tv := u.GetBytes(key)\n\tif v == nil && value != nil {\n\t\tt.Fatalf(\"cannot obtain value for key=%q\", key)\n\t}\n\tif !reflect.DeepEqual(v, value) {\n\t\tt.Fatalf(\"unexpected value for key=%q: %d. Expecting %d\", key, v, value)\n\t}\n}\n\nfunc TestUserDataValueClose(t *testing.T) {\n\tt.Parallel()\n\n\tvar u userData\n\n\tcloseCalls := 0\n\n\t// store values implementing io.Closer\n\tfor i := range 5 {\n\t\tkey := fmt.Sprintf(\"key_%d\", i)\n\t\tu.Set(key, &closerValue{closeCalls: &closeCalls})\n\t}\n\n\t// store values without io.Closer\n\tfor i := range 10 {\n\t\tkey := fmt.Sprintf(\"key_noclose_%d\", i)\n\t\tu.Set(key, i)\n\t}\n\n\tu.Reset()\n\n\tif closeCalls != 5 {\n\t\tt.Fatalf(\"unexpected number of Close calls: %d. Expecting 10\", closeCalls)\n\t}\n}\n\ntype closerValue struct {\n\tcloseCalls *int\n}\n\nfunc (cv *closerValue) Close() error {\n\t(*cv.closeCalls)++\n\treturn nil\n}\n\nfunc TestUserDataDelete(t *testing.T) {\n\tt.Parallel()\n\n\tvar u userData\n\n\tfor i := range 10 {\n\t\tkey := fmt.Sprintf(\"key_%d\", i)\n\t\tu.Set(key, i)\n\t\ttestUserDataGet(t, &u, []byte(key), i)\n\t}\n\n\tfor i := 0; i < 10; i += 2 {\n\t\tk := fmt.Sprintf(\"key_%d\", i)\n\t\tu.Remove(k)\n\t\tif val := u.Get(k); val != nil {\n\t\t\tt.Fatalf(\"unexpected key= %q, value =%v ,Expecting key= %q, value = nil\", k, val, k)\n\t\t}\n\t\tkk := fmt.Sprintf(\"key_%d\", i+1)\n\t\ttestUserDataGet(t, &u, []byte(kk), i+1)\n\t}\n\tfor i := range 10 {\n\t\tkey := fmt.Sprintf(\"key_new_%d\", i)\n\t\tu.Set(key, i)\n\t\ttestUserDataGet(t, &u, []byte(key), i)\n\t}\n}\n\nfunc TestUserDataSetAndRemove(t *testing.T) {\n\tvar (\n\t\tu        userData\n\t\tshortKey = \"[]\"\n\t\tlongKey  = \"[  ]\"\n\t)\n\n\tu.Set(shortKey, \"\")\n\tu.Set(longKey, \"\")\n\tu.Remove(shortKey)\n\tu.Set(shortKey, \"\")\n\ttestUserDataGet(t, &u, []byte(shortKey), \"\")\n\ttestUserDataGet(t, &u, []byte(longKey), \"\")\n}\n\nfunc TestUserData_GC(t *testing.T) {\n\tt.Parallel()\n\n\tvar u userData\n\tkey := \"foo\"\n\tfinal := make(chan struct{})\n\n\tfunc() {\n\t\tval := &RequestHeader{}\n\t\truntime.SetFinalizer(val, func(v *RequestHeader) {\n\t\t\tclose(final)\n\t\t})\n\n\t\tu.Set(key, val)\n\t}()\n\n\tu.Reset()\n\truntime.GC()\n\n\tselect {\n\tcase <-final:\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"value is garbage collected\")\n\t}\n\n\t// Keep u alive, otherwise val will always get garbage collected.\n\tu.Set(\"bar\", 1)\n}\n"
  },
  {
    "path": "userdata_timing_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"testing\"\n)\n\nfunc BenchmarkUserDataCustom(b *testing.B) {\n\tkeys := []string{\"foobar\", \"baz\", \"aaa\", \"bsdfs\"}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tvar u userData\n\t\tvar v any = u\n\t\tfor pb.Next() {\n\t\t\tfor _, key := range keys {\n\t\t\t\tu.Set(key, v)\n\t\t\t}\n\t\t\tfor _, key := range keys {\n\t\t\t\tvv := u.Get(key)\n\t\t\t\tif _, ok := vv.(userData); !ok {\n\t\t\t\t\tb.Fatalf(\"unexpected value %v for key %q\", vv, key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tu.Reset()\n\t\t}\n\t})\n}\n\nfunc BenchmarkUserDataStdMap(b *testing.B) {\n\tkeys := []string{\"foobar\", \"baz\", \"aaa\", \"bsdfs\"}\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tu := make(map[string]any)\n\t\tvar v any = u\n\t\tfor pb.Next() {\n\t\t\tfor _, key := range keys {\n\t\t\t\tu[key] = v\n\t\t\t}\n\t\t\tfor _, key := range keys {\n\t\t\t\tvv := u[key]\n\t\t\t\tif _, ok := vv.(map[string]any); !ok {\n\t\t\t\t\tb.Fatalf(\"unexpected value %v for key %q\", vv, key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor k := range u {\n\t\t\t\tdelete(u, k)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "workerpool.go",
    "content": "package fasthttp\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// workerPool serves incoming connections via a pool of workers\n// in FILO order, i.e. the most recently stopped worker will serve the next\n// incoming connection.\n//\n// Such a scheme keeps CPU caches hot (in theory).\ntype workerPool struct {\n\tworkerChanPool sync.Pool\n\n\tLogger Logger\n\n\t// Function for serving server connections.\n\t// It must leave c unclosed.\n\tWorkerFunc ServeHandler\n\n\tstopCh chan struct{}\n\n\tconnState func(net.Conn, ConnState)\n\n\tready []*workerChan\n\n\tMaxWorkersCount int\n\n\tMaxIdleWorkerDuration time.Duration\n\n\tworkersCount int\n\n\tlock sync.Mutex\n\n\tLogAllErrors bool\n\tmustStop     bool\n}\n\ntype workerChan struct {\n\tlastUseTime time.Time\n\tch          chan net.Conn\n}\n\nfunc (wp *workerPool) Start() {\n\tif wp.stopCh != nil {\n\t\treturn\n\t}\n\twp.stopCh = make(chan struct{})\n\tstopCh := wp.stopCh\n\twp.workerChanPool.New = func() any {\n\t\treturn &workerChan{\n\t\t\tch: make(chan net.Conn, workerChanCap),\n\t\t}\n\t}\n\tgo func() {\n\t\tvar scratch []*workerChan\n\t\tfor {\n\t\t\twp.clean(&scratch)\n\t\t\tselect {\n\t\t\tcase <-stopCh:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttime.Sleep(wp.getMaxIdleWorkerDuration())\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (wp *workerPool) Stop() {\n\tif wp.stopCh == nil {\n\t\treturn\n\t}\n\tclose(wp.stopCh)\n\twp.stopCh = nil\n\n\t// Stop all the workers waiting for incoming connections.\n\t// Do not wait for busy workers - they will stop after\n\t// serving the connection and noticing wp.mustStop = true.\n\twp.lock.Lock()\n\tready := wp.ready\n\tfor i := range ready {\n\t\tready[i].ch <- nil\n\t\tready[i] = nil\n\t}\n\twp.ready = ready[:0]\n\twp.mustStop = true\n\twp.lock.Unlock()\n}\n\nfunc (wp *workerPool) getMaxIdleWorkerDuration() time.Duration {\n\tif wp.MaxIdleWorkerDuration <= 0 {\n\t\treturn 10 * time.Second\n\t}\n\treturn wp.MaxIdleWorkerDuration\n}\n\nfunc (wp *workerPool) clean(scratch *[]*workerChan) {\n\tmaxIdleWorkerDuration := wp.getMaxIdleWorkerDuration()\n\n\t// Clean least recently used workers if they didn't serve connections\n\t// for more than maxIdleWorkerDuration.\n\tcriticalTime := time.Now().Add(-maxIdleWorkerDuration)\n\n\twp.lock.Lock()\n\tready := wp.ready\n\tn := len(ready)\n\n\t// Use binary-search algorithm to find out the index of the least recently worker which can be cleaned up.\n\tl, r := 0, n-1\n\tfor l <= r {\n\t\tmid := (l + r) / 2\n\t\tif criticalTime.After(wp.ready[mid].lastUseTime) {\n\t\t\tl = mid + 1\n\t\t} else {\n\t\t\tr = mid - 1\n\t\t}\n\t}\n\ti := r\n\tif i == -1 {\n\t\twp.lock.Unlock()\n\t\treturn\n\t}\n\n\t*scratch = append((*scratch)[:0], ready[:i+1]...)\n\tm := copy(ready, ready[i+1:])\n\tfor i = m; i < n; i++ {\n\t\tready[i] = nil\n\t}\n\twp.ready = ready[:m]\n\twp.lock.Unlock()\n\n\t// Notify obsolete workers to stop.\n\t// This notification must be outside the wp.lock, since ch.ch\n\t// may be blocking and may consume a lot of time if many workers\n\t// are located on non-local CPUs.\n\ttmp := *scratch\n\tfor i := range tmp {\n\t\ttmp[i].ch <- nil\n\t\ttmp[i] = nil\n\t}\n}\n\nfunc (wp *workerPool) Serve(c net.Conn) bool {\n\tch := wp.getCh()\n\tif ch == nil {\n\t\treturn false\n\t}\n\tch.ch <- c\n\treturn true\n}\n\nvar workerChanCap = func() int {\n\t// Use blocking workerChan if GOMAXPROCS=1.\n\t// This immediately switches Serve to WorkerFunc, which results\n\t// in higher performance (under go1.5 at least).\n\tif runtime.GOMAXPROCS(0) == 1 {\n\t\treturn 0\n\t}\n\n\t// Use non-blocking workerChan if GOMAXPROCS>1,\n\t// since otherwise the Serve caller (Acceptor) may lag accepting\n\t// new connections if WorkerFunc is CPU-bound.\n\treturn 1\n}()\n\nfunc (wp *workerPool) getCh() *workerChan {\n\tvar ch *workerChan\n\tcreateWorker := false\n\n\twp.lock.Lock()\n\tready := wp.ready\n\tn := len(ready) - 1\n\tif n < 0 {\n\t\tif wp.workersCount < wp.MaxWorkersCount {\n\t\t\tcreateWorker = true\n\t\t\twp.workersCount++\n\t\t}\n\t} else {\n\t\tch = ready[n]\n\t\tready[n] = nil\n\t\twp.ready = ready[:n]\n\t}\n\twp.lock.Unlock()\n\n\tif ch == nil {\n\t\tif !createWorker {\n\t\t\treturn nil\n\t\t}\n\t\tvch := wp.workerChanPool.Get()\n\t\tch = vch.(*workerChan)\n\t\tgo func() {\n\t\t\twp.workerFunc(ch)\n\t\t\twp.workerChanPool.Put(vch)\n\t\t}()\n\t}\n\treturn ch\n}\n\nfunc (wp *workerPool) release(ch *workerChan) bool {\n\tch.lastUseTime = time.Now()\n\twp.lock.Lock()\n\tif wp.mustStop {\n\t\twp.lock.Unlock()\n\t\treturn false\n\t}\n\twp.ready = append(wp.ready, ch)\n\twp.lock.Unlock()\n\treturn true\n}\n\nfunc (wp *workerPool) workerFunc(ch *workerChan) {\n\tvar c net.Conn\n\n\tvar err error\n\tfor c = range ch.ch {\n\t\tif c == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif err = wp.WorkerFunc(c); err != nil && err != errHijacked {\n\t\t\terrStr := err.Error()\n\t\t\tshouldIgnore := strings.Contains(errStr, \"broken pipe\") ||\n\t\t\t\tstrings.Contains(errStr, \"reset by peer\") ||\n\t\t\t\tstrings.Contains(errStr, \"request headers: small read buffer\") ||\n\t\t\t\tstrings.Contains(errStr, \"unexpected EOF\") ||\n\t\t\t\tstrings.Contains(errStr, \"i/o timeout\") ||\n\t\t\t\terrors.Is(err, ErrBadTrailer)\n\t\t\tif wp.LogAllErrors || !shouldIgnore {\n\t\t\t\twp.Logger.Printf(\"error when serving connection %q<->%q: %v\", c.LocalAddr(), c.RemoteAddr(), err)\n\t\t\t}\n\t\t}\n\t\tif err == errHijacked {\n\t\t\twp.connState(c, StateHijacked)\n\t\t} else {\n\t\t\t_ = c.Close()\n\t\t\twp.connState(c, StateClosed)\n\t\t}\n\n\t\tif !wp.release(ch) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\twp.lock.Lock()\n\twp.workersCount--\n\twp.lock.Unlock()\n}\n"
  },
  {
    "path": "workerpool_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/valyala/fasthttp/fasthttputil\"\n)\n\nfunc TestWorkerPoolStartStopSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestWorkerPoolStartStop()\n}\n\nfunc TestWorkerPoolStartStopConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tconcurrency := 10\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\ttestWorkerPoolStartStop()\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testWorkerPoolStartStop() {\n\twp := &workerPool{\n\t\tWorkerFunc:      func(conn net.Conn) error { return nil },\n\t\tMaxWorkersCount: 10,\n\t\tLogger:          defaultLogger,\n\t}\n\tfor range 10 {\n\t\twp.Start()\n\t\twp.Stop()\n\t}\n}\n\nfunc TestWorkerPoolMaxWorkersCountSerial(t *testing.T) {\n\tt.Parallel()\n\n\ttestWorkerPoolMaxWorkersCountMulti(t)\n}\n\nfunc TestWorkerPoolMaxWorkersCountConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tconcurrency := 4\n\tch := make(chan struct{}, concurrency)\n\tfor range concurrency {\n\t\tgo func() {\n\t\t\ttestWorkerPoolMaxWorkersCountMulti(t)\n\t\t\tch <- struct{}{}\n\t\t}()\n\t}\n\tfor range concurrency {\n\t\tselect {\n\t\tcase <-ch:\n\t\tcase <-time.After(time.Second * 2):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n}\n\nfunc testWorkerPoolMaxWorkersCountMulti(t *testing.T) {\n\tfor range 5 {\n\t\ttestWorkerPoolMaxWorkersCount(t)\n\t}\n}\n\nfunc testWorkerPoolMaxWorkersCount(t *testing.T) {\n\tready := make(chan struct{})\n\twp := &workerPool{\n\t\tWorkerFunc: func(conn net.Conn) error {\n\t\t\tbuf := make([]byte, 100)\n\t\t\tn, err := conn.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tbuf = buf[:n]\n\t\t\tif string(buf) != \"foobar\" {\n\t\t\t\tt.Errorf(\"unexpected data read: %q. Expecting %q\", buf, \"foobar\")\n\t\t\t}\n\t\t\tif _, err = conn.Write([]byte(\"baz\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\t<-ready\n\n\t\t\treturn nil\n\t\t},\n\t\tMaxWorkersCount: 10,\n\t\tLogger:          defaultLogger,\n\t\tconnState:       func(net.Conn, ConnState) {},\n\t}\n\twp.Start()\n\n\tln := fasthttputil.NewInmemoryListener()\n\n\tclientCh := make(chan struct{}, wp.MaxWorkersCount)\n\tfor i := 0; i < wp.MaxWorkersCount; i++ {\n\t\tgo func() {\n\t\t\tconn, err := ln.Dial()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif _, err = conn.Write([]byte(\"foobar\")); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tdata, err := io.ReadAll(conn)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif string(data) != \"baz\" {\n\t\t\t\tt.Errorf(\"unexpected value read: %q. Expecting %q\", data, \"baz\")\n\t\t\t}\n\t\t\tif err = conn.Close(); err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tclientCh <- struct{}{}\n\t\t}()\n\t}\n\n\tfor i := 0; i < wp.MaxWorkersCount; i++ {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif !wp.Serve(conn) {\n\t\t\tt.Fatalf(\"worker pool must have enough workers to serve the conn\")\n\t\t}\n\t}\n\n\tgo func() {\n\t\tif _, err := ln.Dial(); err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t}()\n\tconn, err := ln.Accept()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tfor range 5 {\n\t\tif wp.Serve(conn) {\n\t\t\tt.Fatalf(\"worker pool must be full\")\n\t\t}\n\t}\n\tif err = conn.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tclose(ready)\n\n\tfor i := 0; i < wp.MaxWorkersCount; i++ {\n\t\tselect {\n\t\tcase <-clientCh:\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Fatalf(\"timeout\")\n\t\t}\n\t}\n\n\tif err := ln.Close(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\twp.Stop()\n}\n"
  },
  {
    "path": "zstd.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/klauspost/compress/zstd\"\n\t\"github.com/valyala/bytebufferpool\"\n\t\"github.com/valyala/fasthttp/stackless\"\n)\n\nconst (\n\tCompressZstdSpeedNotSet = iota\n\tCompressZstdBestSpeed\n\tCompressZstdDefault\n\tCompressZstdSpeedBetter\n\tCompressZstdBestCompression\n)\n\nvar (\n\tzstdDecoderPool            sync.Pool\n\trealZstdWriterPoolMap      = newCompressWriterPoolMap()\n\tstacklessZstdWriterPoolMap = newCompressWriterPoolMap()\n)\n\nfunc acquireZstdReader(r io.Reader) (*zstd.Decoder, error) {\n\tv := zstdDecoderPool.Get()\n\tif v == nil {\n\t\treturn zstd.NewReader(r)\n\t}\n\tzr := v.(*zstd.Decoder)\n\tif err := zr.Reset(r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn zr, nil\n}\n\nfunc releaseZstdReader(zr *zstd.Decoder) {\n\tzstdDecoderPool.Put(zr)\n}\n\nfunc acquireStacklessZstdWriter(w io.Writer, compressLevel int) stackless.Writer {\n\tnLevel := normalizeZstdCompressLevel(compressLevel)\n\tp := stacklessZstdWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\treturn stackless.NewWriter(w, func(w io.Writer) stackless.Writer {\n\t\t\treturn acquireRealZstdWriter(w, compressLevel)\n\t\t})\n\t}\n\tsw := v.(stackless.Writer)\n\tsw.Reset(w)\n\treturn sw\n}\n\nfunc releaseStacklessZstdWriter(zf stackless.Writer, level int) {\n\tzf.Close()\n\tnLevel := normalizeZstdCompressLevel(level)\n\tp := stacklessZstdWriterPoolMap[nLevel]\n\tp.Put(zf)\n}\n\nfunc acquireRealZstdWriter(w io.Writer, level int) *zstd.Encoder {\n\tnLevel := normalizeZstdCompressLevel(level)\n\tp := realZstdWriterPoolMap[nLevel]\n\tv := p.Get()\n\tif v == nil {\n\t\tzw, err := zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.EncoderLevel(nLevel)))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn zw\n\t}\n\tzw := v.(*zstd.Encoder)\n\tzw.Reset(w)\n\treturn zw\n}\n\nfunc releaseRealZstdWriter(zw *zstd.Encoder, level int) {\n\tzw.Close()\n\tnLevel := normalizeZstdCompressLevel(level)\n\tp := realZstdWriterPoolMap[nLevel]\n\tp.Put(zw)\n}\n\nfunc AppendZstdBytesLevel(dst, src []byte, level int) []byte {\n\tw := &byteSliceWriter{b: dst}\n\tWriteZstdLevel(w, src, level) //nolint:errcheck\n\treturn w.b\n}\n\nfunc WriteZstdLevel(w io.Writer, p []byte, level int) (int, error) {\n\tlevel = normalizeZstdCompressLevel(level)\n\tswitch w.(type) {\n\tcase *byteSliceWriter,\n\t\t*bytes.Buffer,\n\t\t*bytebufferpool.ByteBuffer:\n\t\tctx := &compressCtx{\n\t\t\tw:     w,\n\t\t\tp:     p,\n\t\t\tlevel: level,\n\t\t}\n\t\tstacklessWriteZstd(ctx)\n\t\treturn len(p), nil\n\tdefault:\n\t\tzw := acquireStacklessZstdWriter(w, level)\n\t\tn, err := zw.Write(p)\n\t\treleaseStacklessZstdWriter(zw, level)\n\t\treturn n, err\n\t}\n}\n\nvar (\n\tstacklessWriteZstdOnce sync.Once\n\tstacklessWriteZstdFunc func(ctx any) bool\n)\n\nfunc stacklessWriteZstd(ctx any) {\n\tstacklessWriteZstdOnce.Do(func() {\n\t\tstacklessWriteZstdFunc = stackless.NewFunc(nonblockingWriteZstd)\n\t})\n\tstacklessWriteZstdFunc(ctx)\n}\n\nfunc nonblockingWriteZstd(ctxv any) {\n\tctx := ctxv.(*compressCtx)\n\tzw := acquireRealZstdWriter(ctx.w, ctx.level)\n\tzw.Write(ctx.p) //nolint:errcheck\n\treleaseRealZstdWriter(zw, ctx.level)\n}\n\n// AppendZstdBytes appends zstd src to dst and returns the resulting dst.\nfunc AppendZstdBytes(dst, src []byte) []byte {\n\treturn AppendZstdBytesLevel(dst, src, CompressZstdDefault)\n}\n\n// WriteUnzstd writes unzstd p to w and returns the number of uncompressed\n// bytes written to w.\nfunc WriteUnzstd(w io.Writer, p []byte) (int, error) {\n\treturn writeUnzstd(w, p, 0)\n}\n\nfunc writeUnzstd(w io.Writer, p []byte, maxBodySize int) (int, error) {\n\tr := &byteSliceReader{b: p}\n\tzr, err := acquireZstdReader(r)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := copyZeroAllocWithLimit(w, zr, maxBodySize)\n\treleaseZstdReader(zr)\n\tnn := int(n)\n\tif int64(nn) != n {\n\t\treturn 0, fmt.Errorf(\"too much data unzstd: %d\", n)\n\t}\n\treturn nn, err\n}\n\n// AppendUnzstdBytes appends unzstd src to dst and returns the resulting dst.\nfunc AppendUnzstdBytes(dst, src []byte) ([]byte, error) {\n\tw := &byteSliceWriter{b: dst}\n\t_, err := WriteUnzstd(w, src)\n\treturn w.b, err\n}\n\n// normalizes compression level into [0..7], so it could be used as an index\n// in *PoolMap.\nfunc normalizeZstdCompressLevel(level int) int {\n\tif level < CompressZstdSpeedNotSet || level > CompressZstdBestCompression {\n\t\tlevel = CompressZstdDefault\n\t}\n\treturn level\n}\n"
  },
  {
    "path": "zstd_test.go",
    "content": "package fasthttp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestZstdBytesSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testZstdBytes(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestZstdBytesConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testZstdBytes); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testZstdBytes() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testZstdBytesSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testZstdBytesSingleCase(s string) error {\n\tprefix := []byte(\"foobar\")\n\tZstdpedS := AppendZstdBytes(prefix, []byte(s))\n\tif !bytes.Equal(ZstdpedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when compressing %q: %q. Expecting %q\", s, ZstdpedS[:len(prefix)], prefix)\n\t}\n\n\tunZstdedS, err := AppendUnzstdBytes(prefix, ZstdpedS[len(prefix):])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error when uncompressing %q: %w\", s, err)\n\t}\n\tif !bytes.Equal(unZstdedS[:len(prefix)], prefix) {\n\t\treturn fmt.Errorf(\"unexpected prefix when uncompressing %q: %q. Expecting %q\", s, unZstdedS[:len(prefix)], prefix)\n\t}\n\tunZstdedS = unZstdedS[len(prefix):]\n\tif string(unZstdedS) != s {\n\t\treturn fmt.Errorf(\"unexpected uncompressed string %q. Expecting %q\", unZstdedS, s)\n\t}\n\treturn nil\n}\n\nfunc TestZstdCompressSerial(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testZstdCompress(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestZstdCompressConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif err := testConcurrent(10, testZstdCompress); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc testZstdCompress() error {\n\tfor _, s := range compressTestcases {\n\t\tif err := testZstdCompressSingleCase(s); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testZstdCompressSingleCase(s string) error {\n\tvar buf bytes.Buffer\n\tzw := acquireStacklessZstdWriter(&buf, CompressZstdDefault)\n\tif _, err := zw.Write([]byte(s)); err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\treleaseStacklessZstdWriter(zw, CompressZstdDefault)\n\n\tzr, err := acquireZstdReader(&buf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tbody, err := io.ReadAll(zr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error: %w. s=%q\", err, s)\n\t}\n\tif string(body) != s {\n\t\treturn fmt.Errorf(\"unexpected string after decompression: %q. Expecting %q\", body, s)\n\t}\n\treleaseZstdReader(zr)\n\treturn nil\n}\n"
  }
]