refactor: support AbortWithStatusJSON (#200)

This commit is contained in:
Bo-Yi Wu
2017-03-29 09:21:31 +08:00
committed by GitHub
parent d94c9437f5
commit 5b2b3695e5
67 changed files with 55188 additions and 128 deletions

View File

@@ -1,8 +1,6 @@
List of all the awesome people working to make Gin the best Web Framework in Go.
##gin 0.x series authors
## gin 0.x series authors
**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
@@ -226,4 +224,4 @@ People and companies, who have contributed, in alphabetical order.
**@yuyabee**
- Fixed README
- Fixed README

View File

@@ -295,4 +295,4 @@ BenchmarkPossum_GPlusAll 100000 19685 ns/op 6240 B/op
BenchmarkR2router_GPlusAll 100000 16251 ns/op 5040 B/op 76 allocs/op
BenchmarkRevel_GPlusAll 20000 93489 ns/op 21656 B/op 368 allocs/op
BenchmarkRivet_GPlusAll 100000 16907 ns/op 5408 B/op 64 allocs/op
```
```

View File

@@ -1,12 +1,6 @@
# Gin Web Framework <img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
#Gin Web Framework
<img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph Badge](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
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,6 +9,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
```sh
$ cat test.go
```
```go
package main
@@ -86,28 +81,39 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
1. Download and install it:
```sh
$ go get gopkg.in/gin-gonic/gin.v1
```
```sh
$ go get gopkg.in/gin-gonic/gin.v1
```
2. Import it in your code:
```go
import "gopkg.in/gin-gonic/gin.v1"
```
```go
import "gopkg.in/gin-gonic/gin.v1"
```
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
```go
import "net/http"
```
```go
import "net/http"
```
4. (Optional) Use latest changes (note: they may be broken and/or unstable):
```sh
$ GIN_PATH=$GOPATH/src/gopkg.in/gin-gonic/gin.v1
$ git -C $GIN_PATH checkout develop
$ git -C $GIN_PATH pull origin develop
```
## API Examples
#### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
```go
func main() {
// Disable Console Color
// gin.DisableConsoleColor()
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
@@ -127,7 +133,7 @@ func main() {
}
```
#### Parameters in path
### Parameters in path
```go
func main() {
@@ -152,7 +158,8 @@ func main() {
}
```
#### Querystring parameters
### Querystring parameters
```go
func main() {
router := gin.Default()
@@ -219,34 +226,66 @@ func main() {
id: 1234; page: 1; name: manu; message: this_is_great
```
### Another example: upload file
### Upload files
References issue [#548](https://github.com/gin-gonic/gin/issues/548).
#### Single file
References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single).
```go
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// single file
file, _ := c.FormFile("file")
log.Println(file.Filename)
file, header , err := c.Request.FormFile("upload")
filename := header.Filename
fmt.Println(header.Filename)
out, err := os.Create("./tmp/"+filename+".png")
if err != nil {
log.Fatal(err)
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
c.String(http.StatusOK, fmt.Printf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
```
#### Grouping routes
How to `curl`:
```bash
curl -X POST http://localhost:8080/upload \
-F "file=@/Users/appleboy/test.zip" \
-H "Content-Type: multipart/form-data"
```
#### Multiple files
See the detail [example code](examples/upload-file/multiple).
```go
func main() {
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
}
c.String(http.StatusOK, fmt.Printf("%d files uploaded!", len(files)))
})
router.Run(":8080")
}
```
How to `curl`:
```bash
curl -X POST http://localhost:8080/upload \
-F "upload[]=@/Users/appleboy/test1.zip" \
-F "upload[]=@/Users/appleboy/test2.zip" \
-H "Content-Type: multipart/form-data"
```
### Grouping routes
```go
func main() {
router := gin.Default()
@@ -271,14 +310,14 @@ func main() {
}
```
#### Blank Gin without middleware by default
### Blank Gin without middleware by default
Use
```go
r := gin.New()
```
instead of
```go
@@ -286,7 +325,7 @@ r := gin.Default()
```
#### Using middleware
### Using middleware
```go
func main() {
// Creates a router without any middleware by default
@@ -321,7 +360,7 @@ func main() {
}
```
#### Model binding and validation
### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
@@ -371,8 +410,43 @@ func main() {
}
```
### Bind Query String
See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
```go
package main
import "log"
import "github.com/gin-gonic/gin"
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
func main() {
route := gin.Default()
route.GET("/testing", startPage)
route.Run(":8085")
}
func startPage(c *gin.Context) {
var person Person
// If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
// See more at https://github.com/gin-gonic/gin/blob/develop/binding/binding.go#L45
if c.Bind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
}
c.String(200, "Success")
}
```
### Multipart/Urlencoded binding
###Multipart/Urlencoded binding
```go
package main
@@ -410,8 +484,7 @@ Test it with:
$ curl -v --form user=user --form password=password http://localhost:8080/login
```
#### XML, JSON and YAML rendering
### XML, JSON and YAML rendering
```go
func main() {
@@ -450,7 +523,7 @@ func main() {
}
```
####Serving static files
### Serving static files
```go
func main() {
@@ -464,9 +537,9 @@ func main() {
}
```
####HTML rendering
### HTML rendering
Using LoadHTMLTemplates()
Using LoadHTMLGlob() or LoadHTMLFiles()
```go
func main() {
@@ -481,7 +554,9 @@ func main() {
router.Run(":8080")
}
```
templates/index.tmpl
```html
<html>
<h1>
@@ -509,7 +584,9 @@ func main() {
router.Run(":8080")
}
```
templates/posts/index.tmpl
```html
{{ define "posts/index.tmpl" }}
<html><h1>
@@ -519,7 +596,9 @@ templates/posts/index.tmpl
</html>
{{ end }}
```
templates/users/index.tmpl
```html
{{ define "users/index.tmpl" }}
<html><h1>
@@ -543,8 +622,11 @@ func main() {
}
```
### Multitemplate
#### Redirects
Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
### Redirects
Issuing a HTTP redirect is easy:
@@ -556,7 +638,7 @@ r.GET("/test", func(c *gin.Context) {
Both internal and external locations are supported.
#### Custom Middleware
### Custom Middleware
```go
func Logger() gin.HandlerFunc {
@@ -596,7 +678,8 @@ func main() {
}
```
#### Using BasicAuth() middleware
### Using BasicAuth() middleware
```go
// simulate some private data
var secrets = gin.H{
@@ -634,8 +717,8 @@ func main() {
}
```
### Goroutines inside a middleware
#### Goroutines inside a middleware
When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
```go
@@ -667,7 +750,7 @@ func main() {
}
```
#### Custom HTTP configuration
### Custom HTTP configuration
Use `http.ListenAndServe()` directly, like this:
@@ -694,7 +777,7 @@ func main() {
}
```
#### Graceful restart or stop
### Graceful restart or stop
Do you want to graceful restart or stop your web server?
There are some ways this can be done.
@@ -711,6 +794,62 @@ endless.ListenAndServe(":4242", router)
An alternative to endless:
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin.
[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go)
```go
// +build go1.8
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil {
log.Printf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exist")
}
```
## Contributing
@@ -725,7 +864,7 @@ An alternative to endless:
- 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.
## Example
## Users
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.

View File

@@ -15,6 +15,8 @@ const (
MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
)
type Binding interface {
@@ -40,6 +42,7 @@ var (
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
)
func Default(method, contentType string) Binding {
@@ -53,6 +56,8 @@ func Default(method, contentType string) Binding {
return XML
case MIMEPROTOBUF:
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
return Form
}

View File

@@ -8,6 +8,7 @@ import (
"errors"
"reflect"
"strconv"
"time"
)
func mapForm(ptr interface{}, form map[string][]string) error {
@@ -52,6 +53,12 @@ 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 {
return err
}
@@ -140,6 +147,26 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
return err
}
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")
}
l := time.Local
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
l = time.UTC
}
t, err := time.ParseInLocation(timeFormat, val, l)
if err != nil {
return err
}
value.Set(reflect.ValueOf(t))
return nil
}
// Don't pass in pointers to bind to. Can lead to bugs. See:
// https://github.com/codegangsta/martini-contrib/issues/40
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659

28
vendor/github.com/gin-gonic/gin/binding/msgpack.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package binding
import (
"net/http"
"github.com/ugorji/go/codec"
)
type msgpackBinding struct{}
func (msgpackBinding) Name() string {
return "msgpack"
}
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil {
//var decoder *codec.Decoder = codec.NewDecoder(req.Body, &codec.MsgpackHandle)
//if err := decoder.Decode(&obj); err != nil {
return err
}
return validate(obj)
}

View File

@@ -8,6 +8,7 @@ import (
"errors"
"io"
"math"
"mime/multipart"
"net"
"net/http"
"net/url"
@@ -16,8 +17,7 @@ import (
"github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render"
"github.com/manucorporat/sse"
"golang.org/x/net/context"
"gopkg.in/gin-contrib/sse.v0"
)
// Content-Type MIME of the most common data formats
@@ -31,7 +31,10 @@ const (
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
)
const abortIndex int8 = math.MaxInt8 / 2
const (
defaultMemory = 32 << 20 // 32 MB
abortIndex int8 = math.MaxInt8 / 2
)
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
@@ -50,8 +53,6 @@ type Context struct {
Accepted []string
}
var _ context.Context = &Context{}
/************************************/
/********** CONTEXT CREATION ********/
/************************************/
@@ -67,7 +68,7 @@ func (c *Context) reset() {
}
// Copy returns a copy of the current context that can be safely used outside the request's scope.
// This have to be used then the context has to be passed to a goroutine.
// This has to be used when the context has to be passed to a goroutine.
func (c *Context) Copy() *Context {
var cp = *c
cp.writermem.ResponseWriter = nil
@@ -119,6 +120,13 @@ func (c *Context) AbortWithStatus(code int) {
c.Abort()
}
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. This method stops the chain, writes the status code and return a JSON body
// It also sets the Content-Type as "application/json".
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
c.Abort()
c.JSON(code, jsonObj)
}
// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
// pushes the specified error to `c.Errors`.
// See Context.Error() for more details.
@@ -166,9 +174,7 @@ func (c *Context) Set(key string, value interface{}) {
// Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false)
func (c *Context) Get(key string) (value interface{}, exists bool) {
if c.Keys != nil {
value, exists = c.Keys[key]
}
value, exists = c.Keys[key]
return
}
@@ -296,7 +302,7 @@ func (c *Context) PostFormArray(key string) []string {
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
req := c.Request
req.ParseForm()
req.ParseMultipartForm(32 << 20) // 32 MB
req.ParseMultipartForm(defaultMemory)
if values := req.PostForm[key]; len(values) > 0 {
return values, true
}
@@ -308,6 +314,18 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
return []string{}, false
}
// 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)
return fh, err
}
// MultipartForm is the parsed multipart form, including file uploads.
func (c *Context) MultipartForm() (*multipart.Form, error) {
err := c.Request.ParseMultipartForm(defaultMemory)
return c.Request.MultipartForm, err
}
// Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding
@@ -338,13 +356,10 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
func (c *Context) ClientIP() string {
if c.engine.ForwardedByClientIP {
clientIP := strings.TrimSpace(c.requestHeader("X-Real-Ip"))
if len(clientIP) > 0 {
return clientIP
}
clientIP = c.requestHeader("X-Forwarded-For")
clientIP := c.requestHeader("X-Forwarded-For")
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
}
@@ -352,10 +367,22 @@ func (c *Context) ClientIP() string {
if len(clientIP) > 0 {
return clientIP
}
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
if len(clientIP) > 0 {
return clientIP
}
}
if c.engine.AppEngine {
if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" {
return addr
}
}
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
return ip
}
return ""
}
@@ -364,6 +391,16 @@ func (c *Context) ContentType() string {
return filterFlags(c.requestHeader("Content-Type"))
}
// IsWebsocket returns true if the request headers indicate that a websocket
// handshake is being initiated by the client.
func (c *Context) IsWebsocket() bool {
if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
strings.ToLower(c.requestHeader("Upgrade")) == "websocket" {
return true
}
return false
}
func (c *Context) requestHeader(key string) string {
if values, _ := c.Request.Header[key]; len(values) > 0 {
return values[0]
@@ -375,6 +412,19 @@ func (c *Context) requestHeader(key string) string {
/******** RESPONSE RENDERING ********/
/************************************/
// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function
func bodyAllowedForStatus(status int) bool {
switch {
case status >= 100 && status <= 199:
return false
case status == 204:
return false
case status == 304:
return false
}
return true
}
func (c *Context) Status(code int) {
c.writermem.WriteHeader(code)
}
@@ -390,6 +440,11 @@ func (c *Context) Header(key, value string) {
}
}
// GetHeader returns value from request headers
func (c *Context) GetHeader(key string) string {
return c.requestHeader(key)
}
func (c *Context) SetCookie(
name string,
value string,
@@ -424,6 +479,13 @@ func (c *Context) Cookie(name string) (string, error) {
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
panic(err)
}
@@ -448,10 +510,7 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
c.Status(code)
if err := render.WriteJSON(c.Writer, obj); err != nil {
panic(err)
}
c.Render(code, render.JSON{Data: obj})
}
// XML serializes the given struct as XML into the response body.
@@ -467,8 +526,7 @@ func (c *Context) YAML(code int, obj interface{}) {
// String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...interface{}) {
c.Status(code)
render.WriteString(c.Writer, format, values)
c.Render(code, render.String{Format: format, Data: values})
}
// Redirect returns a HTTP redirect to the specific location.

7
vendor/github.com/gin-gonic/gin/context_appengine.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
// +build appengine
package gin
func init() {
defaultAppEngine = true
}

View File

@@ -72,7 +72,7 @@ func (msg *Error) MarshalJSON() ([]byte, error) {
}
// Implements the error interface
func (msg *Error) Error() string {
func (msg Error) Error() string {
return msg.Err.Error()
}

View File

@@ -14,7 +14,7 @@ type (
}
)
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
// in router.Static().
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
// a filesystem that prevents http.FileServer() to list the directory files.

View File

@@ -15,10 +15,11 @@ import (
)
// Version is Framework's version
const Version = "v1.0rc2"
const Version = "v1.1.4"
var default404Body = []byte("404 page not found")
var default405Body = []byte("405 method not allowed")
var defaultAppEngine bool
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
@@ -78,6 +79,17 @@ type (
// handler.
HandleMethodNotAllowed bool
ForwardedByClientIP bool
// #726 #755 If enabled, it will thrust some headers starting with
// 'X-AppEngine...' for better integration with that PaaS.
AppEngine bool
// If enabled, the url.RawPath will be used to find parameters.
UseRawPath bool
// If true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool
}
)
@@ -89,6 +101,8 @@ var _ IRouter = &Engine{}
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
// - UseRawPath: false
// - UnescapePathValues: true
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
@@ -101,6 +115,9 @@ func New() *Engine {
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
trees: make(methodTrees, 0, 9),
}
engine.RouterGroup.engine = engine
@@ -267,9 +284,26 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
engine.pool.Put(c)
}
// Re-enter a context that has been rewritten.
// This can be done by setting c.Request.Path to your new target.
// Disclaimer: You can loop yourself to death with this, use wisely.
func (engine *Engine) HandleContext(c *Context) {
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(context *Context) {
httpMethod := context.Request.Method
path := context.Request.URL.Path
var path string
var unescape bool
if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
path = context.Request.URL.RawPath
unescape = engine.UnescapePathValues
} else {
path = context.Request.URL.Path
unescape = false
}
// Find root of the tree for the given HTTP method
t := engine.trees
@@ -277,7 +311,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
if t[i].method == httpMethod {
root := t[i].root
// Find route in tree
handlers, params, tsr := root.getValue(path, context.Params)
handlers, params, tsr := root.getValue(path, context.Params, unescape)
if handlers != nil {
context.handlers = handlers
context.Params = params
@@ -302,7 +336,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method != httpMethod {
if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
context.handlers = engine.allNoMethod
serveError(context, 405, default405Body)
return

View File

@@ -14,16 +14,21 @@ import (
)
var (
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
reset = string([]byte{27, 91, 48, 109})
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
reset = string([]byte{27, 91, 48, 109})
disableColor = false
)
func DisableConsoleColor() {
disableColor = true
}
func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny)
}
@@ -49,7 +54,9 @@ func Logger() HandlerFunc {
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
isTerm := true
if w, ok := out.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
if w, ok := out.(*os.File); !ok ||
(os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) ||
disableColor {
isTerm = false
}
@@ -87,12 +94,12 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
}
comment := c.Errors.ByType(ErrorTypePrivate).String()
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s %s %-7s %s\n%s",
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s",
end.Format("2006/01/02 - 15:04:05"),
statusColor, statusCode, reset,
latency,
clientIP,
methodColor, reset, method,
methodColor, method, reset,
path,
comment,
)

View File

@@ -19,9 +19,9 @@ const (
TestMode string = "test"
)
const (
debugCode = iota
releaseCode
testCode
debugCode = iota
releaseCode
testCode
)
// DefaultWriter is the default io.Writer used the Gin for debug output and

View File

@@ -11,10 +11,13 @@ type Data struct {
Data []byte
}
func (r Data) Render(w http.ResponseWriter) error {
if len(r.ContentType) > 0 {
w.Header()["Content-Type"] = []string{r.ContentType}
}
w.Write(r.Data)
return nil
// Render (Data) writes data with custom ContentType
func (r Data) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w)
_, err = w.Write(r.Data)
return
}
func (r Data) WriteContentType(w http.ResponseWriter) {
writeContentType(w, []string{r.ContentType})
}

View File

@@ -58,9 +58,14 @@ func (r HTMLDebug) loadTemplate() *template.Template {
}
func (r HTML) Render(w http.ResponseWriter) error {
writeContentType(w, htmlContentType)
r.WriteContentType(w)
if len(r.Name) == 0 {
return r.Template.Execute(w, r.Data)
}
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
}
func (r HTML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, htmlContentType)
}

View File

@@ -21,12 +21,29 @@ type (
var jsonContentType = []string{"application/json; charset=utf-8"}
func (r JSON) Render(w http.ResponseWriter) error {
return WriteJSON(w, r.Data)
func (r JSON) Render(w http.ResponseWriter) (err error) {
if err = WriteJSON(w, r.Data); err != nil {
panic(err)
}
return
}
func (r JSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj)
if err != nil {
return err
}
w.Write(jsonBytes)
return nil
}
func (r IndentedJSON) Render(w http.ResponseWriter) error {
writeContentType(w, jsonContentType)
r.WriteContentType(w)
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
if err != nil {
return err
@@ -35,7 +52,6 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
return nil
}
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
return json.NewEncoder(w).Encode(obj)
}

31
vendor/github.com/gin-gonic/gin/render/msgpack.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package render
import (
"net/http"
"github.com/ugorji/go/codec"
)
type MsgPack struct {
Data interface{}
}
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
writeContentType(w, msgpackContentType)
}
func (r MsgPack) Render(w http.ResponseWriter) error {
return WriteMsgPack(w, r.Data)
}
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)
}

View File

@@ -22,3 +22,5 @@ func (r Redirect) Render(w http.ResponseWriter) error {
http.Redirect(w, r.Request, r.Location, r.Code)
return nil
}
func (r Redirect) WriteContentType(http.ResponseWriter) {}

View File

@@ -8,6 +8,7 @@ import "net/http"
type Render interface {
Render(http.ResponseWriter) error
WriteContentType(w http.ResponseWriter)
}
var (
@@ -21,6 +22,8 @@ var (
_ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ Render = MsgPack{}
_ Render = MsgPack{}
)
func writeContentType(w http.ResponseWriter, value []string) {

View File

@@ -22,9 +22,12 @@ func (r String) Render(w http.ResponseWriter) error {
return nil
}
func (r String) WriteContentType(w http.ResponseWriter) {
writeContentType(w, plainContentType)
}
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
writeContentType(w, plainContentType)
if len(data) > 0 {
fmt.Fprintf(w, format, data...)
} else {

View File

@@ -16,6 +16,10 @@ type XML struct {
var xmlContentType = []string{"application/xml; charset=utf-8"}
func (r XML) Render(w http.ResponseWriter) error {
writeContentType(w, xmlContentType)
r.WriteContentType(w)
return xml.NewEncoder(w).Encode(r.Data)
}
func (r XML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, xmlContentType)
}

View File

@@ -17,7 +17,7 @@ type YAML struct {
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
func (r YAML) Render(w http.ResponseWriter) error {
writeContentType(w, yamlContentType)
r.WriteContentType(w)
bytes, err := yaml.Marshal(r.Data)
if err != nil {
@@ -27,3 +27,7 @@ func (r YAML) Render(w http.ResponseWriter) error {
w.Write(bytes)
return nil
}
func (r YAML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, yamlContentType)
}

13
vendor/github.com/gin-gonic/gin/test_helpers.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package gin
import (
"net/http"
)
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
r = New()
c = r.allocateContext()
c.reset()
c.writermem.reset(w)
return
}

View File

@@ -5,6 +5,7 @@
package gin
import (
"net/url"
"strings"
"unicode"
)
@@ -363,7 +364,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
// made if a handle exists with an extra (without the) trailing slash for the
// given path.
func (n *node) getValue(path string, po Params) (handlers HandlersChain, p Params, tsr bool) {
func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) {
p = po
walk: // Outer loop for walking the tree
for {
@@ -406,7 +407,15 @@ walk: // Outer loop for walking the tree
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[1:]
p[i].Value = path[:end]
val := path[:end]
if unescape {
var err error
if p[i].Value, err = url.QueryUnescape(val); err != nil {
p[i].Value = val // fallback, in case of error
}
} else {
p[i].Value = val
}
// we need to go deeper!
if end < len(path) {
@@ -440,7 +449,14 @@ walk: // Outer loop for walking the tree
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[2:]
p[i].Value = path
if unescape {
var err error
if p[i].Value, err = url.QueryUnescape(path); err != nil {
p[i].Value = path // fallback, in case of error
}
} else {
p[i].Value = path
}
handlers = n.handlers
return