mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-20 00:02:16 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
1a507bb74f
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
- With issues:
|
||||
- Use the search tool before opening a new issue.
|
||||
- Please provide source code and commit sha if you found a bug.
|
||||
- Review existing issues and provide feedback or react to them.
|
||||
|
||||
- go version:
|
||||
- gin version (or commit ref):
|
||||
- operating system:
|
||||
|
||||
## Description
|
||||
|
||||
## Screenshots
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- Your pull request should have no more than two commits, if not you should squash them.
|
||||
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||
- You should add/modify tests to cover your proposed code changes.
|
||||
- If your pull request contains a new feature, please document it on the README.
|
||||
|
@ -6,7 +6,7 @@ go:
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- master
|
||||
- 1.11.x
|
||||
|
||||
git:
|
||||
depth: 10
|
||||
|
@ -1,8 +1,12 @@
|
||||
List of all the awesome people working to make Gin the best Web Framework in Go.
|
||||
|
||||
## gin 1.x series authors
|
||||
|
||||
**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho)
|
||||
|
||||
## gin 0.x series authors
|
||||
|
||||
**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||
|
||||
People and companies, who have contributed, in alphabetical order.
|
||||
|
||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,6 +1,28 @@
|
||||
# CHANGELOG
|
||||
|
||||
### Gin 1.2
|
||||
### Gin 1.3.0
|
||||
|
||||
- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)
|
||||
- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)
|
||||
- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273)
|
||||
- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304)
|
||||
- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341)
|
||||
- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336)
|
||||
- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333)
|
||||
- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138)
|
||||
- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277)
|
||||
- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047)
|
||||
- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117)
|
||||
- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029)
|
||||
- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026)
|
||||
- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999)
|
||||
- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993)
|
||||
- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie)
|
||||
- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072)
|
||||
- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250)
|
||||
- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460)
|
||||
|
||||
### Gin 1.2.0
|
||||
|
||||
- [NEW] Switch from godeps to govendor
|
||||
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
|
||||
|
3
Makefile
3
Makefile
@ -1,5 +1,6 @@
|
||||
GOFMT ?= gofmt "-s"
|
||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||
VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||
|
||||
all: install
|
||||
@ -26,7 +27,7 @@ fmt-check:
|
||||
fi;
|
||||
|
||||
vet:
|
||||
go vet $(PACKAGES)
|
||||
go vet $(VETPACKAGES)
|
||||
|
||||
deps:
|
||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
|
305
README.md
305
README.md
@ -3,10 +3,11 @@
|
||||
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
||||
|
||||
[](https://travis-ci.org/gin-gonic/gin)
|
||||
[](https://codecov.io/gh/gin-gonic/gin)
|
||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||
[](https://godoc.org/github.com/gin-gonic/gin)
|
||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://codecov.io/gh/gin-gonic/gin)
|
||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||
[](https://godoc.org/github.com/gin-gonic/gin)
|
||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||
[](https://www.codetriage.com/gin-gonic/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.
|
||||
@ -15,10 +16,11 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
|
||||
## Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Prerequisite](#prerequisite)
|
||||
- [Quick start](#quick-start)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Gin v1.stable](#gin-v1-stable)
|
||||
- [Start using it](#start-using-it)
|
||||
- [Build with jsoniter](#build-with-jsoniter)
|
||||
- [API Examples](#api-examples)
|
||||
- [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||
@ -26,6 +28,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Querystring parameters](#querystring-parameters)
|
||||
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
||||
- [Another example: query + post form](#another-example-query--post-form)
|
||||
- [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
|
||||
- [Upload files](#upload-files)
|
||||
- [Grouping routes](#grouping-routes)
|
||||
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
||||
@ -37,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
|
||||
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
|
||||
- [JSONP rendering](#jsonp)
|
||||
- [Serving static files](#serving-static-files)
|
||||
- [Serving data from reader](#serving-data-from-reader)
|
||||
@ -56,7 +59,65 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
||||
- [http2 server push](#http2-server-push)
|
||||
- [Testing](#testing)
|
||||
- [Users](#users--)
|
||||
- [Users](#users)
|
||||
|
||||
## Installation
|
||||
|
||||
To install Gin package, you need to install Go and set your Go workspace first.
|
||||
|
||||
1. Download and install it:
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
2. Import it in your code:
|
||||
|
||||
```go
|
||||
import "github.com/gin-gonic/gin"
|
||||
```
|
||||
|
||||
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
|
||||
|
||||
```go
|
||||
import "net/http"
|
||||
```
|
||||
|
||||
### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
|
||||
|
||||
1. `go get` govendor
|
||||
|
||||
```sh
|
||||
$ go get github.com/kardianos/govendor
|
||||
```
|
||||
2. Create your project folder and `cd` inside
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
3. Vendor init your project and add gin
|
||||
|
||||
```sh
|
||||
$ govendor init
|
||||
$ govendor fetch github.com/gin-gonic/gin@v1.3
|
||||
```
|
||||
|
||||
4. Copy a starting template inside your project
|
||||
|
||||
```sh
|
||||
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
|
||||
```
|
||||
|
||||
5. Run your project
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
## Prerequisite
|
||||
|
||||
Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
||||
|
||||
## Quick start
|
||||
|
||||
@ -135,61 +196,9 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
|
||||
- [x] Battle tested
|
||||
- [x] API frozen, new releases will not break your code.
|
||||
|
||||
## Start using it
|
||||
|
||||
1. Download and install it:
|
||||
|
||||
```sh
|
||||
$ go get github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
2. Import it in your code:
|
||||
|
||||
```go
|
||||
import "github.com/gin-gonic/gin"
|
||||
```
|
||||
|
||||
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
|
||||
|
||||
```go
|
||||
import "net/http"
|
||||
```
|
||||
|
||||
### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
|
||||
|
||||
1. `go get` govendor
|
||||
|
||||
```sh
|
||||
$ go get github.com/kardianos/govendor
|
||||
```
|
||||
2. Create your project folder and `cd` inside
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
3. Vendor init your project and add gin
|
||||
|
||||
```sh
|
||||
$ govendor init
|
||||
$ govendor fetch github.com/gin-gonic/gin@v1.2
|
||||
```
|
||||
|
||||
4. Copy a starting template inside your project
|
||||
|
||||
```sh
|
||||
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
|
||||
```
|
||||
|
||||
5. Run your project
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
## Build with [jsoniter](https://github.com/json-iterator/go)
|
||||
|
||||
Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
|
||||
Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
|
||||
|
||||
```sh
|
||||
$ go build -tags=jsoniter .
|
||||
@ -229,7 +238,7 @@ func main() {
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
// This handler will match /user/john but will not match neither /user/ or /user
|
||||
// This handler will match /user/john but will not match /user/ or /user
|
||||
router.GET("/user/:name", func(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
c.String(http.StatusOK, "Hello %s", name)
|
||||
@ -316,6 +325,34 @@ func main() {
|
||||
id: 1234; page: 1; name: manu; message: this_is_great
|
||||
```
|
||||
|
||||
### Map as querystring or postform parameters
|
||||
|
||||
```
|
||||
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
names[first]=thinkerou&names[second]=tianou
|
||||
```
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
router.POST("/post", func(c *gin.Context) {
|
||||
|
||||
ids := c.QueryMap("ids")
|
||||
names := c.PostFormMap("names")
|
||||
|
||||
fmt.Printf("ids: %v; names: %v", ids, names)
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
|
||||
```
|
||||
|
||||
### Upload files
|
||||
|
||||
#### Single file
|
||||
@ -497,10 +534,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
|
||||
|
||||
Also, Gin provides two sets of methods for binding:
|
||||
- **Type** - Must bind
|
||||
- **Methods** - `Bind`, `BindJSON`, `BindQuery`
|
||||
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`
|
||||
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
||||
- **Type** - Should bind
|
||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery`
|
||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`
|
||||
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
|
||||
|
||||
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
|
||||
@ -510,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated
|
||||
```go
|
||||
// Binding from JSON
|
||||
type Login struct {
|
||||
User string `form:"user" json:"user" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
User string `form:"user" json:"user" xml:"user" binding:"required"`
|
||||
Password string `form:"password" json:"password" xml:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -520,30 +557,55 @@ func main() {
|
||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||
router.POST("/loginJSON", func(c *gin.Context) {
|
||||
var json Login
|
||||
if err := c.ShouldBindJSON(&json); err == nil {
|
||||
if json.User == "manu" && json.Password == "123" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBindJSON(&json); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if json.User != "manu" || json.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
// Example for binding XML (
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <root>
|
||||
// <user>user</user>
|
||||
// <password>123</user>
|
||||
// </root>)
|
||||
router.POST("/loginXML", func(c *gin.Context) {
|
||||
var xml Login
|
||||
if err := c.ShouldBindXML(&xml); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if xml.User != "manu" || xml.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
// Example for binding a HTML form (user=manu&password=123)
|
||||
router.POST("/loginForm", func(c *gin.Context) {
|
||||
var form Login
|
||||
// This will infer what binder to use depending on the content-type header.
|
||||
if err := c.ShouldBind(&form); err == nil {
|
||||
if form.User == "manu" && form.Password == "123" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
} else {
|
||||
if err := c.ShouldBind(&form); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if form.User != "manu" || form.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
@ -595,6 +657,7 @@ import (
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// Booking contains binded and validated data.
|
||||
type Booking struct {
|
||||
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
||||
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||
@ -642,7 +705,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
|
||||
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
|
||||
```
|
||||
|
||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way.
|
||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
||||
See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
|
||||
|
||||
### Only Bind Query String
|
||||
@ -688,9 +751,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco
|
||||
```go
|
||||
package main
|
||||
|
||||
import "log"
|
||||
import "github.com/gin-gonic/gin"
|
||||
import "time"
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string `form:"name"`
|
||||
@ -809,7 +875,7 @@ Test it with:
|
||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||
```
|
||||
|
||||
### XML, JSON and YAML rendering
|
||||
### XML, JSON, YAML and ProtoBuf rendering
|
||||
|
||||
```go
|
||||
func main() {
|
||||
@ -843,6 +909,19 @@ func main() {
|
||||
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||
})
|
||||
|
||||
r.GET("/someProtoBuf", func(c *gin.Context) {
|
||||
reps := []int64{int64(1), int64(2)}
|
||||
label := "test"
|
||||
// The specific definition of protobuf is written in the testdata/protoexample file.
|
||||
data := &protoexample.Test{
|
||||
Label: &label,
|
||||
Reps: reps,
|
||||
}
|
||||
// Note that data becomes binary data in the response
|
||||
// Will output protoexample.Test protobuf serialized data
|
||||
c.ProtoBuf(http.StatusOK, data)
|
||||
})
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
@ -893,6 +972,57 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### AsciiJSON
|
||||
|
||||
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.GET("/someJSON", func(c *gin.Context) {
|
||||
data := map[string]interface{}{
|
||||
"lang": "GO语言",
|
||||
"tag": "<br>",
|
||||
}
|
||||
|
||||
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
|
||||
c.AsciiJSON(http.StatusOK, data)
|
||||
})
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
#### PureJSON
|
||||
|
||||
Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead.
|
||||
This feature is unavailable in Go 1.6 and lower.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// Serves unicode entities
|
||||
r.GET("/json", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"html": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
// Serves literal characters
|
||||
r.GET("/purejson", func(c *gin.Context) {
|
||||
c.PureJSON(200, gin.H{
|
||||
"html": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
// listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080)
|
||||
}
|
||||
```
|
||||
|
||||
### Serving static files
|
||||
|
||||
```go
|
||||
@ -1057,7 +1187,7 @@ func main() {
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
router.LoadHTMLFiles("./fixtures/basic/raw.tmpl")
|
||||
router.LoadHTMLFiles("./testdata/template/raw.tmpl")
|
||||
|
||||
router.GET("/raw", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||
@ -1752,9 +1882,10 @@ func TestPingRoute(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
## Users [](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||
## Users
|
||||
|
||||
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||
|
||||
* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go
|
||||
* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go.
|
||||
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
|
||||
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
|
||||
|
3
auth.go
3
auth.go
@ -7,6 +7,7 @@ package gin
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
||||
if !found {
|
||||
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||
c.Header("WWW-Authenticate", realm)
|
||||
c.AbortWithStatus(401)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
12
auth_test.go
12
auth_test.go
@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(BasicAuth(accounts))
|
||||
router.GET("/login", func(c *Context) {
|
||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
||||
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
||||
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "admin", w.Body.String())
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) {
|
||||
router.Use(BasicAuth(accounts))
|
||||
router.GET("/login", func(c *Context) {
|
||||
called = true
|
||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
||||
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@ -121,7 +121,7 @@ func TestBasicAuth401(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called)
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate"))
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||
router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\""))
|
||||
router.GET("/login", func(c *Context) {
|
||||
called = true
|
||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
||||
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called)
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate"))
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
||||
Status string `json:"status"`
|
||||
}{"ok"}
|
||||
router.GET("/json", func(c *Context) {
|
||||
c.JSON(200, data)
|
||||
c.JSON(http.StatusOK, data)
|
||||
})
|
||||
runRequest(B, router, "GET", "/json")
|
||||
}
|
||||
@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) {
|
||||
router.SetHTMLTemplate(t)
|
||||
|
||||
router.GET("/html", func(c *Context) {
|
||||
c.HTML(200, "index", "hola")
|
||||
c.HTML(http.StatusOK, "index", "hola")
|
||||
})
|
||||
runRequest(B, router, "GET", "/html")
|
||||
}
|
||||
@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) {
|
||||
func BenchmarkOneRouteString(B *testing.B) {
|
||||
router := New()
|
||||
router.GET("/text", func(c *Context) {
|
||||
c.String(200, "this is a plain text")
|
||||
c.String(http.StatusOK, "this is a plain text")
|
||||
})
|
||||
runRequest(B, router, "GET", "/text")
|
||||
}
|
||||
|
@ -4,10 +4,9 @@
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
// Content-Type MIME of the most common data formats.
|
||||
const (
|
||||
MIMEJSON = "application/json"
|
||||
MIMEHTML = "text/html"
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/binding/example"
|
||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ugorji/go/codec"
|
||||
@ -55,12 +55,12 @@ func msgPackBody(t *testing.T) string {
|
||||
}
|
||||
|
||||
func TestBindingBodyProto(t *testing.T) {
|
||||
test := example.Test{
|
||||
test := protoexample.Test{
|
||||
Label: proto.String("FOO"),
|
||||
}
|
||||
data, _ := proto.Marshal(&test)
|
||||
req := requestWithBody("POST", "/", string(data))
|
||||
form := example.Test{}
|
||||
form := protoexample.Test{}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
assert.NoError(t, ProtoBuf.BindBody(body, &form))
|
||||
assert.Equal(t, test, form)
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/binding/example"
|
||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ugorji/go/codec"
|
||||
@ -562,7 +562,7 @@ func TestBindingFormMultipartFail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindingProtoBuf(t *testing.T) {
|
||||
test := &example.Test{
|
||||
test := &protoexample.Test{
|
||||
Label: proto.String("yes"),
|
||||
}
|
||||
data, _ := proto.Marshal(test)
|
||||
@ -574,7 +574,7 @@ func TestBindingProtoBuf(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindingProtoBufFail(t *testing.T) {
|
||||
test := &example.Test{
|
||||
test := &protoexample.Test{
|
||||
Label: proto.String("yes"),
|
||||
}
|
||||
data, _ := proto.Marshal(test)
|
||||
@ -1156,14 +1156,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad
|
||||
func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||
assert.Equal(t, name, b.Name())
|
||||
|
||||
obj := example.Test{}
|
||||
obj := protoexample.Test{}
|
||||
req := requestWithBody("POST", path, body)
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
err := b.Bind(req, &obj)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "yes", *obj.Label)
|
||||
|
||||
obj = example.Test{}
|
||||
obj = protoexample.Test{}
|
||||
req = requestWithBody("POST", badPath, badBody)
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
err = ProtoBuf.Bind(req, &obj)
|
||||
@ -1179,7 +1179,7 @@ func (h hook) Read([]byte) (int, error) {
|
||||
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||
assert.Equal(t, name, b.Name())
|
||||
|
||||
obj := example.Test{}
|
||||
obj := protoexample.Test{}
|
||||
req := requestWithBody("POST", path, body)
|
||||
|
||||
req.Body = ioutil.NopCloser(&hook{})
|
||||
@ -1187,7 +1187,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
||||
err := b.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
|
||||
obj = example.Test{}
|
||||
obj = protoexample.Test{}
|
||||
req = requestWithBody("POST", badPath, badBody)
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
err = ProtoBuf.Bind(req, &obj)
|
||||
|
@ -18,11 +18,17 @@ type defaultValidator struct {
|
||||
|
||||
var _ StructValidator = &defaultValidator{}
|
||||
|
||||
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
if kindOfData(obj) == reflect.Struct {
|
||||
value := reflect.ValueOf(obj)
|
||||
valueType := value.Kind()
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
if valueType == reflect.Struct {
|
||||
v.lazyinit()
|
||||
if err := v.validate.Struct(obj); err != nil {
|
||||
return error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() {
|
||||
v.validate = validator.New(config)
|
||||
})
|
||||
}
|
||||
|
||||
func kindOfData(data interface{}) reflect.Kind {
|
||||
value := reflect.ValueOf(data)
|
||||
valueType := value.Kind()
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
return valueType
|
||||
}
|
||||
|
@ -74,16 +74,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
||||
}
|
||||
}
|
||||
val.Field(i).Set(slice)
|
||||
} else {
|
||||
if _, isTime := structField.Interface().(time.Time); isTime {
|
||||
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||
continue
|
||||
}
|
||||
if _, isTime := structField.Interface().(time.Time); isTime {
|
||||
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -178,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
|
||||
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
||||
timeFormat := structField.Tag.Get("time_format")
|
||||
if timeFormat == "" {
|
||||
return errors.New("Blank time format")
|
||||
timeFormat = time.RFC3339
|
||||
}
|
||||
|
||||
if val == "" {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/json"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
||||
|
126
context.go
Executable file → Normal file
126
context.go
Executable file → Normal file
@ -164,16 +164,15 @@ func (c *Context) Error(err error) *Error {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
var parsedError *Error
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
parsedError = err.(*Error)
|
||||
default:
|
||||
|
||||
parsedError, ok := err.(*Error)
|
||||
if !ok {
|
||||
parsedError = &Error{
|
||||
Err: err,
|
||||
Type: ErrorTypePrivate,
|
||||
}
|
||||
}
|
||||
|
||||
c.Errors = append(c.Errors, parsedError)
|
||||
return parsedError
|
||||
}
|
||||
@ -371,6 +370,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
// QueryMap returns a map for a given query key.
|
||||
func (c *Context) QueryMap(key string) map[string]string {
|
||||
dicts, _ := c.GetQueryMap(key)
|
||||
return dicts
|
||||
}
|
||||
|
||||
// GetQueryMap returns a map for a given query key, plus a boolean value
|
||||
// whether at least one value exists for the given key.
|
||||
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
||||
return c.get(c.Request.URL.Query(), key)
|
||||
}
|
||||
|
||||
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||
// when it exists, otherwise it returns an empty string `("")`.
|
||||
func (c *Context) PostForm(key string) string {
|
||||
@ -426,6 +437,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
// PostFormMap returns a map for a given form key.
|
||||
func (c *Context) PostFormMap(key string) map[string]string {
|
||||
dicts, _ := c.GetPostFormMap(key)
|
||||
return dicts
|
||||
}
|
||||
|
||||
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
||||
// whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||
req := c.Request
|
||||
req.ParseForm()
|
||||
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||
dicts, exist := c.get(req.PostForm, key)
|
||||
|
||||
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
|
||||
dicts, exist = c.get(req.MultipartForm.Value, key)
|
||||
}
|
||||
|
||||
return dicts, exist
|
||||
}
|
||||
|
||||
// get is an internal method and returns a map which satisfy conditions.
|
||||
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
|
||||
dicts := make(map[string]string)
|
||||
exist := false
|
||||
for k, v := range m {
|
||||
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
|
||||
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
|
||||
exist = true
|
||||
dicts[k[i+1:][:j]] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dicts, exist
|
||||
}
|
||||
|
||||
// FormFile returns the first file for the provided form key.
|
||||
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := c.Request.FormFile(name)
|
||||
@ -474,6 +521,11 @@ func (c *Context) BindJSON(obj interface{}) error {
|
||||
return c.MustBindWith(obj, binding.JSON)
|
||||
}
|
||||
|
||||
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
|
||||
func (c *Context) BindXML(obj interface{}) error {
|
||||
return c.MustBindWith(obj, binding.XML)
|
||||
}
|
||||
|
||||
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
|
||||
func (c *Context) BindQuery(obj interface{}) error {
|
||||
return c.MustBindWith(obj, binding.Query)
|
||||
@ -484,7 +536,7 @@ func (c *Context) BindQuery(obj interface{}) error {
|
||||
// See the binding package.
|
||||
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
||||
if err = c.ShouldBindWith(obj, b); err != nil {
|
||||
c.AbortWithError(400, err).SetType(ErrorTypeBind)
|
||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)
|
||||
}
|
||||
|
||||
return
|
||||
@ -508,6 +560,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
|
||||
return c.ShouldBindWith(obj, binding.JSON)
|
||||
}
|
||||
|
||||
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
|
||||
func (c *Context) ShouldBindXML(obj interface{}) error {
|
||||
return c.ShouldBindWith(obj, binding.XML)
|
||||
}
|
||||
|
||||
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
||||
func (c *Context) ShouldBindQuery(obj interface{}) error {
|
||||
return c.ShouldBindWith(obj, binding.Query)
|
||||
@ -549,14 +606,10 @@ func (c *Context) ShouldBindBodyWith(
|
||||
func (c *Context) ClientIP() string {
|
||||
if c.engine.ForwardedByClientIP {
|
||||
clientIP := c.requestHeader("X-Forwarded-For")
|
||||
if index := strings.IndexByte(clientIP, ','); index >= 0 {
|
||||
clientIP = clientIP[0:index]
|
||||
clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
|
||||
if clientIP == "" {
|
||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||
}
|
||||
clientIP = strings.TrimSpace(clientIP)
|
||||
if clientIP != "" {
|
||||
return clientIP
|
||||
}
|
||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||
if clientIP != "" {
|
||||
return clientIP
|
||||
}
|
||||
@ -603,9 +656,9 @@ func bodyAllowedForStatus(status int) bool {
|
||||
switch {
|
||||
case status >= 100 && status <= 199:
|
||||
return false
|
||||
case status == 204:
|
||||
case status == http.StatusNoContent:
|
||||
return false
|
||||
case status == 304:
|
||||
case status == http.StatusNotModified:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -622,9 +675,9 @@ func (c *Context) Status(code int) {
|
||||
func (c *Context) Header(key, value string) {
|
||||
if value == "" {
|
||||
c.Writer.Header().Del(key)
|
||||
} else {
|
||||
c.Writer.Header().Set(key, value)
|
||||
return
|
||||
}
|
||||
c.Writer.Header().Set(key, value)
|
||||
}
|
||||
|
||||
// GetHeader returns value from request headers.
|
||||
@ -668,6 +721,7 @@ func (c *Context) Cookie(name string) (string, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Render writes the response headers and calls render.Render to render data.
|
||||
func (c *Context) Render(code int, r render.Render) {
|
||||
c.Status(code)
|
||||
|
||||
@ -709,7 +763,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
|
||||
// It add padding to response body to request data from a server residing in a different domain than the client.
|
||||
// It also sets the Content-Type as "application/javascript".
|
||||
func (c *Context) JSONP(code int, obj interface{}) {
|
||||
c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj})
|
||||
callback := c.DefaultQuery("callback", "")
|
||||
if callback == "" {
|
||||
c.Render(code, render.JSON{Data: obj})
|
||||
return
|
||||
}
|
||||
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
|
||||
}
|
||||
|
||||
// JSON serializes the given struct as JSON into the response body.
|
||||
@ -718,6 +777,12 @@ func (c *Context) JSON(code int, obj interface{}) {
|
||||
c.Render(code, render.JSON{Data: obj})
|
||||
}
|
||||
|
||||
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
|
||||
// It also sets the Content-Type as "application/json".
|
||||
func (c *Context) AsciiJSON(code int, obj interface{}) {
|
||||
c.Render(code, render.AsciiJSON{Data: obj})
|
||||
}
|
||||
|
||||
// XML serializes the given struct as XML into the response body.
|
||||
// It also sets the Content-Type as "application/xml".
|
||||
func (c *Context) XML(code int, obj interface{}) {
|
||||
@ -729,6 +794,11 @@ func (c *Context) YAML(code int, obj interface{}) {
|
||||
c.Render(code, render.YAML{Data: obj})
|
||||
}
|
||||
|
||||
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
|
||||
func (c *Context) ProtoBuf(code int, obj interface{}) {
|
||||
c.Render(code, render.ProtoBuf{Data: obj})
|
||||
}
|
||||
|
||||
// String writes the given string into the response body.
|
||||
func (c *Context) String(code int, format string, values ...interface{}) {
|
||||
c.Render(code, render.String{Format: format, Data: values})
|
||||
@ -774,6 +844,7 @@ func (c *Context) SSEvent(name string, message interface{}) {
|
||||
})
|
||||
}
|
||||
|
||||
// Stream sends a streaming response.
|
||||
func (c *Context) Stream(step func(w io.Writer) bool) {
|
||||
w := c.Writer
|
||||
clientGone := w.CloseNotify()
|
||||
@ -795,6 +866,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) {
|
||||
/******** CONTENT NEGOTIATION *******/
|
||||
/************************************/
|
||||
|
||||
// Negotiate contains all negotiations data.
|
||||
type Negotiate struct {
|
||||
Offered []string
|
||||
HTMLName string
|
||||
@ -804,6 +876,7 @@ type Negotiate struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// Negotiate calls different Render according acceptable Accept format.
|
||||
func (c *Context) Negotiate(code int, config Negotiate) {
|
||||
switch c.NegotiateFormat(config.Offered...) {
|
||||
case binding.MIMEJSON:
|
||||
@ -823,6 +896,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
||||
}
|
||||
}
|
||||
|
||||
// NegotiateFormat returns an acceptable Accept format.
|
||||
func (c *Context) NegotiateFormat(offered ...string) string {
|
||||
assert1(len(offered) > 0, "you must provide at least one offer")
|
||||
|
||||
@ -842,6 +916,7 @@ func (c *Context) NegotiateFormat(offered ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetAccepted sets Accept header data.
|
||||
func (c *Context) SetAccepted(formats ...string) {
|
||||
c.Accepted = formats
|
||||
}
|
||||
@ -850,18 +925,33 @@ func (c *Context) SetAccepted(formats ...string) {
|
||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||
/************************************/
|
||||
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
func (c *Context) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed,
|
||||
// successive calls to Err return the same error.
|
||||
// If Done is not yet closed, Err returns nil.
|
||||
// If Done is closed, Err returns a non-nil error explaining why:
|
||||
// Canceled if the context was canceled
|
||||
// or DeadlineExceeded if the context's deadline passed.
|
||||
func (c *Context) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
func (c *Context) Value(key interface{}) interface{} {
|
||||
if key == 0 {
|
||||
return c.Request
|
||||
|
17
context_17.go
Normal file
17
context_17.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin/render"
|
||||
)
|
||||
|
||||
// PureJSON serializes the given struct as JSON into the response body.
|
||||
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
||||
func (c *Context) PureJSON(code int, obj interface{}) {
|
||||
c.Render(code, render.PureJSON{Data: obj})
|
||||
}
|
27
context_17_test.go
Normal file
27
context_17_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Tests that the response is serialized as JSON
|
||||
// and Content-Type is set to application/json
|
||||
// and special HTML characters are preserved
|
||||
func TestContextRenderPureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
344
context_test.go
344
context_test.go
@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -19,8 +20,11 @@ import (
|
||||
|
||||
"github.com/gin-contrib/sse"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||
)
|
||||
|
||||
var _ context.Context = &Context{}
|
||||
@ -47,6 +51,8 @@ func createMultipartRequest() *http.Request {
|
||||
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
||||
must(mw.WriteField("time_utc", "31/12/2016 14:55"))
|
||||
must(mw.WriteField("time_location", "31/12/2016 14:55"))
|
||||
must(mw.WriteField("names[a]", "thinkerou"))
|
||||
must(mw.WriteField("names[b]", "tianou"))
|
||||
req, err := http.NewRequest("POST", "/", body)
|
||||
must(err)
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||
@ -371,7 +377,8 @@ func TestContextQuery(t *testing.T) {
|
||||
func TestContextQueryAndPostForm(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
||||
c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body)
|
||||
c.Request, _ = http.NewRequest("POST",
|
||||
"/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body)
|
||||
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
||||
|
||||
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||
@ -439,6 +446,30 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
||||
values = c.QueryArray("both")
|
||||
assert.Equal(t, 1, len(values))
|
||||
assert.Equal(t, "GET", values[0])
|
||||
|
||||
dicts, ok := c.GetQueryMap("ids")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "hi", dicts["a"])
|
||||
assert.Equal(t, "3.14", dicts["b"])
|
||||
|
||||
dicts, ok = c.GetQueryMap("nokey")
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
|
||||
dicts, ok = c.GetQueryMap("both")
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
|
||||
dicts, ok = c.GetQueryMap("array")
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
|
||||
dicts = c.QueryMap("ids")
|
||||
assert.Equal(t, "hi", dicts["a"])
|
||||
assert.Equal(t, "3.14", dicts["b"])
|
||||
|
||||
dicts = c.QueryMap("nokey")
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
}
|
||||
|
||||
func TestContextPostFormMultipart(t *testing.T) {
|
||||
@ -515,6 +546,22 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
values = c.PostFormArray("foo")
|
||||
assert.Equal(t, 1, len(values))
|
||||
assert.Equal(t, "bar", values[0])
|
||||
|
||||
dicts, ok := c.GetPostFormMap("names")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "thinkerou", dicts["a"])
|
||||
assert.Equal(t, "tianou", dicts["b"])
|
||||
|
||||
dicts, ok = c.GetPostFormMap("nokey")
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
|
||||
dicts = c.PostFormMap("names")
|
||||
assert.Equal(t, "thinkerou", dicts["a"])
|
||||
assert.Equal(t, "tianou", dicts["b"])
|
||||
|
||||
dicts = c.PostFormMap("nokey")
|
||||
assert.Equal(t, 0, len(dicts))
|
||||
}
|
||||
|
||||
func TestContextSetCookie(t *testing.T) {
|
||||
@ -541,10 +588,11 @@ func TestContextGetCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||
// todo(thinkerou): go1.6 not support StatusProcessing
|
||||
assert.False(t, false, bodyAllowedForStatus(102))
|
||||
assert.False(t, false, bodyAllowedForStatus(204))
|
||||
assert.False(t, false, bodyAllowedForStatus(304))
|
||||
assert.True(t, true, bodyAllowedForStatus(500))
|
||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
||||
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
type TestPanicRender struct {
|
||||
@ -571,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) {
|
||||
|
||||
// Tests that the response is serialized as JSON
|
||||
// and Content-Type is set to application/json
|
||||
// and special HTML characters are escaped
|
||||
func TestContextRenderJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.JSON(201, H{"foo": "bar"})
|
||||
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
@ -589,21 +638,35 @@ func TestContextRenderJSONP(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil)
|
||||
|
||||
c.JSONP(201, H{"foo": "bar"})
|
||||
c.JSONP(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String())
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that the response is serialized as JSONP
|
||||
// and Content-Type is set to application/json
|
||||
func TestContextRenderJSONPWithoutCallback(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("GET", "http://example.com", nil)
|
||||
|
||||
c.JSONP(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no JSON is rendered if code is 204
|
||||
func TestContextRenderNoContentJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.JSON(204, H{"foo": "bar"})
|
||||
c.JSON(http.StatusNoContent, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -615,9 +678,9 @@ func TestContextRenderAPIJSON(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Header("Content-Type", "application/vnd.api+json")
|
||||
c.JSON(201, H{"foo": "bar"})
|
||||
c.JSON(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -628,9 +691,9 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Header("Content-Type", "application/vnd.api+json")
|
||||
c.JSON(204, H{"foo": "bar"})
|
||||
c.JSON(http.StatusNoContent, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json")
|
||||
}
|
||||
@ -641,9 +704,9 @@ func TestContextRenderIndentedJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||
c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -653,9 +716,9 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||
c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -667,9 +730,9 @@ func TestContextRenderSecureJSON(t *testing.T) {
|
||||
c, router := CreateTestContext(w)
|
||||
|
||||
router.SecureJsonPrefix("&&&START&&&")
|
||||
c.SecureJSON(201, []string{"foo", "bar"})
|
||||
c.SecureJSON(http.StatusCreated, []string{"foo", "bar"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -679,13 +742,24 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.SecureJSON(204, []string{"foo", "bar"})
|
||||
c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"})
|
||||
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that the response executes the templates
|
||||
// and responds with Content-Type set to text/html
|
||||
func TestContextRenderHTML(t *testing.T) {
|
||||
@ -695,9 +769,9 @@ func TestContextRenderHTML(t *testing.T) {
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
c.HTML(201, "t", H{"name": "alexandernyquist"})
|
||||
c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -719,9 +793,9 @@ func TestContextRenderHTML2(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "[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", b.String())
|
||||
|
||||
c.HTML(201, "t", H{"name": "alexandernyquist"})
|
||||
c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -733,9 +807,9 @@ func TestContextRenderNoContentHTML(t *testing.T) {
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
c.HTML(204, "t", H{"name": "alexandernyquist"})
|
||||
c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -746,9 +820,9 @@ func TestContextRenderXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.XML(201, H{"foo": "bar"})
|
||||
c.XML(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, 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"))
|
||||
}
|
||||
@ -758,9 +832,9 @@ func TestContextRenderNoContentXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.XML(204, H{"foo": "bar"})
|
||||
c.XML(http.StatusNoContent, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -771,9 +845,9 @@ func TestContextRenderString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.String(201, "test %s %d", "string", 2)
|
||||
c.String(http.StatusCreated, "test %s %d", "string", 2)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "test string 2", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -783,9 +857,9 @@ func TestContextRenderNoContentString(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.String(204, "test %s %d", "string", 2)
|
||||
c.String(http.StatusNoContent, "test %s %d", "string", 2)
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -797,9 +871,9 @@ func TestContextRenderHTMLString(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(201, "<html>%s %d</html>", "string", 3)
|
||||
c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3)
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "<html>string 3</html>", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -810,9 +884,9 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(204, "<html>%s %d</html>", "string", 3)
|
||||
c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3)
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -823,9 +897,9 @@ func TestContextRenderData(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Data(201, "text/csv", []byte(`foo,bar`))
|
||||
c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "foo,bar", w.Body.String())
|
||||
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -835,9 +909,9 @@ func TestContextRenderNoContentData(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Data(204, "text/csv", []byte(`foo,bar`))
|
||||
c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -866,7 +940,7 @@ func TestContextRenderFile(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.File("./gin.go")
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -877,13 +951,37 @@ func TestContextRenderYAML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.YAML(201, H{"foo": "bar"})
|
||||
c.YAML(http.StatusCreated, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
|
||||
// and Content-Type is set to application/x-protobuf
|
||||
// and we just use the example protobuf to check if the response is correct
|
||||
func TestContextRenderProtoBuf(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
reps := []int64{int64(1), int64(2)}
|
||||
label := "test"
|
||||
data := &testdata.Test{
|
||||
Label: &label,
|
||||
Reps: reps,
|
||||
}
|
||||
|
||||
c.ProtoBuf(http.StatusCreated, data)
|
||||
|
||||
protoData, err := proto.Marshal(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, string(protoData), w.Body.String())
|
||||
assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestContextHeaders(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Header("Content-Type", "text/plain")
|
||||
@ -909,9 +1007,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
|
||||
assert.Panics(t, func() { c.Redirect(299, "/new_path") })
|
||||
assert.Panics(t, func() { c.Redirect(309, "/new_path") })
|
||||
|
||||
c.Redirect(301, "/path")
|
||||
c.Redirect(http.StatusMovedPermanently, "/path")
|
||||
c.Writer.WriteHeaderNow()
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
@ -920,10 +1018,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||
c.Redirect(302, "http://google.com")
|
||||
c.Redirect(http.StatusFound, "http://google.com")
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, 302, w.Code)
|
||||
assert.Equal(t, http.StatusFound, w.Code)
|
||||
assert.Equal(t, "http://google.com", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
@ -932,21 +1030,23 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||
c.Redirect(201, "/resource")
|
||||
c.Redirect(http.StatusCreated, "/resource")
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Equal(t, "/resource", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestContextRenderRedirectAll(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||
assert.Panics(t, func() { c.Redirect(200, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(202, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
||||
assert.Panics(t, func() { c.Redirect(309, "/resource") })
|
||||
assert.NotPanics(t, func() { c.Redirect(300, "/resource") })
|
||||
assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") })
|
||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
||||
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
|
||||
}
|
||||
|
||||
@ -955,12 +1055,12 @@ func TestContextNegotiationWithJSON(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
|
||||
c.Negotiate(200, Negotiate{
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEJSON, MIMEXML},
|
||||
Data: H{"foo": "bar"},
|
||||
})
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -970,12 +1070,12 @@ func TestContextNegotiationWithXML(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
|
||||
c.Negotiate(200, Negotiate{
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEXML, MIMEJSON},
|
||||
Data: H{"foo": "bar"},
|
||||
})
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, 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"))
|
||||
}
|
||||
@ -987,13 +1087,13 @@ func TestContextNegotiationWithHTML(t *testing.T) {
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
c.Negotiate(200, Negotiate{
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEHTML},
|
||||
Data: H{"name": "gin"},
|
||||
HTMLName: "t",
|
||||
})
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "Hello gin", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -1003,11 +1103,11 @@ func TestContextNegotiationNotSupport(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
|
||||
c.Negotiate(200, Negotiate{
|
||||
c.Negotiate(http.StatusOK, Negotiate{
|
||||
Offered: []string{MIMEPOSTForm},
|
||||
})
|
||||
|
||||
assert.Equal(t, 406, w.Code)
|
||||
assert.Equal(t, http.StatusNotAcceptable, w.Code)
|
||||
assert.Equal(t, c.index, abortIndex)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
@ -1065,11 +1165,11 @@ func TestContextAbortWithStatus(t *testing.T) {
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.index = 4
|
||||
c.AbortWithStatus(401)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.Equal(t, 401, c.Writer.Status())
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
|
||||
@ -1087,11 +1187,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
||||
in.Bar = "barValue"
|
||||
in.Foo = "fooValue"
|
||||
|
||||
c.AbortWithStatusJSON(415, in)
|
||||
c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in)
|
||||
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.Equal(t, 415, c.Writer.Status())
|
||||
assert.Equal(t, 415, w.Code)
|
||||
assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status())
|
||||
assert.Equal(t, http.StatusUnsupportedMediaType, w.Code)
|
||||
assert.True(t, c.IsAborted())
|
||||
|
||||
contentType := w.Header().Get("Content-Type")
|
||||
@ -1154,9 +1254,9 @@ func TestContextAbortWithError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input")
|
||||
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
@ -1230,6 +1330,26 @@ func TestContextBindWithJSON(t *testing.T) {
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
func TestContextBindWithXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<foo>FOO</foo>
|
||||
<bar>BAR</bar>
|
||||
</root>`))
|
||||
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||
|
||||
var obj struct {
|
||||
Foo string `xml:"foo"`
|
||||
Bar string `xml:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.BindXML(&obj))
|
||||
assert.Equal(t, "FOO", obj.Foo)
|
||||
assert.Equal(t, "BAR", obj.Bar)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextBindWithQuery(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
@ -1264,7 +1384,7 @@ func TestContextBadAutoBind(t *testing.T) {
|
||||
|
||||
assert.Empty(t, obj.Bar)
|
||||
assert.Empty(t, obj.Foo)
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
|
||||
@ -1300,6 +1420,27 @@ func TestContextShouldBindWithJSON(t *testing.T) {
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextShouldBindWithXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<foo>FOO</foo>
|
||||
<bar>BAR</bar>
|
||||
</root>`))
|
||||
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||
|
||||
var obj struct {
|
||||
Foo string `xml:"foo"`
|
||||
Bar string `xml:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.ShouldBindXML(&obj))
|
||||
assert.Equal(t, "FOO", obj.Foo)
|
||||
assert.Equal(t, "BAR", obj.Bar)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextShouldBindWithQuery(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
@ -1490,3 +1631,58 @@ func TestContextRenderDataFromReader(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
|
||||
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
|
||||
}
|
||||
|
||||
type TestResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
closeChannel chan bool
|
||||
}
|
||||
|
||||
func (r *TestResponseRecorder) CloseNotify() <-chan bool {
|
||||
return r.closeChannel
|
||||
}
|
||||
|
||||
func (r *TestResponseRecorder) closeClient() {
|
||||
r.closeChannel <- true
|
||||
}
|
||||
|
||||
func CreateTestResponseRecorder() *TestResponseRecorder {
|
||||
return &TestResponseRecorder{
|
||||
httptest.NewRecorder(),
|
||||
make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextStream(t *testing.T) {
|
||||
w := CreateTestResponseRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
stopStream := true
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
defer func() {
|
||||
stopStream = false
|
||||
}()
|
||||
|
||||
w.Write([]byte("test"))
|
||||
|
||||
return stopStream
|
||||
})
|
||||
|
||||
assert.Equal(t, "testtest", w.Body.String())
|
||||
}
|
||||
|
||||
func TestContextStreamWithClientGone(t *testing.T) {
|
||||
w := CreateTestResponseRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Stream(func(writer io.Writer) bool {
|
||||
defer func() {
|
||||
w.closeClient()
|
||||
}()
|
||||
|
||||
writer.Write([]byte("test"))
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, "test", w.Body.String())
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ set -e
|
||||
|
||||
echo "mode: count" > coverage.out
|
||||
|
||||
for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do
|
||||
for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do
|
||||
go test -v -covermode=count -coverprofile=profile.out $d
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out | grep -v "mode:" >> coverage.out
|
||||
|
3
debug.go
3
debug.go
@ -47,6 +47,9 @@ func debugPrint(format string, values ...interface{}) {
|
||||
}
|
||||
|
||||
func debugPrintWARNINGDefault() {
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
||||
|
||||
`)
|
||||
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||||
|
||||
`)
|
||||
|
@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) {
|
||||
setup(&w)
|
||||
defer teardown()
|
||||
|
||||
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl"))
|
||||
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl"))
|
||||
debugPrintLoadTemplate(templ)
|
||||
assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String())
|
||||
}
|
||||
@ -92,7 +92,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||
defer teardown()
|
||||
|
||||
debugPrintWARNINGDefault()
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String())
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String())
|
||||
}
|
||||
|
||||
func TestDebugPrintWARNINGNew(t *testing.T) {
|
||||
|
22
errors.go
22
errors.go
@ -9,21 +9,27 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gin-gonic/gin/json"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
||||
type ErrorType uint64
|
||||
|
||||
const (
|
||||
ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails
|
||||
ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails
|
||||
// ErrorTypeBind is used when Context.Bind() fails.
|
||||
ErrorTypeBind ErrorType = 1 << 63
|
||||
// ErrorTypeRender is used when Context.Render() fails.
|
||||
ErrorTypeRender ErrorType = 1 << 62
|
||||
// ErrorTypePrivate indicates a private error.
|
||||
ErrorTypePrivate ErrorType = 1 << 0
|
||||
ErrorTypePublic ErrorType = 1 << 1
|
||||
|
||||
// ErrorTypePublic indicates a public error.
|
||||
ErrorTypePublic ErrorType = 1 << 1
|
||||
// ErrorTypeAny indicates other any error.
|
||||
ErrorTypeAny ErrorType = 1<<64 - 1
|
||||
ErrorTypeNu = 2
|
||||
)
|
||||
|
||||
// Error represents a error's specification.
|
||||
type Error struct {
|
||||
Err error
|
||||
Type ErrorType
|
||||
@ -34,11 +40,13 @@ type errorMsgs []*Error
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
// SetType sets the error's type.
|
||||
func (msg *Error) SetType(flags ErrorType) *Error {
|
||||
msg.Type = flags
|
||||
return msg
|
||||
}
|
||||
|
||||
// SetMeta sets the error's meta data.
|
||||
func (msg *Error) SetMeta(data interface{}) *Error {
|
||||
msg.Meta = data
|
||||
return msg
|
||||
@ -70,11 +78,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(msg.JSON())
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
// Error implements the error interface.
|
||||
func (msg Error) Error() string {
|
||||
return msg.Err.Error()
|
||||
}
|
||||
|
||||
// IsType judges one error.
|
||||
func (msg *Error) IsType(flags ErrorType) bool {
|
||||
return (msg.Type & flags) > 0
|
||||
}
|
||||
@ -138,6 +147,7 @@ func (a errorMsgs) JSON() interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaller interface.
|
||||
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(a.JSON())
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/json"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -19,17 +19,17 @@ func TestError(t *testing.T) {
|
||||
Type: ErrorTypePrivate,
|
||||
}
|
||||
assert.Equal(t, err.Error(), baseError.Error())
|
||||
assert.Equal(t, err.JSON(), H{"error": baseError.Error()})
|
||||
assert.Equal(t, H{"error": baseError.Error()}, err.JSON())
|
||||
|
||||
assert.Equal(t, err.SetType(ErrorTypePublic), err)
|
||||
assert.Equal(t, err.Type, ErrorTypePublic)
|
||||
assert.Equal(t, ErrorTypePublic, err.Type)
|
||||
|
||||
assert.Equal(t, err.SetMeta("some data"), err)
|
||||
assert.Equal(t, err.Meta, "some data")
|
||||
assert.Equal(t, err.JSON(), H{
|
||||
assert.Equal(t, "some data", err.Meta)
|
||||
assert.Equal(t, H{
|
||||
"error": baseError.Error(),
|
||||
"meta": "some data",
|
||||
})
|
||||
}, err.JSON())
|
||||
|
||||
jsonBytes, _ := json.Marshal(err)
|
||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||
@ -38,22 +38,22 @@ func TestError(t *testing.T) {
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
assert.Equal(t, err.JSON(), H{
|
||||
assert.Equal(t, H{
|
||||
"error": baseError.Error(),
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
}, err.JSON())
|
||||
|
||||
err.SetMeta(H{
|
||||
"error": "custom error",
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
assert.Equal(t, err.JSON(), H{
|
||||
assert.Equal(t, H{
|
||||
"error": "custom error",
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
}, err.JSON())
|
||||
|
||||
type customError struct {
|
||||
status string
|
||||
|
@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var DB = make(map[string]string)
|
||||
var db = make(map[string]string)
|
||||
|
||||
func setupRouter() *gin.Engine {
|
||||
// Disable Console Color
|
||||
@ -13,17 +15,17 @@ func setupRouter() *gin.Engine {
|
||||
|
||||
// Ping test
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
|
||||
// Get user value
|
||||
r.GET("/user/:name", func(c *gin.Context) {
|
||||
user := c.Params.ByName("name")
|
||||
value, ok := DB[user]
|
||||
value, ok := db[user]
|
||||
if ok {
|
||||
c.JSON(200, gin.H{"user": user, "value": value})
|
||||
c.JSON(http.StatusOK, gin.H{"user": user, "value": value})
|
||||
} else {
|
||||
c.JSON(200, gin.H{"user": user, "status": "no value"})
|
||||
c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"})
|
||||
}
|
||||
})
|
||||
|
||||
@ -48,8 +50,8 @@ func setupRouter() *gin.Engine {
|
||||
}
|
||||
|
||||
if c.Bind(&json) == nil {
|
||||
DB[user] = json.Value
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
db[user] = json.Value
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "pong", w.Body.String())
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// Booking contains binded and validated data.
|
||||
type Booking struct {
|
||||
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
||||
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||
|
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/thinkerou/favicon"
|
||||
)
|
||||
@ -9,7 +11,7 @@ func main() {
|
||||
app := gin.Default()
|
||||
app.Use(favicon.New("./favicon.ico"))
|
||||
app.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "Hello favicon.")
|
||||
c.String(http.StatusOK, "Hello favicon.")
|
||||
})
|
||||
app.Run(":8080")
|
||||
}
|
||||
|
19
examples/grpc/README.md
Normal file
19
examples/grpc/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
## How to run this example
|
||||
|
||||
1. run grpc server
|
||||
|
||||
```sh
|
||||
$ go run grpc/server.go
|
||||
```
|
||||
|
||||
2. run gin server
|
||||
|
||||
```sh
|
||||
$ go run gin/main.go
|
||||
```
|
||||
|
||||
3. use curl command to test it
|
||||
|
||||
```sh
|
||||
$ curl -v 'http://localhost:8052/rest/n/thinkerou'
|
||||
```
|
46
examples/grpc/gin/main.go
Normal file
46
examples/grpc/gin/main.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
pb "github.com/gin-gonic/gin/examples/grpc/pb"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
client := pb.NewGreeterClient(conn)
|
||||
|
||||
// Set up a http setver.
|
||||
r := gin.Default()
|
||||
r.GET("/rest/n/:name", func(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
|
||||
// Contact the server and print out its response.
|
||||
req := &pb.HelloRequest{Name: name}
|
||||
res, err := client.SayHello(c, req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"result": fmt.Sprint(res.Message),
|
||||
})
|
||||
})
|
||||
|
||||
// Run http server
|
||||
if err := r.Run(":8052"); err != nil {
|
||||
log.Fatalf("could not run server: %v", err)
|
||||
}
|
||||
}
|
34
examples/grpc/grpc/server.go
Normal file
34
examples/grpc/grpc/server.go
Normal file
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
pb "github.com/gin-gonic/gin/examples/grpc/pb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct{}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
lis, err := net.Listen("tcp", ":50051")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterGreeterServer(s, &server{})
|
||||
|
||||
// Register reflection service on gRPC server.
|
||||
reflection.Register(s)
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
151
examples/grpc/pb/helloworld.pb.go
Normal file
151
examples/grpc/pb/helloworld.pb.go
Normal file
@ -0,0 +1,151 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: helloworld.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package helloworld is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
helloworld.proto
|
||||
|
||||
It has these top-level messages:
|
||||
HelloRequest
|
||||
HelloReply
|
||||
*/
|
||||
package helloworld
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// The request message containing the user's name.
|
||||
type HelloRequest struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
|
||||
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloRequest) ProtoMessage() {}
|
||||
func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
// The response message containing the greetings
|
||||
type HelloReply struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (m *HelloReply) Reset() { *m = HelloReply{} }
|
||||
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*HelloReply) ProtoMessage() {}
|
||||
func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
|
||||
proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Greeter service
|
||||
|
||||
type GreeterClient interface {
|
||||
// Sends a greeting
|
||||
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
|
||||
}
|
||||
|
||||
type greeterClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
|
||||
return &greeterClient{cc}
|
||||
}
|
||||
|
||||
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
|
||||
out := new(HelloReply)
|
||||
err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Greeter service
|
||||
|
||||
type GreeterServer interface {
|
||||
// Sends a greeting
|
||||
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
|
||||
}
|
||||
|
||||
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
|
||||
s.RegisterService(&_Greeter_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(HelloRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GreeterServer).SayHello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/helloworld.Greeter/SayHello",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Greeter_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "helloworld.Greeter",
|
||||
HandlerType: (*GreeterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SayHello",
|
||||
Handler: _Greeter_SayHello_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "helloworld.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 174 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
|
||||
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
|
||||
0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
|
||||
0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
|
||||
0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
|
||||
0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
|
||||
0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
|
||||
0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7,
|
||||
0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb,
|
||||
0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b,
|
||||
0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
|
||||
}
|
37
examples/grpc/pb/helloworld.proto
Normal file
37
examples/grpc/pb/helloworld.proto
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.grpc.examples.helloworld";
|
||||
option java_outer_classname = "HelloWorldProto";
|
||||
|
||||
package helloworld;
|
||||
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -27,7 +28,7 @@ func main() {
|
||||
r.SetHTMLTemplate(html)
|
||||
|
||||
r.GET("/welcome", func(c *gin.Context) {
|
||||
c.HTML(200, "https", gin.H{
|
||||
c.HTML(http.StatusOK, "https", gin.H{
|
||||
"status": "success",
|
||||
})
|
||||
})
|
||||
|
@ -13,16 +13,19 @@ func main() {
|
||||
StartGin()
|
||||
}
|
||||
|
||||
// ConfigRuntime sets the number of operating system threads.
|
||||
func ConfigRuntime() {
|
||||
nuCPU := runtime.NumCPU()
|
||||
runtime.GOMAXPROCS(nuCPU)
|
||||
fmt.Printf("Running with %d CPUs\n", nuCPU)
|
||||
}
|
||||
|
||||
// StartWorkers start starsWorker by goroutine.
|
||||
func StartWorkers() {
|
||||
go statsWorker()
|
||||
}
|
||||
|
||||
// StartGin starts gin web server with setting router.
|
||||
func StartGin() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) {
|
||||
fmt.Println("ip blocked")
|
||||
}
|
||||
c.Abort()
|
||||
c.String(503, "you were automatically banned :)")
|
||||
c.String(http.StatusServiceUnavailable, "you were automatically banned :)")
|
||||
}
|
||||
}
|
||||
|
||||
func index(c *gin.Context) {
|
||||
c.Redirect(301, "/room/hn")
|
||||
c.Redirect(http.StatusMovedPermanently, "/room/hn")
|
||||
}
|
||||
|
||||
func roomGET(c *gin.Context) {
|
||||
@ -38,7 +39,7 @@ func roomGET(c *gin.Context) {
|
||||
if len(nick) > 13 {
|
||||
nick = nick[0:12] + "..."
|
||||
}
|
||||
c.HTML(200, "room_login.templ.html", gin.H{
|
||||
c.HTML(http.StatusOK, "room_login.templ.html", gin.H{
|
||||
"roomid": roomid,
|
||||
"nick": nick,
|
||||
"timestamp": time.Now().Unix(),
|
||||
@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) {
|
||||
validMessage := len(message) > 1 && len(message) < 200
|
||||
validNick := len(nick) > 1 && len(nick) < 14
|
||||
if !validMessage || !validNick {
|
||||
c.JSON(400, gin.H{
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"status": "failed",
|
||||
"error": "the message or nickname is too long",
|
||||
})
|
||||
@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) {
|
||||
}
|
||||
messages.Add("inbound", 1)
|
||||
room(roomid).Submit(post)
|
||||
c.JSON(200, post)
|
||||
c.JSON(http.StatusOK, post)
|
||||
}
|
||||
|
||||
func streamRoom(c *gin.Context) {
|
||||
|
@ -50,6 +50,7 @@ func connectedUsers() uint64 {
|
||||
return uint64(connected)
|
||||
}
|
||||
|
||||
// Stats returns savedStats data.
|
||||
func Stats() map[string]uint64 {
|
||||
mutexStats.RLock()
|
||||
defer mutexStats.RUnlock()
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -34,7 +35,7 @@ func stream(c *gin.Context) {
|
||||
func roomGET(c *gin.Context) {
|
||||
roomid := c.Param("roomid")
|
||||
userid := fmt.Sprint(rand.Int31())
|
||||
c.HTML(200, "chat_room", gin.H{
|
||||
c.HTML(http.StatusOK, "chat_room", gin.H{
|
||||
"roomid": roomid,
|
||||
"userid": userid,
|
||||
})
|
||||
@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) {
|
||||
message := c.PostForm("message")
|
||||
room(roomid).Submit(userid + ": " + message)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"message": message,
|
||||
})
|
||||
|
@ -20,7 +20,7 @@ func main() {
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl")
|
||||
router.LoadHTMLFiles("../../testdata/template/raw.tmpl")
|
||||
|
||||
router.GET("/raw", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||
|
97
gin.go
97
gin.go
@ -16,7 +16,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version is Framework's version.
|
||||
Version = "v1.2"
|
||||
Version = "v1.3.0"
|
||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
)
|
||||
|
||||
@ -26,7 +26,10 @@ var (
|
||||
defaultAppEngine bool
|
||||
)
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
|
||||
// HandlersChain defines a HandlerFunc array.
|
||||
type HandlersChain []HandlerFunc
|
||||
|
||||
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
||||
@ -37,12 +40,14 @@ func (c HandlersChain) Last() HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RouteInfo represents a request route's specification which contains method and path and its handler.
|
||||
type RouteInfo struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler string
|
||||
}
|
||||
|
||||
// RoutesInfo defines a RouteInfo array.
|
||||
type RoutesInfo []RouteInfo
|
||||
|
||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||
@ -155,6 +160,7 @@ func (engine *Engine) allocateContext() *Context {
|
||||
return &Context{engine: engine, KeysMutex: &sync.RWMutex{}}
|
||||
}
|
||||
|
||||
// Delims sets template left and right delims and returns a Engine instance.
|
||||
func (engine *Engine) Delims(left, right string) *Engine {
|
||||
engine.delims = render.Delims{Left: left, Right: right}
|
||||
return engine
|
||||
@ -171,14 +177,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||
left := engine.delims.Left
|
||||
right := engine.delims.Right
|
||||
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||
|
||||
if IsDebugging() {
|
||||
debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
||||
debugPrintLoadTemplate(templ)
|
||||
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||
return
|
||||
}
|
||||
|
||||
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||
engine.SetHTMLTemplate(templ)
|
||||
}
|
||||
|
||||
@ -349,43 +355,45 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
// Find root of the tree for the given HTTP method
|
||||
t := engine.trees
|
||||
for i, tl := 0, len(t); i < tl; i++ {
|
||||
if t[i].method == httpMethod {
|
||||
root := t[i].root
|
||||
// Find route in tree
|
||||
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
||||
if handlers != nil {
|
||||
c.handlers = handlers
|
||||
c.Params = params
|
||||
c.Next()
|
||||
c.writermem.WriteHeaderNow()
|
||||
if t[i].method != httpMethod {
|
||||
continue
|
||||
}
|
||||
root := t[i].root
|
||||
// Find route in tree
|
||||
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
||||
if handlers != nil {
|
||||
c.handlers = handlers
|
||||
c.Params = params
|
||||
c.Next()
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && path != "/" {
|
||||
if tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && path != "/" {
|
||||
if tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
}
|
||||
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||
return
|
||||
}
|
||||
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if engine.HandleMethodNotAllowed {
|
||||
for _, tree := range engine.trees {
|
||||
if tree.method != httpMethod {
|
||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||
c.handlers = engine.allNoMethod
|
||||
serveError(c, 405, default405Body)
|
||||
return
|
||||
}
|
||||
if tree.method == httpMethod {
|
||||
continue
|
||||
}
|
||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||
c.handlers = engine.allNoMethod
|
||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.handlers = engine.allNoRoute
|
||||
serveError(c, 404, default404Body)
|
||||
serveError(c, http.StatusNotFound, default404Body)
|
||||
}
|
||||
|
||||
var mimePlain = []string{MIMEPlain}
|
||||
@ -393,28 +401,29 @@ var mimePlain = []string{MIMEPlain}
|
||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||
c.writermem.status = code
|
||||
c.Next()
|
||||
if !c.writermem.Written() {
|
||||
if c.writermem.Status() == code {
|
||||
c.writermem.Header()["Content-Type"] = mimePlain
|
||||
c.Writer.Write(defaultMessage)
|
||||
} else {
|
||||
c.writermem.WriteHeaderNow()
|
||||
}
|
||||
if c.writermem.Written() {
|
||||
return
|
||||
}
|
||||
if c.writermem.Status() == code {
|
||||
c.writermem.Header()["Content-Type"] = mimePlain
|
||||
c.Writer.Write(defaultMessage)
|
||||
return
|
||||
}
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
|
||||
func redirectTrailingSlash(c *Context) {
|
||||
req := c.Request
|
||||
path := req.URL.Path
|
||||
code := 301 // Permanent redirect, request with GET method
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = 307
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
|
||||
req.URL.Path = path + "/"
|
||||
if length := len(path); length > 1 && path[length-1] == '/' {
|
||||
req.URL.Path = path[:length-1]
|
||||
} else {
|
||||
req.URL.Path = path + "/"
|
||||
}
|
||||
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||
@ -425,14 +434,10 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
req := c.Request
|
||||
path := req.URL.Path
|
||||
|
||||
fixedPath, found := root.findCaseInsensitivePath(
|
||||
cleanPath(path),
|
||||
trailingSlash,
|
||||
)
|
||||
if found {
|
||||
code := 301 // Permanent redirect, request with GET method
|
||||
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = 307
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
req.URL.Path = string(fixedPath)
|
||||
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||
|
11
ginS/gins.go
11
ginS/gins.go
@ -22,14 +22,17 @@ func engine() *gin.Engine {
|
||||
return internalEngine
|
||||
}
|
||||
|
||||
// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
|
||||
func LoadHTMLGlob(pattern string) {
|
||||
engine().LoadHTMLGlob(pattern)
|
||||
}
|
||||
|
||||
// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles.
|
||||
func LoadHTMLFiles(files ...string) {
|
||||
engine().LoadHTMLFiles(files...)
|
||||
}
|
||||
|
||||
// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
|
||||
func SetHTMLTemplate(templ *template.Template) {
|
||||
engine().SetHTMLTemplate(templ)
|
||||
}
|
||||
@ -39,7 +42,7 @@ func NoRoute(handlers ...gin.HandlerFunc) {
|
||||
engine().NoRoute(handlers...)
|
||||
}
|
||||
|
||||
// NoMethod sets the handlers called when... TODO
|
||||
// NoMethod is a wrapper for Engine.NoMethod.
|
||||
func NoMethod(handlers ...gin.HandlerFunc) {
|
||||
engine().NoMethod(handlers...)
|
||||
}
|
||||
@ -50,6 +53,7 @@ func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
|
||||
return engine().Group(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// Handle is a wrapper for Engine.Handle.
|
||||
func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().Handle(httpMethod, relativePath, handlers...)
|
||||
}
|
||||
@ -89,10 +93,12 @@ func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().HEAD(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// Any is a wrapper for Engine.Any.
|
||||
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().Any(relativePath, handlers...)
|
||||
}
|
||||
|
||||
// StaticFile is a wrapper for Engine.StaticFile.
|
||||
func StaticFile(relativePath, filepath string) gin.IRoutes {
|
||||
return engine().StaticFile(relativePath, filepath)
|
||||
}
|
||||
@ -107,6 +113,7 @@ func Static(relativePath, root string) gin.IRoutes {
|
||||
return engine().Static(relativePath, root)
|
||||
}
|
||||
|
||||
// StaticFS is a wrapper for Engine.StaticFS.
|
||||
func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
||||
return engine().StaticFS(relativePath, fs)
|
||||
}
|
||||
@ -128,7 +135,7 @@ func Run(addr ...string) (err error) {
|
||||
// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
|
||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
||||
func RunTLS(addr string, certFile string, keyFile string) (err error) {
|
||||
func RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
return engine().RunTLS(addr, certFile, keyFile)
|
||||
}
|
||||
|
||||
|
28
gin_test.go
28
gin_test.go
@ -30,7 +30,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl")
|
||||
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||
})
|
||||
@ -41,7 +41,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
||||
})
|
||||
if tls {
|
||||
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||
router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
|
||||
} else {
|
||||
router.Run(":8888")
|
||||
}
|
||||
@ -59,7 +59,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
router.LoadHTMLGlob("./fixtures/basic/*")
|
||||
router.LoadHTMLGlob("./testdata/template/*")
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||
})
|
||||
@ -70,7 +70,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
|
||||
})
|
||||
if tls {
|
||||
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||
router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
|
||||
} else {
|
||||
router.Run(":8888")
|
||||
}
|
||||
@ -88,7 +88,7 @@ func TestLoadHTMLGlob(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
@ -101,7 +101,7 @@ func TestLoadHTMLGlob2(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
@ -114,7 +114,7 @@ func TestLoadHTMLGlob3(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
@ -134,7 +134,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
@ -148,7 +148,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp[:]))
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
@ -187,7 +187,7 @@ func TestLoadHTMLFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
td()
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ func TestLoadHTMLFiles2(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
td()
|
||||
}
|
||||
|
||||
@ -211,7 +211,7 @@ func TestLoadHTMLFiles3(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
td()
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||
td()
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp[:]))
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
|
||||
td()
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) {
|
||||
for _, param := range c.Params {
|
||||
output[param.Key] = param.Value
|
||||
}
|
||||
c.JSON(200, output)
|
||||
c.JSON(http.StatusOK, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
20
internal/json/json.go
Normal file
20
internal/json/json.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !jsoniter
|
||||
|
||||
package json
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
var (
|
||||
// Marshal is exported by gin/json package.
|
||||
Marshal = json.Marshal
|
||||
// MarshalIndent is exported by gin/json package.
|
||||
MarshalIndent = json.MarshalIndent
|
||||
// NewDecoder is exported by gin/json package.
|
||||
NewDecoder = json.NewDecoder
|
||||
// NewEncoder is exported by gin/json package.
|
||||
NewEncoder = json.NewEncoder
|
||||
)
|
21
internal/json/jsoniter.go
Normal file
21
internal/json/jsoniter.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build jsoniter
|
||||
|
||||
package json
|
||||
|
||||
import "github.com/json-iterator/go"
|
||||
|
||||
var (
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
// Marshal is exported by gin/json package.
|
||||
Marshal = json.Marshal
|
||||
// MarshalIndent is exported by gin/json package.
|
||||
MarshalIndent = json.MarshalIndent
|
||||
// NewDecoder is exported by gin/json package.
|
||||
NewDecoder = json.NewDecoder
|
||||
// NewEncoder is exported by gin/json package.
|
||||
NewEncoder = json.NewEncoder
|
||||
)
|
15
json/json.go
15
json/json.go
@ -1,15 +0,0 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !jsoniter
|
||||
|
||||
package json
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
var (
|
||||
Marshal = json.Marshal
|
||||
MarshalIndent = json.MarshalIndent
|
||||
NewDecoder = json.NewDecoder
|
||||
)
|
@ -1,16 +0,0 @@
|
||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build jsoniter
|
||||
|
||||
package json
|
||||
|
||||
import "github.com/json-iterator/go"
|
||||
|
||||
var (
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
Marshal = json.Marshal
|
||||
MarshalIndent = json.MarshalIndent
|
||||
NewDecoder = json.NewDecoder
|
||||
)
|
@ -7,6 +7,7 @@ package gin
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||
|
||||
func colorForStatus(code int) string {
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||
return green
|
||||
case code >= 300 && code < 400:
|
||||
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||
return white
|
||||
case code >= 400 && code < 500:
|
||||
case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
|
||||
return yellow
|
||||
default:
|
||||
return red
|
||||
|
@ -7,6 +7,7 @@ package gin
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestColorForStatus(t *testing.T) {
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red")
|
||||
}
|
||||
|
||||
@ -106,23 +107,23 @@ func TestErrorLogger(t *testing.T) {
|
||||
c.Error(errors.New("this is an error"))
|
||||
})
|
||||
router.GET("/abort", func(c *Context) {
|
||||
c.AbortWithError(401, errors.New("no authorized"))
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized"))
|
||||
})
|
||||
router.GET("/print", func(c *Context) {
|
||||
c.Error(errors.New("this is an error"))
|
||||
c.String(500, "hola!")
|
||||
c.String(http.StatusInternalServerError, "hola!")
|
||||
})
|
||||
|
||||
w := performRequest(router, "GET", "/error")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/abort")
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/print")
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "ACDB", signature)
|
||||
}
|
||||
|
||||
@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.Equal(t, "ACEGHFDB", signature)
|
||||
}
|
||||
|
||||
@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 405, w.Code)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
assert.Equal(t, "ACEGHFDB", signature)
|
||||
}
|
||||
|
||||
@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.Equal(t, "AC X DB", signature)
|
||||
}
|
||||
|
||||
@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
||||
})
|
||||
router.Use(func(c *Context) {
|
||||
signature += "C"
|
||||
c.AbortWithStatus(401)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
c.Next()
|
||||
signature += "D"
|
||||
})
|
||||
@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, "ACD", signature)
|
||||
}
|
||||
|
||||
@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
||||
router.Use(func(c *Context) {
|
||||
signature += "A"
|
||||
c.Next()
|
||||
c.AbortWithStatus(410)
|
||||
c.AbortWithStatus(http.StatusGone)
|
||||
signature += "B"
|
||||
|
||||
})
|
||||
@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 410, w.Code)
|
||||
assert.Equal(t, http.StatusGone, w.Code)
|
||||
assert.Equal(t, "ACB", signature)
|
||||
}
|
||||
|
||||
@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(func(context *Context) {
|
||||
signature += "A"
|
||||
context.AbortWithError(500, errors.New("foo"))
|
||||
context.AbortWithError(http.StatusInternalServerError, errors.New("foo"))
|
||||
})
|
||||
router.Use(func(context *Context) {
|
||||
signature += "B"
|
||||
@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
// TEST
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Equal(t, "A", signature)
|
||||
}
|
||||
|
||||
func TestMiddlewareWrite(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(func(c *Context) {
|
||||
c.String(400, "hola\n")
|
||||
c.String(http.StatusBadRequest, "hola\n")
|
||||
})
|
||||
router.Use(func(c *Context) {
|
||||
c.XML(400, H{"foo": "bar"})
|
||||
c.XML(http.StatusBadRequest, H{"foo": "bar"})
|
||||
})
|
||||
router.Use(func(c *Context) {
|
||||
c.JSON(400, H{"foo": "bar"})
|
||||
c.JSON(http.StatusBadRequest, H{"foo": "bar"})
|
||||
})
|
||||
router.GET("/", func(c *Context) {
|
||||
c.JSON(400, H{"foo": "bar"})
|
||||
c.JSON(http.StatusBadRequest, H{"foo": "bar"})
|
||||
}, func(c *Context) {
|
||||
c.Render(400, sse.Event{
|
||||
c.Render(http.StatusBadRequest, sse.Event{
|
||||
Event: "test",
|
||||
Data: "message",
|
||||
})
|
||||
@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
||||
}
|
||||
|
13
mode.go
13
mode.go
@ -11,12 +11,16 @@ import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
// ENV_GIN_MODE indicates environment name for gin mode.
|
||||
const ENV_GIN_MODE = "GIN_MODE"
|
||||
|
||||
const (
|
||||
DebugMode = "debug"
|
||||
// DebugMode indicates gin mode is debug.
|
||||
DebugMode = "debug"
|
||||
// ReleaseMode indicates gin mode is relase.
|
||||
ReleaseMode = "release"
|
||||
TestMode = "test"
|
||||
// TestMode indicates gin mode is test.
|
||||
TestMode = "test"
|
||||
)
|
||||
const (
|
||||
debugCode = iota
|
||||
@ -42,6 +46,7 @@ func init() {
|
||||
SetMode(mode)
|
||||
}
|
||||
|
||||
// SetMode sets gin mode according to input string.
|
||||
func SetMode(value string) {
|
||||
switch value {
|
||||
case DebugMode, "":
|
||||
@ -59,14 +64,18 @@ func SetMode(value string) {
|
||||
modeName = value
|
||||
}
|
||||
|
||||
// DisableBindValidation closes the default validator.
|
||||
func DisableBindValidation() {
|
||||
binding.Validator = nil
|
||||
}
|
||||
|
||||
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to
|
||||
// call the UseNumber method on the JSON Decoder instance.
|
||||
func EnableJsonDecoderUseNumber() {
|
||||
binding.EnableDecoderUseNumber = true
|
||||
}
|
||||
|
||||
// Mode returns currently gin mode.
|
||||
func Mode() string {
|
||||
return modeName
|
||||
}
|
||||
|
6
path.go
6
path.go
@ -41,7 +41,7 @@ func cleanPath(p string) string {
|
||||
buf[0] = '/'
|
||||
}
|
||||
|
||||
trailing := n > 2 && p[n-1] == '/'
|
||||
trailing := n > 1 && p[n-1] == '/'
|
||||
|
||||
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||
@ -59,11 +59,11 @@ func cleanPath(p string) string {
|
||||
|
||||
case p[r] == '.' && p[r+1] == '/':
|
||||
// . element
|
||||
r++
|
||||
r += 2
|
||||
|
||||
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||
// .. element: remove to last /
|
||||
r += 2
|
||||
r += 3
|
||||
|
||||
if w > 1 {
|
||||
// can backtrack
|
||||
|
@ -24,6 +24,7 @@ var cleanTests = []struct {
|
||||
|
||||
// missing root
|
||||
{"", "/"},
|
||||
{"a/", "/a/"},
|
||||
{"abc", "/abc"},
|
||||
{"abc/def", "/abc/def"},
|
||||
{"a/b/c", "/a/b/c"},
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"runtime"
|
||||
"time"
|
||||
@ -41,7 +42,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset)
|
||||
}
|
||||
c.AbortWithStatus(500)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
|
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -22,7 +23,7 @@ func TestPanicInHandler(t *testing.T) {
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||
assert.Contains(t, buffer.String(), "TestPanicInHandler")
|
||||
@ -33,11 +34,31 @@ func TestPanicWithAbort(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(RecoveryWithWriter(nil))
|
||||
router.GET("/recovery", func(c *Context) {
|
||||
c.AbortWithStatus(400)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
panic("Oupps, Houston, we have a problem")
|
||||
})
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
bs := source(nil, 0)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
|
||||
in := [][]byte{
|
||||
[]byte("Hello world."),
|
||||
[]byte("Hi, gin.."),
|
||||
}
|
||||
bs = source(in, 10)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
|
||||
bs = source(in, 1)
|
||||
assert.Equal(t, []byte("Hello world."), bs)
|
||||
}
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
bs := function(1)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package render
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Data contains ContentType and bytes data.
|
||||
type Data struct {
|
||||
ContentType string
|
||||
Data []byte
|
||||
@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WriteContentType (Data) writes custom ContentType.
|
||||
func (r Data) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, []string{r.ContentType})
|
||||
}
|
||||
|
@ -9,20 +9,27 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
||||
type Delims struct {
|
||||
Left string
|
||||
// Left delimiter, defaults to {{.
|
||||
Left string
|
||||
// Right delimiter, defaults to }}.
|
||||
Right string
|
||||
}
|
||||
|
||||
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
||||
type HTMLRender interface {
|
||||
// Instance returns an HTML instance.
|
||||
Instance(string, interface{}) Render
|
||||
}
|
||||
|
||||
// HTMLProduction contains template reference and its delims.
|
||||
type HTMLProduction struct {
|
||||
Template *template.Template
|
||||
Delims Delims
|
||||
}
|
||||
|
||||
// HTMLDebug contains template delims and pattern and function with file list.
|
||||
type HTMLDebug struct {
|
||||
Files []string
|
||||
Glob string
|
||||
@ -30,6 +37,7 @@ type HTMLDebug struct {
|
||||
FuncMap template.FuncMap
|
||||
}
|
||||
|
||||
// HTML contains template reference and its name with given interface object.
|
||||
type HTML struct {
|
||||
Template *template.Template
|
||||
Name string
|
||||
@ -38,6 +46,7 @@ type HTML struct {
|
||||
|
||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||
|
||||
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
|
||||
func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
||||
return HTML{
|
||||
Template: r.Template,
|
||||
@ -46,6 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
||||
}
|
||||
}
|
||||
|
||||
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
|
||||
func (r HTMLDebug) Instance(name string, data interface{}) Render {
|
||||
return HTML{
|
||||
Template: r.loadTemplate(),
|
||||
@ -66,6 +76,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
|
||||
panic("the HTML debug render was created without files or glob pattern")
|
||||
}
|
||||
|
||||
// Render (HTML) executes template and writes its result with custom ContentType for response.
|
||||
func (r HTML) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
|
||||
@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error {
|
||||
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||
}
|
||||
|
||||
// WriteContentType (HTML) writes HTML ContentType.
|
||||
func (r HTML) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, htmlContentType)
|
||||
}
|
||||
|
49
render/json.go
Executable file → Normal file
49
render/json.go
Executable file → Normal file
@ -6,35 +6,48 @@ package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/json"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
// JSON contains the given interface object.
|
||||
type JSON struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// IndentedJSON contains the given interface object.
|
||||
type IndentedJSON struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// SecureJSON contains the given interface object and its prefix.
|
||||
type SecureJSON struct {
|
||||
Prefix string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// JsonpJSON contains the given interface object its callback.
|
||||
type JsonpJSON struct {
|
||||
Callback string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// AsciiJSON contains the given interface object.
|
||||
type AsciiJSON struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
||||
type SecureJSONPrefix string
|
||||
|
||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||
var jsonAsciiContentType = []string{"application/json"}
|
||||
|
||||
// Render (JSON) writes data with custom ContentType.
|
||||
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||
if err = WriteJSON(w, r.Data); err != nil {
|
||||
panic(err)
|
||||
@ -42,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WriteContentType (JSON) writes JSON ContentType.
|
||||
func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonContentType)
|
||||
}
|
||||
|
||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||
writeContentType(w, jsonContentType)
|
||||
jsonBytes, err := json.Marshal(obj)
|
||||
@ -56,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
||||
@ -66,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (IndentedJSON) writes JSON ContentType.
|
||||
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonContentType)
|
||||
}
|
||||
|
||||
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
||||
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
jsonBytes, err := json.Marshal(r.Data)
|
||||
@ -84,10 +102,12 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (SecureJSON) writes JSON ContentType.
|
||||
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonContentType)
|
||||
}
|
||||
|
||||
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
|
||||
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
r.WriteContentType(w)
|
||||
ret, err := json.Marshal(r.Data)
|
||||
@ -109,6 +129,33 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (JsonpJSON) writes Javascript ContentType.
|
||||
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonpContentType)
|
||||
}
|
||||
|
||||
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
|
||||
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||
r.WriteContentType(w)
|
||||
ret, err := json.Marshal(r.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
for _, r := range string(ret) {
|
||||
cvt := string(r)
|
||||
if r >= 128 {
|
||||
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
||||
}
|
||||
buffer.WriteString(cvt)
|
||||
}
|
||||
|
||||
w.Write(buffer.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonAsciiContentType)
|
||||
}
|
||||
|
31
render/json_17.go
Normal file
31
render/json_17.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
// PureJSON contains the given interface object.
|
||||
type PureJSON struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetEscapeHTML(false)
|
||||
return encoder.Encode(r.Data)
|
||||
}
|
||||
|
||||
// WriteContentType (PureJSON) writes custom ContentType.
|
||||
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonContentType)
|
||||
}
|
@ -10,22 +10,26 @@ import (
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
// MsgPack contains the given interface object.
|
||||
type MsgPack struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
||||
|
||||
// WriteContentType (MsgPack) writes MsgPack ContentType.
|
||||
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, msgpackContentType)
|
||||
}
|
||||
|
||||
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
|
||||
func (r MsgPack) Render(w http.ResponseWriter) error {
|
||||
return WriteMsgPack(w, r.Data)
|
||||
}
|
||||
|
||||
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
|
||||
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
||||
writeContentType(w, msgpackContentType)
|
||||
var h codec.Handle = new(codec.MsgpackHandle)
|
||||
return codec.NewEncoder(w, h).Encode(obj)
|
||||
var mh codec.MsgpackHandle
|
||||
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||
}
|
||||
|
36
render/protobuf.go
Normal file
36
render/protobuf.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// ProtoBuf contains the given interface object.
|
||||
type ProtoBuf struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
var protobufContentType = []string{"application/x-protobuf"}
|
||||
|
||||
// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.
|
||||
func (r ProtoBuf) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
|
||||
bytes, err := proto.Marshal(r.Data.(proto.Message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Write(bytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
|
||||
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, protobufContentType)
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
@ -6,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Reader contains the IO reader and its length, and custom ContentType and other headers.
|
||||
type Reader struct {
|
||||
ContentType string
|
||||
ContentLength int64
|
||||
@ -22,10 +27,12 @@ func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// WriteContentType (Reader) writes custom ContentType.
|
||||
func (r Reader) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, []string{r.ContentType})
|
||||
}
|
||||
|
||||
// writeHeaders writes custom Header.
|
||||
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
|
||||
header := w.Header()
|
||||
for k, v := range headers {
|
||||
|
@ -9,13 +9,17 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Redirect contains the http request reference and redirects status code and location.
|
||||
type Redirect struct {
|
||||
Code int
|
||||
Request *http.Request
|
||||
Location string
|
||||
}
|
||||
|
||||
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
||||
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
||||
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
||||
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||
}
|
||||
@ -23,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (Redirect) don't write any ContentType.
|
||||
func (r Redirect) WriteContentType(http.ResponseWriter) {}
|
||||
|
5
render/render.go
Executable file → Normal file
5
render/render.go
Executable file → Normal file
@ -6,8 +6,11 @@ package render
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
|
||||
type Render interface {
|
||||
// Render writes data with custom ContentType.
|
||||
Render(http.ResponseWriter) error
|
||||
// WriteContentType writes custom ContentType.
|
||||
WriteContentType(w http.ResponseWriter)
|
||||
}
|
||||
|
||||
@ -26,6 +29,8 @@ var (
|
||||
_ Render = YAML{}
|
||||
_ Render = MsgPack{}
|
||||
_ Render = Reader{}
|
||||
_ Render = AsciiJSON{}
|
||||
_ Render = ProtoBuf{}
|
||||
)
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
|
26
render/render_17_test.go
Normal file
26
render/render_17_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderPureJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"html": "<b>",
|
||||
}
|
||||
err := (PureJSON{data}).Render(w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
89
render/render_test.go
Executable file → Normal file
89
render/render_test.go
Executable file → Normal file
@ -15,8 +15,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ugorji/go/codec"
|
||||
|
||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||
)
|
||||
|
||||
// TODO unit tests
|
||||
@ -49,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) {
|
||||
func TestRenderJSON(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"foo": "bar",
|
||||
"html": "<b>",
|
||||
}
|
||||
|
||||
(JSON{data}).WriteContentType(w)
|
||||
@ -58,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
|
||||
err := (JSON{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
@ -158,6 +162,21 @@ func TestRenderJsonpJSON(t *testing.T) {
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderJsonpJSONError2(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}
|
||||
(JsonpJSON{"", data}).WriteContentType(w)
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
|
||||
e := (JsonpJSON{"", data}).Render(w)
|
||||
assert.NoError(t, e)
|
||||
|
||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
||||
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderJsonpJSONFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := make(chan int)
|
||||
@ -167,6 +186,35 @@ func TestRenderJsonpJSONFail(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderAsciiJSON(t *testing.T) {
|
||||
w1 := httptest.NewRecorder()
|
||||
data1 := map[string]interface{}{
|
||||
"lang": "GO语言",
|
||||
"tag": "<br>",
|
||||
}
|
||||
|
||||
err := (AsciiJSON{data1}).Render(w1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
|
||||
assert.Equal(t, "application/json", w1.Header().Get("Content-Type"))
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
data2 := float64(3.1415926)
|
||||
|
||||
err = (AsciiJSON{data2}).Render(w2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "3.1415926", w2.Body.String())
|
||||
}
|
||||
|
||||
func TestRenderAsciiJSONFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := make(chan int)
|
||||
|
||||
// json: unsupported type: chan int
|
||||
assert.Error(t, (AsciiJSON{data}).Render(w))
|
||||
}
|
||||
|
||||
type xmlmap map[string]interface{}
|
||||
|
||||
// Allows type H to be used with xml.Marshal
|
||||
@ -221,6 +269,35 @@ func TestRenderYAMLFail(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// test Protobuf rendering
|
||||
func TestRenderProtoBuf(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
reps := []int64{int64(1), int64(2)}
|
||||
label := "test"
|
||||
data := &testdata.Test{
|
||||
Label: &label,
|
||||
Reps: reps,
|
||||
}
|
||||
|
||||
(ProtoBuf{data}).WriteContentType(w)
|
||||
protoData, err := proto.Marshal(data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||
|
||||
err = (ProtoBuf{data}).Render(w)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(protoData), w.Body.String())
|
||||
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestRenderProtoBufFail(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := &testdata.Test{}
|
||||
err := (ProtoBuf{data}).Render(w)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRenderXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
data := xmlmap{
|
||||
@ -242,7 +319,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
data1 := Redirect{
|
||||
Code: 301,
|
||||
Code: http.StatusMovedPermanently,
|
||||
Request: req,
|
||||
Location: "/new/location",
|
||||
}
|
||||
@ -252,7 +329,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
data2 := Redirect{
|
||||
Code: 200,
|
||||
Code: http.StatusOK,
|
||||
Request: req,
|
||||
Location: "/new/location",
|
||||
}
|
||||
@ -344,7 +421,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
||||
|
||||
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"},
|
||||
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
|
||||
Glob: "",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
@ -363,7 +440,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: nil,
|
||||
Glob: "../fixtures/basic/hello*",
|
||||
Glob: "../testdata/template/hello*",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// String contains the given interface object slice and its format.
|
||||
type String struct {
|
||||
Format string
|
||||
Data []interface{}
|
||||
@ -17,20 +18,23 @@ type String struct {
|
||||
|
||||
var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||
|
||||
// Render (String) writes data with custom ContentType.
|
||||
func (r String) Render(w http.ResponseWriter) error {
|
||||
WriteString(w, r.Format, r.Data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (String) writes Plain ContentType.
|
||||
func (r String) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, plainContentType)
|
||||
}
|
||||
|
||||
// WriteString writes data according to its format and write custom ContentType.
|
||||
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
||||
writeContentType(w, plainContentType)
|
||||
if len(data) > 0 {
|
||||
fmt.Fprintf(w, format, data...)
|
||||
} else {
|
||||
io.WriteString(w, format)
|
||||
return
|
||||
}
|
||||
io.WriteString(w, format)
|
||||
}
|
||||
|
@ -9,17 +9,20 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// XML contains the given interface object.
|
||||
type XML struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
||||
|
||||
// Render (XML) encodes the given interface object and writes data with custom ContentType.
|
||||
func (r XML) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
return xml.NewEncoder(w).Encode(r.Data)
|
||||
}
|
||||
|
||||
// WriteContentType (XML) writes XML ContentType for response.
|
||||
func (r XML) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, xmlContentType)
|
||||
}
|
||||
|
@ -10,12 +10,14 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// YAML contains the given interface object.
|
||||
type YAML struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||
|
||||
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
||||
func (r YAML) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
|
||||
@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteContentType (YAML) writes YAML ContentType for response.
|
||||
func (r YAML) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, yamlContentType)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
const (
|
||||
noWritten = -1
|
||||
defaultStatus = 200
|
||||
defaultStatus = http.StatusOK
|
||||
)
|
||||
|
||||
type responseWriterBase interface {
|
||||
@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
|
||||
// Flush implements the http.Flush interface.
|
||||
func (w *responseWriter) Flush() {
|
||||
w.WriteHeaderNow()
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) {
|
||||
|
||||
writer.reset(testWritter)
|
||||
assert.Equal(t, -1, writer.size)
|
||||
assert.Equal(t, 200, writer.status)
|
||||
assert.Equal(t, http.StatusOK, writer.status)
|
||||
assert.Equal(t, testWritter, writer.ResponseWriter)
|
||||
assert.Equal(t, -1, w.Size())
|
||||
assert.Equal(t, 200, w.Status())
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.False(t, w.Written())
|
||||
}
|
||||
|
||||
@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) {
|
||||
writer.reset(testWritter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
w.WriteHeader(300)
|
||||
w.WriteHeader(http.StatusMultipleChoices)
|
||||
assert.False(t, w.Written())
|
||||
assert.Equal(t, 300, w.Status())
|
||||
assert.NotEqual(t, testWritter.Code, 300)
|
||||
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||
assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code)
|
||||
|
||||
w.WriteHeader(-1)
|
||||
assert.Equal(t, 300, w.Status())
|
||||
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||
}
|
||||
|
||||
func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||
@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||
writer.reset(testWritter)
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
w.WriteHeader(300)
|
||||
w.WriteHeader(http.StatusMultipleChoices)
|
||||
w.WriteHeaderNow()
|
||||
|
||||
assert.True(t, w.Written())
|
||||
assert.Equal(t, 0, w.Size())
|
||||
assert.Equal(t, 300, testWritter.Code)
|
||||
assert.Equal(t, http.StatusMultipleChoices, testWritter.Code)
|
||||
|
||||
writer.size = 10
|
||||
w.WriteHeaderNow()
|
||||
@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) {
|
||||
n, err := w.Write([]byte("hola"))
|
||||
assert.Equal(t, 4, n)
|
||||
assert.Equal(t, 4, w.Size())
|
||||
assert.Equal(t, 200, w.Status())
|
||||
assert.Equal(t, 200, testWritter.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Status())
|
||||
assert.Equal(t, http.StatusOK, testWritter.Code)
|
||||
assert.Equal(t, "hola", testWritter.Body.String())
|
||||
assert.NoError(t, err)
|
||||
|
||||
@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) {
|
||||
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func TestResponseWriterFlush(t *testing.T) {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writer := &responseWriter{}
|
||||
writer.reset(w)
|
||||
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
writer.Flush()
|
||||
}))
|
||||
defer testServer.Close()
|
||||
|
||||
// should return 500
|
||||
resp, err := http.Get(testServer.URL)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IRouter defines all router handle interface includes single and group router.
|
||||
type IRouter interface {
|
||||
IRoutes
|
||||
Group(string, ...HandlerFunc) *RouterGroup
|
||||
}
|
||||
|
||||
// Iroutes defins all router handle interface.
|
||||
type IRoutes interface {
|
||||
Use(...HandlerFunc) IRoutes
|
||||
|
||||
@ -34,8 +36,8 @@ type IRoutes interface {
|
||||
StaticFS(string, http.FileSystem) IRoutes
|
||||
}
|
||||
|
||||
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
|
||||
// and an array of handlers (middleware).
|
||||
// RouterGroup is used internally to configure router, a RouterGroup is associated with
|
||||
// a prefix and an array of handlers (middleware).
|
||||
type RouterGroup struct {
|
||||
Handlers HandlersChain
|
||||
basePath string
|
||||
@ -61,6 +63,8 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
|
||||
}
|
||||
}
|
||||
|
||||
// BasePath returns the base path of router group.
|
||||
// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
|
||||
func (group *RouterGroup) BasePath() string {
|
||||
return group.basePath
|
||||
}
|
||||
@ -184,7 +188,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
||||
_, nolisting := fs.(*onlyfilesFS)
|
||||
return func(c *Context) {
|
||||
if nolisting {
|
||||
c.Writer.WriteHeader(404)
|
||||
c.Writer.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) {
|
||||
assert.Equal(t, "/v1/login/", login.BasePath())
|
||||
|
||||
handler := func(c *Context) {
|
||||
c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
|
||||
c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index)
|
||||
}
|
||||
|
||||
switch method {
|
||||
@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) {
|
||||
}
|
||||
|
||||
w := performRequest(router, method, "/v1/login/test")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
|
||||
|
||||
w = performRequest(router, method, "/v1/test")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
||||
}
|
||||
|
||||
|
@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) {
|
||||
func TestRouterMethod(t *testing.T) {
|
||||
router := New()
|
||||
router.PUT("/hey2", func(c *Context) {
|
||||
c.String(200, "sup2")
|
||||
c.String(http.StatusOK, "sup2")
|
||||
})
|
||||
|
||||
router.PUT("/hey", func(c *Context) {
|
||||
c.String(200, "called")
|
||||
c.String(http.StatusOK, "called")
|
||||
})
|
||||
|
||||
router.PUT("/hey3", func(c *Context) {
|
||||
c.String(200, "sup3")
|
||||
c.String(http.StatusOK, "sup3")
|
||||
})
|
||||
|
||||
w := performRequest(router, "PUT", "/hey")
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "called", w.Body.String())
|
||||
}
|
||||
|
||||
@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/path/")
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3/")
|
||||
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
|
||||
w = performRequest(router, "PUT", "/path4")
|
||||
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2/")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
w = performRequest(router, "PUT", "/path4/")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
router.RedirectTrailingSlash = false
|
||||
|
||||
w = performRequest(router, "GET", "/path/")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "POST", "/path3/")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
w = performRequest(router, "PUT", "/path4")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/PATH")
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3")
|
||||
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path4")
|
||||
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
}
|
||||
|
||||
// TestContextParamsGet tests that a parameter can be parsed from the URL.
|
||||
@ -236,7 +236,7 @@ func TestRouteParamsByName(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/test/john/smith/is/super/great")
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "john", name)
|
||||
assert.Equal(t, "smith", lastName)
|
||||
assert.Equal(t, "/is/super/great", wild)
|
||||
@ -265,7 +265,7 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
w2 := performRequest(router, "GET", "/result")
|
||||
|
||||
assert.Equal(t, w, w2)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
|
||||
@ -273,7 +273,7 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
w4 := performRequest(router, "HEAD", "/result")
|
||||
|
||||
assert.Equal(t, w3, w4)
|
||||
assert.Equal(t, 200, w3.Code)
|
||||
assert.Equal(t, http.StatusOK, w3.Code)
|
||||
}
|
||||
|
||||
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
||||
@ -283,7 +283,7 @@ func TestRouteStaticListingDir(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "gin.go")
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||
}
|
||||
|
||||
@ -310,7 +310,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/gin.go")
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "package gin")
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||
@ -333,19 +333,29 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedEnabled2(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = true
|
||||
// add one methodTree to trees
|
||||
router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}})
|
||||
router.GET("/path2", func(c *Context) {})
|
||||
w := performRequest(router, "POST", "/path2")
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||
router := New()
|
||||
router.HandleMethodNotAllowed = false
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
|
||||
router.NoMethod(func(c *Context) {
|
||||
c.String(http.StatusTeapot, "responseText")
|
||||
})
|
||||
w = performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, "404 page not found", w.Body.String())
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestRouterNotFound(t *testing.T) {
|
||||
@ -360,20 +370,20 @@ func TestRouterNotFound(t *testing.T) {
|
||||
code int
|
||||
location string
|
||||
}{
|
||||
{"/path/", 301, "/path"}, // TSR -/
|
||||
{"/dir", 301, "/dir/"}, // TSR +/
|
||||
{"", 301, "/"}, // TSR +/
|
||||
{"/PATH", 301, "/path"}, // Fixed Case
|
||||
{"/DIR/", 301, "/dir/"}, // Fixed Case
|
||||
{"/PATH/", 301, "/path"}, // Fixed Case -/
|
||||
{"/DIR", 301, "/dir/"}, // Fixed Case +/
|
||||
{"/../path", 301, "/path"}, // CleanPath
|
||||
{"/nope", 404, ""}, // NotFound
|
||||
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||
{"", http.StatusMovedPermanently, "/"}, // TSR +/
|
||||
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||
{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
|
||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
w := performRequest(router, "GET", tr.route)
|
||||
assert.Equal(t, tr.code, w.Code)
|
||||
if w.Code != 404 {
|
||||
if w.Code != http.StatusNotFound {
|
||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||
}
|
||||
}
|
||||
@ -381,24 +391,24 @@ func TestRouterNotFound(t *testing.T) {
|
||||
// Test custom not found handler
|
||||
var notFound bool
|
||||
router.NoRoute(func(c *Context) {
|
||||
c.AbortWithStatus(404)
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
notFound = true
|
||||
})
|
||||
w := performRequest(router, "GET", "/nope")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.True(t, notFound)
|
||||
|
||||
// Test other method than GET (want 307 instead of 301)
|
||||
router.PATCH("/path", func(c *Context) {})
|
||||
w = performRequest(router, "PATCH", "/path/")
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
||||
|
||||
// Test special case where no node for the prefix "/" exists
|
||||
router = New()
|
||||
router.GET("/a", func(c *Context) {})
|
||||
w = performRequest(router, "GET", "/")
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRawPath(t *testing.T) {
|
||||
@ -417,7 +427,7 @@ func TestRouteRawPath(t *testing.T) {
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
@ -437,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||
|
@ -4,9 +4,7 @@
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
// CreateTestContext returns a fresh engine and context for testing purposes
|
||||
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package example is a generated protocol buffer package.
|
||||
Package protoexample is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
test.proto
|
||||
@ -11,7 +11,7 @@ It is generated from these files:
|
||||
It has these top-level messages:
|
||||
Test
|
||||
*/
|
||||
package example
|
||||
package protoexample
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
@ -109,5 +109,5 @@ func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||
proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package example;
|
||||
package protoexample;
|
||||
|
||||
enum FOO {X=17;};
|
||||
|
11
tree_test.go
11
tree_test.go
@ -125,8 +125,6 @@ func TestTreeAddAndGet(t *testing.T) {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/a", false, "/a", nil},
|
||||
{"/", true, "", nil},
|
||||
@ -168,8 +166,6 @@ func TestTreeWildcard(t *testing.T) {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||
@ -208,7 +204,6 @@ func TestUnescapeParameters(t *testing.T) {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
unescape := true
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
@ -260,8 +255,6 @@ func testRoutes(t *testing.T, routes []testRoute) {
|
||||
t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
}
|
||||
|
||||
func TestTreeWildcardConflict(t *testing.T) {
|
||||
@ -328,8 +321,6 @@ func TestTreeDupliatePath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
{"/doc/", false, "/doc/", nil},
|
||||
@ -444,8 +435,6 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
tsrRoutes := [...]string{
|
||||
"/hi/",
|
||||
"/b",
|
||||
|
8
utils.go
8
utils.go
@ -14,8 +14,10 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BindKey indicates a default bind key.
|
||||
const BindKey = "_gin-gonic/gin/bindkey"
|
||||
|
||||
// Bind is a helper function for given interface object and returns a Gin middleware.
|
||||
func Bind(val interface{}) HandlerFunc {
|
||||
value := reflect.ValueOf(val)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
@ -33,16 +35,14 @@ func Bind(val interface{}) HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// WrapF is a helper function for wrapping http.HandlerFunc
|
||||
// Returns a Gin middleware
|
||||
// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
|
||||
func WrapF(f http.HandlerFunc) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
f(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// WrapH is a helper function for wrapping http.Handler
|
||||
// Returns a Gin middleware
|
||||
// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.
|
||||
func WrapH(h http.Handler) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
|
@ -5,6 +5,8 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
@ -23,7 +25,7 @@ type testStruct struct {
|
||||
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
assert.Equal(t.T, "POST", req.Method)
|
||||
assert.Equal(t.T, "/path", req.URL.Path)
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, "hello")
|
||||
}
|
||||
|
||||
@ -33,16 +35,16 @@ func TestWrap(t *testing.T) {
|
||||
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
|
||||
assert.Equal(t, "GET", req.Method)
|
||||
assert.Equal(t, "/path2", req.URL.Path)
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, "hola!")
|
||||
}))
|
||||
|
||||
w := performRequest(router, "POST", "/path")
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Equal(t, "hello", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Equal(t, "hola!", w.Body.String())
|
||||
}
|
||||
|
||||
@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) {
|
||||
Bind(&bindTestStruct{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestMarshalXMLforH(t *testing.T) {
|
||||
h := H{
|
||||
"": "test",
|
||||
}
|
||||
var b bytes.Buffer
|
||||
enc := xml.NewEncoder(&b)
|
||||
var x xml.StartElement
|
||||
e := h.MarshalXML(enc, x)
|
||||
assert.Error(t, e)
|
||||
}
|
||||
|
129
vendor/vendor.json
vendored
129
vendor/vendor.json
vendored
@ -1,19 +1,12 @@
|
||||
{
|
||||
"comment": "v1.2",
|
||||
"comment": "v1.3.0",
|
||||
"ignore": "test",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
|
||||
"comment": "v1.1.0",
|
||||
"checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=",
|
||||
"path": "github.com/davecgh/go-spew/spew",
|
||||
"revision": "346938d642f2ec3594ed81d874461961cd0faa76",
|
||||
"revisionTime": "2016-10-29T20:57:26Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=",
|
||||
"path": "github.com/dustin/go-broadcast",
|
||||
"revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1",
|
||||
"revisionTime": "2014-06-27T04:00:55Z"
|
||||
"revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
|
||||
"revisionTime": "2018-02-21T22:46:20Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
|
||||
@ -22,78 +15,44 @@
|
||||
"revisionTime": "2017-01-09T09:34:21Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=",
|
||||
"path": "github.com/gin-gonic/autotls",
|
||||
"revision": "8ca25fbde72bb72a00466215b94b489c71fcb815",
|
||||
"revisionTime": "2017-09-16T16:54:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
|
||||
"checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=",
|
||||
"path": "github.com/golang/protobuf/proto",
|
||||
"revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55",
|
||||
"revisionTime": "2017-06-01T23:02:30Z"
|
||||
"revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265",
|
||||
"revisionTime": "2018-04-30T18:52:41Z",
|
||||
"version": "v1.1.0",
|
||||
"versionExact": "v1.1.0"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=",
|
||||
"path": "github.com/jessevdk/go-assets",
|
||||
"revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5",
|
||||
"revisionTime": "2016-09-21T14:41:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=",
|
||||
"checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
|
||||
"path": "github.com/json-iterator/go",
|
||||
"revision": "36b14963da70d11297d313183d7e6388c8510e1e",
|
||||
"revisionTime": "2017-08-29T15:58:51Z"
|
||||
"revision": "1624edc4454b8682399def8740d46db5e4362ba4",
|
||||
"revisionTime": "2018-08-06T06:07:27Z",
|
||||
"version": "v1.1",
|
||||
"versionExact": "v1.1.5"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
|
||||
"path": "github.com/manucorporat/stats",
|
||||
"revision": "8f2d6ace262eba462e9beb552382c98be51d807b",
|
||||
"revisionTime": "2015-05-31T20:46:25Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=",
|
||||
"checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
|
||||
"path": "github.com/mattn/go-isatty",
|
||||
"revision": "57fdcb988a5c543893cc61bce354a6e24ab70022",
|
||||
"revisionTime": "2017-03-07T16:30:44Z"
|
||||
"revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39",
|
||||
"revisionTime": "2017-09-25T05:34:41Z",
|
||||
"version": "v0.0.3",
|
||||
"versionExact": "v0.0.3"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||
"comment": "v1.0.0",
|
||||
"path": "github.com/pmezard/go-difflib/difflib",
|
||||
"revision": "792786c7400a136282c1664665ae0a8db921c6c2",
|
||||
"revisionTime": "2016-01-10T10:55:54Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=",
|
||||
"comment": "v1.1.4",
|
||||
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
||||
"path": "github.com/stretchr/testify/assert",
|
||||
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
|
||||
"revisionTime": "2016-09-25T22:06:09Z"
|
||||
"revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
|
||||
"revisionTime": "2018-05-06T18:05:49Z",
|
||||
"version": "v1.2.2",
|
||||
"versionExact": "v1.2.2"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=",
|
||||
"path": "github.com/thinkerou/favicon",
|
||||
"revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93",
|
||||
"revisionTime": "2017-07-10T14:05:20Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
|
||||
"checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=",
|
||||
"path": "github.com/ugorji/go/codec",
|
||||
"revision": "c88ee250d0221a57af388746f5cf03768c21d6e2",
|
||||
"revisionTime": "2017-02-15T20:11:44Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=",
|
||||
"path": "golang.org/x/crypto/acme",
|
||||
"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
|
||||
"revisionTime": "2017-06-19T06:03:41Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=",
|
||||
"path": "golang.org/x/crypto/acme/autocert",
|
||||
"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
|
||||
"revisionTime": "2017-06-19T06:03:41Z"
|
||||
"revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
|
||||
"revisionTime": "2018-04-07T10:07:33Z",
|
||||
"version": "v1.1.1",
|
||||
"versionExact": "v1.1.1"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
|
||||
@ -103,30 +62,20 @@
|
||||
"revisionTime": "2016-10-18T08:54:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
|
||||
"path": "golang.org/x/sync/errgroup",
|
||||
"revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690",
|
||||
"revisionTime": "2017-07-19T03:38:01Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
|
||||
"path": "golang.org/x/sys/unix",
|
||||
"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
|
||||
"revisionTime": "2017-03-08T15:04:45Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
|
||||
"comment": "v8.18.1",
|
||||
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
|
||||
"path": "gopkg.in/go-playground/validator.v8",
|
||||
"revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c",
|
||||
"revisionTime": "2016-07-18T13:41:25Z"
|
||||
"revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf",
|
||||
"revisionTime": "2017-07-30T05:02:35Z",
|
||||
"version": "v8.18.2",
|
||||
"versionExact": "v8.18.2"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=",
|
||||
"comment": "v2",
|
||||
"checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
|
||||
"path": "gopkg.in/yaml.v2",
|
||||
"revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0",
|
||||
"revisionTime": "2016-09-28T15:37:09Z"
|
||||
"revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
|
||||
"revisionTime": "2018-03-28T19:50:20Z",
|
||||
"version": "v2.2.1",
|
||||
"versionExact": "v2.2.1"
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/gin-gonic/gin"
|
||||
|
Loading…
x
Reference in New Issue
Block a user