From 1aa3216303697b27eb57ab321942a116a47217db Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 3 Jul 2014 19:19:06 +0200 Subject: [PATCH 01/67] Some work around bindings. it may do not compile --- binding/binding.go | 34 ++++++++++++++++++++++++++++++ examples/example_basic.go | 3 ++- gin.go | 44 ++++++++++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 binding/binding.go diff --git a/binding/binding.go b/binding/binding.go new file mode 100644 index 0000000..667b3d0 --- /dev/null +++ b/binding/binding.go @@ -0,0 +1,34 @@ +package binding + +import ( + "encoding/json" + "encoding/xml" + "io" +) + +type ( + Binding interface { + Bind(io.Reader, interface{}) error + } + + // JSON binding + jsonBinding struct{} + + // JSON binding + xmlBinding struct{} +) + +var ( + JSON = jsonBinding{} + XML = xmlBinding{} +) + +func (_ jsonBinding) Bind(r io.Reader, obj interface{}) error { + decoder := json.NewDecoder(r) + return decoder.Decode(&obj) +} + +func (_ xmlBinding) Bind(r io.Reader, obj interface{}) error { + decoder := xml.NewDecoder(r) + return decoder.Decode(&obj) +} diff --git a/examples/example_basic.go b/examples/example_basic.go index ea34343..7959a6c 100644 --- a/examples/example_basic.go +++ b/examples/example_basic.go @@ -44,7 +44,8 @@ func main() { var json struct { Value string `json:"value" binding:"required"` } - if c.EnsureBody(&json) { + + if c.Bind(&json) { DB[user] = json.Value c.JSON(200, gin.H{"status": "ok"}) } diff --git a/gin.go b/gin.go index d1d4262..2c4d640 100644 --- a/gin.go +++ b/gin.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "errors" "fmt" + "github.com/gin-gonic/gin/binding" "github.com/julienschmidt/httprouter" "html/template" "log" @@ -371,22 +372,49 @@ func (c *Context) Get(key string) interface{} { /************************************/ // Like ParseBody() but this method also writes a 400 error if the json is not valid. +// DEPRECATED, use Bind() instead. func (c *Context) EnsureBody(item interface{}) bool { - if err := c.ParseBody(item); err != nil { + return c.Bind(item) +} + +// DEPRECATED use bindings directly +// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. +func (c *Context) ParseBody(item interface{}) error { + return binding.JSON.Bind(c.Req.Body, item) +} + +// This function checks the Content-Type to select a binding engine automatically, +// Depending the "Content-Type" header different bindings are used: +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// else --> returns an error +// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. +func (c *Context) Bind(obj interface{}) bool { + var err error + switch c.Req.Header.Get("Content-Type") { + case "application/json": + err = binding.JSON.Bind(c.Req.Body, obj) + case "application/xml": + err = binding.XML.Bind(c.Req.Body, obj) + default: + err = errors.New("unknown content-type: " + c.Req.Header.Get("Content-Type")) + } + if err == nil { + err = Validate(c, obj) + } + if err != nil { c.Fail(400, err) return false } return true } -// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. -func (c *Context) ParseBody(item interface{}) error { - decoder := json.NewDecoder(c.Req.Body) - if err := decoder.Decode(&item); err == nil { - return Validate(c, item) - } else { - return err +func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { + if err := b.Bind(c.Req.Body, obj); err != nil { + c.Fail(400, err) + return false } + return true } // Serializes the given struct as a JSON into the response body in a fast and efficient way. From 108bfb44704ce68ca5c6e05972445965edd247e0 Mon Sep 17 00:00:00 2001 From: Nick Gerakines Date: Thu, 3 Jul 2014 15:17:24 -0400 Subject: [PATCH 02/67] Setting Get metadata method to return both an interface as well as an error to remove panic. --- examples/example_basic.go | 4 ++-- gin.go | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/example_basic.go b/examples/example_basic.go index ea34343..4586907 100644 --- a/examples/example_basic.go +++ b/examples/example_basic.go @@ -38,14 +38,14 @@ func main() { })) authorized.POST("admin", func(c *gin.Context) { - user := c.Get("user").(string) + user, _ := c.Get("user") // Parse JSON var json struct { Value string `json:"value" binding:"required"` } if c.EnsureBody(&json) { - DB[user] = json.Value + DB[user.(string)] = json.Value c.JSON(200, gin.H{"status": "ok"}) } }) diff --git a/gin.go b/gin.go index 197b98c..e29fe9d 100644 --- a/gin.go +++ b/gin.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/julienschmidt/httprouter" "html/template" - "log" "math" "net/http" "path" @@ -283,18 +282,14 @@ func (c *Context) Set(key string, item interface{}) { // Returns the value for the given key. // It panics if the value doesn't exist. -func (c *Context) Get(key string) interface{} { - var ok bool - var item interface{} +func (c *Context) Get(key string) (interface{}, error) { if c.Keys != nil { - item, ok = c.Keys[key] - } else { - item, ok = nil, false + item, ok := c.Keys[key] + if ok { + return item, nil + } } - if !ok || item == nil { - log.Panicf("Key %s doesn't exist", key) - } - return item + return nil, errors.New("Key does not exist.") } /************************************/ From abe076b8f8961378daecca4d8309929302f3c37a Mon Sep 17 00:00:00 2001 From: Nick Gerakines Date: Thu, 3 Jul 2014 18:16:41 -0400 Subject: [PATCH 03/67] Adding MustGet method. Updating README. --- README.md | 14 +++++++------- gin.go | 13 +++++++++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 53efb20..7c36793 100644 --- a/README.md +++ b/README.md @@ -275,14 +275,14 @@ func main() { func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() - + // Set example variable c.Set("example", "12345") - + // before request - + c.Next() - + // after request latency := time.Since(t) log.Print(latency) @@ -292,10 +292,10 @@ func Logger() gin.HandlerFunc { func main() { r := gin.New() r.Use(Logger()) - + r.GET("/test", func(c *gin.Context){ - example := r.Get("example").(string) - + example := c.MustGet("example").(string) + // it would print: "12345" log.Println(example) }) diff --git a/gin.go b/gin.go index e29fe9d..48a2b6d 100644 --- a/gin.go +++ b/gin.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/julienschmidt/httprouter" "html/template" + "log" "math" "net/http" "path" @@ -280,8 +281,7 @@ func (c *Context) Set(key string, item interface{}) { c.Keys[key] = item } -// Returns the value for the given key. -// It panics if the value doesn't 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) { if c.Keys != nil { item, ok := c.Keys[key] @@ -292,6 +292,15 @@ func (c *Context) Get(key string) (interface{}, error) { return nil, errors.New("Key does not exist.") } +// MustGet returns the value for the given key or panics if the value doesn't exist. +func (c *Context) MustGet(key string) interface{} { + value, err := c.Get(key) + if err != nil || value == nil { + log.Panicf("Key %s doesn't exist", key) + } + return value +} + /************************************/ /******** ENCOGING MANAGEMENT********/ /************************************/ From b5db76b87a6a430c80c6467b62837a52099fb7f9 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 4 Jul 2014 16:59:57 +0200 Subject: [PATCH 04/67] Adds default config variable --- gin.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index d76eae1..03bf8e5 100644 --- a/gin.go +++ b/gin.go @@ -68,6 +68,13 @@ type ( } ) +var ( + DefaultConfig = Config{ + CacheSize: 1024, + Preallocated: 512, + } +) + func (a ErrorMsgs) String() string { var buffer bytes.Buffer for i, msg := range a { @@ -101,10 +108,7 @@ func NewWithConfig(config Config) *Engine { // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New() *Engine { - return NewWithConfig(Config{ - CacheSize: 1024, - Preallocated: 512, - }) + return NewWithConfig(DefaultConfig) } // Returns a Engine instance with the Logger and Recovery already attached. From 592af3026c09cf2110b3295806b903d6cf4f7be5 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Fri, 4 Jul 2014 19:44:07 +0200 Subject: [PATCH 05/67] README: gofmt the code examples --- README.md | 343 +++++++++++++++++++++++++++--------------------------- 1 file changed, 173 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index 53efb20..20a2a03 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,18 @@ import "github.com/gin-gonic/gin" #### Create most basic PING/PONG HTTP endpoint ```go +package main + import "github.com/gin-gonic/gin" func main() { - r := gin.Default() - r.GET("/ping", func(c *gin.Context){ - c.String(200, "pong") - }) - - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -60,17 +62,17 @@ func main() { ```go func main() { - // Creates a gin router + logger and recovery (crash-free) middlewares - r := gin.Default() - - r.GET("/someGet", getting) - r.POST("/somePost", posting) - r.PUT("/somePut", putting) - r.DELETE("/someDelete", deleting) - r.PATCH("/somePatch", patching) + // Creates a gin router + logger and recovery (crash-free) middlewares + r := gin.Default() - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + r.GET("/someGet", getting) + r.POST("/somePost", posting) + r.PUT("/somePut", putting) + r.DELETE("/someDelete", deleting) + r.PATCH("/somePatch", patching) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -78,16 +80,16 @@ func main() { ```go func main() { - r := gin.Default() - - r.GET("/user/:name", func(c *gin.Context) { - name := c.Params.ByName("name") - message := "Hello "+name - c.String(200, message) - }) + r := gin.Default() - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + r.GET("/user/:name", func(c *gin.Context) { + name := c.Params.ByName("name") + message := "Hello " + name + c.String(200, message) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -95,26 +97,26 @@ func main() { #### Grouping routes ```go func main() { - r := gin.Default() - - // Simple group: v1 - v1 := r.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := r.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } + r := gin.Default() - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + // Simple group: v1 + v1 := r.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := r.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -136,35 +138,35 @@ r := gin.Default() #### Using middlewares ```go func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middlewares - r.Use(gin.Logger()) - r.Use(gin.Recovery()) - - // Per route middlewares, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + // Creates a router without any middleware by default + r := gin.New() - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same than: - authorized := r.Group("/") - // per group middlewares! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + // Global middlewares + r.Use(gin.Logger()) + r.Use(gin.Recovery()) + + // Per route middlewares, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same than: + authorized := r.Group("/") + // per group middlewares! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -173,30 +175,30 @@ func main() { ```go type LoginJSON struct { - User string `json:"user" binding:"required"` - Password string `json:"password" binding:"required"` + User string `json:"user" binding:"required"` + Password string `json:"password" binding:"required"` } func main() { - r := gin.Default() - - r.POST("/login", func(c *gin.Context) { - var json LoginJSON - - // If EnsureBody returns false, it will write automatically the error - // in the HTTP stream and return a 400 error. If you want custom error - // handling you should use: c.ParseBody(interface{}) error - if c.EnsureBody(&json) { - if json.User=="manu" && json.Password=="123" { - c.JSON(200, gin.H{"status": "you are logged in"}) - }else{ - c.JSON(401, gin.H{"status": "unauthorized"}) - } - } - }) + r := gin.Default() - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + r.POST("/login", func(c *gin.Context) { + var json LoginJSON + + // If EnsureBody returns false, it will write automatically the error + // in the HTTP stream and return a 400 error. If you want custom error + // handling you should use: c.ParseBody(interface{}) error + if c.EnsureBody(&json) { + if json.User == "manu" && json.Password == "123" { + c.JSON(200, gin.H{"status": "you are logged in"}) + } else { + c.JSON(401, gin.H{"status": "unauthorized"}) + } + } + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -204,34 +206,34 @@ func main() { ```go func main() { - r := gin.Default() - - // gin.H is a shortcup for map[string]interface{} - r.GET("/someJSON", func(c *gin.Context) { - c.JSON(200, gin.H{"message": "hey", "status": 200}) - }) - - r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct - var msg struct { - Name string `json:"user"` - Message string - Number int - } - msg.Name = "Lena" - msg.Message = "hey" - msg.Number = 123 - // Note that msg.Name becomes "user" in the JSON - // Will output : {"user": "Lena", "Message": "hey", "Number": 123} - c.JSON(200, msg) - }) - - r.GET("/someXML", func(c *gin.Context) { - c.XML(200, gin.H{"message": "hey", "status": 200}) - }) + r := gin.Default() - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + // gin.H is a shortcup for map[string]interface{} + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "hey", "status": 200}) + }) + + r.GET("/moreJSON", func(c *gin.Context) { + // You also can use a struct + var msg struct { + Name string `json:"user"` + Message string + Number int + } + msg.Name = "Lena" + msg.Message = "hey" + msg.Number = 123 + // Note that msg.Name becomes "user" in the JSON + // Will output : {"user": "Lena", "Message": "hey", "Number": 123} + c.JSON(200, msg) + }) + + r.GET("/someXML", func(c *gin.Context) { + c.XML(200, gin.H{"message": "hey", "status": 200}) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -242,15 +244,15 @@ Using LoadHTMLTemplates() ```go func main() { - r := gin.Default() - r.LoadHTMLTemplates("templates/*") - r.GET("/index", func(c *gin.Context) { - obj := gin.H{"title": "Main website"} - c.HTML(200, "index.tmpl", obj) - }) + r := gin.Default() + r.LoadHTMLTemplates("templates/*") + r.GET("/index", func(c *gin.Context) { + obj := gin.H{"title": "Main website"} + c.HTML(200, "index.tmpl", obj) + }) - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -258,13 +260,14 @@ You can also use your own html template render ```go import "html/template" -func main() { - r := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - r.HTMLTemplates = html - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") +func main() { + r := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + r.HTMLTemplates = html + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -273,35 +276,35 @@ func main() { ```go func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - } + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + } } func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context){ - example := r.Get("example").(string) - - // it would print: "12345" - log.Println(example) - }) + r := gin.New() + r.Use(Logger()) - // Listen and server on 0.0.0.0:8080 - r.Run(":8080") + r.GET("/test", func(c *gin.Context) { + example := r.Get("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and server on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -314,23 +317,23 @@ Use `http.ListenAndServe()` directly, like this: ```go func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) + router := gin.Default() + http.ListenAndServe(":8080", router) } ``` or ```go func main() { - router := gin.Default() + router := gin.Default() - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() } ``` From 9634a387040757ef933b1fad98471ea81c622dda Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 4 Jul 2014 23:28:50 +0200 Subject: [PATCH 06/67] New bindings for JSON, XML and form parsing and validation --- binding/binding.go | 75 ++++++++++++++++++++++++++++++++++++++++------ deprecated.go | 17 +++++++++++ gin.go | 49 +++++++++++++++--------------- validation.go | 45 ---------------------------- 4 files changed, 107 insertions(+), 79 deletions(-) create mode 100644 deprecated.go delete mode 100644 validation.go diff --git a/binding/binding.go b/binding/binding.go index 667b3d0..cb831f3 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -3,32 +3,89 @@ package binding import ( "encoding/json" "encoding/xml" - "io" + "errors" + "net/http" + "reflect" + "strings" ) type ( Binding interface { - Bind(io.Reader, interface{}) error + Bind(*http.Request, interface{}) error } // JSON binding jsonBinding struct{} - // JSON binding + // XML binding xmlBinding struct{} + + // // form binding + formBinding struct{} ) var ( JSON = jsonBinding{} XML = xmlBinding{} + Form = formBinding{} // todo ) -func (_ jsonBinding) Bind(r io.Reader, obj interface{}) error { - decoder := json.NewDecoder(r) - return decoder.Decode(&obj) +func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error { + decoder := json.NewDecoder(req.Body) + if err := decoder.Decode(obj); err == nil { + return Validate(obj) + } else { + return err + } } -func (_ xmlBinding) Bind(r io.Reader, obj interface{}) error { - decoder := xml.NewDecoder(r) - return decoder.Decode(&obj) +func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error { + decoder := xml.NewDecoder(req.Body) + if err := decoder.Decode(obj); err == nil { + return Validate(obj) + } else { + return err + } +} + +func (_ formBinding) Bind(req *http.Request, obj interface{}) error { + return nil +} + +func Validate(obj interface{}) error { + + typ := reflect.TypeOf(obj) + val := reflect.ValueOf(obj) + + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldValue := val.Field(i).Interface() + zero := reflect.Zero(field.Type).Interface() + + // Validate nested and embedded structs (if pointer, only do so if not nil) + if field.Type.Kind() == reflect.Struct || + (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) { + if err := Validate(fieldValue); err != nil { + return err + } + } + + if strings.Index(field.Tag.Get("binding"), "required") > -1 { + if reflect.DeepEqual(zero, fieldValue) { + name := field.Name + if j := field.Tag.Get("json"); j != "" { + name = j + } else if f := field.Tag.Get("form"); f != "" { + name = f + } + return errors.New("Required " + name) + } + } + } + return nil } diff --git a/deprecated.go b/deprecated.go new file mode 100644 index 0000000..b629098 --- /dev/null +++ b/deprecated.go @@ -0,0 +1,17 @@ +package gin + +import ( + "github.com/gin-gonic/gin/binding" +) + +// DEPRECATED, use Bind() instead. +// Like ParseBody() but this method also writes a 400 error if the json is not valid. +func (c *Context) EnsureBody(item interface{}) bool { + return c.Bind(item) +} + +// DEPRECATED use bindings directly +// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. +func (c *Context) ParseBody(item interface{}) error { + return binding.JSON.Bind(c.Req, item) +} diff --git a/gin.go b/gin.go index 2c4d640..716d2f4 100644 --- a/gin.go +++ b/gin.go @@ -17,6 +17,11 @@ import ( const ( AbortIndex = math.MaxInt8 / 2 + MIMEJSON = "application/json" + MIMEHTML = "text/html" + MIMEXML = "application/xml" + MIMEXML2 = "text/xml" + MIMEPlain = "text/plain" ) type ( @@ -371,16 +376,13 @@ func (c *Context) Get(key string) interface{} { /******** ENCOGING MANAGEMENT********/ /************************************/ -// Like ParseBody() but this method also writes a 400 error if the json is not valid. -// DEPRECATED, use Bind() instead. -func (c *Context) EnsureBody(item interface{}) bool { - return c.Bind(item) -} - -// DEPRECATED use bindings directly -// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. -func (c *Context) ParseBody(item interface{}) error { - return binding.JSON.Bind(c.Req.Body, item) +func filterFlags(content string) string { + for i, a := range content { + if a == ' ' || a == ';' { + return content[:i] + } + } + return content } // This function checks the Content-Type to select a binding engine automatically, @@ -390,27 +392,24 @@ func (c *Context) ParseBody(item interface{}) error { // else --> returns an error // if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. func (c *Context) Bind(obj interface{}) bool { - var err error - switch c.Req.Header.Get("Content-Type") { - case "application/json": - err = binding.JSON.Bind(c.Req.Body, obj) - case "application/xml": - err = binding.XML.Bind(c.Req.Body, obj) + var b binding.Binding + ctype := filterFlags(c.Req.Header.Get("Content-Type")) + switch { + case c.Req.Method == "GET": + b = binding.Form + case ctype == MIMEJSON: + b = binding.JSON + case ctype == MIMEXML || ctype == MIMEXML2: + b = binding.XML default: - err = errors.New("unknown content-type: " + c.Req.Header.Get("Content-Type")) - } - if err == nil { - err = Validate(c, obj) - } - if err != nil { - c.Fail(400, err) + c.Fail(400, errors.New("unknown content-type: "+ctype)) return false } - return true + return c.BindWith(obj, b) } func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { - if err := b.Bind(c.Req.Body, obj); err != nil { + if err := b.Bind(c.Req, obj); err != nil { c.Fail(400, err) return false } diff --git a/validation.go b/validation.go deleted file mode 100644 index 501ee50..0000000 --- a/validation.go +++ /dev/null @@ -1,45 +0,0 @@ -package gin - -import ( - "errors" - "reflect" - "strings" -) - -func Validate(c *Context, obj interface{}) error { - - var err error - typ := reflect.TypeOf(obj) - val := reflect.ValueOf(obj) - - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - val = val.Elem() - } - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - fieldValue := val.Field(i).Interface() - zero := reflect.Zero(field.Type).Interface() - - // Validate nested and embedded structs (if pointer, only do so if not nil) - if field.Type.Kind() == reflect.Struct || - (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) { - err = Validate(c, fieldValue) - } - - if strings.Index(field.Tag.Get("binding"), "required") > -1 { - if reflect.DeepEqual(zero, fieldValue) { - name := field.Name - if j := field.Tag.Get("json"); j != "" { - name = j - } else if f := field.Tag.Get("form"); f != "" { - name = f - } - err = errors.New("Required " + name) - c.Error(err, "json validation") - } - } - } - return err -} From d90868e5bdc4091840bebe78c97ed923455abb21 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 02:44:32 +0200 Subject: [PATCH 07/67] Adds FORM bindings --- binding/binding.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/binding/binding.go b/binding/binding.go index cb831f3..c826073 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -6,6 +6,7 @@ import ( "errors" "net/http" "reflect" + "strconv" "strings" ) @@ -49,9 +50,107 @@ func (_ xmlBinding) Bind(req *http.Request, obj interface{}) error { } func (_ formBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); 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() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" { + structField := formStruct.Field(i) + if !structField.CanSet() { + continue + } + + inputValue, exists := form[inputFieldName] + if !exists { + continue + } + numElems := len(inputValue) + if structField.Kind() == reflect.Slice && numElems > 0 { + sliceOf := structField.Type().Elem().Kind() + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for i := 0; i < numElems; i++ { + if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil { + return err + } + } + formStruct.Elem().Field(i).Set(slice) + } else { + if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err + } + } + } + } return nil } +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.Bool: + if val == "" { + val = "false" + } + boolVal, err := strconv.ParseBool(val) + if err != nil { + return err + } else { + structField.SetBool(boolVal) + } + case reflect.Float32: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 32) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.Float64: + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } else { + structField.SetFloat(floatVal) + } + case reflect.String: + structField.SetString(val) + } + return nil +} + +// Don't pass in pointers to bind to. Can lead to bugs. See: +// https://github.com/codegangsta/martini-contrib/issues/40 +// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659 +func ensureNotPointer(obj interface{}) { + if reflect.TypeOf(obj).Kind() == reflect.Ptr { + panic("Pointers are not accepted as binding models") + } +} + func Validate(obj interface{}) error { typ := reflect.TypeOf(obj) From cde876395f3d297698938c1d82171aa61a94563d Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 02:51:14 +0200 Subject: [PATCH 08/67] Using constant values for Content-Type --- gin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index f470fc8..30b1afb 100644 --- a/gin.go +++ b/gin.go @@ -428,7 +428,7 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { // Serializes the given struct as JSON into the response body in a fast and efficient way. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { - c.Writer.Header().Set("Content-Type", "application/json") + c.Writer.Header().Set("Content-Type", MIMEJSON) if code >= 0 { c.Writer.WriteHeader(code) } @@ -442,7 +442,7 @@ func (c *Context) JSON(code int, obj interface{}) { // Serializes the given struct as XML into the response body in a fast and efficient way. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { - c.Writer.Header().Set("Content-Type", "application/xml") + c.Writer.Header().Set("Content-Type", MIMEXML) if code >= 0 { c.Writer.WriteHeader(code) } @@ -457,7 +457,7 @@ func (c *Context) XML(code int, obj interface{}) { // It also updates the HTTP code and sets the Content-Type as "text/html". // See http://golang.org/doc/articles/wiki/ func (c *Context) HTML(code int, name string, data interface{}) { - c.Writer.Header().Set("Content-Type", "text/html") + c.Writer.Header().Set("Content-Type", MIMEHTML) if code >= 0 { c.Writer.WriteHeader(code) } @@ -475,7 +475,7 @@ func (c *Context) String(code int, msg string) { if code >= 0 { c.Writer.WriteHeader(code) } - c.Writer.Header().Set("Content-Type", "text/plain") + c.Writer.Header().Set("Content-Type", MIMEPlain) c.Writer.Write([]byte(msg)) } From 3c2da979192acd7570d201999556e21076ed8b98 Mon Sep 17 00:00:00 2001 From: Chad Russell Date: Fri, 4 Jul 2014 20:51:25 -0400 Subject: [PATCH 09/67] Fix XML Marshal to work with gin.H --- gin.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gin.go b/gin.go index 197b98c..bf2338e 100644 --- a/gin.go +++ b/gin.go @@ -62,6 +62,17 @@ type ( } ) +// Allows type H to be used with xml.Marshal +func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + e.EncodeToken(start) + for key, value := range h { + elem := xml.StartElement{Name: xml.Name{Local: key}} + e.EncodeElement(value, elem) + } + e.EncodeToken(xml.EndElement{start.Name}) + return nil +} + func (a ErrorMsgs) String() string { var buffer bytes.Buffer for i, msg := range a { From f387bdda9e165aee139233a1a8a5cb79ea9ea84d Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 02:51:52 +0200 Subject: [PATCH 10/67] Fixes context.String() --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 30b1afb..fc49c83 100644 --- a/gin.go +++ b/gin.go @@ -472,10 +472,10 @@ func (c *Context) HTML(code int, name string, data interface{}) { // Writes the given string into the response body and sets the Content-Type to "text/plain". func (c *Context) String(code int, msg string) { + c.Writer.Header().Set("Content-Type", MIMEPlain) if code >= 0 { c.Writer.WriteHeader(code) } - c.Writer.Header().Set("Content-Type", MIMEPlain) c.Writer.Write([]byte(msg)) } From aadd33af2c277718817d90de5ef935ff06923815 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 02:53:51 +0200 Subject: [PATCH 11/67] Context.Data() takes content-type --- gin.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index fc49c83..a407ddb 100644 --- a/gin.go +++ b/gin.go @@ -480,7 +480,12 @@ func (c *Context) String(code int, msg string) { } // Writes some data into the body stream and updates the HTTP code. -func (c *Context) Data(code int, data []byte) { - c.Writer.WriteHeader(code) +func (c *Context) Data(code int, contentType string, data []byte) { + if len(contentType) > 0 { + c.Writer.Header().Set("Content-Type", contentType) + } + if code >= 0 { + c.Writer.WriteHeader(code) + } c.Writer.Write(data) } From c978efa42f3e072a1969c42bfa5305131d0a7935 Mon Sep 17 00:00:00 2001 From: Chad Russell Date: Fri, 4 Jul 2014 23:18:05 -0400 Subject: [PATCH 12/67] added error handling --- gin.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/gin.go b/gin.go index bf2338e..308a34c 100644 --- a/gin.go +++ b/gin.go @@ -64,12 +64,21 @@ type ( // Allows type H to be used with xml.Marshal func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - e.EncodeToken(start) - for key, value := range h { - elem := xml.StartElement{Name: xml.Name{Local: key}} - e.EncodeElement(value, elem) + if err := e.EncodeToken(start); err != nil { + return err + } + for key, value := range h { + elem := xml.StartElement{ + xml.Name{"", key}, + []xml.Attr{}, + } + if err = e.EncodeElement(value, elem); err != nil { + return err + } + } + if err = e.EncodeToken(xml.EndElement{start.Name}); err != nil { + return err } - e.EncodeToken(xml.EndElement{start.Name}) return nil } From ee1406bc4f57fea441dbcd6ad9907a13523cb59e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 16:14:43 +0200 Subject: [PATCH 13/67] ServeFiles should not be part of Gin core. We have to create a middleware to handle static files --- gin.go | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/gin.go b/gin.go index a407ddb..f150af3 100644 --- a/gin.go +++ b/gin.go @@ -148,20 +148,6 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { engine.reuseContext(c) } -// ServeFiles serves files from the given file system root. -// The path must end with "/*filepath", files are then served from the local -// path /defined/root/dir/*filepath. -// For example if root is "/etc" and *filepath is "passwd", the local file -// "/etc/passwd" would be served. -// 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 http.Dir: -// router.ServeFiles("/src/*filepath", http.Dir("/var/www")) -func (engine *Engine) ServeFiles(path string, root http.FileSystem) { - engine.router.ServeFiles(path, root) -} - // ServeHTTP makes the router implement the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { engine.router.ServeHTTP(w, req) @@ -471,12 +457,12 @@ func (c *Context) HTML(code int, name string, data interface{}) { } // Writes the given string into the response body and sets the Content-Type to "text/plain". -func (c *Context) String(code int, msg string) { +func (c *Context) String(code int, format string, values ...interface{}) { c.Writer.Header().Set("Content-Type", MIMEPlain) if code >= 0 { c.Writer.WriteHeader(code) } - c.Writer.Write([]byte(msg)) + c.Writer.Write([]byte(fmt.Sprintf(format, values...))) } // Writes some data into the body stream and updates the HTTP code. From f19cca070e962d3a7126feb97bd3f18a15bd821e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 19:23:40 +0200 Subject: [PATCH 14/67] Using c.Data() to write 404 error --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index f150af3..a4871f4 100644 --- a/gin.go +++ b/gin.go @@ -143,7 +143,7 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { c.Writer.setStatus(404) c.Next() if !c.Writer.Written() { - c.String(404, "404 page not found") + c.Data(404, MIMEPlain, []byte("404 page not found")) } engine.reuseContext(c) } From 8ad554dac3fd1e958bafc72f1dc9dd795dc6db05 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 19:27:33 +0200 Subject: [PATCH 15/67] Adds task list --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b035fdf..9c8a729 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,16 @@ Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcomin ##Roadmap for v0.2 -- Performance improments, reduce allocation and garbage collection overhead -- Fix bugs -- Ask our designer for a cool logo -- Add tons of unit tests and benchmarks -- Improve logging system -- Improve JSON/XML validation using bindings -- Improve XML support -- Improve documentation -- Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) -- Continuous integration +- [x] Performance improments, reduce allocation and garbage collection overhead +- [] Fix bugs +- [ ] Ask our designer for a cool logo +- [ ] Add tons of unit tests and benchmarks +- [x] Improve logging system +- [x] Improve JSON/XML validation using bindings +- [ ] Improve XML support +- [ ]Improve documentation +- [ ]Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) +- [x]Continuous integration From db259897f4a3bc488a627ce4925387f972601145 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 19:28:45 +0200 Subject: [PATCH 16/67] Adds task list (part2) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9c8a729..f920543 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcomin ##Roadmap for v0.2 - [x] Performance improments, reduce allocation and garbage collection overhead -- [] Fix bugs +- [ ] Fix bugs - [ ] Ask our designer for a cool logo - [ ] Add tons of unit tests and benchmarks - [x] Improve logging system - [x] Improve JSON/XML validation using bindings - [ ] Improve XML support -- [ ]Improve documentation -- [ ]Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) -- [x]Continuous integration +- [ ] Improve documentation +- [ ] Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) +- [x] Continuous integration From 027341c80b63905372b4c92584399a9673983d18 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sat, 5 Jul 2014 19:31:26 +0200 Subject: [PATCH 17/67] Adds cache pressure in logger --- logger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logger.go b/logger.go index dde0d5d..a593b5f 100644 --- a/logger.go +++ b/logger.go @@ -48,10 +48,11 @@ func Logger() HandlerFunc { color = red } latency := time.Since(start) - logger.Printf("[GIN] %v |%s %3d %s| %12v | %3s %s\n", + logger.Printf("[GIN] %v |%s %3d %s| %12v | %3.1f%% | %3s | %s\n", time.Now().Format("2006/01/02 - 15:04:05"), color, c.Writer.Status(), reset, latency, + c.Engine.CacheStress()*100, c.Req.Method, c.Req.URL.Path, ) From f8d85c1b4de8a6987446e984885fc111ae4a001b Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 6 Jul 2014 18:25:17 +0200 Subject: [PATCH 18/67] Fixes MarshalXML() and renames initial "H" tag to "map". --- gin.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index cc37508..33459cb 100644 --- a/gin.go +++ b/gin.go @@ -83,6 +83,7 @@ var ( // Allows type H to be used with xml.Marshal func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name = xml.Name{"", "map"} if err := e.EncodeToken(start); err != nil { return err } @@ -91,11 +92,11 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { xml.Name{"", key}, []xml.Attr{}, } - if err = e.EncodeElement(value, elem); err != nil { + if err := e.EncodeElement(value, elem); err != nil { return err } } - if err = e.EncodeToken(xml.EndElement{start.Name}); err != nil { + if err := e.EncodeToken(xml.EndElement{start.Name}); err != nil { return err } return nil From c1775e85cc39bbc32f8433e7971fca01bdbe031e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 6 Jul 2014 18:26:40 +0200 Subject: [PATCH 19/67] Adds source IP in built-in logger --- logger.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/logger.go b/logger.go index a593b5f..7bd87b7 100644 --- a/logger.go +++ b/logger.go @@ -1,7 +1,6 @@ package gin import ( - "fmt" "log" "os" "time" @@ -27,7 +26,9 @@ var ( ) func Logger() HandlerFunc { - logger := log.New(os.Stdout, "", 0) + stdlogger := log.New(os.Stdout, "", 0) + //errlogger := log.New(os.Stderr, "", 0) + return func(c *Context) { // Start timer start := time.Now() @@ -35,6 +36,18 @@ func Logger() HandlerFunc { // Process request c.Next() + // save the IP of the requester + requester := c.Req.Header.Get("X-Real-IP") + // if the requester-header is empty, check the forwarded-header + if requester == "" { + requester = c.Req.Header.Get("X-Forwarded-For") + } + + // if the requester is still empty, use the hard-coded address from the socket + if requester == "" { + requester = c.Req.RemoteAddr + } + var color string code := c.Writer.Status() switch { @@ -48,17 +61,18 @@ func Logger() HandlerFunc { color = red } latency := time.Since(start) - logger.Printf("[GIN] %v |%s %3d %s| %12v | %3.1f%% | %3s | %s\n", + stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %3.1f%% | %s %4s %s\n", time.Now().Format("2006/01/02 - 15:04:05"), color, c.Writer.Status(), reset, latency, c.Engine.CacheStress()*100, + requester, c.Req.Method, c.Req.URL.Path, ) // Calculate resolution time if len(c.Errors) > 0 { - fmt.Println(c.Errors.String()) + stdlogger.Println(c.Errors.String()) } } } From 99cc7ca362c41c63f3b2237df01e782cfa7fbef6 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 6 Jul 2014 18:41:07 +0200 Subject: [PATCH 20/67] Go1.1 is not supported anymore --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57bd4a7..1b1a9d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.1 - 1.2 - 1.3 - tip From 31417dbc63643fd26c05302f29713728a1ac947d Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 6 Jul 2014 18:57:58 +0200 Subject: [PATCH 21/67] Updates task list. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f920543..6ebc4ac 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,15 @@ Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcomin ##Roadmap for v0.2 - [x] Performance improments, reduce allocation and garbage collection overhead -- [ ] Fix bugs +- [x] Fix bugs - [ ] Ask our designer for a cool logo -- [ ] Add tons of unit tests and benchmarks +- [ ] Add tons of unit tests +- [ ] Add internal benchmarks suite - [x] Improve logging system - [x] Improve JSON/XML validation using bindings -- [ ] Improve XML support +- [x] Improve XML support - [ ] Improve documentation -- [ ] Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) +- [ ] Add more cool middlewares, for example redis catching (this also helps developers to understand the framework). - [x] Continuous integration From 3e4033673eb26f63e1b38b3870afc8e1fdc31469 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 6 Jul 2014 21:09:23 +0200 Subject: [PATCH 22/67] Using sync.Pool instead of a channel --- gin.go | 80 ++++++++++++------------------------------------------- logger.go | 3 +-- 2 files changed, 18 insertions(+), 65 deletions(-) diff --git a/gin.go b/gin.go index 33459cb..8521654 100644 --- a/gin.go +++ b/gin.go @@ -13,6 +13,7 @@ import ( "math" "net/http" "path" + "sync" ) const ( @@ -37,11 +38,6 @@ type ( ErrorMsgs []ErrorMsg - Config struct { - CacheSize int - Preallocated int - } - // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. Context struct { @@ -68,19 +64,12 @@ type ( Engine struct { *RouterGroup HTMLTemplates *template.Template - cache chan *Context + cache sync.Pool handlers404 []HandlerFunc router *httprouter.Router } ) -var ( - DefaultConfig = Config{ - CacheSize: 1024, - Preallocated: 512, - } -) - // Allows type H to be used with xml.Marshal func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{"", "map"} @@ -112,32 +101,19 @@ func (a ErrorMsgs) String() string { return buffer.String() } -func NewWithConfig(config Config) *Engine { - if config.CacheSize < 2 { - panic("CacheSize must be at least 2") - } - if config.Preallocated > config.CacheSize { - panic("Preallocated must be less or equal to CacheSize") - } +// Returns a new blank Engine instance without any middleware attached. +// The most basic configuration +func New() *Engine { engine := &Engine{} engine.RouterGroup = &RouterGroup{nil, "/", nil, engine} engine.router = httprouter.New() engine.router.NotFound = engine.handle404 - engine.cache = make(chan *Context, config.CacheSize) - - // Fill it with empty contexts - for i := 0; i < config.Preallocated; i++ { - engine.cache <- &Context{Engine: engine, Writer: &responseWriter{}} + engine.cache.New = func() interface{} { + return &Context{Engine: engine, Writer: &responseWriter{}} } return engine } -// Returns a new blank Engine instance without any middleware attached. -// The most basic configuration -func New() *Engine { - return NewWithConfig(DefaultConfig) -} - // Returns a Engine instance with the Logger and Recovery already attached. func Default() *Engine { engine := New() @@ -154,10 +130,6 @@ func (engine *Engine) NotFound404(handlers ...HandlerFunc) { engine.handlers404 = handlers } -func (engine *Engine) CacheStress() float32 { - return 1.0 - float32(len(engine.cache))/float32(cap(engine.cache)) -} - func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { handlers := engine.combineHandlers(engine.handlers404) c := engine.createContext(w, req, nil, handlers) @@ -166,7 +138,7 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { if !c.Writer.Written() { c.Data(404, MIMEPlain, []byte("404 page not found")) } - engine.reuseContext(c) + engine.cache.Put(c) } // ServeHTTP makes the router implement the http.Handler interface. @@ -185,32 +157,14 @@ func (engine *Engine) Run(addr string) { /************************************/ func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { - select { - case c := <-engine.cache: - c.Writer.reset(w) - c.Req = req - c.Params = params - c.handlers = handlers - c.Keys = nil - c.index = -1 - return c - default: - return &Context{ - Writer: &responseWriter{w, -1, false}, - Req: req, - Params: params, - handlers: handlers, - index: -1, - Engine: engine, - } - } -} - -func (engine *Engine) reuseContext(c *Context) { - select { - case engine.cache <- c: - default: - } + c := engine.cache.Get().(*Context) + c.Writer.reset(w) + c.Req = req + c.Params = params + c.handlers = handlers + c.Keys = nil + c.index = -1 + return c } // Adds middlewares to the group, see example code in github. @@ -246,7 +200,7 @@ func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) { 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() - group.engine.reuseContext(c) + group.engine.cache.Put(c) }) } diff --git a/logger.go b/logger.go index 7bd87b7..3120cbe 100644 --- a/logger.go +++ b/logger.go @@ -61,11 +61,10 @@ func Logger() HandlerFunc { color = red } latency := time.Since(start) - stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %3.1f%% | %s %4s %s\n", + stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n", time.Now().Format("2006/01/02 - 15:04:05"), color, c.Writer.Status(), reset, latency, - c.Engine.CacheStress()*100, requester, c.Req.Method, c.Req.URL.Path, ) From 55d6bb7a683d41442ff100becbb4ae95d3e6b241 Mon Sep 17 00:00:00 2001 From: dickeyxxx Date: Sun, 6 Jul 2014 15:21:40 -0700 Subject: [PATCH 23/67] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53efb20..2817ae7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcomin - Improve JSON/XML validation using bindings - Improve XML support - Improve documentation -- Add more cool middlewares, for example redis catching (this also helps developers to understand the framework) +- Add more cool middlewares, for example redis caching (this also helps developers to understand the framework) - Continuous integration From b5ddd484de8ff3a6ebf4ef2295a1013db1ad3156 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Mon, 7 Jul 2014 03:04:06 +0200 Subject: [PATCH 24/67] Timestamp is calculated once --- logger.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logger.go b/logger.go index 7bd87b7..ff843a1 100644 --- a/logger.go +++ b/logger.go @@ -60,9 +60,10 @@ func Logger() HandlerFunc { default: color = red } - latency := time.Since(start) + end := time.Now() + latency := end.Sub(start) stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %3.1f%% | %s %4s %s\n", - time.Now().Format("2006/01/02 - 15:04:05"), + end.Format("2006/01/02 - 15:04:05"), color, c.Writer.Status(), reset, latency, c.Engine.CacheStress()*100, From fc494c80948e203f6b0e5a960d5acb347b9be6c2 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 7 Jul 2014 11:58:22 +0800 Subject: [PATCH 25/67] Fix READEME.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 850ee12..2bd6eb0 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ func main() { r.Use(Logger()) r.GET("/test", func(c *gin.Context){ - example := r.Get("example").(string) + example := c.Get("example").(string) // it would print: "12345" log.Println(example) From 3295c6e9c47878cd3682325681a8e148d23bbb4c Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 8 Jul 2014 00:16:41 +0200 Subject: [PATCH 26/67] Improves error management --- gin.go | 54 +++++++++++++++++++++++++++++++++++++++--------------- logger.go | 7 ++++++- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/gin.go b/gin.go index 33459cb..d6c7a6a 100644 --- a/gin.go +++ b/gin.go @@ -24,18 +24,25 @@ const ( MIMEPlain = "text/plain" ) +const ( + ErrorTypeInternal = 1 << iota + ErrorTypeExternal = 1 << iota + ErrorTypeAll = 0xffffffff +) + type ( HandlerFunc func(*Context) H map[string]interface{} // Used internally to collect errors that occurred during an http request. - ErrorMsg struct { + errorMsg struct { Err string `json:"error"` + Type uint32 `json:"-"` Meta interface{} `json:"meta"` } - ErrorMsgs []ErrorMsg + errorMsgs []errorMsg Config struct { CacheSize int @@ -48,7 +55,7 @@ type ( Req *http.Request Writer ResponseWriter Keys map[string]interface{} - Errors ErrorMsgs + Errors errorMsgs Params httprouter.Params Engine *Engine handlers []HandlerFunc @@ -102,13 +109,25 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return nil } -func (a ErrorMsgs) String() string { +func (a errorMsgs) ByType(typ uint32) errorMsgs { + if len(a) == 0 { + return a + } + result := make(errorMsgs, 0, len(a)) + for _, msg := range a { + if msg.Type&typ > 0 { + result = append(result, msg) + } + } + return result +} + +func (a errorMsgs) String() string { var buffer bytes.Buffer for i, msg := range a { text := fmt.Sprintf("Error #%02d: %s \n Meta: %v\n", (i + 1), msg.Err, msg.Meta) buffer.WriteString(text) } - buffer.WriteString("\n") return buffer.String() } @@ -336,14 +355,19 @@ func (c *Context) Fail(code int, err error) { c.Abort(code) } +func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) { + c.Errors = append(c.Errors, errorMsg{ + Err: err.Error(), + Type: typ, + Meta: meta, + }) +} + // Attaches an error to the current context. The error is pushed to a list of errors. // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. func (c *Context) Error(err error, meta interface{}) { - c.Errors = append(c.Errors, ErrorMsg{ - Err: err.Error(), - Meta: meta, - }) + c.ErrorTyped(err, ErrorTypeExternal, meta) } func (c *Context) LastError() error { @@ -441,8 +465,8 @@ func (c *Context) JSON(code int, obj interface{}) { } encoder := json.NewEncoder(c.Writer) if err := encoder.Encode(obj); err != nil { - c.Error(err, obj) - http.Error(c.Writer, err.Error(), 500) + c.ErrorTyped(err, ErrorTypeInternal, obj) + c.Abort(500) } } @@ -455,8 +479,8 @@ func (c *Context) XML(code int, obj interface{}) { } encoder := xml.NewEncoder(c.Writer) if err := encoder.Encode(obj); err != nil { - c.Error(err, obj) - http.Error(c.Writer, err.Error(), 500) + c.ErrorTyped(err, ErrorTypeInternal, obj) + c.Abort(500) } } @@ -469,11 +493,11 @@ func (c *Context) HTML(code int, name string, data interface{}) { c.Writer.WriteHeader(code) } if err := c.Engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { - c.Error(err, map[string]interface{}{ + c.ErrorTyped(err, ErrorTypeInternal, H{ "name": name, "data": data, }) - http.Error(c.Writer, err.Error(), 500) + c.Abort(500) } } diff --git a/logger.go b/logger.go index ff843a1..f7d41d7 100644 --- a/logger.go +++ b/logger.go @@ -7,10 +7,15 @@ import ( ) func ErrorLogger() HandlerFunc { + return ErrorLoggerT(ErrorTypeAll) +} + +func ErrorLoggerT(typ uint32) HandlerFunc { return func(c *Context) { c.Next() - if len(c.Errors) > 0 { + errs := c.Errors.ByType(typ) + if len(errs) > 0 { // -1 status code = do not change current one c.JSON(-1, c.Errors) } From 058201713b5badcabe7a5f200bb9008b590e3307 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 8 Jul 2014 01:09:48 +0200 Subject: [PATCH 27/67] New static file serving --- deprecated.go | 16 ++++++++++++++++ gin.go | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/deprecated.go b/deprecated.go index b629098..ca572d7 100644 --- a/deprecated.go +++ b/deprecated.go @@ -2,6 +2,7 @@ package gin import ( "github.com/gin-gonic/gin/binding" + "net/http" ) // DEPRECATED, use Bind() instead. @@ -15,3 +16,18 @@ func (c *Context) EnsureBody(item interface{}) bool { func (c *Context) ParseBody(item interface{}) error { return binding.JSON.Bind(c.Req, item) } + +// DEPRECATED use gin.Static() instead +// ServeFiles serves files from the given file system root. +// The path must end with "/*filepath", files are then served from the local +// path /defined/root/dir/*filepath. +// For example if root is "/etc" and *filepath is "passwd", the local file +// "/etc/passwd" would be served. +// 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 http.Dir: +// router.ServeFiles("/src/*filepath", http.Dir("/var/www")) +func (engine *Engine) ServeFiles(path string, root http.FileSystem) { + engine.router.ServeFiles(path, root) +} diff --git a/gin.go b/gin.go index d6c7a6a..8cae8ba 100644 --- a/gin.go +++ b/gin.go @@ -304,6 +304,24 @@ 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) { + p = path.Join(p, "/*filepath") + fileServer := http.FileServer(http.Dir(root)) + + group.GET(p, func(c *Context) { + original := c.Req.URL.Path + c.Req.URL.Path = c.Params.ByName("filepath") + fileServer.ServeHTTP(c.Writer, c.Req) + c.Req.URL.Path = original + }) +} + func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc { s := len(group.Handlers) + len(handlers) h := make([]HandlerFunc, 0, s) From b6be4ba58cad0863b712329b2b7be69f4c8e7e81 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 8 Jul 2014 01:16:51 +0200 Subject: [PATCH 28/67] Updates travis. Only compile for Go1.3 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1b1a9d0..98b4346 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: go go: - - 1.2 - 1.3 - tip From 6fa85864edb1680633342759b5f536e21d3a79c1 Mon Sep 17 00:00:00 2001 From: mopemoepe Date: Tue, 8 Jul 2014 15:53:10 +0900 Subject: [PATCH 29/67] Manage Dependencies With Godep --- Godeps/Godeps.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Godeps/Godeps.json diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..a224fcd --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,10 @@ +{ + "ImportPath": "github.com/gin-gonic/gin", + "GoVersion": "devel +2699961d1143 Wed Jun 25 09:57:48 2014 -0400", + "Deps": [ + { + "ImportPath": "github.com/julienschmidt/httprouter", + "Rev": "7deadb6844d2c6ff1dfb812eaa439b87cdaedf20" + } + ] +} From fadb06968fec7462f4c2ecfa74e6a1b2509470d8 Mon Sep 17 00:00:00 2001 From: mopemoepe Date: Tue, 8 Jul 2014 17:53:55 +0900 Subject: [PATCH 30/67] Fix go version --- Godeps/Godeps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a224fcd..d963b7e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/gin-gonic/gin", - "GoVersion": "devel +2699961d1143 Wed Jun 25 09:57:48 2014 -0400", + "GoVersion": "go1.3", "Deps": [ { "ImportPath": "github.com/julienschmidt/httprouter", From cfb3ce5aac1934640d0854264c3f5f05a57dbe27 Mon Sep 17 00:00:00 2001 From: mopemoepe Date: Tue, 8 Jul 2014 18:41:12 +0900 Subject: [PATCH 31/67] Ingnore godep workspace --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08e8496 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Godeps/* +!Godep.json From 6c8c90115d513f4b008f04dcc9aae4c8ffe0bff3 Mon Sep 17 00:00:00 2001 From: Javier Provecho Date: Tue, 8 Jul 2014 11:55:20 +0200 Subject: [PATCH 32/67] Update README.md Example of Catch-All parameters. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index acba80f..63c67e8 100644 --- a/README.md +++ b/README.md @@ -84,20 +84,22 @@ func main() { ```go func main() { r := gin.Default() - + + // This handler will match /user/john but will not match neither /user/ or /user r.GET("/user/:name", func(c *gin.Context) { name := c.Params.ByName("name") message := "Hello "+name c.String(200, message) }) - r.GET("/user/:name/:action", func(c *gin.Context) { + // However, this one will match /user/john and also /user/john/send + r.GET("/user/:name/*action", func(c *gin.Context) { name := c.Params.ByName("name") action := c.Params.ByName("action") message := name + " is " + action c.String(200, message) }) - + // Listen and server on 0.0.0.0:8080 r.Run(":8080") } From c224bf82111883dbe354edf9376642f615b7248e Mon Sep 17 00:00:00 2001 From: Javier Provecho Date: Tue, 8 Jul 2014 12:20:05 +0200 Subject: [PATCH 33/67] Update .gitignore Fixed PR #56. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 08e8496..96c135f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ Godeps/* -!Godep.json +!Godeps/Godeps.json From 9880758ddbc807d38625b4d9fbbb88cea8575325 Mon Sep 17 00:00:00 2001 From: Dmitry Sedykh Date: Tue, 8 Jul 2014 16:07:59 +0400 Subject: [PATCH 34/67] No repeat call c.Writer.Status() --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index da35ffb..5c018a4 100644 --- a/logger.go +++ b/logger.go @@ -69,7 +69,7 @@ func Logger() HandlerFunc { latency := end.Sub(start) stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n", end.Format("2006/01/02 - 15:04:05"), - color, c.Writer.Status(), reset, + color, code, reset, latency, requester, c.Req.Method, c.Req.URL.Path, From f6971041b05606445e99d070bf9e60eba9875962 Mon Sep 17 00:00:00 2001 From: kyledinh Date: Mon, 7 Jul 2014 18:29:38 -0700 Subject: [PATCH 35/67] RunTLS() for http.ListenAndServeTLS run the engine for https Fixes PR #52 --- gin.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gin.go b/gin.go index a06c13a..1c4b019 100644 --- a/gin.go +++ b/gin.go @@ -171,6 +171,12 @@ func (engine *Engine) Run(addr string) { } } +func (engine *Engine) RunTLS(addr string, cert string, key string) { + if err := http.ListenAndServeTLS(addr, cert, key, engine); err != nil { + panic(err) + } +} + /************************************/ /********** ROUTES GROUPING *********/ /************************************/ From a235e0fb32df61edfa9859548c18ca925487cd7f Mon Sep 17 00:00:00 2001 From: Dmitry Sedykh Date: Tue, 8 Jul 2014 16:10:27 +0400 Subject: [PATCH 36/67] Add HTML POST Form support in Bind --- gin.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/gin.go b/gin.go index a06c13a..55fa833 100644 --- a/gin.go +++ b/gin.go @@ -17,12 +17,13 @@ import ( ) const ( - AbortIndex = math.MaxInt8 / 2 - MIMEJSON = "application/json" - MIMEHTML = "text/html" - MIMEXML = "application/xml" - MIMEXML2 = "text/xml" - MIMEPlain = "text/plain" + 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" ) const ( @@ -407,7 +408,7 @@ func (c *Context) Bind(obj interface{}) bool { var b binding.Binding ctype := filterFlags(c.Req.Header.Get("Content-Type")) switch { - case c.Req.Method == "GET": + case c.Req.Method == "GET" || ctype == MIMEPOSTForm: b = binding.Form case ctype == MIMEJSON: b = binding.JSON From 2078ecd8e1ce28393b58642359df5ac60e2617e5 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 8 Jul 2014 16:57:04 +0200 Subject: [PATCH 37/67] Renaming Context.Req to Context.Request --- auth.go | 2 +- deprecated.go | 2 +- gin.go | 18 +++++++++--------- logger.go | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/auth.go b/auth.go index a8720c4..0a51295 100644 --- a/auth.go +++ b/auth.go @@ -74,7 +74,7 @@ func BasicAuth(accounts Accounts) HandlerFunc { } return func(c *Context) { // Search user in the slice of allowed credentials - user := searchCredential(pairs, c.Req.Header.Get("Authorization")) + user := searchCredential(pairs, c.Request.Header.Get("Authorization")) if len(user) == 0 { // Credentials doesn't match, we return 401 Unauthorized and abort request. c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"") diff --git a/deprecated.go b/deprecated.go index ca572d7..7b3018f 100644 --- a/deprecated.go +++ b/deprecated.go @@ -14,7 +14,7 @@ func (c *Context) EnsureBody(item interface{}) bool { // DEPRECATED use bindings directly // Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer. func (c *Context) ParseBody(item interface{}) error { - return binding.JSON.Bind(c.Req, item) + return binding.JSON.Bind(c.Request, item) } // DEPRECATED use gin.Static() instead diff --git a/gin.go b/gin.go index 40f92e0..00b07d5 100644 --- a/gin.go +++ b/gin.go @@ -49,7 +49,7 @@ type ( // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. Context struct { - Req *http.Request + Request *http.Request Writer ResponseWriter Keys map[string]interface{} Errors errorMsgs @@ -185,7 +185,7 @@ func (engine *Engine) RunTLS(addr string, cert string, key string) { func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { c := engine.cache.Get().(*Context) c.Writer.reset(w) - c.Req = req + c.Request = req c.Params = params c.handlers = handlers c.Keys = nil @@ -276,10 +276,10 @@ func (group *RouterGroup) Static(p, root string) { fileServer := http.FileServer(http.Dir(root)) group.GET(p, func(c *Context) { - original := c.Req.URL.Path - c.Req.URL.Path = c.Params.ByName("filepath") - fileServer.ServeHTTP(c.Writer, c.Req) - c.Req.URL.Path = original + original := c.Request.URL.Path + c.Request.URL.Path = c.Params.ByName("filepath") + fileServer.ServeHTTP(c.Writer, c.Request) + c.Request.URL.Path = original }) } @@ -412,9 +412,9 @@ func filterFlags(content string) string { // if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. func (c *Context) Bind(obj interface{}) bool { var b binding.Binding - ctype := filterFlags(c.Req.Header.Get("Content-Type")) + ctype := filterFlags(c.Request.Header.Get("Content-Type")) switch { - case c.Req.Method == "GET" || ctype == MIMEPOSTForm: + case c.Request.Method == "GET" || ctype == MIMEPOSTForm: b = binding.Form case ctype == MIMEJSON: b = binding.JSON @@ -428,7 +428,7 @@ func (c *Context) Bind(obj interface{}) bool { } func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { - if err := b.Bind(c.Req, obj); err != nil { + if err := b.Bind(c.Request, obj); err != nil { c.Fail(400, err) return false } diff --git a/logger.go b/logger.go index 5c018a4..e141778 100644 --- a/logger.go +++ b/logger.go @@ -42,15 +42,15 @@ func Logger() HandlerFunc { c.Next() // save the IP of the requester - requester := c.Req.Header.Get("X-Real-IP") + requester := c.Request.Header.Get("X-Real-IP") // if the requester-header is empty, check the forwarded-header if requester == "" { - requester = c.Req.Header.Get("X-Forwarded-For") + requester = c.Request.Header.Get("X-Forwarded-For") } // if the requester is still empty, use the hard-coded address from the socket if requester == "" { - requester = c.Req.RemoteAddr + requester = c.Request.RemoteAddr } var color string @@ -72,7 +72,7 @@ func Logger() HandlerFunc { color, code, reset, latency, requester, - c.Req.Method, c.Req.URL.Path, + c.Request.Method, c.Request.URL.Path, ) // Calculate resolution time From 6c31570472b3b50ae638ceb02a68b8b3123e3c47 Mon Sep 17 00:00:00 2001 From: Lucas Clemente Date: Tue, 8 Jul 2014 00:28:39 +0200 Subject: [PATCH 38/67] work around path.Join removing trailing slashes from routes --- gin.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index a06c13a..712230a 100644 --- a/gin.go +++ b/gin.go @@ -191,10 +191,20 @@ func (group *RouterGroup) Use(middlewares ...HandlerFunc) { group.Handlers = append(group.Handlers, middlewares...) } +func joinGroupPath(elems ...string) string { + joined := path.Join(elems...) + lastComponent := elems[len(elems)-1] + // Append a '/' if the last component had one, but only if it's not there already + if len(lastComponent) > 0 && lastComponent[len(lastComponent)-1] == '/' && joined[len(joined)-1] != '/' { + return joined + "/" + } + return joined +} + // 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 := path.Join(group.prefix, component) + prefix := joinGroupPath(group.prefix, component) return &RouterGroup{ Handlers: group.combineHandlers(handlers), parent: group, @@ -214,7 +224,7 @@ func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *Rout // 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 = path.Join(group.prefix, p) + p = joinGroupPath(group.prefix, p) handlers = group.combineHandlers(handlers) group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { c := group.engine.createContext(w, req, params, handlers) From e2242b59e6b855b23d701f975a3b57519191eecb Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 13 Jul 2014 00:17:01 +0200 Subject: [PATCH 39/67] Fixes "Can't unmarshal JSON array with #63" --- binding/binding.go | 57 +++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index c826073..bb6cbde 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -152,7 +152,6 @@ func ensureNotPointer(obj interface{}) { } func Validate(obj interface{}) error { - typ := reflect.TypeOf(obj) val := reflect.ValueOf(obj) @@ -161,30 +160,46 @@ func Validate(obj interface{}) error { val = val.Elem() } - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - fieldValue := val.Field(i).Interface() - zero := reflect.Zero(field.Type).Interface() + switch typ.Kind() { + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) - // Validate nested and embedded structs (if pointer, only do so if not nil) - if field.Type.Kind() == reflect.Struct || - (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) { - if err := Validate(fieldValue); err != nil { + // Allow ignored fields in the struct + if field.Tag.Get("form") == "-" { + continue + } + + fieldValue := val.Field(i).Interface() + zero := reflect.Zero(field.Type).Interface() + + if strings.Index(field.Tag.Get("binding"), "required") > -1 { + fieldType := field.Type.Kind() + if fieldType == reflect.Struct { + err := Validate(fieldValue) + if err != nil { + return err + } + } else if reflect.DeepEqual(zero, fieldValue) { + return errors.New("Required " + field.Name) + } else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct { + err := Validate(fieldValue) + if err != nil { + return err + } + } + } + } + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + fieldValue := val.Index(i).Interface() + err := Validate(fieldValue) + if err != nil { return err } } - - if strings.Index(field.Tag.Get("binding"), "required") > -1 { - if reflect.DeepEqual(zero, fieldValue) { - name := field.Name - if j := field.Tag.Get("json"); j != "" { - name = j - } else if f := field.Tag.Get("form"); f != "" { - name = f - } - return errors.New("Required " + name) - } - } + default: + return nil } return nil } From f63a354b40991d77b0488553b5a54f2d320ee0e8 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 13 Jul 2014 00:18:33 +0200 Subject: [PATCH 40/67] Normal log and error log are printed in the same call. --- gin.go | 3 +++ logger.go | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index 00b07d5..020b66d 100644 --- a/gin.go +++ b/gin.go @@ -113,6 +113,9 @@ func (a errorMsgs) ByType(typ uint32) errorMsgs { } func (a errorMsgs) String() string { + if len(a) == 0 { + return "" + } var buffer bytes.Buffer for i, msg := range a { text := fmt.Sprintf("Error #%02d: %s \n Meta: %v\n", (i + 1), msg.Err, msg.Meta) diff --git a/logger.go b/logger.go index e141778..5a67f7c 100644 --- a/logger.go +++ b/logger.go @@ -67,17 +67,13 @@ func Logger() HandlerFunc { } end := time.Now() latency := end.Sub(start) - stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n", + stdlogger.Printf("[GIN] %v |%s %3d %s| %12v | %s %4s %s\n%s", end.Format("2006/01/02 - 15:04:05"), color, code, reset, latency, requester, c.Request.Method, c.Request.URL.Path, + c.Errors.String(), ) - - // Calculate resolution time - if len(c.Errors) > 0 { - stdlogger.Println(c.Errors.String()) - } } } From 94bc35bb2db753c4af7b43595569938a84d8f57e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Sun, 13 Jul 2014 16:53:46 +0200 Subject: [PATCH 41/67] Using keyed initialization to fix app-engine integration --- gin.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index 020b66d..70108df 100644 --- a/gin.go +++ b/gin.go @@ -80,20 +80,23 @@ type ( // Allows type H to be used with xml.Marshal func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Name = xml.Name{"", "map"} + start.Name = xml.Name{ + Space: "", + Local: "map", + } if err := e.EncodeToken(start); err != nil { return err } for key, value := range h { elem := xml.StartElement{ - xml.Name{"", key}, - []xml.Attr{}, + Name: xml.Name{Space: "", Local: key}, + Attr: []xml.Attr{}, } if err := e.EncodeElement(value, elem); err != nil { return err } } - if err := e.EncodeToken(xml.EndElement{start.Name}); err != nil { + if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { return err } return nil From 5ea7a92267a38b12e1315deda60fbfc636da7fa1 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Tue, 15 Jul 2014 17:41:56 +0200 Subject: [PATCH 42/67] Adds flexible render system --- deprecated.go | 5 ++++ gin.go | 73 ++++++++++++++++++++---------------------------- render/render.go | 69 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 42 deletions(-) create mode 100644 render/render.go diff --git a/deprecated.go b/deprecated.go index 7b3018f..e20e1f2 100644 --- a/deprecated.go +++ b/deprecated.go @@ -31,3 +31,8 @@ func (c *Context) ParseBody(item interface{}) error { func (engine *Engine) ServeFiles(path string, root http.FileSystem) { engine.router.ServeFiles(path, root) } + +// DEPRECATED use gin.LoadHTMLGlob() or gin.LoadHTMLFiles() instead +func (engine *Engine) LoadHTMLTemplates(pattern string) { + engine.LoadHTMLGlob(pattern) +} diff --git a/gin.go b/gin.go index 70108df..aba3298 100644 --- a/gin.go +++ b/gin.go @@ -2,11 +2,11 @@ package gin import ( "bytes" - "encoding/json" "encoding/xml" "errors" "fmt" "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/render" "github.com/julienschmidt/httprouter" "html/template" "log" @@ -71,10 +71,10 @@ type ( // Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares. Engine struct { *RouterGroup - HTMLTemplates *template.Template - cache sync.Pool - handlers404 []HandlerFunc - router *httprouter.Router + HTMLRender render.Render + cache sync.Pool + handlers404 []HandlerFunc + router *httprouter.Router } ) @@ -147,8 +147,20 @@ func Default() *Engine { return engine } -func (engine *Engine) LoadHTMLTemplates(pattern string) { - engine.HTMLTemplates = template.Must(template.ParseGlob(pattern)) +func (engine *Engine) LoadHTMLGlob(pattern string) { + templ := template.Must(template.ParseGlob(pattern)) + engine.SetHTTPTemplate(templ) +} + +func (engine *Engine) LoadHTMLFiles(files ...string) { + templ := template.Must(template.ParseFiles(files...)) + engine.SetHTTPTemplate(templ) +} + +func (engine *Engine) SetHTTPTemplate(templ *template.Template) { + engine.HTMLRender = render.HTMLRender{ + Template: templ, + } } // Adds handlers for NotFound. It return a 404 code by default. @@ -441,58 +453,35 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { return true } -// Serializes the given struct as JSON into the response body in a fast and efficient way. -// It also sets the Content-Type as "application/json". -func (c *Context) JSON(code int, obj interface{}) { - c.Writer.Header().Set("Content-Type", MIMEJSON) - if code >= 0 { - c.Writer.WriteHeader(code) - } - encoder := json.NewEncoder(c.Writer) - if err := encoder.Encode(obj); err != nil { +func (c *Context) Render(code int, render render.Render, obj ...interface{}) { + if err := render.Render(c.Writer, code, obj); err != nil { c.ErrorTyped(err, ErrorTypeInternal, obj) c.Abort(500) } } +// Serializes the given struct as JSON into the response body in a fast and efficient way. +// It also sets the Content-Type as "application/json". +func (c *Context) JSON(code int, obj interface{}) { + c.Render(code, render.JSON, obj) +} + // Serializes the given struct as XML into the response body in a fast and efficient way. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { - c.Writer.Header().Set("Content-Type", MIMEXML) - if code >= 0 { - c.Writer.WriteHeader(code) - } - encoder := xml.NewEncoder(c.Writer) - if err := encoder.Encode(obj); err != nil { - c.ErrorTyped(err, ErrorTypeInternal, obj) - c.Abort(500) - } + c.Render(code, render.XML, obj) } // Renders the HTTP template specified by its file name. // It also updates the HTTP code and sets the Content-Type as "text/html". // See http://golang.org/doc/articles/wiki/ -func (c *Context) HTML(code int, name string, data interface{}) { - c.Writer.Header().Set("Content-Type", MIMEHTML) - if code >= 0 { - c.Writer.WriteHeader(code) - } - if err := c.Engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { - c.ErrorTyped(err, ErrorTypeInternal, H{ - "name": name, - "data": data, - }) - c.Abort(500) - } +func (c *Context) HTML(code int, name string, obj interface{}) { + c.Render(code, c.Engine.HTMLRender, name, obj) } // Writes the given string into the response body and sets the Content-Type to "text/plain". func (c *Context) String(code int, format string, values ...interface{}) { - c.Writer.Header().Set("Content-Type", MIMEPlain) - if code >= 0 { - c.Writer.WriteHeader(code) - } - c.Writer.Write([]byte(fmt.Sprintf(format, values...))) + c.Render(code, render.Plain, format, values) } // Writes some data into the body stream and updates the HTTP code. diff --git a/render/render.go b/render/render.go new file mode 100644 index 0000000..cc67aff --- /dev/null +++ b/render/render.go @@ -0,0 +1,69 @@ +package render + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "html/template" + "net/http" +) + +type ( + Render interface { + Render(http.ResponseWriter, int, ...interface{}) error + } + + // JSON binding + jsonRender struct{} + + // XML binding + xmlRender struct{} + + // Plain text + plainRender struct{} + + // form binding + HTMLRender struct { + Template *template.Template + } +) + +var ( + JSON = jsonRender{} + XML = xmlRender{} + Plain = plainRender{} +) + +func writeHeader(w http.ResponseWriter, code int, contentType string) { + if code >= 0 { + w.Header().Set("Content-Type", contentType) + w.WriteHeader(code) + } +} + +func (_ jsonRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "application/xml") + encoder := json.NewEncoder(w) + return encoder.Encode(data[0]) +} + +func (_ xmlRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "application/xml") + encoder := xml.NewEncoder(w) + return encoder.Encode(data[0]) +} + +func (html HTMLRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "text/html") + file := data[0].(string) + obj := data[1] + return html.Template.ExecuteTemplate(w, file, obj) +} + +func (_ plainRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { + writeHeader(w, code, "text/plain") + format := data[0].(string) + args := data[1].([]interface{}) + _, err := w.Write([]byte(fmt.Sprintf(format, args))) + return err +} From a1ff907768adcb93160f77d651fd41ee314f9f4a Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 16 Jul 2014 02:49:22 +0200 Subject: [PATCH 43/67] Fixed Content-Type for json render. Thank you @mdigger --- render/render.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/render.go b/render/render.go index cc67aff..809ef89 100644 --- a/render/render.go +++ b/render/render.go @@ -42,7 +42,7 @@ func writeHeader(w http.ResponseWriter, code int, contentType string) { } func (_ jsonRender) Render(w http.ResponseWriter, code int, data ...interface{}) error { - writeHeader(w, code, "application/xml") + writeHeader(w, code, "application/json") encoder := json.NewEncoder(w) return encoder.Encode(data[0]) } From fc5caf070607067718a4c69842856f9ce86f8a50 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 16 Jul 2014 03:40:58 +0200 Subject: [PATCH 44/67] Adds AUTHORS file Inspired by the cocos2d-iphone community! My old house --- AUTHORS.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 AUTHORS.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..65d1bed --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,91 @@ +List of all the awesome people working to make Gin the best Web Framework in Go! + + + +##gin 0.x series authors + +**Lead Developer:** Manu Martinez-Almeida (@manucorporat) +**Stuff:** +Javier Provecho (@javierprovecho) + +People and companies, who have contributed, in alphabetical order. + +**@adammck (Adam Mckaig)** +- Add MIT license + + +**@AlexanderChen1989 (Alexander)** +- Typos in README + + +**@alexandernyquist (Alexander Nyquist)** +- Using template.Must to fix multiple return issue +- ★ Added support for OPTIONS verb +- ★ Setting response headers before calling WriteHeader + + +**@austinheap (Austin Heap)** +- Adds travis CI integration + + +**@bluele (Jun Kimura)** +- Fixes code examples in README + + +**@chad-russell** +- ★ Support for serializing gin.H into XML + + +**@dickeyxxx (Jeff Dickey)** +- Typos in README + + +**@jasonrhansen** +- Fix spelling and grammar errors in documentation + + +**@julienschmidt (Julien Schmidt)** +- gofmt the code examples + + +**@kyledinh (Kyle Dinh)** +- Adds RunTLS() + + +**@LinusU (Linus Unnebäck)** +- Small fixes in README + + +**@lucas-clemente (Lucas Clemente)** +- ★ work around path.Join removing trailing slashes from routes + + +**@mdigger (Dmitry Sedykh)** +- Fixes Form binding when content-type is x-www-form-urlencoded +- No repeat call c.Writer.Status() in gin.Logger +- Fixed Content-Type for json render + + +**@mopemope (Yutaka Matsubara)** +- ★ Adds Godep support (Dependencies Manager) + + +**@msemenistyi (Mykyta Semenistyi)** +- update Readme.md. Add code to String method + + +**@ngerakines (Nick Gerakines)** +- ★ Improves API, c.GET() doesn't panic +- Adds MustGet() method + + +**@r8k (Rajiv Kilaparti)** +- Fix Port usage in README. + + +**@silasb (Silas Baronda)** +- Fixing quotes in README + + +**@SkuliOskarsson (Skuli Oskarsson)** +- Fixes some texts in README II \ No newline at end of file From 08875b30d628e72616d24e5a0f0c4b528c8c31e6 Mon Sep 17 00:00:00 2001 From: mopemoepe Date: Wed, 16 Jul 2014 14:18:45 +0900 Subject: [PATCH 45/67] Fix variadic parameter --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 7acddbd..4d76872 100644 --- a/gin.go +++ b/gin.go @@ -464,7 +464,7 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { } 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.Abort(500) } From 3cf2d1338f23a01e6d46f7d28765c442607ef68c Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 16 Jul 2014 12:18:02 +0200 Subject: [PATCH 46/67] Updates AUTHORS file --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 65d1bed..677165f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -68,6 +68,7 @@ People and companies, who have contributed, in alphabetical order. **@mopemope (Yutaka Matsubara)** - ★ Adds Godep support (Dependencies Manager) +- Fix variadic parameter in the flexible render API **@msemenistyi (Mykyta Semenistyi)** From 176edde1fbe1ece915f9374a05e0155c3695d14b Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Wed, 16 Jul 2014 12:53:57 +0000 Subject: [PATCH 47/67] SetHTTPTemplate -> SetHTMLTemplate --- gin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index 4d76872..eae215f 100644 --- a/gin.go +++ b/gin.go @@ -149,15 +149,15 @@ func Default() *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { templ := template.Must(template.ParseGlob(pattern)) - engine.SetHTTPTemplate(templ) + engine.SetHTMLTemplate(templ) } func (engine *Engine) LoadHTMLFiles(files ...string) { templ := template.Must(template.ParseFiles(files...)) - engine.SetHTTPTemplate(templ) + engine.SetHTMLTemplate(templ) } -func (engine *Engine) SetHTTPTemplate(templ *template.Template) { +func (engine *Engine) SetHTMLTemplate(templ *template.Template) { engine.HTMLRender = render.HTMLRender{ Template: templ, } From f3fd8b92f8ec3c9ea4fc62c4c79cbb3aedc8b07f Mon Sep 17 00:00:00 2001 From: Austin Heap Date: Wed, 16 Jul 2014 10:00:47 -0700 Subject: [PATCH 48/67] adding info for IRC and mailing list --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 63c67e8..f38d3be 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ import "github.com/gin-gonic/gin" ``` +##Community +If you'd like to help out with the project, there's a mailing list and IRC channel where Gin discussions normally happen. + +* IRC + * [irc.freenode.net #getgin](irc://irc.freenode.net:6667/getgin) + * [Webchat](http://webchat.freenode.net?randomnick=1&channels=%23getgin) +* Mailing List + * Subscribe: [getgin@librelist.org](mailto:getgin@librelist.org) + * [Archives](http://librelist.com/browser/getgin/) + + ##API Examples #### Create most basic PING/PONG HTTP endpoint From dc4337261068402e037614671cadf380d00deca1 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Wed, 16 Jul 2014 20:14:03 +0200 Subject: [PATCH 49/67] Splitting source code in different files --- context.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++ gin.go | 292 ----------------------------------------------------- utils.go | 51 ++++++++++ 3 files changed, 308 insertions(+), 292 deletions(-) create mode 100644 context.go create mode 100644 utils.go diff --git a/context.go b/context.go new file mode 100644 index 0000000..004bee2 --- /dev/null +++ b/context.go @@ -0,0 +1,257 @@ +package gin + +import ( + "bytes" + "errors" + "fmt" + "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/render" + "github.com/julienschmidt/httprouter" + "log" + "net/http" +) + +const ( + ErrorTypeInternal = 1 << iota + ErrorTypeExternal = 1 << iota + ErrorTypeAll = 0xffffffff +) + +// Used internally to collect errors that occurred during an http request. +type errorMsg struct { + Err string `json:"error"` + Type uint32 `json:"-"` + Meta interface{} `json:"meta"` +} + +type errorMsgs []errorMsg + +func (a errorMsgs) ByType(typ uint32) errorMsgs { + if len(a) == 0 { + return a + } + result := make(errorMsgs, 0, len(a)) + for _, msg := range a { + if msg.Type&typ > 0 { + result = append(result, msg) + } + } + return result +} + +func (a errorMsgs) String() string { + if len(a) == 0 { + return "" + } + var buffer bytes.Buffer + for i, msg := range a { + text := fmt.Sprintf("Error #%02d: %s \n Meta: %v\n", (i + 1), msg.Err, msg.Meta) + buffer.WriteString(text) + } + return buffer.String() +} + +// Context is the most important part of gin. It allows us to pass variables between middleware, +// manage the flow, validate the JSON of a request and render a JSON response for example. +type Context struct { + Request *http.Request + Writer ResponseWriter + Keys map[string]interface{} + Errors errorMsgs + Params httprouter.Params + Engine *Engine + handlers []HandlerFunc + index int8 +} + +/************************************/ +/********** ROUTES GROUPING *********/ +/************************************/ + +func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { + c := engine.cache.Get().(*Context) + c.Writer.reset(w) + c.Request = req + c.Params = params + c.handlers = handlers + c.Keys = nil + c.index = -1 + return c +} + +/************************************/ +/****** FLOW AND ERROR MANAGEMENT****/ +/************************************/ + +func (c *Context) Copy() *Context { + var cp Context = *c + cp.index = AbortIndex + cp.handlers = nil + return &cp +} + +// Next should be used only in the middlewares. +// It executes the pending handlers in the chain inside the calling handler. +// See example in github. +func (c *Context) Next() { + c.index++ + s := int8(len(c.handlers)) + for ; c.index < s; c.index++ { + c.handlers[c.index](c) + } +} + +// Forces the system to do not continue calling the pending handlers. +// For example, the first handler checks if the request is authorized. If it's not, context.Abort(401) should be called. +// 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 +} + +// Fail is the same as Abort plus an error message. +// Calling `context.Fail(500, err)` is equivalent to: +// ``` +// context.Error("Operation aborted", err) +// context.Abort(500) +// ``` +func (c *Context) Fail(code int, err error) { + c.Error(err, "Operation aborted") + c.Abort(code) +} + +func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) { + c.Errors = append(c.Errors, errorMsg{ + Err: err.Error(), + Type: typ, + Meta: meta, + }) +} + +// Attaches an error to the current context. The error is pushed to a list of errors. +// It's a good idea to call Error for each error that occurred during the resolution of a request. +// A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. +func (c *Context) Error(err error, meta interface{}) { + c.ErrorTyped(err, ErrorTypeExternal, meta) +} + +func (c *Context) LastError() error { + s := len(c.Errors) + if s > 0 { + return errors.New(c.Errors[s-1].Err) + } else { + return nil + } +} + +/************************************/ +/******** METADATA MANAGEMENT********/ +/************************************/ + +// Sets a new pair key/value just for the specified context. +// It also lazy initializes the hashmap. +func (c *Context) Set(key string, item interface{}) { + if c.Keys == nil { + c.Keys = make(map[string]interface{}) + } + c.Keys[key] = item +} + +// 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) { + if c.Keys != nil { + item, ok := c.Keys[key] + if ok { + return item, nil + } + } + return nil, errors.New("Key does not exist.") +} + +// MustGet returns the value for the given key or panics if the value doesn't exist. +func (c *Context) MustGet(key string) interface{} { + value, err := c.Get(key) + if err != nil || value == nil { + log.Panicf("Key %s doesn't exist", key) + } + return value +} + +/************************************/ +/******** ENCOGING MANAGEMENT********/ +/************************************/ + +// This function checks the Content-Type to select a binding engine automatically, +// Depending the "Content-Type" header different bindings are used: +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// else --> returns an error +// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. +func (c *Context) Bind(obj interface{}) bool { + var b binding.Binding + ctype := filterFlags(c.Request.Header.Get("Content-Type")) + switch { + case c.Request.Method == "GET" || ctype == MIMEPOSTForm: + b = binding.Form + case ctype == MIMEJSON: + b = binding.JSON + case ctype == MIMEXML || ctype == MIMEXML2: + b = binding.XML + default: + c.Fail(400, errors.New("unknown content-type: "+ctype)) + return false + } + return c.BindWith(obj, b) +} + +func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { + if err := b.Bind(c.Request, obj); err != nil { + c.Fail(400, err) + return false + } + return true +} + +func (c *Context) Render(code int, render render.Render, obj ...interface{}) { + if err := render.Render(c.Writer, code, obj...); err != nil { + c.ErrorTyped(err, ErrorTypeInternal, obj) + c.Abort(500) + } +} + +// Serializes the given struct as JSON into the response body in a fast and efficient way. +// It also sets the Content-Type as "application/json". +func (c *Context) JSON(code int, obj interface{}) { + c.Render(code, render.JSON, obj) +} + +// Serializes the given struct as XML into the response body in a fast and efficient way. +// It also sets the Content-Type as "application/xml". +func (c *Context) XML(code int, obj interface{}) { + c.Render(code, render.XML, obj) +} + +// Renders the HTTP template specified by its file name. +// It also updates the HTTP code and sets the Content-Type as "text/html". +// See http://golang.org/doc/articles/wiki/ +func (c *Context) HTML(code int, name string, obj interface{}) { + c.Render(code, c.Engine.HTMLRender, name, obj) +} + +// Writes the given string into the response body and sets the Content-Type to "text/plain". +func (c *Context) String(code int, format string, values ...interface{}) { + c.Render(code, render.Plain, format, values) +} + +// Writes some data into the body stream and updates the HTTP code. +func (c *Context) Data(code int, contentType string, data []byte) { + if len(contentType) > 0 { + c.Writer.Header().Set("Content-Type", contentType) + } + if code >= 0 { + c.Writer.WriteHeader(code) + } + c.Writer.Write(data) +} diff --git a/gin.go b/gin.go index 4d76872..de7f86d 100644 --- a/gin.go +++ b/gin.go @@ -1,15 +1,9 @@ package gin import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" "github.com/julienschmidt/httprouter" "html/template" - "log" "math" "net/http" "path" @@ -26,39 +20,9 @@ const ( MIMEPOSTForm = "application/x-www-form-urlencoded" ) -const ( - ErrorTypeInternal = 1 << iota - ErrorTypeExternal = 1 << iota - ErrorTypeAll = 0xffffffff -) - type ( HandlerFunc func(*Context) - H map[string]interface{} - - // Used internally to collect errors that occurred during an http request. - errorMsg struct { - Err string `json:"error"` - Type uint32 `json:"-"` - Meta interface{} `json:"meta"` - } - - errorMsgs []errorMsg - - // Context is the most important part of gin. It allows us to pass variables between middleware, - // manage the flow, validate the JSON of a request and render a JSON response for example. - Context struct { - Request *http.Request - Writer ResponseWriter - Keys map[string]interface{} - Errors errorMsgs - Params httprouter.Params - Engine *Engine - handlers []HandlerFunc - index int8 - } - // Used internally to configure router, a RouterGroup is associated with a prefix // and an array of handlers (middlewares) RouterGroup struct { @@ -78,55 +42,6 @@ type ( } ) -// Allows type H to be used with xml.Marshal -func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - start.Name = xml.Name{ - Space: "", - Local: "map", - } - if err := e.EncodeToken(start); err != nil { - return err - } - for key, value := range h { - elem := xml.StartElement{ - Name: xml.Name{Space: "", Local: key}, - Attr: []xml.Attr{}, - } - if err := e.EncodeElement(value, elem); err != nil { - return err - } - } - if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { - return err - } - return nil -} - -func (a errorMsgs) ByType(typ uint32) errorMsgs { - if len(a) == 0 { - return a - } - result := make(errorMsgs, 0, len(a)) - for _, msg := range a { - if msg.Type&typ > 0 { - result = append(result, msg) - } - } - return result -} - -func (a errorMsgs) String() string { - if len(a) == 0 { - return "" - } - var buffer bytes.Buffer - for i, msg := range a { - text := fmt.Sprintf("Error #%02d: %s \n Meta: %v\n", (i + 1), msg.Err, msg.Meta) - buffer.WriteString(text) - } - return buffer.String() -} - // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New() *Engine { @@ -200,32 +115,11 @@ func (engine *Engine) RunTLS(addr string, cert string, key string) { /********** ROUTES GROUPING *********/ /************************************/ -func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { - c := engine.cache.Get().(*Context) - c.Writer.reset(w) - c.Request = req - c.Params = params - c.handlers = handlers - c.Keys = nil - c.index = -1 - return c -} - // Adds middlewares to the group, see example code in github. func (group *RouterGroup) Use(middlewares ...HandlerFunc) { group.Handlers = append(group.Handlers, middlewares...) } -func joinGroupPath(elems ...string) string { - joined := path.Join(elems...) - lastComponent := elems[len(elems)-1] - // Append a '/' if the last component had one, but only if it's not there already - if len(lastComponent) > 0 && lastComponent[len(lastComponent)-1] == '/' && joined[len(joined)-1] != '/' { - return joined + "/" - } - return joined -} - // 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 { @@ -318,189 +212,3 @@ func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc h = append(h, handlers...) return h } - -/************************************/ -/****** FLOW AND ERROR MANAGEMENT****/ -/************************************/ - -func (c *Context) Copy() *Context { - var cp Context = *c - cp.index = AbortIndex - cp.handlers = nil - return &cp -} - -// Next should be used only in the middlewares. -// It executes the pending handlers in the chain inside the calling handler. -// See example in github. -func (c *Context) Next() { - c.index++ - s := int8(len(c.handlers)) - for ; c.index < s; c.index++ { - c.handlers[c.index](c) - } -} - -// Forces the system to do not continue calling the pending handlers. -// For example, the first handler checks if the request is authorized. If it's not, context.Abort(401) should be called. -// 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 -} - -// Fail is the same as Abort plus an error message. -// Calling `context.Fail(500, err)` is equivalent to: -// ``` -// context.Error("Operation aborted", err) -// context.Abort(500) -// ``` -func (c *Context) Fail(code int, err error) { - c.Error(err, "Operation aborted") - c.Abort(code) -} - -func (c *Context) ErrorTyped(err error, typ uint32, meta interface{}) { - c.Errors = append(c.Errors, errorMsg{ - Err: err.Error(), - Type: typ, - Meta: meta, - }) -} - -// Attaches an error to the current context. The error is pushed to a list of errors. -// It's a good idea to call Error for each error that occurred during the resolution of a request. -// A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. -func (c *Context) Error(err error, meta interface{}) { - c.ErrorTyped(err, ErrorTypeExternal, meta) -} - -func (c *Context) LastError() error { - s := len(c.Errors) - if s > 0 { - return errors.New(c.Errors[s-1].Err) - } else { - return nil - } -} - -/************************************/ -/******** METADATA MANAGEMENT********/ -/************************************/ - -// Sets a new pair key/value just for the specified context. -// It also lazy initializes the hashmap. -func (c *Context) Set(key string, item interface{}) { - if c.Keys == nil { - c.Keys = make(map[string]interface{}) - } - c.Keys[key] = item -} - -// 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) { - if c.Keys != nil { - item, ok := c.Keys[key] - if ok { - return item, nil - } - } - return nil, errors.New("Key does not exist.") -} - -// MustGet returns the value for the given key or panics if the value doesn't exist. -func (c *Context) MustGet(key string) interface{} { - value, err := c.Get(key) - if err != nil || value == nil { - log.Panicf("Key %s doesn't exist", key) - } - return value -} - -/************************************/ -/******** ENCOGING MANAGEMENT********/ -/************************************/ - -func filterFlags(content string) string { - for i, a := range content { - if a == ' ' || a == ';' { - return content[:i] - } - } - return content -} - -// This function checks the Content-Type to select a binding engine automatically, -// Depending the "Content-Type" header different bindings are used: -// "application/json" --> JSON binding -// "application/xml" --> XML binding -// else --> returns an error -// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid. -func (c *Context) Bind(obj interface{}) bool { - var b binding.Binding - ctype := filterFlags(c.Request.Header.Get("Content-Type")) - switch { - case c.Request.Method == "GET" || ctype == MIMEPOSTForm: - b = binding.Form - case ctype == MIMEJSON: - b = binding.JSON - case ctype == MIMEXML || ctype == MIMEXML2: - b = binding.XML - default: - c.Fail(400, errors.New("unknown content-type: "+ctype)) - return false - } - return c.BindWith(obj, b) -} - -func (c *Context) BindWith(obj interface{}, b binding.Binding) bool { - if err := b.Bind(c.Request, obj); err != nil { - c.Fail(400, err) - return false - } - return true -} - -func (c *Context) Render(code int, render render.Render, obj ...interface{}) { - if err := render.Render(c.Writer, code, obj...); err != nil { - c.ErrorTyped(err, ErrorTypeInternal, obj) - c.Abort(500) - } -} - -// Serializes the given struct as JSON into the response body in a fast and efficient way. -// It also sets the Content-Type as "application/json". -func (c *Context) JSON(code int, obj interface{}) { - c.Render(code, render.JSON, obj) -} - -// Serializes the given struct as XML into the response body in a fast and efficient way. -// It also sets the Content-Type as "application/xml". -func (c *Context) XML(code int, obj interface{}) { - c.Render(code, render.XML, obj) -} - -// Renders the HTTP template specified by its file name. -// It also updates the HTTP code and sets the Content-Type as "text/html". -// See http://golang.org/doc/articles/wiki/ -func (c *Context) HTML(code int, name string, obj interface{}) { - c.Render(code, c.Engine.HTMLRender, name, obj) -} - -// Writes the given string into the response body and sets the Content-Type to "text/plain". -func (c *Context) String(code int, format string, values ...interface{}) { - c.Render(code, render.Plain, format, values) -} - -// Writes some data into the body stream and updates the HTTP code. -func (c *Context) Data(code int, contentType string, data []byte) { - if len(contentType) > 0 { - c.Writer.Header().Set("Content-Type", contentType) - } - if code >= 0 { - c.Writer.WriteHeader(code) - } - c.Writer.Write(data) -} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..f6e3046 --- /dev/null +++ b/utils.go @@ -0,0 +1,51 @@ +package gin + +import ( + "encoding/xml" + "path" +) + +type H map[string]interface{} + +// Allows type H to be used with xml.Marshal +func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Name = xml.Name{ + Space: "", + Local: "map", + } + if err := e.EncodeToken(start); err != nil { + return err + } + for key, value := range h { + elem := xml.StartElement{ + Name: xml.Name{Space: "", Local: key}, + Attr: []xml.Attr{}, + } + if err := e.EncodeElement(value, elem); err != nil { + return err + } + } + if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { + return err + } + return nil +} + +func joinGroupPath(elems ...string) string { + joined := path.Join(elems...) + lastComponent := elems[len(elems)-1] + // Append a '/' if the last component had one, but only if it's not there already + if len(lastComponent) > 0 && lastComponent[len(lastComponent)-1] == '/' && joined[len(joined)-1] != '/' { + return joined + "/" + } + return joined +} + +func filterFlags(content string) string { + for i, a := range content { + if a == ' ' || a == ';' { + return content[:i] + } + } + return content +} From 8ed55606c3a783a273d655c8db08eb27f385cf3e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 02:01:42 +0200 Subject: [PATCH 50/67] Adds context.File(path) --- context.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/context.go b/context.go index 004bee2..290bfff 100644 --- a/context.go +++ b/context.go @@ -255,3 +255,8 @@ func (c *Context) Data(code int, contentType string, data []byte) { } c.Writer.Write(data) } + +// Writes the specified file into the body stream +func (c *Context) File(filepath string) { + http.ServeFile(c.Writer, c.Request, filepath) +} From dda70bf38282ba97e33bad083de607355ec6065b Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 02:02:09 +0200 Subject: [PATCH 51/67] Refactors Static() file serving --- gin.go | 21 ++++++++++++++------- utils.go | 11 ----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/gin.go b/gin.go index 095c018..cab00e9 100644 --- a/gin.go +++ b/gin.go @@ -123,7 +123,8 @@ func (group *RouterGroup) Use(middlewares ...HandlerFunc) { // 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 := joinGroupPath(group.prefix, component) + prefix := group.pathFor(component) + return &RouterGroup{ Handlers: group.combineHandlers(handlers), parent: group, @@ -132,6 +133,15 @@ func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *Rout } } +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(p)-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. @@ -143,7 +153,7 @@ func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *Rout // 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 = joinGroupPath(group.prefix, p) + p = group.pathFor(p) handlers = group.combineHandlers(handlers) group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { c := group.engine.createContext(w, req, params, handlers) @@ -194,14 +204,11 @@ func (group *RouterGroup) HEAD(path string, handlers ...HandlerFunc) { // use : // router.Static("/static", "/var/www") func (group *RouterGroup) Static(p, root string) { + prefix := group.pathFor(p) p = path.Join(p, "/*filepath") - fileServer := http.FileServer(http.Dir(root)) - + fileServer := http.StripPrefix(prefix, http.FileServer(http.Dir(root))) group.GET(p, func(c *Context) { - original := c.Request.URL.Path - c.Request.URL.Path = c.Params.ByName("filepath") fileServer.ServeHTTP(c.Writer, c.Request) - c.Request.URL.Path = original }) } diff --git a/utils.go b/utils.go index f6e3046..90cca1b 100644 --- a/utils.go +++ b/utils.go @@ -2,7 +2,6 @@ package gin import ( "encoding/xml" - "path" ) type H map[string]interface{} @@ -31,16 +30,6 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return nil } -func joinGroupPath(elems ...string) string { - joined := path.Join(elems...) - lastComponent := elems[len(elems)-1] - // Append a '/' if the last component had one, but only if it's not there already - if len(lastComponent) > 0 && lastComponent[len(lastComponent)-1] == '/' && joined[len(joined)-1] != '/' { - return joined + "/" - } - return joined -} - func filterFlags(content string) string { for i, a := range content { if a == ' ' || a == ';' { From 15c27c712d15bfde4fa363cff6b60d22e5244d08 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 02:07:18 +0200 Subject: [PATCH 52/67] Adds HEAD method in Static file serving --- gin.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gin.go b/gin.go index cab00e9..0778197 100644 --- a/gin.go +++ b/gin.go @@ -210,6 +210,9 @@ func (group *RouterGroup) Static(p, root string) { 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 { From d91cfbade48628a812e3e42a26a66424b369754b Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 20:18:50 +0200 Subject: [PATCH 53/67] Renames NotFound404 to NotFound --- deprecated.go | 5 +++++ gin.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/deprecated.go b/deprecated.go index e20e1f2..74fcd79 100644 --- a/deprecated.go +++ b/deprecated.go @@ -36,3 +36,8 @@ func (engine *Engine) ServeFiles(path string, root http.FileSystem) { func (engine *Engine) LoadHTMLTemplates(pattern string) { engine.LoadHTMLGlob(pattern) } + +// DEPRECATED. Use NotFound() instead +func (engine *Engine) NotFound404(handlers ...HandlerFunc) { + engine.NotFound(handlers...) +} diff --git a/gin.go b/gin.go index 0778197..36d51f3 100644 --- a/gin.go +++ b/gin.go @@ -79,7 +79,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { } // Adds handlers for NotFound. It return a 404 code by default. -func (engine *Engine) NotFound404(handlers ...HandlerFunc) { +func (engine *Engine) NotFound(handlers ...HandlerFunc) { engine.handlers404 = handlers } From 4731e82bb7408d6255cfe0a682d4b291962a866d Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 23:42:23 +0200 Subject: [PATCH 54/67] Renames NotFound() to NoRoute() --- deprecated.go | 2 +- gin.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deprecated.go b/deprecated.go index 74fcd79..91d0823 100644 --- a/deprecated.go +++ b/deprecated.go @@ -39,5 +39,5 @@ func (engine *Engine) LoadHTMLTemplates(pattern string) { // DEPRECATED. Use NotFound() instead func (engine *Engine) NotFound404(handlers ...HandlerFunc) { - engine.NotFound(handlers...) + engine.NoRoute(handlers...) } diff --git a/gin.go b/gin.go index 36d51f3..111e81d 100644 --- a/gin.go +++ b/gin.go @@ -78,8 +78,8 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { } } -// Adds handlers for NotFound. It return a 404 code by default. -func (engine *Engine) NotFound(handlers ...HandlerFunc) { +// Adds handlers for NoRoute. It return a 404 code by default. +func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.handlers404 = handlers } From 184a02ee2d3d60b288e46682cb5977908641e401 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Thu, 17 Jul 2014 23:43:41 +0200 Subject: [PATCH 55/67] Improves performance of NoRouter() handler --- gin.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/gin.go b/gin.go index 111e81d..d72822c 100644 --- a/gin.go +++ b/gin.go @@ -42,6 +42,16 @@ type ( } ) +func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { + c := engine.createContext(w, req, nil, engine.handlers404) + c.Writer.setStatus(404) + c.Next() + if !c.Writer.Written() { + c.Data(404, MIMEPlain, []byte("404 page not found")) + } + engine.cache.Put(c) +} + // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New() *Engine { @@ -80,18 +90,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { // Adds handlers for NoRoute. It return a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { - engine.handlers404 = handlers -} - -func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { - handlers := engine.combineHandlers(engine.handlers404) - c := engine.createContext(w, req, nil, handlers) - c.Writer.setStatus(404) - c.Next() - if !c.Writer.Written() { - c.Data(404, MIMEPlain, []byte("404 page not found")) - } - engine.cache.Put(c) + engine.handlers404 = engine.combineHandlers(handlers) } // ServeHTTP makes the router implement the http.Handler interface. From 48f4914165a9f2d54fcbe7f6b02bb365ad9ec71c Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 00:10:28 +0200 Subject: [PATCH 56/67] Performance improvement - Reduces number of allocations per context - Improves CPU cache usage --- context.go | 20 +++++++++++--------- gin.go | 4 +++- response_writer.go | 1 - 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/context.go b/context.go index 290bfff..01a053f 100644 --- a/context.go +++ b/context.go @@ -54,14 +54,15 @@ func (a errorMsgs) String() string { // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. type Context struct { - Request *http.Request - Writer ResponseWriter - Keys map[string]interface{} - Errors errorMsgs - Params httprouter.Params - Engine *Engine - handlers []HandlerFunc - index int8 + writermem responseWriter + Request *http.Request + Writer ResponseWriter + Keys map[string]interface{} + Errors errorMsgs + Params httprouter.Params + Engine *Engine + handlers []HandlerFunc + index int8 } /************************************/ @@ -70,7 +71,8 @@ type Context struct { func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { c := engine.cache.Get().(*Context) - c.Writer.reset(w) + c.writermem.reset(w) + c.Request = req c.Params = params c.handlers = handlers diff --git a/gin.go b/gin.go index d72822c..2dd2da6 100644 --- a/gin.go +++ b/gin.go @@ -60,7 +60,9 @@ func New() *Engine { engine.router = httprouter.New() engine.router.NotFound = engine.handle404 engine.cache.New = func() interface{} { - return &Context{Engine: engine, Writer: &responseWriter{}} + c := &Context{Engine: engine} + c.Writer = &c.writermem + return c } return engine } diff --git a/response_writer.go b/response_writer.go index 88c1b20..cf02e90 100644 --- a/response_writer.go +++ b/response_writer.go @@ -11,7 +11,6 @@ type ( Written() bool // private - reset(http.ResponseWriter) setStatus(int) } From c7fdc2e03a4b6a015a4f6d646c13f2599f719080 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 00:29:20 +0200 Subject: [PATCH 57/67] Errors in context are removed --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 01a053f..17ba45c 100644 --- a/context.go +++ b/context.go @@ -72,12 +72,12 @@ type Context struct { func (engine *Engine) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { c := engine.cache.Get().(*Context) c.writermem.reset(w) - c.Request = req c.Params = params c.handlers = handlers c.Keys = nil c.index = -1 + c.Errors = c.Errors[0:0] return c } From d0fb4a6bf02b1c92231f89220c563175dad381f8 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 00:29:44 +0200 Subject: [PATCH 58/67] Fixes new NoRoute() logic --- gin.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index 2dd2da6..cbda609 100644 --- a/gin.go +++ b/gin.go @@ -35,15 +35,16 @@ type ( // Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares. Engine struct { *RouterGroup - HTMLRender render.Render - cache sync.Pool - handlers404 []HandlerFunc - router *httprouter.Router + HTMLRender render.Render + cache sync.Pool + finalNoRoute []HandlerFunc + noRoute []HandlerFunc + router *httprouter.Router } ) func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { - c := engine.createContext(w, req, nil, engine.handlers404) + c := engine.createContext(w, req, nil, engine.finalNoRoute) c.Writer.setStatus(404) c.Next() if !c.Writer.Written() { @@ -92,7 +93,13 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { // Adds handlers for NoRoute. It return a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { - engine.handlers404 = engine.combineHandlers(handlers) + engine.noRoute = handlers + engine.finalNoRoute = engine.combineHandlers(engine.noRoute) +} + +func (engine *Engine) Use(middlewares ...HandlerFunc) { + engine.RouterGroup.Use(middlewares...) + engine.finalNoRoute = engine.combineHandlers(engine.noRoute) } // ServeHTTP makes the router implement the http.Handler interface. From cc94aa2dd980bea28a543ab8488013d4c991a58e Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 01:34:29 +0200 Subject: [PATCH 59/67] Fixes pathFor() --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index cbda609..106e1b9 100644 --- a/gin.go +++ b/gin.go @@ -144,7 +144,7 @@ func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *Rout 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(p)-1] != '/' { + if len(p) > 0 && p[len(p)-1] == '/' && joined[len(joined)-1] != '/' { return joined + "/" } return joined From 2947981b6137df0e8e9ed89c36c40cbafef86e7b Mon Sep 17 00:00:00 2001 From: mopemoepe Date: Fri, 18 Jul 2014 08:56:59 +0900 Subject: [PATCH 60/67] Fix Corrupted plainRender --- render/render.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/render/render.go b/render/render.go index 809ef89..2915ddc 100644 --- a/render/render.go +++ b/render/render.go @@ -64,6 +64,11 @@ func (_ plainRender) Render(w http.ResponseWriter, code int, data ...interface{} writeHeader(w, code, "text/plain") format := data[0].(string) args := data[1].([]interface{}) - _, err := w.Write([]byte(fmt.Sprintf(format, args))) + var err error + if len(args) > 0 { + _, err = w.Write([]byte(fmt.Sprintf(format, args...))) + } else { + _, err = w.Write([]byte(format)) + } return err } From 590bda514be4d32f4929e50492afd0f25f211725 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 14:59:44 +0200 Subject: [PATCH 61/67] Updates AUTHORS --- AUTHORS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 677165f..67535a4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -40,6 +40,10 @@ People and companies, who have contributed, in alphabetical order. - Typos in README +**@fmd (Fareed Dudhia)** +- Fix typo. SetHTTPTemplate -> SetHTMLTemplate + + **@jasonrhansen** - Fix spelling and grammar errors in documentation @@ -69,6 +73,8 @@ People and companies, who have contributed, in alphabetical order. **@mopemope (Yutaka Matsubara)** - ★ Adds Godep support (Dependencies Manager) - Fix variadic parameter in the flexible render API +- Fix Corrupted plain render +- Fix variadic parameter in new flexible render API **@msemenistyi (Mykyta Semenistyi)** From 4acef47df51c04c9af734111c30fa4b5ec6ebb5c Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 15:20:46 +0200 Subject: [PATCH 62/67] Adds CHANGELOG --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4c64e2f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,45 @@ +##Changelog + +###Gin 0.3 (??) + +- [PERFORMANCE] Normal log and error log are printed in the same call. +- [PERFORMANCE] Improve performance of NoRouter() +- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults. +- [NEW] Flexible rendering API +- [NEW] Add Context.File() +- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS +- [FIX] Rename NotFound404() to NoRoute() +- [FIX] Errors in context are purged +- [FIX] Adds HEAD method in Static file serving +- [FIX] Refactors Static() file serving +- [FIX] Using keyed initialization to fix app-engine integration +- [FIX] Can't unmarshal JSON array, #63 +- [FIX] Renaming Context.Req to Context.Request +- [FIX] Check application/x-www-form-urlencoded when parsing form + + +###Gin 0.2b (Jul 08, 2014) +- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead +- [NEW] Travis CI integration +- [NEW] Completely new logger +- [NEW] New API for serving static files. gin.Static() +- [NEW] gin.H() can be serialized into XML +- [NEW] Typed errors. Errors can be typed. Internet/external/custom. +- [NEW] Support for Godebs +- [NEW] Travis/Godocs badges in README +- [NEW] New Bind() and BindWith() methods for parsing request body. +- [NEW] Add Content.Copy() +- [NEW] Add context.LastError() +- [NEW] Add shorcut for OPTIONS HTTP method +- [FIX] Tons of README fixes +- [FIX] Header is written before body +- [FIX] BasicAuth() and changes API a little bit +- [FIX] Recovery() middleware only prints panics +- [FIX] Context.Get() does not panic anymore. Use MustGet() instead. +- [FIX] Multiple http.WriteHeader() in NotFound handlers +- [FIX] Engine.Run() panics if http server can't be setted up +- [FIX] Crash when route path doesn't start with '/' +- [FIX] Do not update header when status code is negative +- [FIX] Setting response headers before calling WriteHeader in context.String() +- [FIX] Add MIT license +- [FIX] Changes behaviour of ErrorLogger() and Logger() From 7a6d58d8c4be09b14a7cf5a3c5e313d81934d5b1 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 15:22:38 +0200 Subject: [PATCH 63/67] Updates README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63c67e8..cbda621 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,18 @@ Gin is a web framework written in Golang. It features a martini-like API with mu Yes, Gin is an internal project of [my](https://github.com/manucorporat) upcoming startup. We developed it and we are going to continue using and improve it. -##Roadmap for v0.2 +##Roadmap for v1.0 - [x] Performance improments, reduce allocation and garbage collection overhead - [x] Fix bugs +- [ ] Stable API - [ ] Ask our designer for a cool logo - [ ] Add tons of unit tests - [ ] Add internal benchmarks suite - [x] Improve logging system - [x] Improve JSON/XML validation using bindings - [x] Improve XML support +- [x] Flexible rendering system +- [ ] More powerful validation API - [ ] Improve documentation - [ ] Add more cool middlewares, for example redis caching (this also helps developers to understand the framework). - [x] Continuous integration From 97729696c0c30725e8d239ad03814eb8c80ce8f1 Mon Sep 17 00:00:00 2001 From: Manu Mtz-Almeida Date: Fri, 18 Jul 2014 15:25:55 +0200 Subject: [PATCH 64/67] Updates CHANGELOG --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c64e2f..8c39907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ##Changelog -###Gin 0.3 (??) +###Gin 0.4 (??) + + +###Gin 0.3 (Jul 18, 2014) - [PERFORMANCE] Normal log and error log are printed in the same call. - [PERFORMANCE] Improve performance of NoRouter() From 893387458242d5c76707d5e0d8ffdb8504d04839 Mon Sep 17 00:00:00 2001 From: Sasha Myasoedov Date: Fri, 18 Jul 2014 17:42:38 +0300 Subject: [PATCH 65/67] Fixed tests up to master branch --- gin_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gin_test.go b/gin_test.go index c853b0f..5bf737c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -230,7 +230,11 @@ func TestContextSetGet(t *testing.T) { // Set c.Set("foo", "bar") - if v := c.Get("foo"); v != "bar" { + v, err := c.Get("foo") + if err != nil { + t.Errorf("Error on exist key") + } + if v != "bar" { t.Errorf("Value should be bar, was %s", v) } }) @@ -267,7 +271,8 @@ func TestContextHTML(t *testing.T) { w := httptest.NewRecorder() r := Default() - r.HTMLTemplates = template.Must(template.New("t").Parse(`Hello {{.Name}}`)) + templ, _ := template.New("t").Parse(`Hello {{.Name}}`) + r.SetHTMLTemplate(templ) type TestData struct{ Name string } From 89b4c6e0d1b65514c04f1a05a13b55a1d269bde5 Mon Sep 17 00:00:00 2001 From: Sasha Myasoedov Date: Thu, 24 Jul 2014 16:51:11 +0300 Subject: [PATCH 66/67] Replaced deprecated ServeFiles --- gin_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin_test.go b/gin_test.go index 5bf737c..730794e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -334,7 +334,7 @@ func TestHandleStaticFile(t *testing.T) { w := httptest.NewRecorder() r := Default() - r.ServeFiles("/*filepath", http.Dir("./")) + r.Static("./", testRoot) r.ServeHTTP(w, req) @@ -359,7 +359,7 @@ func TestHandleStaticDir(t *testing.T) { w := httptest.NewRecorder() r := Default() - r.ServeFiles("/*filepath", http.Dir("./")) + r.Static("/", "./") r.ServeHTTP(w, req) From 74ca5f3bd9c4a076b76adb545638480f27e15779 Mon Sep 17 00:00:00 2001 From: Sasha Myasoedov Date: Mon, 28 Jul 2014 13:05:23 +0300 Subject: [PATCH 67/67] Added dummy tests for middleware --- gin_test.go | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/gin_test.go b/gin_test.go index 730794e..60fa148 100644 --- a/gin_test.go +++ b/gin_test.go @@ -1,6 +1,7 @@ package gin import ( + "errors" "html/template" "io/ioutil" "net/http" @@ -381,3 +382,159 @@ func TestHandleStaticDir(t *testing.T) { t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) } } + +// TestHandleHeadToDir - ensure the root/sub dir handles properly +func TestHandleHeadToDir(t *testing.T) { + + req, _ := http.NewRequest("HEAD", "/", nil) + + w := httptest.NewRecorder() + + r := Default() + r.Static("/", "./") + + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Errorf("Response code should be Ok, was: %s", w.Code) + } + + bodyAsString := w.Body.String() + + if len(bodyAsString) == 0 { + t.Errorf("Got empty body instead of file tree") + } + if !strings.Contains(bodyAsString, "gin.go") { + t.Errorf("Can't find:`gin.go` in file tree: %s", bodyAsString) + } + + if w.HeaderMap.Get("Content-Type") != "text/html; charset=utf-8" { + t.Errorf("Content-Type should be text/plain, was %s", w.HeaderMap.Get("Content-Type")) + } +} + +// TestHandlerFunc - ensure that custom middleware works properly +func TestHandlerFunc(t *testing.T) { + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r := Default() + var stepsPassed int = 0 + + r.Use(func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }) + + r.ServeHTTP(w, req) + + if w.Code != 404 { + t.Errorf("Response code should be Not found, was: %s", w.Code) + } + + if stepsPassed != 2 { + t.Errorf("Falied to switch context in handler function: %s", stepsPassed) + } +} + +// TestBadAbortHandlersChain - ensure that Abort after switch context will not interrupt pending handlers +func TestBadAbortHandlersChain(t *testing.T) { + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r := Default() + var stepsPassed int = 0 + + r.Use(func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + // after check and abort + context.Abort(409) + }, + func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + context.Abort(403) + }, + ) + + r.ServeHTTP(w, req) + + if w.Code != 403 { + t.Errorf("Response code should be Forbiden, was: %s", w.Code) + } + + if stepsPassed != 4 { + t.Errorf("Falied to switch context in handler function: %s", stepsPassed) + } +} + +// TestAbortHandlersChain - ensure that Abort interrupt used middlewares in fifo order +func TestAbortHandlersChain(t *testing.T) { + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r := Default() + var stepsPassed int = 0 + + r.Use(func(context *Context) { + stepsPassed += 1 + context.Abort(409) + }, + func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }, + ) + + r.ServeHTTP(w, req) + + if w.Code != 409 { + t.Errorf("Response code should be Conflict, was: %s", w.Code) + } + + if stepsPassed != 1 { + t.Errorf("Falied to switch context in handler function: %s", stepsPassed) + } +} + +// TestFailHandlersChain - ensure that Fail interrupt used middlewares in fifo order as +// as well as Abort +func TestFailHandlersChain(t *testing.T) { + + req, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + + r := Default() + var stepsPassed int = 0 + + r.Use(func(context *Context) { + stepsPassed += 1 + + context.Fail(500, errors.New("foo")) + }, + func(context *Context) { + stepsPassed += 1 + context.Next() + stepsPassed += 1 + }, + ) + + r.ServeHTTP(w, req) + + if w.Code != 500 { + t.Errorf("Response code should be Server error, was: %s", w.Code) + } + + if stepsPassed != 1 { + t.Errorf("Falied to switch context in handler function: %s", stepsPassed) + } + +}