replace zxq.co/ripple/hanayo
This commit is contained in:
3
vendor/github.com/valyala/fasthttp/.gitignore
generated
vendored
Normal file
3
vendor/github.com/valyala/fasthttp/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
tags
|
||||
*.pprof
|
||||
*.fasthttp.gz
|
15
vendor/github.com/valyala/fasthttp/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/valyala/fasthttp/.travis.yml
generated
vendored
Normal 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
22
vendor/github.com/valyala/fasthttp/LICENSE
generated
vendored
Normal 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
579
vendor/github.com/valyala/fasthttp/README.md
generated
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
[](https://travis-ci.org/valyala/fasthttp)
|
||||
[](http://godoc.org/github.com/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
4
vendor/github.com/valyala/fasthttp/TODO
generated
vendored
Normal 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
469
vendor/github.com/valyala/fasthttp/args.go
generated
vendored
Normal 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
453
vendor/github.com/valyala/fasthttp/args_test.go
generated
vendored
Normal 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
30
vendor/github.com/valyala/fasthttp/args_timing_test.go
generated
vendored
Normal 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
88
vendor/github.com/valyala/fasthttp/bytebuffer.go
generated
vendored
Normal 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)
|
||||
}
|
29
vendor/github.com/valyala/fasthttp/bytebuffer_example_test.go
generated
vendored
Normal file
29
vendor/github.com/valyala/fasthttp/bytebuffer_example_test.go
generated
vendored
Normal 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
43
vendor/github.com/valyala/fasthttp/bytebuffer_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
32
vendor/github.com/valyala/fasthttp/bytebuffer_timing_test.go
generated
vendored
Normal file
32
vendor/github.com/valyala/fasthttp/bytebuffer_timing_test.go
generated
vendored
Normal 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
422
vendor/github.com/valyala/fasthttp/bytesconv.go
generated
vendored
Normal 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 = "<"
|
||||
case '>':
|
||||
sub = ">"
|
||||
case '"':
|
||||
sub = """
|
||||
case '\'':
|
||||
sub = "'"
|
||||
}
|
||||
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
8
vendor/github.com/valyala/fasthttp/bytesconv_32.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !amd64,!arm64,!ppc64
|
||||
|
||||
package fasthttp
|
||||
|
||||
const (
|
||||
maxIntChars = 9
|
||||
maxHexIntChars = 7
|
||||
)
|
39
vendor/github.com/valyala/fasthttp/bytesconv_32_test.go
generated
vendored
Normal file
39
vendor/github.com/valyala/fasthttp/bytesconv_32_test.go
generated
vendored
Normal 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
8
vendor/github.com/valyala/fasthttp/bytesconv_64.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build amd64 arm64 ppc64
|
||||
|
||||
package fasthttp
|
||||
|
||||
const (
|
||||
maxIntChars = 18
|
||||
maxHexIntChars = 15
|
||||
)
|
41
vendor/github.com/valyala/fasthttp/bytesconv_64_test.go
generated
vendored
Normal file
41
vendor/github.com/valyala/fasthttp/bytesconv_64_test.go
generated
vendored
Normal 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
259
vendor/github.com/valyala/fasthttp/bytesconv_test.go
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAppendHTMLEscape(t *testing.T) {
|
||||
testAppendHTMLEscape(t, "", "")
|
||||
testAppendHTMLEscape(t, "<", "<")
|
||||
testAppendHTMLEscape(t, "a", "a")
|
||||
testAppendHTMLEscape(t, `><"''`, "><"''")
|
||||
testAppendHTMLEscape(t, "fo<b x='ss'>a</b>xxx", "fo<b x='ss'>a</b>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)
|
||||
}
|
||||
}
|
167
vendor/github.com/valyala/fasthttp/bytesconv_timing_test.go
generated
vendored
Normal file
167
vendor/github.com/valyala/fasthttp/bytesconv_timing_test.go
generated
vendored
Normal 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
1888
vendor/github.com/valyala/fasthttp/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
39
vendor/github.com/valyala/fasthttp/client_example_test.go
generated
vendored
Normal file
39
vendor/github.com/valyala/fasthttp/client_example_test.go
generated
vendored
Normal 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
914
vendor/github.com/valyala/fasthttp/client_test.go
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
633
vendor/github.com/valyala/fasthttp/client_timing_test.go
generated
vendored
Normal file
633
vendor/github.com/valyala/fasthttp/client_timing_test.go
generated
vendored
Normal 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
284
vendor/github.com/valyala/fasthttp/compress.go
generated
vendored
Normal 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
89
vendor/github.com/valyala/fasthttp/compress_test.go
generated
vendored
Normal 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
394
vendor/github.com/valyala/fasthttp/cookie.go
generated
vendored
Normal 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
234
vendor/github.com/valyala/fasthttp/cookie_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
35
vendor/github.com/valyala/fasthttp/cookie_timing_test.go
generated
vendored
Normal file
35
vendor/github.com/valyala/fasthttp/cookie_timing_test.go
generated
vendored
Normal 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
40
vendor/github.com/valyala/fasthttp/doc.go
generated
vendored
Normal 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
|
4
vendor/github.com/valyala/fasthttp/examples/README.md
generated
vendored
Normal file
4
vendor/github.com/valyala/fasthttp/examples/README.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Code examples
|
||||
|
||||
* [HelloWorld server](helloworldserver)
|
||||
* [Static file server](fileserver)
|
1
vendor/github.com/valyala/fasthttp/examples/fileserver/.gitignore
generated
vendored
Normal file
1
vendor/github.com/valyala/fasthttp/examples/fileserver/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
fileserver
|
7
vendor/github.com/valyala/fasthttp/examples/fileserver/Makefile
generated
vendored
Normal file
7
vendor/github.com/valyala/fasthttp/examples/fileserver/Makefile
generated
vendored
Normal 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
|
84
vendor/github.com/valyala/fasthttp/examples/fileserver/README.md
generated
vendored
Normal file
84
vendor/github.com/valyala/fasthttp/examples/fileserver/README.md
generated
vendored
Normal 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
|
||||
```
|
120
vendor/github.com/valyala/fasthttp/examples/fileserver/fileserver.go
generated
vendored
Normal file
120
vendor/github.com/valyala/fasthttp/examples/fileserver/fileserver.go
generated
vendored
Normal 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")
|
||||
)
|
28
vendor/github.com/valyala/fasthttp/examples/fileserver/ssl-cert-snakeoil.key
generated
vendored
Normal file
28
vendor/github.com/valyala/fasthttp/examples/fileserver/ssl-cert-snakeoil.key
generated
vendored
Normal 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-----
|
17
vendor/github.com/valyala/fasthttp/examples/fileserver/ssl-cert-snakeoil.pem
generated
vendored
Normal file
17
vendor/github.com/valyala/fasthttp/examples/fileserver/ssl-cert-snakeoil.pem
generated
vendored
Normal 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-----
|
1
vendor/github.com/valyala/fasthttp/examples/helloworldserver/.gitignore
generated
vendored
Normal file
1
vendor/github.com/valyala/fasthttp/examples/helloworldserver/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
helloworldserver
|
6
vendor/github.com/valyala/fasthttp/examples/helloworldserver/Makefile
generated
vendored
Normal file
6
vendor/github.com/valyala/fasthttp/examples/helloworldserver/Makefile
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
helloworldserver: clean
|
||||
go get -u github.com/valyala/fasthttp
|
||||
go build
|
||||
|
||||
clean:
|
||||
rm -f helloworldserver
|
17
vendor/github.com/valyala/fasthttp/examples/helloworldserver/README.md
generated
vendored
Normal file
17
vendor/github.com/valyala/fasthttp/examples/helloworldserver/README.md
generated
vendored
Normal 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
|
||||
```
|
55
vendor/github.com/valyala/fasthttp/examples/helloworldserver/helloworldserver.go
generated
vendored
Normal file
55
vendor/github.com/valyala/fasthttp/examples/helloworldserver/helloworldserver.go
generated
vendored
Normal 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)
|
||||
}
|
62
vendor/github.com/valyala/fasthttp/expvarhandler/expvar.go
generated
vendored
Normal file
62
vendor/github.com/valyala/fasthttp/expvarhandler/expvar.go
generated
vendored
Normal 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
|
||||
}
|
67
vendor/github.com/valyala/fasthttp/expvarhandler/expvar_test.go
generated
vendored
Normal file
67
vendor/github.com/valyala/fasthttp/expvarhandler/expvar_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
135
vendor/github.com/valyala/fasthttp/fasthttpadaptor/adaptor.go
generated
vendored
Normal file
135
vendor/github.com/valyala/fasthttp/fasthttpadaptor/adaptor.go
generated
vendored
Normal 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
|
||||
}
|
125
vendor/github.com/valyala/fasthttp/fasthttpadaptor/adaptor_test.go
generated
vendored
Normal file
125
vendor/github.com/valyala/fasthttp/fasthttpadaptor/adaptor_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
2
vendor/github.com/valyala/fasthttp/fasthttputil/doc.go
generated
vendored
Normal file
2
vendor/github.com/valyala/fasthttp/fasthttputil/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package fasthttputil provides utility functions for fasthttp.
|
||||
package fasthttputil
|
84
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener.go
generated
vendored
Normal file
84
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener.go
generated
vendored
Normal 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
|
||||
}
|
92
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener_test.go
generated
vendored
Normal file
92
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
108
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener_timing_test.go
generated
vendored
Normal file
108
vendor/github.com/valyala/fasthttp/fasthttputil/inmemory_listener_timing_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
221
vendor/github.com/valyala/fasthttp/fasthttputil/pipeconns.go
generated
vendored
Normal file
221
vendor/github.com/valyala/fasthttp/fasthttputil/pipeconns.go
generated
vendored
Normal 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),
|
||||
}
|
||||
},
|
||||
}
|
236
vendor/github.com/valyala/fasthttp/fasthttputil/pipeconns_test.go
generated
vendored
Normal file
236
vendor/github.com/valyala/fasthttp/fasthttputil/pipeconns_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
28
vendor/github.com/valyala/fasthttp/fasthttputil/ssl-cert-snakeoil.key
generated
vendored
Normal file
28
vendor/github.com/valyala/fasthttp/fasthttputil/ssl-cert-snakeoil.key
generated
vendored
Normal 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-----
|
17
vendor/github.com/valyala/fasthttp/fasthttputil/ssl-cert-snakeoil.pem
generated
vendored
Normal file
17
vendor/github.com/valyala/fasthttp/fasthttputil/ssl-cert-snakeoil.pem
generated
vendored
Normal 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
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
28
vendor/github.com/valyala/fasthttp/fs_example_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
47
vendor/github.com/valyala/fasthttp/fs_handler_example_test.go
generated
vendored
Normal file
47
vendor/github.com/valyala/fasthttp/fs_handler_example_test.go
generated
vendored
Normal 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
586
vendor/github.com/valyala/fasthttp/fs_test.go
generated
vendored
Normal 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
2038
vendor/github.com/valyala/fasthttp/header.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
87
vendor/github.com/valyala/fasthttp/header_regression_test.go
generated
vendored
Normal file
87
vendor/github.com/valyala/fasthttp/header_regression_test.go
generated
vendored
Normal 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
1972
vendor/github.com/valyala/fasthttp/header_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
146
vendor/github.com/valyala/fasthttp/header_timing_test.go
generated
vendored
Normal file
146
vendor/github.com/valyala/fasthttp/header_timing_test.go
generated
vendored
Normal 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
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
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
9
vendor/github.com/valyala/fasthttp/nocopy.go
generated
vendored
Normal 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
100
vendor/github.com/valyala/fasthttp/peripconn.go
generated
vendored
Normal 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
59
vendor/github.com/valyala/fasthttp/peripconn_test.go
generated
vendored
Normal 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()
|
||||
}
|
32
vendor/github.com/valyala/fasthttp/requestctx_setbodystreamwriter_example_test.go
generated
vendored
Normal file
32
vendor/github.com/valyala/fasthttp/requestctx_setbodystreamwriter_example_test.go
generated
vendored
Normal 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
21
vendor/github.com/valyala/fasthttp/reuseport/LICENSE
generated
vendored
Normal 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.
|
106
vendor/github.com/valyala/fasthttp/reuseport/reuseport.go
generated
vendored
Normal file
106
vendor/github.com/valyala/fasthttp/reuseport/reuseport.go
generated
vendored
Normal 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
|
||||
}
|
9
vendor/github.com/valyala/fasthttp/reuseport/reuseport_bsd.go
generated
vendored
Normal file
9
vendor/github.com/valyala/fasthttp/reuseport/reuseport_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build darwin dragonfly freebsd netbsd openbsd rumprun
|
||||
|
||||
package reuseport
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const soReusePort = syscall.SO_REUSEPORT
|
24
vendor/github.com/valyala/fasthttp/reuseport/reuseport_example_test.go
generated
vendored
Normal file
24
vendor/github.com/valyala/fasthttp/reuseport/reuseport_example_test.go
generated
vendored
Normal 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!")
|
||||
}
|
5
vendor/github.com/valyala/fasthttp/reuseport/reuseport_linux.go
generated
vendored
Normal file
5
vendor/github.com/valyala/fasthttp/reuseport/reuseport_linux.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// +build linux
|
||||
|
||||
package reuseport
|
||||
|
||||
const soReusePort = 0x0F
|
98
vendor/github.com/valyala/fasthttp/reuseport/reuseport_test.go
generated
vendored
Normal file
98
vendor/github.com/valyala/fasthttp/reuseport/reuseport_test.go
generated
vendored
Normal 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
1901
vendor/github.com/valyala/fasthttp/server.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
177
vendor/github.com/valyala/fasthttp/server_example_test.go
generated
vendored
Normal file
177
vendor/github.com/valyala/fasthttp/server_example_test.go
generated
vendored
Normal 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
2351
vendor/github.com/valyala/fasthttp/server_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
451
vendor/github.com/valyala/fasthttp/server_timing_test.go
generated
vendored
Normal file
451
vendor/github.com/valyala/fasthttp/server_timing_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
28
vendor/github.com/valyala/fasthttp/ssl-cert-snakeoil.key
generated
vendored
Normal file
28
vendor/github.com/valyala/fasthttp/ssl-cert-snakeoil.key
generated
vendored
Normal 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-----
|
17
vendor/github.com/valyala/fasthttp/ssl-cert-snakeoil.pem
generated
vendored
Normal file
17
vendor/github.com/valyala/fasthttp/ssl-cert-snakeoil.pem
generated
vendored
Normal 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
147
vendor/github.com/valyala/fasthttp/status.go
generated
vendored
Normal 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
61
vendor/github.com/valyala/fasthttp/stream.go
generated
vendored
Normal 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
102
vendor/github.com/valyala/fasthttp/stream_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
70
vendor/github.com/valyala/fasthttp/stream_timing_test.go
generated
vendored
Normal file
70
vendor/github.com/valyala/fasthttp/stream_timing_test.go
generated
vendored
Normal 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
71
vendor/github.com/valyala/fasthttp/strings.go
generated
vendored
Normal 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
367
vendor/github.com/valyala/fasthttp/tcpdialer.go
generated
vendored
Normal 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
44
vendor/github.com/valyala/fasthttp/timer.go
generated
vendored
Normal 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
496
vendor/github.com/valyala/fasthttp/uri.go
generated
vendored
Normal 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
311
vendor/github.com/valyala/fasthttp/uri_test.go
generated
vendored
Normal 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
49
vendor/github.com/valyala/fasthttp/uri_timing_test.go
generated
vendored
Normal 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
12
vendor/github.com/valyala/fasthttp/uri_unix.go
generated
vendored
Normal 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
12
vendor/github.com/valyala/fasthttp/uri_windows.go
generated
vendored
Normal 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
12
vendor/github.com/valyala/fasthttp/uri_windows_test.go
generated
vendored
Normal 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
71
vendor/github.com/valyala/fasthttp/userdata.go
generated
vendored
Normal 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
74
vendor/github.com/valyala/fasthttp/userdata_test.go
generated
vendored
Normal 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
|
||||
}
|
48
vendor/github.com/valyala/fasthttp/userdata_timing_test.go
generated
vendored
Normal file
48
vendor/github.com/valyala/fasthttp/userdata_timing_test.go
generated
vendored
Normal 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
241
vendor/github.com/valyala/fasthttp/workerpool.go
generated
vendored
Normal 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
267
vendor/github.com/valyala/fasthttp/workerpool_test.go
generated
vendored
Normal 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()
|
||||
}
|
Reference in New Issue
Block a user