replace zxq.co/ripple/hanayo

This commit is contained in:
Alicia
2019-02-23 13:29:15 +00:00
commit c3d206c173
5871 changed files with 1353715 additions and 0 deletions

3
vendor/github.com/valyala/fasthttp/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
tags
*.pprof
*.fasthttp.gz

15
vendor/github.com/valyala/fasthttp/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
language: go
go:
- 1.6
script:
# build test for supported platforms
- GOOS=linux go build
- GOOS=darwin go build
- GOOS=freebsd go build
- GOOS=windows go build
- GOARCH=386 go build
# run tests on a standard platform
- go test -v ./...

22
vendor/github.com/valyala/fasthttp/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015-2016 Aliaksandr Valialkin, VertaMedia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

579
vendor/github.com/valyala/fasthttp/README.md generated vendored Normal file
View File

@@ -0,0 +1,579 @@
[![Build Status](https://travis-ci.org/valyala/fasthttp.svg)](https://travis-ci.org/valyala/fasthttp)
[![GoDoc](https://godoc.org/github.com/valyala/fasthttp?status.svg)](http://godoc.org/github.com/valyala/fasthttp)
[![Go Report](http://goreportcard.com/badge/valyala/fasthttp)](http://goreportcard.com/report/valyala/fasthttp)
# fasthttp
Fast HTTP implementation for Go.
Currently fasthttp is successfully used by [VertaMedia](https://vertamedia.com/)
in a production serving up to 200K rps from more than 1.5M concurrent keep-alive
connections per physical server.
[TechEmpower Benchmark round 12 results](https://www.techempower.com/benchmarks/#section=data-r12&hw=peak&test=plaintext)
[Server Benchmarks](#http-server-performance-comparison-with-nethttp)
[Client Benchmarks](#http-client-comparison-with-nethttp)
[Install](#install)
[Documentation](https://godoc.org/github.com/valyala/fasthttp)
[Examples from docs](https://godoc.org/github.com/valyala/fasthttp#pkg-examples)
[Code examples](examples)
[Switching from net/http to fasthttp](#switching-from-nethttp-to-fasthttp)
[Fasthttp best practices](#fasthttp-best-practices)
[Tricks with byte buffers](#tricks-with-byte-buffers)
[Related projects](#related-projects)
[FAQ](#faq)
# HTTP server performance comparison with [net/http](https://golang.org/pkg/net/http/)
In short, fasthttp server is up to 10 times faster than net/http.
Below are benchmark results.
*GOMAXPROCS=1*
net/http server:
```
$ GOMAXPROCS=1 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s
BenchmarkNetHTTPServerGet1ReqPerConn 1000000 12052 ns/op 2297 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn 1000000 12278 ns/op 2327 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn 2000000 8903 ns/op 2112 B/op 19 allocs/op
BenchmarkNetHTTPServerGet10KReqPerConn 2000000 8451 ns/op 2058 B/op 18 allocs/op
BenchmarkNetHTTPServerGet1ReqPerConn10KClients 500000 26733 ns/op 3229 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn10KClients 1000000 23351 ns/op 3211 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn10KClients 1000000 13390 ns/op 2483 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn10KClients 1000000 13484 ns/op 2171 B/op 18 allocs/op
```
fasthttp server:
```
$ GOMAXPROCS=1 go test -bench=kServerGet -benchmem -benchtime=10s
BenchmarkServerGet1ReqPerConn 10000000 1559 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn 10000000 1248 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn 20000000 797 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10KReqPerConn 20000000 716 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet1ReqPerConn10KClients 10000000 1974 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn10KClients 10000000 1352 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn10KClients 20000000 789 ns/op 2 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn10KClients 20000000 604 ns/op 0 B/op 0 allocs/op
```
*GOMAXPROCS=4*
net/http server:
```
$ GOMAXPROCS=4 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s
BenchmarkNetHTTPServerGet1ReqPerConn-4 3000000 4529 ns/op 2389 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn-4 5000000 3896 ns/op 2418 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn-4 5000000 3145 ns/op 2160 B/op 19 allocs/op
BenchmarkNetHTTPServerGet10KReqPerConn-4 5000000 3054 ns/op 2065 B/op 18 allocs/op
BenchmarkNetHTTPServerGet1ReqPerConn10KClients-4 1000000 10321 ns/op 3710 B/op 30 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn10KClients-4 2000000 7556 ns/op 3296 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn10KClients-4 5000000 3905 ns/op 2349 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn10KClients-4 5000000 3435 ns/op 2130 B/op 18 allocs/op
```
fasthttp server:
```
$ GOMAXPROCS=4 go test -bench=kServerGet -benchmem -benchtime=10s
BenchmarkServerGet1ReqPerConn-4 10000000 1141 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn-4 20000000 707 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn-4 30000000 341 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10KReqPerConn-4 50000000 310 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet1ReqPerConn10KClients-4 10000000 1119 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn10KClients-4 20000000 644 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn10KClients-4 30000000 346 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn10KClients-4 50000000 282 ns/op 0 B/op 0 allocs/op
```
# HTTP client comparison with net/http
In short, fasthttp client is up to 10 times faster than net/http.
Below are benchmark results.
*GOMAXPROCS=1*
net/http client:
```
$ GOMAXPROCS=1 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkNetHTTPClientDoFastServer 1000000 12567 ns/op 2616 B/op 35 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1TCP 200000 67030 ns/op 5028 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10TCP 300000 51098 ns/op 5031 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100TCP 300000 45096 ns/op 5026 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1Inmemory 500000 24779 ns/op 5035 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10Inmemory 1000000 26425 ns/op 5035 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100Inmemory 500000 28515 ns/op 5045 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1000Inmemory 500000 39511 ns/op 5096 B/op 56 allocs/op
```
fasthttp client:
```
$ GOMAXPROCS=1 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkClientDoFastServer 20000000 865 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1TCP 1000000 18711 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10TCP 1000000 14664 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100TCP 1000000 14043 ns/op 1 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1Inmemory 5000000 3965 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10Inmemory 3000000 4060 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100Inmemory 5000000 3396 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1000Inmemory 5000000 3306 ns/op 2 B/op 0 allocs/op
```
*GOMAXPROCS=4*
net/http client:
```
$ GOMAXPROCS=4 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkNetHTTPClientDoFastServer-4 2000000 8774 ns/op 2619 B/op 35 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1TCP-4 500000 22951 ns/op 5047 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10TCP-4 1000000 19182 ns/op 5037 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100TCP-4 1000000 16535 ns/op 5031 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1Inmemory-4 1000000 14495 ns/op 5038 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10Inmemory-4 1000000 10237 ns/op 5034 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100Inmemory-4 1000000 10125 ns/op 5045 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1000Inmemory-4 1000000 11132 ns/op 5136 B/op 56 allocs/op
```
fasthttp client:
```
$ GOMAXPROCS=4 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkClientDoFastServer-4 50000000 397 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1TCP-4 2000000 7388 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10TCP-4 2000000 6689 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100TCP-4 3000000 4927 ns/op 1 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1Inmemory-4 10000000 1604 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10Inmemory-4 10000000 1458 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100Inmemory-4 10000000 1329 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1000Inmemory-4 10000000 1316 ns/op 5 B/op 0 allocs/op
```
# Install
```
go get -u github.com/valyala/fasthttp
```
# Switching from net/http to fasthttp
Unfortunately, fasthttp doesn't provide API identical to net/http.
See the [FAQ](#faq) for details.
There is [net/http -> fasthttp handler converter](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor),
but it is advisable writing fasthttp request handlers by hands for gaining
all the fasthttp advantages (especially high performance :) ).
Important points:
* Fasthttp works with [RequestHandler functions](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
instead of objects implementing [Handler interface](https://golang.org/pkg/net/http/#Handler).
Fortunately, it is easy to pass bound struct methods to fasthttp:
```go
type MyHandler struct {
foobar string
}
// request handler in net/http style, i.e. method bound to MyHandler struct.
func (h *MyHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {
// notice that we may access MyHandler properties here - see h.foobar.
fmt.Fprintf(ctx, "Hello, world! Requested path is %q. Foobar is %q",
ctx.Path(), h.foobar)
}
// request handler in fasthttp style, i.e. just plain function.
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hi there! RequestURI is %q", ctx.RequestURI())
}
// pass bound struct method to fasthttp
myHandler := &MyHandler{
foobar: "foobar",
}
fasthttp.ListenAndServe(":8080", myHandler.HandleFastHTTP)
// pass plain function to fasthttp
fasthttp.ListenAndServe(":8081", fastHTTPHandler)
```
* The [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
accepts only one argument - [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx).
It contains all the functionality required for http request processing
and response writing. Below is an example of a simple request handler conversion
from net/http to fasthttp.
```go
// net/http request handler
requestHandler := func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/foo":
fooHandler(w, r)
case "/bar":
barHandler(w, r)
default:
http.Error(w, "Unsupported path", http.StatusNotFound)
}
}
```
```go
// the corresponding fasthttp request handler
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/foo":
fooHandler(ctx)
case "/bar":
barHandler(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
```
* Fasthttp allows setting response headers and writing response body
in arbitrary order. There is no 'headers first, then body' restriction
like in net/http. The following code is valid for fasthttp:
```go
requestHandler := func(ctx *fasthttp.RequestCtx) {
// set some headers and status code first
ctx.SetContentType("foo/bar")
ctx.SetStatusCode(fasthttp.StatusOK)
// then write the first part of body
fmt.Fprintf(ctx, "this is the first part of body\n")
// then set more headers
ctx.Response.Header.Set("Foo-Bar", "baz")
// then write more body
fmt.Fprintf(ctx, "this is the second part of body\n")
// then override already written body
ctx.SetBody([]byte("this is completely new body contents"))
// then update status code
ctx.SetStatusCode(fasthttp.StatusNotFound)
// basically, anything may be updated many times before
// returning from RequestHandler.
//
// Unlike net/http fasthttp doesn't put response to the wire until
// returning from RequestHandler.
}
```
* Fasthttp doesn't provide [ServeMux](https://golang.org/pkg/net/http/#ServeMux),
but there are more powerful third-party routers and web frameworks
with fasthttp support exist:
* [Iris](https://github.com/kataras/iris)
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)
* [fasthttprouter](https://github.com/buaazp/fasthttprouter)
* [echo v2](https://github.com/labstack/echo)
Net/http code with simple ServeMux is trivially converted to fasthttp code:
```go
// net/http code
m := &http.ServeMux{}
m.HandleFunc("/foo", fooHandlerFunc)
m.HandleFunc("/bar", barHandlerFunc)
m.Handle("/baz", bazHandler)
http.ListenAndServe(":80", m)
```
```go
// the corresponding fasthttp code
m := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/foo":
fooHandlerFunc(ctx)
case "/bar":
barHandlerFunc(ctx)
case "/baz":
bazHandler.HandlerFunc(ctx)
default:
ctx.Error("not found", fasthttp.StatusNotFound)
}
}
fastttp.ListenAndServe(":80", m)
```
* net/http -> fasthttp conversion table:
* All the pseudocode below assumes w, r and ctx have these types:
```go
var (
w http.ResponseWriter
r *http.Request
ctx *fasthttp.RequestCtx
)
```
* r.Body -> [ctx.PostBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody)
* r.URL.Path -> [ctx.Path()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Path)
* r.URL -> [ctx.URI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.URI)
* r.Method -> [ctx.Method()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Method)
* r.Header -> [ctx.Request.Header](https://godoc.org/github.com/valyala/fasthttp#RequestHeader)
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Peek)
* r.Host -> [ctx.Host()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Host)
* r.Form -> [ctx.QueryArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
[ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.PostForm -> [ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.FormValue() -> [ctx.FormValue()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormValue)
* r.FormFile() -> [ctx.FormFile()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormFile)
* r.MultipartForm -> [ctx.MultipartForm()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
* r.RemoteAddr -> [ctx.RemoteAddr()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
* r.RequestURI -> [ctx.RequestURI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RequestURI)
* r.TLS -> [ctx.IsTLS()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.IsTLS)
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Cookie)
* r.Referer() -> [ctx.Referer()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Referer)
* r.UserAgent() -> [ctx.UserAgent()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.UserAgent)
* w.Header() -> [ctx.Response.Header](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader)
* w.Header().Set() -> [ctx.Response.Header.Set()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.Set)
* w.Header().Set("Content-Type") -> [ctx.SetContentType()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetContentType)
* w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
* w.Write() -> [ctx.Write()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Write),
[ctx.SetBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBody),
[ctx.SetBodyStream()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
[ctx.SetBodyStreamWriter()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
* w.WriteHeader() -> [ctx.SetStatusCode()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack)
* http.Error() -> [ctx.Error()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Error)
* http.FileServer() -> [fasthttp.FSHandler()](https://godoc.org/github.com/valyala/fasthttp#FSHandler),
[fasthttp.FS](https://godoc.org/github.com/valyala/fasthttp#FS)
* http.ServeFile() -> [fasthttp.ServeFile()](https://godoc.org/github.com/valyala/fasthttp#ServeFile)
* http.Redirect() -> [ctx.Redirect()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Redirect)
* http.NotFound() -> [ctx.NotFound()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.NotFound)
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://godoc.org/github.com/valyala/fasthttp#PathRewriteFunc)
* *VERY IMPORTANT!* Fasthttp disallows holding references
to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) or to its'
members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
Otherwise [data races](http://blog.golang.org/race-detector) are inevitable.
Carefully inspect all the net/http request handlers converted to fasthttp whether
they retain references to RequestCtx or to its' members after returning.
RequestCtx provides the following _band aids_ for this case:
* Wrap RequestHandler into [TimeoutHandler](https://godoc.org/github.com/valyala/fasthttp#TimeoutHandler).
* Call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from RequestHandler if there are references to RequestCtx or to its' members.
See [the example](https://godoc.org/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError)
for more details.
Use brilliant tool - [race detector](http://blog.golang.org/race-detector) -
for detecting and eliminating data races in your program. If you detected
data race related to fasthttp in your program, then there is high probability
you forgot calling [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
* Blind switching from net/http to fasthttp won't give you performance boost.
While fasthttp is optimized for speed, its' performance may be easily saturated
by slow [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
So [profile](http://blog.golang.org/profiling-go-programs) and optimize your
code after switching to fasthttp. For instance, use [quicktemplate](https://github.com/valyala/quicktemplate)
instead of [html/template](https://golang.org/pkg/html/template/).
* See also [fasthttputil](https://godoc.org/github.com/valyala/fasthttp/fasthttputil),
[fasthttpadaptor](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor) and
[expvarhandler](https://godoc.org/github.com/valyala/fasthttp/expvarhandler).
# Performance optimization tips for multi-core systems
* Use [reuseport](https://godoc.org/github.com/valyala/fasthttp/reuseport) listener.
* Run a separate server instance per CPU core with GOMAXPROCS=1.
* Pin each server instance to a separate CPU core using [taskset](http://linux.die.net/man/1/taskset).
* Ensure the interrupts of multiqueue network card are evenly distributed between CPU cores.
See [this article](https://blog.cloudflare.com/how-to-achieve-low-latency/) for details.
* Use Go 1.6 as it provides some considerable performance improvements.
# Fasthttp best practices
* Do not allocate objects and `[]byte` buffers - just reuse them as much
as possible. Fasthttp API design encourages this.
* [sync.Pool](https://golang.org/pkg/sync/#Pool) is your best friend.
* [Profile your program](http://blog.golang.org/profiling-go-programs)
in production.
`go tool pprof --alloc_objects your-program mem.pprof` usually gives better
insights for optimization opportunities than `go tool pprof your-program cpu.pprof`.
* Write [tests and benchmarks](https://golang.org/pkg/testing/) for hot paths.
* Avoid conversion between `[]byte` and `string`, since this may result in memory
allocation+copy. Fasthttp API provides functions for both `[]byte` and `string` -
use these functions instead of converting manually between `[]byte` and `string`.
There are some exceptions - see [this wiki page](https://github.com/golang/go/wiki/CompilerOptimizations#string-and-byte)
for more details.
* Verify your tests and production code under
[race detector](https://golang.org/doc/articles/race_detector.html) on a regular basis.
* Prefer [quicktemplate](https://github.com/valyala/quicktemplate) instead of
[html/template](https://golang.org/pkg/html/template/) in your webserver.
# Tricks with `[]byte` buffers
The following tricks are used by fasthttp. Use them in your code too.
* Standard Go functions accept nil buffers
```go
var (
// both buffers are uninitialized
dst []byte
src []byte
)
dst = append(dst, src...) // is legal if dst is nil and/or src is nil
copy(dst, src) // is legal if dst is nil and/or src is nil
(string(src) == "") // is true if src is nil
(len(src) == 0) // is true if src is nil
src = src[:0] // works like a charm with nil src
// this for loop doesn't panic if src is nil
for i, ch := range src {
doSomething(i, ch)
}
```
So throw away nil checks for `[]byte` buffers from you code. For example,
```go
srcLen := 0
if src != nil {
srcLen = len(src)
}
```
becomes
```go
srcLen := len(src)
```
* String may be appended to `[]byte` buffer with `append`
```go
dst = append(dst, "foobar"...)
```
* `[]byte` buffer may be extended to its' capacity.
```go
buf := make([]byte, 100)
a := buf[:10] // len(a) == 10, cap(a) == 100.
b := a[:100] // is valid, since cap(a) == 100.
```
* All fasthttp functions accept nil `[]byte` buffer
```go
statusCode, body, err := fasthttp.Get(nil, "http://google.com/")
uintBuf := fasthttp.AppendUint(nil, 1234)
```
# Related projects
* [fasthttp-contrib](https://github.com/fasthttp-contrib) - various useful
helpers for projects based on fasthttp.
* [iris](https://github.com/kataras/iris) - web application framework built
on top of fasthttp. Features speed and functionality.
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing) - fast and
powerful routing package for fasthttp servers.
* [fasthttprouter](https://github.com/buaazp/fasthttprouter) - a high
performance fasthttp request router that scales well.
* [echo](https://github.com/labstack/echo) - fast and unfancy HTTP server
framework with fasthttp support.
* [websocket](https://github.com/leavengood/websocket) - Gorilla-based
websocket implementation for fasthttp.
# FAQ
* *Why creating yet another http package instead of optimizing net/http?*
Because net/http API limits many optimization opportunities.
For example:
* net/http Request object lifetime isn't limited by request handler execution
time. So the server must create new request object per each request instead
of reusing existing objects like fasthttp do.
* net/http headers are stored in a `map[string][]string`. So the server
must parse all the headers, convert them from `[]byte` to `string` and put
them into the map before calling user-provided request handler.
This all requires unnecessary memory allocations avoided by fasthttp.
* net/http client API requires creating new response object per each request.
* *Why fasthttp API is incompatible with net/http?*
Because net/http API limits many optimization opportunities. See the answer
above for more details. Also certain net/http API parts are suboptimal
for use:
* Compare [net/http connection hijacking](https://golang.org/pkg/net/http/#Hijacker)
to [fasthttp connection hijacking](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack).
* Compare [net/http Request.Body reading](https://golang.org/pkg/net/http/#Request)
to [fasthttp request body reading](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody).
* *Why fasthttp doesn't support HTTP/2.0 and WebSockets?*
There are [plans](TODO) for adding HTTP/2.0 and WebSockets support
in the future.
In the mean time, third parties may use [RequestCtx.Hijack](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack)
for implementing these goodies. See [the first third-party websocket implementation on the top of fasthttp](https://github.com/leavengood/websocket).
* *Are there known net/http advantages comparing to fasthttp?*
Yes:
* net/http supports [HTTP/2.0 starting from go1.6](https://http2.golang.org/).
* net/http API is stable, while fasthttp API constantly evolves.
* net/http handles more HTTP corner cases.
* net/http should contain less bugs, since it is used and tested by much
wider audience.
* net/http works on Go older than 1.5.
* *Why fasthttp API prefers returning `[]byte` instead of `string`?*
Because `[]byte` to `string` conversion isn't free - it requires memory
allocation and copy. Feel free wrapping returned `[]byte` result into
`string()` if you prefer working with strings instead of byte slices.
But be aware that this has non-zero overhead.
* *Which GO versions are supported by fasthttp?*
Go1.5+. Older versions won't be supported, since their standard package
[miss useful functions](https://github.com/valyala/fasthttp/issues/5).
* *Please provide real benchmark data and sever information*
See [this issue](https://github.com/valyala/fasthttp/issues/4).
* *Are there plans to add request routing to fasthttp?*
There are no plans to add request routing into fasthttp.
Use third-party routers and web frameworks with fasthttp support:
* [Iris](https://github.com/kataras/iris)
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)
* [fasthttprouter](https://github.com/buaazp/fasthttprouter)
* [echo v2](https://github.com/labstack/echo)
See also [this issue](https://github.com/valyala/fasthttp/issues/9) for more info.
* *I detected data race in fasthttp!*
Cool! [File a bug](https://github.com/valyala/fasthttp/issues/new). But before
doing this check the following in your code:
* Make sure there are no references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx)
or to its' members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
* Make sure you call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
if there are references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx)
or to its' members, which may be accessed by other goroutines.
* *I didn't find an answer for my question here*
Try exploring [these questions](https://github.com/valyala/fasthttp/issues?q=label%3Aquestion).

4
vendor/github.com/valyala/fasthttp/TODO generated vendored Normal file
View File

@@ -0,0 +1,4 @@
- SessionClient with referer and cookies support.
- ProxyHandler similar to FSHandler.
- WebSockets. See https://tools.ietf.org/html/rfc6455 .
- HTTP/2.0. See https://tools.ietf.org/html/rfc7540 .

469
vendor/github.com/valyala/fasthttp/args.go generated vendored Normal file
View File

@@ -0,0 +1,469 @@
package fasthttp
import (
"bytes"
"errors"
"io"
"sync"
)
// AcquireArgs returns an empty Args object from the pool.
//
// The returned Args may be returned to the pool with ReleaseArgs
// when no longer needed. This allows reducing GC load.
func AcquireArgs() *Args {
return argsPool.Get().(*Args)
}
// ReleaseArgs returns the object acquired via AquireArgs to the pool.
//
// Do not access the released Args object, otherwise data races may occur.
func ReleaseArgs(a *Args) {
a.Reset()
argsPool.Put(a)
}
var argsPool = &sync.Pool{
New: func() interface{} {
return &Args{}
},
}
// Args represents query arguments.
//
// It is forbidden copying Args instances. Create new instances instead
// and use CopyTo().
//
// Args instance MUST NOT be used from concurrently running goroutines.
type Args struct {
noCopy noCopy
args []argsKV
buf []byte
}
type argsKV struct {
key []byte
value []byte
}
// Reset clears query args.
func (a *Args) Reset() {
a.args = a.args[:0]
}
// CopyTo copies all args to dst.
func (a *Args) CopyTo(dst *Args) {
dst.Reset()
dst.args = copyArgs(dst.args, a.args)
}
// VisitAll calls f for each existing arg.
//
// f must not retain references to key and value after returning.
// Make key and/or value copies if you need storing them after returning.
func (a *Args) VisitAll(f func(key, value []byte)) {
visitArgs(a.args, f)
}
// Len returns the number of query args.
func (a *Args) Len() int {
return len(a.args)
}
// Parse parses the given string containing query args.
func (a *Args) Parse(s string) {
a.buf = append(a.buf[:0], s...)
a.ParseBytes(a.buf)
}
// ParseBytes parses the given b containing query args.
func (a *Args) ParseBytes(b []byte) {
a.Reset()
var s argsScanner
s.b = b
var kv *argsKV
a.args, kv = allocArg(a.args)
for s.next(kv) {
if len(kv.key) > 0 || len(kv.value) > 0 {
a.args, kv = allocArg(a.args)
}
}
a.args = releaseArg(a.args)
}
// String returns string representation of query args.
func (a *Args) String() string {
return string(a.QueryString())
}
// QueryString returns query string for the args.
//
// The returned value is valid until the next call to Args methods.
func (a *Args) QueryString() []byte {
a.buf = a.AppendBytes(a.buf[:0])
return a.buf
}
// AppendBytes appends query string to dst and returns the extended dst.
func (a *Args) AppendBytes(dst []byte) []byte {
for i, n := 0, len(a.args); i < n; i++ {
kv := &a.args[i]
dst = AppendQuotedArg(dst, kv.key)
if len(kv.value) > 0 {
dst = append(dst, '=')
dst = AppendQuotedArg(dst, kv.value)
}
if i+1 < n {
dst = append(dst, '&')
}
}
return dst
}
// WriteTo writes query string to w.
//
// WriteTo implements io.WriterTo interface.
func (a *Args) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(a.QueryString())
return int64(n), err
}
// Del deletes argument with the given key from query args.
func (a *Args) Del(key string) {
a.args = delAllArgs(a.args, key)
}
// DelBytes deletes argument with the given key from query args.
func (a *Args) DelBytes(key []byte) {
a.args = delAllArgs(a.args, b2s(key))
}
// Add adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) Add(key, value string) {
a.args = appendArg(a.args, key, value)
}
// AddBytesK adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesK(key []byte, value string) {
a.args = appendArg(a.args, b2s(key), value)
}
// AddBytesV adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesV(key string, value []byte) {
a.args = appendArg(a.args, key, b2s(value))
}
// AddBytesKV adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesKV(key, value []byte) {
a.args = appendArg(a.args, b2s(key), b2s(value))
}
// Set sets 'key=value' argument.
func (a *Args) Set(key, value string) {
a.args = setArg(a.args, key, value)
}
// SetBytesK sets 'key=value' argument.
func (a *Args) SetBytesK(key []byte, value string) {
a.args = setArg(a.args, b2s(key), value)
}
// SetBytesV sets 'key=value' argument.
func (a *Args) SetBytesV(key string, value []byte) {
a.args = setArg(a.args, key, b2s(value))
}
// SetBytesKV sets 'key=value' argument.
func (a *Args) SetBytesKV(key, value []byte) {
a.args = setArgBytes(a.args, key, value)
}
// Peek returns query arg value for the given key.
//
// Returned value is valid until the next Args call.
func (a *Args) Peek(key string) []byte {
return peekArgStr(a.args, key)
}
// PeekBytes returns query arg value for the given key.
//
// Returned value is valid until the next Args call.
func (a *Args) PeekBytes(key []byte) []byte {
return peekArgBytes(a.args, key)
}
// PeekMulti returns all the arg values for the given key.
func (a *Args) PeekMulti(key string) [][]byte {
var values [][]byte
a.VisitAll(func(k, v []byte) {
if string(k) == key {
values = append(values, v)
}
})
return values
}
// PeekMultiBytes returns all the arg values for the given key.
func (a *Args) PeekMultiBytes(key []byte) [][]byte {
return a.PeekMulti(b2s(key))
}
// Has returns true if the given key exists in Args.
func (a *Args) Has(key string) bool {
return hasArg(a.args, key)
}
// HasBytes returns true if the given key exists in Args.
func (a *Args) HasBytes(key []byte) bool {
return hasArg(a.args, b2s(key))
}
// ErrNoArgValue is returned when Args value with the given key is missing.
var ErrNoArgValue = errors.New("no Args value for the given key")
// GetUint returns uint value for the given key.
func (a *Args) GetUint(key string) (int, error) {
value := a.Peek(key)
if len(value) == 0 {
return -1, ErrNoArgValue
}
return ParseUint(value)
}
// SetUint sets uint value for the given key.
func (a *Args) SetUint(key string, value int) {
bb := AcquireByteBuffer()
bb.B = AppendUint(bb.B[:0], value)
a.SetBytesV(key, bb.B)
ReleaseByteBuffer(bb)
}
// SetUintBytes sets uint value for the given key.
func (a *Args) SetUintBytes(key []byte, value int) {
a.SetUint(b2s(key), value)
}
// GetUintOrZero returns uint value for the given key.
//
// Zero (0) is returned on error.
func (a *Args) GetUintOrZero(key string) int {
n, err := a.GetUint(key)
if err != nil {
n = 0
}
return n
}
// GetUfloat returns ufloat value for the given key.
func (a *Args) GetUfloat(key string) (float64, error) {
value := a.Peek(key)
if len(value) == 0 {
return -1, ErrNoArgValue
}
return ParseUfloat(value)
}
// GetUfloatOrZero returns ufloat value for the given key.
//
// Zero (0) is returned on error.
func (a *Args) GetUfloatOrZero(key string) float64 {
f, err := a.GetUfloat(key)
if err != nil {
f = 0
}
return f
}
func visitArgs(args []argsKV, f func(k, v []byte)) {
for i, n := 0, len(args); i < n; i++ {
kv := &args[i]
f(kv.key, kv.value)
}
}
func copyArgs(dst, src []argsKV) []argsKV {
if cap(dst) < len(src) {
tmp := make([]argsKV, len(src))
copy(tmp, dst)
dst = tmp
}
n := len(src)
dst = dst[:n]
for i := 0; i < n; i++ {
dstKV := &dst[i]
srcKV := &src[i]
dstKV.key = append(dstKV.key[:0], srcKV.key...)
dstKV.value = append(dstKV.value[:0], srcKV.value...)
}
return dst
}
func delAllArgsBytes(args []argsKV, key []byte) []argsKV {
return delAllArgs(args, b2s(key))
}
func delAllArgs(args []argsKV, key string) []argsKV {
for i, n := 0, len(args); i < n; i++ {
kv := &args[i]
if key == string(kv.key) {
tmp := *kv
copy(args[i:], args[i+1:])
n--
args[n] = tmp
args = args[:n]
}
}
return args
}
func setArgBytes(h []argsKV, key, value []byte) []argsKV {
return setArg(h, b2s(key), b2s(value))
}
func setArg(h []argsKV, key, value string) []argsKV {
n := len(h)
for i := 0; i < n; i++ {
kv := &h[i]
if key == string(kv.key) {
kv.value = append(kv.value[:0], value...)
return h
}
}
return appendArg(h, key, value)
}
func appendArgBytes(h []argsKV, key, value []byte) []argsKV {
return appendArg(h, b2s(key), b2s(value))
}
func appendArg(args []argsKV, key, value string) []argsKV {
var kv *argsKV
args, kv = allocArg(args)
kv.key = append(kv.key[:0], key...)
kv.value = append(kv.value[:0], value...)
return args
}
func allocArg(h []argsKV) ([]argsKV, *argsKV) {
n := len(h)
if cap(h) > n {
h = h[:n+1]
} else {
h = append(h, argsKV{})
}
return h, &h[n]
}
func releaseArg(h []argsKV) []argsKV {
return h[:len(h)-1]
}
func hasArg(h []argsKV, key string) bool {
for i, n := 0, len(h); i < n; i++ {
kv := &h[i]
if key == string(kv.key) {
return true
}
}
return false
}
func peekArgBytes(h []argsKV, k []byte) []byte {
for i, n := 0, len(h); i < n; i++ {
kv := &h[i]
if bytes.Equal(kv.key, k) {
return kv.value
}
}
return nil
}
func peekArgStr(h []argsKV, k string) []byte {
for i, n := 0, len(h); i < n; i++ {
kv := &h[i]
if string(kv.key) == k {
return kv.value
}
}
return nil
}
type argsScanner struct {
b []byte
}
func (s *argsScanner) next(kv *argsKV) bool {
if len(s.b) == 0 {
return false
}
isKey := true
k := 0
for i, c := range s.b {
switch c {
case '=':
if isKey {
isKey = false
kv.key = decodeArg(kv.key, s.b[:i], true)
k = i + 1
}
case '&':
if isKey {
kv.key = decodeArg(kv.key, s.b[:i], true)
kv.value = kv.value[:0]
} else {
kv.value = decodeArg(kv.value, s.b[k:i], true)
}
s.b = s.b[i+1:]
return true
}
}
if isKey {
kv.key = decodeArg(kv.key, s.b, true)
kv.value = kv.value[:0]
} else {
kv.value = decodeArg(kv.value, s.b[k:], true)
}
s.b = s.b[len(s.b):]
return true
}
func decodeArg(dst, src []byte, decodePlus bool) []byte {
return decodeArgAppend(dst[:0], src, decodePlus)
}
func decodeArgAppend(dst, src []byte, decodePlus bool) []byte {
for i, n := 0, len(src); i < n; i++ {
c := src[i]
if c == '%' {
if i+2 >= n {
return append(dst, src[i:]...)
}
x1 := hexbyte2int(src[i+1])
x2 := hexbyte2int(src[i+2])
if x1 < 0 || x2 < 0 {
dst = append(dst, c)
} else {
dst = append(dst, byte(x1<<4|x2))
i += 2
}
} else if decodePlus && c == '+' {
dst = append(dst, ' ')
} else {
dst = append(dst, c)
}
}
return dst
}

453
vendor/github.com/valyala/fasthttp/args_test.go generated vendored Normal file
View File

@@ -0,0 +1,453 @@
package fasthttp
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
)
func TestArgsAdd(t *testing.T) {
var a Args
a.Add("foo", "bar")
a.Add("foo", "baz")
a.Add("foo", "1")
a.Add("ba", "23")
if a.Len() != 4 {
t.Fatalf("unexpected number of elements: %d. Expecting 4", a.Len())
}
s := a.String()
expectedS := "foo=bar&foo=baz&foo=1&ba=23"
if s != expectedS {
t.Fatalf("unexpected result: %q. Expecting %q", s, expectedS)
}
var a1 Args
a1.Parse(s)
if a1.Len() != 4 {
t.Fatalf("unexpected number of elements: %d. Expecting 4", a.Len())
}
var barFound, bazFound, oneFound, baFound bool
a1.VisitAll(func(k, v []byte) {
switch string(k) {
case "foo":
switch string(v) {
case "bar":
barFound = true
case "baz":
bazFound = true
case "1":
oneFound = true
default:
t.Fatalf("unexpected value %q", v)
}
case "ba":
if string(v) != "23" {
t.Fatalf("unexpected value: %q. Expecting %q", v, "23")
}
baFound = true
default:
t.Fatalf("unexpected key found %q", k)
}
})
if !barFound || !bazFound || !oneFound || !baFound {
t.Fatalf("something is missing: %v, %v, %v, %v", barFound, bazFound, oneFound, baFound)
}
}
func TestArgsAcquireReleaseSequential(t *testing.T) {
testArgsAcquireRelease(t)
}
func TestArgsAcquireReleaseConcurrent(t *testing.T) {
ch := make(chan struct{}, 10)
for i := 0; i < 10; i++ {
go func() {
testArgsAcquireRelease(t)
ch <- struct{}{}
}()
}
for i := 0; i < 10; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testArgsAcquireRelease(t *testing.T) {
a := AcquireArgs()
for i := 0; i < 10; i++ {
k := fmt.Sprintf("key_%d", i)
v := fmt.Sprintf("value_%d", i*3+123)
a.Set(k, v)
}
s := a.String()
a.Reset()
a.Parse(s)
for i := 0; i < 10; i++ {
k := fmt.Sprintf("key_%d", i)
expectedV := fmt.Sprintf("value_%d", i*3+123)
v := a.Peek(k)
if string(v) != expectedV {
t.Fatalf("unexpected value %q for key %q. Expecting %q", v, k, expectedV)
}
}
ReleaseArgs(a)
}
func TestArgsPeekMulti(t *testing.T) {
var a Args
a.Parse("foo=123&bar=121&foo=321&foo=&barz=sdf")
vv := a.PeekMulti("foo")
expectedVV := [][]byte{
[]byte("123"),
[]byte("321"),
[]byte(nil),
}
if !reflect.DeepEqual(vv, expectedVV) {
t.Fatalf("unexpected vv\n%#v\nExpecting\n%#v\n", vv, expectedVV)
}
vv = a.PeekMulti("aaaa")
if len(vv) > 0 {
t.Fatalf("expecting empty result for non-existing key. Got %#v", vv)
}
vv = a.PeekMulti("bar")
expectedVV = [][]byte{[]byte("121")}
if !reflect.DeepEqual(vv, expectedVV) {
t.Fatalf("unexpected vv\n%#v\nExpecting\n%#v\n", vv, expectedVV)
}
}
func TestArgsEscape(t *testing.T) {
testArgsEscape(t, "foo", "bar", "foo=bar")
testArgsEscape(t, "f.o,1:2/4", "~`!@#$%^&*()_-=+\\|/[]{};:'\"<>,./?",
"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")
}
func testArgsEscape(t *testing.T, k, v, expectedS string) {
var a Args
a.Set(k, v)
s := a.String()
if s != expectedS {
t.Fatalf("unexpected args %q. Expecting %q. k=%q, v=%q", s, expectedS, k, v)
}
}
func TestArgsWriteTo(t *testing.T) {
s := "foo=bar&baz=123&aaa=bbb"
var a Args
a.Parse(s)
var w ByteBuffer
n, err := a.WriteTo(&w)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != int64(len(s)) {
t.Fatalf("unexpected n: %d. Expecting %d", n, len(s))
}
result := string(w.B)
if result != s {
t.Fatalf("unexpected result %q. Expecting %q", result, s)
}
}
func TestArgsUint(t *testing.T) {
var a Args
a.SetUint("foo", 123)
a.SetUint("bar", 0)
a.SetUint("aaaa", 34566)
expectedS := "foo=123&bar=0&aaaa=34566"
s := string(a.QueryString())
if s != expectedS {
t.Fatalf("unexpected args %q. Expecting %q", s, expectedS)
}
if a.GetUintOrZero("foo") != 123 {
t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("foo"), 123)
}
if a.GetUintOrZero("bar") != 0 {
t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("bar"), 0)
}
if a.GetUintOrZero("aaaa") != 34566 {
t.Fatalf("unexpected arg value %d. Expecting %d", a.GetUintOrZero("aaaa"), 34566)
}
if string(a.Peek("foo")) != "123" {
t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("foo"), "123")
}
if string(a.Peek("bar")) != "0" {
t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("bar"), "0")
}
if string(a.Peek("aaaa")) != "34566" {
t.Fatalf("unexpected arg value %q. Expecting %q", a.Peek("aaaa"), "34566")
}
}
func TestArgsCopyTo(t *testing.T) {
var a Args
// empty args
testCopyTo(t, &a)
a.Set("foo", "bar")
testCopyTo(t, &a)
a.Set("xxx", "yyy")
testCopyTo(t, &a)
a.Del("foo")
testCopyTo(t, &a)
}
func testCopyTo(t *testing.T, a *Args) {
keys := make(map[string]struct{})
a.VisitAll(func(k, v []byte) {
keys[string(k)] = struct{}{}
})
var b Args
a.CopyTo(&b)
b.VisitAll(func(k, v []byte) {
if _, ok := keys[string(k)]; !ok {
t.Fatalf("unexpected key %q after copying from %q", k, a.String())
}
delete(keys, string(k))
})
if len(keys) > 0 {
t.Fatalf("missing keys %#v after copying from %q", keys, a.String())
}
}
func TestArgsVisitAll(t *testing.T) {
var a Args
a.Set("foo", "bar")
i := 0
a.VisitAll(func(k, v []byte) {
if string(k) != "foo" {
t.Fatalf("unexpected key %q. Expected %q", k, "foo")
}
if string(v) != "bar" {
t.Fatalf("unexpected value %q. Expected %q", v, "bar")
}
i++
})
if i != 1 {
t.Fatalf("unexpected number of VisitAll calls: %d. Expected %d", i, 1)
}
}
func TestArgsStringCompose(t *testing.T) {
var a Args
a.Set("foo", "bar")
a.Set("aa", "bbb")
a.Set("привет", "мир")
a.Set("", "xxxx")
a.Set("cvx", "")
expectedS := "foo=bar&aa=bbb&%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=%D0%BC%D0%B8%D1%80&=xxxx&cvx"
s := a.String()
if s != expectedS {
t.Fatalf("Unexpected string %q. Exected %q", s, expectedS)
}
}
func TestArgsString(t *testing.T) {
var a Args
testArgsString(t, &a, "")
testArgsString(t, &a, "foobar")
testArgsString(t, &a, "foo=bar")
testArgsString(t, &a, "foo=bar&baz=sss")
testArgsString(t, &a, "")
testArgsString(t, &a, "f%20o=x.x*-_8x%D0%BF%D1%80%D0%B8%D0%B2%D0%B5aaa&sdf=ss")
testArgsString(t, &a, "=asdfsdf")
}
func testArgsString(t *testing.T, a *Args, s string) {
a.Parse(s)
s1 := a.String()
if s != s1 {
t.Fatalf("Unexpected args %q. Expected %q", s1, s)
}
}
func TestArgsSetGetDel(t *testing.T) {
var a Args
if len(a.Peek("foo")) > 0 {
t.Fatalf("Unexpected value: %q", a.Peek("foo"))
}
if len(a.Peek("")) > 0 {
t.Fatalf("Unexpected value: %q", a.Peek(""))
}
a.Del("xxx")
for j := 0; j < 3; j++ {
for i := 0; i < 10; i++ {
k := fmt.Sprintf("foo%d", i)
v := fmt.Sprintf("bar_%d", i)
a.Set(k, v)
if string(a.Peek(k)) != v {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
}
}
}
for i := 0; i < 10; i++ {
k := fmt.Sprintf("foo%d", i)
v := fmt.Sprintf("bar_%d", i)
if string(a.Peek(k)) != v {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
}
a.Del(k)
if string(a.Peek(k)) != "" {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), "")
}
}
a.Parse("aaa=xxx&bb=aa")
if string(a.Peek("foo0")) != "" {
t.Fatalf("Unepxected value %q", a.Peek("foo0"))
}
if string(a.Peek("aaa")) != "xxx" {
t.Fatalf("Unexpected value %q. Expected %q", a.Peek("aaa"), "xxx")
}
if string(a.Peek("bb")) != "aa" {
t.Fatalf("Unexpected value %q. Expected %q", a.Peek("bb"), "aa")
}
for i := 0; i < 10; i++ {
k := fmt.Sprintf("xx%d", i)
v := fmt.Sprintf("yy%d", i)
a.Set(k, v)
if string(a.Peek(k)) != v {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
}
}
for i := 5; i < 10; i++ {
k := fmt.Sprintf("xx%d", i)
v := fmt.Sprintf("yy%d", i)
if string(a.Peek(k)) != v {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), v)
}
a.Del(k)
if string(a.Peek(k)) != "" {
t.Fatalf("Unexpected value: %q. Expected %q", a.Peek(k), "")
}
}
}
func TestArgsParse(t *testing.T) {
var a Args
// empty args
testArgsParse(t, &a, "", 0, "foo=", "bar=", "=")
// arg without value
testArgsParse(t, &a, "foo1", 1, "foo=", "bar=", "=")
// arg without value, but with equal sign
testArgsParse(t, &a, "foo2=", 1, "foo=", "bar=", "=")
// arg with value
testArgsParse(t, &a, "foo3=bar1", 1, "foo3=bar1", "bar=", "=")
// empty key
testArgsParse(t, &a, "=bar2", 1, "foo=", "=bar2", "bar2=")
// missing kv
testArgsParse(t, &a, "&&&&", 0, "foo=", "bar=", "=")
// multiple values with the same key
testArgsParse(t, &a, "x=1&x=2&x=3", 3, "x=1")
// multiple args
testArgsParse(t, &a, "&&&qw=er&tyx=124&&&zxc_ss=2234&&", 3, "qw=er", "tyx=124", "zxc_ss=2234")
// multiple args without values
testArgsParse(t, &a, "&&a&&b&&bar&baz", 4, "a=", "b=", "bar=", "baz=")
// values with '='
testArgsParse(t, &a, "zz=1&k=v=v=a=a=s", 2, "k=v=v=a=a=s", "zz=1")
// mixed '=' and '&'
testArgsParse(t, &a, "sss&z=dsf=&df", 3, "sss=", "z=dsf=", "df=")
// encoded args
testArgsParse(t, &a, "f+o%20o=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82+test", 1, "f o o=привет test")
// invalid percent encoding
testArgsParse(t, &a, "f%=x&qw%z=d%0k%20p&%%20=%%%20x", 3, "f%=x", "qw%z=d%0k p", "% =%% x")
// special chars
testArgsParse(t, &a, "a.b,c:d/e=f.g,h:i/q", 1, "a.b,c:d/e=f.g,h:i/q")
}
func TestArgsHas(t *testing.T) {
var a Args
// single arg
testArgsHas(t, &a, "foo", "foo")
testArgsHasNot(t, &a, "foo", "bar", "baz", "")
// multi args without values
testArgsHas(t, &a, "foo&bar", "foo", "bar")
testArgsHasNot(t, &a, "foo&bar", "", "aaaa")
// multi args
testArgsHas(t, &a, "b=xx&=aaa&c=", "b", "", "c")
testArgsHasNot(t, &a, "b=xx&=aaa&c=", "xx", "aaa", "foo")
// encoded args
testArgsHas(t, &a, "a+b=c+d%20%20e", "a b")
testArgsHasNot(t, &a, "a+b=c+d", "a+b", "c+d")
}
func testArgsHas(t *testing.T, a *Args, s string, expectedKeys ...string) {
a.Parse(s)
for _, key := range expectedKeys {
if !a.Has(key) {
t.Fatalf("Missing key %q in %q", key, s)
}
}
}
func testArgsHasNot(t *testing.T, a *Args, s string, unexpectedKeys ...string) {
a.Parse(s)
for _, key := range unexpectedKeys {
if a.Has(key) {
t.Fatalf("Unexpected key %q in %q", key, s)
}
}
}
func testArgsParse(t *testing.T, a *Args, s string, expectedLen int, expectedArgs ...string) {
a.Parse(s)
if a.Len() != expectedLen {
t.Fatalf("Unexpected args len %d. Expected %d. s=%q", a.Len(), expectedLen, s)
}
for _, xx := range expectedArgs {
tmp := strings.SplitN(xx, "=", 2)
k := tmp[0]
v := tmp[1]
buf := a.Peek(k)
if string(buf) != v {
t.Fatalf("Unexpected value for key=%q: %q. Expected %q. s=%q", k, buf, v, s)
}
}
}

30
vendor/github.com/valyala/fasthttp/args_timing_test.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
package fasthttp
import (
"bytes"
"testing"
)
func BenchmarkArgsParse(b *testing.B) {
s := []byte("foo=bar&baz=qqq&aaaaa=bbbb")
b.RunParallel(func(pb *testing.PB) {
var a Args
for pb.Next() {
a.ParseBytes(s)
}
})
}
func BenchmarkArgsPeek(b *testing.B) {
value := []byte("foobarbaz1234")
key := "foobarbaz"
b.RunParallel(func(pb *testing.PB) {
var a Args
a.SetBytesV(key, value)
for pb.Next() {
if !bytes.Equal(a.Peek(key), value) {
b.Fatalf("unexpected arg value %q. Expecting %q", a.Peek(key), value)
}
}
})
}

88
vendor/github.com/valyala/fasthttp/bytebuffer.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package fasthttp
import (
"sync"
)
const (
defaultByteBufferSize = 128
)
// ByteBuffer provides byte buffer, which can be used with fasthttp API
// in order to minimize memory allocations.
//
// ByteBuffer may be used with functions appending data to the given []byte
// slice. See example code for details.
//
// Use AcquireByteBuffer for obtaining an empty byte buffer.
type ByteBuffer struct {
// B is a byte buffer to use in append-like workloads.
// See example code for details.
B []byte
}
// Write implements io.Writer - it appends p to ByteBuffer.B
func (b *ByteBuffer) Write(p []byte) (int, error) {
b.B = append(b.B, p...)
return len(p), nil
}
// WriteString appends s to ByteBuffer.B
func (b *ByteBuffer) WriteString(s string) (int, error) {
b.B = append(b.B, s...)
return len(s), nil
}
// Set sets ByteBuffer.B to p
func (b *ByteBuffer) Set(p []byte) {
b.B = append(b.B[:0], p...)
}
// SetString sets ByteBuffer.B to s
func (b *ByteBuffer) SetString(s string) {
b.B = append(b.B[:0], s...)
}
// Reset makes ByteBuffer.B empty.
func (b *ByteBuffer) Reset() {
b.B = b.B[:0]
}
// AcquireByteBuffer returns an empty byte buffer from the pool.
//
// Acquired byte buffer may be returned to the pool via ReleaseByteBuffer call.
// This reduces the number of memory allocations required for byte buffer
// management.
func AcquireByteBuffer() *ByteBuffer {
return defaultByteBufferPool.Acquire()
}
// ReleaseByteBuffer returns byte buffer to the pool.
//
// ByteBuffer.B mustn't be touched after returning it to the pool.
// Otherwise data races occur.
func ReleaseByteBuffer(b *ByteBuffer) {
defaultByteBufferPool.Release(b)
}
type byteBufferPool struct {
pool sync.Pool
}
var defaultByteBufferPool byteBufferPool
func (p *byteBufferPool) Acquire() *ByteBuffer {
v := p.pool.Get()
if v == nil {
return &ByteBuffer{
B: make([]byte, 0, defaultByteBufferSize),
}
}
return v.(*ByteBuffer)
}
func (p *byteBufferPool) Release(b *ByteBuffer) {
b.B = b.B[:0]
p.pool.Put(b)
}

View File

@@ -0,0 +1,29 @@
package fasthttp_test
import (
"fmt"
"github.com/valyala/fasthttp"
)
func ExampleByteBuffer() {
// This request handler sets 'Your-IP' response header
// to 'Your IP is <ip>'. It uses ByteBuffer for constructing response
// header value with zero memory allocations.
yourIPRequestHandler := func(ctx *fasthttp.RequestCtx) {
b := fasthttp.AcquireByteBuffer()
b.B = append(b.B, "Your IP is <"...)
b.B = fasthttp.AppendIPv4(b.B, ctx.RemoteIP())
b.B = append(b.B, ">"...)
ctx.Response.Header.SetBytesV("Your-IP", b.B)
fmt.Fprintf(ctx, "Check response headers - they must contain 'Your-IP: %s'", b.B)
// It is safe to release byte buffer now, since it is
// no longer used.
fasthttp.ReleaseByteBuffer(b)
}
// Start fasthttp server returning your ip in response headers.
fasthttp.ListenAndServe(":8080", yourIPRequestHandler)
}

43
vendor/github.com/valyala/fasthttp/bytebuffer_test.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package fasthttp
import (
"fmt"
"testing"
"time"
)
func TestByteBufferAcquireReleaseSerial(t *testing.T) {
testByteBufferAcquireRelease(t)
}
func TestByteBufferAcquireReleaseConcurrent(t *testing.T) {
concurrency := 10
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testByteBufferAcquireRelease(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout!")
}
}
}
func testByteBufferAcquireRelease(t *testing.T) {
for i := 0; i < 10; i++ {
b := AcquireByteBuffer()
b.B = append(b.B, "num "...)
b.B = AppendUint(b.B, i)
expectedS := fmt.Sprintf("num %d", i)
if string(b.B) != expectedS {
t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS)
}
ReleaseByteBuffer(b)
}
}

View File

@@ -0,0 +1,32 @@
package fasthttp
import (
"bytes"
"testing"
)
func BenchmarkByteBufferWrite(b *testing.B) {
s := []byte("foobarbaz")
b.RunParallel(func(pb *testing.PB) {
var buf ByteBuffer
for pb.Next() {
for i := 0; i < 100; i++ {
buf.Write(s)
}
buf.Reset()
}
})
}
func BenchmarkBytesBufferWrite(b *testing.B) {
s := []byte("foobarbaz")
b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer
for pb.Next() {
for i := 0; i < 100; i++ {
buf.Write(s)
}
buf.Reset()
}
})
}

422
vendor/github.com/valyala/fasthttp/bytesconv.go generated vendored Normal file
View File

@@ -0,0 +1,422 @@
package fasthttp
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"math"
"net"
"reflect"
"sync"
"time"
"unsafe"
)
// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
func AppendHTMLEscape(dst []byte, s string) []byte {
var prev int
var sub string
for i, n := 0, len(s); i < n; i++ {
sub = ""
switch s[i] {
case '<':
sub = "&lt;"
case '>':
sub = "&gt;"
case '"':
sub = "&quot;"
case '\'':
sub = "&#39;"
}
if len(sub) > 0 {
dst = append(dst, s[prev:i]...)
dst = append(dst, sub...)
prev = i + 1
}
}
return append(dst, s[prev:]...)
}
// AppendHTMLEscapeBytes appends html-escaped s to dst and returns
// the extended dst.
func AppendHTMLEscapeBytes(dst, s []byte) []byte {
return AppendHTMLEscape(dst, b2s(s))
}
// AppendIPv4 appends string representation of the given ip v4 to dst
// and returns the extended dst.
func AppendIPv4(dst []byte, ip net.IP) []byte {
ip = ip.To4()
if ip == nil {
return append(dst, "non-v4 ip passed to AppendIPv4"...)
}
dst = AppendUint(dst, int(ip[0]))
for i := 1; i < 4; i++ {
dst = append(dst, '.')
dst = AppendUint(dst, int(ip[i]))
}
return dst
}
var errEmptyIPStr = errors.New("empty ip address string")
// ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.
func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
if len(ipStr) == 0 {
return dst, errEmptyIPStr
}
if len(dst) < net.IPv4len {
dst = make([]byte, net.IPv4len)
}
copy(dst, net.IPv4zero)
dst = dst.To4()
if dst == nil {
panic("BUG: dst must not be nil")
}
b := ipStr
for i := 0; i < 3; i++ {
n := bytes.IndexByte(b, '.')
if n < 0 {
return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
}
v, err := ParseUint(b[:n])
if err != nil {
return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
}
if v > 255 {
return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
}
dst[i] = byte(v)
b = b[n+1:]
}
v, err := ParseUint(b)
if err != nil {
return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
}
if v > 255 {
return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
}
dst[3] = byte(v)
return dst, nil
}
// AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
// to dst and returns the extended dst.
func AppendHTTPDate(dst []byte, date time.Time) []byte {
dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
copy(dst[len(dst)-3:], strGMT)
return dst
}
// ParseHTTPDate parses HTTP-compliant (RFC1123) date.
func ParseHTTPDate(date []byte) (time.Time, error) {
return time.Parse(time.RFC1123, b2s(date))
}
// AppendUint appends n to dst and returns the extended dst.
func AppendUint(dst []byte, n int) []byte {
if n < 0 {
panic("BUG: int must be positive")
}
var b [20]byte
buf := b[:]
i := len(buf)
var q int
for n >= 10 {
i--
q = n / 10
buf[i] = '0' + byte(n-q*10)
n = q
}
i--
buf[i] = '0' + byte(n)
dst = append(dst, buf[i:]...)
return dst
}
// ParseUint parses uint from buf.
func ParseUint(buf []byte) (int, error) {
v, n, err := parseUintBuf(buf)
if n != len(buf) {
return -1, errUnexpectedTrailingChar
}
return v, err
}
var (
errEmptyInt = errors.New("empty integer")
errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
errUnexpectedTrailingChar = errors.New("unexpected traling char found. Expecting 0-9")
errTooLongInt = errors.New("too long int")
)
func parseUintBuf(b []byte) (int, int, error) {
n := len(b)
if n == 0 {
return -1, 0, errEmptyInt
}
v := 0
for i := 0; i < n; i++ {
c := b[i]
k := c - '0'
if k > 9 {
if i == 0 {
return -1, i, errUnexpectedFirstChar
}
return v, i, nil
}
if i >= maxIntChars {
return -1, i, errTooLongInt
}
v = 10*v + int(k)
}
return v, n, nil
}
var (
errEmptyFloat = errors.New("empty float number")
errDuplicateFloatPoint = errors.New("duplicate point found in float number")
errUnexpectedFloatEnd = errors.New("unexpected end of float number")
errInvalidFloatExponent = errors.New("invalid float number exponent")
errUnexpectedFloatChar = errors.New("unexpected char found in float number")
)
// ParseUfloat parses unsigned float from buf.
func ParseUfloat(buf []byte) (float64, error) {
if len(buf) == 0 {
return -1, errEmptyFloat
}
b := buf
var v uint64
var offset = 1.0
var pointFound bool
for i, c := range b {
if c < '0' || c > '9' {
if c == '.' {
if pointFound {
return -1, errDuplicateFloatPoint
}
pointFound = true
continue
}
if c == 'e' || c == 'E' {
if i+1 >= len(b) {
return -1, errUnexpectedFloatEnd
}
b = b[i+1:]
minus := -1
switch b[0] {
case '+':
b = b[1:]
minus = 1
case '-':
b = b[1:]
default:
minus = 1
}
vv, err := ParseUint(b)
if err != nil {
return -1, errInvalidFloatExponent
}
return float64(v) * offset * math.Pow10(minus*int(vv)), nil
}
return -1, errUnexpectedFloatChar
}
v = 10*v + uint64(c-'0')
if pointFound {
offset /= 10
}
}
return float64(v) * offset, nil
}
var (
errEmptyHexNum = errors.New("empty hex number")
errTooLargeHexNum = errors.New("too large hex number")
)
func readHexInt(r *bufio.Reader) (int, error) {
n := 0
i := 0
var k int
for {
c, err := r.ReadByte()
if err != nil {
if err == io.EOF && i > 0 {
return n, nil
}
return -1, err
}
k = hexbyte2int(c)
if k < 0 {
if i == 0 {
return -1, errEmptyHexNum
}
r.UnreadByte()
return n, nil
}
if i >= maxHexIntChars {
return -1, errTooLargeHexNum
}
n = (n << 4) | k
i++
}
}
var hexIntBufPool sync.Pool
func writeHexInt(w *bufio.Writer, n int) error {
if n < 0 {
panic("BUG: int must be positive")
}
v := hexIntBufPool.Get()
if v == nil {
v = make([]byte, maxHexIntChars+1)
}
buf := v.([]byte)
i := len(buf) - 1
for {
buf[i] = int2hexbyte(n & 0xf)
n >>= 4
if n == 0 {
break
}
i--
}
_, err := w.Write(buf[i:])
hexIntBufPool.Put(v)
return err
}
func int2hexbyte(n int) byte {
if n < 10 {
return '0' + byte(n)
}
return 'a' + byte(n) - 10
}
func hexCharUpper(c byte) byte {
if c < 10 {
return '0' + c
}
return c - 10 + 'A'
}
var hex2intTable = func() []byte {
b := make([]byte, 255)
for i := byte(0); i < 255; i++ {
c := byte(0)
if i >= '0' && i <= '9' {
c = 1 + i - '0'
} else if i >= 'a' && i <= 'f' {
c = 1 + i - 'a' + 10
} else if i >= 'A' && i <= 'F' {
c = 1 + i - 'A' + 10
}
b[i] = c
}
return b
}()
func hexbyte2int(c byte) int {
return int(hex2intTable[c]) - 1
}
const toLower = 'a' - 'A'
func uppercaseByte(p *byte) {
c := *p
if c >= 'a' && c <= 'z' {
*p = c - toLower
}
}
func lowercaseByte(p *byte) {
c := *p
if c >= 'A' && c <= 'Z' {
*p = c + toLower
}
}
func lowercaseBytes(b []byte) {
for i, n := 0, len(b); i < n; i++ {
lowercaseByte(&b[i])
}
}
// b2s converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func b2s(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// s2b converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func s2b(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}
// AppendQuotedArg appends url-encoded src to dst and returns appended dst.
func AppendQuotedArg(dst, src []byte) []byte {
for _, c := range src {
// See http://www.w3.org/TR/html5/forms.html#form-submission-algorithm
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
c == '*' || c == '-' || c == '.' || c == '_' {
dst = append(dst, c)
} else {
dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
}
}
return dst
}
func appendQuotedPath(dst, src []byte) []byte {
for _, c := range src {
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
c == '/' || c == '.' || c == ',' || c == '=' || c == ':' || c == '&' || c == '~' || c == '-' || c == '_' {
dst = append(dst, c)
} else {
dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
}
}
return dst
}
// EqualBytesStr returns true if string(b) == s.
//
// This function has no performance benefits comparing to string(b) == s.
// It is left here for backwards compatibility only.
//
// This function is deperecated and may be deleted soon.
func EqualBytesStr(b []byte, s string) bool {
return string(b) == s
}
// AppendBytesStr appends src to dst and returns the extended dst.
//
// This function has no performance benefits comparing to append(dst, src...).
// It is left here for backwards compatibility only.
//
// This function is deprecated and may be deleted soon.
func AppendBytesStr(dst []byte, src string) []byte {
return append(dst, src...)
}

8
vendor/github.com/valyala/fasthttp/bytesconv_32.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// +build !amd64,!arm64,!ppc64
package fasthttp
const (
maxIntChars = 9
maxHexIntChars = 7
)

View File

@@ -0,0 +1,39 @@
// +build !amd64,!arm64,!ppc64
package fasthttp
import (
"testing"
)
func TestWriteHexInt(t *testing.T) {
testWriteHexInt(t, 0, "0")
testWriteHexInt(t, 1, "1")
testWriteHexInt(t, 0x123, "123")
testWriteHexInt(t, 0x7fffffff, "7fffffff")
}
func TestAppendUint(t *testing.T) {
testAppendUint(t, 0)
testAppendUint(t, 123)
testAppendUint(t, 0x7fffffff)
for i := 0; i < 2345; i++ {
testAppendUint(t, i)
}
}
func TestReadHexIntSuccess(t *testing.T) {
testReadHexIntSuccess(t, "0", 0)
testReadHexIntSuccess(t, "fF", 0xff)
testReadHexIntSuccess(t, "00abc", 0xabc)
testReadHexIntSuccess(t, "7ffffff", 0x7ffffff)
testReadHexIntSuccess(t, "000", 0)
testReadHexIntSuccess(t, "1234ZZZ", 0x1234)
}
func TestParseUintSuccess(t *testing.T) {
testParseUintSuccess(t, "0", 0)
testParseUintSuccess(t, "123", 123)
testParseUintSuccess(t, "123456789", 123456789)
}

8
vendor/github.com/valyala/fasthttp/bytesconv_64.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// +build amd64 arm64 ppc64
package fasthttp
const (
maxIntChars = 18
maxHexIntChars = 15
)

View File

@@ -0,0 +1,41 @@
// +build amd64 arm64 ppc64
package fasthttp
import (
"testing"
)
func TestWriteHexInt(t *testing.T) {
testWriteHexInt(t, 0, "0")
testWriteHexInt(t, 1, "1")
testWriteHexInt(t, 0x123, "123")
testWriteHexInt(t, 0x7fffffffffffffff, "7fffffffffffffff")
}
func TestAppendUint(t *testing.T) {
testAppendUint(t, 0)
testAppendUint(t, 123)
testAppendUint(t, 0x7fffffffffffffff)
for i := 0; i < 2345; i++ {
testAppendUint(t, i)
}
}
func TestReadHexIntSuccess(t *testing.T) {
testReadHexIntSuccess(t, "0", 0)
testReadHexIntSuccess(t, "fF", 0xff)
testReadHexIntSuccess(t, "00abc", 0xabc)
testReadHexIntSuccess(t, "7fffffff", 0x7fffffff)
testReadHexIntSuccess(t, "000", 0)
testReadHexIntSuccess(t, "1234ZZZ", 0x1234)
testReadHexIntSuccess(t, "7ffffffffffffff", 0x7ffffffffffffff)
}
func TestParseUintSuccess(t *testing.T) {
testParseUintSuccess(t, "0", 0)
testParseUintSuccess(t, "123", 123)
testParseUintSuccess(t, "1234567890", 1234567890)
testParseUintSuccess(t, "123456789012345678", 123456789012345678)
}

259
vendor/github.com/valyala/fasthttp/bytesconv_test.go generated vendored Normal file
View File

@@ -0,0 +1,259 @@
package fasthttp
import (
"bufio"
"bytes"
"fmt"
"net"
"testing"
"time"
)
func TestAppendHTMLEscape(t *testing.T) {
testAppendHTMLEscape(t, "", "")
testAppendHTMLEscape(t, "<", "&lt;")
testAppendHTMLEscape(t, "a", "a")
testAppendHTMLEscape(t, `><"''`, "&gt;&lt;&quot;&#39;&#39;")
testAppendHTMLEscape(t, "fo<b x='ss'>a</b>xxx", "fo&lt;b x=&#39;ss&#39;&gt;a&lt;/b&gt;xxx")
}
func testAppendHTMLEscape(t *testing.T, s, expectedS string) {
buf := AppendHTMLEscapeBytes(nil, []byte(s))
if string(buf) != expectedS {
t.Fatalf("unexpected html-escaped string %q. Expecting %q. Original string %q", buf, expectedS, s)
}
}
func TestParseIPv4(t *testing.T) {
testParseIPv4(t, "0.0.0.0", true)
testParseIPv4(t, "255.255.255.255", true)
testParseIPv4(t, "123.45.67.89", true)
// ipv6 shouldn't work
testParseIPv4(t, "2001:4860:0:2001::68", false)
// invalid ip
testParseIPv4(t, "foobar", false)
testParseIPv4(t, "1.2.3", false)
testParseIPv4(t, "123.456.789.11", false)
}
func testParseIPv4(t *testing.T, ipStr string, isValid bool) {
ip, err := ParseIPv4(nil, []byte(ipStr))
if isValid {
if err != nil {
t.Fatalf("unexpected error when parsing ip %q: %s", ipStr, err)
}
s := string(AppendIPv4(nil, ip))
if s != ipStr {
t.Fatalf("unexpected ip parsed %q. Expecting %q", s, ipStr)
}
} else {
if err == nil {
t.Fatalf("expecting error when parsing ip %q", ipStr)
}
}
}
func TestAppendIPv4(t *testing.T) {
testAppendIPv4(t, "0.0.0.0", true)
testAppendIPv4(t, "127.0.0.1", true)
testAppendIPv4(t, "8.8.8.8", true)
testAppendIPv4(t, "123.45.67.89", true)
// ipv6 shouldn't work
testAppendIPv4(t, "2001:4860:0:2001::68", false)
}
func testAppendIPv4(t *testing.T, ipStr string, isValid bool) {
ip := net.ParseIP(ipStr)
if ip == nil {
t.Fatalf("cannot parse ip %q", ipStr)
}
s := string(AppendIPv4(nil, ip))
if isValid {
if s != ipStr {
t.Fatalf("unepxected ip %q. Expecting %q", s, ipStr)
}
} else {
ipStr = "non-v4 ip passed to AppendIPv4"
if s != ipStr {
t.Fatalf("unexpected ip %q. Expecting %q", s, ipStr)
}
}
}
func testAppendUint(t *testing.T, n int) {
expectedS := fmt.Sprintf("%d", n)
s := AppendUint(nil, n)
if string(s) != expectedS {
t.Fatalf("unexpected uint %q. Expecting %q. n=%d", s, expectedS, n)
}
}
func testWriteHexInt(t *testing.T, n int, expectedS string) {
var w ByteBuffer
bw := bufio.NewWriter(&w)
if err := writeHexInt(bw, n); err != nil {
t.Fatalf("unexpected error when writing hex %x: %s", n, err)
}
if err := bw.Flush(); err != nil {
t.Fatalf("unexpected error when flushing hex %x: %s", n, err)
}
s := string(w.B)
if s != expectedS {
t.Fatalf("unexpected hex after writing %q. Expected %q", s, expectedS)
}
}
func TestReadHexIntError(t *testing.T) {
testReadHexIntError(t, "")
testReadHexIntError(t, "ZZZ")
testReadHexIntError(t, "-123")
testReadHexIntError(t, "+434")
}
func testReadHexIntError(t *testing.T, s string) {
r := bytes.NewBufferString(s)
br := bufio.NewReader(r)
n, err := readHexInt(br)
if err == nil {
t.Fatalf("expecting error when reading hex int %q", s)
}
if n >= 0 {
t.Fatalf("unexpected hex value read %d for hex int %q. must be negative", n, s)
}
}
func testReadHexIntSuccess(t *testing.T, s string, expectedN int) {
r := bytes.NewBufferString(s)
br := bufio.NewReader(r)
n, err := readHexInt(br)
if err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
if n != expectedN {
t.Fatalf("unexpected hex int %d. Expected %d. s=%q", n, expectedN, s)
}
}
func TestAppendHTTPDate(t *testing.T) {
d := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
s := string(AppendHTTPDate(nil, d))
expectedS := "Tue, 10 Nov 2009 23:00:00 GMT"
if s != expectedS {
t.Fatalf("unexpected date %q. Expecting %q", s, expectedS)
}
b := []byte("prefix")
s = string(AppendHTTPDate(b, d))
if s[:len(b)] != string(b) {
t.Fatalf("unexpected prefix %q. Expecting %q", s[:len(b)], b)
}
s = s[len(b):]
if s != expectedS {
t.Fatalf("unexpected date %q. Expecting %q", s, expectedS)
}
}
func TestParseUintError(t *testing.T) {
// empty string
testParseUintError(t, "")
// negative value
testParseUintError(t, "-123")
// non-num
testParseUintError(t, "foobar234")
// non-num chars at the end
testParseUintError(t, "123w")
// floating point num
testParseUintError(t, "1234.545")
// too big num
testParseUintError(t, "12345678901234567890")
}
func TestParseUfloatSuccess(t *testing.T) {
testParseUfloatSuccess(t, "0", 0)
testParseUfloatSuccess(t, "1.", 1.)
testParseUfloatSuccess(t, ".1", 0.1)
testParseUfloatSuccess(t, "123.456", 123.456)
testParseUfloatSuccess(t, "123", 123)
testParseUfloatSuccess(t, "1234e2", 1234e2)
testParseUfloatSuccess(t, "1234E-5", 1234E-5)
testParseUfloatSuccess(t, "1.234e+3", 1.234e+3)
}
func TestParseUfloatError(t *testing.T) {
// empty num
testParseUfloatError(t, "")
// negative num
testParseUfloatError(t, "-123.53")
// non-num chars
testParseUfloatError(t, "123sdfsd")
testParseUfloatError(t, "sdsf234")
testParseUfloatError(t, "sdfdf")
// non-num chars in exponent
testParseUfloatError(t, "123e3s")
testParseUfloatError(t, "12.3e-op")
testParseUfloatError(t, "123E+SS5")
// duplicate point
testParseUfloatError(t, "1.3.4")
// duplicate exponent
testParseUfloatError(t, "123e5e6")
// missing exponent
testParseUfloatError(t, "123534e")
}
func testParseUfloatError(t *testing.T, s string) {
n, err := ParseUfloat([]byte(s))
if err == nil {
t.Fatalf("Expecting error when parsing %q. obtained %f", s, n)
}
if n >= 0 {
t.Fatalf("Expecting negative num instead of %f when parsing %q", n, s)
}
}
func testParseUfloatSuccess(t *testing.T, s string, expectedF float64) {
f, err := ParseUfloat([]byte(s))
if err != nil {
t.Fatalf("Unexpected error when parsing %q: %s", s, err)
}
delta := f - expectedF
if delta < 0 {
delta = -delta
}
if delta > expectedF*1e-10 {
t.Fatalf("Unexpected value when parsing %q: %f. Expected %f", s, f, expectedF)
}
}
func testParseUintError(t *testing.T, s string) {
n, err := ParseUint([]byte(s))
if err == nil {
t.Fatalf("Expecting error when parsing %q. obtained %d", s, n)
}
if n >= 0 {
t.Fatalf("Unexpected n=%d when parsing %q. Expected negative num", n, s)
}
}
func testParseUintSuccess(t *testing.T, s string, expectedN int) {
n, err := ParseUint([]byte(s))
if err != nil {
t.Fatalf("Unexpected error when parsing %q: %s", s, err)
}
if n != expectedN {
t.Fatalf("Unexpected value %d. Expected %d. num=%q", n, expectedN, s)
}
}

View File

@@ -0,0 +1,167 @@
package fasthttp
import (
"bufio"
"html"
"net"
"testing"
)
func BenchmarkAppendHTMLEscape(b *testing.B) {
sOrig := "<b>foobarbazxxxyyyzzz</b>"
sExpected := string(AppendHTMLEscape(nil, sOrig))
b.RunParallel(func(pb *testing.PB) {
var buf []byte
for pb.Next() {
for i := 0; i < 10; i++ {
buf = AppendHTMLEscape(buf[:0], sOrig)
if string(buf) != sExpected {
b.Fatalf("unexpected escaped string: %s. Expecting %s", buf, sExpected)
}
}
}
})
}
func BenchmarkHTMLEscapeString(b *testing.B) {
sOrig := "<b>foobarbazxxxyyyzzz</b>"
sExpected := html.EscapeString(sOrig)
b.RunParallel(func(pb *testing.PB) {
var s string
for pb.Next() {
for i := 0; i < 10; i++ {
s = html.EscapeString(sOrig)
if s != sExpected {
b.Fatalf("unexpected escaped string: %s. Expecting %s", s, sExpected)
}
}
}
})
}
func BenchmarkParseIPv4(b *testing.B) {
ipStr := []byte("123.145.167.189")
b.RunParallel(func(pb *testing.PB) {
var ip net.IP
var err error
for pb.Next() {
ip, err = ParseIPv4(ip, ipStr)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
}
})
}
func BenchmarkAppendIPv4(b *testing.B) {
ip := net.ParseIP("123.145.167.189")
b.RunParallel(func(pb *testing.PB) {
var buf []byte
for pb.Next() {
buf = AppendIPv4(buf[:0], ip)
}
})
}
func BenchmarkInt2HexByte(b *testing.B) {
buf := []int{1, 0xf, 2, 0xd, 3, 0xe, 4, 0xa, 5, 0xb, 6, 0xc, 7, 0xf, 0, 0xf, 6, 0xd, 9, 8, 4, 0x5}
b.RunParallel(func(pb *testing.PB) {
var n int
for pb.Next() {
for _, n = range buf {
int2hexbyte(n)
}
}
})
}
func BenchmarkHexByte2Int(b *testing.B) {
buf := []byte("0A1B2c3d4E5F6C7a8D9ab7cd03ef")
b.RunParallel(func(pb *testing.PB) {
var c byte
for pb.Next() {
for _, c = range buf {
hexbyte2int(c)
}
}
})
}
func BenchmarkWriteHexInt(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var w ByteBuffer
bw := bufio.NewWriter(&w)
i := 0
for pb.Next() {
writeHexInt(bw, i)
i++
if i > 0x7fffffff {
i = 0
}
w.Reset()
bw.Reset(&w)
}
})
}
func BenchmarkParseUint(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
buf := []byte("1234567")
for pb.Next() {
n, err := ParseUint(buf)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if n != 1234567 {
b.Fatalf("unexpected result: %d. Expecting %s", n, buf)
}
}
})
}
func BenchmarkAppendUint(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var buf []byte
i := 0
for pb.Next() {
buf = AppendUint(buf[:0], i)
i++
if i > 0x7fffffff {
i = 0
}
}
})
}
func BenchmarkLowercaseBytesNoop(b *testing.B) {
src := []byte("foobarbaz_lowercased_all")
b.RunParallel(func(pb *testing.PB) {
s := make([]byte, len(src))
for pb.Next() {
copy(s, src)
lowercaseBytes(s)
}
})
}
func BenchmarkLowercaseBytesAll(b *testing.B) {
src := []byte("FOOBARBAZ_UPPERCASED_ALL")
b.RunParallel(func(pb *testing.PB) {
s := make([]byte, len(src))
for pb.Next() {
copy(s, src)
lowercaseBytes(s)
}
})
}
func BenchmarkLowercaseBytesMixed(b *testing.B) {
src := []byte("Foobarbaz_Uppercased_Mix")
b.RunParallel(func(pb *testing.PB) {
s := make([]byte, len(src))
for pb.Next() {
copy(s, src)
lowercaseBytes(s)
}
})
}

1888
vendor/github.com/valyala/fasthttp/client.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
package fasthttp_test
import (
"log"
"github.com/valyala/fasthttp"
)
func ExampleHostClient() {
// Perpare a client, which fetches webpages via HTTP proxy listening
// on the localhost:8080.
c := &fasthttp.HostClient{
Addr: "localhost:8080",
}
// Fetch google page via local proxy.
statusCode, body, err := c.Get(nil, "http://google.com/foo/bar")
if err != nil {
log.Fatalf("Error when loading google page through local proxy: %s", err)
}
if statusCode != fasthttp.StatusOK {
log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK)
}
useResponseBody(body)
// Fetch foobar page via local proxy. Reuse body buffer.
statusCode, body, err = c.Get(body, "http://foobar.com/google/com")
if err != nil {
log.Fatalf("Error when loading foobar page through local proxy: %s", err)
}
if statusCode != fasthttp.StatusOK {
log.Fatalf("Unexpected status code: %d. Expecting %d", statusCode, fasthttp.StatusOK)
}
useResponseBody(body)
}
func useResponseBody(body []byte) {
// Do something with body :)
}

914
vendor/github.com/valyala/fasthttp/client_test.go generated vendored Normal file
View File

@@ -0,0 +1,914 @@
package fasthttp
import (
"crypto/tls"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp/fasthttputil"
)
func TestPipelineClientDoSerial(t *testing.T) {
testPipelineClientDoConcurrent(t, 1, 0)
}
func TestPipelineClientDoConcurrent(t *testing.T) {
testPipelineClientDoConcurrent(t, 10, 0)
}
func TestPipelineClientDoBatchDelayConcurrent(t *testing.T) {
testPipelineClientDoConcurrent(t, 10, 5*time.Millisecond)
}
func testPipelineClientDoConcurrent(t *testing.T, concurrency int, maxBatchDelay time.Duration) {
ln := fasthttputil.NewInmemoryListener()
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.WriteString("OK")
},
}
serverStopCh := make(chan struct{})
go func() {
if err := s.Serve(ln); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(serverStopCh)
}()
c := &PipelineClient{
Dial: func(addr string) (net.Conn, error) {
return ln.Dial()
},
MaxIdleConnDuration: 23 * time.Millisecond,
MaxPendingRequests: 6,
MaxBatchDelay: maxBatchDelay,
Logger: &customLogger{},
}
clientStopCh := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testPipelineClientDo(t, c)
clientStopCh <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-clientStopCh:
case <-time.After(3 * time.Second):
t.Fatalf("timeout")
}
}
if c.PendingRequests() != 0 {
t.Fatalf("unexpected number of pending requests: %d. Expecting zero", c.PendingRequests())
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case <-serverStopCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
func testPipelineClientDo(t *testing.T, c *PipelineClient) {
var err error
req := AcquireRequest()
req.SetRequestURI("http://foobar/baz")
resp := AcquireResponse()
for i := 0; i < 10; i++ {
if i&1 == 0 {
err = c.DoTimeout(req, resp, time.Second)
} else {
err = c.Do(req, resp)
}
if err != nil {
if err == ErrPipelineOverflow {
time.Sleep(10 * time.Millisecond)
continue
}
t.Fatalf("unexpected error on iteration %d: %s", i, err)
}
if resp.StatusCode() != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
}
body := string(resp.Body())
if body != "OK" {
t.Fatalf("unexpected body: %q. Expecting %q", body, "OK")
}
// sleep for a while, so the connection to the host may expire.
if i%5 == 0 {
time.Sleep(30 * time.Millisecond)
}
}
ReleaseRequest(req)
ReleaseResponse(resp)
}
func TestClientDoTimeoutDisableNormalizing(t *testing.T) {
ln := fasthttputil.NewInmemoryListener()
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.Response.Header.Set("foo-BAR", "baz")
},
DisableHeaderNamesNormalizing: true,
}
serverStopCh := make(chan struct{})
go func() {
if err := s.Serve(ln); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(serverStopCh)
}()
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return ln.Dial()
},
DisableHeaderNamesNormalizing: true,
}
var req Request
req.SetRequestURI("http://aaaai.com/bsdf?sddfsd")
var resp Response
for i := 0; i < 5; i++ {
if err := c.DoTimeout(&req, &resp, time.Second); err != nil {
t.Fatalf("unexpected error: %s", err)
}
hv := resp.Header.Peek("foo-BAR")
if string(hv) != "baz" {
t.Fatalf("unexpected header value: %q. Expecting %q", hv, "baz")
}
hv = resp.Header.Peek("Foo-Bar")
if len(hv) > 0 {
t.Fatalf("unexpected non-empty header value %q", hv)
}
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case <-serverStopCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
func TestHostClientMaxConnDuration(t *testing.T) {
ln := fasthttputil.NewInmemoryListener()
connectionCloseCount := uint32(0)
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.WriteString("abcd")
if ctx.Request.ConnectionClose() {
atomic.AddUint32(&connectionCloseCount, 1)
}
},
}
serverStopCh := make(chan struct{})
go func() {
if err := s.Serve(ln); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(serverStopCh)
}()
c := &HostClient{
Addr: "foobar",
Dial: func(addr string) (net.Conn, error) {
return ln.Dial()
},
MaxConnDuration: 10 * time.Millisecond,
}
for i := 0; i < 5; i++ {
statusCode, body, err := c.Get(nil, "http://aaaa.com/bbb/cc")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code %d. Expecting %d", statusCode, StatusOK)
}
if string(body) != "abcd" {
t.Fatalf("unexpected body %q. Expecting %q", body, "abcd")
}
time.Sleep(c.MaxConnDuration)
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case <-serverStopCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
if connectionCloseCount == 0 {
t.Fatalf("expecting at least one 'Connection: close' request header")
}
}
func TestHostClientMultipleAddrs(t *testing.T) {
ln := fasthttputil.NewInmemoryListener()
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.Write(ctx.Host())
ctx.SetConnectionClose()
},
}
serverStopCh := make(chan struct{})
go func() {
if err := s.Serve(ln); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(serverStopCh)
}()
dialsCount := make(map[string]int)
c := &HostClient{
Addr: "foo,bar,baz",
Dial: func(addr string) (net.Conn, error) {
dialsCount[addr]++
return ln.Dial()
},
}
for i := 0; i < 9; i++ {
statusCode, body, err := c.Get(nil, "http://foobar/baz/aaa?bbb=ddd")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code %d. Expecting %d", statusCode, StatusOK)
}
if string(body) != "foobar" {
t.Fatalf("unexpected body %q. Expecting %q", body, "foobar")
}
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case <-serverStopCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
if len(dialsCount) != 3 {
t.Fatalf("unexpected dialsCount size %d. Expecting 3", len(dialsCount))
}
for _, k := range []string{"foo", "bar", "baz"} {
if dialsCount[k] != 3 {
t.Fatalf("unexpected dialsCount for %q. Expecting 3", k)
}
}
}
func TestClientFollowRedirects(t *testing.T) {
addr := "127.0.0.1:55234"
s := &Server{
Handler: func(ctx *RequestCtx) {
switch string(ctx.Path()) {
case "/foo":
u := ctx.URI()
u.Update("/xy?z=wer")
ctx.Redirect(u.String(), StatusFound)
case "/xy":
u := ctx.URI()
u.Update("/bar")
ctx.Redirect(u.String(), StatusFound)
default:
ctx.Success("text/plain", ctx.Path())
}
},
}
ln, err := net.Listen("tcp4", addr)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
serverStopCh := make(chan struct{})
go func() {
if err := s.Serve(ln); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(serverStopCh)
}()
uri := fmt.Sprintf("http://%s/foo", addr)
for i := 0; i < 10; i++ {
statusCode, body, err := GetTimeout(nil, uri, time.Second)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d", statusCode)
}
if string(body) != "/bar" {
t.Fatalf("unexpected response %q. Expecting %q", body, "/bar")
}
}
uri = fmt.Sprintf("http://%s/aaab/sss", addr)
for i := 0; i < 10; i++ {
statusCode, body, err := Get(nil, uri)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d", statusCode)
}
if string(body) != "/aaab/sss" {
t.Fatalf("unexpected response %q. Expecting %q", body, "/aaab/sss")
}
}
}
func TestClientGetTimeoutSuccess(t *testing.T) {
addr := "127.0.0.1:56889"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
testClientGetTimeoutSuccess(t, &defaultClient, addr, 100)
}
func TestClientGetTimeoutSuccessConcurrent(t *testing.T) {
addr := "127.0.0.1:56989"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testClientGetTimeoutSuccess(t, &defaultClient, addr, 100)
}()
}
wg.Wait()
}
func TestClientDoTimeoutSuccess(t *testing.T) {
addr := "127.0.0.1:63897"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
testClientDoTimeoutSuccess(t, &defaultClient, addr, 100)
}
func TestClientDoTimeoutSuccessConcurrent(t *testing.T) {
addr := "127.0.0.1:63898"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testClientDoTimeoutSuccess(t, &defaultClient, addr, 100)
}()
}
wg.Wait()
}
func TestClientGetTimeoutError(t *testing.T) {
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return &readTimeoutConn{t: time.Second}, nil
},
}
testClientGetTimeoutError(t, c, 100)
}
func TestClientGetTimeoutErrorConcurrent(t *testing.T) {
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return &readTimeoutConn{t: time.Second}, nil
},
MaxConnsPerHost: 1000,
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testClientGetTimeoutError(t, c, 100)
}()
}
wg.Wait()
}
func TestClientDoTimeoutError(t *testing.T) {
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return &readTimeoutConn{t: time.Second}, nil
},
}
testClientDoTimeoutError(t, c, 100)
}
func TestClientDoTimeoutErrorConcurrent(t *testing.T) {
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return &readTimeoutConn{t: time.Second}, nil
},
MaxConnsPerHost: 1000,
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testClientDoTimeoutError(t, c, 100)
}()
}
wg.Wait()
}
func testClientDoTimeoutError(t *testing.T, c *Client, n int) {
var req Request
var resp Response
req.SetRequestURI("http://foobar.com/baz")
for i := 0; i < n; i++ {
err := c.DoTimeout(&req, &resp, time.Millisecond)
if err == nil {
t.Fatalf("expecting error")
}
if err != ErrTimeout {
t.Fatalf("unexpected error: %s. Expecting %s", err, ErrTimeout)
}
}
}
func testClientGetTimeoutError(t *testing.T, c *Client, n int) {
buf := make([]byte, 10)
for i := 0; i < n; i++ {
statusCode, body, err := c.GetTimeout(buf, "http://foobar.com/baz", time.Millisecond)
if err == nil {
t.Fatalf("expecting error")
}
if err != ErrTimeout {
t.Fatalf("unexpected error: %s. Expecting %s", err, ErrTimeout)
}
if statusCode != 0 {
t.Fatalf("unexpected statusCode=%d. Expecting %d", statusCode, 0)
}
if body == nil {
t.Fatalf("body must be non-nil")
}
}
}
type readTimeoutConn struct {
net.Conn
t time.Duration
}
func (r *readTimeoutConn) Read(p []byte) (int, error) {
time.Sleep(r.t)
return 0, io.EOF
}
func (r *readTimeoutConn) Write(p []byte) (int, error) {
return len(p), nil
}
func (r *readTimeoutConn) Close() error {
return nil
}
func TestClientIdempotentRequest(t *testing.T) {
dialsCount := 0
c := &Client{
Dial: func(addr string) (net.Conn, error) {
switch dialsCount {
case 0:
dialsCount++
return &readErrorConn{}, nil
case 1:
dialsCount++
return &singleReadConn{
s: "HTTP/1.1 345 OK\r\nContent-Type: foobar\r\nContent-Length: 7\r\n\r\n0123456",
}, nil
default:
t.Fatalf("unexpected number of dials: %d", dialsCount)
}
panic("unreachable")
},
}
statusCode, body, err := c.Get(nil, "http://foobar/a/b")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if statusCode != 345 {
t.Fatalf("unexpected status code: %d. Expecting 345", statusCode)
}
if string(body) != "0123456" {
t.Fatalf("unexpected body: %q. Expecting %q", body, "0123456")
}
var args Args
dialsCount = 0
statusCode, body, err = c.Post(nil, "http://foobar/a/b", &args)
if err == nil {
t.Fatalf("expecting error")
}
dialsCount = 0
statusCode, body, err = c.Post(nil, "http://foobar/a/b", nil)
if err == nil {
t.Fatalf("expecting error")
}
}
type readErrorConn struct {
net.Conn
}
func (r *readErrorConn) Read(p []byte) (int, error) {
return 0, fmt.Errorf("error")
}
func (r *readErrorConn) Write(p []byte) (int, error) {
return len(p), nil
}
func (r *readErrorConn) Close() error {
return nil
}
type singleReadConn struct {
net.Conn
s string
n int
}
func (r *singleReadConn) Read(p []byte) (int, error) {
if len(r.s) == r.n {
return 0, io.EOF
}
n := copy(p, []byte(r.s[r.n:]))
r.n += n
return n, nil
}
func (r *singleReadConn) Write(p []byte) (int, error) {
return len(p), nil
}
func (r *singleReadConn) Close() error {
return nil
}
func TestClientHTTPSConcurrent(t *testing.T) {
addrHTTP := "127.0.0.1:56793"
sHTTP := startEchoServer(t, "tcp", addrHTTP)
defer sHTTP.Stop()
addrHTTPS := "127.0.0.1:56794"
sHTTPS := startEchoServerTLS(t, "tcp", addrHTTPS)
defer sHTTPS.Stop()
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
addr := "http://" + addrHTTP
if i&1 != 0 {
addr = "https://" + addrHTTPS
}
go func() {
defer wg.Done()
testClientGet(t, &defaultClient, addr, 20)
testClientPost(t, &defaultClient, addr, 10)
}()
}
wg.Wait()
}
func TestClientManyServers(t *testing.T) {
var addrs []string
for i := 0; i < 10; i++ {
addr := fmt.Sprintf("127.0.0.1:%d", 56904+i)
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addrs = append(addrs, addr)
}
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
addr := "http://" + addrs[i]
go func() {
defer wg.Done()
testClientGet(t, &defaultClient, addr, 20)
testClientPost(t, &defaultClient, addr, 10)
}()
}
wg.Wait()
}
func TestClientGet(t *testing.T) {
addr := "127.0.0.1:56789"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
testClientGet(t, &defaultClient, addr, 100)
}
func TestClientPost(t *testing.T) {
addr := "127.0.0.1:56798"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
testClientPost(t, &defaultClient, addr, 100)
}
func TestClientConcurrent(t *testing.T) {
addr := "127.0.0.1:55780"
s := startEchoServer(t, "tcp", addr)
defer s.Stop()
addr = "http://" + addr
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testClientGet(t, &defaultClient, addr, 30)
testClientPost(t, &defaultClient, addr, 10)
}()
}
wg.Wait()
}
func skipIfNotUnix(tb testing.TB) {
switch runtime.GOOS {
case "android", "nacl", "plan9", "windows":
tb.Skipf("%s does not support unix sockets", runtime.GOOS)
}
if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
tb.Skip("iOS does not support unix, unixgram")
}
}
func TestHostClientGet(t *testing.T) {
skipIfNotUnix(t)
addr := "TestHostClientGet.unix"
s := startEchoServer(t, "unix", addr)
defer s.Stop()
c := createEchoClient(t, "unix", addr)
testHostClientGet(t, c, 100)
}
func TestHostClientPost(t *testing.T) {
skipIfNotUnix(t)
addr := "./TestHostClientPost.unix"
s := startEchoServer(t, "unix", addr)
defer s.Stop()
c := createEchoClient(t, "unix", addr)
testHostClientPost(t, c, 100)
}
func TestHostClientConcurrent(t *testing.T) {
skipIfNotUnix(t)
addr := "./TestHostClientConcurrent.unix"
s := startEchoServer(t, "unix", addr)
defer s.Stop()
c := createEchoClient(t, "unix", addr)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
testHostClientGet(t, c, 30)
testHostClientPost(t, c, 10)
}()
}
wg.Wait()
}
func testClientGet(t *testing.T, c clientGetter, addr string, n int) {
var buf []byte
for i := 0; i < n; i++ {
uri := fmt.Sprintf("%s/foo/%d?bar=baz", addr, i)
statusCode, body, err := c.Get(buf, uri)
buf = body
if err != nil {
t.Fatalf("unexpected error when doing http request: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
resultURI := string(body)
if strings.HasPrefix(uri, "https") {
resultURI = uri[:5] + resultURI[4:]
}
if resultURI != uri {
t.Fatalf("unexpected uri %q. Expecting %q", resultURI, uri)
}
}
}
func testClientDoTimeoutSuccess(t *testing.T, c *Client, addr string, n int) {
var req Request
var resp Response
for i := 0; i < n; i++ {
uri := fmt.Sprintf("%s/foo/%d?bar=baz", addr, i)
req.SetRequestURI(uri)
if err := c.DoTimeout(&req, &resp, time.Second); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode() != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
}
resultURI := string(resp.Body())
if strings.HasPrefix(uri, "https") {
resultURI = uri[:5] + resultURI[4:]
}
if resultURI != uri {
t.Fatalf("unexpected uri %q. Expecting %q", resultURI, uri)
}
}
}
func testClientGetTimeoutSuccess(t *testing.T, c *Client, addr string, n int) {
var buf []byte
for i := 0; i < n; i++ {
uri := fmt.Sprintf("%s/foo/%d?bar=baz", addr, i)
statusCode, body, err := c.GetTimeout(buf, uri, time.Second)
buf = body
if err != nil {
t.Fatalf("unexpected error when doing http request: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
resultURI := string(body)
if strings.HasPrefix(uri, "https") {
resultURI = uri[:5] + resultURI[4:]
}
if resultURI != uri {
t.Fatalf("unexpected uri %q. Expecting %q", resultURI, uri)
}
}
}
func testClientPost(t *testing.T, c clientPoster, addr string, n int) {
var buf []byte
var args Args
for i := 0; i < n; i++ {
uri := fmt.Sprintf("%s/foo/%d?bar=baz", addr, i)
args.Set("xx", fmt.Sprintf("yy%d", i))
args.Set("zzz", fmt.Sprintf("qwe_%d", i))
argsS := args.String()
statusCode, body, err := c.Post(buf, uri, &args)
buf = body
if err != nil {
t.Fatalf("unexpected error when doing http request: %s", err)
}
if statusCode != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
s := string(body)
if s != argsS {
t.Fatalf("unexpected response %q. Expecting %q", s, argsS)
}
}
}
func testHostClientGet(t *testing.T, c *HostClient, n int) {
testClientGet(t, c, "http://google.com", n)
}
func testHostClientPost(t *testing.T, c *HostClient, n int) {
testClientPost(t, c, "http://post-host.com", n)
}
type clientPoster interface {
Post(dst []byte, uri string, postArgs *Args) (int, []byte, error)
}
type clientGetter interface {
Get(dst []byte, uri string) (int, []byte, error)
}
func createEchoClient(t *testing.T, network, addr string) *HostClient {
return &HostClient{
Addr: addr,
Dial: func(addr string) (net.Conn, error) {
return net.Dial(network, addr)
},
}
}
type testEchoServer struct {
s *Server
ln net.Listener
ch chan struct{}
t *testing.T
}
func (s *testEchoServer) Stop() {
s.ln.Close()
select {
case <-s.ch:
case <-time.After(time.Second):
s.t.Fatalf("timeout when waiting for server close")
}
}
func startEchoServerTLS(t *testing.T, network, addr string) *testEchoServer {
return startEchoServerExt(t, network, addr, true)
}
func startEchoServer(t *testing.T, network, addr string) *testEchoServer {
return startEchoServerExt(t, network, addr, false)
}
func startEchoServerExt(t *testing.T, network, addr string, isTLS bool) *testEchoServer {
if network == "unix" {
os.Remove(addr)
}
var ln net.Listener
var err error
if isTLS {
certFile := "./ssl-cert-snakeoil.pem"
keyFile := "./ssl-cert-snakeoil.key"
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
t.Fatalf("Cannot load TLS certificate: %s", err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
}
ln, err = tls.Listen(network, addr, tlsConfig)
} else {
ln, err = net.Listen(network, addr)
}
if err != nil {
t.Fatalf("cannot listen %q: %s", addr, err)
}
s := &Server{
Handler: func(ctx *RequestCtx) {
if ctx.IsGet() {
ctx.Success("text/plain", ctx.URI().FullURI())
} else if ctx.IsPost() {
ctx.PostArgs().WriteTo(ctx)
}
},
}
ch := make(chan struct{})
go func() {
err := s.Serve(ln)
if err != nil {
t.Fatalf("unexpected error returned from Serve(): %s", err)
}
close(ch)
}()
return &testEchoServer{
s: s,
ln: ln,
ch: ch,
t: t,
}
}

View File

@@ -0,0 +1,633 @@
package fasthttp
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp/fasthttputil"
)
type fakeClientConn struct {
net.Conn
s []byte
n int
ch chan struct{}
}
func (c *fakeClientConn) Write(b []byte) (int, error) {
c.ch <- struct{}{}
return len(b), nil
}
func (c *fakeClientConn) Read(b []byte) (int, error) {
if c.n == 0 {
// wait for request :)
<-c.ch
}
n := 0
for len(b) > 0 {
if c.n == len(c.s) {
c.n = 0
return n, nil
}
n = copy(b, c.s[c.n:])
c.n += n
b = b[n:]
}
return n, nil
}
func (c *fakeClientConn) Close() error {
releaseFakeServerConn(c)
return nil
}
func releaseFakeServerConn(c *fakeClientConn) {
c.n = 0
fakeClientConnPool.Put(c)
}
func acquireFakeServerConn(s []byte) *fakeClientConn {
v := fakeClientConnPool.Get()
if v == nil {
c := &fakeClientConn{
s: s,
ch: make(chan struct{}, 1),
}
return c
}
return v.(*fakeClientConn)
}
var fakeClientConnPool sync.Pool
func BenchmarkClientGetTimeoutFastServer(b *testing.B) {
body := []byte("123456789099")
s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body))
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return acquireFakeServerConn(s), nil
},
}
nn := uint32(0)
b.RunParallel(func(pb *testing.PB) {
url := fmt.Sprintf("http://foobar%d.com/aaa/bbb", atomic.AddUint32(&nn, 1))
var statusCode int
var bodyBuf []byte
var err error
for pb.Next() {
statusCode, bodyBuf, err = c.GetTimeout(bodyBuf[:0], url, time.Second)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
b.Fatalf("unexpected status code: %d", statusCode)
}
if !bytes.Equal(bodyBuf, body) {
b.Fatalf("unexpected response body: %q. Expected %q", bodyBuf, body)
}
}
})
}
func BenchmarkClientDoFastServer(b *testing.B) {
body := []byte("012345678912")
s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body))
c := &Client{
Dial: func(addr string) (net.Conn, error) {
return acquireFakeServerConn(s), nil
},
MaxConnsPerHost: runtime.GOMAXPROCS(-1),
}
nn := uint32(0)
b.RunParallel(func(pb *testing.PB) {
var req Request
var resp Response
req.Header.SetRequestURI(fmt.Sprintf("http://foobar%d.com/aaa/bbb", atomic.AddUint32(&nn, 1)))
for pb.Next() {
if err := c.Do(&req, &resp); err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.Header.StatusCode() != StatusOK {
b.Fatalf("unexpected status code: %d", resp.Header.StatusCode())
}
if !bytes.Equal(resp.Body(), body) {
b.Fatalf("unexpected response body: %q. Expected %q", resp.Body(), body)
}
}
})
}
func BenchmarkNetHTTPClientDoFastServer(b *testing.B) {
body := []byte("012345678912")
s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body))
c := &http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
return acquireFakeServerConn(s), nil
},
MaxIdleConnsPerHost: runtime.GOMAXPROCS(-1),
},
}
nn := uint32(0)
b.RunParallel(func(pb *testing.PB) {
req, err := http.NewRequest("GET", fmt.Sprintf("http://foobar%d.com/aaa/bbb", atomic.AddUint32(&nn, 1)), nil)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
for pb.Next() {
resp, err := c.Do(req)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status code: %d", resp.StatusCode)
}
respBody, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
b.Fatalf("unexpected error when reading response body: %s", err)
}
if !bytes.Equal(respBody, body) {
b.Fatalf("unexpected response body: %q. Expected %q", respBody, body)
}
}
})
}
func fasthttpEchoHandler(ctx *RequestCtx) {
ctx.Success("text/plain", ctx.RequestURI())
}
func nethttpEchoHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(r.RequestURI))
}
func BenchmarkClientGetEndToEnd1TCP(b *testing.B) {
benchmarkClientGetEndToEndTCP(b, 1)
}
func BenchmarkClientGetEndToEnd10TCP(b *testing.B) {
benchmarkClientGetEndToEndTCP(b, 10)
}
func BenchmarkClientGetEndToEnd100TCP(b *testing.B) {
benchmarkClientGetEndToEndTCP(b, 100)
}
func benchmarkClientGetEndToEndTCP(b *testing.B, parallelism int) {
addr := "127.0.0.1:8543"
ln, err := net.Listen("tcp4", addr)
if err != nil {
b.Fatalf("cannot listen %q: %s", addr, err)
}
ch := make(chan struct{})
go func() {
if err := Serve(ln, fasthttpEchoHandler); err != nil {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &Client{
MaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,
}
requestURI := "/foo/bar?baz=123"
url := "http://" + addr + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
var buf []byte
for pb.Next() {
statusCode, body, err := c.Get(buf, url)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
if string(body) != requestURI {
b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
}
buf = body
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkNetHTTPClientGetEndToEnd1TCP(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndTCP(b, 1)
}
func BenchmarkNetHTTPClientGetEndToEnd10TCP(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndTCP(b, 10)
}
func BenchmarkNetHTTPClientGetEndToEnd100TCP(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndTCP(b, 100)
}
func benchmarkNetHTTPClientGetEndToEndTCP(b *testing.B, parallelism int) {
addr := "127.0.0.1:8542"
ln, err := net.Listen("tcp4", addr)
if err != nil {
b.Fatalf("cannot listen %q: %s", addr, err)
}
ch := make(chan struct{})
go func() {
if err := http.Serve(ln, http.HandlerFunc(nethttpEchoHandler)); err != nil && !strings.Contains(
err.Error(), "use of closed network connection") {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),
},
}
requestURI := "/foo/bar?baz=123"
url := "http://" + addr + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := c.Get(url)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
b.Fatalf("unexpected error when reading response body: %s", err)
}
if string(body) != requestURI {
b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
}
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkClientGetEndToEnd1Inmemory(b *testing.B) {
benchmarkClientGetEndToEndInmemory(b, 1)
}
func BenchmarkClientGetEndToEnd10Inmemory(b *testing.B) {
benchmarkClientGetEndToEndInmemory(b, 10)
}
func BenchmarkClientGetEndToEnd100Inmemory(b *testing.B) {
benchmarkClientGetEndToEndInmemory(b, 100)
}
func BenchmarkClientGetEndToEnd1000Inmemory(b *testing.B) {
benchmarkClientGetEndToEndInmemory(b, 1000)
}
func BenchmarkClientGetEndToEnd10KInmemory(b *testing.B) {
benchmarkClientGetEndToEndInmemory(b, 10000)
}
func benchmarkClientGetEndToEndInmemory(b *testing.B, parallelism int) {
ln := fasthttputil.NewInmemoryListener()
ch := make(chan struct{})
go func() {
if err := Serve(ln, fasthttpEchoHandler); err != nil {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &Client{
MaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,
Dial: func(addr string) (net.Conn, error) { return ln.Dial() },
}
requestURI := "/foo/bar?baz=123"
url := "http://unused.host" + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
var buf []byte
for pb.Next() {
statusCode, body, err := c.Get(buf, url)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if statusCode != StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
}
if string(body) != requestURI {
b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
}
buf = body
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkNetHTTPClientGetEndToEnd1Inmemory(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndInmemory(b, 1)
}
func BenchmarkNetHTTPClientGetEndToEnd10Inmemory(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndInmemory(b, 10)
}
func BenchmarkNetHTTPClientGetEndToEnd100Inmemory(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndInmemory(b, 100)
}
func BenchmarkNetHTTPClientGetEndToEnd1000Inmemory(b *testing.B) {
benchmarkNetHTTPClientGetEndToEndInmemory(b, 1000)
}
func benchmarkNetHTTPClientGetEndToEndInmemory(b *testing.B, parallelism int) {
ln := fasthttputil.NewInmemoryListener()
ch := make(chan struct{})
go func() {
if err := http.Serve(ln, http.HandlerFunc(nethttpEchoHandler)); err != nil && !strings.Contains(
err.Error(), "use of closed network connection") {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &http.Client{
Transport: &http.Transport{
Dial: func(_, _ string) (net.Conn, error) { return ln.Dial() },
MaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),
},
}
requestURI := "/foo/bar?baz=123"
url := "http://unused.host" + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := c.Get(url)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
b.Fatalf("unexpected error when reading response body: %s", err)
}
if string(body) != requestURI {
b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
}
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkClientEndToEndBigResponse1Inmemory(b *testing.B) {
benchmarkClientEndToEndBigResponseInmemory(b, 1)
}
func BenchmarkClientEndToEndBigResponse10Inmemory(b *testing.B) {
benchmarkClientEndToEndBigResponseInmemory(b, 10)
}
func benchmarkClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) {
bigResponse := createFixedBody(1024 * 1024)
h := func(ctx *RequestCtx) {
ctx.SetContentType("text/plain")
ctx.Write(bigResponse)
}
ln := fasthttputil.NewInmemoryListener()
ch := make(chan struct{})
go func() {
if err := Serve(ln, h); err != nil {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &Client{
MaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism,
Dial: func(addr string) (net.Conn, error) { return ln.Dial() },
}
requestURI := "/foo/bar?baz=123"
url := "http://unused.host" + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
var req Request
req.SetRequestURI(url)
var resp Response
for pb.Next() {
if err := c.DoTimeout(&req, &resp, 5*time.Second); err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode() != StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
}
body := resp.Body()
if !bytes.Equal(bigResponse, body) {
b.Fatalf("unexpected response %q. Expecting %q", body, bigResponse)
}
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkNetHTTPClientEndToEndBigResponse1Inmemory(b *testing.B) {
benchmarkNetHTTPClientEndToEndBigResponseInmemory(b, 1)
}
func BenchmarkNetHTTPClientEndToEndBigResponse10Inmemory(b *testing.B) {
benchmarkNetHTTPClientEndToEndBigResponseInmemory(b, 10)
}
func benchmarkNetHTTPClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) {
bigResponse := createFixedBody(1024 * 1024)
h := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write(bigResponse)
}
ln := fasthttputil.NewInmemoryListener()
ch := make(chan struct{})
go func() {
if err := http.Serve(ln, http.HandlerFunc(h)); err != nil && !strings.Contains(
err.Error(), "use of closed network connection") {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
c := &http.Client{
Transport: &http.Transport{
Dial: func(_, _ string) (net.Conn, error) { return ln.Dial() },
MaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1),
},
Timeout: 5 * time.Second,
}
requestURI := "/foo/bar?baz=123"
url := "http://unused.host" + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
for pb.Next() {
resp, err := c.Do(req)
if err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode != http.StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
b.Fatalf("unexpected error when reading response body: %s", err)
}
if !bytes.Equal(bigResponse, body) {
b.Fatalf("unexpected response %q. Expecting %q", body, bigResponse)
}
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}
func BenchmarkPipelineClient1(b *testing.B) {
benchmarkPipelineClient(b, 1)
}
func BenchmarkPipelineClient10(b *testing.B) {
benchmarkPipelineClient(b, 10)
}
func BenchmarkPipelineClient100(b *testing.B) {
benchmarkPipelineClient(b, 100)
}
func BenchmarkPipelineClient1000(b *testing.B) {
benchmarkPipelineClient(b, 1000)
}
func benchmarkPipelineClient(b *testing.B, parallelism int) {
h := func(ctx *RequestCtx) {
ctx.WriteString("foobar")
}
ln := fasthttputil.NewInmemoryListener()
ch := make(chan struct{})
go func() {
if err := Serve(ln, h); err != nil {
b.Fatalf("error when serving requests: %s", err)
}
close(ch)
}()
var clients []*PipelineClient
for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
c := &PipelineClient{
Dial: func(addr string) (net.Conn, error) { return ln.Dial() },
ReadBufferSize: 1024 * 1024,
WriteBufferSize: 1024 * 1024,
MaxPendingRequests: parallelism,
}
clients = append(clients, c)
}
clientID := uint32(0)
requestURI := "/foo/bar?baz=123"
url := "http://unused.host" + requestURI
b.SetParallelism(parallelism)
b.RunParallel(func(pb *testing.PB) {
n := atomic.AddUint32(&clientID, 1)
c := clients[n%uint32(len(clients))]
var req Request
req.SetRequestURI(url)
var resp Response
for pb.Next() {
if err := c.Do(&req, &resp); err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode() != StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK)
}
body := resp.Body()
if string(body) != "foobar" {
b.Fatalf("unexpected response %q. Expecting %q", body, "foobar")
}
}
})
ln.Close()
select {
case <-ch:
case <-time.After(time.Second):
b.Fatalf("server wasn't stopped")
}
}

284
vendor/github.com/valyala/fasthttp/compress.go generated vendored Normal file
View File

@@ -0,0 +1,284 @@
package fasthttp
import (
"fmt"
"io"
"os"
"sync"
"github.com/klauspost/compress/flate"
"github.com/klauspost/compress/gzip"
"github.com/klauspost/compress/zlib"
)
// Supported compression levels.
const (
CompressNoCompression = flate.NoCompression
CompressBestSpeed = flate.BestSpeed
CompressBestCompression = flate.BestCompression
CompressDefaultCompression = flate.DefaultCompression
)
func acquireGzipReader(r io.Reader) (*gzip.Reader, error) {
v := gzipReaderPool.Get()
if v == nil {
return gzip.NewReader(r)
}
zr := v.(*gzip.Reader)
if err := zr.Reset(r); err != nil {
return nil, err
}
return zr, nil
}
func releaseGzipReader(zr *gzip.Reader) {
zr.Close()
gzipReaderPool.Put(zr)
}
var gzipReaderPool sync.Pool
func acquireFlateReader(r io.Reader) (io.ReadCloser, error) {
v := flateReaderPool.Get()
if v == nil {
zr, err := zlib.NewReader(r)
if err != nil {
return nil, err
}
return zr, nil
}
zr := v.(io.ReadCloser)
if err := resetFlateReader(zr, r); err != nil {
return nil, err
}
return zr, nil
}
func releaseFlateReader(zr io.ReadCloser) {
zr.Close()
flateReaderPool.Put(zr)
}
func resetFlateReader(zr io.ReadCloser, r io.Reader) error {
zrr, ok := zr.(zlib.Resetter)
if !ok {
panic("BUG: zlib.Reader doesn't implement zlib.Resetter???")
}
return zrr.Reset(r, nil)
}
var flateReaderPool sync.Pool
func acquireGzipWriter(w io.Writer, level int) *gzipWriter {
p := gzipWriterPoolMap[level]
if p == nil {
panic(fmt.Sprintf("BUG: unexpected compression level passed: %d. See compress/gzip for supported levels", level))
}
v := p.Get()
if v == nil {
zw, err := gzip.NewWriterLevel(w, level)
if err != nil {
panic(fmt.Sprintf("BUG: unexpected error from gzip.NewWriterLevel(%d): %s", level, err))
}
return &gzipWriter{
Writer: zw,
p: p,
}
}
zw := v.(*gzipWriter)
zw.Reset(w)
return zw
}
func releaseGzipWriter(zw *gzipWriter) {
zw.Close()
zw.p.Put(zw)
}
type gzipWriter struct {
*gzip.Writer
p *sync.Pool
}
var gzipWriterPoolMap = func() map[int]*sync.Pool {
// Initialize pools for all the compression levels defined
// in https://golang.org/pkg/compress/gzip/#pkg-constants .
m := make(map[int]*sync.Pool, 11)
m[-1] = &sync.Pool{}
for i := 0; i < 10; i++ {
m[i] = &sync.Pool{}
}
return m
}()
// AppendGzipBytesLevel appends gzipped src to dst using the given
// compression level and returns the resulting dst.
//
// Supported compression levels are:
//
// * CompressNoCompression
// * CompressBestSpeed
// * CompressBestCompression
// * CompressDefaultCompression
func AppendGzipBytesLevel(dst, src []byte, level int) []byte {
w := &byteSliceWriter{dst}
WriteGzipLevel(w, src, level)
return w.b
}
// WriteGzipLevel writes gzipped p to w using the given compression level
// and returns the number of compressed bytes written to w.
//
// Supported compression levels are:
//
// * CompressNoCompression
// * CompressBestSpeed
// * CompressBestCompression
// * CompressDefaultCompression
func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {
zw := acquireGzipWriter(w, level)
n, err := zw.Write(p)
releaseGzipWriter(zw)
return n, err
}
// WriteGzip writes gzipped p to w and returns the number of compressed
// bytes written to w.
func WriteGzip(w io.Writer, p []byte) (int, error) {
return WriteGzipLevel(w, p, CompressDefaultCompression)
}
// AppendGzipBytes appends gzipped src to dst and returns the resulting dst.
func AppendGzipBytes(dst, src []byte) []byte {
return AppendGzipBytesLevel(dst, src, CompressDefaultCompression)
}
// WriteGunzip writes ungzipped p to w and returns the number of uncompressed
// bytes written to w.
func WriteGunzip(w io.Writer, p []byte) (int, error) {
r := &byteSliceReader{p}
zr, err := acquireGzipReader(r)
if err != nil {
return 0, err
}
n, err := copyZeroAlloc(w, zr)
releaseGzipReader(zr)
nn := int(n)
if int64(nn) != n {
return 0, fmt.Errorf("too much data gunzipped: %d", n)
}
return nn, err
}
// WriteInflate writes inflated p to w and returns the number of uncompressed
// bytes written to w.
func WriteInflate(w io.Writer, p []byte) (int, error) {
r := &byteSliceReader{p}
zr, err := acquireFlateReader(r)
if err != nil {
return 0, err
}
n, err := copyZeroAlloc(w, zr)
releaseFlateReader(zr)
nn := int(n)
if int64(nn) != n {
return 0, fmt.Errorf("too much data inflated: %d", n)
}
return nn, err
}
// AppendGunzipBytes append gunzipped src to dst and returns the resulting dst.
func AppendGunzipBytes(dst, src []byte) ([]byte, error) {
w := &byteSliceWriter{dst}
_, err := WriteGunzip(w, src)
return w.b, err
}
type byteSliceWriter struct {
b []byte
}
func (w *byteSliceWriter) Write(p []byte) (int, error) {
w.b = append(w.b, p...)
return len(p), nil
}
type byteSliceReader struct {
b []byte
}
func (r *byteSliceReader) Read(p []byte) (int, error) {
if len(r.b) == 0 {
return 0, io.EOF
}
n := copy(p, r.b)
r.b = r.b[n:]
return n, nil
}
func acquireFlateWriter(w io.Writer, level int) *flateWriter {
p := flateWriterPoolMap[level]
if p == nil {
panic(fmt.Sprintf("BUG: unexpected compression level passed: %d. See compress/flate for supported levels", level))
}
v := p.Get()
if v == nil {
zw, err := zlib.NewWriterLevel(w, level)
if err != nil {
panic(fmt.Sprintf("BUG: unexpected error in zlib.NewWriterLevel(%d): %s", level, err))
}
return &flateWriter{
Writer: zw,
p: p,
}
}
zw := v.(*flateWriter)
zw.Reset(w)
return zw
}
func releaseFlateWriter(zw *flateWriter) {
zw.Close()
zw.p.Put(zw)
}
type flateWriter struct {
*zlib.Writer
p *sync.Pool
}
var flateWriterPoolMap = func() map[int]*sync.Pool {
// Initialize pools for all the compression levels defined
// in https://golang.org/pkg/compress/flate/#pkg-constants .
m := make(map[int]*sync.Pool, 11)
m[-1] = &sync.Pool{}
for i := 0; i < 10; i++ {
m[i] = &sync.Pool{}
}
return m
}()
func isFileCompressible(f *os.File, minCompressRatio float64) bool {
// Try compressing the first 4kb of of the file
// and see if it can be compressed by more than
// the given minCompressRatio.
b := AcquireByteBuffer()
zw := acquireGzipWriter(b, CompressDefaultCompression)
lr := &io.LimitedReader{
R: f,
N: 4096,
}
_, err := copyZeroAlloc(zw, lr)
releaseGzipWriter(zw)
f.Seek(0, 0)
if err != nil {
return false
}
n := 4096 - lr.N
zn := len(b.B)
ReleaseByteBuffer(b)
return float64(zn) < float64(n)*minCompressRatio
}

89
vendor/github.com/valyala/fasthttp/compress_test.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
package fasthttp
import (
"bytes"
"io/ioutil"
"testing"
)
func TestGzipBytes(t *testing.T) {
testGzipBytes(t, "")
testGzipBytes(t, "foobar")
testGzipBytes(t, "выфаодлодл одлфываыв sd2 k34")
}
func testGzipBytes(t *testing.T, s string) {
prefix := []byte("foobar")
gzippedS := AppendGzipBytes(prefix, []byte(s))
if !bytes.Equal(gzippedS[:len(prefix)], prefix) {
t.Fatalf("unexpected prefix when compressing %q: %q. Expecting %q", s, gzippedS[:len(prefix)], prefix)
}
gunzippedS, err := AppendGunzipBytes(prefix, gzippedS[len(prefix):])
if err != nil {
t.Fatalf("unexpected error when uncompressing %q: %s", s, err)
}
if !bytes.Equal(gunzippedS[:len(prefix)], prefix) {
t.Fatalf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, gunzippedS[:len(prefix)], prefix)
}
gunzippedS = gunzippedS[len(prefix):]
if string(gunzippedS) != s {
t.Fatalf("unexpected uncompressed string %q. Expecting %q", gunzippedS, s)
}
}
func TestGzipCompress(t *testing.T) {
testGzipCompress(t, "")
testGzipCompress(t, "foobar")
testGzipCompress(t, "ajjnkn asdlkjfqoijfw jfqkwj foj eowjiq")
}
func TestFlateCompress(t *testing.T) {
testFlateCompress(t, "")
testFlateCompress(t, "foobar")
testFlateCompress(t, "adf asd asd fasd fasd")
}
func testGzipCompress(t *testing.T, s string) {
var buf bytes.Buffer
zw := acquireGzipWriter(&buf, CompressDefaultCompression)
if _, err := zw.Write([]byte(s)); err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
releaseGzipWriter(zw)
zr, err := acquireGzipReader(&buf)
if err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
body, err := ioutil.ReadAll(zr)
if err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
if string(body) != s {
t.Fatalf("unexpected string after decompression: %q. Expecting %q", body, s)
}
releaseGzipReader(zr)
}
func testFlateCompress(t *testing.T, s string) {
var buf bytes.Buffer
zw := acquireFlateWriter(&buf, CompressDefaultCompression)
if _, err := zw.Write([]byte(s)); err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
releaseFlateWriter(zw)
zr, err := acquireFlateReader(&buf)
if err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
body, err := ioutil.ReadAll(zr)
if err != nil {
t.Fatalf("unexpected error: %s. s=%q", err, s)
}
if string(body) != s {
t.Fatalf("unexpected string after decompression: %q. Expecting %q", body, s)
}
releaseFlateReader(zr)
}

394
vendor/github.com/valyala/fasthttp/cookie.go generated vendored Normal file
View File

@@ -0,0 +1,394 @@
package fasthttp
import (
"bytes"
"errors"
"io"
"sync"
"time"
)
var zeroTime time.Time
var (
// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
CookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
// CookieExpireUnlimited indicates that the cookie doesn't expire.
CookieExpireUnlimited = zeroTime
)
// AcquireCookie returns an empty Cookie object from the pool.
//
// The returned object may be returned back to the pool with ReleaseCookie.
// This allows reducing GC load.
func AcquireCookie() *Cookie {
return cookiePool.Get().(*Cookie)
}
// ReleaseCookie returns the Cookie object acquired with AcquireCookie back
// to the pool.
//
// Do not access released Cookie object, otherwise data races may occur.
func ReleaseCookie(c *Cookie) {
c.Reset()
cookiePool.Put(c)
}
var cookiePool = &sync.Pool{
New: func() interface{} {
return &Cookie{}
},
}
// Cookie represents HTTP response cookie.
//
// Do not copy Cookie objects. Create new object and use CopyTo instead.
//
// Cookie instance MUST NOT be used from concurrently running goroutines.
type Cookie struct {
noCopy noCopy
key []byte
value []byte
expire time.Time
domain []byte
path []byte
httpOnly bool
secure bool
bufKV argsKV
buf []byte
}
// CopyTo copies src cookie to c.
func (c *Cookie) CopyTo(src *Cookie) {
c.Reset()
c.key = append(c.key[:0], src.key...)
c.value = append(c.value[:0], src.value...)
c.expire = src.expire
c.domain = append(c.domain[:0], src.domain...)
c.path = append(c.path[:0], src.path...)
c.httpOnly = src.httpOnly
c.secure = src.secure
}
// HTTPOnly returns true if the cookie is http only.
func (c *Cookie) HTTPOnly() bool {
return c.httpOnly
}
// SetHTTPOnly sets cookie's httpOnly flag to the given value.
func (c *Cookie) SetHTTPOnly(httpOnly bool) {
c.httpOnly = httpOnly
}
// Secure returns true if the cookie is secure.
func (c *Cookie) Secure() bool {
return c.secure
}
// SetSecure sets cookie's secure flag to the given value.
func (c *Cookie) SetSecure(secure bool) {
c.secure = secure
}
// Path returns cookie path.
func (c *Cookie) Path() []byte {
return c.path
}
// SetPath sets cookie path.
func (c *Cookie) SetPath(path string) {
c.buf = append(c.buf[:0], path...)
c.path = normalizePath(c.path, c.buf)
}
// SetPathBytes sets cookie path.
func (c *Cookie) SetPathBytes(path []byte) {
c.buf = append(c.buf[:0], path...)
c.path = normalizePath(c.path, c.buf)
}
// Domain returns cookie domain.
//
// The returned domain is valid until the next Cookie modification method call.
func (c *Cookie) Domain() []byte {
return c.domain
}
// SetDomain sets cookie domain.
func (c *Cookie) SetDomain(domain string) {
c.domain = append(c.domain[:0], domain...)
}
// SetDomainBytes sets cookie domain.
func (c *Cookie) SetDomainBytes(domain []byte) {
c.domain = append(c.domain[:0], domain...)
}
// Expire returns cookie expiration time.
//
// CookieExpireUnlimited is returned if cookie doesn't expire
func (c *Cookie) Expire() time.Time {
expire := c.expire
if expire.IsZero() {
expire = CookieExpireUnlimited
}
return expire
}
// SetExpire sets cookie expiration time.
//
// Set expiration time to CookieExpireDelete for expiring (deleting)
// the cookie on the client.
//
// By default cookie lifetime is limited by browser session.
func (c *Cookie) SetExpire(expire time.Time) {
c.expire = expire
}
// Value returns cookie value.
//
// The returned value is valid until the next Cookie modification method call.
func (c *Cookie) Value() []byte {
return c.value
}
// SetValue sets cookie value.
func (c *Cookie) SetValue(value string) {
c.value = append(c.value[:0], value...)
}
// SetValueBytes sets cookie value.
func (c *Cookie) SetValueBytes(value []byte) {
c.value = append(c.value[:0], value...)
}
// Key returns cookie name.
//
// The returned value is valid until the next Cookie modification method call.
func (c *Cookie) Key() []byte {
return c.key
}
// SetKey sets cookie name.
func (c *Cookie) SetKey(key string) {
c.key = append(c.key[:0], key...)
}
// SetKeyBytes sets cookie name.
func (c *Cookie) SetKeyBytes(key []byte) {
c.key = append(c.key[:0], key...)
}
// Reset clears the cookie.
func (c *Cookie) Reset() {
c.key = c.key[:0]
c.value = c.value[:0]
c.expire = zeroTime
c.domain = c.domain[:0]
c.path = c.path[:0]
c.httpOnly = false
c.secure = false
}
// AppendBytes appends cookie representation to dst and returns
// the extended dst.
func (c *Cookie) AppendBytes(dst []byte) []byte {
if len(c.key) > 0 {
dst = AppendQuotedArg(dst, c.key)
dst = append(dst, '=')
}
dst = AppendQuotedArg(dst, c.value)
if !c.expire.IsZero() {
c.bufKV.value = AppendHTTPDate(c.bufKV.value[:0], c.expire)
dst = append(dst, ';', ' ')
dst = append(dst, strCookieExpires...)
dst = append(dst, '=')
dst = append(dst, c.bufKV.value...)
}
if len(c.domain) > 0 {
dst = appendCookiePart(dst, strCookieDomain, c.domain)
}
if len(c.path) > 0 {
dst = appendCookiePart(dst, strCookiePath, c.path)
}
if c.httpOnly {
dst = append(dst, ';', ' ')
dst = append(dst, strCookieHTTPOnly...)
}
if c.secure {
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSecure...)
}
return dst
}
// Cookie returns cookie representation.
//
// The returned value is valid until the next call to Cookie methods.
func (c *Cookie) Cookie() []byte {
c.buf = c.AppendBytes(c.buf[:0])
return c.buf
}
// String returns cookie representation.
func (c *Cookie) String() string {
return string(c.Cookie())
}
// WriteTo writes cookie representation to w.
//
// WriteTo implements io.WriterTo interface.
func (c *Cookie) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(c.Cookie())
return int64(n), err
}
var errNoCookies = errors.New("no cookies found")
// Parse parses Set-Cookie header.
func (c *Cookie) Parse(src string) error {
c.buf = append(c.buf[:0], src...)
return c.ParseBytes(c.buf)
}
// ParseBytes parses Set-Cookie header.
func (c *Cookie) ParseBytes(src []byte) error {
c.Reset()
var s cookieScanner
s.b = src
kv := &c.bufKV
if !s.next(kv, true) {
return errNoCookies
}
c.key = append(c.key[:0], kv.key...)
c.value = append(c.value[:0], kv.value...)
for s.next(kv, false) {
if len(kv.key) == 0 && len(kv.value) == 0 {
continue
}
switch string(kv.key) {
case "expires":
v := b2s(kv.value)
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
if err != nil {
return err
}
c.expire = exptime
case "domain":
c.domain = append(c.domain[:0], kv.value...)
case "path":
c.path = append(c.path[:0], kv.value...)
case "":
switch string(kv.value) {
case "HttpOnly":
c.httpOnly = true
case "secure":
c.secure = true
}
}
}
return nil
}
func appendCookiePart(dst, key, value []byte) []byte {
dst = append(dst, ';', ' ')
dst = append(dst, key...)
dst = append(dst, '=')
return append(dst, value...)
}
func getCookieKey(dst, src []byte) []byte {
n := bytes.IndexByte(src, '=')
if n >= 0 {
src = src[:n]
}
return decodeCookieArg(dst, src, true)
}
func appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {
for i, n := 0, len(cookies); i < n; i++ {
kv := &cookies[i]
if len(kv.key) > 0 {
dst = AppendQuotedArg(dst, kv.key)
dst = append(dst, '=')
}
dst = AppendQuotedArg(dst, kv.value)
if i+1 < n {
dst = append(dst, ';', ' ')
}
}
return dst
}
func parseRequestCookies(cookies []argsKV, src []byte) []argsKV {
var s cookieScanner
s.b = src
var kv *argsKV
cookies, kv = allocArg(cookies)
for s.next(kv, true) {
if len(kv.key) > 0 || len(kv.value) > 0 {
cookies, kv = allocArg(cookies)
}
}
return releaseArg(cookies)
}
type cookieScanner struct {
b []byte
}
func (s *cookieScanner) next(kv *argsKV, decode bool) bool {
b := s.b
if len(b) == 0 {
return false
}
isKey := true
k := 0
for i, c := range b {
switch c {
case '=':
if isKey {
isKey = false
kv.key = decodeCookieArg(kv.key, b[:i], decode)
k = i + 1
}
case ';':
if isKey {
kv.key = kv.key[:0]
}
kv.value = decodeCookieArg(kv.value, b[k:i], decode)
s.b = b[i+1:]
return true
}
}
if isKey {
kv.key = kv.key[:0]
}
kv.value = decodeCookieArg(kv.value, b[k:], decode)
s.b = b[len(b):]
return true
}
func decodeCookieArg(dst, src []byte, decode bool) []byte {
for len(src) > 0 && src[0] == ' ' {
src = src[1:]
}
for len(src) > 0 && src[len(src)-1] == ' ' {
src = src[:len(src)-1]
}
if !decode {
return append(dst[:0], src...)
}
return decodeArg(dst, src, true)
}

234
vendor/github.com/valyala/fasthttp/cookie_test.go generated vendored Normal file
View File

@@ -0,0 +1,234 @@
package fasthttp
import (
"strings"
"testing"
"time"
)
func TestCookieSecureHttpOnly(t *testing.T) {
var c Cookie
if err := c.Parse("foo=bar; HttpOnly; secure"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !c.Secure() {
t.Fatalf("secure must be set")
}
if !c.HTTPOnly() {
t.Fatalf("HttpOnly must be set")
}
s := c.String()
if !strings.Contains(s, "; secure") {
t.Fatalf("missing secure flag in cookie %q", s)
}
if !strings.Contains(s, "; HttpOnly") {
t.Fatalf("missing HttpOnly flag in cookie %q", s)
}
}
func TestCookieSecure(t *testing.T) {
var c Cookie
if err := c.Parse("foo=bar; secure"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !c.Secure() {
t.Fatalf("secure must be set")
}
s := c.String()
if !strings.Contains(s, "; secure") {
t.Fatalf("missing secure flag in cookie %q", s)
}
if err := c.Parse("foo=bar"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.HTTPOnly() {
t.Fatalf("Unexpected secure flag set")
}
s = c.String()
if strings.Contains(s, "secure") {
t.Fatalf("unexpected secure flag in cookie %q", s)
}
}
func TestCookieHttpOnly(t *testing.T) {
var c Cookie
if err := c.Parse("foo=bar; HttpOnly"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !c.HTTPOnly() {
t.Fatalf("HTTPOnly must be set")
}
s := c.String()
if !strings.Contains(s, "; HttpOnly") {
t.Fatalf("missing HttpOnly flag in cookie %q", s)
}
if err := c.Parse("foo=bar"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if c.HTTPOnly() {
t.Fatalf("Unexpected HTTPOnly flag set")
}
s = c.String()
if strings.Contains(s, "HttpOnly") {
t.Fatalf("unexpected HttpOnly flag in cookie %q", s)
}
}
func TestCookieAcquireReleaseSequential(t *testing.T) {
testCookieAcquireRelease(t)
}
func TestCookieAcquireReleaseConcurrent(t *testing.T) {
ch := make(chan struct{}, 10)
for i := 0; i < 10; i++ {
go func() {
testCookieAcquireRelease(t)
ch <- struct{}{}
}()
}
for i := 0; i < 10; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testCookieAcquireRelease(t *testing.T) {
c := AcquireCookie()
key := "foo"
c.SetKey(key)
value := "bar"
c.SetValue(value)
domain := "foo.bar.com"
c.SetDomain(domain)
path := "/foi/bar/aaa"
c.SetPath(path)
s := c.String()
c.Reset()
if err := c.Parse(s); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(c.Key()) != key {
t.Fatalf("unexpected cookie name %q. Expecting %q", c.Key(), key)
}
if string(c.Value()) != value {
t.Fatalf("unexpected cookie value %q. Expecting %q", c.Value(), value)
}
if string(c.Domain()) != domain {
t.Fatalf("unexpected domain %q. Expecting %q", c.Domain(), domain)
}
if string(c.Path()) != path {
t.Fatalf("unexpected path %q. Expecting %q", c.Path(), path)
}
ReleaseCookie(c)
}
func TestCookieParse(t *testing.T) {
testCookieParse(t, "foo", "foo")
testCookieParse(t, "foo=bar", "foo=bar")
testCookieParse(t, "foo=", "foo=")
testCookieParse(t, "foo=bar; domain=aaa.com; path=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar")
testCookieParse(t, " xxx = yyy ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;",
"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
}
func testCookieParse(t *testing.T, s, expectedS string) {
var c Cookie
if err := c.Parse(s); err != nil {
t.Fatalf("unexpected error: %s", err)
}
result := string(c.Cookie())
if result != expectedS {
t.Fatalf("unexpected cookies %q. Expected %q. Original %q", result, expectedS, s)
}
}
func TestCookieAppendBytes(t *testing.T) {
c := &Cookie{}
testCookieAppendBytes(t, c, "", "bar", "bar")
testCookieAppendBytes(t, c, "foo", "", "foo=")
testCookieAppendBytes(t, c, "ффф", "12 лодлы", "%D1%84%D1%84%D1%84=12%20%D0%BB%D0%BE%D0%B4%D0%BB%D1%8B")
c.SetDomain("foobar.com")
testCookieAppendBytes(t, c, "a", "b", "a=b; domain=foobar.com")
c.SetPath("/a/b")
testCookieAppendBytes(t, c, "aa", "bb", "aa=bb; domain=foobar.com; path=/a/b")
c.SetExpire(CookieExpireDelete)
testCookieAppendBytes(t, c, "xxx", "yyy", "xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
}
func testCookieAppendBytes(t *testing.T, c *Cookie, key, value, expectedS string) {
c.SetKey(key)
c.SetValue(value)
result := string(c.AppendBytes(nil))
if result != expectedS {
t.Fatalf("Unexpected cookie %q. Expected %q", result, expectedS)
}
}
func TestParseRequestCookies(t *testing.T) {
testParseRequestCookies(t, "", "")
testParseRequestCookies(t, "=", "")
testParseRequestCookies(t, "foo", "foo")
testParseRequestCookies(t, "=foo", "foo")
testParseRequestCookies(t, "bar=", "bar=")
testParseRequestCookies(t, "xxx=aa;bb=c; =d; ;;e=g", "xxx=aa; bb=c; d; e=g")
testParseRequestCookies(t, "a;b;c; d=1;d=2", "a; b; c; d=1; d=2")
testParseRequestCookies(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")
}
func testParseRequestCookies(t *testing.T, s, expectedS string) {
cookies := parseRequestCookies(nil, []byte(s))
ss := string(appendRequestCookieBytes(nil, cookies))
if ss != expectedS {
t.Fatalf("Unexpected cookies after parsing: %q. Expected %q. String to parse %q", ss, expectedS, s)
}
}
func TestAppendRequestCookieBytes(t *testing.T) {
testAppendRequestCookieBytes(t, "=", "")
testAppendRequestCookieBytes(t, "foo=", "foo=")
testAppendRequestCookieBytes(t, "=bar", "bar")
testAppendRequestCookieBytes(t, "привет=a b;c&s s=aaa", "%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82=a%20b%3Bc; s%20s=aaa")
}
func testAppendRequestCookieBytes(t *testing.T, s, expectedS string) {
var cookies []argsKV
for _, ss := range strings.Split(s, "&") {
tmp := strings.SplitN(ss, "=", 2)
if len(tmp) != 2 {
t.Fatalf("Cannot find '=' in %q, part of %q", ss, s)
}
cookies = append(cookies, argsKV{
key: []byte(tmp[0]),
value: []byte(tmp[1]),
})
}
prefix := "foobar"
result := string(appendRequestCookieBytes([]byte(prefix), cookies))
if result[:len(prefix)] != prefix {
t.Fatalf("unexpected prefix %q. Expected %q for cookie %q", result[:len(prefix)], prefix, s)
}
result = result[len(prefix):]
if result != expectedS {
t.Fatalf("Unexpected result %q. Expected %q for cookie %q", result, expectedS, s)
}
}

View File

@@ -0,0 +1,35 @@
package fasthttp
import (
"testing"
)
func BenchmarkCookieParseMin(b *testing.B) {
var c Cookie
s := []byte("xxx=yyy")
for i := 0; i < b.N; i++ {
if err := c.ParseBytes(s); err != nil {
b.Fatalf("unexpected error when parsing cookies: %s", err)
}
}
}
func BenchmarkCookieParseNoExpires(b *testing.B) {
var c Cookie
s := []byte("xxx=yyy; domain=foobar.com; path=/a/b")
for i := 0; i < b.N; i++ {
if err := c.ParseBytes(s); err != nil {
b.Fatalf("unexpected error when parsing cookies: %s", err)
}
}
}
func BenchmarkCookieParseFull(b *testing.B) {
var c Cookie
s := []byte("xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
for i := 0; i < b.N; i++ {
if err := c.ParseBytes(s); err != nil {
b.Fatalf("unexpected error when parsing cookies: %s", err)
}
}
}

40
vendor/github.com/valyala/fasthttp/doc.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
/*
Package fasthttp provides fast HTTP server and client API.
Fasthttp provides the following features:
* Optimized for speed. Easily handles more than 100K qps and more than 1M
concurrent keep-alive connections on modern hardware.
* Optimized for low memory usage.
* Easy 'Connection: Upgrade' support via RequestCtx.Hijack.
* Server supports requests' pipelining. Multiple requests may be read from
a single network packet and multiple responses may be sent in a single
network packet. This may be useful for highly loaded REST services.
* Server provides the following anti-DoS limits:
* The number of concurrent connections.
* The number of concurrent connections per client IP.
* The number of requests per connection.
* Request read timeout.
* Response write timeout.
* Maximum request header size.
* Maximum request body size.
* Maximum request execution time.
* Maximum keep-alive connection lifetime.
* Early filtering out non-GET requests.
* A lot of additional useful info is exposed to request handler:
* Server and client address.
* Per-request logger.
* Unique request id.
* Request start time.
* Connection start time.
* Request sequence number for the current connection.
* Client supports automatic retry on idempotent requests' failure.
* Fasthttp API is designed with the ability to extend existing client
and server implementations or to write custom client and server
implementations from scratch.
*/
package fasthttp

View File

@@ -0,0 +1,4 @@
# Code examples
* [HelloWorld server](helloworldserver)
* [Static file server](fileserver)

View File

@@ -0,0 +1 @@
fileserver

View File

@@ -0,0 +1,7 @@
fileserver: clean
go get -u github.com/valyala/fasthttp
go get -u github.com/valyala/fasthttp/expvarhandler
go build
clean:
rm -f fileserver

View File

@@ -0,0 +1,84 @@
# Static file server example
* Serves files from the given directory.
* Supports transparent response compression.
* Supports byte range responses.
* Generates directory index pages.
* Supports TLS (aka SSL or HTTPS).
* Supports virtual hosts.
* Exports various stats on /stats path.
# How to build
```
make
```
# How to run
```
./fileserver -h
./fileserver -addr=tcp.addr.to.listen:to -dir=/path/to/directory/to/serve
```
# fileserver vs nginx performance comparison
Serving default nginx path (`/usr/share/nginx/html` on ubuntu).
* nginx
```
$ ./wrk -t 4 -c 16 -d 10 http://localhost:80
Running 10s test @ http://localhost:80
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 397.76us 1.08ms 20.23ms 95.19%
Req/Sec 21.20k 2.49k 31.34k 79.65%
850220 requests in 10.10s, 695.65MB read
Requests/sec: 84182.71
Transfer/sec: 68.88MB
```
* fileserver
```
$ ./wrk -t 4 -c 16 -d 10 http://localhost:8080
Running 10s test @ http://localhost:8080
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 447.99us 1.59ms 27.20ms 94.79%
Req/Sec 37.13k 3.99k 47.86k 76.00%
1478457 requests in 10.02s, 1.03GB read
Requests/sec: 147597.06
Transfer/sec: 105.15MB
```
8 pipelined requests
* nginx
```
$ ./wrk -s pipeline.lua -t 4 -c 16 -d 10 http://localhost:80 -- 8
Running 10s test @ http://localhost:80
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.34ms 2.15ms 30.91ms 92.16%
Req/Sec 33.54k 7.36k 108.12k 76.81%
1339908 requests in 10.10s, 1.07GB read
Requests/sec: 132705.81
Transfer/sec: 108.58MB
```
* fileserver
```
$ ./wrk -s pipeline.lua -t 4 -c 16 -d 10 http://localhost:8080 -- 8
Running 10s test @ http://localhost:8080
4 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.08ms 6.33ms 88.26ms 92.83%
Req/Sec 116.54k 14.66k 167.98k 69.00%
4642226 requests in 10.03s, 3.23GB read
Requests/sec: 462769.41
Transfer/sec: 329.67MB
```

View File

@@ -0,0 +1,120 @@
// Example static file server.
//
// Serves static files from the given directory.
// Exports various stats at /stats .
package main
import (
"expvar"
"flag"
"log"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/expvarhandler"
)
var (
addr = flag.String("addr", "localhost:8080", "TCP address to listen to")
addrTLS = flag.String("addrTLS", "", "TCP address to listen to TLS (aka SSL or HTTPS) requests. Leave empty for disabling TLS")
byteRange = flag.Bool("byteRange", false, "Enables byte range requests if set to true")
certFile = flag.String("certFile", "./ssl-cert-snakeoil.pem", "Path to TLS certificate file")
compress = flag.Bool("compress", false, "Enables transparent response compression if set to true")
dir = flag.String("dir", "/usr/share/nginx/html", "Directory to serve static files from")
generateIndexPages = flag.Bool("generateIndexPages", true, "Whether to generate directory index pages")
keyFile = flag.String("keyFile", "./ssl-cert-snakeoil.key", "Path to TLS key file")
vhost = flag.Bool("vhost", false, "Enables virtual hosting by prepending the requested path with the requested hostname")
)
func main() {
// Parse command-line flags.
flag.Parse()
// Setup FS handler
fs := &fasthttp.FS{
Root: *dir,
IndexNames: []string{"index.html"},
GenerateIndexPages: *generateIndexPages,
Compress: *compress,
AcceptByteRange: *byteRange,
}
if *vhost {
fs.PathRewrite = fasthttp.NewVHostPathRewriter(0)
}
fsHandler := fs.NewRequestHandler()
// Create RequestHandler serving server stats on /stats and files
// on other requested paths.
// /stats output may be filtered using regexps. For example:
//
// * /stats?r=fs will show only stats (expvars) containing 'fs'
// in their names.
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/stats":
expvarhandler.ExpvarHandler(ctx)
default:
fsHandler(ctx)
updateFSCounters(ctx)
}
}
// Start HTTP server.
if len(*addr) > 0 {
log.Printf("Starting HTTP server on %q", *addr)
go func() {
if err := fasthttp.ListenAndServe(*addr, requestHandler); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}()
}
// Start HTTPS server.
if len(*addrTLS) > 0 {
log.Printf("Starting HTTPS server on %q", *addrTLS)
go func() {
if err := fasthttp.ListenAndServeTLS(*addrTLS, *certFile, *keyFile, requestHandler); err != nil {
log.Fatalf("error in ListenAndServeTLS: %s", err)
}
}()
}
log.Printf("Serving files from directory %q", *dir)
log.Printf("See stats at http://%s/stats", *addr)
// Wait forever.
select {}
}
func updateFSCounters(ctx *fasthttp.RequestCtx) {
// Increment the number of fsHandler calls.
fsCalls.Add(1)
// Update other stats counters
resp := &ctx.Response
switch resp.StatusCode() {
case fasthttp.StatusOK:
fsOKResponses.Add(1)
fsResponseBodyBytes.Add(int64(resp.Header.ContentLength()))
case fasthttp.StatusNotModified:
fsNotModifiedResponses.Add(1)
case fasthttp.StatusNotFound:
fsNotFoundResponses.Add(1)
default:
fsOtherResponses.Add(1)
}
}
// Various counters - see https://golang.org/pkg/expvar/ for details.
var (
// Counter for total number of fs calls
fsCalls = expvar.NewInt("fsCalls")
// Counters for various response status codes
fsOKResponses = expvar.NewInt("fsOKResponses")
fsNotModifiedResponses = expvar.NewInt("fsNotModifiedResponses")
fsNotFoundResponses = expvar.NewInt("fsNotFoundResponses")
fsOtherResponses = expvar.NewInt("fsOtherResponses")
// Total size in bytes for OK response bodies served.
fsResponseBodyBytes = expvar.NewInt("fsResponseBodyBytes")
)

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG
3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U
wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0
FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf
IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg
GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF
sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2
sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D
uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb
K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3
YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+
DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk
B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV
Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x
IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY
wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj
wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D
FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m
tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX
fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU
ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk
K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT
6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt
9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN
Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV
c257YgaWmjK9uB0Y2r2VxS0G
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV
MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D
K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te
+z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij
L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1
xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY
6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG
SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98
L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2
45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li
K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6
X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI
whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
helloworldserver

View File

@@ -0,0 +1,6 @@
helloworldserver: clean
go get -u github.com/valyala/fasthttp
go build
clean:
rm -f helloworldserver

View File

@@ -0,0 +1,17 @@
# HelloWorld server example
* Displays various request info.
* Sets response headers and cookies.
* Supports transparent compression.
# How to build
```
make
```
# How to run
```
./helloworldserver -addr=tcp.addr.to.listen:to
```

View File

@@ -0,0 +1,55 @@
package main
import (
"flag"
"fmt"
"log"
"github.com/valyala/fasthttp"
)
var (
addr = flag.String("addr", ":8080", "TCP address to listen to")
compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
)
func main() {
flag.Parse()
h := requestHandler
if *compress {
h = fasthttp.CompressHandler(h)
}
if err := fasthttp.ListenAndServe(*addr, h); err != nil {
log.Fatalf("Error in ListenAndServe: %s", err)
}
}
func requestHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world!\n\n")
fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method())
fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI())
fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
fmt.Fprintf(ctx, "Host is %q\n", ctx.Host())
fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs())
fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent())
fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime())
fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time())
fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum())
fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP())
fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request)
ctx.SetContentType("text/plain; charset=utf8")
// Set arbitrary headers
ctx.Response.Header.Set("X-My-Header", "my-header-value")
// Set cookies
var c fasthttp.Cookie
c.SetKey("cookie-name")
c.SetValue("cookie-value")
ctx.Response.Header.SetCookie(&c)
}

View File

@@ -0,0 +1,62 @@
// Package expvarhandler provides fasthttp-compatible request handler
// serving expvars.
package expvarhandler
import (
"expvar"
"fmt"
"regexp"
"github.com/valyala/fasthttp"
)
var (
expvarHandlerCalls = expvar.NewInt("expvarHandlerCalls")
expvarRegexpErrors = expvar.NewInt("expvarRegexpErrors")
)
// ExpvarHandler dumps json representation of expvars to http response.
//
// Expvars may be filtered by regexp provided via 'r' query argument.
//
// See https://golang.org/pkg/expvar/ for details.
func ExpvarHandler(ctx *fasthttp.RequestCtx) {
expvarHandlerCalls.Add(1)
ctx.Response.Reset()
r, err := getExpvarRegexp(ctx)
if err != nil {
expvarRegexpErrors.Add(1)
fmt.Fprintf(ctx, "Error when obtaining expvar regexp: %s", err)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return
}
fmt.Fprintf(ctx, "{\n")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if !first {
fmt.Fprintf(ctx, ",\n")
}
if r.MatchString(kv.Key) {
first = false
fmt.Fprintf(ctx, "\t%q: %s", kv.Key, kv.Value)
}
})
fmt.Fprintf(ctx, "\n}\n")
ctx.SetContentType("application/json; charset=utf-8")
}
func getExpvarRegexp(ctx *fasthttp.RequestCtx) (*regexp.Regexp, error) {
r := string(ctx.QueryArgs().Peek("r"))
if len(r) == 0 {
r = "."
}
rr, err := regexp.Compile(r)
if err != nil {
return nil, fmt.Errorf("cannot parse r=%q: %s", r, err)
}
return rr, nil
}

View File

@@ -0,0 +1,67 @@
package expvarhandler
import (
"encoding/json"
"expvar"
"strings"
"testing"
"github.com/valyala/fasthttp"
)
func TestExpvarHandlerBasic(t *testing.T) {
expvar.Publish("customVar", expvar.Func(func() interface{} {
return "foobar"
}))
var ctx fasthttp.RequestCtx
expvarHandlerCalls.Set(0)
ExpvarHandler(&ctx)
body := ctx.Response.Body()
var m map[string]interface{}
if err := json.Unmarshal(body, &m); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if _, ok := m["cmdline"]; !ok {
t.Fatalf("cannot locate cmdline expvar")
}
if _, ok := m["memstats"]; !ok {
t.Fatalf("cannot locate memstats expvar")
}
v := m["customVar"]
sv, ok := v.(string)
if !ok {
t.Fatalf("unexpected custom var type %T. Expecting string", v)
}
if sv != "foobar" {
t.Fatalf("unexpected custom var value: %q. Expecting %q", v, "foobar")
}
v = m["expvarHandlerCalls"]
fv, ok := v.(float64)
if !ok {
t.Fatalf("unexpected expvarHandlerCalls type %T. Expecting float64", v)
}
if int(fv) != 1 {
t.Fatalf("unexpected value for expvarHandlerCalls: %v. Expecting %v", fv, 1)
}
}
func TestExpvarHandlerRegexp(t *testing.T) {
var ctx fasthttp.RequestCtx
ctx.QueryArgs().Set("r", "cmd")
ExpvarHandler(&ctx)
body := string(ctx.Response.Body())
if !strings.Contains(body, `"cmdline"`) {
t.Fatalf("missing 'cmdline' expvar")
}
if strings.Contains(body, `"memstats"`) {
t.Fatalf("unexpected memstats expvar found")
}
}

View File

@@ -0,0 +1,135 @@
// Package fasthttpadaptor provides helper functions for converting net/http
// request handlers to fasthttp request handlers.
package fasthttpadaptor
import (
"io"
"net/http"
"net/url"
"github.com/valyala/fasthttp"
)
// NewFastHTTPHandlerFunc wraps net/http handler func to fasthttp
// request handler, so it can be passed to fasthttp server.
//
// While this function may be used for easy switching from net/http to fasthttp,
// it has the following drawbacks comparing to using manually written fasthttp
// request handler:
//
// * A lot of useful functionality provided by fasthttp is missing
// from net/http handler.
// * net/http -> fasthttp handler conversion has some overhead,
// so the returned handler will be always slower than manually written
// fasthttp handler.
//
// So it is advisable using this function only for quick net/http -> fasthttp
// switching. Then manually convert net/http handlers to fasthttp handlers
// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .
func NewFastHTTPHandlerFunc(h http.HandlerFunc) fasthttp.RequestHandler {
return NewFastHTTPHandler(h)
}
// NewFastHTTPHandler wraps net/http handler to fasthttp request handler,
// so it can be passed to fasthttp server.
//
// While this function may be used for easy switching from net/http to fasthttp,
// it has the following drawbacks comparing to using manually written fasthttp
// request handler:
//
// * A lot of useful functionality provided by fasthttp is missing
// from net/http handler.
// * net/http -> fasthttp handler conversion has some overhead,
// so the returned handler will be always slower than manually written
// fasthttp handler.
//
// So it is advisable using this function only for quick net/http -> fasthttp
// switching. Then manually convert net/http handlers to fasthttp handlers
// according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp .
func NewFastHTTPHandler(h http.Handler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
var r http.Request
body := ctx.PostBody()
r.Method = string(ctx.Method())
r.Proto = "HTTP/1.1"
r.ProtoMajor = 1
r.ProtoMinor = 1
r.RequestURI = string(ctx.RequestURI())
r.ContentLength = int64(len(body))
r.Host = string(ctx.Host())
r.RemoteAddr = ctx.RemoteAddr().String()
hdr := make(http.Header)
ctx.Request.Header.VisitAll(func(k, v []byte) {
hdr.Set(string(k), string(v))
})
r.Header = hdr
r.Body = &netHTTPBody{body}
rURL, err := url.ParseRequestURI(r.RequestURI)
if err != nil {
ctx.Logger().Printf("cannot parse requestURI %q: %s", r.RequestURI, err)
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
return
}
r.URL = rURL
var w netHTTPResponseWriter
h.ServeHTTP(&w, &r)
ctx.SetStatusCode(w.StatusCode())
for k, vv := range w.Header() {
for _, v := range vv {
ctx.Response.Header.Set(k, v)
}
}
ctx.Write(w.body)
}
}
type netHTTPBody struct {
b []byte
}
func (r *netHTTPBody) Read(p []byte) (int, error) {
if len(r.b) == 0 {
return 0, io.EOF
}
n := copy(p, r.b)
r.b = r.b[n:]
return n, nil
}
func (r *netHTTPBody) Close() error {
r.b = r.b[:0]
return nil
}
type netHTTPResponseWriter struct {
statusCode int
h http.Header
body []byte
}
func (w *netHTTPResponseWriter) StatusCode() int {
if w.statusCode == 0 {
return http.StatusOK
}
return w.statusCode
}
func (w *netHTTPResponseWriter) Header() http.Header {
if w.h == nil {
w.h = make(http.Header)
}
return w.h
}
func (w *netHTTPResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
func (w *netHTTPResponseWriter) Write(p []byte) (int, error) {
w.body = append(w.body, p...)
return len(p), nil
}

View File

@@ -0,0 +1,125 @@
package fasthttpadaptor
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"reflect"
"testing"
"github.com/valyala/fasthttp"
)
func TestNewFastHTTPHandler(t *testing.T) {
expectedMethod := "POST"
expectedProto := "HTTP/1.1"
expectedProtoMajor := 1
expectedProtoMinor := 1
expectedRequestURI := "/foo/bar?baz=123"
expectedBody := "body 123 foo bar baz"
expectedContentLength := len(expectedBody)
expectedHost := "foobar.com"
expectedRemoteAddr := "1.2.3.4:6789"
expectedHeader := map[string]string{
"Foo-Bar": "baz",
"Abc": "defg",
"XXX-Remote-Addr": "123.43.4543.345",
}
expectedURL, err := url.ParseRequestURI(expectedRequestURI)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
callsCount := 0
nethttpH := func(w http.ResponseWriter, r *http.Request) {
callsCount++
if r.Method != expectedMethod {
t.Fatalf("unexpected method %q. Expecting %q", r.Method, expectedMethod)
}
if r.Proto != expectedProto {
t.Fatalf("unexpected proto %q. Expecting %q", r.Proto, expectedProto)
}
if r.ProtoMajor != expectedProtoMajor {
t.Fatalf("unexpected protoMajor %d. Expecting %d", r.ProtoMajor, expectedProtoMajor)
}
if r.ProtoMinor != expectedProtoMinor {
t.Fatalf("unexpected protoMinor %d. Expecting %d", r.ProtoMinor, expectedProtoMinor)
}
if r.RequestURI != expectedRequestURI {
t.Fatalf("unexpected requestURI %q. Expecting %q", r.RequestURI, expectedRequestURI)
}
if r.ContentLength != int64(expectedContentLength) {
t.Fatalf("unexpected contentLength %d. Expecting %d", r.ContentLength, expectedContentLength)
}
if r.Host != expectedHost {
t.Fatalf("unexpected host %q. Expecting %q", r.Host, expectedHost)
}
if r.RemoteAddr != expectedRemoteAddr {
t.Fatalf("unexpected remoteAddr %q. Expecting %q", r.RemoteAddr, expectedRemoteAddr)
}
body, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
t.Fatalf("unexpected error when reading request body: %s", err)
}
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}
if !reflect.DeepEqual(r.URL, expectedURL) {
t.Fatalf("unexpected URL: %#v. Expecting %#v", r.URL, expectedURL)
}
for k, expectedV := range expectedHeader {
v := r.Header.Get(k)
if v != expectedV {
t.Fatalf("unexpected header value %q for key %q. Expecting %q", v, k, expectedV)
}
}
w.Header().Set("Header1", "value1")
w.Header().Set("Header2", "value2")
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "request body is %q", body)
}
fasthttpH := NewFastHTTPHandler(http.HandlerFunc(nethttpH))
var ctx fasthttp.RequestCtx
var req fasthttp.Request
req.Header.SetMethod(expectedMethod)
req.SetRequestURI(expectedRequestURI)
req.Header.SetHost(expectedHost)
req.BodyWriter().Write([]byte(expectedBody))
for k, v := range expectedHeader {
req.Header.Set(k, v)
}
remoteAddr, err := net.ResolveTCPAddr("tcp", expectedRemoteAddr)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
ctx.Init(&req, remoteAddr, nil)
fasthttpH(&ctx)
if callsCount != 1 {
t.Fatalf("unexpected callsCount: %d. Expecting 1", callsCount)
}
resp := &ctx.Response
if resp.StatusCode() != fasthttp.StatusBadRequest {
t.Fatalf("unexpected statusCode: %d. Expecting %d", resp.StatusCode(), fasthttp.StatusBadRequest)
}
if string(resp.Header.Peek("Header1")) != "value1" {
t.Fatalf("unexpected header value: %q. Expecting %q", resp.Header.Peek("Header1"), "value1")
}
if string(resp.Header.Peek("Header2")) != "value2" {
t.Fatalf("unexpected header value: %q. Expecting %q", resp.Header.Peek("Header2"), "value2")
}
expectedResponseBody := fmt.Sprintf("request body is %q", expectedBody)
if string(resp.Body()) != expectedResponseBody {
t.Fatalf("unexpected response body %q. Expecting %q", resp.Body(), expectedResponseBody)
}
}

View File

@@ -0,0 +1,2 @@
// Package fasthttputil provides utility functions for fasthttp.
package fasthttputil

View File

@@ -0,0 +1,84 @@
package fasthttputil
import (
"fmt"
"net"
"sync"
)
// InmemoryListener provides in-memory dialer<->net.Listener implementation.
//
// It may be used either for fast in-process client<->server communcations
// without network stack overhead or for client<->server tests.
type InmemoryListener struct {
lock sync.Mutex
closed bool
conns chan net.Conn
}
// NewInmemoryListener returns new in-memory dialer<->net.Listener.
func NewInmemoryListener() *InmemoryListener {
return &InmemoryListener{
conns: make(chan net.Conn, 1024),
}
}
// Accept implements net.Listener's Accept.
//
// It is safe calling Accept from concurrently running goroutines.
//
// Accept returns new connection per each Dial call.
func (ln *InmemoryListener) Accept() (net.Conn, error) {
c, ok := <-ln.conns
if !ok {
return nil, fmt.Errorf("InmemoryListener is already closed: use of closed network connection")
}
return c, nil
}
// Close implements net.Listener's Close.
func (ln *InmemoryListener) Close() error {
var err error
ln.lock.Lock()
if !ln.closed {
close(ln.conns)
ln.closed = true
} else {
err = fmt.Errorf("InmemoryListener is already closed")
}
ln.lock.Unlock()
return err
}
// Addr implements net.Listener's Addr.
func (ln *InmemoryListener) Addr() net.Addr {
return &net.UnixAddr{
Name: "InmemoryListener",
Net: "memory",
}
}
// Dial creates new client<->server connection, enqueues server side
// of the connection to Accept and returns client side of the connection.
//
// It is safe calling Dial from concurrently running goroutines.
func (ln *InmemoryListener) Dial() (net.Conn, error) {
pc := NewPipeConns()
cConn := pc.Conn1()
sConn := pc.Conn2()
ln.lock.Lock()
if !ln.closed {
ln.conns <- sConn
} else {
sConn.Close()
cConn.Close()
cConn = nil
}
ln.lock.Unlock()
if cConn == nil {
return nil, fmt.Errorf("InmemoryListener is already closed")
}
return cConn, nil
}

View File

@@ -0,0 +1,92 @@
package fasthttputil
import (
"bytes"
"fmt"
"testing"
"time"
)
func TestInmemoryListener(t *testing.T) {
ln := NewInmemoryListener()
ch := make(chan struct{})
for i := 0; i < 10; i++ {
go func(n int) {
conn, err := ln.Dial()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
defer conn.Close()
req := fmt.Sprintf("request_%d", n)
nn, err := conn.Write([]byte(req))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if nn != len(req) {
t.Fatalf("unexpected number of bytes written: %d. Expecting %d", nn, len(req))
}
buf := make([]byte, 30)
nn, err = conn.Read(buf)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
buf = buf[:nn]
resp := fmt.Sprintf("response_%d", n)
if nn != len(resp) {
t.Fatalf("unexpected number of bytes read: %d. Expecting %d", nn, len(resp))
}
if string(buf) != resp {
t.Fatalf("unexpected response %q. Expecting %q", buf, resp)
}
ch <- struct{}{}
}(i)
}
serverCh := make(chan struct{})
go func() {
for {
conn, err := ln.Accept()
if err != nil {
close(serverCh)
return
}
defer conn.Close()
buf := make([]byte, 30)
n, err := conn.Read(buf)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
buf = buf[:n]
if !bytes.HasPrefix(buf, []byte("request_")) {
t.Fatalf("unexpected request prefix %q. Expecting %q", buf, "request_")
}
resp := fmt.Sprintf("response_%s", buf[len("request_"):])
n, err = conn.Write([]byte(resp))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != len(resp) {
t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(resp))
}
}
}()
for i := 0; i < 10; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case <-serverCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}

View File

@@ -0,0 +1,108 @@
package fasthttputil_test
import (
"net"
"testing"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
)
// BenchmarkPlainStreaming measures end-to-end plaintext streaming performance
// for fasthttp client and server.
//
// It issues http requests over a small number of keep-alive connections.
func BenchmarkPlainStreaming(b *testing.B) {
benchmark(b, streamingHandler, false)
}
// BenchmarkPlainHandshake measures end-to-end plaintext handshake performance
// for fasthttp client and server.
//
// It re-establishes new connection per each http request.
func BenchmarkPlainHandshake(b *testing.B) {
benchmark(b, handshakeHandler, false)
}
// BenchmarkTLSStreaming measures end-to-end TLS streaming performance
// for fasthttp client and server.
//
// It issues http requests over a small number of TLS keep-alive connections.
func BenchmarkTLSStreaming(b *testing.B) {
benchmark(b, streamingHandler, true)
}
// BenchmarkTLSHandshake measures end-to-end TLS handshake performance
// for fasthttp client and server.
//
// It re-establishes new TLS connection per each http request.
func BenchmarkTLSHandshake(b *testing.B) {
benchmark(b, handshakeHandler, true)
}
func benchmark(b *testing.B, h fasthttp.RequestHandler, isTLS bool) {
ln := fasthttputil.NewInmemoryListener()
serverStopCh := startServer(b, ln, h, isTLS)
c := newClient(ln, isTLS)
b.RunParallel(func(pb *testing.PB) {
runRequests(b, pb, c)
})
ln.Close()
<-serverStopCh
}
func streamingHandler(ctx *fasthttp.RequestCtx) {
ctx.WriteString("foobar")
}
func handshakeHandler(ctx *fasthttp.RequestCtx) {
streamingHandler(ctx)
// Explicitly close connection after each response.
ctx.SetConnectionClose()
}
func startServer(b *testing.B, ln *fasthttputil.InmemoryListener, h fasthttp.RequestHandler, isTLS bool) <-chan struct{} {
ch := make(chan struct{})
go func() {
var err error
if isTLS {
err = fasthttp.ServeTLS(ln, certFile, keyFile, h)
} else {
err = fasthttp.Serve(ln, h)
}
if err != nil {
b.Fatalf("unexpected error in server: %s", err)
}
close(ch)
}()
return ch
}
const (
certFile = "./ssl-cert-snakeoil.pem"
keyFile = "./ssl-cert-snakeoil.key"
)
func newClient(ln *fasthttputil.InmemoryListener, isTLS bool) *fasthttp.HostClient {
return &fasthttp.HostClient{
Dial: func(addr string) (net.Conn, error) {
return ln.Dial()
},
IsTLS: isTLS,
}
}
func runRequests(b *testing.B, pb *testing.PB, c *fasthttp.HostClient) {
var req fasthttp.Request
req.SetRequestURI("http://foo.bar/baz")
var resp fasthttp.Response
for pb.Next() {
if err := c.Do(&req, &resp); err != nil {
b.Fatalf("unexpected error: %s", err)
}
if resp.StatusCode() != fasthttp.StatusOK {
b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), fasthttp.StatusOK)
}
}
}

View File

@@ -0,0 +1,221 @@
package fasthttputil
import (
"errors"
"io"
"net"
"sync"
"time"
)
// NewPipeConns returns new bi-directonal connection pipe.
func NewPipeConns() *PipeConns {
ch1 := make(chan *byteBuffer, 4)
ch2 := make(chan *byteBuffer, 4)
pc := &PipeConns{
stopCh: make(chan struct{}),
}
pc.c1.rCh = ch1
pc.c1.wCh = ch2
pc.c2.rCh = ch2
pc.c2.wCh = ch1
pc.c1.pc = pc
pc.c2.pc = pc
return pc
}
// PipeConns provides bi-directional connection pipe,
// which use in-process memory as a transport.
//
// PipeConns must be created by calling NewPipeConns.
//
// PipeConns has the following additional features comparing to connections
// returned from net.Pipe():
//
// * It is faster.
// * It buffers Write calls, so there is no need to have concurrent goroutine
// calling Read in order to unblock each Write call.
type PipeConns struct {
c1 pipeConn
c2 pipeConn
stopCh chan struct{}
stopChLock sync.Mutex
}
// Conn1 returns the first end of bi-directional pipe.
//
// Data written to Conn1 may be read from Conn2.
// Data written to Conn2 may be read from Conn1.
func (pc *PipeConns) Conn1() net.Conn {
return &pc.c1
}
// Conn2 returns the second end of bi-directional pipe.
//
// Data written to Conn2 may be read from Conn1.
// Data written to Conn1 may be read from Conn2.
func (pc *PipeConns) Conn2() net.Conn {
return &pc.c2
}
// Close closes pipe connections.
func (pc *PipeConns) Close() error {
pc.stopChLock.Lock()
select {
case <-pc.stopCh:
default:
close(pc.stopCh)
}
pc.stopChLock.Unlock()
return nil
}
type pipeConn struct {
b *byteBuffer
bb []byte
rCh chan *byteBuffer
wCh chan *byteBuffer
pc *PipeConns
}
func (c *pipeConn) Write(p []byte) (int, error) {
b := acquireByteBuffer()
b.b = append(b.b[:0], p...)
select {
case <-c.pc.stopCh:
releaseByteBuffer(b)
return 0, errConnectionClosed
default:
}
select {
case c.wCh <- b:
default:
select {
case c.wCh <- b:
case <-c.pc.stopCh:
releaseByteBuffer(b)
return 0, errConnectionClosed
}
}
return len(p), nil
}
func (c *pipeConn) Read(p []byte) (int, error) {
mayBlock := true
nn := 0
for len(p) > 0 {
n, err := c.read(p, mayBlock)
nn += n
if err != nil {
if !mayBlock && err == errWouldBlock {
err = nil
}
return nn, err
}
p = p[n:]
mayBlock = false
}
return nn, nil
}
func (c *pipeConn) read(p []byte, mayBlock bool) (int, error) {
if len(c.bb) == 0 {
if err := c.readNextByteBuffer(mayBlock); err != nil {
return 0, err
}
}
n := copy(p, c.bb)
c.bb = c.bb[n:]
return n, nil
}
func (c *pipeConn) readNextByteBuffer(mayBlock bool) error {
releaseByteBuffer(c.b)
c.b = nil
select {
case c.b = <-c.rCh:
default:
if !mayBlock {
return errWouldBlock
}
select {
case c.b = <-c.rCh:
case <-c.pc.stopCh:
return io.EOF
}
}
c.bb = c.b.b
return nil
}
var (
errWouldBlock = errors.New("would block")
errConnectionClosed = errors.New("connection closed")
errNoDeadlines = errors.New("deadline not supported")
)
func (c *pipeConn) Close() error {
return c.pc.Close()
}
func (c *pipeConn) LocalAddr() net.Addr {
return pipeAddr(0)
}
func (c *pipeConn) RemoteAddr() net.Addr {
return pipeAddr(0)
}
func (c *pipeConn) SetDeadline(t time.Time) error {
return errNoDeadlines
}
func (c *pipeConn) SetReadDeadline(t time.Time) error {
return c.SetDeadline(t)
}
func (c *pipeConn) SetWriteDeadline(t time.Time) error {
return c.SetDeadline(t)
}
type pipeAddr int
func (pipeAddr) Network() string {
return "pipe"
}
func (pipeAddr) String() string {
return "pipe"
}
type byteBuffer struct {
b []byte
}
func acquireByteBuffer() *byteBuffer {
return byteBufferPool.Get().(*byteBuffer)
}
func releaseByteBuffer(b *byteBuffer) {
if b != nil {
byteBufferPool.Put(b)
}
}
var byteBufferPool = &sync.Pool{
New: func() interface{} {
return &byteBuffer{
b: make([]byte, 1024),
}
},
}

View File

@@ -0,0 +1,236 @@
package fasthttputil
import (
"fmt"
"io"
"io/ioutil"
"net"
"testing"
"time"
)
func TestPipeConnsCloseWhileReadWriteConcurrent(t *testing.T) {
concurrency := 4
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testPipeConnsCloseWhileReadWriteSerial(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(3 * time.Second):
t.Fatalf("timeout")
}
}
}
func TestPipeConnsCloseWhileReadWriteSerial(t *testing.T) {
testPipeConnsCloseWhileReadWriteSerial(t)
}
func testPipeConnsCloseWhileReadWriteSerial(t *testing.T) {
for i := 0; i < 10; i++ {
testPipeConnsCloseWhileReadWrite(t)
}
}
func testPipeConnsCloseWhileReadWrite(t *testing.T) {
pc := NewPipeConns()
c1 := pc.Conn1()
c2 := pc.Conn2()
readCh := make(chan error)
go func() {
var err error
if _, err = io.Copy(ioutil.Discard, c1); err != nil {
if err != errConnectionClosed {
err = fmt.Errorf("unexpected error: %s", err)
} else {
err = nil
}
}
readCh <- err
}()
writeCh := make(chan error)
go func() {
var err error
for {
if _, err = c2.Write([]byte("foobar")); err != nil {
if err != errConnectionClosed {
err = fmt.Errorf("unexpected error: %s", err)
} else {
err = nil
}
break
}
}
writeCh <- err
}()
time.Sleep(10 * time.Millisecond)
if err := c1.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := c2.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case err := <-readCh:
if err != nil {
t.Fatalf("unexpected error in reader: %s", err)
}
case <-time.After(time.Second):
t.Fatalf("timeout")
}
select {
case err := <-writeCh:
if err != nil {
t.Fatalf("unexpected error in writer: %s", err)
}
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
func TestPipeConnsReadWriteSerial(t *testing.T) {
testPipeConnsReadWriteSerial(t)
}
func TestPipeConnsReadWriteConcurrent(t *testing.T) {
testConcurrency(t, 10, testPipeConnsReadWriteSerial)
}
func testPipeConnsReadWriteSerial(t *testing.T) {
pc := NewPipeConns()
testPipeConnsReadWrite(t, pc.Conn1(), pc.Conn2())
pc = NewPipeConns()
testPipeConnsReadWrite(t, pc.Conn2(), pc.Conn1())
}
func testPipeConnsReadWrite(t *testing.T, c1, c2 net.Conn) {
defer c1.Close()
defer c2.Close()
var buf [32]byte
for i := 0; i < 10; i++ {
// The first write
s1 := fmt.Sprintf("foo_%d", i)
n, err := c1.Write([]byte(s1))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != len(s1) {
t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(s1))
}
// The second write
s2 := fmt.Sprintf("bar_%d", i)
n, err = c1.Write([]byte(s2))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != len(s2) {
t.Fatalf("unexpected number of bytes written: %d. Expecting %d", n, len(s2))
}
// Read data written above in two writes
s := s1 + s2
n, err = c2.Read(buf[:])
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != len(s) {
t.Fatalf("unexpected number of bytes read: %d. Expecting %d", n, len(s))
}
if string(buf[:n]) != s {
t.Fatalf("unexpected string read: %q. Expecting %q", buf[:n], s)
}
}
}
func TestPipeConnsCloseSerial(t *testing.T) {
testPipeConnsCloseSerial(t)
}
func TestPipeConnsCloseConcurrent(t *testing.T) {
testConcurrency(t, 10, testPipeConnsCloseSerial)
}
func testPipeConnsCloseSerial(t *testing.T) {
pc := NewPipeConns()
testPipeConnsClose(t, pc.Conn1(), pc.Conn2())
pc = NewPipeConns()
testPipeConnsClose(t, pc.Conn2(), pc.Conn1())
}
func testPipeConnsClose(t *testing.T, c1, c2 net.Conn) {
if err := c1.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
var buf [10]byte
// attempt writing to closed conn
for i := 0; i < 10; i++ {
n, err := c1.Write(buf[:])
if err == nil {
t.Fatalf("expecting error")
}
if n != 0 {
t.Fatalf("unexpected number of bytes written: %d. Expecting 0", n)
}
}
// attempt reading from closed conn
for i := 0; i < 10; i++ {
n, err := c2.Read(buf[:])
if err == nil {
t.Fatalf("expecting error")
}
if err != io.EOF {
t.Fatalf("unexpected error: %s. Expecting %s", err, io.EOF)
}
if n != 0 {
t.Fatalf("unexpected number of bytes read: %d. Expecting 0", n)
}
}
if err := c2.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
// attempt closing already closed conns
for i := 0; i < 10; i++ {
if err := c1.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
if err := c2.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
}
func testConcurrency(t *testing.T, concurrency int, f func(*testing.T)) {
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
f(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG
3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U
wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0
FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf
IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg
GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF
sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2
sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D
uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb
K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3
YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+
DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk
B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV
Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x
IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY
wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj
wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D
FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m
tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX
fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU
ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk
K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT
6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt
9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN
Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV
c257YgaWmjK9uB0Y2r2VxS0G
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV
MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D
K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te
+z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij
L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1
xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY
6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG
SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98
L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2
45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li
K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6
X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI
whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd
-----END CERTIFICATE-----

1257
vendor/github.com/valyala/fasthttp/fs.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

28
vendor/github.com/valyala/fasthttp/fs_example_test.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package fasthttp_test
import (
"log"
"github.com/valyala/fasthttp"
)
func ExampleFS() {
fs := &fasthttp.FS{
// Path to directory to serve.
Root: "/var/www/static-site",
// Generate index pages if client requests directory contents.
GenerateIndexPages: true,
// Enable transparent compression to save network traffic.
Compress: true,
}
// Create request handler for serving static files.
h := fs.NewRequestHandler()
// Start the server.
if err := fasthttp.ListenAndServe(":8080", h); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}

View File

@@ -0,0 +1,47 @@
package fasthttp_test
import (
"bytes"
"log"
"github.com/valyala/fasthttp"
)
// Setup file handlers (aka 'file server config')
var (
// Handler for serving images from /img/ path,
// i.e. /img/foo/bar.jpg will be served from
// /var/www/images/foo/bar.jpb .
imgPrefix = []byte("/img/")
imgHandler = fasthttp.FSHandler("/var/www/images", 1)
// Handler for serving css from /static/css/ path,
// i.e. /static/css/foo/bar.css will be served from
// /home/dev/css/foo/bar.css .
cssPrefix = []byte("/static/css/")
cssHandler = fasthttp.FSHandler("/home/dev/css", 2)
// Handler for serving the rest of requests,
// i.e. /foo/bar/baz.html will be served from
// /var/www/files/foo/bar/baz.html .
filesHandler = fasthttp.FSHandler("/var/www/files", 0)
)
// Main request handler
func requestHandler(ctx *fasthttp.RequestCtx) {
path := ctx.Path()
switch {
case bytes.HasPrefix(path, imgPrefix):
imgHandler(ctx)
case bytes.HasPrefix(path, cssPrefix):
cssHandler(ctx)
default:
filesHandler(ctx)
}
}
func ExampleFSHandler() {
if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil {
log.Fatalf("Error in server: %s", err)
}
}

586
vendor/github.com/valyala/fasthttp/fs_test.go generated vendored Normal file
View File

@@ -0,0 +1,586 @@
package fasthttp
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"math/rand"
"os"
"sort"
"testing"
"time"
)
func TestNewVHostPathRewriter(t *testing.T) {
var ctx RequestCtx
var req Request
req.Header.SetHost("foobar.com")
req.SetRequestURI("/foo/bar/baz")
ctx.Init(&req, nil, nil)
f := NewVHostPathRewriter(0)
path := f(&ctx)
expectedPath := "/foobar.com/foo/bar/baz"
if string(path) != expectedPath {
t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath)
}
ctx.Request.Reset()
ctx.Request.SetRequestURI("https://aaa.bbb.cc/one/two/three/four?asdf=dsf")
f = NewVHostPathRewriter(2)
path = f(&ctx)
expectedPath = "/aaa.bbb.cc/three/four"
if string(path) != expectedPath {
t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath)
}
}
func TestNewVHostPathRewriterMaliciousHost(t *testing.T) {
var ctx RequestCtx
var req Request
req.Header.SetHost("/../../../etc/passwd")
req.SetRequestURI("/foo/bar/baz")
ctx.Init(&req, nil, nil)
f := NewVHostPathRewriter(0)
path := f(&ctx)
expectedPath := "/invalid-host/foo/bar/baz"
if string(path) != expectedPath {
t.Fatalf("unexpected path %q. Expecting %q", path, expectedPath)
}
}
func TestServeFileHead(t *testing.T) {
var ctx RequestCtx
var req Request
req.Header.SetMethod("HEAD")
req.SetRequestURI("http://foobar.com/baz")
ctx.Init(&req, nil, nil)
ServeFile(&ctx, "fs.go")
var resp Response
resp.SkipBody = true
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ce := resp.Header.Peek("Content-Encoding")
if len(ce) > 0 {
t.Fatalf("Unexpected 'Content-Encoding' %q", ce)
}
body := resp.Body()
if len(body) > 0 {
t.Fatalf("unexpected response body %q. Expecting empty body", body)
}
expectedBody, err := getFileContents("/fs.go")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
contentLength := resp.Header.ContentLength()
if contentLength != len(expectedBody) {
t.Fatalf("unexpected Content-Length: %d. expecting %d", contentLength, len(expectedBody))
}
}
func TestServeFileCompressed(t *testing.T) {
var ctx RequestCtx
var req Request
req.SetRequestURI("http://foobar.com/baz")
req.Header.Set("Accept-Encoding", "gzip")
ctx.Init(&req, nil, nil)
ServeFile(&ctx, "fs.go")
var resp Response
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ce := resp.Header.Peek("Content-Encoding")
if string(ce) != "gzip" {
t.Fatalf("Unexpected 'Content-Encoding' %q. Expecting %q", ce, "gzip")
}
body, err := resp.BodyGunzip()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
expectedBody, err := getFileContents("/fs.go")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !bytes.Equal(body, expectedBody) {
t.Fatalf("unexpected body %q. expecting %q", body, expectedBody)
}
}
func TestServeFileUncompressed(t *testing.T) {
var ctx RequestCtx
var req Request
req.SetRequestURI("http://foobar.com/baz")
req.Header.Set("Accept-Encoding", "gzip")
ctx.Init(&req, nil, nil)
ServeFileUncompressed(&ctx, "fs.go")
var resp Response
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
ce := resp.Header.Peek("Content-Encoding")
if len(ce) > 0 {
t.Fatalf("Unexpected 'Content-Encoding' %q", ce)
}
body := resp.Body()
expectedBody, err := getFileContents("/fs.go")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !bytes.Equal(body, expectedBody) {
t.Fatalf("unexpected body %q. expecting %q", body, expectedBody)
}
}
func TestFSByteRangeConcurrent(t *testing.T) {
fs := &FS{
Root: ".",
AcceptByteRange: true,
}
h := fs.NewRequestHandler()
concurrency := 10
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
for j := 0; j < 5; j++ {
testFSByteRange(t, h, "/fs.go")
testFSByteRange(t, h, "/README.md")
}
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-time.After(time.Second):
t.Fatalf("timeout")
case <-ch:
}
}
}
func TestFSByteRangeSingleThread(t *testing.T) {
fs := &FS{
Root: ".",
AcceptByteRange: true,
}
h := fs.NewRequestHandler()
testFSByteRange(t, h, "/fs.go")
testFSByteRange(t, h, "/README.md")
}
func testFSByteRange(t *testing.T, h RequestHandler, filePath string) {
var ctx RequestCtx
ctx.Init(&Request{}, nil, nil)
expectedBody, err := getFileContents(filePath)
if err != nil {
t.Fatalf("cannot read file %q: %s", filePath, err)
}
fileSize := len(expectedBody)
startPos := rand.Intn(fileSize)
endPos := rand.Intn(fileSize)
if endPos < startPos {
startPos, endPos = endPos, startPos
}
ctx.Request.SetRequestURI(filePath)
ctx.Request.Header.SetByteRange(startPos, endPos)
h(&ctx)
var resp Response
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s. filePath=%q", err, filePath)
}
if resp.StatusCode() != StatusPartialContent {
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusPartialContent, filePath)
}
cr := resp.Header.Peek("Content-Range")
expectedCR := fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, fileSize)
if string(cr) != expectedCR {
t.Fatalf("unexpected content-range %q. Expecting %q. filePath=%q", cr, expectedCR, filePath)
}
body := resp.Body()
bodySize := endPos - startPos + 1
if len(body) != bodySize {
t.Fatalf("unexpected body size %d. Expecting %d. filePath=%q, startPos=%d, endPos=%d",
len(body), bodySize, filePath, startPos, endPos)
}
expectedBody = expectedBody[startPos : endPos+1]
if !bytes.Equal(body, expectedBody) {
t.Fatalf("unexpected body %q. Expecting %q. filePath=%q, startPos=%d, endPos=%d",
body, expectedBody, filePath, startPos, endPos)
}
}
func getFileContents(path string) ([]byte, error) {
path = "." + path
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}
func TestParseByteRangeSuccess(t *testing.T) {
testParseByteRangeSuccess(t, "bytes=0-0", 1, 0, 0)
testParseByteRangeSuccess(t, "bytes=1234-6789", 6790, 1234, 6789)
testParseByteRangeSuccess(t, "bytes=123-", 456, 123, 455)
testParseByteRangeSuccess(t, "bytes=-1", 1, 0, 0)
testParseByteRangeSuccess(t, "bytes=-123", 456, 333, 455)
// End position exceeding content-length. It should be updated to content-length-1.
// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
testParseByteRangeSuccess(t, "bytes=1-2345", 234, 1, 233)
testParseByteRangeSuccess(t, "bytes=0-2345", 2345, 0, 2344)
// Start position overflow. Whole range must be returned.
// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
testParseByteRangeSuccess(t, "bytes=-567", 56, 0, 55)
}
func testParseByteRangeSuccess(t *testing.T, v string, contentLength, startPos, endPos int) {
startPos1, endPos1, err := ParseByteRange([]byte(v), contentLength)
if err != nil {
t.Fatalf("unexpected error: %s. v=%q, contentLength=%d", err, v, contentLength)
}
if startPos1 != startPos {
t.Fatalf("unexpected startPos=%d. Expecting %d. v=%q, contentLength=%d", startPos1, startPos, v, contentLength)
}
if endPos1 != endPos {
t.Fatalf("unexpected endPos=%d. Expectind %d. v=%q, contentLenght=%d", endPos1, endPos, v, contentLength)
}
}
func TestParseByteRangeError(t *testing.T) {
// invalid value
testParseByteRangeError(t, "asdfasdfas", 1234)
// invalid units
testParseByteRangeError(t, "foobar=1-34", 600)
// missing '-'
testParseByteRangeError(t, "bytes=1234", 1235)
// non-numeric range
testParseByteRangeError(t, "bytes=foobar", 123)
testParseByteRangeError(t, "bytes=1-foobar", 123)
testParseByteRangeError(t, "bytes=df-344", 545)
// multiple byte ranges
testParseByteRangeError(t, "bytes=1-2,4-6", 123)
// byte range exceeding contentLength
testParseByteRangeError(t, "bytes=123-", 12)
// startPos exceeding endPos
testParseByteRangeError(t, "bytes=123-34", 1234)
}
func testParseByteRangeError(t *testing.T, v string, contentLength int) {
_, _, err := ParseByteRange([]byte(v), contentLength)
if err == nil {
t.Fatalf("expecting error when parsing byte range %q", v)
}
}
func TestFSCompressConcurrent(t *testing.T) {
fs := &FS{
Root: ".",
GenerateIndexPages: true,
Compress: true,
}
h := fs.NewRequestHandler()
concurrency := 4
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
for j := 0; j < 5; j++ {
testFSCompress(t, h, "/fs.go")
testFSCompress(t, h, "/")
testFSCompress(t, h, "/README.md")
}
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func TestFSCompressSingleThread(t *testing.T) {
fs := &FS{
Root: ".",
GenerateIndexPages: true,
Compress: true,
}
h := fs.NewRequestHandler()
testFSCompress(t, h, "/fs.go")
testFSCompress(t, h, "/")
testFSCompress(t, h, "/README.md")
}
func testFSCompress(t *testing.T, h RequestHandler, filePath string) {
var ctx RequestCtx
ctx.Init(&Request{}, nil, nil)
// request uncompressed file
ctx.Request.Reset()
ctx.Request.SetRequestURI(filePath)
h(&ctx)
var resp Response
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s. filePath=%q", err, filePath)
}
if resp.StatusCode() != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusOK, filePath)
}
ce := resp.Header.Peek("Content-Encoding")
if string(ce) != "" {
t.Fatalf("unexpected content-encoding %q. Expecting empty string. filePath=%q", ce, filePath)
}
body := string(resp.Body())
// request compressed file
ctx.Request.Reset()
ctx.Request.SetRequestURI(filePath)
ctx.Request.Header.Set("Accept-Encoding", "gzip")
h(&ctx)
s = ctx.Response.String()
br = bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %s. filePath=%q", err, filePath)
}
if resp.StatusCode() != StatusOK {
t.Fatalf("unexpected status code: %d. Expecting %d. filePath=%q", resp.StatusCode(), StatusOK, filePath)
}
ce = resp.Header.Peek("Content-Encoding")
if string(ce) != "gzip" {
t.Fatalf("unexpected content-encoding %q. Expecting %q. filePath=%q", ce, "gzip", filePath)
}
zbody, err := resp.BodyGunzip()
if err != nil {
t.Fatalf("unexpected error when gunzipping response body: %s. filePath=%q", err, filePath)
}
if string(zbody) != body {
t.Fatalf("unexpected body %q. Expected %q. FilePath=%q", zbody, body, filePath)
}
}
func TestFileLock(t *testing.T) {
for i := 0; i < 10; i++ {
filePath := fmt.Sprintf("foo/bar/%d.jpg", i)
lock := getFileLock(filePath)
lock.Lock()
lock.Unlock()
}
for i := 0; i < 10; i++ {
filePath := fmt.Sprintf("foo/bar/%d.jpg", i)
lock := getFileLock(filePath)
lock.Lock()
lock.Unlock()
}
}
func TestFSHandlerSingleThread(t *testing.T) {
requestHandler := FSHandler(".", 0)
f, err := os.Open(".")
if err != nil {
t.Fatalf("cannot open cwd: %s", err)
}
filenames, err := f.Readdirnames(0)
f.Close()
if err != nil {
t.Fatalf("cannot read dirnames in cwd: %s", err)
}
sort.Sort(sort.StringSlice(filenames))
for i := 0; i < 3; i++ {
fsHandlerTest(t, requestHandler, filenames)
}
}
func TestFSHandlerConcurrent(t *testing.T) {
requestHandler := FSHandler(".", 0)
f, err := os.Open(".")
if err != nil {
t.Fatalf("cannot open cwd: %s", err)
}
filenames, err := f.Readdirnames(0)
f.Close()
if err != nil {
t.Fatalf("cannot read dirnames in cwd: %s", err)
}
sort.Sort(sort.StringSlice(filenames))
concurrency := 10
ch := make(chan struct{}, concurrency)
for j := 0; j < concurrency; j++ {
go func() {
for i := 0; i < 3; i++ {
fsHandlerTest(t, requestHandler, filenames)
}
ch <- struct{}{}
}()
}
for j := 0; j < concurrency; j++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func fsHandlerTest(t *testing.T, requestHandler RequestHandler, filenames []string) {
var ctx RequestCtx
var req Request
ctx.Init(&req, nil, defaultLogger)
ctx.Request.Header.SetHost("foobar.com")
filesTested := 0
for _, name := range filenames {
f, err := os.Open(name)
if err != nil {
t.Fatalf("cannot open file %q: %s", name, err)
}
stat, err := f.Stat()
if err != nil {
t.Fatalf("cannot get file stat %q: %s", name, err)
}
if stat.IsDir() {
f.Close()
continue
}
data, err := ioutil.ReadAll(f)
f.Close()
if err != nil {
t.Fatalf("cannot read file contents %q: %s", name, err)
}
ctx.URI().Update(name)
requestHandler(&ctx)
if ctx.Response.bodyStream == nil {
t.Fatalf("response body stream must be non-empty")
}
body, err := ioutil.ReadAll(ctx.Response.bodyStream)
if err != nil {
t.Fatalf("error when reading response body stream: %s", err)
}
if !bytes.Equal(body, data) {
t.Fatalf("unexpected body returned: %q. Expecting %q", body, data)
}
filesTested++
if filesTested >= 10 {
break
}
}
// verify index page generation
ctx.URI().Update("/")
requestHandler(&ctx)
if ctx.Response.bodyStream == nil {
t.Fatalf("response body stream must be non-empty")
}
body, err := ioutil.ReadAll(ctx.Response.bodyStream)
if err != nil {
t.Fatalf("error when reading response body stream: %s", err)
}
if len(body) == 0 {
t.Fatalf("index page must be non-empty")
}
}
func TestStripPathSlashes(t *testing.T) {
testStripPathSlashes(t, "", 0, "")
testStripPathSlashes(t, "", 10, "")
testStripPathSlashes(t, "/", 0, "")
testStripPathSlashes(t, "/", 1, "")
testStripPathSlashes(t, "/", 10, "")
testStripPathSlashes(t, "/foo/bar/baz", 0, "/foo/bar/baz")
testStripPathSlashes(t, "/foo/bar/baz", 1, "/bar/baz")
testStripPathSlashes(t, "/foo/bar/baz", 2, "/baz")
testStripPathSlashes(t, "/foo/bar/baz", 3, "")
testStripPathSlashes(t, "/foo/bar/baz", 10, "")
// trailing slash
testStripPathSlashes(t, "/foo/bar/", 0, "/foo/bar")
testStripPathSlashes(t, "/foo/bar/", 1, "/bar")
testStripPathSlashes(t, "/foo/bar/", 2, "")
testStripPathSlashes(t, "/foo/bar/", 3, "")
}
func testStripPathSlashes(t *testing.T, path string, stripSlashes int, expectedPath string) {
s := stripLeadingSlashes([]byte(path), stripSlashes)
s = stripTrailingSlashes(s)
if string(s) != expectedPath {
t.Fatalf("unexpected path after stripping %q with stripSlashes=%d: %q. Expecting %q", path, stripSlashes, s, expectedPath)
}
}
func TestFileExtension(t *testing.T) {
testFileExtension(t, "foo.bar", false, "zzz", ".bar")
testFileExtension(t, "foobar", false, "zzz", "")
testFileExtension(t, "foo.bar.baz", false, "zzz", ".baz")
testFileExtension(t, "", false, "zzz", "")
testFileExtension(t, "/a/b/c.d/efg.jpg", false, ".zzz", ".jpg")
testFileExtension(t, "foo.bar", true, ".zzz", ".bar")
testFileExtension(t, "foobar.zzz", true, ".zzz", "")
testFileExtension(t, "foo.bar.baz.fasthttp.gz", true, ".fasthttp.gz", ".baz")
testFileExtension(t, "", true, ".zzz", "")
testFileExtension(t, "/a/b/c.d/efg.jpg.xxx", true, ".xxx", ".jpg")
}
func testFileExtension(t *testing.T, path string, compressed bool, compressedFileSuffix, expectedExt string) {
ext := fileExtension(path, compressed, compressedFileSuffix)
if ext != expectedExt {
t.Fatalf("unexpected file extension for file %q: %q. Expecting %q", path, ext, expectedExt)
}
}

2038
vendor/github.com/valyala/fasthttp/header.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
package fasthttp
import (
"bufio"
"bytes"
"fmt"
"strings"
"testing"
)
func TestIssue28ResponseWithoutBodyNoContentType(t *testing.T) {
var r Response
// Empty response without content-type
s := r.String()
if strings.Contains(s, "Content-Type") {
t.Fatalf("unexpected Content-Type found in response header with empty body: %q", s)
}
// Explicitly set content-type
r.Header.SetContentType("foo/bar")
s = r.String()
if !strings.Contains(s, "Content-Type: foo/bar\r\n") {
t.Fatalf("missing explicitly set content-type for empty response: %q", s)
}
// Non-empty response.
r.Reset()
r.SetBodyString("foobar")
s = r.String()
if !strings.Contains(s, fmt.Sprintf("Content-Type: %s\r\n", defaultContentType)) {
t.Fatalf("missing default content-type for non-empty response: %q", s)
}
// Non-empty response with custom content-type.
r.Header.SetContentType("aaa/bbb")
s = r.String()
if !strings.Contains(s, "Content-Type: aaa/bbb\r\n") {
t.Fatalf("missing custom content-type: %q", s)
}
}
func TestIssue6RequestHeaderSetContentType(t *testing.T) {
testIssue6RequestHeaderSetContentType(t, "GET")
testIssue6RequestHeaderSetContentType(t, "POST")
testIssue6RequestHeaderSetContentType(t, "PUT")
testIssue6RequestHeaderSetContentType(t, "PATCH")
}
func testIssue6RequestHeaderSetContentType(t *testing.T, method string) {
contentType := "application/json"
contentLength := 123
var h RequestHeader
h.SetMethod(method)
h.SetRequestURI("http://localhost/test")
h.SetContentType(contentType)
h.SetContentLength(contentLength)
issue6VerifyRequestHeader(t, &h, contentType, contentLength, method)
s := h.String()
var h1 RequestHeader
br := bufio.NewReader(bytes.NewBufferString(s))
if err := h1.Read(br); err != nil {
t.Fatalf("unexpected error: %s", err)
}
issue6VerifyRequestHeader(t, &h1, contentType, contentLength, method)
}
func issue6VerifyRequestHeader(t *testing.T, h *RequestHeader, contentType string, contentLength int, method string) {
if string(h.ContentType()) != contentType {
t.Fatalf("unexpected content-type: %q. Expecting %q. method=%q", h.ContentType(), contentType, method)
}
if string(h.Method()) != method {
t.Fatalf("unexpected method: %q. Expecting %q", h.Method(), method)
}
if method != "GET" {
if h.ContentLength() != contentLength {
t.Fatalf("unexpected content-length: %d. Expecting %d. method=%q", h.ContentLength(), contentLength, method)
}
} else if h.ContentLength() != 0 {
t.Fatalf("unexpected content-length for GET method: %d. Expecting 0", h.ContentLength())
}
}

1972
vendor/github.com/valyala/fasthttp/header_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
package fasthttp
import (
"bufio"
"bytes"
"io"
"testing"
)
var strFoobar = []byte("foobar.com")
type benchReadBuf struct {
s []byte
n int
}
func (r *benchReadBuf) Read(p []byte) (int, error) {
if r.n == len(r.s) {
return 0, io.EOF
}
n := copy(p, r.s[r.n:])
r.n += n
return n, nil
}
func BenchmarkRequestHeaderRead(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h RequestHeader
buf := &benchReadBuf{
s: []byte("GET /foo/bar HTTP/1.1\r\nHost: foobar.com\r\nUser-Agent: aaa.bbb\r\nReferer: http://google.com/aaa/bbb\r\n\r\n"),
}
br := bufio.NewReader(buf)
for pb.Next() {
buf.n = 0
br.Reset(buf)
if err := h.Read(br); err != nil {
b.Fatalf("unexpected error when reading header: %s", err)
}
}
})
}
func BenchmarkResponseHeaderRead(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h ResponseHeader
buf := &benchReadBuf{
s: []byte("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 1256\r\nServer: aaa 1/2.3\r\nTest: 1.2.3\r\n\r\n"),
}
br := bufio.NewReader(buf)
for pb.Next() {
buf.n = 0
br.Reset(buf)
if err := h.Read(br); err != nil {
b.Fatalf("unexpected error when reading header: %s", err)
}
}
})
}
func BenchmarkRequestHeaderWrite(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h RequestHeader
h.SetRequestURI("/foo/bar")
h.SetHost("foobar.com")
h.SetUserAgent("aaa.bbb")
h.SetReferer("http://google.com/aaa/bbb")
var w ByteBuffer
for pb.Next() {
if _, err := h.WriteTo(&w); err != nil {
b.Fatalf("unexpected error when writing header: %s", err)
}
w.Reset()
}
})
}
func BenchmarkResponseHeaderWrite(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h ResponseHeader
h.SetStatusCode(200)
h.SetContentType("text/html")
h.SetContentLength(1256)
h.SetServer("aaa 1/2.3")
h.Set("Test", "1.2.3")
var w ByteBuffer
for pb.Next() {
if _, err := h.WriteTo(&w); err != nil {
b.Fatalf("unexpected error when writing header: %s", err)
}
w.Reset()
}
})
}
func BenchmarkRequestHeaderPeekBytesCanonical(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h RequestHeader
h.SetBytesV("Host", strFoobar)
for pb.Next() {
v := h.PeekBytes(strHost)
if !bytes.Equal(v, strFoobar) {
b.Fatalf("unexpected result: %q. Expected %q", v, strFoobar)
}
}
})
}
func BenchmarkRequestHeaderPeekBytesNonCanonical(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var h RequestHeader
h.SetBytesV("Host", strFoobar)
hostBytes := []byte("HOST")
for pb.Next() {
v := h.PeekBytes(hostBytes)
if !bytes.Equal(v, strFoobar) {
b.Fatalf("unexpected result: %q. Expected %q", v, strFoobar)
}
}
})
}
func BenchmarkNormalizeHeaderKeyCommonCase(b *testing.B) {
src := []byte("User-Agent-Host-Content-Type-Content-Length-Server")
benchmarkNormalizeHeaderKey(b, src)
}
func BenchmarkNormalizeHeaderKeyLowercase(b *testing.B) {
src := []byte("user-agent-host-content-type-content-length-server")
benchmarkNormalizeHeaderKey(b, src)
}
func BenchmarkNormalizeHeaderKeyUppercase(b *testing.B) {
src := []byte("USER-AGENT-HOST-CONTENT-TYPE-CONTENT-LENGTH-SERVER")
benchmarkNormalizeHeaderKey(b, src)
}
func benchmarkNormalizeHeaderKey(b *testing.B, src []byte) {
b.RunParallel(func(pb *testing.PB) {
buf := make([]byte, len(src))
for pb.Next() {
copy(buf, src)
normalizeHeaderKey(buf, false)
}
})
}

1568
vendor/github.com/valyala/fasthttp/http.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1521
vendor/github.com/valyala/fasthttp/http_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

9
vendor/github.com/valyala/fasthttp/nocopy.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
package fasthttp
// Embed this type into a struct, which mustn't be copied,
// so `go vet` gives a warning if this struct is copied.
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
type noCopy struct{}
func (*noCopy) Lock() {}

100
vendor/github.com/valyala/fasthttp/peripconn.go generated vendored Normal file
View File

@@ -0,0 +1,100 @@
package fasthttp
import (
"fmt"
"net"
"sync"
)
type perIPConnCounter struct {
pool sync.Pool
lock sync.Mutex
m map[uint32]int
}
func (cc *perIPConnCounter) Register(ip uint32) int {
cc.lock.Lock()
if cc.m == nil {
cc.m = make(map[uint32]int)
}
n := cc.m[ip] + 1
cc.m[ip] = n
cc.lock.Unlock()
return n
}
func (cc *perIPConnCounter) Unregister(ip uint32) {
cc.lock.Lock()
if cc.m == nil {
cc.lock.Unlock()
panic("BUG: perIPConnCounter.Register() wasn't called")
}
n := cc.m[ip] - 1
if n < 0 {
cc.lock.Unlock()
panic(fmt.Sprintf("BUG: negative per-ip counter=%d for ip=%d", n, ip))
}
cc.m[ip] = n
cc.lock.Unlock()
}
type perIPConn struct {
net.Conn
ip uint32
perIPConnCounter *perIPConnCounter
}
func acquirePerIPConn(conn net.Conn, ip uint32, counter *perIPConnCounter) *perIPConn {
v := counter.pool.Get()
if v == nil {
v = &perIPConn{
perIPConnCounter: counter,
}
}
c := v.(*perIPConn)
c.Conn = conn
c.ip = ip
return c
}
func releasePerIPConn(c *perIPConn) {
c.Conn = nil
c.perIPConnCounter.pool.Put(c)
}
func (c *perIPConn) Close() error {
err := c.Conn.Close()
c.perIPConnCounter.Unregister(c.ip)
releasePerIPConn(c)
return err
}
func getUint32IP(c net.Conn) uint32 {
return ip2uint32(getConnIP4(c))
}
func getConnIP4(c net.Conn) net.IP {
addr := c.RemoteAddr()
ipAddr, ok := addr.(*net.TCPAddr)
if !ok {
return net.IPv4zero
}
return ipAddr.IP.To4()
}
func ip2uint32(ip net.IP) uint32 {
if len(ip) != 4 {
return 0
}
return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])
}
func uint322ip(ip uint32) net.IP {
b := make([]byte, 4)
b[0] = byte(ip >> 24)
b[1] = byte(ip >> 16)
b[2] = byte(ip >> 8)
b[3] = byte(ip)
return b
}

59
vendor/github.com/valyala/fasthttp/peripconn_test.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package fasthttp
import (
"testing"
)
func TestIPxUint32(t *testing.T) {
testIPxUint32(t, 0)
testIPxUint32(t, 10)
testIPxUint32(t, 0x12892392)
}
func testIPxUint32(t *testing.T, n uint32) {
ip := uint322ip(n)
nn := ip2uint32(ip)
if n != nn {
t.Fatalf("Unexpected value=%d for ip=%s. Expected %d", nn, ip, n)
}
}
func TestPerIPConnCounter(t *testing.T) {
var cc perIPConnCounter
expectPanic(t, func() { cc.Unregister(123) })
for i := 1; i < 100; i++ {
if n := cc.Register(123); n != i {
t.Fatalf("Unexpected counter value=%d. Expected %d", n, i)
}
}
n := cc.Register(456)
if n != 1 {
t.Fatalf("Unexpected counter value=%d. Expected 1", n)
}
for i := 1; i < 100; i++ {
cc.Unregister(123)
}
cc.Unregister(456)
expectPanic(t, func() { cc.Unregister(123) })
expectPanic(t, func() { cc.Unregister(456) })
n = cc.Register(123)
if n != 1 {
t.Fatalf("Unexpected counter value=%d. Expected 1", n)
}
cc.Unregister(123)
}
func expectPanic(t *testing.T, f func()) {
defer func() {
if r := recover(); r == nil {
t.Fatalf("Expecting panic")
}
}()
f()
}

View File

@@ -0,0 +1,32 @@
package fasthttp_test
import (
"bufio"
"fmt"
"log"
"time"
"github.com/valyala/fasthttp"
)
func ExampleRequestCtx_SetBodyStreamWriter() {
// Start fasthttp server for streaming responses.
if err := fasthttp.ListenAndServe(":8080", responseStreamHandler); err != nil {
log.Fatalf("unexpected error in server: %s", err)
}
}
func responseStreamHandler(ctx *fasthttp.RequestCtx) {
// Send the response in chunks and wait for a second between each chunk.
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
for i := 0; i < 10; i++ {
fmt.Fprintf(w, "this is a message number %d", i)
// Do not forget flushing streamed data to the client.
if err := w.Flush(); err != nil {
return
}
time.Sleep(time.Second)
}
})
}

21
vendor/github.com/valyala/fasthttp/reuseport/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Max Riveiro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,106 @@
// +build linux darwin dragonfly freebsd netbsd openbsd rumprun
// Package reuseport provides TCP net.Listener with SO_REUSEPORT support.
//
// SO_REUSEPORT allows linear scaling server performance on multi-CPU servers.
// See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for more details :)
//
// The package is based on https://github.com/kavu/go_reuseport .
package reuseport
import (
"errors"
"fmt"
"net"
"os"
"syscall"
)
func getSockaddr(network, addr string) (sa syscall.Sockaddr, soType int, err error) {
// TODO: add support for tcp and tcp6 networks.
if network != "tcp4" {
return nil, -1, errors.New("only tcp4 network is supported")
}
tcpAddr, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, -1, err
}
var sa4 syscall.SockaddrInet4
sa4.Port = tcpAddr.Port
copy(sa4.Addr[:], tcpAddr.IP.To4())
return &sa4, syscall.AF_INET, nil
}
// ErrNoReusePort is returned if the OS doesn't support SO_REUSEPORT.
type ErrNoReusePort struct {
err error
}
// Error implements error interface.
func (e *ErrNoReusePort) Error() string {
return fmt.Sprintf("The OS doesn't support SO_REUSEPORT: %s", e.err)
}
// Listen returns TCP listener with SO_REUSEPORT option set.
//
// Only tcp4 network is supported.
//
// ErrNoReusePort error is returned if the system doesn't support SO_REUSEPORT.
func Listen(network, addr string) (l net.Listener, err error) {
var (
soType, fd int
file *os.File
sockaddr syscall.Sockaddr
)
if sockaddr, soType, err = getSockaddr(network, addr); err != nil {
return nil, err
}
syscall.ForkLock.RLock()
fd, err = syscall.Socket(soType, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err == nil {
syscall.CloseOnExec(fd)
}
syscall.ForkLock.RUnlock()
if err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, soReusePort, 1); err != nil {
syscall.Close(fd)
return nil, &ErrNoReusePort{err}
}
if err = syscall.Bind(fd, sockaddr); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
syscall.Close(fd)
return nil, err
}
name := fmt.Sprintf("reuseport.%d.%s.%s", os.Getpid(), network, addr)
file = os.NewFile(uintptr(fd), name)
if l, err = net.FileListener(file); err != nil {
file.Close()
return nil, err
}
if err = file.Close(); err != nil {
l.Close()
return nil, err
}
return l, err
}

View File

@@ -0,0 +1,9 @@
// +build darwin dragonfly freebsd netbsd openbsd rumprun
package reuseport
import (
"syscall"
)
const soReusePort = syscall.SO_REUSEPORT

View File

@@ -0,0 +1,24 @@
package reuseport_test
import (
"fmt"
"log"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/reuseport"
)
func ExampleListen() {
ln, err := reuseport.Listen("tcp4", "localhost:12345")
if err != nil {
log.Fatalf("error in reuseport listener: %s", err)
}
if err = fasthttp.Serve(ln, requestHandler); err != nil {
log.Fatalf("error in fasthttp Server: %s", err)
}
}
func requestHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world!")
}

View File

@@ -0,0 +1,5 @@
// +build linux
package reuseport
const soReusePort = 0x0F

View File

@@ -0,0 +1,98 @@
package reuseport
import (
"fmt"
"io/ioutil"
"net"
"testing"
"time"
)
func TestNewListener(t *testing.T) {
addr := "localhost:10081"
serversCount := 20
requestsCount := 1000
var lns []net.Listener
doneCh := make(chan struct{}, serversCount)
for i := 0; i < serversCount; i++ {
ln, err := Listen("tcp4", addr)
if err != nil {
t.Fatalf("cannot create listener %d: %s", i, err)
}
go func() {
serveEcho(t, ln)
doneCh <- struct{}{}
}()
lns = append(lns, ln)
}
for i := 0; i < requestsCount; i++ {
c, err := net.Dial("tcp4", addr)
if err != nil {
t.Fatalf("%d. unexpected error when dialing: %s", i, err)
}
req := fmt.Sprintf("request number %d", i)
if _, err = c.Write([]byte(req)); err != nil {
t.Fatalf("%d. unexpected error when writing request: %s", i, err)
}
if err = c.(*net.TCPConn).CloseWrite(); err != nil {
t.Fatalf("%d. unexpected error when closing write end of the connection: %s", i, err)
}
var resp []byte
ch := make(chan struct{})
go func() {
if resp, err = ioutil.ReadAll(c); err != nil {
t.Fatalf("%d. unexpected error when reading response: %s", i, err)
}
close(ch)
}()
select {
case <-ch:
case <-time.After(200 * time.Millisecond):
t.Fatalf("%d. timeout when waiting for response: %s", i, err)
}
if string(resp) != req {
t.Fatalf("%d. unexpected response %q. Expecting %q", i, resp, req)
}
if err = c.Close(); err != nil {
t.Fatalf("%d. unexpected error when closing connection: %s", i, err)
}
}
for _, ln := range lns {
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error when closing listener: %s", err)
}
}
for i := 0; i < serversCount; i++ {
select {
case <-doneCh:
case <-time.After(200 * time.Millisecond):
t.Fatalf("timeout when waiting for servers to be closed")
}
}
}
func serveEcho(t *testing.T, ln net.Listener) {
for {
c, err := ln.Accept()
if err != nil {
break
}
req, err := ioutil.ReadAll(c)
if err != nil {
t.Fatalf("unepxected error when reading request: %s", err)
}
if _, err = c.Write(req); err != nil {
t.Fatalf("unexpected error when writing response: %s", err)
}
if err = c.Close(); err != nil {
t.Fatalf("unexpected error when closing connection: %s", err)
}
}
}

1901
vendor/github.com/valyala/fasthttp/server.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,177 @@
package fasthttp_test
import (
"fmt"
"log"
"math/rand"
"net"
"time"
"github.com/valyala/fasthttp"
)
func ExampleListenAndServe() {
// The server will listen for incoming requests on this address.
listenAddr := "127.0.0.1:80"
// This function will be called by the server for each incoming request.
//
// RequestCtx provides a lot of functionality related to http request
// processing. See RequestCtx docs for details.
requestHandler := func(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path())
}
// Start the server with default settings.
// Create Server instance for adjusting server settings.
//
// ListenAndServe returns only on error, so usually it blocks forever.
if err := fasthttp.ListenAndServe(listenAddr, requestHandler); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func ExampleServe() {
// Create network listener for accepting incoming requests.
//
// Note that you are not limited by TCP listener - arbitrary
// net.Listener may be used by the server.
// For example, unix socket listener or TLS listener.
ln, err := net.Listen("tcp4", "127.0.0.1:8080")
if err != nil {
log.Fatalf("error in net.Listen: %s", err)
}
// This function will be called by the server for each incoming request.
//
// RequestCtx provides a lot of functionality related to http request
// processing. See RequestCtx docs for details.
requestHandler := func(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path())
}
// Start the server with default settings.
// Create Server instance for adjusting server settings.
//
// Serve returns on ln.Close() or error, so usually it blocks forever.
if err := fasthttp.Serve(ln, requestHandler); err != nil {
log.Fatalf("error in Serve: %s", err)
}
}
func ExampleServer() {
// This function will be called by the server for each incoming request.
//
// RequestCtx provides a lot of functionality related to http request
// processing. See RequestCtx docs for details.
requestHandler := func(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world! Requested path is %q", ctx.Path())
}
// Create custom server.
s := &fasthttp.Server{
Handler: requestHandler,
// Every response will contain 'Server: My super server' header.
Name: "My super server",
// Other Server settings may be set here.
}
// Start the server listening for incoming requests on the given address.
//
// ListenAndServe returns only on error, so usually it blocks forever.
if err := s.ListenAndServe("127.0.0.1:80"); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func ExampleRequestCtx_Hijack() {
// hijackHandler is called on hijacked connection.
hijackHandler := func(c net.Conn) {
fmt.Fprintf(c, "This message is sent over a hijacked connection to the client %s\n", c.RemoteAddr())
fmt.Fprintf(c, "Send me something and I'll echo it to you\n")
var buf [1]byte
for {
if _, err := c.Read(buf[:]); err != nil {
log.Printf("error when reading from hijacked connection: %s", err)
return
}
fmt.Fprintf(c, "You sent me %q. Waiting for new data\n", buf[:])
}
}
// requestHandler is called for each incoming request.
requestHandler := func(ctx *fasthttp.RequestCtx) {
path := ctx.Path()
switch {
case string(path) == "/hijack":
// Note that the connection is hijacked only after
// returning from requestHandler and sending http response.
ctx.Hijack(hijackHandler)
// The connection will be hijacked after sending this response.
fmt.Fprintf(ctx, "Hijacked the connection!")
case string(path) == "/":
fmt.Fprintf(ctx, "Root directory requested")
default:
fmt.Fprintf(ctx, "Requested path is %q", path)
}
}
if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func ExampleRequestCtx_TimeoutError() {
requestHandler := func(ctx *fasthttp.RequestCtx) {
// Emulate long-running task, which touches ctx.
doneCh := make(chan struct{})
go func() {
workDuration := time.Millisecond * time.Duration(rand.Intn(2000))
time.Sleep(workDuration)
fmt.Fprintf(ctx, "ctx has been accessed by long-running task\n")
fmt.Fprintf(ctx, "The reuqestHandler may be finished by this time.\n")
close(doneCh)
}()
select {
case <-doneCh:
fmt.Fprintf(ctx, "The task has been finished in less than a second")
case <-time.After(time.Second):
// Since the long-running task is still running and may access ctx,
// we must call TimeoutError before returning from requestHandler.
//
// Otherwise the program will suffer from data races.
ctx.TimeoutError("Timeout!")
}
}
if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}
func ExampleRequestCtx_Logger() {
requestHandler := func(ctx *fasthttp.RequestCtx) {
if string(ctx.Path()) == "/top-secret" {
ctx.Logger().Printf("Alarm! Alien intrusion detected!")
ctx.Error("Access denied!", fasthttp.StatusForbidden)
return
}
// Logger may be cached in local variables.
logger := ctx.Logger()
logger.Printf("Good request from User-Agent %q", ctx.Request.Header.UserAgent())
fmt.Fprintf(ctx, "Good request to %q", ctx.Path())
logger.Printf("Multiple log messages may be written during a single request")
}
if err := fasthttp.ListenAndServe(":80", requestHandler); err != nil {
log.Fatalf("error in ListenAndServe: %s", err)
}
}

2351
vendor/github.com/valyala/fasthttp/server_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,451 @@
package fasthttp
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
)
var defaultClientsCount = runtime.NumCPU()
func BenchmarkServerGet1ReqPerConn(b *testing.B) {
benchmarkServerGet(b, defaultClientsCount, 1)
}
func BenchmarkServerGet2ReqPerConn(b *testing.B) {
benchmarkServerGet(b, defaultClientsCount, 2)
}
func BenchmarkServerGet10ReqPerConn(b *testing.B) {
benchmarkServerGet(b, defaultClientsCount, 10)
}
func BenchmarkServerGet10KReqPerConn(b *testing.B) {
benchmarkServerGet(b, defaultClientsCount, 10000)
}
func BenchmarkNetHTTPServerGet1ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerGet(b, defaultClientsCount, 1)
}
func BenchmarkNetHTTPServerGet2ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerGet(b, defaultClientsCount, 2)
}
func BenchmarkNetHTTPServerGet10ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerGet(b, defaultClientsCount, 10)
}
func BenchmarkNetHTTPServerGet10KReqPerConn(b *testing.B) {
benchmarkNetHTTPServerGet(b, defaultClientsCount, 10000)
}
func BenchmarkServerPost1ReqPerConn(b *testing.B) {
benchmarkServerPost(b, defaultClientsCount, 1)
}
func BenchmarkServerPost2ReqPerConn(b *testing.B) {
benchmarkServerPost(b, defaultClientsCount, 2)
}
func BenchmarkServerPost10ReqPerConn(b *testing.B) {
benchmarkServerPost(b, defaultClientsCount, 10)
}
func BenchmarkServerPost10KReqPerConn(b *testing.B) {
benchmarkServerPost(b, defaultClientsCount, 10000)
}
func BenchmarkNetHTTPServerPost1ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerPost(b, defaultClientsCount, 1)
}
func BenchmarkNetHTTPServerPost2ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerPost(b, defaultClientsCount, 2)
}
func BenchmarkNetHTTPServerPost10ReqPerConn(b *testing.B) {
benchmarkNetHTTPServerPost(b, defaultClientsCount, 10)
}
func BenchmarkNetHTTPServerPost10KReqPerConn(b *testing.B) {
benchmarkNetHTTPServerPost(b, defaultClientsCount, 10000)
}
func BenchmarkServerGet1ReqPerConn10KClients(b *testing.B) {
benchmarkServerGet(b, 10000, 1)
}
func BenchmarkServerGet2ReqPerConn10KClients(b *testing.B) {
benchmarkServerGet(b, 10000, 2)
}
func BenchmarkServerGet10ReqPerConn10KClients(b *testing.B) {
benchmarkServerGet(b, 10000, 10)
}
func BenchmarkServerGet100ReqPerConn10KClients(b *testing.B) {
benchmarkServerGet(b, 10000, 100)
}
func BenchmarkNetHTTPServerGet1ReqPerConn10KClients(b *testing.B) {
benchmarkNetHTTPServerGet(b, 10000, 1)
}
func BenchmarkNetHTTPServerGet2ReqPerConn10KClients(b *testing.B) {
benchmarkNetHTTPServerGet(b, 10000, 2)
}
func BenchmarkNetHTTPServerGet10ReqPerConn10KClients(b *testing.B) {
benchmarkNetHTTPServerGet(b, 10000, 10)
}
func BenchmarkNetHTTPServerGet100ReqPerConn10KClients(b *testing.B) {
benchmarkNetHTTPServerGet(b, 10000, 100)
}
func BenchmarkServerHijack(b *testing.B) {
clientsCount := 1000
requestsPerConn := 10000
ch := make(chan struct{}, b.N)
responseBody := []byte("123")
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.Hijack(func(c net.Conn) {
// emulate server loop :)
err := ServeConn(c, func(ctx *RequestCtx) {
ctx.Success("foobar", responseBody)
registerServedRequest(b, ch)
})
if err != nil {
b.Fatalf("error when serving connection")
}
})
ctx.Success("foobar", responseBody)
registerServedRequest(b, ch)
},
Concurrency: 16 * clientsCount,
}
req := "GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n"
benchmarkServer(b, s, clientsCount, requestsPerConn, req)
verifyRequestsServed(b, ch)
}
func BenchmarkServerMaxConnsPerIP(b *testing.B) {
clientsCount := 1000
requestsPerConn := 10
ch := make(chan struct{}, b.N)
responseBody := []byte("123")
s := &Server{
Handler: func(ctx *RequestCtx) {
ctx.Success("foobar", responseBody)
registerServedRequest(b, ch)
},
MaxConnsPerIP: clientsCount * 2,
Concurrency: 16 * clientsCount,
}
req := "GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n"
benchmarkServer(b, s, clientsCount, requestsPerConn, req)
verifyRequestsServed(b, ch)
}
func BenchmarkServerTimeoutError(b *testing.B) {
clientsCount := 10
requestsPerConn := 1
ch := make(chan struct{}, b.N)
n := uint32(0)
responseBody := []byte("123")
s := &Server{
Handler: func(ctx *RequestCtx) {
if atomic.AddUint32(&n, 1)&7 == 0 {
ctx.TimeoutError("xxx")
go func() {
ctx.Success("foobar", responseBody)
}()
} else {
ctx.Success("foobar", responseBody)
}
registerServedRequest(b, ch)
},
Concurrency: 16 * clientsCount,
}
req := "GET /foo HTTP/1.1\r\nHost: google.com\r\n\r\n"
benchmarkServer(b, s, clientsCount, requestsPerConn, req)
verifyRequestsServed(b, ch)
}
type fakeServerConn struct {
net.TCPConn
ln *fakeListener
requestsCount int
pos int
closed uint32
}
func (c *fakeServerConn) Read(b []byte) (int, error) {
nn := 0
reqLen := len(c.ln.request)
for len(b) > 0 {
if c.requestsCount == 0 {
if nn == 0 {
return 0, io.EOF
}
return nn, nil
}
pos := c.pos % reqLen
n := copy(b, c.ln.request[pos:])
b = b[n:]
nn += n
c.pos += n
if n+pos == reqLen {
c.requestsCount--
}
}
return nn, nil
}
func (c *fakeServerConn) Write(b []byte) (int, error) {
return len(b), nil
}
var fakeAddr = net.TCPAddr{
IP: []byte{1, 2, 3, 4},
Port: 12345,
}
func (c *fakeServerConn) RemoteAddr() net.Addr {
return &fakeAddr
}
func (c *fakeServerConn) Close() error {
if atomic.AddUint32(&c.closed, 1) == 1 {
c.ln.ch <- c
}
return nil
}
func (c *fakeServerConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *fakeServerConn) SetWriteDeadline(t time.Time) error {
return nil
}
type fakeListener struct {
lock sync.Mutex
requestsCount int
requestsPerConn int
request []byte
ch chan *fakeServerConn
done chan struct{}
closed bool
}
func (ln *fakeListener) Accept() (net.Conn, error) {
ln.lock.Lock()
if ln.requestsCount == 0 {
ln.lock.Unlock()
for len(ln.ch) < cap(ln.ch) {
time.Sleep(10 * time.Millisecond)
}
ln.lock.Lock()
if !ln.closed {
close(ln.done)
ln.closed = true
}
ln.lock.Unlock()
return nil, io.EOF
}
requestsCount := ln.requestsPerConn
if requestsCount > ln.requestsCount {
requestsCount = ln.requestsCount
}
ln.requestsCount -= requestsCount
ln.lock.Unlock()
c := <-ln.ch
c.requestsCount = requestsCount
c.closed = 0
c.pos = 0
return c, nil
}
func (ln *fakeListener) Close() error {
return nil
}
func (ln *fakeListener) Addr() net.Addr {
return &fakeAddr
}
func newFakeListener(requestsCount, clientsCount, requestsPerConn int, request string) *fakeListener {
ln := &fakeListener{
requestsCount: requestsCount,
requestsPerConn: requestsPerConn,
request: []byte(request),
ch: make(chan *fakeServerConn, clientsCount),
done: make(chan struct{}),
}
for i := 0; i < clientsCount; i++ {
ln.ch <- &fakeServerConn{
ln: ln,
}
}
return ln
}
var (
fakeResponse = []byte("Hello, world!")
getRequest = "GET /foobar?baz HTTP/1.1\r\nHost: google.com\r\nUser-Agent: aaa/bbb/ccc/ddd/eee Firefox Chrome MSIE Opera\r\n" +
"Referer: http://xxx.com/aaa?bbb=ccc\r\nCookie: foo=bar; baz=baraz; aa=aakslsdweriwereowriewroire\r\n\r\n"
postRequest = fmt.Sprintf("POST /foobar?baz HTTP/1.1\r\nHost: google.com\r\nContent-Type: foo/bar\r\nContent-Length: %d\r\n"+
"User-Agent: Opera Chrome MSIE Firefox and other/1.2.34\r\nReferer: http://google.com/aaaa/bbb/ccc\r\n"+
"Cookie: foo=bar; baz=baraz; aa=aakslsdweriwereowriewroire\r\n\r\n%s",
len(fakeResponse), fakeResponse)
)
func benchmarkServerGet(b *testing.B, clientsCount, requestsPerConn int) {
ch := make(chan struct{}, b.N)
s := &Server{
Handler: func(ctx *RequestCtx) {
if !ctx.IsGet() {
b.Fatalf("Unexpected request method: %s", ctx.Method())
}
ctx.Success("text/plain", fakeResponse)
if requestsPerConn == 1 {
ctx.SetConnectionClose()
}
registerServedRequest(b, ch)
},
Concurrency: 16 * clientsCount,
}
benchmarkServer(b, s, clientsCount, requestsPerConn, getRequest)
verifyRequestsServed(b, ch)
}
func benchmarkNetHTTPServerGet(b *testing.B, clientsCount, requestsPerConn int) {
ch := make(chan struct{}, b.N)
s := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
b.Fatalf("Unexpected request method: %s", req.Method)
}
h := w.Header()
h.Set("Content-Type", "text/plain")
if requestsPerConn == 1 {
h.Set("Connection", "close")
}
w.Write(fakeResponse)
registerServedRequest(b, ch)
}),
}
benchmarkServer(b, s, clientsCount, requestsPerConn, getRequest)
verifyRequestsServed(b, ch)
}
func benchmarkServerPost(b *testing.B, clientsCount, requestsPerConn int) {
ch := make(chan struct{}, b.N)
s := &Server{
Handler: func(ctx *RequestCtx) {
if !ctx.IsPost() {
b.Fatalf("Unexpected request method: %s", ctx.Method())
}
body := ctx.Request.Body()
if !bytes.Equal(body, fakeResponse) {
b.Fatalf("Unexpected body %q. Expected %q", body, fakeResponse)
}
ctx.Success("text/plain", body)
if requestsPerConn == 1 {
ctx.SetConnectionClose()
}
registerServedRequest(b, ch)
},
Concurrency: 16 * clientsCount,
}
benchmarkServer(b, s, clientsCount, requestsPerConn, postRequest)
verifyRequestsServed(b, ch)
}
func benchmarkNetHTTPServerPost(b *testing.B, clientsCount, requestsPerConn int) {
ch := make(chan struct{}, b.N)
s := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
b.Fatalf("Unexpected request method: %s", req.Method)
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
b.Fatalf("Unexpected error: %s", err)
}
req.Body.Close()
if !bytes.Equal(body, fakeResponse) {
b.Fatalf("Unexpected body %q. Expected %q", body, fakeResponse)
}
h := w.Header()
h.Set("Content-Type", "text/plain")
if requestsPerConn == 1 {
h.Set("Connection", "close")
}
w.Write(body)
registerServedRequest(b, ch)
}),
}
benchmarkServer(b, s, clientsCount, requestsPerConn, postRequest)
verifyRequestsServed(b, ch)
}
func registerServedRequest(b *testing.B, ch chan<- struct{}) {
select {
case ch <- struct{}{}:
default:
b.Fatalf("More than %d requests served", cap(ch))
}
}
func verifyRequestsServed(b *testing.B, ch <-chan struct{}) {
requestsServed := 0
for len(ch) > 0 {
<-ch
requestsServed++
}
requestsSent := b.N
for requestsServed < requestsSent {
select {
case <-ch:
requestsServed++
case <-time.After(100 * time.Millisecond):
b.Fatalf("Unexpected number of requests served %d. Expected %d", requestsServed, requestsSent)
}
}
}
type realServer interface {
Serve(ln net.Listener) error
}
func benchmarkServer(b *testing.B, s realServer, clientsCount, requestsPerConn int, request string) {
ln := newFakeListener(b.N, clientsCount, requestsPerConn, request)
ch := make(chan struct{})
go func() {
s.Serve(ln)
ch <- struct{}{}
}()
<-ln.done
select {
case <-ch:
case <-time.After(10 * time.Second):
b.Fatalf("Server.Serve() didn't stop")
}
}

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG
3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U
wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0
FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf
IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg
GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF
sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2
sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D
uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb
K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3
YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+
DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk
B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV
Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x
IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY
wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj
wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D
FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m
tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX
fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU
ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk
K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT
6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt
9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN
Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV
c257YgaWmjK9uB0Y2r2VxS0G
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV
MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D
K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te
+z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij
L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1
xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY
6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG
SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98
L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2
45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li
K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6
X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI
whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd
-----END CERTIFICATE-----

147
vendor/github.com/valyala/fasthttp/status.go generated vendored Normal file
View File

@@ -0,0 +1,147 @@
package fasthttp
import (
"fmt"
"sync/atomic"
)
// HTTP status codes were stolen from net/http.
const (
StatusContinue = 100
StatusSwitchingProtocols = 101
StatusOK = 200
StatusCreated = 201
StatusAccepted = 202
StatusNonAuthoritativeInfo = 203
StatusNoContent = 204
StatusResetContent = 205
StatusPartialContent = 206
StatusMultipleChoices = 300
StatusMovedPermanently = 301
StatusFound = 302
StatusSeeOther = 303
StatusNotModified = 304
StatusUseProxy = 305
StatusTemporaryRedirect = 307
StatusBadRequest = 400
StatusUnauthorized = 401
StatusPaymentRequired = 402
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusNotAcceptable = 406
StatusProxyAuthRequired = 407
StatusRequestTimeout = 408
StatusConflict = 409
StatusGone = 410
StatusLengthRequired = 411
StatusPreconditionFailed = 412
StatusRequestEntityTooLarge = 413
StatusRequestURITooLong = 414
StatusUnsupportedMediaType = 415
StatusRequestedRangeNotSatisfiable = 416
StatusExpectationFailed = 417
StatusTeapot = 418
StatusPreconditionRequired = 428
StatusTooManyRequests = 429
StatusRequestHeaderFieldsTooLarge = 431
StatusInternalServerError = 500
StatusNotImplemented = 501
StatusBadGateway = 502
StatusServiceUnavailable = 503
StatusGatewayTimeout = 504
StatusHTTPVersionNotSupported = 505
StatusNetworkAuthenticationRequired = 511
)
var (
statusLines atomic.Value
statusMessages = map[int]string{
StatusContinue: "Continue",
StatusSwitchingProtocols: "SwitchingProtocols",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Info",
StatusNoContent: "No Content",
StatusResetContent: "Reset Content",
StatusPartialContent: "Partial Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusUseProxy: "Use Proxy",
StatusTemporaryRedirect: "Temporary Redirect",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusProxyAuthRequired: "Proxy Auth Required",
StatusRequestTimeout: "Request Timeout",
StatusConflict: "Conflict",
StatusGone: "Gone",
StatusLengthRequired: "Length Required",
StatusPreconditionFailed: "Precondition Failed",
StatusRequestEntityTooLarge: "Request Entity Too Large",
StatusRequestURITooLong: "Request URI Too Long",
StatusUnsupportedMediaType: "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
StatusExpectationFailed: "Expectation Failed",
StatusTeapot: "Teapot",
StatusPreconditionRequired: "Precondition Required",
StatusTooManyRequests: "Too Many Requests",
StatusRequestHeaderFieldsTooLarge: "Request HeaderFields Too Large",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
StatusServiceUnavailable: "Service Unavailable",
StatusGatewayTimeout: "Gateway Timeout",
StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
StatusNetworkAuthenticationRequired: "Network Authentication Required",
}
)
// StatusMessage returns HTTP status message for the given status code.
func StatusMessage(statusCode int) string {
s := statusMessages[statusCode]
if s == "" {
s = "Unknown Status Code"
}
return s
}
func init() {
statusLines.Store(make(map[int][]byte))
}
func statusLine(statusCode int) []byte {
m := statusLines.Load().(map[int][]byte)
h := m[statusCode]
if h != nil {
return h
}
statusText := StatusMessage(statusCode)
h = []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", statusCode, statusText))
newM := make(map[int][]byte, len(m)+1)
for k, v := range m {
newM[k] = v
}
newM[statusCode] = h
statusLines.Store(newM)
return h
}

61
vendor/github.com/valyala/fasthttp/stream.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
package fasthttp
import (
"bufio"
"io"
"runtime/debug"
"sync"
"github.com/valyala/fasthttp/fasthttputil"
)
// StreamWriter must write data to w.
//
// Usually StreamWriter writes data to w in a loop (aka 'data streaming').
//
// StreamWriter must return immediately if w returns error.
//
// Since the written data is buffered, do not forget calling w.Flush
// when the data must be propagated to reader.
type StreamWriter func(w *bufio.Writer)
// NewStreamReader returns a reader, which replays all the data generated by sw.
//
// The returned reader may be passed to Response.SetBodyStream.
//
// Close must be called on the returned reader after all the required data
// has been read. Otherwise goroutine leak may occur.
//
// See also Response.SetBodyStreamWriter.
func NewStreamReader(sw StreamWriter) io.ReadCloser {
pc := fasthttputil.NewPipeConns()
pw := pc.Conn1()
pr := pc.Conn2()
var bw *bufio.Writer
v := streamWriterBufPool.Get()
if v == nil {
bw = bufio.NewWriter(pw)
} else {
bw = v.(*bufio.Writer)
bw.Reset(pw)
}
go func() {
defer func() {
if r := recover(); r != nil {
defaultLogger.Printf("panic in StreamWriter: %s\nStack trace:\n%s", r, debug.Stack())
}
}()
sw(bw)
bw.Flush()
pw.Close()
streamWriterBufPool.Put(bw)
}()
return pr
}
var streamWriterBufPool sync.Pool

102
vendor/github.com/valyala/fasthttp/stream_test.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package fasthttp
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"testing"
"time"
)
func TestNewStreamReader(t *testing.T) {
ch := make(chan struct{})
r := NewStreamReader(func(w *bufio.Writer) {
fmt.Fprintf(w, "Hello, world\n")
fmt.Fprintf(w, "Line #2\n")
close(ch)
})
data, err := ioutil.ReadAll(r)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
expectedData := "Hello, world\nLine #2\n"
if string(data) != expectedData {
t.Fatalf("unexpected data %q. Expecting %q", data, expectedData)
}
if err = r.Close(); err != nil {
t.Fatalf("unexpected error")
}
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
func TestStreamReaderClose(t *testing.T) {
firstLine := "the first line must pass"
ch := make(chan error, 1)
r := NewStreamReader(func(w *bufio.Writer) {
fmt.Fprintf(w, "%s", firstLine)
if err := w.Flush(); err != nil {
ch <- fmt.Errorf("unexpected error on first flush: %s", err)
return
}
data := createFixedBody(4000)
for i := 0; i < 100; i++ {
w.Write(data)
}
if err := w.Flush(); err == nil {
ch <- fmt.Errorf("expecting error on the second flush")
}
ch <- nil
})
buf := make([]byte, len(firstLine))
n, err := io.ReadFull(r, buf)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if n != len(buf) {
t.Fatalf("unexpected number of bytes read: %d. Expecting %d", n, len(buf))
}
if string(buf) != firstLine {
t.Fatalf("unexpected result: %q. Expecting %q", buf, firstLine)
}
if err := r.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
select {
case err := <-ch:
if err != nil {
t.Fatalf("error returned from stream reader: %s", err)
}
case <-time.After(time.Second):
t.Fatalf("timeout when waiting for stream reader")
}
// read trailing data
go func() {
if _, err := ioutil.ReadAll(r); err != nil {
ch <- fmt.Errorf("unexpected error when reading trailing data: %s", err)
return
}
ch <- nil
}()
select {
case err := <-ch:
if err != nil {
t.Fatalf("error returned when reading tail data: %s", err)
}
case <-time.After(time.Second):
t.Fatalf("timeout when reading tail data")
}
}

View File

@@ -0,0 +1,70 @@
package fasthttp
import (
"bufio"
"io"
"testing"
"time"
)
func BenchmarkStreamReader1(b *testing.B) {
benchmarkStreamReader(b, 1)
}
func BenchmarkStreamReader10(b *testing.B) {
benchmarkStreamReader(b, 10)
}
func BenchmarkStreamReader100(b *testing.B) {
benchmarkStreamReader(b, 100)
}
func BenchmarkStreamReader1K(b *testing.B) {
benchmarkStreamReader(b, 1000)
}
func BenchmarkStreamReader10K(b *testing.B) {
benchmarkStreamReader(b, 10000)
}
func benchmarkStreamReader(b *testing.B, size int) {
src := createFixedBody(size)
b.SetBytes(int64(size))
b.RunParallel(func(pb *testing.PB) {
dst := make([]byte, size)
ch := make(chan error, 1)
sr := NewStreamReader(func(w *bufio.Writer) {
for pb.Next() {
if _, err := w.Write(src); err != nil {
ch <- err
return
}
if err := w.Flush(); err != nil {
ch <- err
return
}
}
ch <- nil
})
for {
if _, err := sr.Read(dst); err != nil {
if err == io.EOF {
break
}
b.Fatalf("unexpected error when reading from stream reader: %s", err)
}
}
if err := sr.Close(); err != nil {
b.Fatalf("unexpected error when closing stream reader: %s", err)
}
select {
case err := <-ch:
if err != nil {
b.Fatalf("unexpected error from stream reader: %s", err)
}
case <-time.After(time.Second):
b.Fatalf("timeout")
}
})
}

71
vendor/github.com/valyala/fasthttp/strings.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package fasthttp
var (
defaultServerName = []byte("fasthttp")
defaultUserAgent = []byte("fasthttp")
defaultContentType = []byte("text/plain; charset=utf-8")
)
var (
strSlash = []byte("/")
strSlashSlash = []byte("//")
strSlashDotDot = []byte("/..")
strSlashDotSlash = []byte("/./")
strSlashDotDotSlash = []byte("/../")
strCRLF = []byte("\r\n")
strHTTP = []byte("http")
strHTTPS = []byte("https")
strHTTP11 = []byte("HTTP/1.1")
strColonSlashSlash = []byte("://")
strColonSpace = []byte(": ")
strGMT = []byte("GMT")
strResponseContinue = []byte("HTTP/1.1 100 Continue\r\n\r\n")
strGet = []byte("GET")
strHead = []byte("HEAD")
strPost = []byte("POST")
strPut = []byte("PUT")
strDelete = []byte("DELETE")
strExpect = []byte("Expect")
strConnection = []byte("Connection")
strContentLength = []byte("Content-Length")
strContentType = []byte("Content-Type")
strDate = []byte("Date")
strHost = []byte("Host")
strReferer = []byte("Referer")
strServer = []byte("Server")
strTransferEncoding = []byte("Transfer-Encoding")
strContentEncoding = []byte("Content-Encoding")
strAcceptEncoding = []byte("Accept-Encoding")
strUserAgent = []byte("User-Agent")
strCookie = []byte("Cookie")
strSetCookie = []byte("Set-Cookie")
strLocation = []byte("Location")
strIfModifiedSince = []byte("If-Modified-Since")
strLastModified = []byte("Last-Modified")
strAcceptRanges = []byte("Accept-Ranges")
strRange = []byte("Range")
strContentRange = []byte("Content-Range")
strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
strCookiePath = []byte("path")
strCookieHTTPOnly = []byte("HttpOnly")
strCookieSecure = []byte("secure")
strClose = []byte("close")
strGzip = []byte("gzip")
strDeflate = []byte("deflate")
strKeepAlive = []byte("keep-alive")
strKeepAliveCamelCase = []byte("Keep-Alive")
strUpgrade = []byte("Upgrade")
strChunked = []byte("chunked")
strIdentity = []byte("identity")
str100Continue = []byte("100-continue")
strPostArgsContentType = []byte("application/x-www-form-urlencoded")
strMultipartFormData = []byte("multipart/form-data")
strBoundary = []byte("boundary")
strBytes = []byte("bytes")
)

367
vendor/github.com/valyala/fasthttp/tcpdialer.go generated vendored Normal file
View File

@@ -0,0 +1,367 @@
package fasthttp
import (
"errors"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
)
// Dial dials the given TCP addr using tcp4.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
// * It returns ErrDialTimeout if connection cannot be established during
// DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func Dial(addr string) (net.Conn, error) {
return getDialer(DefaultDialTimeout, false)(addr)
}
// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return getDialer(timeout, false)(addr)
}
// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
// * It returns ErrDialTimeout if connection cannot be established during
// DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
// timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func DialDualStack(addr string) (net.Conn, error) {
return getDialer(DefaultDialTimeout, true)(addr)
}
// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
// using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return getDialer(timeout, true)(addr)
}
func getDialer(timeout time.Duration, dualStack bool) DialFunc {
if timeout <= 0 {
timeout = DefaultDialTimeout
}
timeoutRounded := int(timeout.Seconds()*10 + 9)
m := dialMap
if dualStack {
m = dialDualStackMap
}
dialMapLock.Lock()
d := m[timeoutRounded]
if d == nil {
dialer := dialerStd
if dualStack {
dialer = dialerDualStack
}
d = dialer.NewDial(timeout)
m[timeoutRounded] = d
}
dialMapLock.Unlock()
return d
}
var (
dialerStd = &tcpDialer{}
dialerDualStack = &tcpDialer{DualStack: true}
dialMap = make(map[int]DialFunc)
dialDualStackMap = make(map[int]DialFunc)
dialMapLock sync.Mutex
)
type tcpDialer struct {
DualStack bool
tcpAddrsLock sync.Mutex
tcpAddrsMap map[string]*tcpAddrEntry
concurrencyCh chan struct{}
once sync.Once
}
const maxDialConcurrency = 1000
func (d *tcpDialer) NewDial(timeout time.Duration) DialFunc {
d.once.Do(func() {
d.concurrencyCh = make(chan struct{}, maxDialConcurrency)
d.tcpAddrsMap = make(map[string]*tcpAddrEntry)
go d.tcpAddrsClean()
})
return func(addr string) (net.Conn, error) {
addrs, idx, err := d.getTCPAddrs(addr)
if err != nil {
return nil, err
}
network := "tcp4"
if d.DualStack {
network = "tcp"
}
var conn net.Conn
n := uint32(len(addrs))
deadline := time.Now().Add(timeout)
for n > 0 {
conn, err = tryDial(network, &addrs[idx%n], deadline, d.concurrencyCh)
if err == nil {
return conn, nil
}
if err == ErrDialTimeout {
return nil, err
}
idx++
n--
}
return nil, err
}
}
func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyCh chan struct{}) (net.Conn, error) {
timeout := -time.Since(deadline)
if timeout <= 0 {
return nil, ErrDialTimeout
}
select {
case concurrencyCh <- struct{}{}:
default:
tc := acquireTimer(timeout)
isTimeout := false
select {
case concurrencyCh <- struct{}{}:
case <-tc.C:
isTimeout = true
}
releaseTimer(tc)
if isTimeout {
return nil, ErrDialTimeout
}
}
timeout = -time.Since(deadline)
if timeout <= 0 {
<-concurrencyCh
return nil, ErrDialTimeout
}
chv := dialResultChanPool.Get()
if chv == nil {
chv = make(chan dialResult, 1)
}
ch := chv.(chan dialResult)
go func() {
var dr dialResult
dr.conn, dr.err = net.DialTCP(network, nil, addr)
ch <- dr
<-concurrencyCh
}()
var (
conn net.Conn
err error
)
tc := acquireTimer(timeout)
select {
case dr := <-ch:
conn = dr.conn
err = dr.err
dialResultChanPool.Put(ch)
case <-tc.C:
err = ErrDialTimeout
}
releaseTimer(tc)
return conn, err
}
var dialResultChanPool sync.Pool
type dialResult struct {
conn net.Conn
err error
}
// ErrDialTimeout is returned when TCP dialing is timed out.
var ErrDialTimeout = errors.New("dialing to the given TCP address timed out")
// DefaultDialTimeout is timeout used by Dial and DialDualStack
// for establishing TCP connections.
const DefaultDialTimeout = 3 * time.Second
type tcpAddrEntry struct {
addrs []net.TCPAddr
addrsIdx uint32
resolveTime time.Time
pending bool
}
// DefaultDNSCacheDuration is the duration for caching resolved TCP addresses
// by Dial* functions.
const DefaultDNSCacheDuration = time.Minute
func (d *tcpDialer) tcpAddrsClean() {
expireDuration := 2 * DefaultDNSCacheDuration
for {
time.Sleep(time.Second)
t := time.Now()
d.tcpAddrsLock.Lock()
for k, e := range d.tcpAddrsMap {
if t.Sub(e.resolveTime) > expireDuration {
delete(d.tcpAddrsMap, k)
}
}
d.tcpAddrsLock.Unlock()
}
}
func (d *tcpDialer) getTCPAddrs(addr string) ([]net.TCPAddr, uint32, error) {
d.tcpAddrsLock.Lock()
e := d.tcpAddrsMap[addr]
if e != nil && !e.pending && time.Since(e.resolveTime) > DefaultDNSCacheDuration {
e.pending = true
e = nil
}
d.tcpAddrsLock.Unlock()
if e == nil {
addrs, err := resolveTCPAddrs(addr, d.DualStack)
if err != nil {
d.tcpAddrsLock.Lock()
e = d.tcpAddrsMap[addr]
if e != nil && e.pending {
e.pending = false
}
d.tcpAddrsLock.Unlock()
return nil, 0, err
}
e = &tcpAddrEntry{
addrs: addrs,
resolveTime: time.Now(),
}
d.tcpAddrsLock.Lock()
d.tcpAddrsMap[addr] = e
d.tcpAddrsLock.Unlock()
}
idx := uint32(0)
if len(e.addrs) > 0 {
idx = atomic.AddUint32(&e.addrsIdx, 1)
}
return e.addrs, idx, nil
}
func resolveTCPAddrs(addr string, dualStack bool) ([]net.TCPAddr, error) {
host, portS, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(portS)
if err != nil {
return nil, err
}
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
n := len(ips)
addrs := make([]net.TCPAddr, 0, n)
for i := 0; i < n; i++ {
ip := ips[i]
if !dualStack && ip.To4() == nil {
continue
}
addrs = append(addrs, net.TCPAddr{
IP: ip,
Port: port,
})
}
return addrs, nil
}

44
vendor/github.com/valyala/fasthttp/timer.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
package fasthttp
import (
"sync"
"time"
)
func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
if t == nil {
return time.NewTimer(timeout)
}
if t.Reset(timeout) {
panic("BUG: active timer trapped into initTimer()")
}
return t
}
func stopTimer(t *time.Timer) {
if !t.Stop() {
// Collect possibly added time from the channel
// if timer has been stopped and nobody collected its' value.
select {
case <-t.C:
default:
}
}
}
func acquireTimer(timeout time.Duration) *time.Timer {
v := timerPool.Get()
if v == nil {
return time.NewTimer(timeout)
}
t := v.(*time.Timer)
initTimer(t, timeout)
return t
}
func releaseTimer(t *time.Timer) {
stopTimer(t)
timerPool.Put(t)
}
var timerPool sync.Pool

496
vendor/github.com/valyala/fasthttp/uri.go generated vendored Normal file
View File

@@ -0,0 +1,496 @@
package fasthttp
import (
"bytes"
"io"
"sync"
)
// AcquireURI returns an empty URI instance from the pool.
//
// Release the URI with ReleaseURI after the URI is no longer needed.
// This allows reducing GC load.
func AcquireURI() *URI {
return uriPool.Get().(*URI)
}
// ReleaseURI releases the URI acquired via AcquireURI.
//
// The released URI mustn't be used after releasing it, otherwise data races
// may occur.
func ReleaseURI(u *URI) {
u.Reset()
uriPool.Put(u)
}
var uriPool = &sync.Pool{
New: func() interface{} {
return &URI{}
},
}
// URI represents URI :) .
//
// It is forbidden copying URI instances. Create new instance and use CopyTo
// instead.
//
// URI instance MUST NOT be used from concurrently running goroutines.
type URI struct {
noCopy noCopy
pathOriginal []byte
scheme []byte
path []byte
queryString []byte
hash []byte
host []byte
queryArgs Args
parsedQueryArgs bool
fullURI []byte
requestURI []byte
h *RequestHeader
}
// CopyTo copies uri contents to dst.
func (u *URI) CopyTo(dst *URI) {
dst.Reset()
dst.pathOriginal = append(dst.pathOriginal[:0], u.pathOriginal...)
dst.scheme = append(dst.scheme[:0], u.scheme...)
dst.path = append(dst.path[:0], u.path...)
dst.queryString = append(dst.queryString[:0], u.queryString...)
dst.hash = append(dst.hash[:0], u.hash...)
dst.host = append(dst.host[:0], u.host...)
u.queryArgs.CopyTo(&dst.queryArgs)
dst.parsedQueryArgs = u.parsedQueryArgs
// fullURI and requestURI shouldn't be copied, since they are created
// from scratch on each FullURI() and RequestURI() call.
dst.h = u.h
}
// Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .
//
// The returned value is valid until the next URI method call.
func (u *URI) Hash() []byte {
return u.hash
}
// SetHash sets URI hash.
func (u *URI) SetHash(hash string) {
u.hash = append(u.hash[:0], hash...)
}
// SetHashBytes sets URI hash.
func (u *URI) SetHashBytes(hash []byte) {
u.hash = append(u.hash[:0], hash...)
}
// QueryString returns URI query string,
// i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .
//
// The returned value is valid until the next URI method call.
func (u *URI) QueryString() []byte {
return u.queryString
}
// SetQueryString sets URI query string.
func (u *URI) SetQueryString(queryString string) {
u.queryString = append(u.queryString[:0], queryString...)
u.parsedQueryArgs = false
}
// SetQueryStringBytes sets URI query string.
func (u *URI) SetQueryStringBytes(queryString []byte) {
u.queryString = append(u.queryString[:0], queryString...)
u.parsedQueryArgs = false
}
// Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .
//
// The returned path is always urldecoded and normalized,
// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.
//
// The returned value is valid until the next URI method call.
func (u *URI) Path() []byte {
path := u.path
if len(path) == 0 {
path = strSlash
}
return path
}
// SetPath sets URI path.
func (u *URI) SetPath(path string) {
u.pathOriginal = append(u.pathOriginal[:0], path...)
u.path = normalizePath(u.path, u.pathOriginal)
}
// SetPathBytes sets URI path.
func (u *URI) SetPathBytes(path []byte) {
u.pathOriginal = append(u.pathOriginal[:0], path...)
u.path = normalizePath(u.path, u.pathOriginal)
}
// PathOriginal returns the original path from requestURI passed to URI.Parse().
//
// The returned value is valid until the next URI method call.
func (u *URI) PathOriginal() []byte {
return u.pathOriginal
}
// Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .
//
// Returned scheme is always lowercased.
//
// The returned value is valid until the next URI method call.
func (u *URI) Scheme() []byte {
scheme := u.scheme
if len(scheme) == 0 {
scheme = strHTTP
}
return scheme
}
// SetScheme sets URI scheme, i.e. http, https, ftp, etc.
func (u *URI) SetScheme(scheme string) {
u.scheme = append(u.scheme[:0], scheme...)
lowercaseBytes(u.scheme)
}
// SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.
func (u *URI) SetSchemeBytes(scheme []byte) {
u.scheme = append(u.scheme[:0], scheme...)
lowercaseBytes(u.scheme)
}
// Reset clears uri.
func (u *URI) Reset() {
u.pathOriginal = u.pathOriginal[:0]
u.scheme = u.scheme[:0]
u.path = u.path[:0]
u.queryString = u.queryString[:0]
u.hash = u.hash[:0]
u.host = u.host[:0]
u.queryArgs.Reset()
u.parsedQueryArgs = false
// There is no need in u.fullURI = u.fullURI[:0], since full uri
// is calucalted on each call to FullURI().
// There is no need in u.requestURI = u.requestURI[:0], since requestURI
// is calculated on each call to RequestURI().
u.h = nil
}
// Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .
//
// Host is always lowercased.
func (u *URI) Host() []byte {
if len(u.host) == 0 && u.h != nil {
u.host = append(u.host[:0], u.h.Host()...)
lowercaseBytes(u.host)
u.h = nil
}
return u.host
}
// SetHost sets host for the uri.
func (u *URI) SetHost(host string) {
u.host = append(u.host[:0], host...)
lowercaseBytes(u.host)
}
// SetHostBytes sets host for the uri.
func (u *URI) SetHostBytes(host []byte) {
u.host = append(u.host[:0], host...)
lowercaseBytes(u.host)
}
// Parse initializes URI from the given host and uri.
func (u *URI) Parse(host, uri []byte) {
u.parse(host, uri, nil)
}
func (u *URI) parseQuick(uri []byte, h *RequestHeader) {
u.parse(nil, uri, h)
}
func (u *URI) parse(host, uri []byte, h *RequestHeader) {
u.Reset()
u.h = h
scheme, host, uri := splitHostURI(host, uri)
u.scheme = append(u.scheme, scheme...)
lowercaseBytes(u.scheme)
u.host = append(u.host, host...)
lowercaseBytes(u.host)
b := uri
queryIndex := bytes.IndexByte(b, '?')
fragmentIndex := bytes.IndexByte(b, '#')
// Ignore query in fragment part
if fragmentIndex >= 0 && queryIndex > fragmentIndex {
queryIndex = -1
}
if queryIndex < 0 && fragmentIndex < 0 {
u.pathOriginal = append(u.pathOriginal, b...)
u.path = normalizePath(u.path, u.pathOriginal)
return
}
if queryIndex >= 0 {
// Path is everything up to the start of the query
u.pathOriginal = append(u.pathOriginal, b[:queryIndex]...)
u.path = normalizePath(u.path, u.pathOriginal)
if fragmentIndex < 0 {
u.queryString = append(u.queryString, b[queryIndex+1:]...)
} else {
u.queryString = append(u.queryString, b[queryIndex+1:fragmentIndex]...)
u.hash = append(u.hash, b[fragmentIndex+1:]...)
}
return
}
// fragmentIndex >= 0 && queryIndex < 0
// Path is up to the start of fragment
u.pathOriginal = append(u.pathOriginal, b[:fragmentIndex]...)
u.path = normalizePath(u.path, u.pathOriginal)
u.hash = append(u.hash, b[fragmentIndex+1:]...)
}
func normalizePath(dst, src []byte) []byte {
dst = dst[:0]
dst = addLeadingSlash(dst, src)
dst = decodeArgAppend(dst, src, false)
// remove duplicate slashes
b := dst
bSize := len(b)
for {
n := bytes.Index(b, strSlashSlash)
if n < 0 {
break
}
b = b[n:]
copy(b, b[1:])
b = b[:len(b)-1]
bSize--
}
dst = dst[:bSize]
// remove /./ parts
b = dst
for {
n := bytes.Index(b, strSlashDotSlash)
if n < 0 {
break
}
nn := n + len(strSlashDotSlash) - 1
copy(b[n:], b[nn:])
b = b[:len(b)-nn+n]
}
// remove /foo/../ parts
for {
n := bytes.Index(b, strSlashDotDotSlash)
if n < 0 {
break
}
nn := bytes.LastIndexByte(b[:n], '/')
if nn < 0 {
nn = 0
}
n += len(strSlashDotDotSlash) - 1
copy(b[nn:], b[n:])
b = b[:len(b)-n+nn]
}
// remove trailing /foo/..
n := bytes.LastIndex(b, strSlashDotDot)
if n >= 0 && n+len(strSlashDotDot) == len(b) {
nn := bytes.LastIndexByte(b[:n], '/')
if nn < 0 {
return strSlash
}
b = b[:nn+1]
}
return b
}
// RequestURI returns RequestURI - i.e. URI without Scheme and Host.
func (u *URI) RequestURI() []byte {
dst := appendQuotedPath(u.requestURI[:0], u.Path())
if u.queryArgs.Len() > 0 {
dst = append(dst, '?')
dst = u.queryArgs.AppendBytes(dst)
} else if len(u.queryString) > 0 {
dst = append(dst, '?')
dst = append(dst, u.queryString...)
}
if len(u.hash) > 0 {
dst = append(dst, '#')
dst = append(dst, u.hash...)
}
u.requestURI = dst
return u.requestURI
}
// LastPathSegment returns the last part of uri path after '/'.
//
// Examples:
//
// * For /foo/bar/baz.html path returns baz.html.
// * For /foo/bar/ returns empty byte slice.
// * For /foobar.js returns foobar.js.
func (u *URI) LastPathSegment() []byte {
path := u.Path()
n := bytes.LastIndexByte(path, '/')
if n < 0 {
return path
}
return path[n+1:]
}
// Update updates uri.
//
// The following newURI types are accepted:
//
// * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
// uri is replaced by newURI.
// * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
// of the original uri is replaced.
// * Relative path, i.e. xx?yy=abc . In this case the original RequestURI
// is updated according to the new relative path.
func (u *URI) Update(newURI string) {
u.UpdateBytes(s2b(newURI))
}
// UpdateBytes updates uri.
//
// The following newURI types are accepted:
//
// * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
// uri is replaced by newURI.
// * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
// of the original uri is replaced.
// * Relative path, i.e. xx?yy=abc . In this case the original RequestURI
// is updated according to the new relative path.
func (u *URI) UpdateBytes(newURI []byte) {
u.requestURI = u.updateBytes(newURI, u.requestURI)
}
func (u *URI) updateBytes(newURI, buf []byte) []byte {
if len(newURI) == 0 {
return buf
}
if newURI[0] == '/' {
// uri without host
buf = u.appendSchemeHost(buf[:0])
buf = append(buf, newURI...)
u.Parse(nil, buf)
return buf
}
n := bytes.Index(newURI, strColonSlashSlash)
if n >= 0 {
// absolute uri
u.Parse(nil, newURI)
return buf
}
// relative path
switch newURI[0] {
case '?':
// query string only update
u.SetQueryStringBytes(newURI[1:])
return append(buf[:0], u.FullURI()...)
case '#':
// update only hash
u.SetHashBytes(newURI[1:])
return append(buf[:0], u.FullURI()...)
default:
// update the last path part after the slash
path := u.Path()
n = bytes.LastIndexByte(path, '/')
if n < 0 {
panic("BUG: path must contain at least one slash")
}
buf = u.appendSchemeHost(buf[:0])
buf = appendQuotedPath(buf, path[:n+1])
buf = append(buf, newURI...)
u.Parse(nil, buf)
return buf
}
}
// FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.
func (u *URI) FullURI() []byte {
u.fullURI = u.AppendBytes(u.fullURI[:0])
return u.fullURI
}
// AppendBytes appends full uri to dst and returns the extended dst.
func (u *URI) AppendBytes(dst []byte) []byte {
dst = u.appendSchemeHost(dst)
return append(dst, u.RequestURI()...)
}
func (u *URI) appendSchemeHost(dst []byte) []byte {
dst = append(dst, u.Scheme()...)
dst = append(dst, strColonSlashSlash...)
return append(dst, u.Host()...)
}
// WriteTo writes full uri to w.
//
// WriteTo implements io.WriterTo interface.
func (u *URI) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(u.FullURI())
return int64(n), err
}
// String returns full uri.
func (u *URI) String() string {
return string(u.FullURI())
}
func splitHostURI(host, uri []byte) ([]byte, []byte, []byte) {
n := bytes.Index(uri, strColonSlashSlash)
if n < 0 {
return strHTTP, host, uri
}
scheme := uri[:n]
if bytes.IndexByte(scheme, '/') >= 0 {
return strHTTP, host, uri
}
n += len(strColonSlashSlash)
uri = uri[n:]
n = bytes.IndexByte(uri, '/')
if n < 0 {
return scheme, uri, strSlash
}
return scheme, uri[:n], uri[n:]
}
// QueryArgs returns query args.
func (u *URI) QueryArgs() *Args {
u.parseQueryArgs()
return &u.queryArgs
}
func (u *URI) parseQueryArgs() {
if u.parsedQueryArgs {
return
}
u.queryArgs.ParseBytes(u.queryString)
u.parsedQueryArgs = true
}

311
vendor/github.com/valyala/fasthttp/uri_test.go generated vendored Normal file
View File

@@ -0,0 +1,311 @@
package fasthttp
import (
"bytes"
"fmt"
"testing"
"time"
)
func TestURICopyToQueryArgs(t *testing.T) {
var u URI
a := u.QueryArgs()
a.Set("foo", "bar")
var u1 URI
u.CopyTo(&u1)
a1 := u1.QueryArgs()
if string(a1.Peek("foo")) != "bar" {
t.Fatalf("unexpected query args value %q. Expecting %q", a1.Peek("foo"), "bar")
}
}
func TestURIAcquireReleaseSequential(t *testing.T) {
testURIAcquireRelease(t)
}
func TestURIAcquireReleaseConcurrent(t *testing.T) {
ch := make(chan struct{}, 10)
for i := 0; i < 10; i++ {
go func() {
testURIAcquireRelease(t)
ch <- struct{}{}
}()
}
for i := 0; i < 10; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testURIAcquireRelease(t *testing.T) {
for i := 0; i < 10; i++ {
u := AcquireURI()
host := fmt.Sprintf("host.%d.com", i*23)
path := fmt.Sprintf("/foo/%d/bar", i*17)
queryArgs := "?foo=bar&baz=aass"
u.Parse([]byte(host), []byte(path+queryArgs))
if string(u.Host()) != host {
t.Fatalf("unexpected host %q. Expecting %q", u.Host(), host)
}
if string(u.Path()) != path {
t.Fatalf("unexpected path %q. Expecting %q", u.Path(), path)
}
ReleaseURI(u)
}
}
func TestURILastPathSegment(t *testing.T) {
testURILastPathSegment(t, "", "")
testURILastPathSegment(t, "/", "")
testURILastPathSegment(t, "/foo/bar/", "")
testURILastPathSegment(t, "/foobar.js", "foobar.js")
testURILastPathSegment(t, "/foo/bar/baz.html", "baz.html")
}
func testURILastPathSegment(t *testing.T, path, expectedSegment string) {
var u URI
u.SetPath(path)
segment := u.LastPathSegment()
if string(segment) != expectedSegment {
t.Fatalf("unexpected last path segment for path %q: %q. Expecting %q", path, segment, expectedSegment)
}
}
func TestURIPathEscape(t *testing.T) {
testURIPathEscape(t, "/foo/bar", "/foo/bar")
testURIPathEscape(t, "/f_o-o=b:ar,b.c&q", "/f_o-o=b:ar,b.c&q")
testURIPathEscape(t, "/aa?bb.тест~qq", "/aa%3Fbb.%D1%82%D0%B5%D1%81%D1%82~qq")
}
func testURIPathEscape(t *testing.T, path, expectedRequestURI string) {
var u URI
u.SetPath(path)
requestURI := u.RequestURI()
if string(requestURI) != expectedRequestURI {
t.Fatalf("unexpected requestURI %q. Expecting %q. path %q", requestURI, expectedRequestURI, path)
}
}
func TestURIUpdate(t *testing.T) {
// full uri
testURIUpdate(t, "http://foo.bar/baz?aaa=22#aaa", "https://aa.com/bb", "https://aa.com/bb")
// empty uri
testURIUpdate(t, "http://aaa.com/aaa.html?234=234#add", "", "http://aaa.com/aaa.html?234=234#add")
// request uri
testURIUpdate(t, "ftp://aaa/xxx/yyy?aaa=bb#aa", "/boo/bar?xx", "ftp://aaa/boo/bar?xx")
// relative uri
testURIUpdate(t, "http://foo.bar/baz/xxx.html?aaa=22#aaa", "bb.html?xx=12#pp", "http://foo.bar/baz/bb.html?xx=12#pp")
testURIUpdate(t, "http://xx/a/b/c/d", "../qwe/p?zx=34", "http://xx/a/b/qwe/p?zx=34")
testURIUpdate(t, "https://qqq/aaa.html?foo=bar", "?baz=434&aaa#xcv", "https://qqq/aaa.html?baz=434&aaa#xcv")
testURIUpdate(t, "http://foo.bar/baz", "~a/%20b=c,тест?йцу=ке", "http://foo.bar/~a/%20b=c,%D1%82%D0%B5%D1%81%D1%82?йцу=ке")
testURIUpdate(t, "http://foo.bar/baz", "/qwe#fragment", "http://foo.bar/qwe#fragment")
testURIUpdate(t, "http://foobar/baz/xxx", "aaa.html#bb?cc=dd&ee=dfd", "http://foobar/baz/aaa.html#bb?cc=dd&ee=dfd")
// hash
testURIUpdate(t, "http://foo.bar/baz#aaa", "#fragment", "http://foo.bar/baz#fragment")
}
func testURIUpdate(t *testing.T, base, update, result string) {
var u URI
u.Parse(nil, []byte(base))
u.Update(update)
s := u.String()
if s != result {
t.Fatalf("unexpected result %q. Expecting %q. base=%q, update=%q", s, result, base, update)
}
}
func TestURIPathNormalize(t *testing.T) {
var u URI
// double slash
testURIPathNormalize(t, &u, "/aa//bb", "/aa/bb")
// triple slash
testURIPathNormalize(t, &u, "/x///y/", "/x/y/")
// multi slashes
testURIPathNormalize(t, &u, "/abc//de///fg////", "/abc/de/fg/")
// encoded slashes
testURIPathNormalize(t, &u, "/xxxx%2fyyy%2f%2F%2F", "/xxxx/yyy/")
// dotdot
testURIPathNormalize(t, &u, "/aaa/..", "/")
// dotdot with trailing slash
testURIPathNormalize(t, &u, "/xxx/yyy/../", "/xxx/")
// multi dotdots
testURIPathNormalize(t, &u, "/aaa/bbb/ccc/../../ddd", "/aaa/ddd")
// dotdots separated by other data
testURIPathNormalize(t, &u, "/a/b/../c/d/../e/..", "/a/c/")
// too many dotdots
testURIPathNormalize(t, &u, "/aaa/../../../../xxx", "/xxx")
testURIPathNormalize(t, &u, "/../../../../../..", "/")
testURIPathNormalize(t, &u, "/../../../../../../", "/")
// encoded dotdots
testURIPathNormalize(t, &u, "/aaa%2Fbbb%2F%2E.%2Fxxx", "/aaa/xxx")
// double slash with dotdots
testURIPathNormalize(t, &u, "/aaa////..//b", "/b")
// fake dotdot
testURIPathNormalize(t, &u, "/aaa/..bbb/ccc/..", "/aaa/..bbb/")
// single dot
testURIPathNormalize(t, &u, "/a/./b/././c/./d.html", "/a/b/c/d.html")
testURIPathNormalize(t, &u, "./foo/", "/foo/")
testURIPathNormalize(t, &u, "./../.././../../aaa/bbb/../../../././../", "/")
testURIPathNormalize(t, &u, "./a/./.././../b/./foo.html", "/b/foo.html")
}
func testURIPathNormalize(t *testing.T, u *URI, requestURI, expectedPath string) {
u.Parse(nil, []byte(requestURI))
if string(u.Path()) != expectedPath {
t.Fatalf("Unexpected path %q. Expected %q. requestURI=%q", u.Path(), expectedPath, requestURI)
}
}
func TestURIFullURI(t *testing.T) {
var args Args
// empty scheme, path and hash
testURIFullURI(t, "", "foobar.com", "", "", &args, "http://foobar.com/")
// empty scheme and hash
testURIFullURI(t, "", "aa.com", "/foo/bar", "", &args, "http://aa.com/foo/bar")
// empty hash
testURIFullURI(t, "fTP", "XXx.com", "/foo", "", &args, "ftp://xxx.com/foo")
// empty args
testURIFullURI(t, "https", "xx.com", "/", "aaa", &args, "https://xx.com/#aaa")
// non-empty args and non-ASCII path
args.Set("foo", "bar")
args.Set("xxx", "йух")
testURIFullURI(t, "", "xxx.com", "/тест123", "2er", &args, "http://xxx.com/%D1%82%D0%B5%D1%81%D1%82123?foo=bar&xxx=%D0%B9%D1%83%D1%85#2er")
// test with empty args and non-empty query string
var u URI
u.Parse([]byte("google.com"), []byte("/foo?bar=baz&baraz#qqqq"))
uri := u.FullURI()
expectedURI := "http://google.com/foo?bar=baz&baraz#qqqq"
if string(uri) != expectedURI {
t.Fatalf("Unexpected URI: %q. Expected %q", uri, expectedURI)
}
}
func testURIFullURI(t *testing.T, scheme, host, path, hash string, args *Args, expectedURI string) {
var u URI
u.SetScheme(scheme)
u.SetHost(host)
u.SetPath(path)
u.SetHash(hash)
args.CopyTo(u.QueryArgs())
uri := u.FullURI()
if string(uri) != expectedURI {
t.Fatalf("Unexpected URI: %q. Expected %q", uri, expectedURI)
}
}
func TestURIParseNilHost(t *testing.T) {
testURIParseScheme(t, "http://google.com/foo?bar#baz", "http")
testURIParseScheme(t, "HTtP://google.com/", "http")
testURIParseScheme(t, "://google.com/", "http")
testURIParseScheme(t, "fTP://aaa.com", "ftp")
testURIParseScheme(t, "httPS://aaa.com", "https")
}
func testURIParseScheme(t *testing.T, uri, expectedScheme string) {
var u URI
u.Parse(nil, []byte(uri))
if string(u.Scheme()) != expectedScheme {
t.Fatalf("Unexpected scheme %q. Expected %q for uri %q", u.Scheme(), expectedScheme, uri)
}
}
func TestURIParse(t *testing.T) {
var u URI
// no args
testURIParse(t, &u, "aaa", "sdfdsf",
"http://aaa/sdfdsf", "aaa", "/sdfdsf", "sdfdsf", "", "")
// args
testURIParse(t, &u, "xx", "/aa?ss",
"http://xx/aa?ss", "xx", "/aa", "/aa", "ss", "")
// args and hash
testURIParse(t, &u, "foobar.com", "/a.b.c?def=gkl#mnop",
"http://foobar.com/a.b.c?def=gkl#mnop", "foobar.com", "/a.b.c", "/a.b.c", "def=gkl", "mnop")
// '?' and '#' in hash
testURIParse(t, &u, "aaa.com", "/foo#bar?baz=aaa#bbb",
"http://aaa.com/foo#bar?baz=aaa#bbb", "aaa.com", "/foo", "/foo", "", "bar?baz=aaa#bbb")
// encoded path
testURIParse(t, &u, "aa.com", "/Test%20+%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf",
"http://aa.com/Test%20%2B%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf", "aa.com", "/Test + при", "/Test%20+%20%D0%BF%D1%80%D0%B8", "asdf=%20%20&s=12", "sdf")
// host in uppercase
testURIParse(t, &u, "FOObar.COM", "/bC?De=F#Gh",
"http://foobar.com/bC?De=F#Gh", "foobar.com", "/bC", "/bC", "De=F", "Gh")
// uri with hostname
testURIParse(t, &u, "xxx.com", "http://aaa.com/foo/bar?baz=aaa#ddd",
"http://aaa.com/foo/bar?baz=aaa#ddd", "aaa.com", "/foo/bar", "/foo/bar", "baz=aaa", "ddd")
testURIParse(t, &u, "xxx.com", "https://ab.com/f/b%20r?baz=aaa#ddd",
"https://ab.com/f/b%20r?baz=aaa#ddd", "ab.com", "/f/b r", "/f/b%20r", "baz=aaa", "ddd")
// no slash after hostname in uri
testURIParse(t, &u, "aaa.com", "http://google.com",
"http://google.com/", "google.com", "/", "/", "", "")
// uppercase hostname in uri
testURIParse(t, &u, "abc.com", "http://GoGLE.com/aaa",
"http://gogle.com/aaa", "gogle.com", "/aaa", "/aaa", "", "")
// http:// in query params
testURIParse(t, &u, "aaa.com", "/foo?bar=http://google.com",
"http://aaa.com/foo?bar=http://google.com", "aaa.com", "/foo", "/foo", "bar=http://google.com", "")
}
func testURIParse(t *testing.T, u *URI, host, uri,
expectedURI, expectedHost, expectedPath, expectedPathOriginal, expectedArgs, expectedHash string) {
u.Parse([]byte(host), []byte(uri))
if !bytes.Equal(u.FullURI(), []byte(expectedURI)) {
t.Fatalf("Unexpected uri %q. Expected %q. host=%q, uri=%q", u.FullURI(), expectedURI, host, uri)
}
if !bytes.Equal(u.Host(), []byte(expectedHost)) {
t.Fatalf("Unexpected host %q. Expected %q. host=%q, uri=%q", u.Host(), expectedHost, host, uri)
}
if !bytes.Equal(u.PathOriginal(), []byte(expectedPathOriginal)) {
t.Fatalf("Unexpected original path %q. Expected %q. host=%q, uri=%q", u.PathOriginal(), expectedPathOriginal, host, uri)
}
if !bytes.Equal(u.Path(), []byte(expectedPath)) {
t.Fatalf("Unexpected path %q. Expected %q. host=%q, uri=%q", u.Path(), expectedPath, host, uri)
}
if !bytes.Equal(u.QueryString(), []byte(expectedArgs)) {
t.Fatalf("Unexpected args %q. Expected %q. host=%q, uri=%q", u.QueryString(), expectedArgs, host, uri)
}
if !bytes.Equal(u.Hash(), []byte(expectedHash)) {
t.Fatalf("Unexpected hash %q. Expected %q. host=%q, uri=%q", u.Hash(), expectedHash, host, uri)
}
}

49
vendor/github.com/valyala/fasthttp/uri_timing_test.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package fasthttp
import (
"testing"
)
func BenchmarkURIParsePath(b *testing.B) {
benchmarkURIParse(b, "google.com", "/foo/bar")
}
func BenchmarkURIParsePathQueryString(b *testing.B) {
benchmarkURIParse(b, "google.com", "/foo/bar?query=string&other=value")
}
func BenchmarkURIParsePathQueryStringHash(b *testing.B) {
benchmarkURIParse(b, "google.com", "/foo/bar?query=string&other=value#hashstring")
}
func BenchmarkURIParseHostname(b *testing.B) {
benchmarkURIParse(b, "google.com", "http://foobar.com/foo/bar?query=string&other=value#hashstring")
}
func BenchmarkURIFullURI(b *testing.B) {
host := []byte("foobar.com")
requestURI := []byte("/foobar/baz?aaa=bbb&ccc=ddd")
uriLen := len(host) + len(requestURI) + 7
b.RunParallel(func(pb *testing.PB) {
var u URI
u.Parse(host, requestURI)
for pb.Next() {
uri := u.FullURI()
if len(uri) != uriLen {
b.Fatalf("unexpected uri len %d. Expecting %d", len(uri), uriLen)
}
}
})
}
func benchmarkURIParse(b *testing.B, host, uri string) {
strHost, strURI := []byte(host), []byte(uri)
b.RunParallel(func(pb *testing.PB) {
var u URI
for pb.Next() {
u.Parse(strHost, strURI)
}
})
}

12
vendor/github.com/valyala/fasthttp/uri_unix.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build !windows
package fasthttp
func addLeadingSlash(dst, src []byte) []byte {
// add leading slash for unix paths
if len(src) == 0 || src[0] != '/' {
dst = append(dst, '/')
}
return dst
}

12
vendor/github.com/valyala/fasthttp/uri_windows.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build windows
package fasthttp
func addLeadingSlash(dst, src []byte) []byte {
// zero length and "C:/" case
if len(src) == 0 || (len(src) > 2 && src[1] != ':') {
dst = append(dst, '/')
}
return dst
}

12
vendor/github.com/valyala/fasthttp/uri_windows_test.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build windows
package fasthttp
import "testing"
func TestURIPathNormalizeIssue86(t *testing.T) {
// see https://github.com/valyala/fasthttp/issues/86
var u URI
testURIPathNormalize(t, &u, `C:\a\b\c\fs.go`, `C:\a\b\c\fs.go`)
}

71
vendor/github.com/valyala/fasthttp/userdata.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package fasthttp
import (
"io"
)
type userDataKV struct {
key []byte
value interface{}
}
type userData []userDataKV
func (d *userData) Set(key string, value interface{}) {
args := *d
n := len(args)
for i := 0; i < n; i++ {
kv := &args[i]
if string(kv.key) == key {
kv.value = value
return
}
}
c := cap(args)
if c > n {
args = args[:n+1]
kv := &args[n]
kv.key = append(kv.key[:0], key...)
kv.value = value
*d = args
return
}
kv := userDataKV{}
kv.key = append(kv.key[:0], key...)
kv.value = value
*d = append(args, kv)
}
func (d *userData) SetBytes(key []byte, value interface{}) {
d.Set(b2s(key), value)
}
func (d *userData) Get(key string) interface{} {
args := *d
n := len(args)
for i := 0; i < n; i++ {
kv := &args[i]
if string(kv.key) == key {
return kv.value
}
}
return nil
}
func (d *userData) GetBytes(key []byte) interface{} {
return d.Get(b2s(key))
}
func (d *userData) Reset() {
args := *d
n := len(args)
for i := 0; i < n; i++ {
v := args[i].value
if vc, ok := v.(io.Closer); ok {
vc.Close()
}
}
*d = (*d)[:0]
}

74
vendor/github.com/valyala/fasthttp/userdata_test.go generated vendored Normal file
View File

@@ -0,0 +1,74 @@
package fasthttp
import (
"fmt"
"reflect"
"testing"
)
func TestUserData(t *testing.T) {
var u userData
for i := 0; i < 10; i++ {
key := []byte(fmt.Sprintf("key_%d", i))
u.SetBytes(key, i+5)
testUserDataGet(t, &u, key, i+5)
u.SetBytes(key, i)
testUserDataGet(t, &u, key, i)
}
for i := 0; i < 10; i++ {
key := []byte(fmt.Sprintf("key_%d", i))
testUserDataGet(t, &u, key, i)
}
u.Reset()
for i := 0; i < 10; i++ {
key := []byte(fmt.Sprintf("key_%d", i))
testUserDataGet(t, &u, key, nil)
}
}
func testUserDataGet(t *testing.T, u *userData, key []byte, value interface{}) {
v := u.GetBytes(key)
if v == nil && value != nil {
t.Fatalf("cannot obtain value for key=%q", key)
}
if !reflect.DeepEqual(v, value) {
t.Fatalf("unexpected value for key=%q: %d. Expecting %d", key, v, value)
}
}
func TestUserDataValueClose(t *testing.T) {
var u userData
closeCalls := 0
// store values implementing io.Closer
for i := 0; i < 5; i++ {
key := fmt.Sprintf("key_%d", i)
u.Set(key, &closerValue{&closeCalls})
}
// store values without io.Closer
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key_noclose_%d", i)
u.Set(key, i)
}
u.Reset()
if closeCalls != 5 {
t.Fatalf("unexpected number of Close calls: %d. Expecting 10", closeCalls)
}
}
type closerValue struct {
closeCalls *int
}
func (cv *closerValue) Close() error {
(*cv.closeCalls)++
return nil
}

View File

@@ -0,0 +1,48 @@
package fasthttp
import (
"testing"
)
func BenchmarkUserDataCustom(b *testing.B) {
keys := []string{"foobar", "baz", "aaa", "bsdfs"}
b.RunParallel(func(pb *testing.PB) {
var u userData
var v interface{} = u
for pb.Next() {
for _, key := range keys {
u.Set(key, v)
}
for _, key := range keys {
vv := u.Get(key)
if _, ok := vv.(userData); !ok {
b.Fatalf("unexpected value %v for key %q", vv, key)
}
}
u.Reset()
}
})
}
func BenchmarkUserDataStdMap(b *testing.B) {
keys := []string{"foobar", "baz", "aaa", "bsdfs"}
b.RunParallel(func(pb *testing.PB) {
u := make(map[string]interface{})
var v interface{} = u
for pb.Next() {
for _, key := range keys {
u[key] = v
}
for _, key := range keys {
vv := u[key]
if _, ok := vv.(map[string]interface{}); !ok {
b.Fatalf("unexpected value %v for key %q", vv, key)
}
}
for k := range u {
delete(u, k)
}
}
})
}

241
vendor/github.com/valyala/fasthttp/workerpool.go generated vendored Normal file
View File

@@ -0,0 +1,241 @@
package fasthttp
import (
"net"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
)
// workerPool serves incoming connections via a pool of workers
// in FILO order, i.e. the most recently stopped worker will serve the next
// incoming connection.
//
// Such a scheme keeps CPU caches hot (in theory).
type workerPool struct {
// Function for serving server connections.
// It must leave c unclosed.
WorkerFunc func(c net.Conn) error
MaxWorkersCount int
LogAllErrors bool
MaxIdleWorkerDuration time.Duration
Logger Logger
lock sync.Mutex
workersCount int
mustStop bool
ready []*workerChan
stopCh chan struct{}
workerChanPool sync.Pool
}
type workerChan struct {
lastUseTime time.Time
ch chan net.Conn
}
func (wp *workerPool) Start() {
if wp.stopCh != nil {
panic("BUG: workerPool already started")
}
wp.stopCh = make(chan struct{})
stopCh := wp.stopCh
go func() {
var scratch []*workerChan
for {
wp.clean(&scratch)
select {
case <-stopCh:
return
default:
time.Sleep(wp.getMaxIdleWorkerDuration())
}
}
}()
}
func (wp *workerPool) Stop() {
if wp.stopCh == nil {
panic("BUG: workerPool wasn't started")
}
close(wp.stopCh)
wp.stopCh = nil
// Stop all the workers waiting for incoming connections.
// Do not wait for busy workers - they will stop after
// serving the connection and noticing wp.mustStop = true.
wp.lock.Lock()
ready := wp.ready
for i, ch := range ready {
ch.ch <- nil
ready[i] = nil
}
wp.ready = ready[:0]
wp.mustStop = true
wp.lock.Unlock()
}
func (wp *workerPool) getMaxIdleWorkerDuration() time.Duration {
if wp.MaxIdleWorkerDuration <= 0 {
return 10 * time.Second
}
return wp.MaxIdleWorkerDuration
}
func (wp *workerPool) clean(scratch *[]*workerChan) {
maxIdleWorkerDuration := wp.getMaxIdleWorkerDuration()
// Clean least recently used workers if they didn't serve connections
// for more than maxIdleWorkerDuration.
currentTime := time.Now()
wp.lock.Lock()
ready := wp.ready
n := len(ready)
i := 0
for i < n && currentTime.Sub(ready[i].lastUseTime) > maxIdleWorkerDuration {
i++
}
*scratch = append((*scratch)[:0], ready[:i]...)
if i > 0 {
m := copy(ready, ready[i:])
for i = m; i < n; i++ {
ready[i] = nil
}
wp.ready = ready[:m]
}
wp.lock.Unlock()
// Notify obsolete workers to stop.
// This notification must be outside the wp.lock, since ch.ch
// may be blocking and may consume a lot of time if many workers
// are located on non-local CPUs.
tmp := *scratch
for i, ch := range tmp {
ch.ch <- nil
tmp[i] = nil
}
}
func (wp *workerPool) Serve(c net.Conn) bool {
ch := wp.getCh()
if ch == nil {
return false
}
ch.ch <- c
return true
}
var workerChanCap = func() int {
// Use blocking workerChan if GOMAXPROCS=1.
// This immediately switches Serve to WorkerFunc, which results
// in higher performance (under go1.5 at least).
if runtime.GOMAXPROCS(0) == 1 {
return 0
}
// Use non-blocking workerChan if GOMAXPROCS>1,
// since otherwise the Serve caller (Acceptor) may lag accepting
// new connections if WorkerFunc is CPU-bound.
return 1
}()
func (wp *workerPool) getCh() *workerChan {
var ch *workerChan
createWorker := false
wp.lock.Lock()
ready := wp.ready
n := len(ready) - 1
if n < 0 {
if wp.workersCount < wp.MaxWorkersCount {
createWorker = true
wp.workersCount++
}
} else {
ch = ready[n]
ready[n] = nil
wp.ready = ready[:n]
}
wp.lock.Unlock()
if ch == nil {
if !createWorker {
return nil
}
vch := wp.workerChanPool.Get()
if vch == nil {
vch = &workerChan{
ch: make(chan net.Conn, workerChanCap),
}
}
ch = vch.(*workerChan)
go func() {
wp.workerFunc(ch)
wp.workerChanPool.Put(vch)
}()
}
return ch
}
func (wp *workerPool) release(ch *workerChan) bool {
ch.lastUseTime = time.Now()
wp.lock.Lock()
if wp.mustStop {
wp.lock.Unlock()
return false
}
wp.ready = append(wp.ready, ch)
wp.lock.Unlock()
return true
}
func (wp *workerPool) workerFunc(ch *workerChan) {
var c net.Conn
defer func() {
if r := recover(); r != nil {
wp.Logger.Printf("panic: %s\nStack trace:\n%s", r, debug.Stack())
if c != nil {
c.Close()
}
}
wp.lock.Lock()
wp.workersCount--
wp.lock.Unlock()
}()
var err error
for c = range ch.ch {
if c == nil {
break
}
if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
errStr := err.Error()
if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") ||
strings.Contains(errStr, "reset by peer") ||
strings.Contains(errStr, "i/o timeout")) {
wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err)
}
}
if err != errHijacked {
c.Close()
}
c = nil
if !wp.release(ch) {
break
}
}
}

267
vendor/github.com/valyala/fasthttp/workerpool_test.go generated vendored Normal file
View File

@@ -0,0 +1,267 @@
package fasthttp
import (
"fmt"
"io/ioutil"
"net"
"sync/atomic"
"testing"
"time"
"github.com/valyala/fasthttp/fasthttputil"
)
func TestWorkerPoolStartStopSerial(t *testing.T) {
testWorkerPoolStartStop(t)
}
func TestWorkerPoolStartStopConcurrent(t *testing.T) {
concurrency := 10
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testWorkerPoolStartStop(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testWorkerPoolStartStop(t *testing.T) {
wp := &workerPool{
WorkerFunc: func(conn net.Conn) error { return nil },
MaxWorkersCount: 10,
Logger: defaultLogger,
}
for i := 0; i < 10; i++ {
wp.Start()
wp.Stop()
}
}
func TestWorkerPoolMaxWorkersCountSerial(t *testing.T) {
testWorkerPoolMaxWorkersCountMulti(t)
}
func TestWorkerPoolMaxWorkersCountConcurrent(t *testing.T) {
concurrency := 4
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testWorkerPoolMaxWorkersCountMulti(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testWorkerPoolMaxWorkersCountMulti(t *testing.T) {
for i := 0; i < 5; i++ {
testWorkerPoolMaxWorkersCount(t)
}
}
func testWorkerPoolMaxWorkersCount(t *testing.T) {
ready := make(chan struct{})
wp := &workerPool{
WorkerFunc: func(conn net.Conn) error {
buf := make([]byte, 100)
n, err := conn.Read(buf)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
buf = buf[:n]
if string(buf) != "foobar" {
t.Fatalf("unexpected data read: %q. Expecting %q", buf, "foobar")
}
if _, err = conn.Write([]byte("baz")); err != nil {
t.Fatalf("unexpected error: %s", err)
}
<-ready
return nil
},
MaxWorkersCount: 10,
Logger: defaultLogger,
}
wp.Start()
ln := fasthttputil.NewInmemoryListener()
clientCh := make(chan struct{}, wp.MaxWorkersCount)
for i := 0; i < wp.MaxWorkersCount; i++ {
go func() {
conn, err := ln.Dial()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if _, err = conn.Write([]byte("foobar")); err != nil {
t.Fatalf("unexpected error: %s", err)
}
data, err := ioutil.ReadAll(conn)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if string(data) != "baz" {
t.Fatalf("unexpected value read: %q. Expecting %q", data, "baz")
}
if err = conn.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
clientCh <- struct{}{}
}()
}
for i := 0; i < wp.MaxWorkersCount; i++ {
conn, err := ln.Accept()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !wp.Serve(conn) {
t.Fatalf("worker pool must have enough workers to serve the conn")
}
}
go func() {
if _, err := ln.Dial(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}()
conn, err := ln.Accept()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
for i := 0; i < 5; i++ {
if wp.Serve(conn) {
t.Fatalf("worker pool must be full")
}
}
if err = conn.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
close(ready)
for i := 0; i < wp.MaxWorkersCount; i++ {
select {
case <-clientCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
wp.Stop()
}
func TestWorkerPoolPanicErrorSerial(t *testing.T) {
testWorkerPoolPanicErrorMulti(t)
}
func TestWorkerPoolPanicErrorConcurrent(t *testing.T) {
concurrency := 10
ch := make(chan struct{}, concurrency)
for i := 0; i < concurrency; i++ {
go func() {
testWorkerPoolPanicErrorMulti(t)
ch <- struct{}{}
}()
}
for i := 0; i < concurrency; i++ {
select {
case <-ch:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
}
func testWorkerPoolPanicErrorMulti(t *testing.T) {
var globalCount uint64
wp := &workerPool{
WorkerFunc: func(conn net.Conn) error {
count := atomic.AddUint64(&globalCount, 1)
switch count % 3 {
case 0:
panic("foobar")
case 1:
return fmt.Errorf("fake error")
}
return nil
},
MaxWorkersCount: 1000,
MaxIdleWorkerDuration: time.Millisecond,
Logger: &customLogger{},
}
for i := 0; i < 10; i++ {
testWorkerPoolPanicError(t, wp)
}
}
func testWorkerPoolPanicError(t *testing.T, wp *workerPool) {
wp.Start()
ln := fasthttputil.NewInmemoryListener()
clientsCount := 10
clientCh := make(chan struct{}, clientsCount)
for i := 0; i < clientsCount; i++ {
go func() {
conn, err := ln.Dial()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
data, err := ioutil.ReadAll(conn)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if len(data) > 0 {
t.Fatalf("unexpected data read: %q. Expecting empty data", data)
}
if err = conn.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
clientCh <- struct{}{}
}()
}
for i := 0; i < clientsCount; i++ {
conn, err := ln.Accept()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !wp.Serve(conn) {
t.Fatalf("worker pool mustn't be full")
}
}
for i := 0; i < clientsCount; i++ {
select {
case <-clientCh:
case <-time.After(time.Second):
t.Fatalf("timeout")
}
}
if err := ln.Close(); err != nil {
t.Fatalf("unexpected error: %s", err)
}
wp.Stop()
}