Merge remote-tracking branch 'upstream/master'

pull/601/head
Fenny 2020-07-14 14:35:26 +02:00
commit 0a3eb08530
9 changed files with 267 additions and 65 deletions

View File

@ -124,18 +124,18 @@ go get -u github.com/gofiber/fiber
- [内存占用低](https://docs.gofiber.io/benchmarks) - [内存占用低](https://docs.gofiber.io/benchmarks)
- [API接口](https://docs.gofiber.io/context) - [API接口](https://docs.gofiber.io/context)
- [中间件](https://docs.gofiber.io/middleware)和[Next](https://docs.gofiber.io/context#next)支持 - [中间件](https://docs.gofiber.io/middleware)和[Next](https://docs.gofiber.io/context#next)支持
- [快速](https://dev.to/koddr/welcome-to-fiber-an-express-js-styled-fastest-web-framework-written-with-on-golang-497)服务器端编程 - [快速](https://dev.to/koddr/welcome-to-fiber-an-express-js-styled-fastest-web-framework-written-with-on-golang-497)服务器端编程
- [模版引擎](https://github.com/gofiber/template) - [模版引擎](https://github.com/gofiber/template)
- [WebSocket支持](https://docs.gofiber.io/middleware#websocket) - [WebSocket支持](https://docs.gofiber.io/middleware#websocket)
- [频率限制器](https://docs.gofiber.io/middleware#limiter) - [频率限制器](https://docs.gofiber.io/middleware#limiter)
- [15语言](https://docs.gofiber.io/) - [15语言](https://docs.gofiber.io/)
- 以及更多请[探索文档](https://docs.gofiber.io/) - 以及更多请[探索文档](https://docs.gofiber.io/)
## 💡 哲学 ## 💡 哲学
从[Node.js](https://nodejs.org/en/about/)切换到[Go](https://golang.org/doc/)的新`gopher`在开始构建`Web`应用程序或微服务之前正在应对学习曲线。 `Fiber`作为一个**Web框架** ,是按照**极简主义**的思想并遵循**UNIX方式**创建的,因此新的`gopher`可以在热烈和可信赖的欢迎中迅速进入`Go`的世界。 从[Node.js](https://nodejs.org/en/about/)切换到[Go](https://golang.org/doc/)的新`gopher`在开始构建`Web`应用程序或微服务之前正在应对学习曲线。 `Fiber`作为一个**Web框架** ,是按照**极简主义**的思想并遵循**UNIX方式**创建的,因此新的`gopher`可以在热烈和可信赖的欢迎中迅速进入`Go`的世界。
`Fiber`受到了互联网上最流行的`Web`框架`Express`的**启发** 。我们结合了`Express`的**易用性**和`Go`的**原始性能** 。如果您曾经在`Node.js`上实现过`Web`应用程序(*使用Express.js或类似工具*),那么许多方法和原理对您来说应该**非常易懂**。 `Fiber`受到了互联网上最流行的`Web`框架`Express`的**启发** 。我们结合了`Express`的**易用性**和`Go`的**原始性能** 。如果您曾经在`Node.js`上实现过`Web`应用程序(*使用Express或类似工具*),那么许多方法和原理对您来说应该**非常易懂**。
我们**关注** _整个互联网_ 用户在[issues](https://github.com/gofiber/fiber/issues)和Discord [channel](https://gofiber.io/discord)的消息,为了创建一个**迅速****灵活**以及**友好**的`Go web`框架,满足**任何**任务,**最后期限**和开发者**技能**。就像`Express`在`JavaScript`世界中一样。 我们**关注** _整个互联网_ 用户在[issues](https://github.com/gofiber/fiber/issues)和Discord [channel](https://gofiber.io/discord)的消息,为了创建一个**迅速****灵活**以及**友好**的`Go web`框架,满足**任何**任务,**最后期限**和开发者**技能**。就像`Express`在`JavaScript`世界中一样。
@ -243,9 +243,9 @@ func main() {
如果未设置模版引擎,则`Fiber`默认使用[html/template](https://golang.org/pkg/html/template/)。 如果未设置模版引擎,则`Fiber`默认使用[html/template](https://golang.org/pkg/html/template/)。
如果您要执行部分模版或使用其他引擎,例如[amber](https://github.com/eknkc/amber)[handlebars](https://github.com/aymerick/raymond)[mustache](https://github.com/cbroglie/mustache)或者[pug](https://github.com/Joker/jade)等等 如果您要执行部分模版或使用其他引擎,例如[amber](https://github.com/eknkc/amber)[handlebars](https://github.com/aymerick/raymond)[mustache](https://github.com/cbroglie/mustache)或者[pug](https://github.com/Joker/jade)等等...
查看我们的[Template](https://github.com/gofiber/template)包,该包支持多个模版引擎。 查看我们的[Template](https://github.com/gofiber/template)包,该包支持多个模版引擎。
```go ```go
import ( import (
@ -299,7 +299,7 @@ func main() {
} }
``` ```
### 访问日志中间件 ### 日志中间件
📖 [Logger](https://github.com/gofiber/fiber/blob/master/middleware/logger.md) 📖 [Logger](https://github.com/gofiber/fiber/blob/master/middleware/logger.md)
@ -330,7 +330,7 @@ func main() {
app.Listen(3000) app.Listen(3000)
} }
``` ```
### 跨域资源共享(CORS) ### 跨域资源共享(CORS)中间件
📖 [CORS](https://docs.gofiber.io/middleware#cors) 📖 [CORS](https://docs.gofiber.io/middleware#cors)
@ -356,7 +356,7 @@ func main() {
curl -H "Origin: http://example.com" --verbose http://localhost:3000 curl -H "Origin: http://example.com" --verbose http://localhost:3000
``` ```
### 自定义404 ### 自定义404
📖 [HTTP Methods](https://docs.gofiber.io/application#http-methods) 📖 [HTTP Methods](https://docs.gofiber.io/application#http-methods)
@ -414,7 +414,7 @@ func main() {
} }
``` ```
### WebSocket支持 ### 升级到WebSocket
📖 [Websocket](https://docs.gofiber.io/middleware#websocket) 📖 [Websocket](https://docs.gofiber.io/middleware#websocket)
@ -500,7 +500,7 @@ func main() {
## 🌱 第三方中间件 ## 🌱 第三方中间件
这是由`Fiber`社区创建的中间件列表,如果您想看到自己的中间件,请创建`PR`。 这是由`Fiber`社区创建的中间件列表,如果您想看到自己的中间件,请创建`PR`。
- [arsmn/fiber-swagger](https://github.com/arsmn/fiber-swagger) - [arsmn/fiber-swagger](https://github.com/arsmn/fiber-swagger)
- [arsmn/fiber-casbin](https://github.com/arsmn/fiber-casbin) - [arsmn/fiber-casbin](https://github.com/arsmn/fiber-casbin)
- [arsmn/fiber-introspect](https://github.com/arsmn/fiber-introspect) - [arsmn/fiber-introspect](https://github.com/arsmn/fiber-introspect)

5
app.go
View File

@ -425,6 +425,7 @@ func (app *App) Routes() []*Route {
for i := range routes { for i := range routes {
if routes[i].Method == methodUse && routes[i].Name == app.stack[m][r].Name { if routes[i].Method == methodUse && routes[i].Name == app.stack[m][r].Name {
duplicate = true duplicate = true
break
} }
} }
if !duplicate { if !duplicate {
@ -684,7 +685,7 @@ func (app *App) startupMessage(addr string, tls bool, pids string) {
osName = utils.ToUpper(runtime.GOOS) osName = utils.ToUpper(runtime.GOOS)
memTotal = utils.ByteSize(utils.MemoryTotal()) memTotal = utils.ByteSize(utils.MemoryTotal())
cpuCores = runtime.NumCPU() cpuCores = runtime.NumCPU()
ppid = os.Getppid() pid = os.Getpid()
) )
if host == "" { if host == "" {
host = "0.0.0.0" host = "0.0.0.0"
@ -711,7 +712,7 @@ func (app *App) startupMessage(addr string, tls bool, pids string) {
cCyan, cBlack, fmt.Sprintf(" HOST %s\tOS %s", cyan(host), cyan(osName)), cCyan, cBlack, fmt.Sprintf(" HOST %s\tOS %s", cyan(host), cyan(osName)),
cCyan, cBlack, fmt.Sprintf(" PORT %s\tCORES %s", cyan(port), cyan(cpuCores)), cCyan, cBlack, fmt.Sprintf(" PORT %s\tCORES %s", cyan(port), cyan(cpuCores)),
cCyan, cBlack, fmt.Sprintf(" TLS %s\tMEM %s", cyan(tlsStr), cyan(memTotal)), cCyan, cBlack, fmt.Sprintf(" TLS %s\tMEM %s", cyan(tlsStr), cyan(memTotal)),
cBlack, cyan(Version), fmt.Sprintf(" ROUTES %s\t\t\t PPID %s%s%s\n", cyan(routesLen), cyan(ppid), pids, cReset), cBlack, cyan(Version), fmt.Sprintf(" ROUTES %s\t\t\t PID %s%s%s\n", cyan(routesLen), cyan(pid), pids, cReset),
) )
// Write to io.write // Write to io.write
_ = out.Flush() _ = out.Flush()

View File

@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/http/httptest" "net/http/httptest"
"regexp" "regexp"
"strings" "strings"
@ -17,6 +16,7 @@ import (
utils "github.com/gofiber/utils" utils "github.com/gofiber/utils"
fasthttp "github.com/valyala/fasthttp" fasthttp "github.com/valyala/fasthttp"
fasthttputil "github.com/valyala/fasthttp/fasthttputil"
) )
func testStatus200(t *testing.T, app *App, url string, method string) { func testStatus200(t *testing.T, app *App, url string, method string) {
@ -97,10 +97,11 @@ func Test_App_Custom_Middleware_404_Should_Not_SetMethodNotAllowed(t *testing.T)
func Test_App_Routes(t *testing.T) { func Test_App_Routes(t *testing.T) {
app := New() app := New()
h := func(c *Ctx) {} h := func(c *Ctx) {}
app.Use("/", h)
app.Get("/Get", h) app.Get("/Get", h)
app.Head("/Head", h) app.Head("/Head", h)
app.Post("/post", h) app.Post("/post", h)
utils.AssertEqual(t, 3, len(app.Routes())) utils.AssertEqual(t, 4, len(app.Routes()))
} }
func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) { func Test_App_ServerErrorHandler_SmallReadBuffer(t *testing.T) {
@ -360,18 +361,28 @@ func Test_App_Static_Index_Default(t *testing.T) {
app := New() app := New()
app.Static("/prefix", "./.github/workflows") app.Static("/prefix", "./.github/workflows")
app.Static("/", "./.github") app.Static("", "./.github/")
app.Static("test", "", Static{Index: "index.html"})
resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) resp, err := app.Test(httptest.NewRequest("GET", "/", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)") utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code") utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
utils.AssertEqual(t, false, resp.Header.Get("Content-Length") == "") utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "")
utils.AssertEqual(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type")) utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err) utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!")) utils.AssertEqual(t, true, strings.Contains(string(body), "Hello, World!"))
resp, err = app.Test(httptest.NewRequest("GET", "/not-found", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, 404, resp.StatusCode, "Status code")
utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "")
utils.AssertEqual(t, MIMETextPlainCharsetUTF8, resp.Header.Get(HeaderContentType))
body, err = ioutil.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, "Cannot GET /not-found", string(body))
} }
// go test -run Test_App_Static_Index // go test -run Test_App_Static_Index
@ -649,14 +660,14 @@ func Test_App_Listen(t *testing.T) {
utils.AssertEqual(t, nil, app.Shutdown()) utils.AssertEqual(t, nil, app.Shutdown())
}() }()
utils.AssertEqual(t, nil, app.Listen(4003)) utils.AssertEqual(t, nil, app.Listen("127.0.0.1:"))
go func() { go func() {
time.Sleep(1000 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown()) utils.AssertEqual(t, nil, app.Shutdown())
}() }()
utils.AssertEqual(t, nil, app.Listen("4010")) utils.AssertEqual(t, nil, app.Listen("127.0.0.1:"))
} }
// go test -run Test_App_Listener // go test -run Test_App_Listener
@ -665,14 +676,13 @@ func Test_App_Listener(t *testing.T) {
DisableStartupMessage: true, DisableStartupMessage: true,
Prefork: true, Prefork: true,
}) })
ln, err := net.Listen("tcp4", ":4020")
utils.AssertEqual(t, nil, err)
go func() { go func() {
time.Sleep(1000 * time.Millisecond) time.Sleep(500 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown()) utils.AssertEqual(t, nil, app.Shutdown())
}() }()
ln := fasthttputil.NewInmemoryListener()
utils.AssertEqual(t, nil, app.Listener(ln)) utils.AssertEqual(t, nil, app.Listener(ln))
} }
@ -699,3 +709,10 @@ func Benchmark_App_ETag_Weak(b *testing.B) {
} }
utils.AssertEqual(b, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag))) utils.AssertEqual(b, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
} }
// go test -run Test_NewError
func Test_NewError(t *testing.T) {
e := NewError(StatusForbidden, "permission denied")
utils.AssertEqual(t, StatusForbidden, e.Code)
utils.AssertEqual(t, "permission denied", e.Message)
}

40
ctx.go
View File

@ -181,8 +181,8 @@ func (ctx *Ctx) Append(field string, values ...string) {
for _, value := range values { for _, value := range values {
if len(h) == 0 { if len(h) == 0 {
h = value h = value
} else if h != value && !strings.HasSuffix(h, " "+value) && } else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) &&
!strings.Contains(h, value+",") { !strings.Contains(h, " "+value+",") {
h += ", " + value h += ", " + value
} }
} }
@ -388,7 +388,7 @@ func (ctx *Ctx) Format(body interface{}) {
ctx.Send(body) // Fallback ctx.Send(body) // Fallback
log.Println("Format: error serializing json ", err) log.Println("Format: error serializing json ", err)
} }
case "text": case "txt":
ctx.SendString(b) ctx.SendString(b)
case "xml": case "xml":
raw, err := xml.Marshal(body) raw, err := xml.Marshal(body)
@ -415,7 +415,7 @@ func (ctx *Ctx) FormValue(key string) (value string) {
return getString(ctx.Fasthttp.FormValue(key)) return getString(ctx.Fasthttp.FormValue(key))
} }
var cacheControlNoCacheRegexp, _ = regexp.Compile(`/(?:^|,)\s*?no-cache\s*?(?:,|$)/`) var cacheControlNoCacheRegexp, _ = regexp.Compile(`(?:^|,)\s*?no-cache\s*?(?:,|$)`)
// Fresh returns true when the response is still “fresh” in the client's cache, // Fresh returns true when the response is still “fresh” in the client's cache,
// otherwise false is returned to indicate that the client cache is now stale // otherwise false is returned to indicate that the client cache is now stale
@ -436,7 +436,7 @@ func (ctx *Ctx) Fresh() bool {
// Always return stale when Cache-Control: no-cache // Always return stale when Cache-Control: no-cache
// to support end-to-end reload requests // to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4 // https://tools.ietf.org/html/rfc2616#section-14.9.4
var cacheControl = ctx.Get(HeaderCacheControl) cacheControl := ctx.Get(HeaderCacheControl)
if cacheControl != "" && cacheControlNoCacheRegexp.MatchString(cacheControl) { if cacheControl != "" && cacheControlNoCacheRegexp.MatchString(cacheControl) {
return false return false
} }
@ -447,16 +447,15 @@ func (ctx *Ctx) Fresh() bool {
if etag == "" { if etag == "" {
return false return false
} }
var etagStal = true var etagStale = true
var matches = parseTokenList(getBytes(noneMatch)) var matches = parseTokenList(getBytes(noneMatch))
for i := range matches { for _, match := range matches {
match := matches[i]
if match == etag || match == "W/"+etag || "W/"+match == etag { if match == etag || match == "W/"+etag || "W/"+match == etag {
etagStal = false etagStale = false
break break
} }
} }
if etagStal { if etagStale {
return false return false
} }
@ -716,19 +715,30 @@ func (ctx *Ctx) Query(key string, defaultValue ...string) string {
return defaultString(getString(ctx.Fasthttp.QueryArgs().Peek(key)), defaultValue) return defaultString(getString(ctx.Fasthttp.QueryArgs().Peek(key)), defaultValue)
} }
var (
ErrRangeMalformed = errors.New("range: malformed range header string")
ErrRangeUnsatisfiable = errors.New("range: unsatisfiable range")
)
// Range returns a struct containing the type and a slice of ranges. // Range returns a struct containing the type and a slice of ranges.
func (ctx *Ctx) Range(size int) (rangeData Range, err error) { func (ctx *Ctx) Range(size int) (rangeData Range, err error) {
rangeStr := ctx.Get(HeaderRange) rangeStr := ctx.Get(HeaderRange)
if rangeStr == "" || !strings.Contains(rangeStr, "=") { if rangeStr == "" || !strings.Contains(rangeStr, "=") {
return rangeData, fmt.Errorf("range: malformed range header string") err = ErrRangeMalformed
return
} }
data := strings.Split(rangeStr, "=") data := strings.Split(rangeStr, "=")
if len(data) != 2 {
err = ErrRangeMalformed
return
}
rangeData.Type = data[0] rangeData.Type = data[0]
arr := strings.Split(data[1], ",") arr := strings.Split(data[1], ",")
for i := 0; i < len(arr); i++ { for i := 0; i < len(arr); i++ {
item := strings.Split(arr[i], "-") item := strings.Split(arr[i], "-")
if len(item) == 1 { if len(item) == 1 {
return rangeData, fmt.Errorf("range: malformed range header string") err = ErrRangeMalformed
return
} }
start, startErr := strconv.Atoi(item[0]) start, startErr := strconv.Atoi(item[0])
end, endErr := strconv.Atoi(item[1]) end, endErr := strconv.Atoi(item[1])
@ -753,9 +763,11 @@ func (ctx *Ctx) Range(size int) (rangeData Range, err error) {
}) })
} }
if len(rangeData.Ranges) < 1 { if len(rangeData.Ranges) < 1 {
return rangeData, fmt.Errorf("range: unsatisfiable range") err = ErrRangeUnsatisfiable
return
} }
return rangeData, nil
return
} }
// Redirect to the URL derived from the specified path, with specified status. // Redirect to the URL derived from the specified path, with specified status.

View File

@ -172,8 +172,30 @@ func Test_Ctx_Append(t *testing.T) {
ctx.Append("X-Test", "Hello") ctx.Append("X-Test", "Hello")
ctx.Append("X-Test", "World") ctx.Append("X-Test", "World")
ctx.Append("X-Test", "Hello", "World") ctx.Append("X-Test", "Hello", "World")
// similar value in the middle
ctx.Append("X2-Test", "World")
ctx.Append("X2-Test", "XHello")
ctx.Append("X2-Test", "Hello", "World")
// similar value at the start
ctx.Append("X3-Test", "XHello")
ctx.Append("X3-Test", "World")
ctx.Append("X3-Test", "Hello", "World")
// try it with multiple similar values
ctx.Append("X4-Test", "XHello")
ctx.Append("X4-Test", "Hello")
ctx.Append("X4-Test", "HelloZ")
ctx.Append("X4-Test", "YHello")
ctx.Append("X4-Test", "Hello")
ctx.Append("X4-Test", "YHello")
ctx.Append("X4-Test", "HelloZ")
ctx.Append("X4-Test", "XHello")
// without append value
ctx.Append("X-Custom-Header") ctx.Append("X-Custom-Header")
utils.AssertEqual(t, "Hello, World", string(ctx.Fasthttp.Response.Header.Peek("X-Test"))) utils.AssertEqual(t, "Hello, World", string(ctx.Fasthttp.Response.Header.Peek("X-Test")))
utils.AssertEqual(t, "World, XHello, Hello", string(ctx.Fasthttp.Response.Header.Peek("X2-Test")))
utils.AssertEqual(t, "XHello, World, Hello", string(ctx.Fasthttp.Response.Header.Peek("X3-Test")))
utils.AssertEqual(t, "XHello, Hello, HelloZ, YHello", string(ctx.Fasthttp.Response.Header.Peek("X4-Test")))
utils.AssertEqual(t, "", string(ctx.Fasthttp.Response.Header.Peek("x-custom-header"))) utils.AssertEqual(t, "", string(ctx.Fasthttp.Response.Header.Peek("x-custom-header")))
} }
@ -356,24 +378,29 @@ func Test_Ctx_Format(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "plain/text") ctx.Fasthttp.Request.Header.Set(HeaderAccept, MIMETextPlain)
ctx.Format([]byte("Hello, World!")) ctx.Format([]byte("Hello, World!"))
utils.AssertEqual(t, "Hello, World!", string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, "Hello, World!", string(ctx.Fasthttp.Response.Body()))
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "text/html") ctx.Fasthttp.Request.Header.Set(HeaderAccept, MIMETextHTML)
ctx.Format("Hello, World!") ctx.Format("Hello, World!")
utils.AssertEqual(t, "<p>Hello, World!</p>", string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, "<p>Hello, World!</p>", string(ctx.Fasthttp.Response.Body()))
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "application/json") ctx.Fasthttp.Request.Header.Set(HeaderAccept, MIMEApplicationJSON)
ctx.Format("Hello, World!") ctx.Format("Hello, World!")
utils.AssertEqual(t, `"Hello, World!"`, string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, `"Hello, World!"`, string(ctx.Fasthttp.Response.Body()))
ctx.Format(complex(1, 1))
utils.AssertEqual(t, "(1+1i)", string(ctx.Fasthttp.Response.Body()))
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "application/xml") ctx.Fasthttp.Request.Header.Set(HeaderAccept, MIMEApplicationXML)
ctx.Format("Hello, World!") ctx.Format("Hello, World!")
utils.AssertEqual(t, `<string>Hello, World!</string>`, string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, `<string>Hello, World!</string>`, string(ctx.Fasthttp.Response.Body()))
ctx.Format(Map{})
utils.AssertEqual(t, "map[]", string(ctx.Fasthttp.Response.Body()))
type broken string
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "broken/accept") ctx.Fasthttp.Request.Header.Set(HeaderAccept, "broken/accept")
ctx.Format("Hello, World!") ctx.Format(broken("Hello, World!"))
utils.AssertEqual(t, `Hello, World!`, string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, `Hello, World!`, string(ctx.Fasthttp.Response.Body()))
} }
@ -506,6 +533,31 @@ func Test_Ctx_Fresh(t *testing.T) {
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
utils.AssertEqual(t, false, ctx.Fresh()) utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, "*")
ctx.Fasthttp.Request.Header.Set(HeaderCacheControl, "no-cache")
utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, "675af34563dc-tr34")
ctx.Fasthttp.Request.Header.Set(HeaderCacheControl, "public")
utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, "a, b")
ctx.Fasthttp.Response.Header.Set(HeaderETag, "c")
utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Response.Header.Set(HeaderETag, "a")
utils.AssertEqual(t, true, ctx.Fresh())
ctx.Fasthttp.Request.Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT")
ctx.Fasthttp.Response.Header.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT")
utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Response.Header.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
utils.AssertEqual(t, false, ctx.Fresh())
ctx.Fasthttp.Request.Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT")
utils.AssertEqual(t, false, ctx.Fresh())
} }
// go test -run Test_Ctx_Get // go test -run Test_Ctx_Get
@ -783,12 +835,40 @@ func Test_Ctx_Range(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
ctx.Fasthttp.Request.Header.Set(HeaderRange, "bytes=500-700")
result, err := ctx.Range(1000) var (
utils.AssertEqual(t, nil, err) result Range
utils.AssertEqual(t, "bytes", result.Type) err error
utils.AssertEqual(t, 500, result.Ranges[0].Start) )
utils.AssertEqual(t, 700, result.Ranges[0].End)
result, err = ctx.Range(1000)
utils.AssertEqual(t, true, err != nil)
ctx.Fasthttp.Request.Header.Set(HeaderRange, "bytes=500")
result, err = ctx.Range(1000)
utils.AssertEqual(t, true, err != nil)
ctx.Fasthttp.Request.Header.Set(HeaderRange, "bytes=500=")
result, err = ctx.Range(1000)
utils.AssertEqual(t, true, err != nil)
ctx.Fasthttp.Request.Header.Set(HeaderRange, "bytes=500-300")
result, err = ctx.Range(1000)
utils.AssertEqual(t, true, err != nil)
testRange := func(header string, start, end int) {
ctx.Fasthttp.Request.Header.Set(HeaderRange, header)
result, err = ctx.Range(1000)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, "bytes", result.Type)
utils.AssertEqual(t, start, result.Ranges[0].Start)
utils.AssertEqual(t, end, result.Ranges[0].End)
}
testRange("bytes=a-700", 300, 999)
testRange("bytes=500-b", 500, 999)
testRange("bytes=500-1000", 500, 999)
testRange("bytes=500-700", 500, 700)
} }
// go test -run Test_Ctx_Route // go test -run Test_Ctx_Route
@ -801,6 +881,13 @@ func Test_Ctx_Route(t *testing.T) {
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil)) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)") utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code") utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
utils.AssertEqual(t, "/", ctx.Route().Path)
utils.AssertEqual(t, MethodGet, ctx.Route().Method)
utils.AssertEqual(t, 0, len(ctx.Route().Handlers))
} }
// go test -run Test_Ctx_RouteNormalized // go test -run Test_Ctx_RouteNormalized
@ -1011,6 +1098,9 @@ func Test_Ctx_JSON(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
utils.AssertEqual(t, true, ctx.JSON(complex(1, 1)) != nil)
ctx.JSON(Map{ // map has no order ctx.JSON(Map{ // map has no order
"Name": "Grame", "Name": "Grame",
"Age": 20, "Age": 20,
@ -1059,7 +1149,17 @@ func Test_Ctx_JSONP(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
ctx.JSONP(Map{ // map has no order
utils.AssertEqual(t, true, ctx.JSONP(complex(1, 1)) != nil)
ctx.JSONP(Map{
"Name": "Grame",
"Age": 20,
})
utils.AssertEqual(t, `callback({"Age":20,"Name":"Grame"});`, string(ctx.Fasthttp.Response.Body()))
utils.AssertEqual(t, "application/javascript; charset=utf-8", string(ctx.Fasthttp.Response.Header.Peek("content-type")))
ctx.JSONP(Map{
"Name": "Grame", "Name": "Grame",
"Age": 20, "Age": 20,
}, "john") }, "john")
@ -1097,6 +1197,10 @@ func Test_Ctx_Links(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
ctx.Links()
utils.AssertEqual(t, "", string(ctx.Fasthttp.Response.Header.Peek(HeaderLink)))
ctx.Links( ctx.Links(
"http://api.example.com/users?page=2", "next", "http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last", "http://api.example.com/users?page=5", "last",
@ -1164,6 +1268,11 @@ func Test_Ctx_Redirect(t *testing.T) {
app := New() app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
ctx.Redirect("http://default.com")
utils.AssertEqual(t, 302, ctx.Fasthttp.Response.StatusCode())
utils.AssertEqual(t, "http://default.com", string(ctx.Fasthttp.Response.Header.Peek(HeaderLocation)))
ctx.Redirect("http://example.com", 301) ctx.Redirect("http://example.com", 301)
utils.AssertEqual(t, 301, ctx.Fasthttp.Response.StatusCode()) utils.AssertEqual(t, 301, ctx.Fasthttp.Response.StatusCode())
utils.AssertEqual(t, "http://example.com", string(ctx.Fasthttp.Response.Header.Peek(HeaderLocation))) utils.AssertEqual(t, "http://example.com", string(ctx.Fasthttp.Response.Header.Peek(HeaderLocation)))
@ -1420,6 +1529,7 @@ func Test_Ctx_Write(t *testing.T) {
ctx.Write("Hello, ") ctx.Write("Hello, ")
ctx.Write([]byte("World! ")) ctx.Write([]byte("World! "))
ctx.Write(123) ctx.Write(123)
ctx.Write(123.321)
ctx.Write(true) ctx.Write(true)
ctx.Write(bytes.NewReader([]byte("Don't crash please"))) ctx.Write(bytes.NewReader([]byte("Don't crash please")))
utils.AssertEqual(t, "Don't crash please", string(ctx.Fasthttp.Response.Body())) utils.AssertEqual(t, "Don't crash please", string(ctx.Fasthttp.Response.Body()))
@ -1553,6 +1663,11 @@ func Test_Ctx_QueryParser(t *testing.T) {
q := new(Query) q := new(Query)
utils.AssertEqual(t, nil, ctx.QueryParser(q)) utils.AssertEqual(t, nil, ctx.QueryParser(q))
utils.AssertEqual(t, 2, len(q.Hobby)) utils.AssertEqual(t, 2, len(q.Hobby))
empty := new(Query)
ctx.Fasthttp.Request.URI().SetQueryString("")
utils.AssertEqual(t, nil, ctx.QueryParser(empty))
utils.AssertEqual(t, 0, len(empty.Hobby))
} }
// go test -v -run=^$ -bench=Benchmark_Ctx_QueryParser -benchmem -count=4 // go test -v -run=^$ -bench=Benchmark_Ctx_QueryParser -benchmem -count=4

View File

@ -82,6 +82,11 @@ func Test_Middleware_RequestID_Options_And_WithConfig(t *testing.T) {
header: RequestIDConfigDefault.Header, header: RequestIDConfigDefault.Header,
handler: RequestID(func() string { return "fake-id" }), handler: RequestID(func() string { return "fake-id" }),
}, },
{
idLen: UUIDLen,
header: RequestIDConfigDefault.Header,
handler: RequestID(RequestIDConfig{}),
},
} }
for _, testCase := range testCases { for _, testCase := range testCases {

View File

@ -114,7 +114,7 @@ func (app *App) next(ctx *Ctx) bool {
return true return true
} }
// If c.Next() does not match, return 404 // If c.Next() does not match, return 404
ctx.SendStatus(404) ctx.SendStatus(StatusNotFound)
ctx.SendString("Cannot " + ctx.method + " " + ctx.pathOriginal) ctx.SendString("Cannot " + ctx.method + " " + ctx.pathOriginal)
// Scan stack for other methods // Scan stack for other methods
@ -279,7 +279,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) *Route {
return path return path
}, },
PathNotFound: func(ctx *fasthttp.RequestCtx) { PathNotFound: func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetStatusCode(404) ctx.Response.SetStatusCode(StatusNotFound)
}, },
} }
// Set config if provided // Set config if provided
@ -297,12 +297,12 @@ func (app *App) registerStatic(prefix, root string, config ...Static) *Route {
fileHandler(c.Fasthttp) fileHandler(c.Fasthttp)
// Return request if found and not forbidden // Return request if found and not forbidden
status := c.Fasthttp.Response.StatusCode() status := c.Fasthttp.Response.StatusCode()
if status != 404 && status != 403 { if status != StatusNotFound && status != StatusForbidden {
return return
} }
// Reset response to default // Reset response to default
c.Fasthttp.SetContentType("") // Issue #420 c.Fasthttp.SetContentType("") // Issue #420
c.Fasthttp.Response.SetStatusCode(200) c.Fasthttp.Response.SetStatusCode(StatusOK)
c.Fasthttp.Response.SetBodyString("") c.Fasthttp.Response.SetBodyString("")
// Next middleware // Next middleware
c.Next() c.Next()

View File

@ -17,6 +17,7 @@ import (
fasthttp "github.com/valyala/fasthttp" fasthttp "github.com/valyala/fasthttp"
) )
// quoteString escape special characters in a given string
func quoteString(raw string) string { func quoteString(raw string) string {
bb := bytebufferpool.Get() bb := bytebufferpool.Get()
quoted := string(fasthttp.AppendQuotedArg(bb.B, getBytes(raw))) quoted := string(fasthttp.AppendQuotedArg(bb.B, getBytes(raw)))
@ -76,7 +77,7 @@ func defaultString(value string, defaultValue []string) string {
// Generate and set ETag header to response // Generate and set ETag header to response
func setETag(ctx *Ctx, weak bool) { func setETag(ctx *Ctx, weak bool) {
// Don't generate ETags for invalid responses // Don't generate ETags for invalid responses
if ctx.Fasthttp.Response.StatusCode() != 200 { if ctx.Fasthttp.Response.StatusCode() != StatusOK {
return return
} }
body := ctx.Fasthttp.Response.Body() body := ctx.Fasthttp.Response.Body()
@ -101,7 +102,7 @@ func setETag(ctx *Ctx, weak bool) {
// Check if server's ETag is weak // Check if server's ETag is weak
if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] { if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] {
// W/1 == 1 || W/1 == W/1 // W/1 == 1 || W/1 == W/1
ctx.SendStatus(304) ctx.SendStatus(StatusNotModified)
ctx.Fasthttp.ResetBody() ctx.Fasthttp.ResetBody()
return return
} }
@ -111,7 +112,7 @@ func setETag(ctx *Ctx, weak bool) {
} }
if strings.Contains(clientEtag, etag) { if strings.Contains(clientEtag, etag) {
// 1 == 1 // 1 == 1
ctx.SendStatus(304) ctx.SendStatus(StatusNotModified)
ctx.Fasthttp.ResetBody() ctx.Fasthttp.ResetBody()
return return
} }

View File

@ -15,11 +15,40 @@ import (
func Test_Utils_ETag(t *testing.T) { func Test_Utils_ETag(t *testing.T) {
app := New() app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}) t.Run("Not Status OK", func(t *testing.T) {
defer app.ReleaseCtx(c) c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Send("Hello, World!") defer app.ReleaseCtx(c)
setETag(c, false) c.Send("Hello, World!")
utils.AssertEqual(t, `"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag))) c.Status(201)
setETag(c, false)
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
})
t.Run("No Body", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
setETag(c, false)
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
})
t.Run("Has HeaderIfNoneMatch", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
c.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, `"13-1831710635"`)
setETag(c, false)
utils.AssertEqual(t, 304, c.Fasthttp.Response.StatusCode())
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Body()))
})
t.Run("No HeaderIfNoneMatch", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
setETag(c, false)
utils.AssertEqual(t, `"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
})
} }
// go test -v -run=^$ -bench=Benchmark_App_ETag -benchmem -count=4 // go test -v -run=^$ -bench=Benchmark_App_ETag -benchmem -count=4
@ -36,11 +65,33 @@ func Benchmark_Utils_ETag(b *testing.B) {
func Test_Utils_ETag_Weak(t *testing.T) { func Test_Utils_ETag_Weak(t *testing.T) {
app := New() app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}) t.Run("Set Weak", func(t *testing.T) {
defer app.ReleaseCtx(c) c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Send("Hello, World!") defer app.ReleaseCtx(c)
setETag(c, true) c.Send("Hello, World!")
utils.AssertEqual(t, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag))) setETag(c, true)
utils.AssertEqual(t, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
})
t.Run("Match Weak ETag", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
c.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, `W/"13-1831710635"`)
setETag(c, true)
utils.AssertEqual(t, 304, c.Fasthttp.Response.StatusCode())
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
utils.AssertEqual(t, "", string(c.Fasthttp.Response.Body()))
})
t.Run("Not Match Weak ETag", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
c.Fasthttp.Request.Header.Set(HeaderIfNoneMatch, `W/"13-1831710635xx"`)
setETag(c, true)
utils.AssertEqual(t, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
})
} }
// go test -v -run=^$ -bench=Benchmark_App_ETag_Weak -benchmem -count=4 // go test -v -run=^$ -bench=Benchmark_App_ETag_Weak -benchmem -count=4