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)
- [API接口](https://docs.gofiber.io/context)
- [中间件](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)
- [WebSocket支持](https://docs.gofiber.io/middleware#websocket)
- [频率限制器](https://docs.gofiber.io/middleware#limiter)
- [15语言](https://docs.gofiber.io/)
- [15语言](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`的世界。
`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`世界中一样。
@ -243,9 +243,9 @@ func main() {
如果未设置模版引擎,则`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
import (
@ -299,7 +299,7 @@ func main() {
}
```
### 访问日志中间件
### 日志中间件
📖 [Logger](https://github.com/gofiber/fiber/blob/master/middleware/logger.md)
@ -330,7 +330,7 @@ func main() {
app.Listen(3000)
}
```
### 跨域资源共享(CORS)
### 跨域资源共享(CORS)中间件
📖 [CORS](https://docs.gofiber.io/middleware#cors)
@ -356,7 +356,7 @@ func main() {
curl -H "Origin: http://example.com" --verbose http://localhost:3000
```
### 自定义404
### 自定义404
📖 [HTTP Methods](https://docs.gofiber.io/application#http-methods)
@ -414,7 +414,7 @@ func main() {
}
```
### WebSocket支持
### 升级到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-casbin](https://github.com/arsmn/fiber-casbin)
- [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 {
if routes[i].Method == methodUse && routes[i].Name == app.stack[m][r].Name {
duplicate = true
break
}
}
if !duplicate {
@ -684,7 +685,7 @@ func (app *App) startupMessage(addr string, tls bool, pids string) {
osName = utils.ToUpper(runtime.GOOS)
memTotal = utils.ByteSize(utils.MemoryTotal())
cpuCores = runtime.NumCPU()
ppid = os.Getppid()
pid = os.Getpid()
)
if host == "" {
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(" PORT %s\tCORES %s", cyan(port), cyan(cpuCores)),
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
_ = out.Flush()

View File

@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http/httptest"
"regexp"
"strings"
@ -17,6 +16,7 @@ import (
utils "github.com/gofiber/utils"
fasthttp "github.com/valyala/fasthttp"
fasthttputil "github.com/valyala/fasthttp/fasthttputil"
)
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) {
app := New()
h := func(c *Ctx) {}
app.Use("/", h)
app.Get("/Get", h)
app.Head("/Head", 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) {
@ -360,18 +361,28 @@ func Test_App_Static_Index_Default(t *testing.T) {
app := New()
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))
utils.AssertEqual(t, nil, err, "app.Test(req)")
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
utils.AssertEqual(t, false, resp.Header.Get("Content-Length") == "")
utils.AssertEqual(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
utils.AssertEqual(t, false, resp.Header.Get(HeaderContentLength) == "")
utils.AssertEqual(t, MIMETextHTMLCharsetUTF8, resp.Header.Get(HeaderContentType))
body, err := ioutil.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
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
@ -649,14 +660,14 @@ func Test_App_Listen(t *testing.T) {
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() {
time.Sleep(1000 * time.Millisecond)
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
@ -665,14 +676,13 @@ func Test_App_Listener(t *testing.T) {
DisableStartupMessage: true,
Prefork: true,
})
ln, err := net.Listen("tcp4", ":4020")
utils.AssertEqual(t, nil, err)
go func() {
time.Sleep(1000 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
ln := fasthttputil.NewInmemoryListener()
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)))
}
// 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 {
if len(h) == 0 {
h = value
} else if h != value && !strings.HasSuffix(h, " "+value) &&
!strings.Contains(h, value+",") {
} else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) &&
!strings.Contains(h, " "+value+",") {
h += ", " + value
}
}
@ -388,7 +388,7 @@ func (ctx *Ctx) Format(body interface{}) {
ctx.Send(body) // Fallback
log.Println("Format: error serializing json ", err)
}
case "text":
case "txt":
ctx.SendString(b)
case "xml":
raw, err := xml.Marshal(body)
@ -415,7 +415,7 @@ func (ctx *Ctx) FormValue(key string) (value string) {
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,
// 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
// to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4
var cacheControl = ctx.Get(HeaderCacheControl)
cacheControl := ctx.Get(HeaderCacheControl)
if cacheControl != "" && cacheControlNoCacheRegexp.MatchString(cacheControl) {
return false
}
@ -447,16 +447,15 @@ func (ctx *Ctx) Fresh() bool {
if etag == "" {
return false
}
var etagStal = true
var etagStale = true
var matches = parseTokenList(getBytes(noneMatch))
for i := range matches {
match := matches[i]
for _, match := range matches {
if match == etag || match == "W/"+etag || "W/"+match == etag {
etagStal = false
etagStale = false
break
}
}
if etagStal {
if etagStale {
return false
}
@ -716,19 +715,30 @@ func (ctx *Ctx) Query(key string, defaultValue ...string) string {
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.
func (ctx *Ctx) Range(size int) (rangeData Range, err error) {
rangeStr := ctx.Get(HeaderRange)
if rangeStr == "" || !strings.Contains(rangeStr, "=") {
return rangeData, fmt.Errorf("range: malformed range header string")
err = ErrRangeMalformed
return
}
data := strings.Split(rangeStr, "=")
if len(data) != 2 {
err = ErrRangeMalformed
return
}
rangeData.Type = data[0]
arr := strings.Split(data[1], ",")
for i := 0; i < len(arr); i++ {
item := strings.Split(arr[i], "-")
if len(item) == 1 {
return rangeData, fmt.Errorf("range: malformed range header string")
err = ErrRangeMalformed
return
}
start, startErr := strconv.Atoi(item[0])
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 {
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.

View File

@ -172,8 +172,30 @@ func Test_Ctx_Append(t *testing.T) {
ctx.Append("X-Test", "Hello")
ctx.Append("X-Test", "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")
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")))
}
@ -356,24 +378,29 @@ func Test_Ctx_Format(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
ctx.Fasthttp.Request.Header.Set(HeaderAccept, "plain/text")
ctx.Fasthttp.Request.Header.Set(HeaderAccept, MIMETextPlain)
ctx.Format([]byte("Hello, World!"))
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!")
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!")
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!")
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.Format("Hello, World!")
ctx.Format(broken("Hello, World!"))
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{})
defer app.ReleaseCtx(ctx)
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
@ -783,12 +835,40 @@ func Test_Ctx_Range(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
ctx.Fasthttp.Request.Header.Set(HeaderRange, "bytes=500-700")
result, err := ctx.Range(1000)
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, "bytes", result.Type)
utils.AssertEqual(t, 500, result.Ranges[0].Start)
utils.AssertEqual(t, 700, result.Ranges[0].End)
var (
result Range
err error
)
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
@ -801,6 +881,13 @@ func Test_Ctx_Route(t *testing.T) {
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
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
@ -1011,6 +1098,9 @@ func Test_Ctx_JSON(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
utils.AssertEqual(t, true, ctx.JSON(complex(1, 1)) != nil)
ctx.JSON(Map{ // map has no order
"Name": "Grame",
"Age": 20,
@ -1059,7 +1149,17 @@ func Test_Ctx_JSONP(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
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",
"Age": 20,
}, "john")
@ -1097,6 +1197,10 @@ func Test_Ctx_Links(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
ctx.Links()
utils.AssertEqual(t, "", string(ctx.Fasthttp.Response.Header.Peek(HeaderLink)))
ctx.Links(
"http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last",
@ -1164,6 +1268,11 @@ func Test_Ctx_Redirect(t *testing.T) {
app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
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)
utils.AssertEqual(t, 301, ctx.Fasthttp.Response.StatusCode())
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([]byte("World! "))
ctx.Write(123)
ctx.Write(123.321)
ctx.Write(true)
ctx.Write(bytes.NewReader([]byte("Don't crash please")))
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)
utils.AssertEqual(t, nil, ctx.QueryParser(q))
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

View File

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

View File

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

View File

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

View File

@ -15,11 +15,40 @@ import (
func Test_Utils_ETag(t *testing.T) {
app := New()
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)))
t.Run("Not Status OK", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
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
@ -36,11 +65,33 @@ func Benchmark_Utils_ETag(b *testing.B) {
func Test_Utils_ETag_Weak(t *testing.T) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
setETag(c, true)
utils.AssertEqual(t, `W/"13-1831710635"`, string(c.Fasthttp.Response.Header.Peek(HeaderETag)))
t.Run("Set Weak", func(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Send("Hello, World!")
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