Merge branch 'develop' | Update AUTHORS and CHANGELOG
This commit is contained in:
commit
5cfe2c56dd
@ -1,5 +1,6 @@
|
|||||||
language: go
|
language: go
|
||||||
|
sudo: false
|
||||||
go:
|
go:
|
||||||
- 1.3
|
- 1.3
|
||||||
|
- 1.4
|
||||||
- tip
|
- tip
|
||||||
|
58
AUTHORS.md
58
AUTHORS.md
@ -1,12 +1,11 @@
|
|||||||
List of all the awesome people working to make Gin the best Web Framework in Go!
|
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
|
||||||
|
|
||||||
**Lead Developer:** Manu Martinez-Almeida (@manucorporat)
|
**Original Developer:** Manu Martinez-Almeida (@manucorporat)
|
||||||
**Staff:**
|
**Long-term Maintainer:** Javier Provecho (@javierprovecho)
|
||||||
Javier Provecho (@javierprovecho)
|
|
||||||
|
|
||||||
People and companies, who have contributed, in alphabetical order.
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
@ -31,6 +30,14 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Added travis CI integration
|
- Added travis CI integration
|
||||||
|
|
||||||
|
|
||||||
|
**@andredublin (Andre Dublin)**
|
||||||
|
- Fix typo in comment
|
||||||
|
|
||||||
|
|
||||||
|
**@bredov (Ludwig Valda Vasquez)**
|
||||||
|
- Fix html templating in debug mode
|
||||||
|
|
||||||
|
|
||||||
**@bluele (Jun Kimura)**
|
**@bluele (Jun Kimura)**
|
||||||
- Fixes code examples in README
|
- Fixes code examples in README
|
||||||
|
|
||||||
@ -41,20 +48,38 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
|
|
||||||
**@dickeyxxx (Jeff Dickey)**
|
**@dickeyxxx (Jeff Dickey)**
|
||||||
- Typos in README
|
- Typos in README
|
||||||
|
- Add example about serving static files
|
||||||
|
|
||||||
|
|
||||||
|
**@dutchcoders (DutchCoders)**
|
||||||
|
- ★ Fix security bug that allows client to spoof ip
|
||||||
|
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
|
||||||
|
|
||||||
|
|
||||||
**@fmd (Fareed Dudhia)**
|
**@fmd (Fareed Dudhia)**
|
||||||
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
||||||
|
|
||||||
|
|
||||||
|
**@jammie-stackhouse (Jamie Stackhouse)
|
||||||
|
- Add more shortcuts for router methods
|
||||||
|
|
||||||
|
|
||||||
**@jasonrhansen**
|
**@jasonrhansen**
|
||||||
- Fix spelling and grammar errors in documentation
|
- Fix spelling and grammar errors in documentation
|
||||||
|
|
||||||
|
|
||||||
|
**@JasonSoft (Jason Lee)**
|
||||||
|
- Fix typo in comment
|
||||||
|
|
||||||
|
|
||||||
**@julienschmidt (Julien Schmidt)**
|
**@julienschmidt (Julien Schmidt)**
|
||||||
- gofmt the code examples
|
- gofmt the code examples
|
||||||
|
|
||||||
|
|
||||||
|
**@kelcecil (Kel Cecil)**
|
||||||
|
- Fix readme typo
|
||||||
|
|
||||||
|
|
||||||
**@kyledinh (Kyle Dinh)**
|
**@kyledinh (Kyle Dinh)**
|
||||||
- Adds RunTLS()
|
- Adds RunTLS()
|
||||||
|
|
||||||
@ -63,6 +88,10 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Small fixes in README
|
- Small fixes in README
|
||||||
|
|
||||||
|
|
||||||
|
**@loongmxbt (Saint Asky)**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
**@lucas-clemente (Lucas Clemente)**
|
**@lucas-clemente (Lucas Clemente)**
|
||||||
- ★ work around path.Join removing trailing slashes from routes
|
- ★ work around path.Join removing trailing slashes from routes
|
||||||
|
|
||||||
@ -73,10 +102,15 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Fixes Content-Type for json render
|
- Fixes Content-Type for json render
|
||||||
|
|
||||||
|
|
||||||
|
**@mirzac (Mirza Ceric)**
|
||||||
|
- Fix debug printing
|
||||||
|
|
||||||
|
|
||||||
**@mopemope (Yutaka Matsubara)**
|
**@mopemope (Yutaka Matsubara)**
|
||||||
- ★ Adds Godep support (Dependencies Manager)
|
- ★ Adds Godep support (Dependencies Manager)
|
||||||
- Fix variadic parameter in the flexible render API
|
- Fix variadic parameter in the flexible render API
|
||||||
- Fix Corrupted plain render
|
- Fix Corrupted plain render
|
||||||
|
- Add Pluggable View Renderer Example
|
||||||
|
|
||||||
|
|
||||||
**@msemenistyi (Mykyta Semenistyi)**
|
**@msemenistyi (Mykyta Semenistyi)**
|
||||||
@ -96,6 +130,10 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Fix Port usage in README.
|
- Fix Port usage in README.
|
||||||
|
|
||||||
|
|
||||||
|
**@se77en (Damon Zhao)**
|
||||||
|
- Improve color logging
|
||||||
|
|
||||||
|
|
||||||
**@silasb (Silas Baronda)**
|
**@silasb (Silas Baronda)**
|
||||||
- Fixing quotes in README
|
- Fixing quotes in README
|
||||||
|
|
||||||
@ -104,5 +142,17 @@ People and companies, who have contributed, in alphabetical order.
|
|||||||
- Fixes some texts in README II
|
- Fixes some texts in README II
|
||||||
|
|
||||||
|
|
||||||
|
**@slimmy (Jimmy Pettersson)
|
||||||
|
- Added messages for required bindings
|
||||||
|
|
||||||
|
|
||||||
|
**@smira (Andrey Smirnov)**
|
||||||
|
- Add support for ignored/unexported fields in binding
|
||||||
|
|
||||||
|
|
||||||
|
**@yosssi (Keiji Yoshida)**
|
||||||
|
- Fix link in README
|
||||||
|
|
||||||
|
|
||||||
**@yuyabee**
|
**@yuyabee**
|
||||||
- Fixed README
|
- Fixed README
|
@ -1,5 +1,12 @@
|
|||||||
#Changelog
|
#Changelog
|
||||||
|
|
||||||
|
###Gin 0.5 (Jan 4, 2015)
|
||||||
|
|
||||||
|
- [NEW] Content Negotiation
|
||||||
|
- [FIX] Solved security bug that allow a client to spoof ip
|
||||||
|
- [FIX] Fix unexported/ignored fields in binding
|
||||||
|
|
||||||
|
|
||||||
###Gin 0.4 (Aug 21, 2014)
|
###Gin 0.4 (Aug 21, 2014)
|
||||||
|
|
||||||
- [NEW] Development mode
|
- [NEW] Development mode
|
||||||
@ -34,7 +41,7 @@
|
|||||||
- [NEW] New API for serving static files. gin.Static()
|
- [NEW] New API for serving static files. gin.Static()
|
||||||
- [NEW] gin.H() can be serialized into XML
|
- [NEW] gin.H() can be serialized into XML
|
||||||
- [NEW] Typed errors. Errors can be typed. Internet/external/custom.
|
- [NEW] Typed errors. Errors can be typed. Internet/external/custom.
|
||||||
- [NEW] Support for Godebs
|
- [NEW] Support for Godeps
|
||||||
- [NEW] Travis/Godocs badges in README
|
- [NEW] Travis/Godocs badges in README
|
||||||
- [NEW] New Bind() and BindWith() methods for parsing request body.
|
- [NEW] New Bind() and BindWith() methods for parsing request body.
|
||||||
- [NEW] Add Content.Copy()
|
- [NEW] Add Content.Copy()
|
||||||
|
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@ -4,7 +4,7 @@
|
|||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||||
"Rev": "7deadb6844d2c6ff1dfb812eaa439b87cdaedf20"
|
"Rev": "aeec11926f7a8fab580383810e1b1bbba99bdaa7"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
71
README.md
71
README.md
@ -3,8 +3,35 @@
|
|||||||
[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
|
[![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)
|
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
|
||||||
|
|
||||||
Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster. If you need performance and good productivity, you will love Gin.
|
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](http://gin-gonic.github.io/gin/other/console.png)
|
|
||||||
|
![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cat test.go
|
||||||
|
```
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/", func(c *gin.Context) {
|
||||||
|
c.String(200, "hello world")
|
||||||
|
})
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
router.POST("/submit", func(c *gin.Context) {
|
||||||
|
c.String(401, "not authorized")
|
||||||
|
})
|
||||||
|
router.PUT("/error", func(c *gin.Context) {
|
||||||
|
c.String(500, "and error hapenned :(")
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
##Gin is new, will it be supported?
|
##Gin is new, will it be supported?
|
||||||
|
|
||||||
@ -24,19 +51,19 @@ Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcomin
|
|||||||
- [x] Flexible rendering system
|
- [x] Flexible rendering system
|
||||||
- [ ] More powerful validation API
|
- [ ] More powerful validation API
|
||||||
- [ ] Improve documentation
|
- [ ] Improve documentation
|
||||||
- [ ] Add more cool middlewares, for example redis caching (this also helps developers to understand the framework).
|
- [X] Add more cool middlewares, for example redis caching (this also helps developers to understand the framework).
|
||||||
- [x] Continuous integration
|
- [x] Continuous integration
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Start using it
|
## Start using it
|
||||||
Obviously, you need to have Git and Go! already installed to run Gin.
|
Obviously, you need to have Git and Go already installed to run Gin.
|
||||||
Run this in your terminal
|
Run this in your terminal
|
||||||
|
|
||||||
```
|
```
|
||||||
go get github.com/gin-gonic/gin
|
go get github.com/gin-gonic/gin
|
||||||
```
|
```
|
||||||
Then import it in your Go! code:
|
Then import it in your Go code:
|
||||||
|
|
||||||
```
|
```
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
@ -223,7 +250,7 @@ func main() {
|
|||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||||
r.POST("/login", func(c *gin.Context) {
|
r.POST("/loginJSON", func(c *gin.Context) {
|
||||||
var json LoginJSON
|
var json LoginJSON
|
||||||
|
|
||||||
c.Bind(&json) // This will infer what binder to use depending on the content-type header.
|
c.Bind(&json) // This will infer what binder to use depending on the content-type header.
|
||||||
@ -234,8 +261,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Example for binding a HTLM form (user=manu&password=123)
|
// Example for binding a HTML form (user=manu&password=123)
|
||||||
r.POST("/login", func(c *gin.Context) {
|
r.POST("/loginHTML", func(c *gin.Context) {
|
||||||
var form LoginForm
|
var form LoginForm
|
||||||
|
|
||||||
c.BindWith(&form, binding.Form) // You can also specify which binder to use. We support binding.Form, binding.JSON and binding.XML.
|
c.BindWith(&form, binding.Form) // You can also specify which binder to use. We support binding.Form, binding.JSON and binding.XML.
|
||||||
@ -257,7 +284,7 @@ func main() {
|
|||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
// gin.H is a shortcup for map[string]interface{}
|
// gin.H is a shortcut for map[string]interface{}
|
||||||
r.GET("/someJSON", func(c *gin.Context) {
|
r.GET("/someJSON", func(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"message": "hey", "status": 200})
|
c.JSON(200, gin.H{"message": "hey", "status": 200})
|
||||||
})
|
})
|
||||||
@ -286,6 +313,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
####Serving static files
|
||||||
|
|
||||||
|
Use Engine.ServeFiles(path string, root http.FileSystem):
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Static("/assets", "./assets")
|
||||||
|
|
||||||
|
// Listen and server on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: this will use `httpNotFound` instead of the Router's `NotFound` handler.
|
||||||
|
|
||||||
####Serving static files
|
####Serving static files
|
||||||
|
|
||||||
@ -310,7 +352,7 @@ Using LoadHTMLTemplates()
|
|||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.LoadHTMLTemplates("templates/*")
|
r.LoadHTMLGlob("templates/*")
|
||||||
r.GET("/index", func(c *gin.Context) {
|
r.GET("/index", func(c *gin.Context) {
|
||||||
obj := gin.H{"title": "Main website"}
|
obj := gin.H{"title": "Main website"}
|
||||||
c.HTML(200, "index.tmpl", obj)
|
c.HTML(200, "index.tmpl", obj)
|
||||||
@ -320,6 +362,11 @@ func main() {
|
|||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
```html
|
||||||
|
<h1>
|
||||||
|
{{ .title }}
|
||||||
|
</h1>
|
||||||
|
```
|
||||||
|
|
||||||
You can also use your own html template render
|
You can also use your own html template render
|
||||||
|
|
||||||
@ -329,7 +376,7 @@ import "html/template"
|
|||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
html := template.Must(template.ParseFiles("file1", "file2"))
|
html := template.Must(template.ParseFiles("file1", "file2"))
|
||||||
r.HTMLTemplates = html
|
r.SetHTMLTemplate(html)
|
||||||
|
|
||||||
// Listen and server on 0.0.0.0:8080
|
// Listen and server on 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
@ -413,7 +460,7 @@ func main() {
|
|||||||
// hit "localhost:8080/admin/secrets
|
// hit "localhost:8080/admin/secrets
|
||||||
authorized.GET("/secrets", func(c *gin.Context) {
|
authorized.GET("/secrets", func(c *gin.Context) {
|
||||||
// get user, it was setted by the BasicAuth middleware
|
// get user, it was setted by the BasicAuth middleware
|
||||||
user := c.Get(gin.AuthUserKey).(string)
|
user := c.MustGet(gin.AuthUserKey).(string)
|
||||||
if secret, ok := secrets[user]; ok {
|
if secret, ok := secrets[user]; ok {
|
||||||
c.JSON(200, gin.H{"user": user, "secret": secret})
|
c.JSON(200, gin.H{"user": user, "secret": secret})
|
||||||
} else {
|
} else {
|
||||||
|
104
auth.go
104
auth.go
@ -16,70 +16,29 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
BasicAuthPair struct {
|
Accounts map[string]string
|
||||||
Code string
|
authPair struct {
|
||||||
|
Value string
|
||||||
User string
|
User string
|
||||||
}
|
}
|
||||||
Accounts map[string]string
|
authPairs []authPair
|
||||||
Pairs []BasicAuthPair
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a Pairs) Len() int { return len(a) }
|
func (a authPairs) Len() int { return len(a) }
|
||||||
func (a Pairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a authPairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a Pairs) Less(i, j int) bool { return a[i].Code < a[j].Code }
|
func (a authPairs) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||||
|
|
||||||
func processCredentials(accounts Accounts) (Pairs, error) {
|
|
||||||
if len(accounts) == 0 {
|
|
||||||
return nil, errors.New("Empty list of authorized credentials.")
|
|
||||||
}
|
|
||||||
pairs := make(Pairs, 0, len(accounts))
|
|
||||||
for user, password := range accounts {
|
|
||||||
if len(user) == 0 || len(password) == 0 {
|
|
||||||
return nil, errors.New("User or password is empty")
|
|
||||||
}
|
|
||||||
base := user + ":" + password
|
|
||||||
code := "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
|
|
||||||
pairs = append(pairs, BasicAuthPair{code, user})
|
|
||||||
}
|
|
||||||
// We have to sort the credentials in order to use bsearch later.
|
|
||||||
sort.Sort(pairs)
|
|
||||||
return pairs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func secureCompare(given, actual string) bool {
|
|
||||||
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
|
|
||||||
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
|
|
||||||
} else {
|
|
||||||
/* Securely compare actual to itself to keep constant time, but always return false */
|
|
||||||
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchCredential(pairs Pairs, auth string) string {
|
|
||||||
if len(auth) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Search user in the slice of allowed credentials
|
|
||||||
r := sort.Search(len(pairs), func(i int) bool { return pairs[i].Code >= auth })
|
|
||||||
if r < len(pairs) && secureCompare(pairs[r].Code, auth) {
|
|
||||||
return pairs[r].User
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where
|
// 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.
|
// the key is the user name and the value is the password.
|
||||||
func BasicAuth(accounts Accounts) HandlerFunc {
|
func BasicAuth(accounts Accounts) HandlerFunc {
|
||||||
|
pairs, err := processAccounts(accounts)
|
||||||
pairs, err := processCredentials(accounts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
// Search user in the slice of allowed credentials
|
// Search user in the slice of allowed credentials
|
||||||
user := searchCredential(pairs, c.Request.Header.Get("Authorization"))
|
user, ok := searchCredential(pairs, c.Request.Header.Get("Authorization"))
|
||||||
if len(user) == 0 {
|
if !ok {
|
||||||
// Credentials doesn't match, we return 401 Unauthorized and abort request.
|
// Credentials doesn't match, we return 401 Unauthorized and abort request.
|
||||||
c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
||||||
c.Fail(401, errors.New("Unauthorized"))
|
c.Fail(401, errors.New("Unauthorized"))
|
||||||
@ -90,3 +49,46 @@ func BasicAuth(accounts Accounts) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processAccounts(accounts Accounts) (authPairs, error) {
|
||||||
|
if len(accounts) == 0 {
|
||||||
|
return nil, errors.New("Empty list of authorized credentials")
|
||||||
|
}
|
||||||
|
pairs := make(authPairs, 0, len(accounts))
|
||||||
|
for user, password := range accounts {
|
||||||
|
if len(user) == 0 {
|
||||||
|
return nil, errors.New("User can not be empty")
|
||||||
|
}
|
||||||
|
base := user + ":" + password
|
||||||
|
value := "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
|
||||||
|
pairs = append(pairs, authPair{
|
||||||
|
Value: value,
|
||||||
|
User: user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// We have to sort the credentials in order to use bsearch later.
|
||||||
|
sort.Sort(pairs)
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchCredential(pairs authPairs, auth string) (string, bool) {
|
||||||
|
if len(auth) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
// Search user in the slice of allowed credentials
|
||||||
|
r := sort.Search(len(pairs), func(i int) bool { return pairs[i].Value >= auth })
|
||||||
|
if r < len(pairs) && secureCompare(pairs[r].Value, auth) {
|
||||||
|
return pairs[r].User, true
|
||||||
|
} else {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func secureCompare(given, actual string) bool {
|
||||||
|
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
|
||||||
|
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
|
||||||
|
} else {
|
||||||
|
/* Securely compare actual to itself to keep constant time, but always return false */
|
||||||
|
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -87,7 +87,7 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formStruct.Elem().Field(i).Set(slice)
|
formStruct.Field(i).Set(slice)
|
||||||
} else {
|
} else {
|
||||||
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -169,8 +169,8 @@ func Validate(obj interface{}, parents ...string) error {
|
|||||||
for i := 0; i < typ.NumField(); i++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
field := typ.Field(i)
|
field := typ.Field(i)
|
||||||
|
|
||||||
// Allow ignored fields in the struct
|
// Allow ignored and unexported fields in the struct
|
||||||
if field.Tag.Get("form") == "-" {
|
if field.Tag.Get("form") == "-" || field.PkgPath != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
205
context.go
205
context.go
@ -12,7 +12,9 @@ import (
|
|||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -67,27 +69,29 @@ type Context struct {
|
|||||||
Engine *Engine
|
Engine *Engine
|
||||||
handlers []HandlerFunc
|
handlers []HandlerFunc
|
||||||
index int8
|
index int8
|
||||||
|
accepted []string
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/********** ROUTES GROUPING *********/
|
/********** CONTEXT CREATION ********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
||||||
func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
|
func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
|
||||||
c := engine.cache.Get().(*Context)
|
c := engine.pool.Get().(*Context)
|
||||||
c.writermem.reset(w)
|
c.writermem.reset(w)
|
||||||
c.Request = req
|
c.Request = req
|
||||||
c.Params = params
|
c.Params = params
|
||||||
c.handlers = handlers
|
c.handlers = handlers
|
||||||
c.Keys = nil
|
c.Keys = nil
|
||||||
c.index = -1
|
c.index = -1
|
||||||
|
c.accepted = nil
|
||||||
c.Errors = c.Errors[0:0]
|
c.Errors = c.Errors[0:0]
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
func (engine *Engine) reuseContext(c *Context) {
|
||||||
/****** FLOW AND ERROR MANAGEMENT****/
|
engine.pool.Put(c)
|
||||||
/************************************/
|
}
|
||||||
|
|
||||||
func (c *Context) Copy() *Context {
|
func (c *Context) Copy() *Context {
|
||||||
var cp Context = *c
|
var cp Context = *c
|
||||||
@ -96,6 +100,10 @@ func (c *Context) Copy() *Context {
|
|||||||
return &cp
|
return &cp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/*************** FLOW ***************/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
// Next should be used only in the middlewares.
|
// Next should be used only in the middlewares.
|
||||||
// It executes the pending handlers in the chain inside the calling handler.
|
// It executes the pending handlers in the chain inside the calling handler.
|
||||||
// See example in github.
|
// See example in github.
|
||||||
@ -107,25 +115,31 @@ func (c *Context) Next() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces the system to do not continue calling the pending handlers.
|
// Forces the system to do not continue calling the pending handlers in the chain.
|
||||||
// For example, the first handler checks if the request is authorized. If it's not, context.Abort(401) should be called.
|
func (c *Context) Abort() {
|
||||||
// The rest of pending handlers would never be called for that request.
|
|
||||||
func (c *Context) Abort(code int) {
|
|
||||||
if code >= 0 {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
}
|
|
||||||
c.index = AbortIndex
|
c.index = AbortIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same than AbortWithStatus() but also writes the specified response status code.
|
||||||
|
// For example, the first handler checks if the request is authorized. If it's not, context.AbortWithStatus(401) should be called.
|
||||||
|
func (c *Context) AbortWithStatus(code int) {
|
||||||
|
c.Writer.WriteHeader(code)
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/********* ERROR MANAGEMENT *********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
// Fail is the same as Abort plus an error message.
|
// Fail is the same as Abort plus an error message.
|
||||||
// Calling `context.Fail(500, err)` is equivalent to:
|
// Calling `context.Fail(500, err)` is equivalent to:
|
||||||
// ```
|
// ```
|
||||||
// context.Error("Operation aborted", err)
|
// context.Error("Operation aborted", err)
|
||||||
// context.Abort(500)
|
// context.AbortWithStatus(500)
|
||||||
// ```
|
// ```
|
||||||
func (c *Context) Fail(code int, err error) {
|
func (c *Context) Fail(code int, err error) {
|
||||||
c.Error(err, "Operation aborted")
|
c.Error(err, "Operation aborted")
|
||||||
c.Abort(code)
|
c.AbortWithStatus(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) {
|
func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) {
|
||||||
@ -144,9 +158,9 @@ func (c *Context) Error(err error, meta interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) LastError() error {
|
func (c *Context) LastError() error {
|
||||||
s := len(c.Errors)
|
nuErrors := len(c.Errors)
|
||||||
if s > 0 {
|
if nuErrors > 0 {
|
||||||
return errors.New(c.Errors[s-1].Err)
|
return errors.New(c.Errors[nuErrors-1].Err)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -168,9 +182,9 @@ func (c *Context) Set(key string, item interface{}) {
|
|||||||
// Get returns the value for the given key or an error if the key does not exist.
|
// Get returns the value for the given key or an error if the key does not exist.
|
||||||
func (c *Context) Get(key string) (interface{}, error) {
|
func (c *Context) Get(key string) (interface{}, error) {
|
||||||
if c.Keys != nil {
|
if c.Keys != nil {
|
||||||
item, ok := c.Keys[key]
|
value, ok := c.Keys[key]
|
||||||
if ok {
|
if ok {
|
||||||
return item, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errors.New("Key does not exist.")
|
return nil, errors.New("Key does not exist.")
|
||||||
@ -180,13 +194,93 @@ func (c *Context) Get(key string) (interface{}, error) {
|
|||||||
func (c *Context) MustGet(key string) interface{} {
|
func (c *Context) MustGet(key string) interface{} {
|
||||||
value, err := c.Get(key)
|
value, err := c.Get(key)
|
||||||
if err != nil || value == nil {
|
if err != nil || value == nil {
|
||||||
log.Panicf("Key %s doesn't exist", key)
|
log.Panicf("Key %s doesn't exist", value)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipInMasks(ip net.IP, masks []interface{}) bool {
|
||||||
|
for _, proxy := range masks {
|
||||||
|
var mask *net.IPNet
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch t := proxy.(type) {
|
||||||
|
case string:
|
||||||
|
if _, mask, err = net.ParseCIDR(t); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case net.IP:
|
||||||
|
mask = &net.IPNet{IP: t, Mask: net.CIDRMask(len(t)*8, len(t)*8)}
|
||||||
|
case net.IPNet:
|
||||||
|
mask = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
if mask.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// the ForwardedFor middleware unwraps the X-Forwarded-For headers, be careful to only use this
|
||||||
|
// middleware if you've got servers in front of this server. The list with (known) proxies and
|
||||||
|
// local ips are being filtered out of the forwarded for list, giving the last not local ip being
|
||||||
|
// the real client ip.
|
||||||
|
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"}
|
||||||
|
|
||||||
|
proxies = make([]interface{}, len(reservedLocalIps))
|
||||||
|
|
||||||
|
for i, v := range reservedLocalIps {
|
||||||
|
proxies[i] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *Context) {
|
||||||
|
// the X-Forwarded-For header contains an array with left most the client ip, then
|
||||||
|
// comma separated, all proxies the request passed. The last proxy appears
|
||||||
|
// as the remote address of the request. Returning the client
|
||||||
|
// ip to comply with default RemoteAddr response.
|
||||||
|
|
||||||
|
// check if remoteaddr is local ip or in list of defined proxies
|
||||||
|
remoteIp := net.ParseIP(strings.Split(c.Request.RemoteAddr, ":")[0])
|
||||||
|
|
||||||
|
if !ipInMasks(remoteIp, proxies) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if forwardedFor := c.Request.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||||
|
parts := strings.Split(forwardedFor, ",")
|
||||||
|
|
||||||
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
|
part := parts[i]
|
||||||
|
|
||||||
|
ip := net.ParseIP(strings.TrimSpace(part))
|
||||||
|
|
||||||
|
if ipInMasks(ip, proxies) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// returning remote addr conform the original remote addr format
|
||||||
|
c.Request.RemoteAddr = ip.String() + ":0"
|
||||||
|
|
||||||
|
// remove forwarded for address
|
||||||
|
c.Request.Header.Set("X-Forwarded-For", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) ClientIP() string {
|
||||||
|
return c.Request.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/******** ENCODING MANAGEMENT********/
|
/********* PARSING REQUEST **********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
||||||
// This function checks the Content-Type to select a binding engine automatically,
|
// This function checks the Content-Type to select a binding engine automatically,
|
||||||
@ -220,10 +314,14 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** RESPONSE RENDERING ********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
|
func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
|
||||||
if err := render.Render(c.Writer, code, obj...); err != nil {
|
if err := render.Render(c.Writer, code, obj...); err != nil {
|
||||||
c.ErrorTyped(err, ErrorTypeInternal, obj)
|
c.ErrorTyped(err, ErrorTypeInternal, obj)
|
||||||
c.Abort(500)
|
c.AbortWithStatus(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,9 +363,7 @@ func (c *Context) Data(code int, contentType string, data []byte) {
|
|||||||
if len(contentType) > 0 {
|
if len(contentType) > 0 {
|
||||||
c.Writer.Header().Set("Content-Type", contentType)
|
c.Writer.Header().Set("Content-Type", contentType)
|
||||||
}
|
}
|
||||||
if code >= 0 {
|
|
||||||
c.Writer.WriteHeader(code)
|
c.Writer.WriteHeader(code)
|
||||||
}
|
|
||||||
c.Writer.Write(data)
|
c.Writer.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,3 +371,64 @@ func (c *Context) Data(code int, contentType string, data []byte) {
|
|||||||
func (c *Context) File(filepath string) {
|
func (c *Context) File(filepath string) {
|
||||||
http.ServeFile(c.Writer, c.Request, filepath)
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** CONTENT NEGOTIATION *******/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
type Negotiate struct {
|
||||||
|
Offered []string
|
||||||
|
HTMLPath string
|
||||||
|
HTMLData interface{}
|
||||||
|
JSONData interface{}
|
||||||
|
XMLData interface{}
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Negotiate(code int, config Negotiate) {
|
||||||
|
switch c.NegotiateFormat(config.Offered...) {
|
||||||
|
case MIMEJSON:
|
||||||
|
data := chooseData(config.JSONData, config.Data)
|
||||||
|
c.JSON(code, data)
|
||||||
|
|
||||||
|
case MIMEHTML:
|
||||||
|
data := chooseData(config.HTMLData, config.Data)
|
||||||
|
if len(config.HTMLPath) == 0 {
|
||||||
|
panic("negotiate config is wrong. html path is needed")
|
||||||
|
}
|
||||||
|
c.HTML(code, config.HTMLPath, data)
|
||||||
|
|
||||||
|
case MIMEXML:
|
||||||
|
data := chooseData(config.XMLData, config.Data)
|
||||||
|
c.XML(code, data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
c.Fail(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) NegotiateFormat(offered ...string) string {
|
||||||
|
if len(offered) == 0 {
|
||||||
|
panic("you must provide at least one offer")
|
||||||
|
}
|
||||||
|
if c.accepted == nil {
|
||||||
|
c.accepted = parseAccept(c.Request.Header.Get("Accept"))
|
||||||
|
}
|
||||||
|
if len(c.accepted) == 0 {
|
||||||
|
return offered[0]
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for _, accepted := range c.accepted {
|
||||||
|
for _, offert := range offered {
|
||||||
|
if accepted == offert {
|
||||||
|
return offert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetAccepted(formats ...string) {
|
||||||
|
c.accepted = formats
|
||||||
|
}
|
||||||
|
@ -232,13 +232,13 @@ func TestBadAbortHandlersChain(t *testing.T) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
stepsPassed += 1
|
stepsPassed += 1
|
||||||
// after check and abort
|
// after check and abort
|
||||||
c.Abort(409)
|
c.AbortWithStatus(409)
|
||||||
})
|
})
|
||||||
r.Use(func(c *Context) {
|
r.Use(func(c *Context) {
|
||||||
stepsPassed += 1
|
stepsPassed += 1
|
||||||
c.Next()
|
c.Next()
|
||||||
stepsPassed += 1
|
stepsPassed += 1
|
||||||
c.Abort(403)
|
c.AbortWithStatus(403)
|
||||||
})
|
})
|
||||||
|
|
||||||
// RUN
|
// RUN
|
||||||
@ -260,7 +260,7 @@ func TestAbortHandlersChain(t *testing.T) {
|
|||||||
r := New()
|
r := New()
|
||||||
r.Use(func(context *Context) {
|
r.Use(func(context *Context) {
|
||||||
stepsPassed += 1
|
stepsPassed += 1
|
||||||
context.Abort(409)
|
context.AbortWithStatus(409)
|
||||||
})
|
})
|
||||||
r.Use(func(context *Context) {
|
r.Use(func(context *Context) {
|
||||||
stepsPassed += 1
|
stepsPassed += 1
|
||||||
@ -440,3 +440,44 @@ func TestBindingJSONMalformed(t *testing.T) {
|
|||||||
t.Errorf("Content-Type should not be application/json, was %s", w.HeaderMap.Get("Content-Type"))
|
t.Errorf("Content-Type should not be application/json, was %s", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientIP(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
var clientIP string = ""
|
||||||
|
r.GET("/", func(c *Context) {
|
||||||
|
clientIP = c.ClientIP()
|
||||||
|
})
|
||||||
|
|
||||||
|
body := bytes.NewBuffer([]byte(""))
|
||||||
|
req, _ := http.NewRequest("GET", "/", body)
|
||||||
|
req.RemoteAddr = "clientip:1234"
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if clientIP != "clientip:1234" {
|
||||||
|
t.Errorf("ClientIP should not be %s, but clientip:1234", clientIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientIPWithXForwardedForWithProxy(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
r.Use(ForwardedFor())
|
||||||
|
|
||||||
|
var clientIP string = ""
|
||||||
|
r.GET("/", func(c *Context) {
|
||||||
|
clientIP = c.ClientIP()
|
||||||
|
})
|
||||||
|
|
||||||
|
body := bytes.NewBuffer([]byte(""))
|
||||||
|
req, _ := http.NewRequest("GET", "/", body)
|
||||||
|
req.RemoteAddr = "172.16.8.3:1234"
|
||||||
|
req.Header.Set("X-Real-Ip", "realip")
|
||||||
|
req.Header.Set("X-Forwarded-For", "1.2.3.4, 10.10.0.4, 192.168.0.43, 172.16.8.4")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if clientIP != "1.2.3.4:0" {
|
||||||
|
t.Errorf("ClientIP should not be %s, but 1.2.3.4:0", clientIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,7 +41,7 @@ func (engine *Engine) LoadHTMLTemplates(pattern string) {
|
|||||||
engine.LoadHTMLGlob(pattern)
|
engine.LoadHTMLGlob(pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEPRECATED. Use NotFound() instead
|
// DEPRECATED. Use NoRoute() instead
|
||||||
func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
|
func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
|
||||||
engine.NoRoute(handlers...)
|
engine.NoRoute(handlers...)
|
||||||
}
|
}
|
||||||
|
58
examples/pluggable_renderer/example_pongo2.go
Normal file
58
examples/pluggable_renderer/example_pongo2.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/flosch/pongo2"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pongoRender struct {
|
||||||
|
cache map[string]*pongo2.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPongoRender() *pongoRender {
|
||||||
|
return &pongoRender{map[string]*pongo2.Template{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeHeader(w http.ResponseWriter, code int, contentType string) {
|
||||||
|
if code >= 0 {
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.WriteHeader(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pongoRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
|
||||||
|
file := data[0].(string)
|
||||||
|
ctx := data[1].(pongo2.Context)
|
||||||
|
var t *pongo2.Template
|
||||||
|
|
||||||
|
if tmpl, ok := p.cache[file]; ok {
|
||||||
|
t = tmpl
|
||||||
|
} else {
|
||||||
|
tmpl, err := pongo2.FromFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.cache[file] = tmpl
|
||||||
|
t = tmpl
|
||||||
|
}
|
||||||
|
writeHeader(w, code, "text/html")
|
||||||
|
return t.ExecuteWriter(ctx, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.HTMLRender = newPongoRender()
|
||||||
|
|
||||||
|
r.GET("/index", func(c *gin.Context) {
|
||||||
|
name := c.Request.FormValue("name")
|
||||||
|
ctx := pongo2.Context{
|
||||||
|
"title": "Gin meets pongo2 !",
|
||||||
|
"name": name,
|
||||||
|
}
|
||||||
|
c.HTML(200, "index.html", ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and server on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
12
examples/pluggable_renderer/index.html
Normal file
12
examples/pluggable_renderer/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ja">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta name="keywords" content="">
|
||||||
|
<meta name="description" content="">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Hello {{ name }} !
|
||||||
|
</body>
|
||||||
|
</html>
|
209
gin.go
209
gin.go
@ -5,13 +5,11 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,49 +26,31 @@ const (
|
|||||||
type (
|
type (
|
||||||
HandlerFunc func(*Context)
|
HandlerFunc func(*Context)
|
||||||
|
|
||||||
// Used internally to configure router, a RouterGroup is associated with a prefix
|
|
||||||
// and an array of handlers (middlewares)
|
|
||||||
RouterGroup struct {
|
|
||||||
Handlers []HandlerFunc
|
|
||||||
prefix string
|
|
||||||
parent *RouterGroup
|
|
||||||
engine *Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
|
// Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares.
|
||||||
Engine struct {
|
Engine struct {
|
||||||
*RouterGroup
|
*RouterGroup
|
||||||
HTMLRender render.Render
|
HTMLRender render.Render
|
||||||
cache sync.Pool
|
Default404Body []byte
|
||||||
finalNoRoute []HandlerFunc
|
pool sync.Pool
|
||||||
|
allNoRoute []HandlerFunc
|
||||||
noRoute []HandlerFunc
|
noRoute []HandlerFunc
|
||||||
router *httprouter.Router
|
router *httprouter.Router
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
|
|
||||||
c := engine.createContext(w, req, nil, engine.finalNoRoute)
|
|
||||||
// set 404 by default, useful for logging
|
|
||||||
c.Writer.WriteHeader(404)
|
|
||||||
c.Next()
|
|
||||||
if !c.Writer.Written() {
|
|
||||||
if c.Writer.Status() == 404 {
|
|
||||||
c.Data(-1, MIMEPlain, []byte("404 page not found"))
|
|
||||||
} else {
|
|
||||||
c.Writer.WriteHeaderNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
engine.cache.Put(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new blank Engine instance without any middleware attached.
|
// Returns a new blank Engine instance without any middleware attached.
|
||||||
// The most basic configuration
|
// The most basic configuration
|
||||||
func New() *Engine {
|
func New() *Engine {
|
||||||
engine := &Engine{}
|
engine := &Engine{}
|
||||||
engine.RouterGroup = &RouterGroup{nil, "/", nil, engine}
|
engine.RouterGroup = &RouterGroup{
|
||||||
|
Handlers: nil,
|
||||||
|
absolutePath: "/",
|
||||||
|
engine: engine,
|
||||||
|
}
|
||||||
engine.router = httprouter.New()
|
engine.router = httprouter.New()
|
||||||
|
engine.Default404Body = []byte("404 page not found")
|
||||||
engine.router.NotFound = engine.handle404
|
engine.router.NotFound = engine.handle404
|
||||||
engine.cache.New = func() interface{} {
|
engine.pool.New = func() interface{} {
|
||||||
c := &Context{Engine: engine}
|
c := &Context{Engine: engine}
|
||||||
c.Writer = &c.writermem
|
c.Writer = &c.writermem
|
||||||
return c
|
return c
|
||||||
@ -86,7 +66,8 @@ func Default() *Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
if gin_mode == debugCode {
|
if IsDebugging() {
|
||||||
|
render.HTMLDebug.AddGlob(pattern)
|
||||||
engine.HTMLRender = render.HTMLDebug
|
engine.HTMLRender = render.HTMLDebug
|
||||||
} else {
|
} else {
|
||||||
templ := template.Must(template.ParseGlob(pattern))
|
templ := template.Must(template.ParseGlob(pattern))
|
||||||
@ -95,7 +76,8 @@ func (engine *Engine) LoadHTMLGlob(pattern string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
if gin_mode == debugCode {
|
if IsDebugging() {
|
||||||
|
render.HTMLDebug.AddFiles(files...)
|
||||||
engine.HTMLRender = render.HTMLDebug
|
engine.HTMLRender = render.HTMLDebug
|
||||||
} else {
|
} else {
|
||||||
templ := template.Must(template.ParseFiles(files...))
|
templ := template.Must(template.ParseFiles(files...))
|
||||||
@ -112,151 +94,50 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
|||||||
// Adds handlers for NoRoute. It return a 404 code by default.
|
// Adds handlers for NoRoute. It return a 404 code by default.
|
||||||
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||||
engine.noRoute = handlers
|
engine.noRoute = handlers
|
||||||
engine.finalNoRoute = engine.combineHandlers(engine.noRoute)
|
engine.rebuild404Handlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Use(middlewares ...HandlerFunc) {
|
func (engine *Engine) Use(middlewares ...HandlerFunc) {
|
||||||
engine.RouterGroup.Use(middlewares...)
|
engine.RouterGroup.Use(middlewares...)
|
||||||
engine.finalNoRoute = engine.combineHandlers(engine.noRoute)
|
engine.rebuild404Handlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) rebuild404Handlers() {
|
||||||
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
|
||||||
|
c := engine.createContext(w, req, nil, engine.allNoRoute)
|
||||||
|
// set 404 by default, useful for logging
|
||||||
|
c.Writer.WriteHeader(404)
|
||||||
|
c.Next()
|
||||||
|
if !c.Writer.Written() {
|
||||||
|
if c.Writer.Status() == 404 {
|
||||||
|
c.Data(-1, MIMEPlain, engine.Default404Body)
|
||||||
|
} else {
|
||||||
|
c.Writer.WriteHeaderNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
engine.reuseContext(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP makes the router implement the http.Handler interface.
|
// ServeHTTP makes the router implement the http.Handler interface.
|
||||||
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (engine *Engine) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
engine.router.ServeHTTP(w, req)
|
engine.router.ServeHTTP(writer, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Run(addr string) {
|
func (engine *Engine) Run(addr string) error {
|
||||||
if gin_mode == debugCode {
|
debugPrint("Listening and serving HTTP on %s", addr)
|
||||||
fmt.Println("[GIN-debug] Listening and serving HTTP on " + addr)
|
|
||||||
}
|
|
||||||
if err := http.ListenAndServe(addr, engine); err != nil {
|
if err := http.ListenAndServe(addr, engine); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) RunTLS(addr string, cert string, key string) {
|
func (engine *Engine) RunTLS(addr string, cert string, key string) error {
|
||||||
if gin_mode == debugCode {
|
debugPrint("Listening and serving HTTPS on %s", addr)
|
||||||
fmt.Println("[GIN-debug] Listening and serving HTTPS on " + addr)
|
|
||||||
}
|
|
||||||
if err := http.ListenAndServeTLS(addr, cert, key, engine); err != nil {
|
if err := http.ListenAndServeTLS(addr, cert, key, engine); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/********** ROUTES GROUPING *********/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
// Adds middlewares to the group, see example code in github.
|
|
||||||
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
|
|
||||||
group.Handlers = append(group.Handlers, middlewares...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
|
|
||||||
// For example, all the routes that use a common middlware for authorization could be grouped.
|
|
||||||
func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *RouterGroup {
|
|
||||||
prefix := group.pathFor(component)
|
|
||||||
|
|
||||||
return &RouterGroup{
|
|
||||||
Handlers: group.combineHandlers(handlers),
|
|
||||||
parent: group,
|
|
||||||
prefix: prefix,
|
|
||||||
engine: group.engine,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *RouterGroup) pathFor(p string) string {
|
|
||||||
joined := path.Join(group.prefix, p)
|
|
||||||
// Append a '/' if the last component had one, but only if it's not there already
|
|
||||||
if len(p) > 0 && p[len(p)-1] == '/' && joined[len(joined)-1] != '/' {
|
|
||||||
return joined + "/"
|
|
||||||
}
|
|
||||||
return joined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers a new request handle and middlewares with the given path and method.
|
|
||||||
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
|
|
||||||
// See the example code in github.
|
|
||||||
//
|
|
||||||
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
|
||||||
// functions can be used.
|
|
||||||
//
|
|
||||||
// This function is intended for bulk loading and to allow the usage of less
|
|
||||||
// frequently used, non-standardized or custom methods (e.g. for internal
|
|
||||||
// communication with a proxy).
|
|
||||||
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
|
|
||||||
p = group.pathFor(p)
|
|
||||||
handlers = group.combineHandlers(handlers)
|
|
||||||
if gin_mode == debugCode {
|
|
||||||
nuHandlers := len(handlers)
|
|
||||||
name := funcName(handlers[nuHandlers-1])
|
|
||||||
fmt.Printf("[GIN-debug] %-5s %-25s --> %s (%d handlers)\n", method, p, name, nuHandlers)
|
|
||||||
}
|
|
||||||
group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
||||||
c := group.engine.createContext(w, req, params, handlers)
|
|
||||||
c.Next()
|
|
||||||
c.Writer.WriteHeaderNow()
|
|
||||||
group.engine.cache.Put(c)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST is a shortcut for router.Handle("POST", path, handle)
|
|
||||||
func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("POST", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET is a shortcut for router.Handle("GET", path, handle)
|
|
||||||
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("GET", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
|
||||||
func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("DELETE", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
|
||||||
func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("PATCH", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
|
||||||
func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("PUT", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
|
||||||
func (group *RouterGroup) OPTIONS(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("OPTIONS", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
|
||||||
func (group *RouterGroup) HEAD(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("HEAD", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static serves files from the given file system root.
|
|
||||||
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
|
||||||
// of the Router's NotFound handler.
|
|
||||||
// To use the operating system's file system implementation,
|
|
||||||
// use :
|
|
||||||
// router.Static("/static", "/var/www")
|
|
||||||
func (group *RouterGroup) Static(p, root string) {
|
|
||||||
prefix := group.pathFor(p)
|
|
||||||
p = path.Join(p, "/*filepath")
|
|
||||||
fileServer := http.StripPrefix(prefix, http.FileServer(http.Dir(root)))
|
|
||||||
group.GET(p, func(c *Context) {
|
|
||||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
|
||||||
})
|
|
||||||
group.HEAD(p, func(c *Context) {
|
|
||||||
fileServer.ServeHTTP(c.Writer, c.Request)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
|
|
||||||
s := len(group.Handlers) + len(handlers)
|
|
||||||
h := make([]HandlerFunc, 0, s)
|
|
||||||
h = append(h, group.Handlers...)
|
|
||||||
h = append(h, handlers...)
|
|
||||||
return h
|
|
||||||
}
|
}
|
||||||
|
@ -108,9 +108,8 @@ func testRouteNotOK2(method string, t *testing.T) {
|
|||||||
if passed == true {
|
if passed == true {
|
||||||
t.Errorf(method + " route handler was invoked, when it should not")
|
t.Errorf(method + " route handler was invoked, when it should not")
|
||||||
}
|
}
|
||||||
if w.Code != http.StatusNotFound {
|
if w.Code != http.StatusMethodNotAllowed {
|
||||||
// If this fails, it's because httprouter needs to be updated to at least f78f58a0db
|
t.Errorf("Status code should be %v, was %d. Location: %s", http.StatusMethodNotAllowed, w.Code, w.HeaderMap.Get("Location"))
|
||||||
t.Errorf("Status code should be %v, was %d. Location: %s", http.StatusNotFound, w.Code, w.HeaderMap.Get("Location"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +145,7 @@ func TestHandleStaticFile(t *testing.T) {
|
|||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
if w.Code != 200 {
|
if w.Code != 200 {
|
||||||
t.Errorf("Response code should be Ok, was: %s", w.Code)
|
t.Errorf("Response code should be 200, was: %d", w.Code)
|
||||||
}
|
}
|
||||||
if w.Body.String() != "Gin Web Framework" {
|
if w.Body.String() != "Gin Web Framework" {
|
||||||
t.Errorf("Response should be test, was: %s", w.Body.String())
|
t.Errorf("Response should be test, was: %s", w.Body.String())
|
||||||
@ -168,7 +167,7 @@ func TestHandleStaticDir(t *testing.T) {
|
|||||||
// TEST
|
// TEST
|
||||||
bodyAsString := w.Body.String()
|
bodyAsString := w.Body.String()
|
||||||
if w.Code != 200 {
|
if w.Code != 200 {
|
||||||
t.Errorf("Response code should be Ok, was: %s", w.Code)
|
t.Errorf("Response code should be 200, was: %d", w.Code)
|
||||||
}
|
}
|
||||||
if len(bodyAsString) == 0 {
|
if len(bodyAsString) == 0 {
|
||||||
t.Errorf("Got empty body instead of file tree")
|
t.Errorf("Got empty body instead of file tree")
|
||||||
|
93
logger.go
93
logger.go
@ -10,6 +10,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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})
|
||||||
|
)
|
||||||
|
|
||||||
func ErrorLogger() HandlerFunc {
|
func ErrorLogger() HandlerFunc {
|
||||||
return ErrorLoggerT(ErrorTypeAll)
|
return ErrorLoggerT(ErrorTypeAll)
|
||||||
}
|
}
|
||||||
@ -26,14 +37,6 @@ func ErrorLoggerT(typ uint32) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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})
|
|
||||||
reset = string([]byte{27, 91, 48, 109})
|
|
||||||
)
|
|
||||||
|
|
||||||
func Logger() HandlerFunc {
|
func Logger() HandlerFunc {
|
||||||
stdlogger := log.New(os.Stdout, "", 0)
|
stdlogger := log.New(os.Stdout, "", 0)
|
||||||
//errlogger := log.New(os.Stderr, "", 0)
|
//errlogger := log.New(os.Stderr, "", 0)
|
||||||
@ -45,38 +48,58 @@ func Logger() HandlerFunc {
|
|||||||
// Process request
|
// Process request
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
// save the IP of the requester
|
// Stop timer
|
||||||
requester := c.Request.Header.Get("X-Real-IP")
|
|
||||||
// if the requester-header is empty, check the forwarded-header
|
|
||||||
if len(requester) == 0 {
|
|
||||||
requester = c.Request.Header.Get("X-Forwarded-For")
|
|
||||||
}
|
|
||||||
// if the requester is still empty, use the hard-coded address from the socket
|
|
||||||
if len(requester) == 0 {
|
|
||||||
requester = c.Request.RemoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var color string
|
|
||||||
code := c.Writer.Status()
|
|
||||||
switch {
|
|
||||||
case code >= 200 && code <= 299:
|
|
||||||
color = green
|
|
||||||
case code >= 300 && code <= 399:
|
|
||||||
color = white
|
|
||||||
case code >= 400 && code <= 499:
|
|
||||||
color = yellow
|
|
||||||
default:
|
|
||||||
color = red
|
|
||||||
}
|
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
latency := end.Sub(start)
|
latency := end.Sub(start)
|
||||||
stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n%s",
|
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
method := c.Request.Method
|
||||||
|
statusCode := c.Writer.Status()
|
||||||
|
statusColor := colorForStatus(statusCode)
|
||||||
|
methodColor := colorForMethod(method)
|
||||||
|
|
||||||
|
stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s |%s %s %-7s %s\n%s",
|
||||||
end.Format("2006/01/02 - 15:04:05"),
|
end.Format("2006/01/02 - 15:04:05"),
|
||||||
color, code, reset,
|
statusColor, statusCode, reset,
|
||||||
latency,
|
latency,
|
||||||
requester,
|
clientIP,
|
||||||
c.Request.Method, c.Request.URL.Path,
|
methodColor, reset, method,
|
||||||
|
c.Request.URL.Path,
|
||||||
c.Errors.String(),
|
c.Errors.String(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func colorForStatus(code int) string {
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code <= 299:
|
||||||
|
return green
|
||||||
|
case code >= 300 && code <= 399:
|
||||||
|
return white
|
||||||
|
case code >= 400 && code <= 499:
|
||||||
|
return yellow
|
||||||
|
default:
|
||||||
|
return red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorForMethod(method string) string {
|
||||||
|
switch {
|
||||||
|
case method == "GET":
|
||||||
|
return blue
|
||||||
|
case method == "POST":
|
||||||
|
return cyan
|
||||||
|
case method == "PUT":
|
||||||
|
return yellow
|
||||||
|
case method == "DELETE":
|
||||||
|
return red
|
||||||
|
case method == "PATCH":
|
||||||
|
return green
|
||||||
|
case method == "HEAD":
|
||||||
|
return magenta
|
||||||
|
case method == "OPTIONS":
|
||||||
|
return white
|
||||||
|
default:
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
31
mode.go
31
mode.go
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +23,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var gin_mode int = debugCode
|
var gin_mode int = debugCode
|
||||||
|
var mode_name string = DebugMode
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
value := os.Getenv(GIN_MODE)
|
||||||
|
if len(value) == 0 {
|
||||||
|
SetMode(DebugMode)
|
||||||
|
} else {
|
||||||
|
SetMode(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetMode(value string) {
|
func SetMode(value string) {
|
||||||
switch value {
|
switch value {
|
||||||
@ -32,15 +43,21 @@ func SetMode(value string) {
|
|||||||
case TestMode:
|
case TestMode:
|
||||||
gin_mode = testCode
|
gin_mode = testCode
|
||||||
default:
|
default:
|
||||||
panic("gin mode unknown, the allowed modes are: " + DebugMode + " and " + ReleaseMode)
|
panic("gin mode unknown: " + value)
|
||||||
}
|
}
|
||||||
|
mode_name = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func Mode() string {
|
||||||
value := os.Getenv(GIN_MODE)
|
return mode_name
|
||||||
if len(value) == 0 {
|
}
|
||||||
SetMode(DebugMode)
|
|
||||||
} else {
|
func IsDebugging() bool {
|
||||||
SetMode(value)
|
return gin_mode == debugCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrint(format string, values ...interface{}) {
|
||||||
|
if IsDebugging() {
|
||||||
|
fmt.Printf("[GIN-debug] "+format, values...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
r := New()
|
r := New()
|
||||||
r.Use(Recovery())
|
r.Use(Recovery())
|
||||||
r.GET("/recovery", func(c *Context) {
|
r.GET("/recovery", func(c *Context) {
|
||||||
c.Abort(400)
|
c.AbortWithStatus(400)
|
||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -30,7 +30,10 @@ type (
|
|||||||
redirectRender struct{}
|
redirectRender struct{}
|
||||||
|
|
||||||
// Redirects
|
// Redirects
|
||||||
htmlDebugRender struct{}
|
htmlDebugRender struct {
|
||||||
|
files []string
|
||||||
|
globs []string
|
||||||
|
}
|
||||||
|
|
||||||
// form binding
|
// form binding
|
||||||
HTMLRender struct {
|
HTMLRender struct {
|
||||||
@ -43,7 +46,7 @@ var (
|
|||||||
XML = xmlRender{}
|
XML = xmlRender{}
|
||||||
Plain = plainRender{}
|
Plain = plainRender{}
|
||||||
Redirect = redirectRender{}
|
Redirect = redirectRender{}
|
||||||
HTMLDebug = htmlDebugRender{}
|
HTMLDebug = &htmlDebugRender{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeHeader(w http.ResponseWriter, code int, contentType string) {
|
func writeHeader(w http.ResponseWriter, code int, contentType string) {
|
||||||
@ -82,14 +85,33 @@ func (_ plainRender) Render(w http.ResponseWriter, code int, data ...interface{}
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ htmlDebugRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
|
func (r *htmlDebugRender) AddGlob(pattern string) {
|
||||||
|
r.globs = append(r.globs, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *htmlDebugRender) AddFiles(files ...string) {
|
||||||
|
r.files = append(r.files, files...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *htmlDebugRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
|
||||||
writeHeader(w, code, "text/html")
|
writeHeader(w, code, "text/html")
|
||||||
file := data[0].(string)
|
file := data[0].(string)
|
||||||
obj := data[1]
|
obj := data[1]
|
||||||
t, err := template.ParseFiles(file)
|
|
||||||
if err != nil {
|
t := template.New("")
|
||||||
|
|
||||||
|
if len(r.files) > 0 {
|
||||||
|
if _, err := t.ParseFiles(r.files...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, glob := range r.globs {
|
||||||
|
if _, err := t.ParseGlob(glob); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return t.ExecuteTemplate(w, file, obj)
|
return t.ExecuteTemplate(w, file, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoWritten = -1
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ResponseWriter interface {
|
ResponseWriter interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
@ -20,6 +24,7 @@ type (
|
|||||||
http.CloseNotifier
|
http.CloseNotifier
|
||||||
|
|
||||||
Status() int
|
Status() int
|
||||||
|
Size() int
|
||||||
Written() bool
|
Written() bool
|
||||||
WriteHeaderNow()
|
WriteHeaderNow()
|
||||||
}
|
}
|
||||||
@ -27,43 +32,49 @@ type (
|
|||||||
responseWriter struct {
|
responseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
status int
|
status int
|
||||||
written bool
|
size int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||||
w.ResponseWriter = writer
|
w.ResponseWriter = writer
|
||||||
w.status = 200
|
w.status = 200
|
||||||
w.written = false
|
w.size = NoWritten
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeader(code int) {
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
if code > 0 {
|
if code > 0 {
|
||||||
w.status = code
|
w.status = code
|
||||||
if w.written {
|
if w.Written() {
|
||||||
log.Println("[GIN] WARNING. Headers were already written!")
|
log.Println("[GIN] WARNING. Headers were already written!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeaderNow() {
|
func (w *responseWriter) WriteHeaderNow() {
|
||||||
if !w.written {
|
if !w.Written() {
|
||||||
w.written = true
|
w.size = 0
|
||||||
w.ResponseWriter.WriteHeader(w.status)
|
w.ResponseWriter.WriteHeader(w.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Write(data []byte) (n int, err error) {
|
func (w *responseWriter) Write(data []byte) (n int, err error) {
|
||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
return w.ResponseWriter.Write(data)
|
n, err = w.ResponseWriter.Write(data)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Status() int {
|
func (w *responseWriter) Status() int {
|
||||||
return w.status
|
return w.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Size() int {
|
||||||
|
return w.size
|
||||||
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Written() bool {
|
func (w *responseWriter) Written() bool {
|
||||||
return w.written
|
return w.size != NoWritten
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the http.Hijacker interface
|
// Implements the http.Hijacker interface
|
||||||
|
148
routergroup.go
Normal file
148
routergroup.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Used internally to configure router, a RouterGroup is associated with a prefix
|
||||||
|
// and an array of handlers (middlewares)
|
||||||
|
type RouterGroup struct {
|
||||||
|
Handlers []HandlerFunc
|
||||||
|
absolutePath string
|
||||||
|
engine *Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds middlewares to the group, see example code in github.
|
||||||
|
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
|
||||||
|
group.Handlers = append(group.Handlers, middlewares...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
|
||||||
|
// For example, all the routes that use a common middlware for authorization could be grouped.
|
||||||
|
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
|
||||||
|
return &RouterGroup{
|
||||||
|
Handlers: group.combineHandlers(handlers),
|
||||||
|
absolutePath: group.calculateAbsolutePath(relativePath),
|
||||||
|
engine: group.engine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new request handle and middlewares with the given path and method.
|
||||||
|
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
|
||||||
|
// See the example code in github.
|
||||||
|
//
|
||||||
|
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||||
|
// functions can be used.
|
||||||
|
//
|
||||||
|
// This function is intended for bulk loading and to allow the usage of less
|
||||||
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
|
// communication with a proxy).
|
||||||
|
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers []HandlerFunc) {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
handlers = group.combineHandlers(handlers)
|
||||||
|
if IsDebugging() {
|
||||||
|
nuHandlers := len(handlers)
|
||||||
|
handlerName := nameOfFunction(handlers[nuHandlers-1])
|
||||||
|
debugPrint("%-5s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.engine.router.Handle(httpMethod, absolutePath, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
|
context := group.engine.createContext(w, req, params, handlers)
|
||||||
|
context.Next()
|
||||||
|
context.Writer.WriteHeaderNow()
|
||||||
|
group.engine.reuseContext(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST is a shortcut for router.Handle("POST", path, handle)
|
||||||
|
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("POST", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET is a shortcut for router.Handle("GET", path, handle)
|
||||||
|
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("GET", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
||||||
|
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("DELETE", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
||||||
|
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("PATCH", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
||||||
|
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("PUT", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
||||||
|
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("OPTIONS", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
||||||
|
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("HEAD", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LINK is a shortcut for router.Handle("LINK", path, handle)
|
||||||
|
func (group *RouterGroup) LINK(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("LINK", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNLINK is a shortcut for router.Handle("UNLINK", path, handle)
|
||||||
|
func (group *RouterGroup) UNLINK(relativePath string, handlers ...HandlerFunc) {
|
||||||
|
group.Handle("UNLINK", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static serves files from the given file system root.
|
||||||
|
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
||||||
|
// of the Router's NotFound handler.
|
||||||
|
// To use the operating system's file system implementation,
|
||||||
|
// use :
|
||||||
|
// router.Static("/static", "/var/www")
|
||||||
|
func (group *RouterGroup) Static(relativePath, root string) {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
handler := group.createStaticHandler(absolutePath, root)
|
||||||
|
absolutePath = path.Join(absolutePath, "/*filepath")
|
||||||
|
|
||||||
|
// Register GET and HEAD handlers
|
||||||
|
group.GET(absolutePath, handler)
|
||||||
|
group.HEAD(absolutePath, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) createStaticHandler(absolutePath, root string) func(*Context) {
|
||||||
|
fileServer := http.StripPrefix(absolutePath, http.FileServer(http.Dir(root)))
|
||||||
|
return func(c *Context) {
|
||||||
|
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
|
||||||
|
finalSize := len(group.Handlers) + len(handlers)
|
||||||
|
mergedHandlers := make([]HandlerFunc, 0, finalSize)
|
||||||
|
mergedHandlers = append(mergedHandlers, group.Handlers...)
|
||||||
|
return append(mergedHandlers, handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
|
||||||
|
if len(relativePath) == 0 {
|
||||||
|
return group.absolutePath
|
||||||
|
}
|
||||||
|
absolutePath := path.Join(group.absolutePath, relativePath)
|
||||||
|
appendSlash := lastChar(relativePath) == '/' && lastChar(absolutePath) != '/'
|
||||||
|
if appendSlash {
|
||||||
|
return absolutePath + "/"
|
||||||
|
}
|
||||||
|
return absolutePath
|
||||||
|
}
|
38
utils.go
38
utils.go
@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type H map[string]interface{}
|
type H map[string]interface{}
|
||||||
@ -37,14 +38,45 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filterFlags(content string) string {
|
func filterFlags(content string) string {
|
||||||
for i, a := range content {
|
for i, char := range content {
|
||||||
if a == ' ' || a == ';' {
|
if char == ' ' || char == ';' {
|
||||||
return content[:i]
|
return content[:i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
func funcName(f interface{}) string {
|
func chooseData(custom, wildcard interface{}) interface{} {
|
||||||
|
if custom == nil {
|
||||||
|
if wildcard == nil {
|
||||||
|
panic("negotiation config is invalid")
|
||||||
|
}
|
||||||
|
return wildcard
|
||||||
|
}
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAccept(accept string) []string {
|
||||||
|
parts := strings.Split(accept, ",")
|
||||||
|
for i, part := range parts {
|
||||||
|
index := strings.IndexByte(part, ';')
|
||||||
|
if index >= 0 {
|
||||||
|
part = part[0:index]
|
||||||
|
}
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
parts[i] = part
|
||||||
|
}
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastChar(str string) uint8 {
|
||||||
|
size := len(str)
|
||||||
|
if size == 0 {
|
||||||
|
panic("The length of the string can't be 0")
|
||||||
|
}
|
||||||
|
return str[size-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameOfFunction(f interface{}) string {
|
||||||
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user