forked from mirror/gin
Merge branch 'develop' | v0.5 release
This commit is contained in:
commit
e2fa89777e
56
AUTHORS.md
56
AUTHORS.md
|
@ -9,6 +9,10 @@ List of all the awesome people working to make Gin the best Web Framework in Go.
|
|||
|
||||
People and companies, who have contributed, in alphabetical order.
|
||||
|
||||
**@858806258 (杰哥)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@achedeuzot (Klemen Sever)**
|
||||
- Fix newline debug printing
|
||||
|
||||
|
@ -21,6 +25,10 @@ People and companies, who have contributed, in alphabetical order.
|
|||
- Typos in README
|
||||
|
||||
|
||||
**@alexanderdidenko (Aleksandr Didenko)**
|
||||
- Add support multipart/form-data
|
||||
|
||||
|
||||
**@alexandernyquist (Alexander Nyquist)**
|
||||
- Using template.Must to fix multiple return issue
|
||||
- ★ Added support for OPTIONS verb
|
||||
|
@ -55,15 +63,39 @@ People and companies, who have contributed, in alphabetical order.
|
|||
- Add example about serving static files
|
||||
|
||||
|
||||
**@donileo (Adonis)**
|
||||
- Add NoMethod handler
|
||||
|
||||
|
||||
**@dutchcoders (DutchCoders)**
|
||||
- ★ Fix security bug that allows client to spoof ip
|
||||
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
|
||||
|
||||
|
||||
**@el3ctro- (Joshua Loper)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@ethankan (Ethan Kan)**
|
||||
- Unsigned integers in binding
|
||||
|
||||
|
||||
**(Evgeny Persienko)**
|
||||
- Validate sub structures
|
||||
|
||||
|
||||
**@frankbille (Frank Bille)**
|
||||
- Add support for HTTP Realm Auth
|
||||
|
||||
|
||||
**@fmd (Fareed Dudhia)**
|
||||
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
||||
|
||||
|
||||
**@ironiridis (Christopher Harrington)**
|
||||
- Remove old reference
|
||||
|
||||
|
||||
**@jammie-stackhouse (Jamie Stackhouse)**
|
||||
- Add more shortcuts for router methods
|
||||
|
||||
|
@ -104,6 +136,10 @@ People and companies, who have contributed, in alphabetical order.
|
|||
- ★ work around path.Join removing trailing slashes from routes
|
||||
|
||||
|
||||
**@mattn (Yasuhiro Matsumoto)**
|
||||
- Improve color logger
|
||||
|
||||
|
||||
**@mdigger (Dmitry Sedykh)**
|
||||
- Fixes Form binding when content-type is x-www-form-urlencoded
|
||||
- No repeat call c.Writer.Status() in gin.Logger
|
||||
|
@ -138,10 +174,22 @@ People and companies, who have contributed, in alphabetical order.
|
|||
- Fix Port usage in README.
|
||||
|
||||
|
||||
**@rayrod2030 (Ray Rodriguez)**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@rns**
|
||||
- Fix typo in example
|
||||
|
||||
|
||||
**@RobAWilkinson (Robert Wilkinson)**
|
||||
- Add example of forms and params
|
||||
|
||||
|
||||
**@rogierlommers (Rogier Lommers)**
|
||||
- Add updated static serve example
|
||||
|
||||
|
||||
**@se77en (Damon Zhao)**
|
||||
- Improve color logging
|
||||
|
||||
|
@ -166,6 +214,14 @@ People and companies, who have contributed, in alphabetical order.
|
|||
- Update httprouter godeps
|
||||
|
||||
|
||||
**@tebeka (Miki Tebeka)**
|
||||
- Use net/http constants instead of numeric values
|
||||
|
||||
|
||||
**@techjanitor**
|
||||
- Update context.go reserved IPs
|
||||
|
||||
|
||||
**@yosssi (Keiji Yoshida)**
|
||||
- Fix link in README
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#Changelog
|
||||
|
||||
###Gin 0.6 (Mar 7, 2015)
|
||||
###Gin 0.6 (Mar 9, 2015)
|
||||
|
||||
- [ADD] Support multipart/form-data
|
||||
- [ADD] NoMethod handler
|
||||
- [ADD] Validate sub structures
|
||||
- [ADD] Support for HTTP Realm Auth
|
||||
- [FIX] Unsigned integers in binding
|
||||
- [FIX] Improve color logger
|
||||
|
||||
|
||||
###Gin 0.5 (Feb 7, 2015)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||
"Rev": "00ce1c6a267162792c367acc43b1681a884e1872"
|
||||
"Rev": "b428fda53bb0a764fea9c76c9413512eda291dec"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
132
README.md
132
README.md
|
@ -1,5 +1,7 @@
|
|||
#Gin Web Framework [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
|
||||
|
||||
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||
|
||||
![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
|
||||
|
@ -10,21 +12,24 @@ $ cat test.go
|
|||
```go
|
||||
package main
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.String(200, "hello world")
|
||||
c.String(http.StatusOK, "hello world")
|
||||
})
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
router.POST("/submit", func(c *gin.Context) {
|
||||
c.String(401, "not authorized")
|
||||
c.String(http.StatusUnauthorized, "not authorized")
|
||||
})
|
||||
router.PUT("/error", func(c *gin.Context) {
|
||||
c.String(500, "and error hapenned :(")
|
||||
c.String(http.StatusInternalServerError, "and error happened :(")
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
@ -85,15 +90,18 @@ If you'd like to help out with the project, there's a mailing list and IRC chann
|
|||
```go
|
||||
package main
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
@ -128,7 +136,7 @@ func main() {
|
|||
r.GET("/user/:name", func(c *gin.Context) {
|
||||
name := c.Params.ByName("name")
|
||||
message := "Hello "+name
|
||||
c.String(200, message)
|
||||
c.String(http.StatusOK, message)
|
||||
})
|
||||
|
||||
// However, this one will match /user/john/ and also /user/john/send
|
||||
|
@ -137,7 +145,7 @@ func main() {
|
|||
name := c.Params.ByName("name")
|
||||
action := c.Params.ByName("action")
|
||||
message := name + " is " + action
|
||||
c.String(200, message)
|
||||
c.String(http.StatusOK, message)
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
|
@ -155,15 +163,56 @@ func main() {
|
|||
c.Request.ParseForm()
|
||||
|
||||
firstname := c.Request.Form.Get("firstname")
|
||||
lastname := c.Request.Form.get("lastname")
|
||||
lastname := c.Request.Form.Get("lastname")
|
||||
|
||||
message := "Hello "+ firstname + lastname
|
||||
c.String(200, message)
|
||||
c.String(http.StatusOK, message)
|
||||
})
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
###Multipart Form
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
type LoginForm struct {
|
||||
User string `form:"user" binding:"required"`
|
||||
Password string `form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
r.POST("/login", func(c *gin.Context) {
|
||||
|
||||
var form LoginForm
|
||||
c.BindWith(&form, binding.MultipartForm)
|
||||
|
||||
if form.User == "user" && form.Password == "password" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
r.Run(":8080")
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Test it with:
|
||||
```bash
|
||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||
```
|
||||
|
||||
#### Grouping routes
|
||||
```go
|
||||
func main() {
|
||||
|
@ -272,9 +321,9 @@ func main() {
|
|||
|
||||
c.Bind(&json) // This will infer what binder to use depending on the content-type header.
|
||||
if json.User == "manu" && json.Password == "123" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -284,9 +333,9 @@ func main() {
|
|||
|
||||
c.BindWith(&form, binding.Form) // You can also specify which binder to use. We support binding.Form, binding.JSON and binding.XML.
|
||||
if form.User == "manu" && form.Password == "123" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -303,7 +352,7 @@ func main() {
|
|||
|
||||
// gin.H is a shortcut for map[string]interface{}
|
||||
r.GET("/someJSON", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "hey", "status": 200})
|
||||
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||
})
|
||||
|
||||
r.GET("/moreJSON", func(c *gin.Context) {
|
||||
|
@ -318,11 +367,11 @@ func main() {
|
|||
msg.Number = 123
|
||||
// Note that msg.Name becomes "user" in the JSON
|
||||
// Will output : {"user": "Lena", "Message": "hey", "Number": 123}
|
||||
c.JSON(200, msg)
|
||||
c.JSON(http.StatusOK, msg)
|
||||
})
|
||||
|
||||
r.GET("/someXML", func(c *gin.Context) {
|
||||
c.XML(200, gin.H{"message": "hey", "status": 200})
|
||||
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
|
@ -331,7 +380,6 @@ func main() {
|
|||
```
|
||||
|
||||
####Serving static files
|
||||
|
||||
Use Engine.ServeFiles(path string, root http.FileSystem):
|
||||
|
||||
```go
|
||||
|
@ -344,6 +392,13 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
Use the following example to serve static files at top level route of your domain. Files are being served from directory ./html.
|
||||
|
||||
```
|
||||
r := gin.Default()
|
||||
r.Use(static.Serve("/", static.LocalFile("html", false)))
|
||||
```
|
||||
|
||||
Note: this will use `httpNotFound` instead of the Router's `NotFound` handler.
|
||||
|
||||
####HTML rendering
|
||||
|
@ -356,7 +411,7 @@ func main() {
|
|||
r.LoadHTMLGlob("templates/*")
|
||||
r.GET("/index", func(c *gin.Context) {
|
||||
obj := gin.H{"title": "Main website"}
|
||||
c.HTML(200, "index.tmpl", obj)
|
||||
c.HTML(http.StatusOK, "index.tmpl", obj)
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
|
@ -384,13 +439,40 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
#####Using layout files with templates
|
||||
```go
|
||||
var baseTemplate = "main.tmpl"
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
r.SetHTMLTemplate(template.Must(template.ParseFiles(baseTemplate, "whatever.tmpl")))
|
||||
c.HTML(200, "base", data)
|
||||
})
|
||||
```
|
||||
main.tmpl
|
||||
```html
|
||||
{{define "base"}}
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
{{template "content" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
```
|
||||
whatever.tmpl
|
||||
```html
|
||||
{{define "content"}}
|
||||
<h1>Hello World!</h1>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
#### Redirects
|
||||
|
||||
Issuing a HTTP redirect is easy:
|
||||
|
||||
```go
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.Redirect(301, "http://www.google.com/")
|
||||
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
|
||||
})
|
||||
```
|
||||
Both internal and external locations are supported.
|
||||
|
@ -438,7 +520,7 @@ func main() {
|
|||
|
||||
#### Using BasicAuth() middleware
|
||||
```go
|
||||
// similate some private data
|
||||
// simulate some private data
|
||||
var secrets = gin.H{
|
||||
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
|
||||
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
|
||||
|
@ -463,9 +545,9 @@ func main() {
|
|||
// get user, it was setted by the BasicAuth middleware
|
||||
user := c.MustGet(gin.AuthUserKey).(string)
|
||||
if secret, ok := secrets[user]; ok {
|
||||
c.JSON(200, gin.H{"user": user, "secret": secret})
|
||||
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
|
||||
} else {
|
||||
c.JSON(200, gin.H{"user": user, "secret": "NO SECRET :("})
|
||||
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
19
auth.go
19
auth.go
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
@ -28,9 +29,10 @@ func (a authPairs) Len() int { return len(a) }
|
|||
func (a authPairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a authPairs) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
|
||||
// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where
|
||||
// the key is the user name and the value is the password.
|
||||
func BasicAuth(accounts Accounts) HandlerFunc {
|
||||
// Implements a basic Basic HTTP Authorization. It takes as arguments a map[string]string where
|
||||
// the key is the user name and the value is the password, as well as the name of the Realm
|
||||
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
|
||||
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
||||
pairs, err := processAccounts(accounts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -40,7 +42,10 @@ func BasicAuth(accounts Accounts) HandlerFunc {
|
|||
user, ok := searchCredential(pairs, c.Request.Header.Get("Authorization"))
|
||||
if !ok {
|
||||
// Credentials doesn't match, we return 401 Unauthorized and abort request.
|
||||
c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
||||
if realm == "" {
|
||||
realm = "Authorization Required"
|
||||
}
|
||||
c.Writer.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", realm))
|
||||
c.Fail(401, errors.New("Unauthorized"))
|
||||
} else {
|
||||
// user is allowed, set UserId to key "user" in this context, the userId can be read later using
|
||||
|
@ -50,6 +55,12 @@ func BasicAuth(accounts Accounts) HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where
|
||||
// the key is the user name and the value is the password.
|
||||
func BasicAuth(accounts Accounts) HandlerFunc {
|
||||
return BasicAuthForRealm(accounts, "")
|
||||
}
|
||||
|
||||
func processAccounts(accounts Accounts) (authPairs, error) {
|
||||
if len(accounts) == 0 {
|
||||
return nil, errors.New("Empty list of authorized credentials")
|
||||
|
|
24
auth_test.go
24
auth_test.go
|
@ -59,3 +59,27 @@ func TestBasicAuth401(t *testing.T) {
|
|||
t.Errorf("WWW-Authenticate header is incorrect: %s", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/login", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r := New()
|
||||
accounts := Accounts{"foo": "bar"}
|
||||
r.Use(BasicAuthForRealm(accounts, "My Custom Realm"))
|
||||
|
||||
r.GET("/login", func(c *Context) {
|
||||
c.String(200, "autorized")
|
||||
})
|
||||
|
||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 401 {
|
||||
t.Errorf("Response code should be Not autorized, was: %s", w.Code)
|
||||
}
|
||||
|
||||
if w.HeaderMap.Get("WWW-Authenticate") != "Basic realm=\"My Custom Realm\"" {
|
||||
t.Errorf("WWW-Authenticate header is incorrect: %s", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,20 @@ type (
|
|||
// XML binding
|
||||
xmlBinding struct{}
|
||||
|
||||
// // form binding
|
||||
// form binding
|
||||
formBinding struct{}
|
||||
|
||||
// multipart form binding
|
||||
multipartFormBinding struct{}
|
||||
)
|
||||
|
||||
const MAX_MEMORY = 1 * 1024 * 1024
|
||||
|
||||
var (
|
||||
JSON = jsonBinding{}
|
||||
XML = xmlBinding{}
|
||||
Form = formBinding{} // todo
|
||||
JSON = jsonBinding{}
|
||||
XML = xmlBinding{}
|
||||
Form = formBinding{} // todo
|
||||
MultipartForm = multipartFormBinding{}
|
||||
)
|
||||
|
||||
func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
|
@ -63,6 +69,16 @@ func (_ formBinding) Bind(req *http.Request, obj interface{}) error {
|
|||
return Validate(obj)
|
||||
}
|
||||
|
||||
func (_ multipartFormBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
if err := req.ParseMultipartForm(MAX_MEMORY); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mapForm(obj, req.Form); err != nil {
|
||||
return err
|
||||
}
|
||||
return Validate(obj)
|
||||
}
|
||||
|
||||
func mapForm(ptr interface{}, form map[string][]string) error {
|
||||
typ := reflect.TypeOf(ptr).Elem()
|
||||
formStruct := reflect.ValueOf(ptr).Elem()
|
||||
|
@ -98,18 +114,54 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setIntField(val string, bitSize int, structField reflect.Value) error {
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
|
||||
intVal, err := strconv.ParseInt(val, 10, bitSize)
|
||||
if err == nil {
|
||||
structField.SetInt(intVal)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func setUintField(val string, bitSize int, structField reflect.Value) error {
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
|
||||
uintVal, err := strconv.ParseUint(val, 10, bitSize)
|
||||
if err == nil {
|
||||
structField.SetUint(uintVal)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
||||
switch valueKind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
intVal, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
structField.SetInt(int64(intVal))
|
||||
}
|
||||
case reflect.Int:
|
||||
return setIntField(val, 0, structField)
|
||||
case reflect.Int8:
|
||||
return setIntField(val, 8, structField)
|
||||
case reflect.Int16:
|
||||
return setIntField(val, 16, structField)
|
||||
case reflect.Int32:
|
||||
return setIntField(val, 32, structField)
|
||||
case reflect.Int64:
|
||||
return setIntField(val, 64, structField)
|
||||
case reflect.Uint:
|
||||
return setUintField(val, 0, structField)
|
||||
case reflect.Uint8:
|
||||
return setUintField(val, 8, structField)
|
||||
case reflect.Uint16:
|
||||
return setUintField(val, 16, structField)
|
||||
case reflect.Uint32:
|
||||
return setUintField(val, 32, structField)
|
||||
case reflect.Uint64:
|
||||
return setUintField(val, 64, structField)
|
||||
case reflect.Bool:
|
||||
if val == "" {
|
||||
val = "false"
|
||||
|
@ -199,6 +251,22 @@ func Validate(obj interface{}, parents ...string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fieldType := field.Type.Kind()
|
||||
if fieldType == reflect.Struct {
|
||||
if reflect.DeepEqual(zero, fieldValue) {
|
||||
continue
|
||||
}
|
||||
err := Validate(fieldValue, field.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct {
|
||||
err := Validate(fieldValue, field.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
|
|
|
@ -230,7 +230,7 @@ func ipInMasks(ip net.IP, masks []interface{}) bool {
|
|||
func ForwardedFor(proxies ...interface{}) HandlerFunc {
|
||||
if len(proxies) == 0 {
|
||||
// default to local ips
|
||||
var reservedLocalIps = []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
|
||||
var reservedLocalIps = []string{"10.0.0.0/8", "127.0.0.1/32", "172.16.0.0/12", "192.168.0.0/16"}
|
||||
|
||||
proxies = make([]interface{}, len(reservedLocalIps))
|
||||
|
||||
|
@ -295,6 +295,8 @@ func (c *Context) Bind(obj interface{}) bool {
|
|||
switch {
|
||||
case c.Request.Method == "GET" || ctype == MIMEPOSTForm:
|
||||
b = binding.Form
|
||||
case ctype == MIMEMultipartPOSTForm:
|
||||
b = binding.MultipartForm
|
||||
case ctype == MIMEJSON:
|
||||
b = binding.JSON
|
||||
case ctype == MIMEXML || ctype == MIMEXML2:
|
||||
|
@ -349,6 +351,11 @@ func (c *Context) String(code int, format string, values ...interface{}) {
|
|||
c.Render(code, render.Plain, format, values)
|
||||
}
|
||||
|
||||
// Writes the given string into the response body and sets the Content-Type to "text/html" without template.
|
||||
func (c *Context) HTMLString(code int, format string, values ...interface{}) {
|
||||
c.Render(code, render.HTMLPlain, format, values)
|
||||
}
|
||||
|
||||
// Returns a HTTP redirect to the specific location.
|
||||
func (c *Context) Redirect(code int, location string) {
|
||||
if code >= 300 && code <= 308 {
|
||||
|
|
|
@ -441,6 +441,42 @@ func TestBindingJSONMalformed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBindingForm(t *testing.T) {
|
||||
|
||||
body := bytes.NewBuffer([]byte("foo=bar&num=123&unum=1234567890"))
|
||||
|
||||
r := New()
|
||||
r.POST("/binding/form", func(c *Context) {
|
||||
var body struct {
|
||||
Foo string `form:"foo"`
|
||||
Num int `form:"num"`
|
||||
Unum uint `form:"unum"`
|
||||
}
|
||||
if c.Bind(&body) {
|
||||
c.JSON(200, H{"foo": body.Foo, "num": body.Num, "unum": body.Unum})
|
||||
}
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("POST", "/binding/form", body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Errorf("Response code should be Ok, was: %d", w.Code)
|
||||
}
|
||||
|
||||
expected := "{\"foo\":\"bar\",\"num\":123,\"unum\":1234567890}\n"
|
||||
if w.Body.String() != expected {
|
||||
t.Errorf("Response should be %s, was %s", expected, w.Body.String())
|
||||
}
|
||||
|
||||
if w.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" {
|
||||
t.Errorf("Content-Type should be application/json, was %s", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientIP(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
|
|
60
gin.go
60
gin.go
|
@ -14,13 +14,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
AbortIndex = math.MaxInt8 / 2
|
||||
MIMEJSON = "application/json"
|
||||
MIMEHTML = "text/html"
|
||||
MIMEXML = "application/xml"
|
||||
MIMEXML2 = "text/xml"
|
||||
MIMEPlain = "text/plain"
|
||||
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||
AbortIndex = math.MaxInt8 / 2
|
||||
MIMEJSON = "application/json"
|
||||
MIMEHTML = "text/html"
|
||||
MIMEXML = "application/xml"
|
||||
MIMEXML2 = "text/xml"
|
||||
MIMEPlain = "text/plain"
|
||||
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -29,12 +30,14 @@ type (
|
|||
// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
|
||||
Engine struct {
|
||||
*RouterGroup
|
||||
HTMLRender render.Render
|
||||
Default404Body []byte
|
||||
pool sync.Pool
|
||||
allNoRoute []HandlerFunc
|
||||
noRoute []HandlerFunc
|
||||
router *httprouter.Router
|
||||
HTMLRender render.Render
|
||||
Default404Body []byte
|
||||
Default405Body []byte
|
||||
pool sync.Pool
|
||||
allNoRouteNoMethod []HandlerFunc
|
||||
noRoute []HandlerFunc
|
||||
noMethod []HandlerFunc
|
||||
router *httprouter.Router
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -49,7 +52,9 @@ func New() *Engine {
|
|||
}
|
||||
engine.router = httprouter.New()
|
||||
engine.Default404Body = []byte("404 page not found")
|
||||
engine.Default405Body = []byte("405 method not allowed")
|
||||
engine.router.NotFound = engine.handle404
|
||||
engine.router.MethodNotAllowed = engine.handle405
|
||||
engine.pool.New = func() interface{} {
|
||||
c := &Context{Engine: engine}
|
||||
c.Writer = &c.writermem
|
||||
|
@ -97,17 +102,27 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
|||
engine.rebuild404Handlers()
|
||||
}
|
||||
|
||||
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||
engine.noMethod = handlers
|
||||
engine.rebuild405Handlers()
|
||||
}
|
||||
|
||||
func (engine *Engine) Use(middlewares ...HandlerFunc) {
|
||||
engine.RouterGroup.Use(middlewares...)
|
||||
engine.rebuild404Handlers()
|
||||
engine.rebuild405Handlers()
|
||||
}
|
||||
|
||||
func (engine *Engine) rebuild404Handlers() {
|
||||
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||
engine.allNoRouteNoMethod = engine.combineHandlers(engine.noRoute)
|
||||
}
|
||||
|
||||
func (engine *Engine) rebuild405Handlers() {
|
||||
engine.allNoRouteNoMethod = engine.combineHandlers(engine.noMethod)
|
||||
}
|
||||
|
||||
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
|
||||
c := engine.createContext(w, req, nil, engine.allNoRoute)
|
||||
c := engine.createContext(w, req, nil, engine.allNoRouteNoMethod)
|
||||
// set 404 by default, useful for logging
|
||||
c.Writer.WriteHeader(404)
|
||||
c.Next()
|
||||
|
@ -121,6 +136,21 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
|
|||
engine.reuseContext(c)
|
||||
}
|
||||
|
||||
func (engine *Engine) handle405(w http.ResponseWriter, req *http.Request) {
|
||||
c := engine.createContext(w, req, nil, engine.allNoRouteNoMethod)
|
||||
// set 405 by default, useful for logging
|
||||
c.Writer.WriteHeader(405)
|
||||
c.Next()
|
||||
if !c.Writer.Written() {
|
||||
if c.Writer.Status() == 405 {
|
||||
c.Data(-1, MIMEPlain, engine.Default405Body)
|
||||
} else {
|
||||
c.Writer.WriteHeaderNow()
|
||||
}
|
||||
}
|
||||
engine.reuseContext(c)
|
||||
}
|
||||
|
||||
// ServeHTTP makes the router implement the http.Handler interface.
|
||||
func (engine *Engine) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
engine.router.ServeHTTP(writer, request)
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
package gin
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-colorable"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -38,7 +38,7 @@ func ErrorLoggerT(typ uint32) HandlerFunc {
|
|||
}
|
||||
|
||||
func Logger() HandlerFunc {
|
||||
stdlogger := log.New(os.Stdout, "", 0)
|
||||
stdlogger := log.New(colorable.NewColorableStdout(), "", 0)
|
||||
//errlogger := log.New(os.Stderr, "", 0)
|
||||
|
||||
return func(c *Context) {
|
||||
|
|
|
@ -82,7 +82,7 @@ func function(pc uintptr) []byte {
|
|||
}
|
||||
|
||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||
// While Martini is in development mode, Recovery will also output the panic as HTML.
|
||||
// While Gin is in development mode, Recovery will also output the panic as HTML.
|
||||
func Recovery() HandlerFunc {
|
||||
return func(c *Context) {
|
||||
defer func() {
|
||||
|
|
|
@ -26,6 +26,9 @@ type (
|
|||
// Plain text
|
||||
plainRender struct{}
|
||||
|
||||
// HTML Plain text
|
||||
htmlPlainRender struct{}
|
||||
|
||||
// Redirects
|
||||
redirectRender struct{}
|
||||
|
||||
|
@ -45,6 +48,7 @@ var (
|
|||
JSON = jsonRender{}
|
||||
XML = xmlRender{}
|
||||
Plain = plainRender{}
|
||||
HTMLPlain = htmlPlainRender{}
|
||||
Redirect = redirectRender{}
|
||||
HTMLDebug = &htmlDebugRender{}
|
||||
)
|
||||
|
@ -85,6 +89,19 @@ func (_ plainRender) Render(w http.ResponseWriter, code int, data ...interface{}
|
|||
return err
|
||||
}
|
||||
|
||||
func (_ htmlPlainRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
|
||||
writeHeader(w, code, "text/html")
|
||||
format := data[0].(string)
|
||||
args := data[1].([]interface{})
|
||||
var err error
|
||||
if len(args) > 0 {
|
||||
_, err = w.Write([]byte(fmt.Sprintf(format, args...)))
|
||||
} else {
|
||||
_, err = w.Write([]byte(format))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *htmlDebugRender) AddGlob(pattern string) {
|
||||
r.globs = append(r.globs, pattern)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue