Pārlūkot izejas kodu

Merge branch 'master' into develop

Javier Provecho Fernandez 8 gadi atpakaļ
vecāks
revīzija
d875f07409

+ 6 - 5
.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)

+ 9 - 0
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
 

+ 61 - 0
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)

+ 91 - 13
README.md

@@ -1,6 +1,12 @@
-# Gin Web Framework <img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
+# Gin Web Framework
 
-[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
+<img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.png">
+
+[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
+ [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
+ [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
+ [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
+ [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](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.

+ 4 - 0
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 (

+ 13 - 13
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
 	}
 }
 

+ 1 - 2
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 {

+ 4 - 0
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 (

+ 1 - 1
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

+ 0 - 1
binding/json.go

@@ -6,7 +6,6 @@ package binding
 
 import (
 	"encoding/json"
-
 	"net/http"
 )
 

+ 2 - 2
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{}

+ 110 - 9
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

+ 4 - 0
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() {

+ 163 - 1
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, "<map><foo>bar</foo></map>", 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)

+ 20 - 0
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)

+ 14 - 1
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)
+}

+ 1 - 1
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 {

+ 7 - 0
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) {

+ 2 - 1
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.

+ 10 - 0
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

+ 0 - 1
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 {

+ 7 - 5
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)

+ 9 - 0
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

+ 1 - 0
fixtures/basic/hello.tmpl

@@ -0,0 +1 @@
+<h1>Hello {[{.name}]}</h1>

+ 1 - 0
fixtures/basic/raw.tmpl

@@ -0,0 +1 @@
+Date: {[{.now | formatAsDate}]}

+ 4 - 0
fs.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 (

+ 22 - 8
gin.go

@@ -45,7 +45,9 @@ type (
 	// Create an instance of Engine, by using New() or Default()
 	Engine struct {
 		RouterGroup
+		delims      render.Delims
 		HTMLRender  render.HTMLRender
+		FuncMap     template.FuncMap
 		allNoRoute  HandlersChain
 		allNoMethod HandlersChain
 		noRoute     HandlersChain
@@ -111,6 +113,7 @@ func New() *Engine {
 			basePath: "/",
 			root:     true,
 		},
+		FuncMap:                template.FuncMap{},
 		RedirectTrailingSlash:  true,
 		RedirectFixedPath:      false,
 		HandleMethodNotAllowed: false,
@@ -119,6 +122,7 @@ func New() *Engine {
 		UseRawPath:             false,
 		UnescapePathValues:     true,
 		trees:                  make(methodTrees, 0, 9),
+		delims:                 render.Delims{"{{", "}}"},
 	}
 	engine.RouterGroup.engine = engine
 	engine.pool.New = func() interface{} {
@@ -138,21 +142,26 @@ func (engine *Engine) allocateContext() *Context {
 	return &Context{engine: engine}
 }
 
+func (engine *Engine) Delims(left, right string) *Engine {
+	engine.delims = render.Delims{left, right}
+	return engine
+}
+
 func (engine *Engine) LoadHTMLGlob(pattern string) {
 	if IsDebugging() {
-		debugPrintLoadTemplate(template.Must(template.ParseGlob(pattern)))
-		engine.HTMLRender = render.HTMLDebug{Glob: pattern}
+		debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))
+		engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
 	} else {
-		templ := template.Must(template.ParseGlob(pattern))
+		templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))
 		engine.SetHTMLTemplate(templ)
 	}
 }
 
 func (engine *Engine) LoadHTMLFiles(files ...string) {
 	if IsDebugging() {
-		engine.HTMLRender = render.HTMLDebug{Files: files}
+		engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
 	} else {
-		templ := template.Must(template.ParseFiles(files...))
+		templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
 		engine.SetHTMLTemplate(templ)
 	}
 }
@@ -161,7 +170,12 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
 	if len(engine.trees) > 0 {
 		debugPrintWARNINGSetHTMLTemplate()
 	}
-	engine.HTMLRender = render.HTMLProduction{Template: templ}
+
+	engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
+}
+
+func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
+	engine.FuncMap = funcMap
 }
 
 // NoRoute adds handlers for NoRoute. It return a 404 code by default.
@@ -318,8 +332,8 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
 				context.Next()
 				context.writermem.WriteHeaderNow()
 				return
-
-			} else if httpMethod != "CONNECT" && path != "/" {
+			}
+			if httpMethod != "CONNECT" && path != "/" {
 				if tsr && engine.RedirectTrailingSlash {
 					redirectTrailingSlash(context)
 					return

+ 1 - 1
ginS/README.md

@@ -1,4 +1,4 @@
-#Gin Default Server
+# Gin Default Server
 
 This is API experiment for Gin.
 

+ 6 - 2
gin_integration_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 (
@@ -6,18 +10,18 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
+	"net/http/httptest"
 	"os"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"net/http/httptest"
 )
 
 func testRequest(t *testing.T, url string) {
 	resp, err := http.Get(url)
-	defer resp.Body.Close()
 	assert.NoError(t, err)
+	defer resp.Body.Close()
 
 	body, ioerr := ioutil.ReadAll(resp.Body)
 	assert.NoError(t, ioerr)

+ 111 - 1
gin_test.go

@@ -5,14 +5,98 @@
 package gin
 
 import (
+	"fmt"
+	"html/template"
+	"io/ioutil"
+	"net/http"
 	"reflect"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 )
 
+func formatAsDate(t time.Time) string {
+	year, month, day := t.Date()
+	return fmt.Sprintf("%d/%02d/%02d", year, month, day)
+}
+
+func setupHTMLFiles(t *testing.T) func() {
+	go func() {
+		SetMode(TestMode)
+		router := New()
+		router.Delims("{[{", "}]}")
+		router.SetFuncMap(template.FuncMap{
+			"formatAsDate": formatAsDate,
+		})
+		router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl")
+		router.GET("/test", func(c *Context) {
+			c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
+		})
+		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),
+			})
+		})
+		router.Run(":8888")
+	}()
+	t.Log("waiting 1 second for server startup")
+	time.Sleep(1 * time.Second)
+	return func() {}
+}
+
+func setupHTMLGlob(t *testing.T) func() {
+	go func() {
+		SetMode(DebugMode)
+		router := New()
+		router.Delims("{[{", "}]}")
+		router.SetFuncMap(template.FuncMap{
+			"formatAsDate": formatAsDate,
+		})
+		router.LoadHTMLGlob("./fixtures/basic/*")
+		router.GET("/test", func(c *Context) {
+			c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
+		})
+		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),
+			})
+		})
+		router.Run(":8888")
+	}()
+	t.Log("waiting 1 second for server startup")
+	time.Sleep(1 * time.Second)
+	return func() {}
+}
+
 //TODO
-// func (engine *Engine) LoadHTMLGlob(pattern string) {
+func TestLoadHTMLGlob(t *testing.T) {
+	td := setupHTMLGlob(t)
+	res, err := http.Get("http://127.0.0.1:8888/test")
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	resp, _ := ioutil.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
+
+	td()
+}
+
+func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
+	time.Now()
+	td := setupHTMLGlob(t)
+	res, err := http.Get("http://127.0.0.1:8888/raw")
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	resp, _ := ioutil.ReadAll(res.Body)
+	assert.Equal(t, "Date: 2017/07/01\n", string(resp[:]))
+
+	td()
+}
+
 // func (engine *Engine) LoadHTMLFiles(files ...string) {
 // func (engine *Engine) RunTLS(addr string, cert string, key string) error {
 
@@ -42,6 +126,32 @@ func TestCreateEngine(t *testing.T) {
 // 	SetMode(TestMode)
 // }
 
+func TestLoadHTMLFiles(t *testing.T) {
+	td := setupHTMLFiles(t)
+	res, err := http.Get("http://127.0.0.1:8888/test")
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	resp, _ := ioutil.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
+	td()
+}
+
+func TestLoadHTMLFilesFuncMap(t *testing.T) {
+	time.Now()
+	td := setupHTMLFiles(t)
+	res, err := http.Get("http://127.0.0.1:8888/raw")
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	resp, _ := ioutil.ReadAll(res.Body)
+	assert.Equal(t, "Date: 2017/07/01\n", string(resp[:]))
+
+	td()
+}
+
 func TestLoadHTMLReleaseMode(t *testing.T) {
 
 }

BIN
logo.jpg


BIN
logo.png


+ 1 - 2
middleware_test.go

@@ -7,11 +7,10 @@ package gin
 import (
 	"errors"
 	"strings"
-
 	"testing"
 
+	"github.com/gin-contrib/sse"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/gin-contrib/sse.v0"
 )
 
 func TestMiddlewareGeneralCase(t *testing.T) {

+ 1 - 1
path.go

@@ -1,7 +1,7 @@
 // Copyright 2013 Julien Schmidt. All rights reserved.
 // Based on the path package, Copyright 2009 The Go Authors.
 // Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.
 
 package gin
 

+ 1 - 1
path_test.go

@@ -1,7 +1,7 @@
 // Copyright 2013 Julien Schmidt. All rights reserved.
 // Based on the path package, Copyright 2009 The Go Authors.
 // Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
 
 package gin
 

+ 15 - 4
render/html.go

@@ -10,17 +10,25 @@ import (
 )
 
 type (
+	Delims struct {
+		Left  string
+		Right string
+	}
+
 	HTMLRender interface {
 		Instance(string, interface{}) Render
 	}
 
 	HTMLProduction struct {
 		Template *template.Template
+		Delims   Delims
 	}
 
 	HTMLDebug struct {
-		Files []string
-		Glob  string
+		Files   []string
+		Glob    string
+		Delims  Delims
+		FuncMap template.FuncMap
 	}
 
 	HTML struct {
@@ -48,11 +56,14 @@ func (r HTMLDebug) Instance(name string, data interface{}) Render {
 	}
 }
 func (r HTMLDebug) loadTemplate() *template.Template {
+	if r.FuncMap == nil {
+		r.FuncMap = template.FuncMap{}
+	}
 	if len(r.Files) > 0 {
-		return template.Must(template.ParseFiles(r.Files...))
+		return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
 	}
 	if len(r.Glob) > 0 {
-		return template.Must(template.ParseGlob(r.Glob))
+		return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
 	}
 	panic("the HTML debug render was created without files or glob pattern")
 }

+ 4 - 0
test_helpers.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 (

+ 3 - 2
tree.go

@@ -1,6 +1,6 @@
 // Copyright 2013 Julien Schmidt. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
 
 package gin
 
@@ -432,7 +432,8 @@ walk: // Outer loop for walking the tree
 
 					if handlers = n.handlers; handlers != nil {
 						return
-					} else if len(n.children) == 1 {
+					}
+					if len(n.children) == 1 {
 						// No handle found. Check if a handle for this path + a
 						// trailing slash exists for TSR recommendation
 						n = n.children[0]

+ 1 - 1
tree_test.go

@@ -1,6 +1,6 @@
 // Copyright 2013 Julien Schmidt. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
 
 package gin
 

+ 29 - 11
vendor/vendor.json

@@ -1,5 +1,5 @@
 {
-	"comment": "v1.1.4",
+	"comment": "v1.2",
 	"ignore": "test",
 	"package": [
 		{
@@ -16,10 +16,22 @@
 			"revisionTime": "2014-06-27T04:00:55Z"
 		},
 		{
-			"checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=",
+			"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
+			"path": "github.com/gin-contrib/sse",
+			"revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae",
+			"revisionTime": "2017-01-09T09:34:21Z"
+		},
+		{
+			"checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=",
+			"path": "github.com/gin-gonic/autotls",
+			"revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d",
+			"revisionTime": "2017-04-16T09:39:34Z"
+		},
+		{
+			"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
 			"path": "github.com/golang/protobuf/proto",
-			"revision": "8ee79997227bf9b34611aee7946ae64735e6fd93",
-			"revisionTime": "2016-11-17T03:31:26Z"
+			"revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55",
+			"revisionTime": "2017-06-01T23:02:30Z"
 		},
 		{
 			"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
@@ -53,6 +65,18 @@
 			"revision": "c88ee250d0221a57af388746f5cf03768c21d6e2",
 			"revisionTime": "2017-02-15T20:11:44Z"
 		},
+		{
+			"checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=",
+			"path": "golang.org/x/crypto/acme",
+			"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
+			"revisionTime": "2017-06-19T06:03:41Z"
+		},
+		{
+			"checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=",
+			"path": "golang.org/x/crypto/acme/autocert",
+			"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
+			"revisionTime": "2017-06-19T06:03:41Z"
+		},
 		{
 			"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
 			"comment": "release-branch.go1.7",
@@ -61,17 +85,11 @@
 			"revisionTime": "2016-10-18T08:54:36Z"
 		},
 		{
-			"checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=",
+			"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
 			"path": "golang.org/x/sys/unix",
 			"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
 			"revisionTime": "2017-03-08T15:04:45Z"
 		},
-		{
-			"checksumSHA1": "pyAPYrymvmZl0M/Mr4yfjOQjA8I=",
-			"path": "gopkg.in/gin-contrib/sse.v0",
-			"revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae",
-			"revisionTime": "2017-01-09T09:34:21Z"
-		},
 		{
 			"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
 			"comment": "v8.18.1",