mirror of https://github.com/gin-gonic/gin.git
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:
parent
65ed60ed13
commit
652faa23c2
12
README.md
12
README.md
|
@ -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
|
||||
|
||||
For each HTTP method, there is a router method to set up handlers on specific paths.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// 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
|
||||
|
||||
Path parameters take the form `:param` and accept any string separated the adjacent slash(es).
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
@ -249,6 +256,9 @@ func main() {
|
|||
|
||||
### Query string parameters
|
||||
|
||||
The query string follows the `?` in the URL. The parameters are accessed using Gin `Context` methods
|
||||
`Query` and `DefaultQuery`.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
@ -547,7 +557,7 @@ func main() {
|
|||
c.String(200, "pong")
|
||||
})
|
||||
|
||||
router.Run(":8080")
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -856,7 +856,11 @@ func (c *Context) Cookie(name string) (string, error) {
|
|||
func (c *Context) Render(code int, r render.Render) {
|
||||
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)
|
||||
c.Writer.WriteHeaderNow()
|
||||
return
|
||||
|
|
|
@ -389,7 +389,7 @@ func TestContextHandler(t *testing.T) {
|
|||
|
||||
func TestContextQuery(t *testing.T) {
|
||||
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")
|
||||
assert.True(t, ok)
|
||||
|
@ -675,6 +675,7 @@ func TestContextRenderPanicIfErr(t *testing.T) {
|
|||
}()
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Render(http.StatusOK, &TestPanicRender{})
|
||||
|
||||
|
@ -687,6 +688,7 @@ func TestContextRenderPanicIfErr(t *testing.T) {
|
|||
func TestContextRenderJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "http://example.com/", nil)
|
||||
|
||||
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||
|
||||
|
@ -740,6 +742,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
|
|||
func TestContextRenderAPIJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "http://example.com/", nil)
|
||||
|
||||
c.Header("Content-Type", "application/vnd.api+json")
|
||||
c.JSON(http.StatusCreated, H{"foo": "bar"})
|
||||
|
@ -767,6 +770,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
|
|||
func TestContextRenderIndentedJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
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) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
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) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
router.SecureJsonPrefix("&&&START&&&")
|
||||
c.SecureJSON(http.StatusCreated, []string{"foo", "bar"})
|
||||
|
@ -805,6 +811,7 @@ func TestContextRenderSecureJSON(t *testing.T) {
|
|||
func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"})
|
||||
|
||||
|
@ -816,6 +823,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
|||
func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"})
|
||||
|
||||
|
@ -830,6 +838,8 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
|||
func TestContextRenderPureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
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) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
@ -855,6 +866,7 @@ func TestContextRenderHTML(t *testing.T) {
|
|||
func TestContextRenderHTML2(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
// print debug warning log when Engine.trees > 0
|
||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||
|
@ -880,6 +892,7 @@ func TestContextRenderHTML2(t *testing.T) {
|
|||
func TestContextRenderNoContentHTML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
|
@ -895,6 +908,7 @@ func TestContextRenderNoContentHTML(t *testing.T) {
|
|||
func TestContextRenderXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.XML(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
|
@ -907,6 +921,7 @@ func TestContextRenderXML(t *testing.T) {
|
|||
func TestContextRenderNoContentXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.XML(http.StatusNoContent, H{"foo": "bar"})
|
||||
|
||||
|
@ -920,6 +935,7 @@ func TestContextRenderNoContentXML(t *testing.T) {
|
|||
func TestContextRenderString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.String(http.StatusCreated, "test %s %d", "string", 2)
|
||||
|
||||
|
@ -932,6 +948,7 @@ func TestContextRenderString(t *testing.T) {
|
|||
func TestContextRenderNoContentString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.String(http.StatusNoContent, "test %s %d", "string", 2)
|
||||
|
||||
|
@ -945,6 +962,7 @@ func TestContextRenderNoContentString(t *testing.T) {
|
|||
func TestContextRenderHTMLString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3)
|
||||
|
@ -958,6 +976,7 @@ func TestContextRenderHTMLString(t *testing.T) {
|
|||
func TestContextRenderNoContentHTMLString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3)
|
||||
|
@ -972,6 +991,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
|||
func TestContextRenderData(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
|
@ -984,6 +1004,7 @@ func TestContextRenderData(t *testing.T) {
|
|||
func TestContextRenderNoContentData(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
|
@ -995,6 +1016,7 @@ func TestContextRenderNoContentData(t *testing.T) {
|
|||
func TestContextRenderSSE(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.SSEvent("float", 1.5)
|
||||
c.Render(-1, sse.Event{
|
||||
|
@ -1012,6 +1034,7 @@ func TestContextRenderSSE(t *testing.T) {
|
|||
func TestContextRenderFile(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.File("./gin.go")
|
||||
|
@ -1024,6 +1047,7 @@ func TestContextRenderFile(t *testing.T) {
|
|||
func TestContextRenderFileFromFS(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Request, _ = http.NewRequest("GET", "/some/path", nil)
|
||||
c.FileFromFS("./gin.go", Dir(".", false))
|
||||
|
@ -1037,6 +1061,7 @@ func TestContextRenderFileFromFS(t *testing.T) {
|
|||
func TestContextRenderAttachment(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
newFilename := "new_filename.go"
|
||||
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
@ -1052,6 +1077,7 @@ func TestContextRenderAttachment(t *testing.T) {
|
|||
func TestContextRenderYAML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.YAML(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
|
@ -1066,6 +1092,7 @@ func TestContextRenderYAML(t *testing.T) {
|
|||
func TestContextRenderProtoBuf(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
reps := []int64{int64(1), int64(2)}
|
||||
label := "test"
|
||||
|
@ -1086,6 +1113,7 @@ func TestContextRenderProtoBuf(t *testing.T) {
|
|||
|
||||
func TestContextHeaders(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.Header("Content-Type", "text/plain")
|
||||
c.Header("X-Custom", "value")
|
||||
|
||||
|
@ -1118,6 +1146,7 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
|
|||
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||
c.Redirect(http.StatusFound, "http://google.com")
|
||||
|
@ -1131,7 +1160,7 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Redirect(http.StatusCreated, "/resource")
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
|
@ -1141,7 +1170,7 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
|||
|
||||
func TestContextRenderRedirectAll(t *testing.T) {
|
||||
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.StatusAccepted, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
||||
|
@ -1153,7 +1182,7 @@ func TestContextRenderRedirectAll(t *testing.T) {
|
|||
func TestContextNegotiationWithJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEJSON, MIMEXML, MIMEYAML},
|
||||
|
@ -1168,7 +1197,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
|
|||
func TestContextNegotiationWithXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEXML, MIMEJSON, MIMEYAML},
|
||||
|
@ -1183,7 +1212,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
|
|||
func TestContextNegotiationWithHTML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
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}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
|
@ -1201,7 +1230,7 @@ func TestContextNegotiationWithHTML(t *testing.T) {
|
|||
func TestContextNegotiationNotSupport(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEPOSTForm},
|
||||
|
@ -1214,7 +1243,7 @@ func TestContextNegotiationNotSupport(t *testing.T) {
|
|||
|
||||
func TestContextNegotiationFormat(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
|
||||
assert.Panics(t, func() { c.NegotiateFormat() })
|
||||
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||
|
@ -1305,6 +1334,7 @@ type testJSONAbortMsg struct {
|
|||
func TestContextAbortWithStatusJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.index = 4
|
||||
|
||||
in := new(testJSONAbortMsg)
|
||||
|
@ -1365,6 +1395,7 @@ func TestContextError(t *testing.T) {
|
|||
|
||||
func TestContextTypedError(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // 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) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
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()
|
||||
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)
|
||||
var obj struct {
|
||||
Foo string `json:"foo"`
|
||||
|
@ -1670,7 +1702,7 @@ func TestContextBadAutoShouldBind(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
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)
|
||||
var obj struct {
|
||||
Foo string `json:"foo"`
|
||||
|
@ -1734,7 +1766,7 @@ func TestContextShouldBindBodyWith(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
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
|
||||
// not typeB but typeA.
|
||||
|
@ -1752,7 +1784,7 @@ func TestContextShouldBindBodyWith(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest(
|
||||
"POST", "http://example.com", bytes.NewBufferString(tt.bodyB),
|
||||
"POST", "/", bytes.NewBufferString(tt.bodyB),
|
||||
)
|
||||
objA := typeA{}
|
||||
assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA))
|
||||
|
@ -1825,6 +1857,7 @@ func TestContextGetRawData(t *testing.T) {
|
|||
func TestContextRenderDataFromReader(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
body := "#!PNG some raw data"
|
||||
reader := strings.NewReader(body)
|
||||
|
@ -1844,6 +1877,7 @@ func TestContextRenderDataFromReader(t *testing.T) {
|
|||
func TestContextRenderDataFromReaderNoHeaders(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
body := "#!PNG some raw data"
|
||||
reader := strings.NewReader(body)
|
||||
|
|
69
gin.go
69
gin.go
|
@ -390,7 +390,6 @@ func (engine *Engine) HandleContext(c *Context) {
|
|||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
httpMethod := c.Request.Method
|
||||
rPath := c.Request.URL.Path
|
||||
unescape := false
|
||||
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||
|
@ -402,40 +401,20 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||
rPath = cleanPath(rPath)
|
||||
}
|
||||
|
||||
// Find root of the tree for the given HTTP method
|
||||
t := engine.trees
|
||||
for i, tl := 0, len(t); i < tl; i++ {
|
||||
if t[i].method != httpMethod {
|
||||
continue
|
||||
}
|
||||
root := t[i].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()
|
||||
if engine.handleHTTPRequestForMethod(c, c.Request.Method, rPath, unescape) {
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && rPath != "/" {
|
||||
if value.tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
|
||||
// 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,
|
||||
// the response content will of course be empty (see net/http for implementation details).
|
||||
if c.Request.Method == http.MethodHead && engine.handleHTTPRequestForMethod(c, http.MethodGet, rPath, unescape) {
|
||||
return
|
||||
}
|
||||
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if engine.HandleMethodNotAllowed {
|
||||
for _, tree := range engine.trees {
|
||||
if tree.method == httpMethod {
|
||||
if tree.method == c.Request.Method {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||
|
|
22
ginS/gins.go
22
ginS/gins.go
|
@ -58,16 +58,23 @@ func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IR
|
|||
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)
|
||||
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
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)
|
||||
func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().DELETE(relativePath, handlers...)
|
||||
|
@ -88,11 +95,6 @@ func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
|||
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.
|
||||
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().Any(relativePath, handlers...)
|
||||
|
|
|
@ -450,7 +450,7 @@ func TestListOfRoutes(t *testing.T) {
|
|||
|
||||
list := router.Routes()
|
||||
|
||||
assert.Len(t, list, 7)
|
||||
assert.Len(t, list, 6)
|
||||
assertRoutePresent(t, list, RouteInfo{
|
||||
Method: "GET",
|
||||
Path: "/favicon.ico",
|
||||
|
|
|
@ -23,58 +23,58 @@ func TestLogger(t *testing.T) {
|
|||
buffer := new(bytes.Buffer)
|
||||
router := New()
|
||||
router.Use(LoggerWithWriter(buffer))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
router.POST("/example", func(c *Context) {})
|
||||
router.PUT("/example", func(c *Context) {})
|
||||
router.DELETE("/example", func(c *Context) {})
|
||||
router.PATCH("/example", func(c *Context) {})
|
||||
router.HEAD("/example", func(c *Context) {})
|
||||
router.OPTIONS("/example", func(c *Context) {})
|
||||
router.GET("/get", func(c *Context) {})
|
||||
router.POST("/post", func(c *Context) {})
|
||||
router.PUT("/put", func(c *Context) {})
|
||||
router.DELETE("/delete", func(c *Context) {})
|
||||
router.PATCH("/patch", func(c *Context) {})
|
||||
router.HEAD("/head", 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(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/get")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
// I wrote these first (extending the above) but then realized they are more
|
||||
// like integration tests because they test the whole logging process rather
|
||||
// than individual functions. Im not sure where these should go.
|
||||
buffer.Reset()
|
||||
performRequest(router, "POST", "/example")
|
||||
performRequest(router, "POST", "/post")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "POST")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/post")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PUT", "/example")
|
||||
performRequest(router, "PUT", "/put")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PUT")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/put")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "DELETE", "/example")
|
||||
performRequest(router, "DELETE", "/delete")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "DELETE")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/delete")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PATCH", "/example")
|
||||
performRequest(router, "PATCH", "/patch")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PATCH")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/patch")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "HEAD", "/example")
|
||||
performRequest(router, "HEAD", "/head")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "HEAD")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/head")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "OPTIONS", "/example")
|
||||
performRequest(router, "OPTIONS", "/options")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "/options")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/notfound")
|
||||
|
|
|
@ -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.
|
||||
// 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.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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).
|
||||
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||
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) {
|
||||
c.File(filepath)
|
||||
}
|
||||
|
||||
// Register GET (and HEAD) handler
|
||||
group.GET(relativePath, handler)
|
||||
group.HEAD(relativePath, handler)
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
|
@ -176,9 +179,8 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou
|
|||
handler := group.createStaticHandler(relativePath, fs)
|
||||
urlPattern := path.Join(relativePath, "/*filepath")
|
||||
|
||||
// Register GET and HEAD handlers
|
||||
// Register GET (and HEAD) handler
|
||||
group.GET(urlPattern, handler)
|
||||
group.HEAD(urlPattern, handler)
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ func TestRouterGroupBasicHandle(t *testing.T) {
|
|||
performRequestInGroup(t, http.MethodPut)
|
||||
performRequestInGroup(t, http.MethodPatch)
|
||||
performRequestInGroup(t, http.MethodDelete)
|
||||
performRequestInGroup(t, http.MethodHead)
|
||||
performRequestInGroup(t, http.MethodOptions)
|
||||
}
|
||||
|
||||
|
@ -70,9 +69,6 @@ func performRequestInGroup(t *testing.T, method string) {
|
|||
case http.MethodDelete:
|
||||
v1.DELETE("/test", handler)
|
||||
login.DELETE("/test", handler)
|
||||
case http.MethodHead:
|
||||
v1.HEAD("/test", handler)
|
||||
login.HEAD("/test", handler)
|
||||
case http.MethodOptions:
|
||||
v1.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())
|
||||
}
|
||||
|
||||
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) {
|
||||
router := New()
|
||||
assert.Panics(t, func() {
|
||||
|
|
|
@ -368,6 +368,18 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
|||
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) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
|
@ -494,7 +506,7 @@ func TestRouterStaticFSNotFound(t *testing.T) {
|
|||
assert.Equal(t, "non existent", w.Body.String())
|
||||
|
||||
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) {
|
||||
|
|
Loading…
Reference in New Issue