diff --git a/README.md b/README.md index 49b044aa..aa5043aa 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Uri](#bind-uri) + - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) @@ -910,6 +911,43 @@ $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 $ curl -v localhost:8088/thinkerou/not-uuid ``` +### Bind Header + +```go +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" +) + +type testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` +} + +func main() { + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} + + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(200, err) + } + + fmt.Printf("%#v\n", h) + c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) + + r.Run() + +// client +// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ +// output +// {"Domain":"music","Rate":300} +} +``` + ### Bind HTML checkboxes See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) diff --git a/binding/binding.go b/binding/binding.go index 520c5109..6d58c3cd 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -78,6 +78,7 @@ var ( MsgPack = msgpackBinding{} YAML = yamlBinding{} Uri = uriBinding{} + Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method diff --git a/binding/binding_test.go b/binding/binding_test.go index 6710e42b..827518f9 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -667,6 +667,31 @@ func TestExistsFails(t *testing.T) { assert.Error(t, err) } +func TestHeaderBinding(t *testing.T) { + h := Header + assert.Equal(t, "header", h.Name()) + + type tHeader struct { + Limit int `header:"limit"` + } + + var theader tHeader + req := requestWithBody("GET", "/", "") + req.Header.Add("limit", "1000") + assert.NoError(t, h.Bind(req, &theader)) + assert.Equal(t, 1000, theader.Limit) + + req = requestWithBody("GET", "/", "") + req.Header.Add("fail", `{fail:fail}`) + + type failStruct struct { + Fail map[string]interface{} `header:"fail"` + } + + err := h.Bind(req, &failStruct{}) + assert.Error(t, err) +} + func TestUriBinding(t *testing.T) { b := Uri assert.Equal(t, "uri", b.Name()) diff --git a/binding/header.go b/binding/header.go new file mode 100644 index 00000000..179ce4ea --- /dev/null +++ b/binding/header.go @@ -0,0 +1,34 @@ +package binding + +import ( + "net/http" + "net/textproto" + "reflect" +) + +type headerBinding struct{} + +func (headerBinding) Name() string { + return "header" +} + +func (headerBinding) Bind(req *http.Request, obj interface{}) error { + + if err := mapHeader(obj, req.Header); err != nil { + return err + } + + return validate(obj) +} + +func mapHeader(ptr interface{}, h map[string][]string) error { + return mappingByPtr(ptr, headerSource(h), "header") +} + +type headerSource map[string][]string + +var _ setter = headerSource(nil) + +func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) +} diff --git a/context.go b/context.go index 77cdc182..d9fcc285 100644 --- a/context.go +++ b/context.go @@ -583,6 +583,11 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). +func (c *Context) BindHeader(obj interface{}) error { + return c.MustBindWith(obj, binding.Header) +} + // BindUri binds the passed struct pointer using binding.Uri. // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { @@ -637,6 +642,11 @@ func (c *Context) ShouldBindYAML(obj interface{}) error { return c.ShouldBindWith(obj, binding.YAML) } +// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). +func (c *Context) ShouldBindHeader(obj interface{}) error { + return c.ShouldBindWith(obj, binding.Header) +} + // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj interface{}) error { m := make(map[string][]string) diff --git a/context_test.go b/context_test.go index b6ecb280..439e8ee6 100644 --- a/context_test.go +++ b/context_test.go @@ -1436,6 +1436,28 @@ func TestContextBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.BindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1543,6 +1565,28 @@ func TestContextShouldBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.ShouldBindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w)