Merge branch 'master' into develop
This commit is contained in:
commit
d875f07409
11
.travis.yml
11
.travis.yml
@ -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)
|
||||||
|
@ -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
61
Makefile
Normal 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
104
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.
|
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.
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
|
@ -6,7 +6,6 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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{}
|
||||||
|
119
context.go
119
context.go
@ -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
|
||||||
|
@ -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() {
|
||||||
|
164
context_test.go
164
context_test.go
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
10
examples/realtime-advanced/Makefile
Normal file
10
examples/realtime-advanced/Makefile
Normal 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
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
9
examples/realtime-chat/Makefile
Normal file
9
examples/realtime-chat/Makefile
Normal 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
|
1
fixtures/basic/hello.tmpl
Normal file
1
fixtures/basic/hello.tmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1>Hello {[{.name}]}</h1>
|
1
fixtures/basic/raw.tmpl
Normal file
1
fixtures/basic/raw.tmpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
Date: {[{.now | formatAsDate}]}
|
4
fs.go
4
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
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
30
gin.go
30
gin.go
@ -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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#Gin Default Server
|
# Gin Default Server
|
||||||
|
|
||||||
This is API experiment for Gin.
|
This is API experiment for Gin.
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
112
gin_test.go
112
gin_test.go
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
2
path.go
2
path.go
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
||||||
|
5
tree.go
5
tree.go
@ -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]
|
||||||
|
@ -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
40
vendor/vendor.json
vendored
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user