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) }