diff --git a/.travis.yml b/.travis.yml
index 644a178a..6532a334 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,13 +10,14 @@ git:
depth: 3
install:
- - go get -v github.com/kardianos/govendor
- - govendor sync
- - go get -u github.com/campoy/embedmd
+ - make install
script:
- - embedmd -d README.md
- - go test -v -covermode=count -coverprofile=coverage.out
+ - make vet
+ - make fmt-check
+ - make embedmd
+ - make misspell-check
+ - make test
after_success:
- bash <(curl -s https://codecov.io/bash)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 441df247..ee485ec3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,13 +6,22 @@
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
- [NEW] Improve README examples and add extra at examples folder
- [NEW] Improved support with App Engine
+- [NEW] Add custom template delimiters, see #860
+- [NEW] Add Template Func Maps, see #962
+- [NEW] Add \*context.Handler(), see #928
- [NEW] Add \*context.GetRawData()
- [NEW] Add \*context.GetHeader() (request)
- [NEW] Add \*context.AbortWithStatusJSON() (JSON content type)
+- [NEW] Add \*context.Keys type cast helpers
+- [NEW] Add \*context.ShouldBindWith()
+- [NEW] Add \*context.MustBindWith()
+- [NEW] Add \*engine.SetFuncMap()
+- [DEPRECATE] On next release: \*context.BindWith(), see #855
- [FIX] Refactor render
- [FIX] Reworked tests
- [FIX] logger now supports cygwin
- [FIX] Use X-Forwarded-For before X-Real-Ip
+- [FIX] time.Time binding (#904)
### Gin 1.1.4
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..9ba475a4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,61 @@
+GOFMT ?= gofmt "-s"
+PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
+GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
+
+all: build
+
+install: deps
+ govendor sync
+
+.PHONY: test
+test:
+ go test -v -covermode=count -coverprofile=coverage.out
+
+.PHONY: fmt
+fmt:
+ $(GOFMT) -w $(GOFILES)
+
+.PHONY: fmt-check
+fmt-check:
+ # get all go files and run go fmt on them
+ @diff=$$($(GOFMT) -d $(GOFILES)); \
+ if [ -n "$$diff" ]; then \
+ echo "Please run 'make fmt' and commit the result:"; \
+ echo "$${diff}"; \
+ exit 1; \
+ fi;
+
+vet:
+ go vet $(PACKAGES)
+
+deps:
+ @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+ go get -u github.com/kardianos/govendor; \
+ fi
+ @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+ go get -u github.com/campoy/embedmd; \
+ fi
+
+embedmd:
+ embedmd -d *.md
+
+.PHONY: lint
+lint:
+ @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+ go get -u github.com/golang/lint/golint; \
+ fi
+ for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
+
+.PHONY: misspell-check
+misspell-check:
+ @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+ go get -u github.com/client9/misspell/cmd/misspell; \
+ fi
+ misspell -error $(GOFILES)
+
+.PHONY: misspell
+misspell:
+ @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+ go get -u github.com/client9/misspell/cmd/misspell; \
+ fi
+ misspell -w $(GOFILES)
diff --git a/README.md b/README.md
index 535ee4eb..494cc8bf 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
-# Gin Web Framework
+# Gin Web Framework
-[](https://travis-ci.org/gin-gonic/gin) [](https://codecov.io/gh/gin-gonic/gin) [](https://goreportcard.com/report/github.com/gin-gonic/gin) [](https://godoc.org/github.com/gin-gonic/gin) [](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
+
+
+[](https://travis-ci.org/gin-gonic/gin)
+ [](https://codecov.io/gh/gin-gonic/gin)
+ [](https://goreportcard.com/report/github.com/gin-gonic/gin)
+ [](https://godoc.org/github.com/gin-gonic/gin)
+ [](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
@@ -13,7 +19,7 @@ $ cat test.go
```go
package main
-import "gopkg.in/gin-gonic/gin.v1"
+import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
@@ -82,13 +88,13 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
1. Download and install it:
```sh
-$ go get gopkg.in/gin-gonic/gin.v1
+$ go get github.com/gin-gonic/gin
```
2. Import it in your code:
```go
-import "gopkg.in/gin-gonic/gin.v1"
+import "github.com/gin-gonic/gin"
```
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
@@ -97,12 +103,36 @@ import "gopkg.in/gin-gonic/gin.v1"
import "net/http"
```
-4. (Optional) Use latest changes (note: they may be broken and/or unstable):
+### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
-```sh
-$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1
-$ git -C $GIN_PATH checkout develop
-$ git -C $GIN_PATH pull origin develop
+1. `go get` govendor
+
+```sh
+$ go get github.com/kardianos/govendor
+```
+2. Create your project folder and `cd` inside
+
+```sh
+$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_"
+```
+
+3. Vendor init your project and add gin
+
+```sh
+$ govendor init
+$ govendor add github.com/gin-gonic/gin@v1.2
+```
+
+4. Copy a starting template inside your project
+
+```sh
+$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* .
+```
+
+5. Run your project
+
+```sh
+$ go run main.go
```
## API Examples
@@ -451,7 +481,7 @@ func startPage(c *gin.Context) {
package main
import (
- "gopkg.in/gin-gonic/gin.v1"
+ "github.com/gin-gonic/gin"
)
type LoginForm struct {
@@ -463,7 +493,7 @@ func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration:
- // c.BindWith(&form, binding.Form)
+ // c.MustBindWith(&form, binding.Form)
// or you can simply use autobinding with Bind method:
var form LoginForm
// in this case proper binding will be automatically selected
@@ -622,6 +652,54 @@ func main() {
}
```
+You may use custom delims
+
+```go
+ r := gin.Default()
+ r.Delims("{[{", "}]}")
+ r.LoadHTMLGlob("/path/to/templates"))
+```
+
+#### Add custom template funcs
+
+main.go
+
+```go
+ ...
+
+ func formatAsDate(t time.Time) string {
+ year, month, day := t.Date()
+ return fmt.Sprintf("%d/%02d/%02d", year, month, day)
+ }
+
+ ...
+
+ router.SetFuncMap(template.FuncMap{
+ "formatAsDate": formatAsDate,
+ })
+
+ ...
+
+ router.GET("/raw", func(c *Context) {
+ c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
+ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
+ })
+ })
+
+ ...
+```
+
+raw.tmpl
+
+```html
+Date: {[{.now | formatAsDate}]}
+```
+
+Result:
+```
+Date: 2017/07/01
+```
+
### Multitemplate
Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
@@ -917,7 +995,7 @@ func main() {
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.
- With pull requests:
- - Open your pull request against develop
+ - Open your pull request against master
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integrations systems such as TravisCI.
- You should add/modify tests to cover your proposed code changes.
diff --git a/benchmarks_test.go b/benchmarks_test.go
index ebe9804c..a2c62ba3 100644
--- a/benchmarks_test.go
+++ b/benchmarks_test.go
@@ -1,3 +1,7 @@
+// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
package gin
import (
diff --git a/binding/binding.go b/binding/binding.go
index d3a2c97e..1dbf2460 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -48,19 +48,19 @@ var (
func Default(method, contentType string) Binding {
if method == "GET" {
return Form
- } else {
- switch contentType {
- case MIMEJSON:
- return JSON
- case MIMEXML, MIMEXML2:
- return XML
- case MIMEPROTOBUF:
- return ProtoBuf
- case MIMEMSGPACK, MIMEMSGPACK2:
- return MsgPack
- default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
- return Form
- }
+ }
+
+ switch contentType {
+ case MIMEJSON:
+ return JSON
+ case MIMEXML, MIMEXML2:
+ return XML
+ case MIMEPROTOBUF:
+ return ProtoBuf
+ case MIMEMSGPACK, MIMEMSGPACK2:
+ return MsgPack
+ default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
+ return Form
}
}
diff --git a/binding/binding_test.go b/binding/binding_test.go
index cf005948..d7cdf77a 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -12,9 +12,8 @@ import (
"github.com/gin-gonic/gin/binding/example"
"github.com/golang/protobuf/proto"
- "github.com/ugorji/go/codec"
-
"github.com/stretchr/testify/assert"
+ "github.com/ugorji/go/codec"
)
type FooStruct struct {
diff --git a/binding/default_validator.go b/binding/default_validator.go
index 760728bb..19885f16 100644
--- a/binding/default_validator.go
+++ b/binding/default_validator.go
@@ -1,3 +1,7 @@
+// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
package binding
import (
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index 1af8165e..34f12678 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -152,7 +152,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
if timeFormat == "" {
return errors.New("Blank time format")
}
-
+
if val == "" {
value.Set(reflect.ValueOf(time.Time{}))
return nil
diff --git a/binding/json.go b/binding/json.go
index 6e532443..486b9733 100644
--- a/binding/json.go
+++ b/binding/json.go
@@ -6,7 +6,6 @@ package binding
import (
"encoding/json"
-
"net/http"
)
diff --git a/binding/protobuf.go b/binding/protobuf.go
index 9f956228..c7eb84e9 100644
--- a/binding/protobuf.go
+++ b/binding/protobuf.go
@@ -5,10 +5,10 @@
package binding
import (
- "github.com/golang/protobuf/proto"
-
"io/ioutil"
"net/http"
+
+ "github.com/golang/protobuf/proto"
)
type protobufBinding struct{}
diff --git a/context.go b/context.go
index dd0dc5dc..5196bb13 100644
--- a/context.go
+++ b/context.go
@@ -16,9 +16,9 @@ import (
"strings"
"time"
+ "github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render"
- "gopkg.in/gin-contrib/sse.v0"
)
// Content-Type MIME of the most common data formats
@@ -85,6 +85,11 @@ func (c *Context) HandlerName() string {
return nameOfFunction(c.handlers.Last())
}
+// Handler returns the main handler.
+func (c *Context) Handler() HandlerFunc {
+ return c.handlers.Last()
+}
+
/************************************/
/*********** FLOW CONTROL ***********/
/************************************/
@@ -191,6 +196,94 @@ func (c *Context) MustGet(key string) interface{} {
panic("Key \"" + key + "\" does not exist")
}
+// GetString returns the value associated with the key as a string.
+func (c *Context) GetString(key string) (s string) {
+ if val, ok := c.Get(key); ok && val != nil {
+ s, _ = val.(string)
+ }
+ return
+}
+
+// GetBool returns the value associated with the key as a boolean.
+func (c *Context) GetBool(key string) (b bool) {
+ if val, ok := c.Get(key); ok && val != nil {
+ b, _ = val.(bool)
+ }
+ return
+}
+
+// GetInt returns the value associated with the key as an integer.
+func (c *Context) GetInt(key string) (i int) {
+ if val, ok := c.Get(key); ok && val != nil {
+ i, _ = val.(int)
+ }
+ return
+}
+
+// GetInt64 returns the value associated with the key as an integer.
+func (c *Context) GetInt64(key string) (i64 int64) {
+ if val, ok := c.Get(key); ok && val != nil {
+ i64, _ = val.(int64)
+ }
+ return
+}
+
+// GetFloat64 returns the value associated with the key as a float64.
+func (c *Context) GetFloat64(key string) (f64 float64) {
+ if val, ok := c.Get(key); ok && val != nil {
+ f64, _ = val.(float64)
+ }
+ return
+}
+
+// GetTime returns the value associated with the key as time.
+func (c *Context) GetTime(key string) (t time.Time) {
+ if val, ok := c.Get(key); ok && val != nil {
+ t, _ = val.(time.Time)
+ }
+ return
+}
+
+// GetDuration returns the value associated with the key as a duration.
+func (c *Context) GetDuration(key string) (d time.Duration) {
+ if val, ok := c.Get(key); ok && val != nil {
+ d, _ = val.(time.Duration)
+ }
+ return
+}
+
+// GetStringSlice returns the value associated with the key as a slice of strings.
+func (c *Context) GetStringSlice(key string) (ss []string) {
+ if val, ok := c.Get(key); ok && val != nil {
+ ss, _ = val.([]string)
+ }
+ return
+}
+
+// GetStringMap returns the value associated with the key as a map of interfaces.
+func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {
+ if val, ok := c.Get(key); ok && val != nil {
+ sm, _ = val.(map[string]interface{})
+ }
+ return
+}
+
+// GetStringMapString returns the value associated with the key as a map of strings.
+func (c *Context) GetStringMapString(key string) (sms map[string]string) {
+ if val, ok := c.Get(key); ok && val != nil {
+ sms, _ = val.(map[string]string)
+ }
+ return
+}
+
+// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
+func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {
+ if val, ok := c.Get(key); ok && val != nil {
+ smss, _ = val.(map[string][]string)
+ }
+ return
+}
+
/************************************/
/************ INPUT DATA ************/
/************************************/
@@ -341,22 +434,30 @@ func (c *Context) MultipartForm() (*multipart.Form, error) {
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
- return c.BindWith(obj, b)
+ return c.MustBindWith(obj, b)
}
-// BindJSON is a shortcut for c.BindWith(obj, binding.JSON)
+// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON)
func (c *Context) BindJSON(obj interface{}) error {
- return c.BindWith(obj, binding.JSON)
+ return c.MustBindWith(obj, binding.JSON)
}
-// BindWith binds the passed struct pointer using the specified binding engine.
+// MustBindWith binds the passed struct pointer using the specified binding
+// engine. It will abort the request with HTTP 400 if any error ocurrs.
// See the binding package.
-func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
- if err := b.Bind(c.Request, obj); err != nil {
+func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
+ if err = c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(400, err).SetType(ErrorTypeBind)
- return err
}
- return nil
+
+ return
+}
+
+// ShouldBindWith binds the passed struct pointer using the specified binding
+// engine.
+// See the binding package.
+func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
+ return b.Bind(c.Request, obj)
}
// ClientIP implements a best effort algorithm to return the real client IP, it parses
diff --git a/context_appengine.go b/context_appengine.go
index d9cb22f3..38c189a0 100644
--- a/context_appengine.go
+++ b/context_appengine.go
@@ -1,5 +1,9 @@
// +build appengine
+// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
package gin
func init() {
diff --git a/context_test.go b/context_test.go
index b4c1f08d..fe31f09a 100644
--- a/context_test.go
+++ b/context_test.go
@@ -12,13 +12,14 @@ import (
"mime/multipart"
"net/http"
"net/http/httptest"
+ "reflect"
"strings"
"testing"
"time"
+ "github.com/gin-contrib/sse"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
- "gopkg.in/gin-contrib/sse.v0"
)
var _ context.Context = &Context{}
@@ -168,6 +169,85 @@ func TestContextSetGetValues(t *testing.T) {
}
+func TestContextGetString(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("string", "this is a string")
+ assert.Equal(t, "this is a string", c.GetString("string"))
+}
+
+func TestContextSetGetBool(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("bool", true)
+ assert.Equal(t, true, c.GetBool("bool"))
+}
+
+func TestContextGetInt(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("int", 1)
+ assert.Equal(t, 1, c.GetInt("int"))
+}
+
+func TestContextGetInt64(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("int64", int64(42424242424242))
+ assert.Equal(t, int64(42424242424242), c.GetInt64("int64"))
+}
+
+func TestContextGetFloat64(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("float64", 4.2)
+ assert.Equal(t, 4.2, c.GetFloat64("float64"))
+}
+
+func TestContextGetTime(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ t1, _ := time.Parse("1/2/2006 15:04:05", "01/01/2017 12:00:00")
+ c.Set("time", t1)
+ assert.Equal(t, t1, c.GetTime("time"))
+}
+
+func TestContextGetDuration(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("duration", time.Second)
+ assert.Equal(t, time.Second, c.GetDuration("duration"))
+}
+
+func TestContextGetStringSlice(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.Set("slice", []string{"foo"})
+ assert.Equal(t, []string{"foo"}, c.GetStringSlice("slice"))
+}
+
+func TestContextGetStringMap(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ var m = make(map[string]interface{})
+ m["foo"] = 1
+ c.Set("map", m)
+
+ assert.Equal(t, m, c.GetStringMap("map"))
+ assert.Equal(t, 1, c.GetStringMap("map")["foo"])
+}
+
+func TestContextGetStringMapString(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ var m = make(map[string]string)
+ m["foo"] = "bar"
+ c.Set("map", m)
+
+ assert.Equal(t, m, c.GetStringMapString("map"))
+ assert.Equal(t, "bar", c.GetStringMapString("map")["foo"])
+}
+
+func TestContextGetStringMapStringSlice(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ var m = make(map[string][]string)
+ m["foo"] = []string{"foo"}
+ c.Set("map", m)
+
+ assert.Equal(t, m, c.GetStringMapStringSlice("map"))
+ assert.Equal(t, []string{"foo"}, c.GetStringMapStringSlice("map")["foo"])
+}
+
func TestContextCopy(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.index = 2
@@ -198,6 +278,17 @@ func handlerNameTest(c *Context) {
}
+var handlerTest HandlerFunc = func(c *Context) {
+
+}
+
+func TestContextHandler(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.handlers = HandlersChain{func(c *Context) {}, handlerTest}
+
+ assert.Equal(t, reflect.ValueOf(handlerTest).Pointer(), reflect.ValueOf(c.Handler()).Pointer())
+}
+
func TestContextQuery(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil)
@@ -384,12 +475,21 @@ func TestContextSetCookie(t *testing.T) {
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
}
+func TestContextSetCookiePathEmpty(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.SetCookie("user", "gin", 1, "", "localhost", true, true)
+ assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
+}
+
func TestContextGetCookie(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user")
assert.Equal(t, cookie, "gin")
+
+ _, err := c.Cookie("nokey")
+ assert.Error(t, err)
}
func TestContextBodyAllowedForStatus(t *testing.T) {
@@ -737,6 +837,68 @@ func TestContextRenderRedirectAll(t *testing.T) {
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
}
+func TestContextNegotiationWithJSON(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+ c.Request, _ = http.NewRequest("POST", "", nil)
+
+ c.Negotiate(200, Negotiate{
+ Offered: []string{MIMEJSON, MIMEXML},
+ Data: H{"foo": "bar"},
+ })
+
+ assert.Equal(t, 200, w.Code)
+ assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
+ assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
+}
+
+func TestContextNegotiationWithXML(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+ c.Request, _ = http.NewRequest("POST", "", nil)
+
+ c.Negotiate(200, Negotiate{
+ Offered: []string{MIMEXML, MIMEJSON},
+ Data: H{"foo": "bar"},
+ })
+
+ assert.Equal(t, 200, w.Code)
+ assert.Equal(t, "", w.Body.String())
+ assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
+}
+
+func TestContextNegotiationWithHTML(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, router := CreateTestContext(w)
+ c.Request, _ = http.NewRequest("POST", "", nil)
+ templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
+ router.SetHTMLTemplate(templ)
+
+ c.Negotiate(200, Negotiate{
+ Offered: []string{MIMEHTML},
+ Data: H{"name": "gin"},
+ HTMLName: "t",
+ })
+
+ assert.Equal(t, 200, w.Code)
+ assert.Equal(t, "Hello gin", w.Body.String())
+ assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
+}
+
+func TestContextNegotiationNotSupport(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+ c.Request, _ = http.NewRequest("POST", "", nil)
+
+ c.Negotiate(200, Negotiate{
+ Offered: []string{MIMEPOSTForm},
+ })
+
+ assert.Equal(t, 406, w.Code)
+ assert.Equal(t, c.index, abortIndex)
+ assert.True(t, c.IsAborted())
+}
+
func TestContextNegotiationFormat(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "", nil)
diff --git a/debug_test.go b/debug_test.go
index deceaa6e..c30081c5 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -7,6 +7,7 @@ package gin
import (
"bytes"
"errors"
+ "html/template"
"io"
"log"
"os"
@@ -66,6 +67,25 @@ func TestDebugPrintRoutes(t *testing.T) {
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String())
}
+func TestDebugPrintLoadTemplate(t *testing.T) {
+ var w bytes.Buffer
+ setup(&w)
+ defer teardown()
+
+ templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl"))
+ debugPrintLoadTemplate(templ)
+ assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n")
+}
+
+func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
+ var w bytes.Buffer
+ setup(&w)
+ defer teardown()
+
+ debugPrintWARNINGSetHTMLTemplate()
+ assert.Equal(t, w.String(), "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n")
+}
+
func setup(w io.Writer) {
SetMode(DebugMode)
log.SetOutput(w)
diff --git a/deprecated.go b/deprecated.go
index 0488a9b0..27e8f558 100644
--- a/deprecated.go
+++ b/deprecated.go
@@ -4,9 +4,22 @@
package gin
-import "log"
+import (
+ "github.com/gin-gonic/gin/binding"
+ "log"
+)
func (c *Context) GetCookie(name string) (string, error) {
log.Println("GetCookie() method is deprecated. Use Cookie() instead.")
return c.Cookie(name)
}
+
+// BindWith binds the passed struct pointer using the specified binding engine.
+// See the binding package.
+func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
+ log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
+ be deprecated, please check issue #662 and either use MustBindWith() if you
+ want HTTP 400 to be automatically returned if any error occur, of use
+ ShouldBindWith() if you need to manage the error.`)
+ return c.MustBindWith(obj, b)
+}
diff --git a/errors.go b/errors.go
index 694b428a..896af6fc 100644
--- a/errors.go
+++ b/errors.go
@@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool {
return (msg.Type & flags) > 0
}
-// Returns a readonly copy filterd the byte.
+// Returns a readonly copy filtered the byte.
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
if len(a) == 0 {
diff --git a/errors_test.go b/errors_test.go
index c9a3407b..1aa0cdde 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -54,6 +54,13 @@ func TestError(t *testing.T) {
"status": "200",
"data": "some data",
})
+
+ type customError struct {
+ status string
+ data string
+ }
+ err.SetMeta(customError{status: "200", data: "other data"})
+ assert.Equal(t, err.JSON(), customError{status: "200", data: "other data"})
}
func TestErrorSlice(t *testing.T) {
diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go
index a5e17962..da7e4ae4 100644
--- a/examples/app-engine/hello.go
+++ b/examples/app-engine/hello.go
@@ -1,8 +1,9 @@
package hello
import (
- "github.com/gin-gonic/gin"
"net/http"
+
+ "github.com/gin-gonic/gin"
)
// This function's name is a must. App Engine uses it to drive the requests properly.
diff --git a/examples/realtime-advanced/Makefile b/examples/realtime-advanced/Makefile
new file mode 100644
index 00000000..104ce809
--- /dev/null
+++ b/examples/realtime-advanced/Makefile
@@ -0,0 +1,10 @@
+all: deps build
+
+.PHONY: deps
+deps:
+ go get -d -v github.com/dustin/go-broadcast/...
+ go get -d -v github.com/manucorporat/stats/...
+
+.PHONY: build
+build: deps
+ go build -o realtime-advanced main.go rooms.go routes.go stats.go
diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go
index b1877565..86da9bea 100644
--- a/examples/realtime-advanced/routes.go
+++ b/examples/realtime-advanced/routes.go
@@ -11,7 +11,6 @@ import (
)
func rateLimit(c *gin.Context) {
-
ip := c.ClientIP()
value := int(ips.Add(ip, 1))
if value%50 == 0 {
diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go
index 554ab86a..4bca3ae4 100644
--- a/examples/realtime-advanced/stats.go
+++ b/examples/realtime-advanced/stats.go
@@ -8,11 +8,13 @@ import (
"github.com/manucorporat/stats"
)
-var ips = stats.New()
-var messages = stats.New()
-var users = stats.New()
-var mutexStats sync.RWMutex
-var savedStats map[string]uint64
+var (
+ ips = stats.New()
+ messages = stats.New()
+ users = stats.New()
+ mutexStats sync.RWMutex
+ savedStats map[string]uint64
+)
func statsWorker() {
c := time.Tick(1 * time.Second)
diff --git a/examples/realtime-chat/Makefile b/examples/realtime-chat/Makefile
new file mode 100644
index 00000000..dea583df
--- /dev/null
+++ b/examples/realtime-chat/Makefile
@@ -0,0 +1,9 @@
+all: deps build
+
+.PHONY: deps
+deps:
+ go get -d -v github.com/dustin/go-broadcast/...
+
+.PHONY: build
+build: deps
+ go build -o realtime-chat main.go rooms.go template.go
diff --git a/fixtures/basic/hello.tmpl b/fixtures/basic/hello.tmpl
new file mode 100644
index 00000000..0767ef3f
--- /dev/null
+++ b/fixtures/basic/hello.tmpl
@@ -0,0 +1 @@
+