Merge branch 'develop' | Update AUTHORS and CHANGELOG
This commit is contained in:
		@ -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 @@
 | 
				
			|||||||
[](https://godoc.org/github.com/gin-gonic/gin)
 | 
					[](https://godoc.org/github.com/gin-gonic/gin)
 | 
				
			||||||
[](https://travis-ci.org/gin-gonic/gin)
 | 
					[](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. 
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ 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 {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										106
									
								
								auth.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								auth.go
									
									
									
									
									
								
							@ -16,70 +16,29 @@ const (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type (
 | 
					type (
 | 
				
			||||||
	BasicAuthPair struct {
 | 
					 | 
				
			||||||
		Code string
 | 
					 | 
				
			||||||
		User string
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	Accounts map[string]string
 | 
						Accounts map[string]string
 | 
				
			||||||
	Pairs    []BasicAuthPair
 | 
						authPair struct {
 | 
				
			||||||
 | 
							Value string
 | 
				
			||||||
 | 
							User  string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						authPairs []authPair
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										207
									
								
								context.go
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								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>
 | 
				
			||||||
							
								
								
									
										215
									
								
								gin.go
									
									
									
									
									
								
							
							
						
						
									
										215
									
								
								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
 | 
				
			||||||
		noRoute      []HandlerFunc
 | 
							allNoRoute     []HandlerFunc
 | 
				
			||||||
		router       *httprouter.Router
 | 
							noRoute        []HandlerFunc
 | 
				
			||||||
 | 
							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("")
 | 
				
			||||||
		return err
 | 
					
 | 
				
			||||||
 | 
						if len(r.files) > 0 {
 | 
				
			||||||
 | 
							if _, err := t.ParseFiles(r.files...); err != nil {
 | 
				
			||||||
 | 
								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,50 +24,57 @@ type (
 | 
				
			|||||||
		http.CloseNotifier
 | 
							http.CloseNotifier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Status() int
 | 
							Status() int
 | 
				
			||||||
 | 
							Size() int
 | 
				
			||||||
		Written() bool
 | 
							Written() bool
 | 
				
			||||||
		WriteHeaderNow()
 | 
							WriteHeaderNow()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user