refactored Engine.handleHTTPRequest so that two passes are made if the request is HEAD and there isn't a matching HEAD handler

added more documentation

re-ordered GET and HEAD methods to be adjacent

rendering is now suppressed for HEAD request, which means that wasteful processing is avoided
This commit is contained in:
Rick 2020-11-05 23:34:33 +00:00
parent 65ed60ed13
commit 652faa23c2
10 changed files with 191 additions and 94 deletions

View File

@ -196,6 +196,8 @@ You can find a number of ready-to-run examples at [Gin examples repository](http
### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
For each HTTP method, there is a router method to set up handlers on specific paths.
```go ```go
func main() { func main() {
// Creates a gin router with default middleware: // Creates a gin router with default middleware:
@ -217,8 +219,13 @@ func main() {
} }
``` ```
Note that every GET handler will also receive its equivalent HEAD requests automatically, so
although you can define HEAD handlers explicitly, you don't really need to.
### Parameters in path ### Parameters in path
Path parameters take the form `:param` and accept any string separated the adjacent slash(es).
```go ```go
func main() { func main() {
router := gin.Default() router := gin.Default()
@ -247,7 +254,10 @@ func main() {
} }
``` ```
### Querystring parameters ### Query string parameters
The query string follows the `?` in the URL. The parameters are accessed using Gin `Context` methods
`Query` and `DefaultQuery`.
```go ```go
func main() { func main() {
@ -315,7 +325,7 @@ func main() {
id: 1234; page: 1; name: manu; message: this_is_great id: 1234; page: 1; name: manu; message: this_is_great
``` ```
### Map as querystring or postform parameters ### Map as query string or post form parameters
``` ```
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
@ -547,7 +557,7 @@ func main() {
c.String(200, "pong") c.String(200, "pong")
}) })
   router.Run(":8080") router.Run(":8080")
} }
``` ```

View File

@ -856,7 +856,11 @@ func (c *Context) Cookie(name string) (string, error) {
func (c *Context) Render(code int, r render.Render) { func (c *Context) Render(code int, r render.Render) {
c.Status(code) c.Status(code)
if !bodyAllowedForStatus(code) { // Rendering is suppressed for 1xx, 204 and 304 and all HEAD requests
// This avoids wasteful response processing, even though the standard
// net/http response processing would discard the response entity in
// (some of) these cases.
if !bodyAllowedForStatus(code) || c.Request.Method == http.MethodHead {
r.WriteContentType(c.Writer) r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
return return

View File

@ -389,7 +389,7 @@ func TestContextHandler(t *testing.T) {
func TestContextQuery(t *testing.T) { func TestContextQuery(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) c.Request, _ = http.NewRequest("GET", "/?foo=bar&page=10&id=", nil)
value, ok := c.GetQuery("foo") value, ok := c.GetQuery("foo")
assert.True(t, ok) assert.True(t, ok)
@ -675,6 +675,7 @@ func TestContextRenderPanicIfErr(t *testing.T) {
}() }()
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Render(http.StatusOK, &TestPanicRender{}) c.Render(http.StatusOK, &TestPanicRender{})
@ -687,6 +688,7 @@ func TestContextRenderPanicIfErr(t *testing.T) {
func TestContextRenderJSON(t *testing.T) { func TestContextRenderJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "http://example.com/", nil)
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"}) c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
@ -740,6 +742,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
func TestContextRenderAPIJSON(t *testing.T) { func TestContextRenderAPIJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "http://example.com/", nil)
c.Header("Content-Type", "application/vnd.api+json") c.Header("Content-Type", "application/vnd.api+json")
c.JSON(http.StatusCreated, H{"foo": "bar"}) c.JSON(http.StatusCreated, H{"foo": "bar"})
@ -767,6 +770,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
func TestContextRenderIndentedJSON(t *testing.T) { func TestContextRenderIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
@ -779,6 +783,7 @@ func TestContextRenderIndentedJSON(t *testing.T) {
func TestContextRenderNoContentIndentedJSON(t *testing.T) { func TestContextRenderNoContentIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
@ -792,6 +797,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
func TestContextRenderSecureJSON(t *testing.T) { func TestContextRenderSecureJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
router.SecureJsonPrefix("&&&START&&&") router.SecureJsonPrefix("&&&START&&&")
c.SecureJSON(http.StatusCreated, []string{"foo", "bar"}) c.SecureJSON(http.StatusCreated, []string{"foo", "bar"})
@ -805,6 +811,7 @@ func TestContextRenderSecureJSON(t *testing.T) {
func TestContextRenderNoContentSecureJSON(t *testing.T) { func TestContextRenderNoContentSecureJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"}) c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"})
@ -816,6 +823,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
func TestContextRenderNoContentAsciiJSON(t *testing.T) { func TestContextRenderNoContentAsciiJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"})
@ -830,6 +838,8 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) {
func TestContextRenderPureJSON(t *testing.T) { func TestContextRenderPureJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"}) c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
@ -841,6 +851,7 @@ func TestContextRenderPureJSON(t *testing.T) {
func TestContextRenderHTML(t *testing.T) { func TestContextRenderHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
@ -855,6 +866,7 @@ func TestContextRenderHTML(t *testing.T) {
func TestContextRenderHTML2(t *testing.T) { func TestContextRenderHTML2(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
// print debug warning log when Engine.trees > 0 // print debug warning log when Engine.trees > 0
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
@ -880,6 +892,7 @@ func TestContextRenderHTML2(t *testing.T) {
func TestContextRenderNoContentHTML(t *testing.T) { func TestContextRenderNoContentHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
@ -895,6 +908,7 @@ func TestContextRenderNoContentHTML(t *testing.T) {
func TestContextRenderXML(t *testing.T) { func TestContextRenderXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.XML(http.StatusCreated, H{"foo": "bar"}) c.XML(http.StatusCreated, H{"foo": "bar"})
@ -907,6 +921,7 @@ func TestContextRenderXML(t *testing.T) {
func TestContextRenderNoContentXML(t *testing.T) { func TestContextRenderNoContentXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.XML(http.StatusNoContent, H{"foo": "bar"}) c.XML(http.StatusNoContent, H{"foo": "bar"})
@ -920,6 +935,7 @@ func TestContextRenderNoContentXML(t *testing.T) {
func TestContextRenderString(t *testing.T) { func TestContextRenderString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.String(http.StatusCreated, "test %s %d", "string", 2) c.String(http.StatusCreated, "test %s %d", "string", 2)
@ -932,6 +948,7 @@ func TestContextRenderString(t *testing.T) {
func TestContextRenderNoContentString(t *testing.T) { func TestContextRenderNoContentString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.String(http.StatusNoContent, "test %s %d", "string", 2) c.String(http.StatusNoContent, "test %s %d", "string", 2)
@ -945,6 +962,7 @@ func TestContextRenderNoContentString(t *testing.T) {
func TestContextRenderHTMLString(t *testing.T) { func TestContextRenderHTMLString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3) c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3)
@ -958,6 +976,7 @@ func TestContextRenderHTMLString(t *testing.T) {
func TestContextRenderNoContentHTMLString(t *testing.T) { func TestContextRenderNoContentHTMLString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3) c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3)
@ -972,6 +991,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
func TestContextRenderData(t *testing.T) { func TestContextRenderData(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`)) c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`))
@ -984,6 +1004,7 @@ func TestContextRenderData(t *testing.T) {
func TestContextRenderNoContentData(t *testing.T) { func TestContextRenderNoContentData(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`)) c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`))
@ -995,6 +1016,7 @@ func TestContextRenderNoContentData(t *testing.T) {
func TestContextRenderSSE(t *testing.T) { func TestContextRenderSSE(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.SSEvent("float", 1.5) c.SSEvent("float", 1.5)
c.Render(-1, sse.Event{ c.Render(-1, sse.Event{
@ -1012,6 +1034,7 @@ func TestContextRenderSSE(t *testing.T) {
func TestContextRenderFile(t *testing.T) { func TestContextRenderFile(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Request, _ = http.NewRequest("GET", "/", nil) c.Request, _ = http.NewRequest("GET", "/", nil)
c.File("./gin.go") c.File("./gin.go")
@ -1024,6 +1047,7 @@ func TestContextRenderFile(t *testing.T) {
func TestContextRenderFileFromFS(t *testing.T) { func TestContextRenderFileFromFS(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Request, _ = http.NewRequest("GET", "/some/path", nil) c.Request, _ = http.NewRequest("GET", "/some/path", nil)
c.FileFromFS("./gin.go", Dir(".", false)) c.FileFromFS("./gin.go", Dir(".", false))
@ -1037,6 +1061,7 @@ func TestContextRenderFileFromFS(t *testing.T) {
func TestContextRenderAttachment(t *testing.T) { func TestContextRenderAttachment(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
newFilename := "new_filename.go" newFilename := "new_filename.go"
c.Request, _ = http.NewRequest("GET", "/", nil) c.Request, _ = http.NewRequest("GET", "/", nil)
@ -1052,6 +1077,7 @@ func TestContextRenderAttachment(t *testing.T) {
func TestContextRenderYAML(t *testing.T) { func TestContextRenderYAML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.YAML(http.StatusCreated, H{"foo": "bar"}) c.YAML(http.StatusCreated, H{"foo": "bar"})
@ -1066,6 +1092,7 @@ func TestContextRenderYAML(t *testing.T) {
func TestContextRenderProtoBuf(t *testing.T) { func TestContextRenderProtoBuf(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
reps := []int64{int64(1), int64(2)} reps := []int64{int64(1), int64(2)}
label := "test" label := "test"
@ -1086,6 +1113,7 @@ func TestContextRenderProtoBuf(t *testing.T) {
func TestContextHeaders(t *testing.T) { func TestContextHeaders(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Header("Content-Type", "text/plain") c.Header("Content-Type", "text/plain")
c.Header("X-Custom", "value") c.Header("X-Custom", "value")
@ -1118,6 +1146,7 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
c.Redirect(http.StatusFound, "http://google.com") c.Redirect(http.StatusFound, "http://google.com")
@ -1131,7 +1160,7 @@ func TestContextRenderRedirectWith201(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Redirect(http.StatusCreated, "/resource") c.Redirect(http.StatusCreated, "/resource")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
@ -1141,7 +1170,7 @@ func TestContextRenderRedirectWith201(t *testing.T) {
func TestContextRenderRedirectAll(t *testing.T) { func TestContextRenderRedirectAll(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") })
assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") })
assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") })
@ -1153,7 +1182,7 @@ func TestContextRenderRedirectAll(t *testing.T) {
func TestContextNegotiationWithJSON(t *testing.T) { func TestContextNegotiationWithJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEJSON, MIMEXML, MIMEYAML}, Offered: []string{MIMEJSON, MIMEXML, MIMEYAML},
@ -1168,7 +1197,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
func TestContextNegotiationWithXML(t *testing.T) { func TestContextNegotiationWithXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEXML, MIMEJSON, MIMEYAML}, Offered: []string{MIMEXML, MIMEJSON, MIMEYAML},
@ -1183,7 +1212,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
func TestContextNegotiationWithHTML(t *testing.T) { func TestContextNegotiationWithHTML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
@ -1201,7 +1230,7 @@ func TestContextNegotiationWithHTML(t *testing.T) {
func TestContextNegotiationNotSupport(t *testing.T) { func TestContextNegotiationNotSupport(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEPOSTForm}, Offered: []string{MIMEPOSTForm},
@ -1214,7 +1243,7 @@ func TestContextNegotiationNotSupport(t *testing.T) {
func TestContextNegotiationFormat(t *testing.T) { func TestContextNegotiationFormat(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
assert.Panics(t, func() { c.NegotiateFormat() }) assert.Panics(t, func() { c.NegotiateFormat() })
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
@ -1305,6 +1334,7 @@ type testJSONAbortMsg struct {
func TestContextAbortWithStatusJSON(t *testing.T) { func TestContextAbortWithStatusJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.index = 4 c.index = 4
in := new(testJSONAbortMsg) in := new(testJSONAbortMsg)
@ -1365,6 +1395,7 @@ func TestContextError(t *testing.T) {
func TestContextTypedError(t *testing.T) { func TestContextTypedError(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/", nil)
c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck
@ -1380,6 +1411,7 @@ func TestContextTypedError(t *testing.T) {
func TestContextAbortWithError(t *testing.T) { func TestContextAbortWithError(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck
@ -1537,7 +1569,7 @@ func TestContextBadAutoBind(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1670,7 +1702,7 @@ func TestContextBadAutoShouldBind(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1734,7 +1766,7 @@ func TestContextShouldBindBodyWith(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest( c.Request, _ = http.NewRequest(
"POST", "http://example.com", bytes.NewBufferString(tt.bodyA), "POST", "/", bytes.NewBufferString(tt.bodyA),
) )
// When it binds to typeA and typeB, it finds the body is // When it binds to typeA and typeB, it finds the body is
// not typeB but typeA. // not typeB but typeA.
@ -1752,7 +1784,7 @@ func TestContextShouldBindBodyWith(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest( c.Request, _ = http.NewRequest(
"POST", "http://example.com", bytes.NewBufferString(tt.bodyB), "POST", "/", bytes.NewBufferString(tt.bodyB),
) )
objA := typeA{} objA := typeA{}
assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA))
@ -1825,6 +1857,7 @@ func TestContextGetRawData(t *testing.T) {
func TestContextRenderDataFromReader(t *testing.T) { func TestContextRenderDataFromReader(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
body := "#!PNG some raw data" body := "#!PNG some raw data"
reader := strings.NewReader(body) reader := strings.NewReader(body)
@ -1844,6 +1877,7 @@ func TestContextRenderDataFromReader(t *testing.T) {
func TestContextRenderDataFromReaderNoHeaders(t *testing.T) { func TestContextRenderDataFromReaderNoHeaders(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
body := "#!PNG some raw data" body := "#!PNG some raw data"
reader := strings.NewReader(body) reader := strings.NewReader(body)

75
gin.go
View File

@ -390,7 +390,6 @@ func (engine *Engine) HandleContext(c *Context) {
} }
func (engine *Engine) handleHTTPRequest(c *Context) { func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path rPath := c.Request.URL.Path
unescape := false unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
@ -402,40 +401,20 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
rPath = cleanPath(rPath) rPath = cleanPath(rPath)
} }
// Find root of the tree for the given HTTP method if engine.handleHTTPRequestForMethod(c, c.Request.Method, rPath, unescape) {
t := engine.trees return
for i, tl := 0, len(t); i < tl; i++ { }
if t[i].method != httpMethod {
continue // For HEAD requests, reaching here means no HEAD handler had been set up, so we retry the
} // equivalent GET handler as if this had been a GET request. As expected for HEAD requests,
root := t[i].root // the response content will of course be empty (see net/http for implementation details).
// Find route in tree if c.Request.Method == http.MethodHead && engine.handleHTTPRequestForMethod(c, http.MethodGet, rPath, unescape) {
value := root.getValue(rPath, c.params, unescape) return
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
} }
if engine.HandleMethodNotAllowed { if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees { for _, tree := range engine.trees {
if tree.method == httpMethod { if tree.method == c.Request.Method {
continue continue
} }
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
@ -449,6 +428,40 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
serveError(c, http.StatusNotFound, default404Body) serveError(c, http.StatusNotFound, default404Body)
} }
func (engine *Engine) handleHTTPRequestForMethod(c *Context, httpMethod, rPath string, unescape bool) bool {
// Find root of the tree for the given HTTP method
for _, t := range engine.trees {
if t.method != httpMethod {
continue
}
root := t.root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return true
}
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return true
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return true
}
}
break
}
return false // probably 404 or 406
}
var mimePlain = []string{MIMEPlain} var mimePlain = []string{MIMEPlain}
func serveError(c *Context, code int, defaultMessage []byte) { func serveError(c *Context, code int, defaultMessage []byte) {

View File

@ -58,16 +58,23 @@ func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IR
return engine().Handle(httpMethod, relativePath, handlers...) return engine().Handle(httpMethod, relativePath, handlers...)
} }
// GET is a shortcut for router.Handle("GET", path, handle)
// HEAD requests will also be handled automatically by this handler.
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().GET(relativePath, handlers...)
}
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
// This is rarely needed because every GET handler will also handle HEAD requests.
func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().HEAD(relativePath, handlers...)
}
// POST is a shortcut for router.Handle("POST", path, handle) // POST is a shortcut for router.Handle("POST", path, handle)
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().POST(relativePath, handlers...) return engine().POST(relativePath, handlers...)
} }
// GET is a shortcut for router.Handle("GET", path, handle)
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().GET(relativePath, handlers...)
}
// DELETE is a shortcut for router.Handle("DELETE", path, handle) // DELETE is a shortcut for router.Handle("DELETE", path, handle)
func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().DELETE(relativePath, handlers...) return engine().DELETE(relativePath, handlers...)
@ -88,11 +95,6 @@ func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().OPTIONS(relativePath, handlers...) return engine().OPTIONS(relativePath, handlers...)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().HEAD(relativePath, handlers...)
}
// Any is a wrapper for Engine.Any. // Any is a wrapper for Engine.Any.
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
return engine().Any(relativePath, handlers...) return engine().Any(relativePath, handlers...)

View File

@ -450,7 +450,7 @@ func TestListOfRoutes(t *testing.T) {
list := router.Routes() list := router.Routes()
assert.Len(t, list, 7) assert.Len(t, list, 6)
assertRoutePresent(t, list, RouteInfo{ assertRoutePresent(t, list, RouteInfo{
Method: "GET", Method: "GET",
Path: "/favicon.ico", Path: "/favicon.ico",

View File

@ -23,58 +23,58 @@ func TestLogger(t *testing.T) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
router := New() router := New()
router.Use(LoggerWithWriter(buffer)) router.Use(LoggerWithWriter(buffer))
router.GET("/example", func(c *Context) {}) router.GET("/get", func(c *Context) {})
router.POST("/example", func(c *Context) {}) router.POST("/post", func(c *Context) {})
router.PUT("/example", func(c *Context) {}) router.PUT("/put", func(c *Context) {})
router.DELETE("/example", func(c *Context) {}) router.DELETE("/delete", func(c *Context) {})
router.PATCH("/example", func(c *Context) {}) router.PATCH("/patch", func(c *Context) {})
router.HEAD("/example", func(c *Context) {}) router.HEAD("/head", func(c *Context) {})
router.OPTIONS("/example", func(c *Context) {}) router.OPTIONS("/options", func(c *Context) {})
performRequest(router, "GET", "/example?a=100") performRequest(router, "GET", "/get?a=100")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "GET")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/get")
assert.Contains(t, buffer.String(), "a=100") assert.Contains(t, buffer.String(), "a=100")
// I wrote these first (extending the above) but then realized they are more // I wrote these first (extending the above) but then realized they are more
// like integration tests because they test the whole logging process rather // like integration tests because they test the whole logging process rather
// than individual functions. Im not sure where these should go. // than individual functions. Im not sure where these should go.
buffer.Reset() buffer.Reset()
performRequest(router, "POST", "/example") performRequest(router, "POST", "/post")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "POST")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/post")
buffer.Reset() buffer.Reset()
performRequest(router, "PUT", "/example") performRequest(router, "PUT", "/put")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "PUT")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/put")
buffer.Reset() buffer.Reset()
performRequest(router, "DELETE", "/example") performRequest(router, "DELETE", "/delete")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "DELETE")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/delete")
buffer.Reset() buffer.Reset()
performRequest(router, "PATCH", "/example") performRequest(router, "PATCH", "/patch")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "PATCH")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/patch")
buffer.Reset() buffer.Reset()
performRequest(router, "HEAD", "/example") performRequest(router, "HEAD", "/head")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "HEAD")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/head")
buffer.Reset() buffer.Reset()
performRequest(router, "OPTIONS", "/example") performRequest(router, "OPTIONS", "/options")
assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "200")
assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "OPTIONS")
assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "/options")
buffer.Reset() buffer.Reset()
performRequest(router, "GET", "/notfound") performRequest(router, "GET", "/notfound")

View File

@ -80,7 +80,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
// See the example code in GitHub. // See the example code in GitHub.
// //
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // For GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS requests the respective shortcut
// functions can be used. // functions can be used.
// //
// This function is intended for bulk loading and to allow the usage of less // This function is intended for bulk loading and to allow the usage of less
@ -98,11 +98,18 @@ func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRo
return group.handle(http.MethodPost, relativePath, handlers) return group.handle(http.MethodPost, relativePath, handlers)
} }
// GET is a shortcut for router.Handle("GET", path, handle). // GET is a shortcut for router.Handle("GET", path, handle). The equivalent
// HEAD requests will also be handled automatically by this handler.
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers) return group.handle(http.MethodGet, relativePath, handlers)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle). This is rarely needed
// because every GET handler will also handle HEAD requests.
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodHead, relativePath, handlers)
}
// DELETE is a shortcut for router.Handle("DELETE", path, handle). // DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodDelete, relativePath, handlers) return group.handle(http.MethodDelete, relativePath, handlers)
@ -123,11 +130,6 @@ func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc)
return group.handle(http.MethodOptions, relativePath, handlers) return group.handle(http.MethodOptions, relativePath, handlers)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodHead, relativePath, handlers)
}
// Any registers a route that matches all the HTTP methods. // Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
@ -152,8 +154,9 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
handler := func(c *Context) { handler := func(c *Context) {
c.File(filepath) c.File(filepath)
} }
// Register GET (and HEAD) handler
group.GET(relativePath, handler) group.GET(relativePath, handler)
group.HEAD(relativePath, handler)
return group.returnObj() return group.returnObj()
} }
@ -176,9 +179,8 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou
handler := group.createStaticHandler(relativePath, fs) handler := group.createStaticHandler(relativePath, fs)
urlPattern := path.Join(relativePath, "/*filepath") urlPattern := path.Join(relativePath, "/*filepath")
// Register GET and HEAD handlers // Register GET (and HEAD) handler
group.GET(urlPattern, handler) group.GET(urlPattern, handler)
group.HEAD(urlPattern, handler)
return group.returnObj() return group.returnObj()
} }

View File

@ -38,7 +38,6 @@ func TestRouterGroupBasicHandle(t *testing.T) {
performRequestInGroup(t, http.MethodPut) performRequestInGroup(t, http.MethodPut)
performRequestInGroup(t, http.MethodPatch) performRequestInGroup(t, http.MethodPatch)
performRequestInGroup(t, http.MethodDelete) performRequestInGroup(t, http.MethodDelete)
performRequestInGroup(t, http.MethodHead)
performRequestInGroup(t, http.MethodOptions) performRequestInGroup(t, http.MethodOptions)
} }
@ -70,9 +69,6 @@ func performRequestInGroup(t *testing.T, method string) {
case http.MethodDelete: case http.MethodDelete:
v1.DELETE("/test", handler) v1.DELETE("/test", handler)
login.DELETE("/test", handler) login.DELETE("/test", handler)
case http.MethodHead:
v1.HEAD("/test", handler)
login.HEAD("/test", handler)
case http.MethodOptions: case http.MethodOptions:
v1.OPTIONS("/test", handler) v1.OPTIONS("/test", handler)
login.OPTIONS("/test", handler) login.OPTIONS("/test", handler)
@ -89,6 +85,30 @@ func performRequestInGroup(t *testing.T, method string) {
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
} }
func TestRouterGroupBasicHandleHEAD(t *testing.T) {
router := New()
v1 := router.Group("v1", func(c *Context) {})
assert.Equal(t, "/v1", v1.BasePath())
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
assert.Equal(t, "/v1/login/", login.BasePath())
handler := func(c *Context) {
c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index)
}
v1.HEAD("/test", handler)
login.HEAD("/test", handler)
w := performRequest(router, http.MethodHead, "/v1/login/test")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "", w.Body.String())
w = performRequest(router, http.MethodHead, "/v1/test")
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "", w.Body.String())
}
func TestRouterGroupInvalidStatic(t *testing.T) { func TestRouterGroupInvalidStatic(t *testing.T) {
router := New() router := New()
assert.Panics(t, func() { assert.Panics(t, func() {

View File

@ -368,6 +368,18 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
} }
func TestRouteHeadUsesGetHandlerByDefault(t *testing.T) {
router := New()
router.GET("/path", func(c *Context) {
c.String(http.StatusOK, "responseText")
})
w := performRequest(router, http.MethodHead, "/path")
assert.Equal(t, http.StatusOK, w.Code)
// The response entity is blank because response rendering is suppressed for HEAD requests.
// Note that the Go net/http handlers would discard the entity for HEAD requests anyway.
assert.Equal(t, "", w.Body.String())
}
func TestRouteNotAllowedEnabled(t *testing.T) { func TestRouteNotAllowedEnabled(t *testing.T) {
router := New() router := New()
router.HandleMethodNotAllowed = true router.HandleMethodNotAllowed = true
@ -494,7 +506,7 @@ func TestRouterStaticFSNotFound(t *testing.T) {
assert.Equal(t, "non existent", w.Body.String()) assert.Equal(t, "non existent", w.Body.String())
w = performRequest(router, http.MethodHead, "/nonexistent") w = performRequest(router, http.MethodHead, "/nonexistent")
assert.Equal(t, "non existent", w.Body.String()) assert.Equal(t, "", w.Body.String())
} }
func TestRouterStaticFSFileNotFound(t *testing.T) { func TestRouterStaticFSFileNotFound(t *testing.T) {