From 0b80e09204bddb5cebbfc77cead737e9dcf4e341 Mon Sep 17 00:00:00 2001 From: Ray Rodriguez Date: Sat, 7 Feb 2015 23:07:06 -0500 Subject: [PATCH 01/24] Fixing typo in Form parameters example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c6ca7e..e3714ed 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ 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) From a900e7888c50e65e5a830794c2cff536944cd51c Mon Sep 17 00:00:00 2001 From: techjanitor Date: Sat, 7 Feb 2015 22:44:53 -0600 Subject: [PATCH 02/24] Update context.go Add localhost to proxies --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 2f0e2d8..c39d5e2 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)) From d852d334f484b807c0ec47e07add734d99f1ec36 Mon Sep 17 00:00:00 2001 From: rns Date: Sun, 8 Feb 2015 21:50:44 +0200 Subject: [PATCH 03/24] Update README.md typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c6ca7e..872b882 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ func main() { c.String(401, "not authorized") }) router.PUT("/error", func(c *gin.Context) { - c.String(500, "and error hapenned :(") + c.String(500, "and error happened :(") }) router.Run(":8080") } From 63503bf682ca0baf48b2900e6d608d0ecf5e7675 Mon Sep 17 00:00:00 2001 From: Joshua Loper Date: Sun, 8 Feb 2015 14:43:41 -0600 Subject: [PATCH 04/24] Fixes a minor typo, changes 'Listen and server on 0.0.0.0:8080' to 'Listen and serve on 0.0.0.0:8080' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c6ca7e..92e7447 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ func main() { c.String(200, "pong") }) - // Listen and server on 0.0.0.0:8080 + // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ``` From 70f280f880dc300085e72988f01f94b4fd7f6eeb Mon Sep 17 00:00:00 2001 From: Ethan Kan Date: Mon, 9 Feb 2015 15:13:05 -0800 Subject: [PATCH 05/24] Added support for unsigned integers in binding parameters of form posts. Also changed parsing of integer fields to take into account the size of the fields. --- binding/binding.go | 56 +++++++++++++++++++++++++++++++++++++--------- context_test.go | 36 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 99f3d0e..55f164f 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -98,18 +98,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" 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() From d806d7a600c72e6572c183a3e96337847fbe351e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E5=93=A5?= <858806258@qq.com> Date: Thu, 12 Feb 2015 14:29:11 +0800 Subject: [PATCH 06/24] wrong spell change similate to simulate --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df4bf28..d4b9eca 100644 --- a/README.md +++ b/README.md @@ -438,7 +438,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"}, From 6bd27d0238f1946bd5076312aa49a35706079b9c Mon Sep 17 00:00:00 2001 From: Christopher Harrington Date: Sat, 14 Feb 2015 21:55:39 -0600 Subject: [PATCH 07/24] Remove reference to Martini from recovery.go --- recovery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() { From 61ef0bea4a16c2afb7fc2083aa0f7475a4b77b7c Mon Sep 17 00:00:00 2001 From: Rogier Lommers Date: Sun, 15 Feb 2015 14:04:44 +0100 Subject: [PATCH 08/24] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4b9eca..e4c1ec2 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,6 @@ func main() { ``` ####Serving static files - Use Engine.ServeFiles(path string, root http.FileSystem): ```go @@ -344,6 +343,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 From f145e435c7e6648789b7be6822103901fe45a322 Mon Sep 17 00:00:00 2001 From: Evgeny Persienko Date: Fri, 20 Feb 2015 11:37:20 +0600 Subject: [PATCH 09/24] Add validating sub structures --- binding/binding.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/binding/binding.go b/binding/binding.go index 81ac3fa..cff06f8 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -199,6 +199,17 @@ 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 + } + } } } case reflect.Slice: From b537c5d15ec87896287bfe0415d7d130244d2781 Mon Sep 17 00:00:00 2001 From: Evgeny Persienko Date: Fri, 20 Feb 2015 14:33:50 +0600 Subject: [PATCH 10/24] Add slice elements check for not required slice --- binding/binding.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/binding/binding.go b/binding/binding.go index cff06f8..1f72b63 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -209,6 +209,11 @@ func Validate(obj interface{}, parents ...string) error { 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 + } } } } From 95230adbcb619cc6ae0d5878937d952a111355f6 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Sat, 21 Feb 2015 11:24:57 +0000 Subject: [PATCH 11/24] Added Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e4c1ec2..32ca3a2 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) From e769b5dd3795522ef976f7a1d185036d761f327b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 4 Mar 2015 13:14:10 +0900 Subject: [PATCH 12/24] colorful logger on windows --- logger.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logger.go b/logger.go index 5054f6e..478953a 100644 --- a/logger.go +++ b/logger.go @@ -6,8 +6,9 @@ package gin import ( "log" - "os" "time" + + "github.com/mattn/go-colorable" ) var ( @@ -38,7 +39,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) { From 07c0d2e8fed7d6c02f6f3d762be0eaf1e42d0255 Mon Sep 17 00:00:00 2001 From: Frank Bille Date: Wed, 4 Mar 2015 23:15:03 +0100 Subject: [PATCH 13/24] Add customizable Realm for Basic authentication Depending on the use case, it might be useful to be able to have different realms for different route groups. --- auth.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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") From 5e3a096828af32eca7f346012d9aaa3634760f8e Mon Sep 17 00:00:00 2001 From: Frank Bille Date: Wed, 4 Mar 2015 23:38:17 +0100 Subject: [PATCH 14/24] Added test for custom realm --- auth_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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")) + } +} From 2e47cda749c5af537f7a0f92ee2bd9bb501bbf5e Mon Sep 17 00:00:00 2001 From: Miki Tebeka Date: Thu, 5 Mar 2015 08:14:01 +0200 Subject: [PATCH 15/24] Using net/http constants instead of numeric values --- README.md | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 32ca3a2..2658662 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,25 @@ $ 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 happened :(") + c.String(http.StatusInternalServerError, "and error happened :(") }) router.Run(":8080") } @@ -87,12 +91,16 @@ 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 serve on 0.0.0.0:8080 @@ -130,7 +138,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 @@ -139,7 +147,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 @@ -160,7 +168,7 @@ func main() { lastname := c.Request.Form.Get("lastname") message := "Hello "+ firstname + lastname - c.String(200, message) + c.String(http.StatusOK, message) }) r.Run(":8080") } @@ -274,9 +282,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"}) } }) @@ -286,9 +294,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"}) } }) @@ -305,7 +313,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) { @@ -320,11 +328,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 @@ -364,7 +372,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 @@ -398,7 +406,7 @@ 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. @@ -471,9 +479,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 :("}) } }) From e5aefdee40200ddb5c0ad299bfe3aca8a4490a1c Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 8 Mar 2015 14:09:13 +0100 Subject: [PATCH 16/24] Reorder README.md example imports --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 2658662..b6020a2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ package main import ( "net/http" - "github.com/gin-gonic/gin" ) @@ -93,7 +92,6 @@ package main import ( "net/http" - "github.com/gin-gonic/gin" ) From dc0091006b1e66546408658c19e1d9d0ed145f72 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 8 Mar 2015 14:19:50 +0100 Subject: [PATCH 17/24] Reorder logger.go imports --- logger.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/logger.go b/logger.go index 478953a..0f1f34b 100644 --- a/logger.go +++ b/logger.go @@ -5,10 +5,9 @@ package gin import ( + "github.com/mattn/go-colorable" "log" "time" - - "github.com/mattn/go-colorable" ) var ( From 0fb7bed1c0c701958d0319018ccdda7e851163e0 Mon Sep 17 00:00:00 2001 From: Aleksandr Didenko Date: Sun, 8 Mar 2015 15:43:37 +0100 Subject: [PATCH 18/24] Added support multipart/form-data #109 --- binding/binding.go | 24 ++++++++++++++++++++---- context.go | 2 ++ gin.go | 15 ++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index b49f1e5..f72b943 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() diff --git a/context.go b/context.go index c39d5e2..d877514 100644 --- a/context.go +++ b/context.go @@ -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: diff --git a/gin.go b/gin.go index 42c4b1f..3e7181c 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 ( From 4e0e7d6e16681b387e1708b61e6e01916ca1494d Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 8 Mar 2015 15:50:23 +0100 Subject: [PATCH 19/24] Add example from PR #121 --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index b6020a2..92d6a0b 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,47 @@ func main() { } ``` +###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() { From bee03fa7b0c9bf88ac211f143e90393ecc8fd4b5 Mon Sep 17 00:00:00 2001 From: techjanitor Date: Sun, 8 Mar 2015 17:24:23 +0100 Subject: [PATCH 20/24] Add documentation for using layout files with templates #219 --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 92d6a0b..8305578 100644 --- a/README.md +++ b/README.md @@ -439,6 +439,33 @@ 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: From cf8150ed2bb0f4c503cddc1737731b5d28798b82 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 8 Mar 2015 17:50:58 +0100 Subject: [PATCH 21/24] Add HTML no template string output support #197 --- context.go | 5 +++++ render/render.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/context.go b/context.go index d877514..5d7e02a 100644 --- a/context.go +++ b/context.go @@ -351,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/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) } From a04d9e271c9aa20db8ded60f7334fe6a395f7340 Mon Sep 17 00:00:00 2001 From: Adonis Date: Sun, 8 Mar 2015 19:37:27 -0400 Subject: [PATCH 22/24] Include NoMethod Method to Gin to allow middlewares to handle 405 No Method Errors in the gin router --- gin.go | 60 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/gin.go b/gin.go index 3e7181c..9178365 100644 --- a/gin.go +++ b/gin.go @@ -14,14 +14,13 @@ 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" - MIMEMultipartPOSTForm = "multipart/form-data" + 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" ) type ( @@ -30,12 +29,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 } ) @@ -50,7 +51,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 @@ -98,17 +101,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() @@ -122,6 +135,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) From 5feda9fa7a9fd5b6846df7c2a0f05155768f5a29 Mon Sep 17 00:00:00 2001 From: Adonis Date: Sun, 8 Mar 2015 20:25:51 -0400 Subject: [PATCH 23/24] added missing MIMEMultipartPOSTFORM and changed http.Router Godep.json SHA --- Godeps/Godeps.json | 2 +- gin.go | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) 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/gin.go b/gin.go index 9178365..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 ( From 2b85363447be53276a6d86690db7369ee595fc7b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 9 Mar 2015 02:50:24 +0100 Subject: [PATCH 24/24] Update AUTHORS and CHANGELOG --- AUTHORS.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 9 ++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) 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)