Make ctx pool accessible + ctx benchmarks (#355)

**🚀 Fiber `v1.9.6`**

The Ctx pool is now accessible for third-party packages

🔥 New
- `func AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx`
- `func ReleaseCtx(ctx *Ctx)`

🩹 Fixes
- Some `Ctx` methods didn't work correctly when called without an `*App` pointer.
- `ctx.Vary` sometimes added duplicates to the response header

🧹 Updates
- Add context benchmarks
- Remove some unnecessary functions from `utils`

🧬 Middleware
- [gofiber/adaptor](https://github.com/gofiber/adaptor) `v0.0.1` Converter for net/http handlers to/from Fiber handlers
pull/366/head
Fenny 2020-05-11 04:30:31 +02:00 committed by GitHub
parent e2cc8106bb
commit 6e58bfcde3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 550 additions and 107 deletions

22
app.go
View File

@ -25,7 +25,7 @@ import (
)
// Version of current package
const Version = "1.9.5"
const Version = "1.9.6"
// Map is a shortcut for map[string]interface{}
type Map map[string]interface{}
@ -33,13 +33,15 @@ type Map map[string]interface{}
// App denotes the Fiber application.
type App struct {
// Internal fields
mutex sync.Mutex // Mutual exclusion
server *fasthttp.Server // FastHTTP server
testconn *testConn // Test connection
routes [][]*Route // Route stack
testconn *testConn // Test connection
routes [][]*Route // Route stack
// External fields
Settings *Settings // Fiber settings
// Fasthttp server
mutex sync.Mutex // Mutual exclusion
server *fasthttp.Server // FastHTTP server
}
// Settings holds is a struct holding the server settings
@ -123,13 +125,13 @@ func New(settings ...*Settings) *App {
// Create settings
app.Settings = new(Settings)
// Set default settings
app.Settings.Prefork = isPrefork()
app.Settings.Prefork = getArgument("-prefork")
app.Settings.BodyLimit = 4 * 1024 * 1024
// If settings exist, set defaults
if len(settings) > 0 {
app.Settings = settings[0] // Set custom settings
if !app.Settings.Prefork { // Default to -prefork flag if false
app.Settings.Prefork = isPrefork()
app.Settings.Prefork = getArgument("-prefork")
}
if app.Settings.BodyLimit <= 0 { // Default MaxRequestBodySize
app.Settings.BodyLimit = 4 * 1024 * 1024
@ -142,7 +144,7 @@ func New(settings ...*Settings) *App {
getBytes = getBytesImmutable
}
}
// Setup test connection
// Setup test listener
app.testconn = new(testConn)
// Setup server
app.server = app.newServer()
@ -403,7 +405,7 @@ func (app *App) Listen(address interface{}, tlsconfig ...*tls.Config) error {
ln = tls.NewListener(ln, tlsconfig[0])
}
// Print listening message
if !app.Settings.DisableStartupMessage && !isChild() {
if !app.Settings.DisableStartupMessage && !getArgument("-child") {
fmt.Printf(" _______ __\n ____ / ____(_) /_ ___ _____\n_____ / /_ / / __ \\/ _ \\/ ___/\n __ / __/ / / /_/ / __/ /\n /_/ /_/_.___/\\___/_/ v%s\n", Version)
fmt.Printf("Started listening on %s\n", ln.Addr().String())
}
@ -487,7 +489,7 @@ func (app *App) Test(request *http.Request, msTimeout ...int) (*http.Response, e
// Sharding: https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
func (app *App) prefork(address string) (ln net.Listener, err error) {
// Master proc
if !isChild() {
if !getArgument("-child") {
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return ln, err

5
app_bench_test.go Normal file
View File

@ -0,0 +1,5 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber

59
ctx.go
View File

@ -69,30 +69,34 @@ var schemaDecoderForm = schema.NewDecoder()
var schemaDecoderQuery = schema.NewDecoder()
var cacheControlNoCacheRegexp, _ = regexp.Compile(`/(?:^|,)\s*?no-cache\s*?(?:,|$)/`)
// Ctx pool
var poolCtx = sync.Pool{
var ctxPool = sync.Pool{
New: func() interface{} {
return new(Ctx)
},
}
// Acquire Ctx from pool
func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
ctx := poolCtx.Get().(*Ctx)
// AcquireCtx from pool
func AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
ctx := ctxPool.Get().(*Ctx)
// Set stack index
ctx.index = -1
// Set path
ctx.path = getString(fctx.URI().Path())
// Set method
ctx.method = getString(fctx.Request.Header.Method())
// Attach fasthttp request to ctx
ctx.Fasthttp = fctx
return ctx
}
// Return Ctx to pool
func releaseCtx(ctx *Ctx) {
// ReleaseCtx to pool
func ReleaseCtx(ctx *Ctx) {
// Reset values
ctx.route = nil
ctx.values = nil
ctx.Fasthttp = nil
ctx.err = nil
poolCtx.Put(ctx)
ctxPool.Put(ctx)
}
// Accepts checks if the specified extensions or content types are acceptable.
@ -605,6 +609,9 @@ func (ctx *Ctx) MultipartForm() (*multipart.Form, error) {
// Next executes the next method in the stack that matches the current route.
// You can pass an optional error for custom error handling.
func (ctx *Ctx) Next(err ...error) {
if ctx.app == nil {
return
}
ctx.route = nil
ctx.values = nil
if len(err) > 0 {
@ -635,7 +642,7 @@ func (ctx *Ctx) Params(key string) (value string) {
// Path returns the path part of the request URL.
// Optionally, you could override the path.
func (ctx *Ctx) Path(override ...string) string {
if len(override) > 0 {
if len(override) > 0 && ctx.app != nil {
// Non strict routing
if !ctx.app.Settings.StrictRouting && len(override[0]) > 1 {
override[0] = strings.TrimRight(override[0], "/")
@ -722,17 +729,18 @@ func (ctx *Ctx) Render(file string, bind interface{}) error {
var err error
var raw []byte
var html string
if ctx.app.Settings.TemplateFolder != "" {
file = filepath.Join(ctx.app.Settings.TemplateFolder, file)
if ctx.app != nil {
if ctx.app.Settings.TemplateFolder != "" {
file = filepath.Join(ctx.app.Settings.TemplateFolder, file)
}
if ctx.app.Settings.TemplateExtension != "" {
file = file + ctx.app.Settings.TemplateExtension
}
if raw, err = ioutil.ReadFile(filepath.Clean(file)); err != nil {
return err
}
}
if ctx.app.Settings.TemplateExtension != "" {
file = file + ctx.app.Settings.TemplateExtension
}
if raw, err = ioutil.ReadFile(filepath.Clean(file)); err != nil {
return err
}
if ctx.app.Settings.TemplateEngine != nil {
if ctx.app != nil && ctx.app.Settings.TemplateEngine != nil {
// Custom template engine
// https://github.com/gofiber/template
if html, err = ctx.app.Settings.TemplateEngine(getString(raw), bind); err != nil {
@ -855,16 +863,19 @@ func (ctx *Ctx) Vary(fields ...string) {
if len(fields) == 0 {
return
}
h := getString(ctx.Fasthttp.Response.Header.Peek(HeaderVary))
h := strings.ToLower(getString(ctx.Fasthttp.Response.Header.Peek(HeaderVary)))
for i := range fields {
if h == "" {
fields[i] = strings.ToLower(fields[i])
if len(h) == 0 {
h += fields[i]
} else {
} else if !strings.Contains(h, " "+fields[i]) && !strings.Contains(h, fields[i]+"") || !strings.Contains(h, fields[i]+",") {
// Next developer, it's your job to optimize the following problem
// Does the header value "Accept" exist in the following header value
// "Origin, User-Agent, Accept-Encoding"
// "Accept-Encoding" contains "Accept", false positive
h += ", " + fields[i]
}
}
ctx.Set(HeaderVary, h)
}

436
ctx_bench_test.go Normal file
View File

@ -0,0 +1,436 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"bytes"
"mime/multipart"
"strconv"
"testing"
"github.com/valyala/fasthttp"
)
func Benchmark_Ctx_Accepts(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9")
var res string
for n := 0; n < b.N; n++ {
res = c.Accepts(".xml")
}
assertEqual(b, ".xml", res)
}
func Benchmark_Ctx_AcceptsCharsets(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5")
var res string
for n := 0; n < b.N; n++ {
res = c.AcceptsCharsets("utf-8")
}
assertEqual(b, "utf-8", res)
}
func Benchmark_Ctx_AcceptsEncodings(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Accept-Encoding", "deflate, gzip;q=1.0, *;q=0.5")
var res string
for n := 0; n < b.N; n++ {
res = c.AcceptsEncodings("gzip")
}
assertEqual(b, "gzip", res)
}
func Benchmark_Ctx_AcceptsLanguages(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Accept-Language", "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5")
var res string
for n := 0; n < b.N; n++ {
res = c.AcceptsEncodings("fr")
}
assertEqual(b, "fr", res)
}
func Benchmark_Ctx_BaseURL(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.SetHost("google.com:1337")
var res string
for n := 0; n < b.N; n++ {
res = c.BaseURL()
}
assertEqual(b, "http://google.com:1337", res)
}
func Benchmark_Ctx_Body(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.SetBody([]byte("The best thing about a boolean is even if you are wrong, you are only off by a bit."))
var res string
for n := 0; n < b.N; n++ {
res = c.Body()
}
assertEqual(b, "The best thing about a boolean is even if you are wrong, you are only off by a bit.", res)
}
// TODO
// func Benchmark_Ctx_BodyParser(b *testing.B) {
// }
func Benchmark_Ctx_Cookies(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Cookie", "john=doe")
var res string
for n := 0; n < b.N; n++ {
res = c.Cookies("john")
}
assertEqual(b, "doe", res)
}
func Benchmark_Ctx_FormFile(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
ioWriter, _ := writer.CreateFormFile("file", "test")
_, _ = ioWriter.Write([]byte("hello world"))
writer.Close()
c.Fasthttp.Request.Header.SetMethod("POST")
c.Fasthttp.Request.Header.Set("Content-Type", writer.FormDataContentType())
c.Fasthttp.Request.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes())))
c.Fasthttp.Request.SetBody(body.Bytes())
var res *multipart.FileHeader
for n := 0; n < b.N; n++ {
res, _ = c.FormFile("file")
}
assertEqual(b, "test", res.Filename)
assertEqual(b, "application/octet-stream", res.Header["Content-Type"][0])
assertEqual(b, int64(11), res.Size)
}
// func Benchmark_Ctx_FormValue(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Fresh(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Get(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Hostname(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_IP(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_IPs(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Is(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Locals(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Method(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_MultipartForm(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_OriginalURL(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Params(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Path(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Query(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Range(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Route(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_SaveFile(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Secure(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Stale(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Subdomains(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Append(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Attachment(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_ClearCookie(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Cookie(b *testing.B) {
// TODO
// }
// func Benchmark_Ctx_Download(b *testing.B) {
// TODO
// }
func Benchmark_Ctx_Format(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("Accept", "text/html")
for n := 0; n < b.N; n++ {
c.Format("Hello, World!")
}
assertEqual(b, "<p>Hello, World!</p>", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_JSON(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
type SomeStruct struct {
Name string
Age uint8
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
for n := 0; n < b.N; n++ {
c.JSON(data)
}
assertEqual(b, `{"Name":"Grame","Age":20}`, string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_JSONP(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
type SomeStruct struct {
Name string
Age uint8
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
for n := 0; n < b.N; n++ {
c.JSONP(data, "john")
}
assertEqual(b, `john({"Name":"Grame","Age":20});`, string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_Links(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Links(
"http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last",
)
}
assertEqual(b, `<http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"`, string(c.Fasthttp.Response.Header.Peek("Link")))
}
// TODO
// func Benchmark_Ctx_Next(b *testing.B) {
// }
func Benchmark_Ctx_Redirect(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Redirect("http://example.com", 301)
}
assertEqual(b, 301, c.Fasthttp.Response.StatusCode())
assertEqual(b, "http://example.com", string(c.Fasthttp.Response.Header.Peek("Location")))
}
// TODO
// func Benchmark_Ctx_Render(b *testing.B) {
// }
func Benchmark_Ctx_Send(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Send([]byte("Hello, World"))
c.Send("Hello, World")
c.Send(1337)
}
assertEqual(b, "1337", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_SendBytes(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.SendBytes([]byte("Hello, World"))
}
assertEqual(b, "Hello, World", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_SendStatus(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.SendStatus(415)
}
assertEqual(b, 415, c.Fasthttp.Response.StatusCode())
assertEqual(b, "Unsupported Media Type", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_SendString(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Send("Hello, World")
}
assertEqual(b, "Hello, World", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_Set(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Set("X-I'm-a-Dummy-HeAdEr", "1337")
}
assertEqual(b, "1337", string(c.Fasthttp.Response.Header.Peek("X-I'm-a-Dummy-HeAdEr")))
}
func Benchmark_Ctx_Type(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Type(".json")
c.Type("json")
}
assertEqual(b, "application/json", string(c.Fasthttp.Response.Header.Peek("Content-Type")))
}
func Benchmark_Ctx_Vary(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Vary("Origin")
}
assertEqual(b, "Origin", string(c.Fasthttp.Response.Header.Peek("Vary")))
}
func Benchmark_Ctx_Write(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
for n := 0; n < b.N; n++ {
c.Write("Hello, ")
c.Write([]byte("World! "))
c.Write(123)
}
c.Send("") // empty body
c.Write("Hello, World!")
assertEqual(b, "Hello, World!", string(c.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_XHR(b *testing.B) {
c := AcquireCtx(&fasthttp.RequestCtx{})
defer ReleaseCtx(c)
c.Fasthttp.Request.Header.Set("X-Requested-With", "xMlHtTpReQuEst")
var res bool
for n := 0; n < b.N; n++ {
res = c.XHR()
}
assertEqual(b, true, res)
}

View File

@ -569,20 +569,6 @@ func Test_Subdomains(t *testing.T) {
assertEqual(t, nil, err, "app.Test(req)")
assertEqual(t, 200, resp.StatusCode, "Status code")
}
func Test_XHR(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
assertEqual(t, true, c.XHR())
})
req := httptest.NewRequest("GET", "/test", nil)
req.Header.Set("X-Requested-With", "XMLHttpRequest")
resp, err := app.Test(req)
assertEqual(t, nil, err, "app.Test(req)")
assertEqual(t, 200, resp.StatusCode, "Status code")
}
func Test_Append(t *testing.T) {
app := New()
@ -979,7 +965,7 @@ func Test_Vary(t *testing.T) {
resp, err := app.Test(req)
assertEqual(t, nil, err, "app.Test(req)")
assertEqual(t, 200, resp.StatusCode, "Status code")
assertEqual(t, "Origin, User-Agent, Accept-Encoding, Accept", resp.Header.Get("Vary"))
assertEqual(t, "origin, user-agent, accept-encoding, accept", resp.Header.Get("Vary"))
}
func Test_Write(t *testing.T) {
app := New()
@ -1000,3 +986,18 @@ func Test_Write(t *testing.T) {
assertEqual(t, nil, err)
assertEqual(t, `Hello, World! 123`, string(body))
}
func Test_XHR(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
assertEqual(t, true, c.XHR())
})
req := httptest.NewRequest("GET", "/test", nil)
req.Header.Set("X-Requested-With", "XMLHttpRequest")
resp, err := app.Test(req)
assertEqual(t, nil, err, "app.Test(req)")
assertEqual(t, 200, resp.StatusCode, "Status code")
}

View File

@ -106,11 +106,10 @@ func (r *Route) matchRoute(path string) (match bool, values []string) {
func (app *App) handler(fctx *fasthttp.RequestCtx) {
// get fiber context from sync pool
ctx := acquireCtx(fctx)
defer releaseCtx(ctx)
ctx := AcquireCtx(fctx)
defer ReleaseCtx(ctx)
// Attach app poiner to access the routes
ctx.app = app
// Case sensitive routing
if !app.Settings.CaseSensitive {
ctx.path = strings.ToLower(ctx.path)
@ -263,7 +262,6 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
}
// Serve file
fileHandler(c.Fasthttp)
// Finish request if found and not forbidden
status := c.Fasthttp.Response.StatusCode()
if status != 404 && status != 403 {

View File

@ -100,67 +100,16 @@ func getMIME(extension string) (mime string) {
return mime
}
// #nosec G103
// getString converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
var getString = func(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
var getStringImmutable = func(b []byte) string {
return string(b)
}
// #nosec G103
// getBytes converts string to a byte slice without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
var getBytes = func(s string) (b []byte) {
return *(*[]byte)(unsafe.Pointer(&s))
}
var getBytesImmutable = func(s string) (b []byte) {
return []byte(s)
}
// Check if -prefork is in arguments
func isPrefork() bool {
// Check if key is in arguments
func getArgument(arg string) bool {
for i := range os.Args[1:] {
if os.Args[1:][i] == "-prefork" {
if os.Args[1:][i] == arg {
return true
}
}
return false
}
// Check if -child is in arguments
func isChild() bool {
for i := range os.Args[1:] {
if os.Args[1:][i] == "-child" {
return true
}
}
return false
}
// https://golang.org/src/net/net.go#L113
// Helper methods for application#test
type testConn struct {
net.Conn
r bytes.Buffer
w bytes.Buffer
}
func (c *testConn) RemoteAddr() net.Addr {
return &net.TCPAddr{
IP: net.IPv4(0, 0, 0, 0),
}
}
func (c *testConn) LocalAddr() net.Addr { return c.RemoteAddr() }
func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) }
func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) }
func (c *testConn) Close() error { return nil }
func (c *testConn) SetDeadline(t time.Time) error { return nil }
func (c *testConn) SetReadDeadline(t time.Time) error { return nil }
func (c *testConn) SetWriteDeadline(t time.Time) error { return nil }
// Adapted from:
// https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L110
func parseTokenList(noneMatchBytes []byte) []string {
@ -189,6 +138,47 @@ func parseTokenList(noneMatchBytes []byte) []string {
return list
}
// https://golang.org/src/net/net.go#L113
// Helper methods for application#test
type testConn struct {
net.Conn
r bytes.Buffer
w bytes.Buffer
}
func (c *testConn) RemoteAddr() net.Addr {
return &net.TCPAddr{
IP: net.IPv4(0, 0, 0, 0),
}
}
func (c *testConn) LocalAddr() net.Addr { return c.RemoteAddr() }
func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) }
func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) }
func (c *testConn) Close() error { return nil }
func (c *testConn) SetDeadline(t time.Time) error { return nil }
func (c *testConn) SetReadDeadline(t time.Time) error { return nil }
func (c *testConn) SetWriteDeadline(t time.Time) error { return nil }
// #nosec G103
// getString converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
var getString = func(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
var getStringImmutable = func(b []byte) string {
return string(b)
}
// #nosec G103
// getBytes converts string to a byte slice without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
var getBytes = func(s string) (b []byte) {
return *(*[]byte)(unsafe.Pointer(&s))
}
var getBytesImmutable = func(s string) (b []byte) {
return []byte(s)
}
// HTTP methods and their unique INTs
var methodINT = map[string]int{
MethodGet: 0,