Merge branch 'master' into develop

This commit is contained in:
Javier Provecho Fernandez 2017-07-02 14:23:55 +02:00 committed by GitHub
commit d875f07409
40 changed files with 740 additions and 90 deletions

View File

@ -10,13 +10,14 @@ git:
depth: 3 depth: 3
install: install:
- go get -v github.com/kardianos/govendor - make install
- govendor sync
- go get -u github.com/campoy/embedmd
script: script:
- embedmd -d README.md - make vet
- go test -v -covermode=count -coverprofile=coverage.out - make fmt-check
- make embedmd
- make misspell-check
- make test
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - bash <(curl -s https://codecov.io/bash)

View File

@ -6,13 +6,22 @@
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Add support for Let's Encrypt via gin-gonic/autotls
- [NEW] Improve README examples and add extra at examples folder - [NEW] Improve README examples and add extra at examples folder
- [NEW] Improved support with App Engine - [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.GetRawData()
- [NEW] Add \*context.GetHeader() (request) - [NEW] Add \*context.GetHeader() (request)
- [NEW] Add \*context.AbortWithStatusJSON() (JSON content type) - [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] Refactor render
- [FIX] Reworked tests - [FIX] Reworked tests
- [FIX] logger now supports cygwin - [FIX] logger now supports cygwin
- [FIX] Use X-Forwarded-For before X-Real-Ip - [FIX] Use X-Forwarded-For before X-Real-Ip
- [FIX] time.Time binding (#904)
### Gin 1.1.4 ### Gin 1.1.4

61
Makefile Normal file
View File

@ -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)

104
README.md
View File

@ -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. 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 ```go
package main package main
import "gopkg.in/gin-gonic/gin.v1" import "github.com/gin-gonic/gin"
func main() { func main() {
r := gin.Default() r := gin.Default()
@ -82,13 +88,13 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
1. Download and install it: 1. Download and install it:
```sh ```sh
$ go get gopkg.in/gin-gonic/gin.v1 $ go get github.com/gin-gonic/gin
``` ```
2. Import it in your code: 2. Import it in your code:
```go ```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`. 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" 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 1. `go get` govendor
$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1
$ git -C $GIN_PATH checkout develop ```sh
$ git -C $GIN_PATH pull origin develop $ 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 ## API Examples
@ -451,7 +481,7 @@ func startPage(c *gin.Context) {
package main package main
import ( import (
"gopkg.in/gin-gonic/gin.v1" "github.com/gin-gonic/gin"
) )
type LoginForm struct { type LoginForm struct {
@ -463,7 +493,7 @@ func main() {
router := gin.Default() router := gin.Default()
router.POST("/login", func(c *gin.Context) { router.POST("/login", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration: // 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: // or you can simply use autobinding with Bind method:
var form LoginForm var form LoginForm
// in this case proper binding will be automatically selected // 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 ### 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`. 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. - Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them. - Review existing issues and provide feedback or react to them.
- With pull requests: - 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. - 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. - 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. - You should add/modify tests to cover your proposed code changes.

View File

@ -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 package gin
import ( import (

View File

@ -48,19 +48,19 @@ var (
func Default(method, contentType string) Binding { func Default(method, contentType string) Binding {
if method == "GET" { if method == "GET" {
return Form return Form
} else { }
switch contentType {
case MIMEJSON: switch contentType {
return JSON case MIMEJSON:
case MIMEXML, MIMEXML2: return JSON
return XML case MIMEXML, MIMEXML2:
case MIMEPROTOBUF: return XML
return ProtoBuf case MIMEPROTOBUF:
case MIMEMSGPACK, MIMEMSGPACK2: return ProtoBuf
return MsgPack case MIMEMSGPACK, MIMEMSGPACK2:
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: return MsgPack
return Form default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
} return Form
} }
} }

View File

@ -12,9 +12,8 @@ import (
"github.com/gin-gonic/gin/binding/example" "github.com/gin-gonic/gin/binding/example"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/ugorji/go/codec"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
) )
type FooStruct struct { type FooStruct struct {

View File

@ -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 package binding
import ( import (

View File

@ -152,7 +152,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
if timeFormat == "" { if timeFormat == "" {
return errors.New("Blank time format") return errors.New("Blank time format")
} }
if val == "" { if val == "" {
value.Set(reflect.ValueOf(time.Time{})) value.Set(reflect.ValueOf(time.Time{}))
return nil return nil

View File

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

View File

@ -5,10 +5,10 @@
package binding package binding
import ( import (
"github.com/golang/protobuf/proto"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/golang/protobuf/proto"
) )
type protobufBinding struct{} type protobufBinding struct{}

View File

@ -16,9 +16,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
"gopkg.in/gin-contrib/sse.v0"
) )
// Content-Type MIME of the most common data formats // Content-Type MIME of the most common data formats
@ -85,6 +85,11 @@ func (c *Context) HandlerName() string {
return nameOfFunction(c.handlers.Last()) return nameOfFunction(c.handlers.Last())
} }
// Handler returns the main handler.
func (c *Context) Handler() HandlerFunc {
return c.handlers.Last()
}
/************************************/ /************************************/
/*********** FLOW CONTROL ***********/ /*********** FLOW CONTROL ***********/
/************************************/ /************************************/
@ -191,6 +196,94 @@ func (c *Context) MustGet(key string) interface{} {
panic("Key \"" + key + "\" does not exist") 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 ************/ /************ 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. // Like ParseBody() but this method also writes a 400 error if the json is not valid.
func (c *Context) Bind(obj interface{}) error { func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType()) 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 { 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. // See the binding package.
func (c *Context) BindWith(obj interface{}, b binding.Binding) error { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err := b.Bind(c.Request, obj); err != nil { if err = c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(400, err).SetType(ErrorTypeBind) 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 // ClientIP implements a best effort algorithm to return the real client IP, it parses

View File

@ -1,5 +1,9 @@
// +build appengine // +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 package gin
func init() { func init() {

View File

@ -12,13 +12,14 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/gin-contrib/sse"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
"gopkg.in/gin-contrib/sse.v0"
) )
var _ context.Context = &Context{} 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) { func TestContextCopy(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.index = 2 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) { func TestContextQuery(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) 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") 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) { func TestContextGetCookie(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin") c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user") cookie, _ := c.Cookie("user")
assert.Equal(t, cookie, "gin") assert.Equal(t, cookie, "gin")
_, err := c.Cookie("nokey")
assert.Error(t, err)
} }
func TestContextBodyAllowedForStatus(t *testing.T) { func TestContextBodyAllowedForStatus(t *testing.T) {
@ -737,6 +837,68 @@ func TestContextRenderRedirectAll(t *testing.T) {
assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) 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) { func TestContextNegotiationFormat(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)

View File

@ -7,6 +7,7 @@ package gin
import ( import (
"bytes" "bytes"
"errors" "errors"
"html/template"
"io" "io"
"log" "log"
"os" "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()) 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) { func setup(w io.Writer) {
SetMode(DebugMode) SetMode(DebugMode)
log.SetOutput(w) log.SetOutput(w)

View File

@ -4,9 +4,22 @@
package gin package gin
import "log" import (
"github.com/gin-gonic/gin/binding"
"log"
)
func (c *Context) GetCookie(name string) (string, error) { func (c *Context) GetCookie(name string) (string, error) {
log.Println("GetCookie() method is deprecated. Use Cookie() instead.") log.Println("GetCookie() method is deprecated. Use Cookie() instead.")
return c.Cookie(name) 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)
}

View File

@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool {
return (msg.Type & flags) > 0 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 // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic
func (a errorMsgs) ByType(typ ErrorType) errorMsgs { func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
if len(a) == 0 { if len(a) == 0 {

View File

@ -54,6 +54,13 @@ func TestError(t *testing.T) {
"status": "200", "status": "200",
"data": "some data", "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) { func TestErrorSlice(t *testing.T) {

View File

@ -1,8 +1,9 @@
package hello package hello
import ( import (
"github.com/gin-gonic/gin"
"net/http" "net/http"
"github.com/gin-gonic/gin"
) )
// This function's name is a must. App Engine uses it to drive the requests properly. // This function's name is a must. App Engine uses it to drive the requests properly.

View File

@ -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

View File

@ -11,7 +11,6 @@ import (
) )
func rateLimit(c *gin.Context) { func rateLimit(c *gin.Context) {
ip := c.ClientIP() ip := c.ClientIP()
value := int(ips.Add(ip, 1)) value := int(ips.Add(ip, 1))
if value%50 == 0 { if value%50 == 0 {

View File

@ -8,11 +8,13 @@ import (
"github.com/manucorporat/stats" "github.com/manucorporat/stats"
) )
var ips = stats.New() var (
var messages = stats.New() ips = stats.New()
var users = stats.New() messages = stats.New()
var mutexStats sync.RWMutex users = stats.New()
var savedStats map[string]uint64 mutexStats sync.RWMutex
savedStats map[string]uint64
)
func statsWorker() { func statsWorker() {
c := time.Tick(1 * time.Second) c := time.Tick(1 * time.Second)

View File

@ -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

View File

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

1
fixtures/basic/raw.tmpl Normal file
View File

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

4
fs.go
View File

@ -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 package gin
import ( import (

30
gin.go
View File

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

View File

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

View File

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

View File

@ -5,14 +5,98 @@
package gin package gin
import ( import (
"fmt"
"html/template"
"io/ioutil"
"net/http"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "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 //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) LoadHTMLFiles(files ...string) {
// func (engine *Engine) RunTLS(addr string, cert string, key string) error { // func (engine *Engine) RunTLS(addr string, cert string, key string) error {
@ -42,6 +126,32 @@ func TestCreateEngine(t *testing.T) {
// SetMode(TestMode) // 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) { func TestLoadHTMLReleaseMode(t *testing.T) {
} }

BIN
logo.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

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

View File

@ -1,7 +1,7 @@
// Copyright 2013 Julien Schmidt. All rights reserved. // Copyright 2013 Julien Schmidt. All rights reserved.
// Based on the path package, Copyright 2009 The Go Authors. // 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 // 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 package gin

View File

@ -1,7 +1,7 @@
// Copyright 2013 Julien Schmidt. All rights reserved. // Copyright 2013 Julien Schmidt. All rights reserved.
// Based on the path package, Copyright 2009 The Go Authors. // 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 // 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 package gin

View File

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

View File

@ -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 package gin
import ( import (

View File

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

View File

@ -1,6 +1,6 @@
// Copyright 2013 Julien Schmidt. All rights reserved. // Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found // 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 package gin

40
vendor/vendor.json vendored
View File

@ -1,5 +1,5 @@
{ {
"comment": "v1.1.4", "comment": "v1.2",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{ {
@ -16,10 +16,22 @@
"revisionTime": "2014-06-27T04:00:55Z" "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", "path": "github.com/golang/protobuf/proto",
"revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55",
"revisionTime": "2016-11-17T03:31:26Z" "revisionTime": "2017-06-01T23:02:30Z"
}, },
{ {
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
@ -53,6 +65,18 @@
"revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2",
"revisionTime": "2017-02-15T20:11:44Z" "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=", "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
"comment": "release-branch.go1.7", "comment": "release-branch.go1.7",
@ -61,17 +85,11 @@
"revisionTime": "2016-10-18T08:54:36Z" "revisionTime": "2016-10-18T08:54:36Z"
}, },
{ {
"checksumSHA1": "/oZpHfYc+ZgOwYAhlvcMhmETYpw=", "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
"path": "golang.org/x/sys/unix", "path": "golang.org/x/sys/unix",
"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
"revisionTime": "2017-03-08T15:04:45Z" "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=", "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
"comment": "v8.18.1", "comment": "v8.18.1",