diff --git a/app.go b/app.go index b839c192..310940cd 100644 --- a/app.go +++ b/app.go @@ -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 diff --git a/app_bench_test.go b/app_bench_test.go new file mode 100644 index 00000000..f944de6a --- /dev/null +++ b/app_bench_test.go @@ -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 diff --git a/ctx.go b/ctx.go index 7be06d45..51244414 100644 --- a/ctx.go +++ b/ctx.go @@ -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) } diff --git a/ctx_bench_test.go b/ctx_bench_test.go new file mode 100644 index 00000000..fd1d49d5 --- /dev/null +++ b/ctx_bench_test.go @@ -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, "

Hello, World!

", 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, `; rel="next",; 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) +} diff --git a/ctx_test.go b/ctx_test.go index 885ea964..26330d6c 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -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") +} diff --git a/router.go b/router.go index 697bd8a5..5f892b7a 100644 --- a/router.go +++ b/router.go @@ -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 { diff --git a/utils.go b/utils.go index 6930e48a..2e1e8ba9 100644 --- a/utils.go +++ b/utils.go @@ -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,