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:
commit
fd1c01ddfa
48
ctx.go
48
ctx.go
@ -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()
|
||||
|
148
ctx_test.go
148
ctx_test.go
@ -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()
|
||||
|
13
prefork.go
13
prefork.go
@ -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
|
||||
}
|
||||
|
@ -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
41
reuseport.go
Normal 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user