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