Skip to content

Commit

Permalink
support bind http header param #1956 (#1957)
Browse files Browse the repository at this point in the history
* support bind http header param #1956

update #1956
```
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}
}
```

* add unit test

* Modify the code to get the http header

When the http header is obtained in the standard library,
the key value will be modified by the CanonicalMIMEHeaderKey function,
and finally the value of the http header will be obtained from the map.
As follows.
```go
func (h MIMEHeader) Get(key string) string {
        // ...
         v := h[CanonicalMIMEHeaderKey(key)]
        // ...
}
```

This pr also follows this modification

* Thanks to vkd for suggestions, modifying code

* Increase test coverage

env GOPATH=`pwd` go test github.com/gin-gonic/gin/binding -coverprofile=cover.prof
ok  	github.com/gin-gonic/gin/binding	0.015s	coverage: 100.0% of statements

* Rollback check code

* add use case to README.md
  • Loading branch information
guonaihong authored and thinkerou committed Jun 27, 2019
1 parent 09a3650 commit f98b339
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 0 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions binding/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)

// Default returns the appropriate Binding instance based on the HTTP method
Expand Down
25 changes: 25 additions & 0 deletions binding/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
34 changes: 34 additions & 0 deletions binding/header.go
Original file line number Diff line number Diff line change
@@ -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)
}
10 changes: 10 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f98b339

Please sign in to comment.