1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-14 14:15:01 +00:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Fenny 2020-07-15 14:28:04 +02:00
commit fd1c01ddfa
5 changed files with 229 additions and 37 deletions

48
ctx.go
View File

@ -15,12 +15,12 @@ import (
"log"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"text/template"
"time"
@ -235,10 +235,10 @@ func (ctx *Ctx) BodyParser(out interface{}) error {
return xml.Unmarshal(ctx.Fasthttp.Request.Body(), out)
case MIMEApplicationForm: // application/x-www-form-urlencoded
schemaDecoder.SetAliasTag("form")
data, err := url.ParseQuery(getString(ctx.Fasthttp.PostBody()))
if err != nil {
return err
}
data := make(map[string][]string)
ctx.Fasthttp.PostArgs().VisitAll(func(key []byte, val []byte) {
data[getString(key)] = append(data[getString(key)], getString(val))
})
return schemaDecoder.Decode(out, data)
}
@ -266,19 +266,26 @@ func (ctx *Ctx) BodyParser(out interface{}) error {
return fmt.Errorf("bodyparser: cannot parse content-type: %v", ctype)
}
// queryDecoderPool helps to improve QueryParser's performance
var queryDecoderPool = &sync.Pool{New: func() interface{} {
var decoder = schema.NewDecoder()
decoder.SetAliasTag("query")
decoder.IgnoreUnknownKeys(true)
return decoder
}}
// QueryParser binds the query string to a struct.
func (ctx *Ctx) QueryParser(out interface{}) error {
if ctx.Fasthttp.QueryArgs().Len() > 0 {
var schemaDecoderQuery = schema.NewDecoder()
schemaDecoderQuery.SetAliasTag("query")
schemaDecoderQuery.IgnoreUnknownKeys(true)
var decoder = queryDecoderPool.Get().(*schema.Decoder)
defer queryDecoderPool.Put(decoder)
data := make(map[string][]string)
ctx.Fasthttp.QueryArgs().VisitAll(func(key []byte, val []byte) {
data[getString(key)] = append(data[getString(key)], getString(val))
})
return schemaDecoderQuery.Decode(out, data)
return decoder.Decode(out, data)
}
return nil
}
@ -496,13 +503,20 @@ func (ctx *Ctx) IP() string {
}
// IPs returns an string slice of IP addresses specified in the X-Forwarded-For request header.
func (ctx *Ctx) IPs() []string {
// TODO: improve with for iteration and string.Index -> like in Accepts
ips := strings.Split(ctx.Get(HeaderXForwardedFor), ",")
for i := range ips {
ips[i] = utils.Trim(ips[i], ' ')
func (ctx *Ctx) IPs() (ips []string) {
header := ctx.Fasthttp.Request.Header.Peek(HeaderXForwardedFor)
ips = make([]string, bytes.Count(header, []byte(","))+1)
var commaPos, i int
for {
commaPos = bytes.IndexByte(header, ',')
if commaPos != -1 {
ips[i] = getString(header[:commaPos])
header, i = header[commaPos+2:], i+1
} else {
ips[i] = getString(header)
return
}
}
return ips
}
// Is returns the matching content type,
@ -822,7 +836,7 @@ func (ctx *Ctx) Render(name string, bind interface{}, layouts ...string) (err er
return err
}
}
// Set Contet-Type to text/html
// Set Content-Type to text/html
ctx.Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
// Set rendered template to body
ctx.SendBytes(buf.Bytes())
@ -909,7 +923,7 @@ func (ctx *Ctx) SendFile(file string, compress ...bool) error {
file += "/"
}
}
// Set new URI for filehandler
// Set new URI for fileHandler
ctx.Fasthttp.Request.SetRequestURI(file)
// Save status code
status := ctx.Fasthttp.Response.StatusCode()

View File

@ -288,15 +288,35 @@ func Test_Ctx_BodyParser(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
type Demo struct {
Name string `json:"name" xml:"name" form:"name" query:"name"`
}
ctx.Fasthttp.Request.SetBody([]byte(`{"name":"john"}`))
ctx.Fasthttp.Request.Header.SetContentType(MIMEApplicationJSON)
ctx.Fasthttp.Request.Header.SetContentLength(len([]byte(`{"name":"john"}`)))
d := new(Demo)
utils.AssertEqual(t, nil, ctx.BodyParser(d))
utils.AssertEqual(t, "john", d.Name)
testDecodeParser := func(contentType, body string) {
ctx.Fasthttp.Request.Header.SetContentType(contentType)
ctx.Fasthttp.Request.SetBody([]byte(body))
ctx.Fasthttp.Request.Header.SetContentLength(len(body))
d := new(Demo)
utils.AssertEqual(t, nil, ctx.BodyParser(d))
utils.AssertEqual(t, "john", d.Name)
}
testDecodeParser(MIMEApplicationJSON, `{"name":"john"}`)
testDecodeParser(MIMEApplicationXML, `<Demo><name>john</name></Demo>`)
testDecodeParser(MIMEApplicationJSON, `{"name":"john"}`)
testDecodeParser(MIMEApplicationForm, "name=john")
testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
testDecodeParserError := func(contentType, body string) {
ctx.Fasthttp.Request.Header.SetContentType(contentType)
ctx.Fasthttp.Request.SetBody([]byte(body))
ctx.Fasthttp.Request.Header.SetContentLength(len(body))
utils.AssertEqual(t, false, ctx.BodyParser(nil) == nil)
}
testDecodeParserError("invalid-content-type", "")
testDecodeParserError(MIMEMultipartForm+`;boundary="b"`, "--b")
type Query struct {
ID int
@ -311,7 +331,102 @@ func Test_Ctx_BodyParser(t *testing.T) {
utils.AssertEqual(t, 2, len(q.Hobby))
}
// TODO Benchmark_Ctx_BodyParser
// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_JSON -benchmem -count=4
func Benchmark_Ctx_BodyParser_JSON(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Demo struct {
Name string `json:"name"`
}
body := []byte(`{"name":"john"}`)
c.Fasthttp.Request.SetBody(body)
c.Fasthttp.Request.Header.SetContentType(MIMEApplicationJSON)
c.Fasthttp.Request.Header.SetContentLength(len(body))
d := new(Demo)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = c.BodyParser(d)
}
utils.AssertEqual(b, nil, c.BodyParser(d))
utils.AssertEqual(b, "john", d.Name)
}
// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_XML -benchmem -count=4
func Benchmark_Ctx_BodyParser_XML(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Demo struct {
Name string `xml:"name"`
}
body := []byte("<Demo><name>john</name></Demo>")
c.Fasthttp.Request.SetBody(body)
c.Fasthttp.Request.Header.SetContentType(MIMEApplicationXML)
c.Fasthttp.Request.Header.SetContentLength(len(body))
d := new(Demo)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = c.BodyParser(d)
}
utils.AssertEqual(b, nil, c.BodyParser(d))
utils.AssertEqual(b, "john", d.Name)
}
// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_Form -benchmem -count=4
func Benchmark_Ctx_BodyParser_Form(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Demo struct {
Name string `form:"name"`
}
body := []byte("name=john")
c.Fasthttp.Request.SetBody(body)
c.Fasthttp.Request.Header.SetContentType(MIMEApplicationForm)
c.Fasthttp.Request.Header.SetContentLength(len(body))
d := new(Demo)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = c.BodyParser(d)
}
utils.AssertEqual(b, nil, c.BodyParser(d))
utils.AssertEqual(b, "john", d.Name)
}
// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_MultipartForm -benchmem -count=4
func Benchmark_Ctx_BodyParser_MultipartForm(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
type Demo struct {
Name string `form:"name"`
}
body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
c.Fasthttp.Request.SetBody(body)
c.Fasthttp.Request.Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
c.Fasthttp.Request.Header.SetContentLength(len(body))
d := new(Demo)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = c.BodyParser(d)
}
utils.AssertEqual(b, nil, c.BodyParser(d))
utils.AssertEqual(b, "john", d.Name)
}
// go test -run Test_Ctx_Context
func Test_Ctx_Context(t *testing.T) {
@ -1320,6 +1435,25 @@ func Test_Ctx_Render_Engine(t *testing.T) {
utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(ctx.Fasthttp.Response.Body()))
}
func Benchmark_Ctx_Render_Engine(b *testing.B) {
engine := &testTemplateEngine{}
err := engine.Load()
utils.AssertEqual(b, nil, err)
app := New()
app.Settings.Views = engine
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
err = ctx.Render("index.tmpl", Map{
"Title": "Hello, World!",
})
}
utils.AssertEqual(b, nil, err)
utils.AssertEqual(b, "<h1>Hello, World!</h1>", string(ctx.Fasthttp.Response.Body()))
}
// go test -run Test_Ctx_Render_Go_Template
func Test_Ctx_Render_Go_Template(t *testing.T) {
t.Parallel()

View File

@ -19,7 +19,10 @@ const (
envPreforkChildVal = "1"
)
var testPreforkMaster = false
var (
testPreforkMaster = false
dummyChildCmd = "date"
)
// IsChild determines if the current process is a result of Prefork
func (app *App) IsChild() bool {
@ -86,7 +89,7 @@ func (app *App) prefork(addr string, tlsconfig ...*tls.Config) (err error) {
// When test prefork master,
// just start the child process
// a cmd on all os is best
cmd = exec.Command("date")
cmd = exec.Command(dummyChildCmd)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -116,9 +119,5 @@ func (app *App) prefork(addr string, tlsconfig ...*tls.Config) (err error) {
}
// return error if child crashes
for sig := range channel {
return sig.err
}
return
return (<-channel).err
}

View File

@ -12,11 +12,12 @@ func Test_App_Prefork_Child_Process(t *testing.T) {
utils.AssertEqual(t, nil, os.Setenv(envPreforkChildKey, envPreforkChildVal))
defer os.Setenv(envPreforkChildKey, "")
app := New(&Settings{
DisableStartupMessage: true,
})
app := New()
app.init()
err := app.prefork("invalid")
utils.AssertEqual(t, false, err == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
@ -28,9 +29,7 @@ func Test_App_Prefork_Child_Process(t *testing.T) {
func Test_App_Prefork_Main_Process(t *testing.T) {
testPreforkMaster = true
app := New(&Settings{
DisableStartupMessage: true,
})
app := New()
app.init()
go func() {
@ -39,4 +38,9 @@ func Test_App_Prefork_Main_Process(t *testing.T) {
}()
utils.AssertEqual(t, nil, app.prefork("127.0.0.1:"))
dummyChildCmd = "invalid"
err := app.prefork("127.0.0.1:")
utils.AssertEqual(t, false, err == nil)
}

41
reuseport.go Normal file
View File

@ -0,0 +1,41 @@
// +build !windows
package fiber
import (
"net"
tcplisten "github.com/valyala/tcplisten"
)
// 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 .
// Listen returns TCP listener with SO_REUSEPORT option set.
//
// The returned listener tries enabling the following TCP options, which usually
// have positive impact on performance:
//
// - TCP_DEFER_ACCEPT. This option expects that the server reads from accepted
// connections before writing to them.
//
// - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details.
//
// Use https://github.com/valyala/tcplisten if you want customizing
// these options.
//
// Only tcp4 and tcp6 networks are supported.
//
// ErrNoReusePort error is returned if the system doesn't support SO_REUSEPORT.
func reuseport(network, addr string) (net.Listener, error) {
cfg := &tcplisten.Config{
ReusePort: true,
DeferAccept: true,
FastOpen: true,
}
return cfg.NewListener(network, addr)
}