From 244c490a2e45684d70f32e846d35db3321706411 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 14 Apr 2015 04:06:39 +0000 Subject: [PATCH 01/53] autogen README --- README.md | 87 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ebd8620..bd40864 100644 --- a/README.md +++ b/README.md @@ -171,14 +171,14 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/lookup/#host", func(w rest.ResponseWriter, req *rest.Request) { + rest.Get("/lookup/#host", func(w rest.ResponseWriter, req *rest.Request) { ip, err := net.LookupIP(req.PathParam("host")) if err != nil { rest.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteJson(&ip) - }}, + }), ) if err != nil { log.Fatal(err) @@ -223,10 +223,10 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/countries", GetAllCountries}, - &rest.Route{"POST", "/countries", PostCountry}, - &rest.Route{"GET", "/countries/:code", GetCountry}, - &rest.Route{"DELETE", "/countries/:code", DeleteCountry}, + rest.Get("/countries", GetAllCountries), + rest.Post("/countries", PostCountry), + rest.Get("/countries/:code", GetCountry), + rest.Delete("/countries/:code", DeleteCountry), ) if err != nil { log.Fatal(err) @@ -345,11 +345,11 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/users", users.GetAllUsers}, - &rest.Route{"POST", "/users", users.PostUser}, - &rest.Route{"GET", "/users/:id", users.GetUser}, - &rest.Route{"PUT", "/users/:id", users.PutUser}, - &rest.Route{"DELETE", "/users/:id", users.DeleteUser}, + rest.Get("/users", users.GetAllUsers), + rest.Post("/users", users.PostUser), + rest.Get("/users/:id", users.GetUser), + rest.Put("/users/:id", users.PutUser), + rest.Delete("/users/:id", users.DeleteUser), ) if err != nil { log.Fatal(err) @@ -475,9 +475,9 @@ func main() { api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) { + rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) { w.WriteJson(map[string]string{"Body": "Hello World!"}) - }}, + }), ) if err != nil { log.Fatal(err) @@ -533,11 +533,11 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/reminders", i.GetAllReminders}, - &rest.Route{"POST", "/reminders", i.PostReminder}, - &rest.Route{"GET", "/reminders/:id", i.GetReminder}, - &rest.Route{"PUT", "/reminders/:id", i.PutReminder}, - &rest.Route{"DELETE", "/reminders/:id", i.DeleteReminder}, + rest.Get("/reminders", i.GetAllReminders), + rest.Post("/reminders", i.PostReminder), + rest.Get("/reminders/:id", i.GetReminder), + rest.Put("/reminders/:id", i.PutReminder), + rest.Delete("/reminders/:id", i.DeleteReminder), ) if err != nil { log.Fatal(err) @@ -674,7 +674,7 @@ func main() { AccessControlMaxAge: 3600, }) router, err := rest.MakeRouter( - &rest.Route{"GET", "/countries", GetAllCountries}, + rest.Get("/countries", GetAllCountries), ) if err != nil { log.Fatal(err) @@ -829,11 +829,9 @@ func main() { api.Use(statusMw) api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/.status", - func(w rest.ResponseWriter, r *rest.Request) { - w.WriteJson(statusMw.GetStatus()) - }, - }, + rest.Get("/.status", func(w rest.ResponseWriter, r *rest.Request) { + w.WriteJson(statusMw.GetStatus()) + }), ) if err != nil { log.Fatal(err) @@ -883,14 +881,12 @@ func main() { }, } router, err := rest.MakeRouter( - &rest.Route{"GET", "/countries", GetAllCountries}, - &rest.Route{"GET", "/.status", - auth.MiddlewareFunc( - func(w rest.ResponseWriter, r *rest.Request) { - w.WriteJson(statusMw.GetStatus()) - }, - ), - }, + rest.Get("/countries", GetAllCountries), + rest.Get("/.status", auth.MiddlewareFunc( + func(w rest.ResponseWriter, r *rest.Request) { + w.WriteJson(statusMw.GetStatus()) + }, + )), ) if err != nil { log.Fatal(err) @@ -974,9 +970,10 @@ func main() { IfTrue: jwt_middleware, }) api_router, _ := rest.MakeRouter( - &rest.Route{"POST", "/login", jwt_middleware.LoginHandler}, - &rest.Route{"GET", "/auth_test", handle_auth}, - &rest.Route{"GET", "/refresh_token", jwt_middleware.RefreshHandler}) + rest.Post("/login", jwt_middleware.LoginHandler), + rest.Get("/auth_test", handle_auth), + rest.Get("/refresh_token", jwt_middleware.RefreshHandler), + ) api.SetApp(api_router) http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler())) @@ -1026,7 +1023,7 @@ func main() { api.Use(&rest.AccessLogApacheMiddleware{}) api.Use(rest.DefaultCommonStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/stream", StreamThings}, + rest.Get("/stream", StreamThings), ) if err != nil { log.Fatal(err) @@ -1094,10 +1091,10 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/message.txt", func(w rest.ResponseWriter, req *rest.Request) { + rest.Get("/message.txt", func(w rest.ResponseWriter, req *rest.Request) { w.Header().Set("Content-Type", "text/plain") w.(http.ResponseWriter).Write([]byte("Hello World!")) - }}, + }), ) if err != nil { log.Fatal(err) @@ -1197,7 +1194,7 @@ func main() { MaxVersion: "3.0.0", }) router, err := rest.MakeRouter( - &rest.Route{"GET", "/#version/message", svmw.MiddlewareFunc( + rest.Get("/#version/message", svmw.MiddlewareFunc( func(w rest.ResponseWriter, req *rest.Request) { version := req.Env["VERSION"].(*semver.Version) if version.Major == 2 { @@ -1211,7 +1208,7 @@ func main() { }) } }, - )}, + )), ) if err != nil { log.Fatal(err) @@ -1361,7 +1358,7 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) { + rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) { for cpt := 1; cpt <= 10; cpt++ { // wait 1 second @@ -1375,7 +1372,7 @@ func main() { // Flush the buffer to client w.(http.Flusher).Flush() } - }}, + }), ) if err != nil { log.Fatal(err) @@ -1434,7 +1431,7 @@ func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/users/:id", GetUser}, + rest.Get("/users/:id", GetUser), ) if err != nil { log.Fatal(err) @@ -1482,9 +1479,9 @@ func init() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( - &rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) { + &rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) { w.WriteJson(map[string]string{"Body": "Hello World!"}) - }}, + }), ) if err != nil { log.Fatal(err) From 74480147340d9827ec04e748096eb80b267102f4 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 26 Apr 2015 00:07:05 +0000 Subject: [PATCH 02/53] README autogen - Favor https when possible --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd40864..b7a391a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ *A quick and easy way to setup a RESTful JSON API* -[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/ant0ine/go-json-rest/rest) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/ant0ine/go-json-rest/master/LICENSE) [![build](https://img.shields.io/travis/ant0ine/go-json-rest.svg?style=flat)](https://travis-ci.org/ant0ine/go-json-rest) +[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/ant0ine/go-json-rest/rest) [![license](https://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/ant0ine/go-json-rest/master/LICENSE) [![build](https://img.shields.io/travis/ant0ine/go-json-rest.svg?style=flat)](https://travis-ci.org/ant0ine/go-json-rest) **Go-Json-Rest** is a thin layer on top of `net/http` that helps building RESTful JSON APIs easily. It provides fast and scalable request routing using a Trie based implementation, helpers to deal with JSON requests and responses, and middlewares for functionalities like CORS, Auth, Gzip, Status ... @@ -1496,7 +1496,7 @@ func init() { ## External Documentation -- [Online Documentation (godoc.org)](http://godoc.org/github.com/ant0ine/go-json-rest/rest) +- [Online Documentation (godoc.org)](https://godoc.org/github.com/ant0ine/go-json-rest/rest) Old v1 blog posts: From 9b6a5da02a0ebb398073782c6637b50776683ffb Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 28 Apr 2015 04:58:06 +0000 Subject: [PATCH 03/53] README autogen - New Websocket example --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/README.md b/README.md index b7a391a..8b11981 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ - [Graceful Shutdown](#graceful-shutdown) - [SPDY](#spdy) - [Google App Engine](#gae) + - [Websocket](#websocket) - [External Documentation](#external-documentation) - [Version 3 release notes](#version-3-release-notes) - [Migration guide from v2 to v3](#migration-guide-from-v2-to-v3) @@ -1492,6 +1493,64 @@ func init() { ``` +#### Websocket + +Demonstrate how to run websocket in go-json-rest + +go client demo: +```go +origin := "http://localhost:8080/" +url := "ws://localhost:8080/ws" +ws, err := websocket.Dial(url, "", origin) +if err != nil { + log.Fatal(err) +} +if _, err := ws.Write([]byte("hello, world\n")); err != nil { + log.Fatal(err) +} +var msg = make([]byte, 512) +var n int +if n, err = ws.Read(msg); err != nil { + log.Fatal(err) +} +log.Printf("Received: %s.", msg[:n]) +``` + +code: +``` go +package main + +import ( + "io" + "log" + "net/http" + + "github.com/ant0ine/go-json-rest/rest" + "golang.org/x/net/websocket" +) + +func main() { + wsHandler := websocket.Handler(func(ws *websocket.Conn) { + io.Copy(ws, ws) + }) + + router, err := rest.MakeRouter( + rest.Get("/ws", func(w rest.ResponseWriter, r *rest.Request) { + wsHandler.ServeHTTP(w.(http.ResponseWriter), r.Request) + }), + ) + if err != nil { + log.Fatal(err) + } + + api := rest.NewApi() + api.Use(rest.DefaultDevStack...) + api.SetApp(router) + log.Fatal(http.ListenAndServe(":8080", api.MakeHandler())) +} + +``` + ## External Documentation From 67cb6d24b9fda5ce50167974ee4c38e6cc8cc6a1 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 28 Apr 2015 05:00:44 +0000 Subject: [PATCH 04/53] Thanks wingyplus! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8b11981..6d4ad3a 100644 --- a/README.md +++ b/README.md @@ -1749,6 +1749,7 @@ Overall, they provide the same features, but with two methods instead of three, - [Yann Kerhervé](https://github.com/yannk) - [Ask Bjørn Hansen](https://github.com/abh) - [Paul Lam](https://github.com/Quantisan) +- [Thanabodee Charoenpiriyakij](https://github.com/wingyplus) Copyright (c) 2013-2015 Antoine Imbert From 3cf4f8c244c4a830907fe2c97e5594b574ec08fe Mon Sep 17 00:00:00 2001 From: Gwynant Jones Date: Fri, 1 May 2015 17:12:23 +0100 Subject: [PATCH 05/53] Changed travis to use the new container based architecture --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f2c6de3..a86f81e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: go go: - 1.1 From 22dc9802d4cbe025a3a449d1a5d0bb9de92a5af5 Mon Sep 17 00:00:00 2001 From: Sebastien Estienne Date: Sun, 10 May 2015 22:27:03 -0700 Subject: [PATCH 06/53] JSONP security fix (http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/) --- rest/jsonp.go | 6 ++++-- rest/jsonp_test.go | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/rest/jsonp.go b/rest/jsonp.go index 8e23456..6071b50 100644 --- a/rest/jsonp.go +++ b/rest/jsonp.go @@ -70,8 +70,10 @@ func (w *jsonpResponseWriter) WriteJson(v interface{}) error { if err != nil { return err } - // TODO add "/**/" ? - w.Write([]byte(w.callbackName + "(")) + // JSONP security fix (http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/) + w.Header().Set("Content-Disposition", "filename=f.txt") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Write([]byte("/**/" + w.callbackName + "(")) w.Write(b) w.Write([]byte(")")) return nil diff --git a/rest/jsonp_test.go b/rest/jsonp_test.go index d9394dc..e556d8f 100644 --- a/rest/jsonp_test.go +++ b/rest/jsonp_test.go @@ -1,8 +1,9 @@ package rest import ( - "github.com/ant0ine/go-json-rest/rest/test" "testing" + + "github.com/ant0ine/go-json-rest/rest/test" ) func TestJsonpMiddleware(t *testing.T) { @@ -33,10 +34,14 @@ func TestJsonpMiddleware(t *testing.T) { recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/ok?callback=parseResponse", nil)) recorded.CodeIs(200) recorded.HeaderIs("Content-Type", "text/javascript") - recorded.BodyIs("parseResponse({\"Id\":\"123\"})") + recorded.HeaderIs("Content-Disposition", "filename=f.txt") + recorded.HeaderIs("X-Content-Type-Options", "nosniff") + recorded.BodyIs("/**/parseResponse({\"Id\":\"123\"})") recorded = test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/error?callback=parseResponse", nil)) recorded.CodeIs(500) recorded.HeaderIs("Content-Type", "text/javascript") - recorded.BodyIs("parseResponse({\"Error\":\"jsonp error\"})") + recorded.HeaderIs("Content-Disposition", "filename=f.txt") + recorded.HeaderIs("X-Content-Type-Options", "nosniff") + recorded.BodyIs("/**/parseResponse({\"Error\":\"jsonp error\"})") } From 20210d50628d0e0d5286c3e87d609fea3483a087 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 12 May 2015 03:21:46 +0000 Subject: [PATCH 07/53] Thanks Sebastien Estienne! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d4ad3a..96548b4 100644 --- a/README.md +++ b/README.md @@ -1750,6 +1750,7 @@ Overall, they provide the same features, but with two methods instead of three, - [Ask Bjørn Hansen](https://github.com/abh) - [Paul Lam](https://github.com/Quantisan) - [Thanabodee Charoenpiriyakij](https://github.com/wingyplus) +- [Sebastien Estienne](https://github.com/sebest) Copyright (c) 2013-2015 Antoine Imbert From fc2c9a071db8a007b1d30d171b63af9b7366886c Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 17 May 2015 19:22:26 +0000 Subject: [PATCH 08/53] Update the documentation of the test pkg to not use ResourceHandler --- rest/test/doc.go | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/rest/test/doc.go b/rest/test/doc.go index 2a4bdc7..f58d50e 100644 --- a/rest/test/doc.go +++ b/rest/test/doc.go @@ -5,23 +5,29 @@ // checks end up to be always the same. This test package tries to save // some typing by providing helpers for this particular use case. // -// package main +// package main // -// import ( -// "github.com/ant0ine/go-json-rest/rest/test" -// "testing" -// ) +// import ( +// "github.com/ant0ine/go-json-rest/rest" +// "github.com/ant0ine/go-json-rest/rest/test" +// "testing" +// ) // -// func TestSimpleRequest(t *testing.T) { -// handler := ResourceHandler{} -// handler.SetRoutes( -// Get("/r", func(w ResponseWriter, r *Request) { -// w.WriteJson(map[string]string{"Id": "123"}) -// }), -// ) -// recorded := test.RunRequest(t, &handler, -// test.MakeSimpleRequest("GET", "http://1.2.3.4/r", nil)) -// recorded.CodeIs(200) -// recorded.ContentTypeIsJson() -// } +// func TestSimpleRequest(t *testing.T) { +// api := rest.NewApi() +// api.Use(rest.DefaultDevStack...) +// router, err := rest.MakeRouter( +// rest.Get("/r", func(w rest.ResponseWriter, r *rest.Request) { +// w.WriteJson(map[string]string{"Id": "123"}) +// }), +// ) +// if err != nil { +// log.Fatal(err) +// } +// api.SetApp(router) +// recorded := test.RunRequest(t, api.MakeHandler(), +// test.MakeSimpleRequest("GET", "http://1.2.3.4/r", nil)) +// recorded.CodeIs(200) +// recorded.ContentTypeIsJson() +// } package test From bd5fcdd7b0b7e63f4abb171498784efbd01fda7b Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 17 May 2015 19:30:52 +0000 Subject: [PATCH 09/53] Fix a docstring that was mentioning the ResourceHandler. Also get a more accurate list of implemented interfaces. --- rest/response.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest/response.go b/rest/response.go index d436e17..e2043ea 100644 --- a/rest/response.go +++ b/rest/response.go @@ -8,8 +8,8 @@ import ( ) // A ResponseWriter interface dedicated to JSON HTTP response. -// Note that the object instantiated by the ResourceHandler that implements this interface, -// also happens to implement http.ResponseWriter, http.Flusher and http.CloseNotifier. +// Note, the responseWriter object instantiated by the framework also implements many other interfaces +// accessible by type assertion: http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker. type ResponseWriter interface { // Identical to the http.ResponseWriter interface From db8baeaa46e2f84180f7fcfa0577f72b2f4b4f40 Mon Sep 17 00:00:00 2001 From: Sebastien Estienne Date: Thu, 21 May 2015 01:40:45 -0700 Subject: [PATCH 10/53] Return a specific error when the JSON payload is empty to be able to choose how to handle this case --- rest/request.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rest/request.go b/rest/request.go index 40f2890..71125cf 100644 --- a/rest/request.go +++ b/rest/request.go @@ -2,12 +2,18 @@ package rest import ( "encoding/json" + "errors" "io/ioutil" "net/http" "net/url" "strings" ) +var ( + // ErrJSONPayloadEmpty is returned when the JSON payload is empty. + ErrJSONPayloadEmpty = errors.New("JSON payload is empty") +) + // Request inherits from http.Request, and provides additional methods. type Request struct { *http.Request @@ -31,6 +37,9 @@ func (r *Request) DecodeJsonPayload(v interface{}) error { if err != nil { return err } + if len(content) == 0 { + return ErrJSONPayloadEmpty + } err = json.Unmarshal(content, v) if err != nil { return err From 990050cf5fe4149aedf65fb6e5844d6e51b3cfb7 Mon Sep 17 00:00:00 2001 From: antoine Date: Wed, 27 May 2015 07:35:15 +0000 Subject: [PATCH 11/53] Add unit test for the new empty JSON payload error. Also fix the case convention used in this project, even if different than golint. --- rest/request.go | 6 +++--- rest/request_test.go | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/rest/request.go b/rest/request.go index 71125cf..9d1d792 100644 --- a/rest/request.go +++ b/rest/request.go @@ -10,8 +10,8 @@ import ( ) var ( - // ErrJSONPayloadEmpty is returned when the JSON payload is empty. - ErrJSONPayloadEmpty = errors.New("JSON payload is empty") + // ErrJsonPayloadEmpty is returned when the JSON payload is empty. + ErrJsonPayloadEmpty = errors.New("JSON payload is empty") ) // Request inherits from http.Request, and provides additional methods. @@ -38,7 +38,7 @@ func (r *Request) DecodeJsonPayload(v interface{}) error { return err } if len(content) == 0 { - return ErrJSONPayloadEmpty + return ErrJsonPayloadEmpty } err = json.Unmarshal(content, v) if err != nil { diff --git a/rest/request_test.go b/rest/request_test.go index f7de0ee..66424ac 100644 --- a/rest/request_test.go +++ b/rest/request_test.go @@ -3,6 +3,7 @@ package rest import ( "io" "net/http" + "strings" "testing" ) @@ -18,6 +19,14 @@ func defaultRequest(method string, urlStr string, body io.Reader, t *testing.T) } } +func TestRequestEmptyJson(t *testing.T) { + req := defaultRequest("POST", "http://localhost", strings.NewReader(""), t) + err := req.DecodeJsonPayload(nil) + if err != ErrJsonPayloadEmpty { + t.Error("Expected ErrJsonPayloadEmpty") + } +} + func TestRequestBaseUrl(t *testing.T) { req := defaultRequest("GET", "http://localhost", nil, t) urlBase := req.BaseUrl() From 4c0ab2b46b2318802f6502ea84031905d53cbb2a Mon Sep 17 00:00:00 2001 From: antoine Date: Wed, 27 May 2015 07:46:28 +0000 Subject: [PATCH 12/53] Make golint marginally happier --- rest/access_log_apache.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest/access_log_apache.go b/rest/access_log_apache.go index b847509..72761f8 100644 --- a/rest/access_log_apache.go +++ b/rest/access_log_apache.go @@ -40,13 +40,13 @@ import ( type AccessLogFormat string const ( - // Common Log Format (CLF). + // CommonLogFormat is the Common Log Format (CLF). CommonLogFormat = "%h %l %u %t \"%r\" %s %b" - // NCSA extended/combined log format. + // CombinedLogFormat is the NCSA extended/combined log format. CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"" - // Default format, colored output and response time, convenient for development. + // DefaultLogFormat is the default format, colored output and response time, convenient for development. DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m" ) From 6eac5ab9fc475f517022602fd417c921403c0a28 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 1 Jun 2015 02:38:48 +0000 Subject: [PATCH 13/53] README auto-gen - fix broken example --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 96548b4..84b702c 100644 --- a/README.md +++ b/README.md @@ -1188,12 +1188,13 @@ func (mw *SemVerMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.Handle } func main() { - api := rest.NewApi() - api.Use(rest.DefaultDevStack...) - api.Use(SemVerMiddleware{ + + svmw := SemVerMiddleware{ MinVersion: "1.0.0", MaxVersion: "3.0.0", - }) + } + api := rest.NewApi() + api.Use(rest.DefaultDevStack...) router, err := rest.MakeRouter( rest.Get("/#version/message", svmw.MiddlewareFunc( func(w rest.ResponseWriter, req *rest.Request) { From f3add9911919590b316a2e76ba71de6019ac8708 Mon Sep 17 00:00:00 2001 From: Sebastien Estienne Date: Tue, 2 Jun 2015 23:35:52 -0700 Subject: [PATCH 14/53] Adding the PATCH verb --- rest/route.go | 12 +++++++++++- rest/route_test.go | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/rest/route.go b/rest/route.go index e1fdbcc..1e3ed93 100644 --- a/rest/route.go +++ b/rest/route.go @@ -5,7 +5,7 @@ import ( ) // Route defines a route as consumed by the router. It can be instantiated directly, or using one -// of the shortcut methods: rest.Get, rest.Post, rest.Put, and rest.Delete. +// of the shortcut methods: rest.Get, rest.Post, rest.Put, rest.Patch and rest.Delete. type Route struct { // Any HTTP method. It will be used as uppercase to avoid common mistakes. @@ -67,6 +67,16 @@ func Put(pathExp string, handlerFunc HandlerFunc) *Route { } } +// Patch is a shortcut method that instantiates a PATCH route. See the Route object the parameters definitions. +// Equivalent to &Route{"PATCH", pathExp, handlerFunc} +func Patch(pathExp string, handlerFunc HandlerFunc) *Route { + return &Route{ + HttpMethod: "PATCH", + PathExp: pathExp, + Func: handlerFunc, + } +} + // Delete is a shortcut method that instantiates a DELETE route. Equivalent to &Route{"DELETE", pathExp, handlerFunc} func Delete(pathExp string, handlerFunc HandlerFunc) *Route { return &Route{ diff --git a/rest/route_test.go b/rest/route_test.go index 4fc2c2f..ca83903 100644 --- a/rest/route_test.go +++ b/rest/route_test.go @@ -66,6 +66,11 @@ func TestShortcutMethods(t *testing.T) { t.Errorf("expected PUT, got %s", r.HttpMethod) } + r = Patch("/", nil) + if r.HttpMethod != "PATCH" { + t.Errorf("expected PATCH, got %s", r.HttpMethod) + } + r = Delete("/", nil) if r.HttpMethod != "DELETE" { t.Errorf("expected DELETE, got %s", r.HttpMethod) From 57d3250d22392611efe69a00345f3ca2905bb18a Mon Sep 17 00:00:00 2001 From: antoine Date: Thu, 11 Jun 2015 05:45:49 +0000 Subject: [PATCH 15/53] Add Head and Options Route shortcut methods. Routes corresponding to the HEAD and OPTIONS HTTP methods. --- rest/route.go | 20 ++++++++++++++++++++ rest/route_test.go | 12 +++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/rest/route.go b/rest/route.go index 1e3ed93..efb94a7 100644 --- a/rest/route.go +++ b/rest/route.go @@ -37,6 +37,16 @@ func (route *Route) MakePath(pathParams map[string]string) string { return path } +// Head is a shortcut method that instantiates a HEAD route. See the Route object the parameters definitions. +// Equivalent to &Route{"HEAD", pathExp, handlerFunc} +func Head(pathExp string, handlerFunc HandlerFunc) *Route { + return &Route{ + HttpMethod: "HEAD", + PathExp: pathExp, + Func: handlerFunc, + } +} + // Get is a shortcut method that instantiates a GET route. See the Route object the parameters definitions. // Equivalent to &Route{"GET", pathExp, handlerFunc} func Get(pathExp string, handlerFunc HandlerFunc) *Route { @@ -85,3 +95,13 @@ func Delete(pathExp string, handlerFunc HandlerFunc) *Route { Func: handlerFunc, } } + +// Options is a shortcut method that instantiates an OPTIONS route. See the Route object the parameters definitions. +// Equivalent to &Route{"OPTIONS", pathExp, handlerFunc} +func Options(pathExp string, handlerFunc HandlerFunc) *Route { + return &Route{ + HttpMethod: "OPTIONS", + PathExp: pathExp, + Func: handlerFunc, + } +} diff --git a/rest/route_test.go b/rest/route_test.go index ca83903..5ea63b8 100644 --- a/rest/route_test.go +++ b/rest/route_test.go @@ -51,7 +51,12 @@ func TestReverseRouteResolution(t *testing.T) { func TestShortcutMethods(t *testing.T) { - r := Get("/", nil) + r := Head("/", nil) + if r.HttpMethod != "HEAD" { + t.Errorf("expected HEAD, got %s", r.HttpMethod) + } + + r = Get("/", nil) if r.HttpMethod != "GET" { t.Errorf("expected GET, got %s", r.HttpMethod) } @@ -75,4 +80,9 @@ func TestShortcutMethods(t *testing.T) { if r.HttpMethod != "DELETE" { t.Errorf("expected DELETE, got %s", r.HttpMethod) } + + r = Options("/", nil) + if r.HttpMethod != "OPTIONS" { + t.Errorf("expected OPTIONS, got %s", r.HttpMethod) + } } From 11950f5f17b973642477ed31b68df64fdf4b1c08 Mon Sep 17 00:00:00 2001 From: antoine Date: Sat, 13 Jun 2015 17:57:37 +0000 Subject: [PATCH 16/53] Add a link to the Auth Token middleware. Thanks @grayj ! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84b702c..29706fe 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Third Party Middlewares: |------|-------------| | **[Statsd](https://github.com/ant0ine/go-json-rest-middleware-statsd)** | Send stats to a statsd server | | **[JWT](https://github.com/StephanDollberg/go-json-rest-middleware-jwt)** | Provides authentication via Json Web Tokens | +| **[AuthToken](https://github.com/grayj/go-json-rest-middleware-tokenauth)** | Provides a Token Auth implementation | *If you have a Go-Json-Rest compatible middleware, feel free to submit a PR to add it in this list, and in the examples.* From 2508228d5c311a69addc104ee2e68af69462f606 Mon Sep 17 00:00:00 2001 From: Brian Fallik Date: Tue, 16 Jun 2015 11:00:58 -0400 Subject: [PATCH 17/53] Update README.md include a link to clypd's secure redirect middleware --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 29706fe..36d68a0 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ Third Party Middlewares: | **[Statsd](https://github.com/ant0ine/go-json-rest-middleware-statsd)** | Send stats to a statsd server | | **[JWT](https://github.com/StephanDollberg/go-json-rest-middleware-jwt)** | Provides authentication via Json Web Tokens | | **[AuthToken](https://github.com/grayj/go-json-rest-middleware-tokenauth)** | Provides a Token Auth implementation | +| **[SecureRedirect](https://github.com/clyphub/go-json-rest-middleware)** | Redirect clients from HTTP to HTTPS | *If you have a Go-Json-Rest compatible middleware, feel free to submit a PR to add it in this list, and in the examples.* From c3c57b2a42c1c77a2d8cb5d66393d8796f42cfc0 Mon Sep 17 00:00:00 2001 From: antoine Date: Fri, 27 Nov 2015 00:36:17 +0000 Subject: [PATCH 18/53] Add the ability to customize the field name in the error response. "Error", capitalized, was not a good choice, but will stay the default until the next major version (strict compat, see semver.org) This variable allows the user to customize this field name. --- rest/response.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rest/response.go b/rest/response.go index e2043ea..8baeae6 100644 --- a/rest/response.go +++ b/rest/response.go @@ -29,12 +29,17 @@ type ResponseWriter interface { WriteHeader(int) } +// This allows to customize the field name used in the error response payload. +// It defaults to "Error" for compatibility reason, but can be changed before starting the server. +// eg: rest.ErrorFieldName = "errorMessage" +var ErrorFieldName = "Error" + // Error produces an error response in JSON with the following structure, '{"Error":"My error message"}' // The standard plain text net/http Error helper can still be called like this: // http.Error(w, "error message", code) func Error(w ResponseWriter, error string, code int) { w.WriteHeader(code) - err := w.WriteJson(map[string]string{"Error": error}) + err := w.WriteJson(map[string]string{ErrorFieldName: error}) if err != nil { panic(err) } From 1eb58ebad374f392922eabd64cbcc836a96620e4 Mon Sep 17 00:00:00 2001 From: antoine Date: Fri, 27 Nov 2015 01:02:29 +0000 Subject: [PATCH 19/53] Make Travis CI test go 1.5.1 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a86f81e..bce1574 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,4 @@ go: - 1.2.1 - 1.3 - 1.4 + - 1.5.1 From 3152f30bb246f193be132e9d14e6e4ad174eed50 Mon Sep 17 00:00:00 2001 From: antoine Date: Sat, 28 Nov 2015 00:35:09 +0000 Subject: [PATCH 20/53] Include charset=utf-8 in the JSON response. Per spec, UTF-8 is the default, and the charset parameter should not be necessary. But some clients (eg: Chrome) think otherwise. Since json.Marshal produces UTF-8, setting the charset parameter is a safe option. This changeset also includes a better ContentTypeIsJson test method. It expects the charset to be utf-8 or not set. --- rest/response.go | 6 +++++- rest/test/util.go | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/rest/response.go b/rest/response.go index 8baeae6..52529f1 100644 --- a/rest/response.go +++ b/rest/response.go @@ -66,7 +66,11 @@ type responseWriter struct { func (w *responseWriter) WriteHeader(code int) { if w.Header().Get("Content-Type") == "" { - w.Header().Set("Content-Type", "application/json") + // Per spec, UTF-8 is the default, and the charset parameter should not + // be necessary. But some clients (eg: Chrome) think otherwise. + // Since json.Marshal produces UTF-8, setting the charset parameter is a + // safe option. + w.Header().Set("Content-Type", "application/json; charset=utf-8") } w.ResponseWriter.WriteHeader(code) w.wroteHeader = true diff --git a/rest/test/util.go b/rest/test/util.go index 54fdda4..3b59ba3 100644 --- a/rest/test/util.go +++ b/rest/test/util.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "mime" "net/http" "net/http/httptest" "strings" @@ -56,7 +57,23 @@ func HeaderIs(t *testing.T, r *httptest.ResponseRecorder, headerKey, expectedVal } func ContentTypeIsJson(t *testing.T, r *httptest.ResponseRecorder) { - HeaderIs(t, r, "Content-Type", "application/json") + + mediaType, params, _ := mime.ParseMediaType(r.HeaderMap.Get("Content-Type")) + charset := params["charset"] + + if mediaType != "application/json" { + t.Errorf( + "Content-Type media type: application/json expected, got: %s", + mediaType, + ) + } + + if charset != "" && strings.ToUpper(charset) != "UTF-8" { + t.Errorf( + "Content-Type charset: utf-8 or no charset expected, got: %s", + charset, + ) + } } func ContentEncodingIsGzip(t *testing.T, r *httptest.ResponseRecorder) { @@ -103,7 +120,7 @@ func (rd *Recorded) HeaderIs(headerKey, expectedValue string) { } func (rd *Recorded) ContentTypeIsJson() { - rd.HeaderIs("Content-Type", "application/json") + ContentTypeIsJson(rd.T, rd.Recorder) } func (rd *Recorded) ContentEncodingIsGzip() { From 966a4165151dafad006646822f935b827fb9d728 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 00:41:39 +0000 Subject: [PATCH 21/53] Move some Content Type checking tests As a preparation to remove the deprecated ResourceHandler, make sure no test is loast by dispatching the content of handler_test.go to the right places. This changeset is about the Content Type checking tests. --- rest/content_type_checker_test.go | 6 ++++++ rest/handler_test.go | 31 ------------------------------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/rest/content_type_checker_test.go b/rest/content_type_checker_test.go index d8fc7d0..0f819bc 100644 --- a/rest/content_type_checker_test.go +++ b/rest/content_type_checker_test.go @@ -39,4 +39,10 @@ func TestContentTypeCheckerMiddleware(t *testing.T) { req.Header.Set("Content-Type", "text/x-json") recorded = test.RunRequest(t, handler, req) recorded.CodeIs(415) + + // JSON payload with correct content type but incorrect charset + req = test.MakeSimpleRequest("POST", "http://localhost/", map[string]string{"Id": "123"}) + req.Header.Set("Content-Type", "application/json; charset=ISO-8859-1") + recorded = test.RunRequest(t, handler, req) + recorded.CodeIs(415) } diff --git a/rest/handler_test.go b/rest/handler_test.go index a85d438..d9a535e 100644 --- a/rest/handler_test.go +++ b/rest/handler_test.go @@ -46,37 +46,6 @@ func TestHandler(t *testing.T) { recorded.ContentTypeIsJson() recorded.BodyIs(`{"Id":"123"}`) - // valid post resource - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest( - "POST", "http://1.2.3.4/r/123", &map[string]string{"Test": "Test"})) - recorded.CodeIs(200) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Test":"Test"}`) - - // broken Content-Type post resource - request := test.MakeSimpleRequest("POST", "http://1.2.3.4/r/123", &map[string]string{"Test": "Test"}) - request.Header.Set("Content-Type", "text/html") - recorded = test.RunRequest(t, &handler, request) - recorded.CodeIs(415) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Bad Content-Type or charset, expected 'application/json'"}`) - - // broken Content-Type post resource - request = test.MakeSimpleRequest("POST", "http://1.2.3.4/r/123", &map[string]string{"Test": "Test"}) - request.Header.Set("Content-Type", "application/json; charset=ISO-8859-1") - recorded = test.RunRequest(t, &handler, request) - recorded.CodeIs(415) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Bad Content-Type or charset, expected 'application/json'"}`) - - // Content-Type post resource with charset - request = test.MakeSimpleRequest("POST", "http://1.2.3.4/r/123", &map[string]string{"Test": "Test"}) - request.Header.Set("Content-Type", "application/json;charset=UTF-8") - recorded = test.RunRequest(t, &handler, request) - recorded.CodeIs(200) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Test":"Test"}`) - // auto 405 on undefined route (wrong method) recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("DELETE", "http://1.2.3.4/r/123", nil)) recorded.CodeIs(405) From 392d1448eac9f343d68f54783120b8930db44a01 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 00:50:51 +0000 Subject: [PATCH 22/53] Remove duplicated recover tests As a preparation to remove the deprecated ResourceHandler, make sure no test is lost by dispatching the content of handler_test.go to the right places. This test is already in recover_test.go --- rest/handler_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/rest/handler_test.go b/rest/handler_test.go index d9a535e..996c579 100644 --- a/rest/handler_test.go +++ b/rest/handler_test.go @@ -28,10 +28,6 @@ func TestHandler(t *testing.T) { } w.WriteJson(data) }), - Get("/auto-fails", func(w ResponseWriter, r *Request) { - a := []int{} - _ = a[0] - }), Get("/user-error", func(w ResponseWriter, r *Request) { Error(w, "My error", 500) }), @@ -58,12 +54,6 @@ func TestHandler(t *testing.T) { recorded.ContentTypeIsJson() recorded.BodyIs(`{"Error":"Resource not found"}`) - // auto 500 on unhandled userecorder error - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/auto-fails", nil)) - recorded.CodeIs(500) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Internal Server Error"}`) - // userecorder error recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/user-error", nil)) recorded.CodeIs(500) From 686ae05b1bb018ccafb05d1ce975b5ae5fbb9e5e Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 01:27:38 +0000 Subject: [PATCH 23/53] Move some responseWriter tests As a preparation to remove the deprecated ResourceHandler, make sure no test is lost by dispatching the content of handler_test.go to the right places. This changeset is about the responseWriter tests. --- rest/handler_test.go | 18 ----------------- rest/response_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/rest/handler_test.go b/rest/handler_test.go index 996c579..efc2a2c 100644 --- a/rest/handler_test.go +++ b/rest/handler_test.go @@ -28,12 +28,6 @@ func TestHandler(t *testing.T) { } w.WriteJson(data) }), - Get("/user-error", func(w ResponseWriter, r *Request) { - Error(w, "My error", 500) - }), - Get("/user-notfound", func(w ResponseWriter, r *Request) { - NotFound(w, r) - }), ) // valid get resource @@ -53,16 +47,4 @@ func TestHandler(t *testing.T) { recorded.CodeIs(404) recorded.ContentTypeIsJson() recorded.BodyIs(`{"Error":"Resource not found"}`) - - // userecorder error - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/user-error", nil)) - recorded.CodeIs(500) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"My error"}`) - - // userecorder notfound - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/user-notfound", nil)) - recorded.CodeIs(404) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Resource not found"}`) } diff --git a/rest/response_test.go b/rest/response_test.go index 6de26c8..ba13f38 100644 --- a/rest/response_test.go +++ b/rest/response_test.go @@ -2,6 +2,8 @@ package rest import ( "testing" + + "github.com/ant0ine/go-json-rest/rest/test" ) func TestResponseNotIndent(t *testing.T) { @@ -21,3 +23,46 @@ func TestResponseNotIndent(t *testing.T) { t.Error(expected + " was the expected, but instead got " + gotStr) } } + +// The following tests could instantiate only the reponseWriter, +// but using the Api object allows to use the rest/test utilities, +// and make the tests easier to write. + +func TestWriteJsonResponse(t *testing.T) { + + api := NewApi() + api.SetApp(AppSimple(func(w ResponseWriter, r *Request) { + w.WriteJson(map[string]string{"Id": "123"}) + })) + + recorded := test.RunRequest(t, api.MakeHandler(), test.MakeSimpleRequest("GET", "http://localhost/", nil)) + recorded.CodeIs(200) + recorded.ContentTypeIsJson() + recorded.BodyIs("{\"Id\":\"123\"}") +} + +func TestErrorResponse(t *testing.T) { + + api := NewApi() + api.SetApp(AppSimple(func(w ResponseWriter, r *Request) { + Error(w, "test", 500) + })) + + recorded := test.RunRequest(t, api.MakeHandler(), test.MakeSimpleRequest("GET", "http://localhost/", nil)) + recorded.CodeIs(500) + recorded.ContentTypeIsJson() + recorded.BodyIs("{\"Error\":\"test\"}") +} + +func TestNotFoundResponse(t *testing.T) { + + api := NewApi() + api.SetApp(AppSimple(func(w ResponseWriter, r *Request) { + NotFound(w, r) + })) + + recorded := test.RunRequest(t, api.MakeHandler(), test.MakeSimpleRequest("GET", "http://localhost/", nil)) + recorded.CodeIs(404) + recorded.ContentTypeIsJson() + recorded.BodyIs("{\"Error\":\"Resource not found\"}") +} From ede4afc379e71c3214bfd8f45a8ca20e892857bb Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 01:44:10 +0000 Subject: [PATCH 24/53] Move some router tests As a preparation to remove the deprecated ResourceHandler, make sure no test is lost by dispatching the content of handler_test.go to the right places. This changeset is about the router tests. --- rest/handler_test.go | 50 -------------------------------------------- rest/router_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 50 deletions(-) delete mode 100644 rest/handler_test.go diff --git a/rest/handler_test.go b/rest/handler_test.go deleted file mode 100644 index efc2a2c..0000000 --- a/rest/handler_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package rest - -import ( - "github.com/ant0ine/go-json-rest/rest/test" - "io/ioutil" - "log" - "testing" -) - -func TestHandler(t *testing.T) { - - handler := ResourceHandler{ - DisableJsonIndent: true, - // make the test output less verbose by discarding the error log - ErrorLogger: log.New(ioutil.Discard, "", 0), - } - handler.SetRoutes( - Get("/r/:id", func(w ResponseWriter, r *Request) { - id := r.PathParam("id") - w.WriteJson(map[string]string{"Id": id}) - }), - Post("/r/:id", func(w ResponseWriter, r *Request) { - // JSON echo - data := map[string]string{} - err := r.DecodeJsonPayload(&data) - if err != nil { - t.Fatal(err) - } - w.WriteJson(data) - }), - ) - - // valid get resource - recorded := test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/r/123", nil)) - recorded.CodeIs(200) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Id":"123"}`) - - // auto 405 on undefined route (wrong method) - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("DELETE", "http://1.2.3.4/r/123", nil)) - recorded.CodeIs(405) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Method not allowed"}`) - - // auto 404 on undefined route (wrong path) - recorded = test.RunRequest(t, &handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/s/123", nil)) - recorded.CodeIs(404) - recorded.ContentTypeIsJson() - recorded.BodyIs(`{"Error":"Resource not found"}`) -} diff --git a/rest/router_test.go b/rest/router_test.go index ad732f4..495f434 100644 --- a/rest/router_test.go +++ b/rest/router_test.go @@ -4,6 +4,8 @@ import ( "net/url" "strings" "testing" + + "github.com/ant0ine/go-json-rest/rest/test" ) func TestFindRouteAPI(t *testing.T) { @@ -390,3 +392,47 @@ func TestSimpleExample(t *testing.T) { t.Error("Expected pathMatched to be true") } } + +func TestHttpResponseLayer(t *testing.T) { + + api := NewApi() + router, err := MakeRouter( + Get("/r/:id", func(w ResponseWriter, r *Request) { + id := r.PathParam("id") + w.WriteJson(map[string]string{"Id": id}) + }), + Post("/r/:id", func(w ResponseWriter, r *Request) { + // JSON echo + data := map[string]string{} + err := r.DecodeJsonPayload(&data) + if err != nil { + t.Fatal(err) + } + w.WriteJson(data) + }), + ) + if err != nil { + t.Fatal(err) + } + api.SetApp(router) + + handler := api.MakeHandler() + + // valid get resource + recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/r/123", nil)) + recorded.CodeIs(200) + recorded.ContentTypeIsJson() + recorded.BodyIs(`{"Id":"123"}`) + + // auto 405 on undefined route (wrong method) + recorded = test.RunRequest(t, handler, test.MakeSimpleRequest("DELETE", "http://1.2.3.4/r/123", nil)) + recorded.CodeIs(405) + recorded.ContentTypeIsJson() + recorded.BodyIs(`{"Error":"Method not allowed"}`) + + // auto 404 on undefined route (wrong path) + recorded = test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://1.2.3.4/s/123", nil)) + recorded.CodeIs(404) + recorded.ContentTypeIsJson() + recorded.BodyIs(`{"Error":"Resource not found"}`) +} From 3f29261be5621dc18627ed8d12948468b728b70e Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 01:46:02 +0000 Subject: [PATCH 25/53] Fix old typo --- rest/api_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rest/api_test.go b/rest/api_test.go index eb4b3aa..269edfc 100644 --- a/rest/api_test.go +++ b/rest/api_test.go @@ -14,7 +14,7 @@ func TestApiNoAppNoMiddleware(t *testing.T) { handler := api.MakeHandler() if handler == nil { - t.Fatal("the http.Handler must be have been create") + t.Fatal("the http.Handler must have been created") } recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil)) @@ -30,7 +30,7 @@ func TestApiSimpleAppNoMiddleware(t *testing.T) { handler := api.MakeHandler() if handler == nil { - t.Fatal("the http.Handler must be have been create") + t.Fatal("the http.Handler must have been created") } recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil)) @@ -49,7 +49,7 @@ func TestDevStack(t *testing.T) { handler := api.MakeHandler() if handler == nil { - t.Fatal("the http.Handler must be have been create") + t.Fatal("the http.Handler must have been created") } recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil)) @@ -68,7 +68,7 @@ func TestProdStack(t *testing.T) { handler := api.MakeHandler() if handler == nil { - t.Fatal("the http.Handler must be have been create") + t.Fatal("the http.Handler must have been created") } recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil)) @@ -87,7 +87,7 @@ func TestCommonStack(t *testing.T) { handler := api.MakeHandler() if handler == nil { - t.Fatal("the http.Handler must be have been create") + t.Fatal("the http.Handler must have been created") } recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil)) From f2142cde884de3ed7d7d21d8afb3f068a075e6df Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 01:50:51 +0000 Subject: [PATCH 26/53] Remove ResourceHandler that was marked as deprecated in v3.0.0 v3.0.0 was a big API change for Go-Json-Rest. ResourceHandler was marked as deprecated (doc + warning log). This commit finally removes this object from the code. --- rest/handler.go | 191 ------------------------------------------------ 1 file changed, 191 deletions(-) delete mode 100644 rest/handler.go diff --git a/rest/handler.go b/rest/handler.go deleted file mode 100644 index e038cf7..0000000 --- a/rest/handler.go +++ /dev/null @@ -1,191 +0,0 @@ -package rest - -import ( - "log" - "net/http" -) - -// ResourceHandler implements the http.Handler interface and acts a router for the defined Routes. -// The defaults are intended to be developemnt friendly, for production you may want -// to turn on gzip and disable the JSON indentation for instance. -// ResourceHandler is now DEPRECATED in favor of the new Api object. See the migration guide. -type ResourceHandler struct { - internalRouter *router - statusMiddleware *StatusMiddleware - handlerFunc http.HandlerFunc - - // If true, and if the client accepts the Gzip encoding, the response payloads - // will be compressed using gzip, and the corresponding response header will set. - EnableGzip bool - - // If true, the JSON payload will be written in one line with no space. - DisableJsonIndent bool - - // If true, the status service will be enabled. Various stats and status will - // then be available at GET /.status in a JSON format. - EnableStatusService bool - - // If true, when a "panic" happens, the error string and the stack trace will be - // printed in the 500 response body. - EnableResponseStackTrace bool - - // If true, the records logged to the access log and the error log will be - // printed as JSON. Convenient for log parsing. - // See the AccessLogJsonRecord type for details of the access log JSON record. - EnableLogAsJson bool - - // If true, the handler does NOT check the request Content-Type. Otherwise, it - // must be set to 'application/json' if the content is non-null. - // Note: If a charset parameter exists, it MUST be UTF-8 - EnableRelaxedContentType bool - - // Optional global middlewares that can be used to wrap the all REST endpoints. - // They are used in the defined order, the first wrapping the second, ... - // They are run first, wrapping all go-json-rest middlewares, - // * request.PathParams is not set yet - // * "panic" won't be caught and converted to 500 - // * request.Env["STATUS_CODE"] and request.Env["ELAPSED_TIME"] are set. - // They can be used for extra logging, or reporting. - // (see statsd example in in https://github.com/ant0ine/go-json-rest-examples) - OuterMiddlewares []Middleware - - // Optional global middlewares that can be used to wrap the all REST endpoints. - // They are used in the defined order, the first wrapping the second, ... - // They are run pre REST routing, request.PathParams is not set yet. - // They are run post auto error handling, "panic" will be converted to 500 errors. - // They can be used for instance to manage CORS or authentication. - // (see CORS and Auth examples in https://github.com/ant0ine/go-json-rest-examples) - PreRoutingMiddlewares []Middleware - - // Custom logger for the access log, - // optional, defaults to log.New(os.Stderr, "", 0) - Logger *log.Logger - - // Define the format of the access log record. - // When EnableLogAsJson is false, this format is used to generate the access log. - // See AccessLogFormat for the options and the predefined formats. - // Defaults to a developement friendly format specified by the Default constant. - LoggerFormat AccessLogFormat - - // If true, the access log will be fully disabled. - // (the log middleware is not even instantiated, avoiding any performance penalty) - DisableLogger bool - - // Custom logger used for logging the panic errors, - // optional, defaults to log.New(os.Stderr, "", 0) - ErrorLogger *log.Logger - - // Custom X-Powered-By value, defaults to "go-json-rest". - XPoweredBy string - - // If true, the X-Powered-By header will NOT be set. - DisableXPoweredBy bool -} - -// SetRoutes defines the Routes. The order the Routes matters, -// if a request matches multiple Routes, the first one will be used. -func (rh *ResourceHandler) SetRoutes(routes ...*Route) error { - - log.Print("ResourceHandler is now DEPRECATED in favor of the new Api object, see the migration guide") - - // intantiate all the middlewares based on the settings. - middlewares := []Middleware{} - - middlewares = append(middlewares, - rh.OuterMiddlewares..., - ) - - // log as the first, depends on timer and recorder. - if !rh.DisableLogger { - if rh.EnableLogAsJson { - middlewares = append(middlewares, - &AccessLogJsonMiddleware{ - Logger: rh.Logger, - }, - ) - } else { - middlewares = append(middlewares, - &AccessLogApacheMiddleware{ - Logger: rh.Logger, - Format: rh.LoggerFormat, - }, - ) - } - } - - // also depends on timer and recorder - if rh.EnableStatusService { - // keep track of this middleware for GetStatus() - rh.statusMiddleware = &StatusMiddleware{} - middlewares = append(middlewares, rh.statusMiddleware) - } - - // after gzip in order to track to the content length and speed - middlewares = append(middlewares, - &TimerMiddleware{}, - &RecorderMiddleware{}, - ) - - if rh.EnableGzip { - middlewares = append(middlewares, &GzipMiddleware{}) - } - - if !rh.DisableXPoweredBy { - middlewares = append(middlewares, - &PoweredByMiddleware{ - XPoweredBy: rh.XPoweredBy, - }, - ) - } - - if !rh.DisableJsonIndent { - middlewares = append(middlewares, &JsonIndentMiddleware{}) - } - - // catch user errors - middlewares = append(middlewares, - &RecoverMiddleware{ - Logger: rh.ErrorLogger, - EnableLogAsJson: rh.EnableLogAsJson, - EnableResponseStackTrace: rh.EnableResponseStackTrace, - }, - ) - - middlewares = append(middlewares, - rh.PreRoutingMiddlewares..., - ) - - // verify the request content type - if !rh.EnableRelaxedContentType { - middlewares = append(middlewares, - &ContentTypeCheckerMiddleware{}, - ) - } - - // instantiate the router - rh.internalRouter = &router{ - Routes: routes, - } - err := rh.internalRouter.start() - if err != nil { - return err - } - - // wrap everything - rh.handlerFunc = adapterFunc( - WrapMiddlewares(middlewares, rh.internalRouter.AppFunc()), - ) - - return nil -} - -// This makes ResourceHandler implement the http.Handler interface. -// You probably don't want to use it directly. -func (rh *ResourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - rh.handlerFunc(w, r) -} - -// GetStatus returns a Status object. EnableStatusService must be true. -func (rh *ResourceHandler) GetStatus() *Status { - return rh.statusMiddleware.GetStatus() -} From a7ea7895264f2b2bd124245fd9dbf85b0ac64da1 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 01:57:31 +0000 Subject: [PATCH 27/53] Improve error string, thanks @wichert --- rest/test/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/test/util.go b/rest/test/util.go index 3b59ba3..9f1b77a 100644 --- a/rest/test/util.go +++ b/rest/test/util.go @@ -70,7 +70,7 @@ func ContentTypeIsJson(t *testing.T, r *httptest.ResponseRecorder) { if charset != "" && strings.ToUpper(charset) != "UTF-8" { t.Errorf( - "Content-Type charset: utf-8 or no charset expected, got: %s", + "Content-Type charset: must be empty or UTF-8, got: %s", charset, ) } From 79b3f4084944b05ce8f518b60105d5bb127cf52b Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 02:06:53 +0000 Subject: [PATCH 28/53] Run gofmt -s --- rest/request_test.go | 2 +- rest/router_test.go | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/rest/request_test.go b/rest/request_test.go index 66424ac..78c0a2c 100644 --- a/rest/request_test.go +++ b/rest/request_test.go @@ -68,7 +68,7 @@ func TestRequestUrlForQueryString(t *testing.T) { req := defaultRequest("GET", "http://localhost", nil, t) params := map[string][]string{ - "id": []string{"foo", "bar"}, + "id": {"foo", "bar"}, } urlObj := req.UrlFor("/foo/bar", params) diff --git a/rest/router_test.go b/rest/router_test.go index 495f434..6dfc521 100644 --- a/rest/router_test.go +++ b/rest/router_test.go @@ -12,7 +12,7 @@ func TestFindRouteAPI(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/", }, @@ -105,7 +105,7 @@ func TestEmptyPathExp(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "", }, @@ -122,7 +122,7 @@ func TestInvalidPathExp(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "invalid", }, @@ -139,7 +139,7 @@ func TestUrlEncodedFind(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/with space", // not urlencoded }, @@ -168,7 +168,7 @@ func TestWithQueryString(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/r/:id", }, @@ -200,7 +200,7 @@ func TestNonUrlEncodedFind(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/with%20space", // urlencoded }, @@ -229,11 +229,11 @@ func TestDuplicatedRoute(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/", }, - &Route{ + { HttpMethod: "GET", PathExp: "/", }, @@ -250,7 +250,7 @@ func TestSplatUrlEncoded(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/r/*rest", }, @@ -282,11 +282,11 @@ func TestRouteOrder(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/r/:id", }, - &Route{ + { HttpMethod: "GET", PathExp: "/r/*rest", }, @@ -321,11 +321,11 @@ func TestRelaxedPlaceholder(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/r/:id", }, - &Route{ + { HttpMethod: "GET", PathExp: "/r/#filename", }, @@ -360,11 +360,11 @@ func TestSimpleExample(t *testing.T) { r := router{ Routes: []*Route{ - &Route{ + { HttpMethod: "GET", PathExp: "/resources/:id", }, - &Route{ + { HttpMethod: "GET", PathExp: "/resources", }, From acf7d2c29bbee0a8a1865358f323bb9991150637 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 02:08:43 +0000 Subject: [PATCH 29/53] Update Travis-CI to test 1.3 1.4 1.5.2 --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index bce1574..d44db9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ sudo: false language: go go: - - 1.1 - - 1.2.1 - 1.3 - 1.4 - - 1.5.1 + - 1.5.2 From 01a517a7065ab0030bf286c6a8e5abc9b245c776 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 6 Dec 2015 03:47:29 +0000 Subject: [PATCH 30/53] [README autogen] remove useless struct. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 36d68a0..b81dc4a 100644 --- a/README.md +++ b/README.md @@ -166,10 +166,6 @@ import ( "net/http" ) -type Message struct { - Body string -} - func main() { api := rest.NewApi() api.Use(rest.DefaultDevStack...) From 0a9a1040b5fc77b312296b99945c55967f68e9e4 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 22 Dec 2015 04:45:51 +0000 Subject: [PATCH 31/53] [Auto-gen README] Add Force HTTPS example by @jadengore --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index b81dc4a..e373ff0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - [CORS](#cors) - [JSONP](#jsonp) - [Basic Auth](#basic-auth) + - [Force HTTPS](#forcessl) - [Status](#status) - [Status Auth](#status-auth) - [Advanced](#advanced) @@ -778,6 +779,49 @@ func main() { ``` +#### ForceSSL + +Demonstrate how to use the [ForceSSL Middleware](https://github.com/jadengore/go-json-rest-middleware-force-ssl) to force HTTPS on requests to a `go-json-rest` API. + +For the purposes of this demo, we are using HTTP for all requests and checking the `X-Forwarded-Proto` header to see if it is set to HTTPS (many routers set this to show what type of connection the client is using, such as Heroku). To do a true HTTPS test, make sure and use [`http.ListenAndServeTLS`](https://golang.org/pkg/net/http/#ListenAndServeTLS) with a valid certificate and key file. + +Additional documentation for the ForceSSL middleware can be found [here](https://github.com/jadengore/go-json-rest-middleware-force-ssl). + +curl demo: +``` sh +curl -i 127.0.0.1:8080/ +curl -H "X-Forwarded-Proto:https" -i 127.0.0.1:8080/ +``` + +code: +``` go +package main + +import ( + "github.com/ant0ine/go-json-rest/rest" + "github.com/jadengore/go-json-rest-middleware-force-ssl" + "log" + "net/http" +) + +func main() { + api := rest.NewApi() + api.Use(&forceSSL.Middleware{ + TrustXFPHeader: true, + Enable301Redirects: false, + }) + api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) { + w.WriteJson(map[string]string{"body": "Hello World!"}) + })) + + // For the purposes of this demo, only HTTP connections accepted. + // For true HTTPS, use ListenAndServeTLS. + // https://golang.org/pkg/net/http/#ListenAndServeTLS + log.Fatal(http.ListenAndServe(":8080", api.MakeHandler())) +} + +``` + #### Status Demonstrate how to setup a `/.status` endpoint From eab48093ffe42b1d6d2774db2e6bdc2ea83c71c0 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 22 Dec 2015 04:53:21 +0000 Subject: [PATCH 32/53] [Auto-gen README] New third party middleware link --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e373ff0..5822906 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Third Party Middlewares: | **[Statsd](https://github.com/ant0ine/go-json-rest-middleware-statsd)** | Send stats to a statsd server | | **[JWT](https://github.com/StephanDollberg/go-json-rest-middleware-jwt)** | Provides authentication via Json Web Tokens | | **[AuthToken](https://github.com/grayj/go-json-rest-middleware-tokenauth)** | Provides a Token Auth implementation | +| **[ForceSSL](https://github.com/jadengore/go-json-rest-middleware-force-ssl)** | Forces SSL on requests | | **[SecureRedirect](https://github.com/clyphub/go-json-rest-middleware)** | Redirect clients from HTTP to HTTPS | *If you have a Go-Json-Rest compatible middleware, feel free to submit a PR to add it in this list, and in the examples.* @@ -1794,6 +1795,7 @@ Overall, they provide the same features, but with two methods instead of three, - [Paul Lam](https://github.com/Quantisan) - [Thanabodee Charoenpiriyakij](https://github.com/wingyplus) - [Sebastien Estienne](https://github.com/sebest) +- [Edward Bramanti](https://github.com/jadengore) Copyright (c) 2013-2015 Antoine Imbert From 61ebd1631ca65b0361588dc5b58890f779be234a Mon Sep 17 00:00:00 2001 From: antoine Date: Sat, 2 Jan 2016 23:03:20 +0000 Subject: [PATCH 33/53] Happy New Year! --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 9b48172..7800c4b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2015 Antoine Imbert +Copyright (c) 2013-2016 Antoine Imbert The MIT License diff --git a/README.md b/README.md index 5822906..95938b9 100644 --- a/README.md +++ b/README.md @@ -1798,7 +1798,7 @@ Overall, they provide the same features, but with two methods instead of three, - [Edward Bramanti](https://github.com/jadengore) -Copyright (c) 2013-2015 Antoine Imbert +Copyright (c) 2013-2016 Antoine Imbert [MIT License](https://github.com/ant0ine/go-json-rest/blob/master/LICENSE) From edea6f156cf969361c91d50137a975b22b52b5da Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 11 Jan 2016 02:36:49 +0000 Subject: [PATCH 34/53] [Auto-gen README] Changes suggested by @ReadmeCritic --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 95938b9..69a9ced 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ This package is "go-gettable", just do: The recommended way of using this library in your project is to use the **"vendoring"** method, where this library code is copied in your repository at a specific revision. -[This page](http://nathany.com/go-packages/) is a good summary of package management in Go. +[This page](https://nathany.com/go-packages/) is a good summary of package management in Go. ## Middlewares @@ -1243,7 +1243,7 @@ func main() { func(w rest.ResponseWriter, req *rest.Request) { version := req.Env["VERSION"].(*semver.Version) if version.Major == 2 { - // http://en.wikipedia.org/wiki/Second-system_effect + // https://en.wikipedia.org/wiki/Second-system_effect w.WriteJson(map[string]string{ "Body": "Hello broken World!", }) @@ -1311,7 +1311,7 @@ func main() { #### NewRelic -NewRelic integration based on the GoRelic plugin: [github.com/yvasiyarov/gorelic](http://github.com/yvasiyarov/gorelic) +NewRelic integration based on the GoRelic plugin: [github.com/yvasiyarov/gorelic](https://github.com/yvasiyarov/gorelic) curl demo: ``` sh @@ -1376,7 +1376,7 @@ func main() { #### Graceful Shutdown -This example uses [github.com/stretchr/graceful](https://github.com/stretchr/graceful) to try to be nice with the clients waiting for responses during a server shutdown (or restart). +This example uses [https://github.com/tylerb/graceful](https://github.com/tylerb/graceful) to try to be nice with the clients waiting for responses during a server shutdown (or restart). The HTTP response takes 10 seconds to be completed, printing a message on the wire every second. 10 seconds is also the timeout set for the graceful shutdown. You can play with these numbers to show that the server waits for the responses to complete. @@ -1393,7 +1393,7 @@ package main import ( "fmt" "github.com/ant0ine/go-json-rest/rest" - "github.com/stretchr/graceful" + "gopkg.in/tylerb/graceful.v1" "log" "net/http" "time" @@ -1683,7 +1683,7 @@ In fact the internal code of **go-json-rest** is itself implemented with Middlew #### The import path has changed to `github.com/ant0ine/go-json-rest/rest` -This is more conform to Go style, and makes [goimports](https://godoc.org/code.google.com/p/go.tools/cmd/goimports) work. +This is more conform to Go style, and makes [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) work. This: ``` go From 37ad7dc626602a8355801da6acd85155655520f8 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 19 Jan 2016 00:01:23 +0000 Subject: [PATCH 35/53] bump the Go version to test --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d44db9f..f2ea74d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ language: go go: - 1.3 - 1.4 - - 1.5.2 + - 1.5.3 From dea43f52281de5022cd0718930f60b89dab2a683 Mon Sep 17 00:00:00 2001 From: Matthew Schick Date: Wed, 6 Apr 2016 23:26:23 -0400 Subject: [PATCH 36/53] Use net.SplitHostPort so ipv6 addresses get parsed properly --- rest/access_log_apache.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rest/access_log_apache.go b/rest/access_log_apache.go index 72761f8..e38b8ca 100644 --- a/rest/access_log_apache.go +++ b/rest/access_log_apache.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "log" + "net" "os" "strings" "text/template" @@ -195,8 +196,9 @@ func (u *accessLogUtil) StartTime() *time.Time { func (u *accessLogUtil) ApacheRemoteAddr() string { remoteAddr := u.R.RemoteAddr if remoteAddr != "" { - parts := strings.SplitN(remoteAddr, ":", 2) - return parts[0] + if ip, _, err := net.SplitHostPort(remoteAddr); err == nil { + return ip + } } return "" } From 1c6dbaaf3c0a6bdae4a19f7b0b322a886a56e203 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 10 Apr 2016 22:07:21 +0000 Subject: [PATCH 37/53] Ask Travis to test with Go1.6 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f2ea74d..0343061 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,4 @@ go: - 1.3 - 1.4 - 1.5.3 + - 1.6 From 0b89f68216f29f0fdb2ef236c2168e3c817d5884 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 10 Apr 2016 22:47:58 +0000 Subject: [PATCH 38/53] Fix Apache microsecond logging The ResponseTime may not be available if the timer middleware is not included in the stack. Handle the nil pointer. --- rest/access_log_apache.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest/access_log_apache.go b/rest/access_log_apache.go index e38b8ca..d82894a 100644 --- a/rest/access_log_apache.go +++ b/rest/access_log_apache.go @@ -132,7 +132,10 @@ func (mw *AccessLogApacheMiddleware) convertFormat() { return fmt.Sprintf("%d", value) }, "microseconds": func(dur *time.Duration) string { - return fmt.Sprintf("%d", dur.Nanoseconds()/1000) + if dur != nil { + return fmt.Sprintf("%d", dur.Nanoseconds()/1000) + } + return "" }, "statusCodeColor": func(statusCode int) string { if statusCode >= 400 && statusCode < 500 { From cfd0da14df1db088980175e1c3c7e77c11b90718 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 1 May 2016 18:27:56 +0000 Subject: [PATCH 39/53] Update Gorm example, the Gorm API has changed. Make the FB field a pointer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69a9ced..8979ec1 100644 --- a/README.md +++ b/README.md @@ -556,7 +556,7 @@ type Reminder struct { } type Impl struct { - DB gorm.DB + DB *gorm.DB } func (i *Impl) InitDB() { From 9c33be62e15d98042dadc93efa334a0c69ca425e Mon Sep 17 00:00:00 2001 From: Ivan Kishchenko Date: Tue, 10 May 2016 15:29:53 +0700 Subject: [PATCH 40/53] Close gzip writer --- rest/gzip.go | 6 ++++++ rest/recorder_test.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rest/gzip.go b/rest/gzip.go index 10f4e9d..0fafc05 100644 --- a/rest/gzip.go +++ b/rest/gzip.go @@ -21,6 +21,12 @@ func (mw *GzipMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc { canGzip := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") // client accepts gzip ? writer := &gzipResponseWriter{w, false, canGzip, nil} + defer func() { + // need to close gzip writer + if writer.gzipWriter != nil { + writer.gzipWriter.Close() + } + }() // call the handler with the wrapped writer h(writer, r) } diff --git a/rest/recorder_test.go b/rest/recorder_test.go index c02b846..61c098a 100644 --- a/rest/recorder_test.go +++ b/rest/recorder_test.go @@ -67,7 +67,7 @@ func TestRecorderAndGzipMiddleware(t *testing.T) { } bytesWritten := r.Env["BYTES_WRITTEN"].(int64) // Yes, the gzipped version actually takes more space. - if bytesWritten != 28 { + if bytesWritten != 41 { t.Errorf("BYTES_WRITTEN 28 expected, got %d", bytesWritten) } } From 28f83d71c05db7b40aa3f543df0585bd8f9f5928 Mon Sep 17 00:00:00 2001 From: Ivan Kishchenko Date: Tue, 10 May 2016 19:37:07 +0700 Subject: [PATCH 41/53] Fix test message --- rest/recorder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/recorder_test.go b/rest/recorder_test.go index 61c098a..ebb07f8 100644 --- a/rest/recorder_test.go +++ b/rest/recorder_test.go @@ -68,7 +68,7 @@ func TestRecorderAndGzipMiddleware(t *testing.T) { bytesWritten := r.Env["BYTES_WRITTEN"].(int64) // Yes, the gzipped version actually takes more space. if bytesWritten != 41 { - t.Errorf("BYTES_WRITTEN 28 expected, got %d", bytesWritten) + t.Errorf("BYTES_WRITTEN 41 expected, got %d", bytesWritten) } } })) From 4f1814e6e38174c46a6cccdda906987d04b735f6 Mon Sep 17 00:00:00 2001 From: antoine Date: Sun, 28 Aug 2016 00:39:24 +0000 Subject: [PATCH 42/53] Update Travis CI to test Go1.7 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0343061..d58668b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ language: go go: - 1.3 - 1.4 - - 1.5.3 + - 1.5 - 1.6 + - 1.7 From e4a074e0240ac0fdba457b355194125ef1df798a Mon Sep 17 00:00:00 2001 From: mgkeen Date: Tue, 18 Oct 2016 10:20:09 +0100 Subject: [PATCH 43/53] Recorder records same status code as underlying net/http only allows you to set the status code on the response once. Subsequent calls get ignored, however were being recorded by the recorder. This led to a discrepency in the actual response and what is shown in the logs when using the AccessLogJson middleware. --- rest/recorder.go | 3 +++ rest/recorder_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/rest/recorder.go b/rest/recorder.go index 6cebfa2..20502e9 100644 --- a/rest/recorder.go +++ b/rest/recorder.go @@ -44,6 +44,9 @@ type recorderResponseWriter struct { // Record the status code. func (w *recorderResponseWriter) WriteHeader(code int) { w.ResponseWriter.WriteHeader(code) + if w.wroteHeader { + return + } w.statusCode = code w.wroteHeader = true } diff --git a/rest/recorder_test.go b/rest/recorder_test.go index ebb07f8..c3dabd2 100644 --- a/rest/recorder_test.go +++ b/rest/recorder_test.go @@ -1,8 +1,9 @@ package rest import ( - "github.com/ant0ine/go-json-rest/rest/test" "testing" + + "github.com/ant0ine/go-json-rest/rest/test" ) func TestRecorderMiddleware(t *testing.T) { @@ -91,3 +92,43 @@ func TestRecorderAndGzipMiddleware(t *testing.T) { recorded.CodeIs(200) recorded.ContentTypeIsJson() } + +//Underlying net/http only allows you to set the status code once +func TestRecorderMiddlewareReportsSameStatusCodeAsResponse(t *testing.T) { + api := NewApi() + const firstCode = 400 + const secondCode = 500 + + // a middleware carrying the Env tests + api.Use(MiddlewareSimple(func(handler HandlerFunc) HandlerFunc { + return func(w ResponseWriter, r *Request) { + + handler(w, r) + + if r.Env["STATUS_CODE"] == nil { + t.Error("STATUS_CODE is nil") + } + statusCode := r.Env["STATUS_CODE"].(int) + if statusCode != firstCode { + t.Errorf("STATUS_CODE = %d expected, got %d", firstCode, statusCode) + } + } + })) + + // the middleware to test + api.Use(&RecorderMiddleware{}) + + // a simple app + api.SetApp(AppSimple(func(w ResponseWriter, r *Request) { + w.WriteHeader(firstCode) + w.WriteHeader(secondCode) + })) + + // wrap all + handler := api.MakeHandler() + + req := test.MakeSimpleRequest("GET", "http://localhost/", nil) + recorded := test.RunRequest(t, handler, req) + recorded.CodeIs(firstCode) + recorded.ContentTypeIsJson() +} From af20cdfe1da2ce575bbbfcc283a3e8df4865b51e Mon Sep 17 00:00:00 2001 From: Antoine Imbert Date: Sun, 6 Nov 2016 00:04:50 +0000 Subject: [PATCH 44/53] Update blog post URLs blog migration to www.ant0ine.com --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8979ec1..b57663d 100644 --- a/README.md +++ b/README.md @@ -1603,8 +1603,8 @@ func main() { Old v1 blog posts: -- [(Blog Post) Introducing Go-Json-Rest] (http://blog.ant0ine.com/typepad/2013/04/introducing-go-json-rest.html) -- [(Blog Post) Better URL Routing ?] (http://blog.ant0ine.com/typepad/2013/02/better-url-routing-golang-1.html) +- [(Blog Post) Introducing Go-Json-Rest] (https://www.ant0ine.com/post/introducing-go-json-rest.html) +- [(Blog Post) Better URL Routing ?] (https://www.ant0ine.com/post/better-url-routing-golang.html) ## Version 3 release notes From 9ec7f2b210af61f2323ccb6528f861fc363f7713 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 30 Nov 2016 12:21:47 +0100 Subject: [PATCH 45/53] rest/request: skip empty strings in values of Access-Control-Request-Headers WebKit browsers may send a preflight CORS request setting Access-Control-Request-Headers to an empty value, what is in turn interpreted as an empty string on Golang's http server side. An example scenario that triggers this behavior in Chrome is doing a xhr POST request and setting progress callback. Request headers reported by browser's developer tools are then the following: :authority:docker.mender.io:8080 :method:OPTIONS :path:/api/integrations/0.1/deployments/images :scheme:https accept:*/* accept-encoding:gzip, deflate, sdch, br accept-language:en-US,en;q=0.8,pl;q=0.6 access-control-request-headers: <--- empty value here access-control-request-method:POST dnt:1 origin:http://localhost:9999 referer:http://localhost:9999/test.html user-agent:Mozilla/5.0 (X11; Linux x86_64) ... It is unclear whether in such case, the client wants to send no headers in the actual request or just a bug in client's code. Since the original request is cured and repacked into CorsInfo it makes sense to skip Access-Control-Request-Headers values that are empty. Signed-off-by: Maciej Borzecki --- rest/request.go | 3 +++ rest/request_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/rest/request.go b/rest/request.go index 9d1d792..f3113ef 100644 --- a/rest/request.go +++ b/rest/request.go @@ -120,6 +120,9 @@ func (r *Request) GetCorsInfo() *CorsInfo { reqHeaders := []string{} rawReqHeaders := r.Header[http.CanonicalHeaderKey("Access-Control-Request-Headers")] for _, rawReqHeader := range rawReqHeaders { + if len(rawReqHeader) == 0 { + continue + } // net/http does not handle comma delimited headers for us for _, reqHeader := range strings.Split(rawReqHeader, ",") { reqHeaders = append(reqHeaders, http.CanonicalHeaderKey(strings.TrimSpace(reqHeader))) diff --git a/rest/request_test.go b/rest/request_test.go index 78c0a2c..4186fee 100644 --- a/rest/request_test.go +++ b/rest/request_test.go @@ -148,3 +148,41 @@ func TestCorsInfoPreflightCors(t *testing.T) { t.Error("OriginUrl must be set") } } + +func TestCorsInfoEmptyAccessControlRequestHeaders(t *testing.T) { + req := defaultRequest("OPTIONS", "http://localhost", nil, t) + req.Request.Header.Set("Origin", "http://another.host") + + // make it a preflight request + req.Request.Header.Set("Access-Control-Request-Method", "PUT") + + // WebKit based browsers may send `Access-Control-Request-Headers:` with + // no value, in which case, the header will be present in requests + // Header map, but its value is an empty string. + req.Request.Header.Set("Access-Control-Request-Headers", "") + corsInfo := req.GetCorsInfo() + if corsInfo == nil { + t.Error("Expected non nil CorsInfo") + } + if corsInfo.IsCors == false { + t.Error("This is a CORS request") + } + if len(corsInfo.AccessControlRequestHeaders) > 0 { + t.Error("Access-Control-Request-Headers should have been removed") + } + + req.Request.Header.Set("Access-Control-Request-Headers", "") + corsInfo = req.GetCorsInfo() + if corsInfo == nil { + t.Error("Expected non nil CorsInfo") + } + if corsInfo.IsCors == false { + t.Error("This is a CORS request") + } + if corsInfo.IsPreflight == false { + t.Error("This is a Preflight request") + } + if len(corsInfo.AccessControlRequestHeaders) > 0 { + t.Error("Empty Access-Control-Request-Headers header should have been removed") + } +} From 7535cd05be2a341b2bc63799092fdd87b9a3d7d2 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 30 Nov 2016 12:28:09 +0100 Subject: [PATCH 46/53] rest/cors_test: tests for empty Access-Control-Request-Headers in preflight requests Signed-off-by: Maciej Borzecki --- rest/cors_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 rest/cors_test.go diff --git a/rest/cors_test.go b/rest/cors_test.go new file mode 100644 index 0000000..09bbbc4 --- /dev/null +++ b/rest/cors_test.go @@ -0,0 +1,43 @@ +package rest + +import ( + "net/http" + "testing" + + "github.com/ant0ine/go-json-rest/rest/test" +) + +func TestCorsMiddlewareEmptyAccessControlRequestHeaders(t *testing.T) { + api := NewApi() + + // the middleware to test + api.Use(&CorsMiddleware{ + OriginValidator: func(_ string, _ *Request) bool { + return true + }, + AllowedMethods: []string{ + "GET", + "POST", + "PUT", + }, + AllowedHeaders: []string{ + "Origin", + "Referer", + }, + }) + + // wrap all + handler := api.MakeHandler() + + req, _ := http.NewRequest("OPTIONS", "http://localhost", nil) + req.Header.Set("Origin", "http://another.host") + req.Header.Set("Access-Control-Request-Method", "PUT") + req.Header.Set("Access-Control-Request-Headers", "") + + recorded := test.RunRequest(t, handler, req) + t.Logf("recorded: %+v\n", recorded.Recorder) + recorded.CodeIs(200) + recorded.HeaderIs("Access-Control-Allow-Methods", "GET,POST,PUT") + recorded.HeaderIs("Access-Control-Allow-Headers", "Origin,Referer") + recorded.HeaderIs("Access-Control-Allow-Origin", "http://another.host") +} From 633093c88eac03d3d672dacf7fe042ec26fc4124 Mon Sep 17 00:00:00 2001 From: Anthony Alves Date: Tue, 14 Feb 2017 23:58:26 -0500 Subject: [PATCH 47/53] Fix HTTPS Scheme when using HTTP/2.0 When using HTTP/2.0 the default scheme given is http. Check if the server is running HTTP/2.0 and TLS is not nil --- rest/request.go | 6 ++++++ rest/request_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/rest/request.go b/rest/request.go index f3113ef..bba2146 100644 --- a/rest/request.go +++ b/rest/request.go @@ -55,6 +55,12 @@ func (r *Request) BaseUrl() *url.URL { scheme = "http" } + // HTTP/2.0 gives the default scheme as HTTP even when used with TLS + // Check if version 2.0 and TLS is not nil and given back https scheme + if scheme == "http" && r.ProtoMajor >= 2 && r.TLS != nil { + scheme = "https" + } + host := r.Host if len(host) > 0 && host[len(host)-1] == '/' { host = host[:len(host)-1] diff --git a/rest/request_test.go b/rest/request_test.go index 4186fee..7151d27 100644 --- a/rest/request_test.go +++ b/rest/request_test.go @@ -5,6 +5,7 @@ import ( "net/http" "strings" "testing" + "crypto/tls" ) func defaultRequest(method string, urlStr string, body io.Reader, t *testing.T) *Request { @@ -48,6 +49,30 @@ func TestRequestUrlScheme(t *testing.T) { } } +func TestRequestUrlSchemeHTTP(t *testing.T) { + req := defaultRequest("GET", "http://localhost", nil, t) + urlBase := req.BaseUrl() + + expected := "http" + if urlBase.Scheme != expected { + t.Error(expected + " was the expected scheme, but instead got " + urlBase.Scheme) + } +} + +func TestRequestUrlSchemeHTTP2TLS(t *testing.T) { + req := defaultRequest("GET", "http://localhost", nil, t) + req.Proto = "HTTP" + req.ProtoMajor = 2 + req.ProtoMinor = 0 + req.TLS = &tls.ConnectionState{} + urlBase := req.BaseUrl() + + expected := "https" + if urlBase.Scheme != expected { + t.Error(expected + " was the expected scheme, but instead got " + urlBase.Scheme) + } +} + func TestRequestUrlFor(t *testing.T) { req := defaultRequest("GET", "http://localhost", nil, t) From 7f746a3ebde7cb9ac6aaa3bd436a141678dc962b Mon Sep 17 00:00:00 2001 From: Anthony Alves Date: Wed, 15 Feb 2017 10:38:00 -0500 Subject: [PATCH 48/53] Remove the check for HTTP2 --- rest/request.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest/request.go b/rest/request.go index bba2146..c4eb381 100644 --- a/rest/request.go +++ b/rest/request.go @@ -55,9 +55,9 @@ func (r *Request) BaseUrl() *url.URL { scheme = "http" } - // HTTP/2.0 gives the default scheme as HTTP even when used with TLS - // Check if version 2.0 and TLS is not nil and given back https scheme - if scheme == "http" && r.ProtoMajor >= 2 && r.TLS != nil { + // HTTP sometimes gives the default scheme as HTTP even when used with TLS + // Check if TLS is not nil and given back https scheme + if scheme == "http" && r.TLS != nil { scheme = "https" } From 4602b00d2caab423578a3094c68137dcc1eb2051 Mon Sep 17 00:00:00 2001 From: Antoine Imbert Date: Sun, 19 Feb 2017 23:16:50 +0000 Subject: [PATCH 49/53] Make travis test go1.8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d58668b..f6ca1c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,4 @@ go: - 1.5 - 1.6 - 1.7 + - 1.8 From d49e89c9c67eb356ac1d25766552b0a6eb359761 Mon Sep 17 00:00:00 2001 From: Adam Thomason Date: Tue, 20 Jun 2017 16:32:19 -0700 Subject: [PATCH 50/53] test: allow helpers to handle gzipped responses While MakeSimpleRequest sets `Accept-Encoding: gzip`, it doesn't itself handle gzipped response bodies. This updates the two functions which currently reference r.Body (BodyIs and DecodeJsonPayload) to use a new helper function DecodedBody which transparently handles gzipped response bodies. A matching convenience method on the Recorded type is added as well. Updates https://github.com/ant0ine/go-json-rest/issues/214 --- rest/test/util.go | 36 +++++++++++++++++++++++++++++++--- rest/test/util_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 rest/test/util_test.go diff --git a/rest/test/util.go b/rest/test/util.go index 9f1b77a..b022099 100644 --- a/rest/test/util.go +++ b/rest/test/util.go @@ -1,8 +1,11 @@ package test import ( + "bytes" + "compress/gzip" "encoding/json" "fmt" + "io" "io/ioutil" "mime" "net/http" @@ -81,14 +84,17 @@ func ContentEncodingIsGzip(t *testing.T, r *httptest.ResponseRecorder) { } func BodyIs(t *testing.T, r *httptest.ResponseRecorder, expectedBody string) { - body := r.Body.String() - if body != expectedBody { + body, err := DecodedBody(r) + if err != nil { + t.Errorf("Body '%s' expected, got error: '%s'", expectedBody, err) + } + if string(body) != expectedBody { t.Errorf("Body '%s' expected, got: '%s'", expectedBody, body) } } func DecodeJsonPayload(r *httptest.ResponseRecorder, v interface{}) error { - content, err := ioutil.ReadAll(r.Body) + content, err := DecodedBody(r) if err != nil { return err } @@ -99,6 +105,26 @@ func DecodeJsonPayload(r *httptest.ResponseRecorder, v interface{}) error { return nil } +// DecodedBody returns the entire body read from r.Body, with it +// gunzipped if Content-Encoding is set to gzip +func DecodedBody(r *httptest.ResponseRecorder) ([]byte, error) { + if r.Header().Get("Content-Encoding") != "gzip" { + return ioutil.ReadAll(r.Body) + } + dec, err := gzip.NewReader(r.Body) + if err != nil { + return nil, err + } + b := new(bytes.Buffer) + if _, err = io.Copy(b, dec); err != nil { + return nil, err + } + if err = dec.Close(); err != nil { + return nil, err + } + return b.Bytes(), nil +} + type Recorded struct { T *testing.T Recorder *httptest.ResponseRecorder @@ -134,3 +160,7 @@ func (rd *Recorded) BodyIs(expectedBody string) { func (rd *Recorded) DecodeJsonPayload(v interface{}) error { return DecodeJsonPayload(rd.Recorder, v) } + +func (rd *Recorded) DecodedBody() ([]byte, error) { + return DecodedBody(rd.Recorder) +} diff --git a/rest/test/util_test.go b/rest/test/util_test.go new file mode 100644 index 0000000..32fe099 --- /dev/null +++ b/rest/test/util_test.go @@ -0,0 +1,44 @@ +package test + +import ( + "compress/gzip" + "io" + "net/http/httptest" + "reflect" + "testing" +) + +func testDecodedBody(t *testing.T, zip bool) { + type Data struct { + N int + } + input := `{"N": 1}` + expectedData := Data{N: 1} + + w := httptest.NewRecorder() + + if zip { + w.Header().Set("Content-Encoding", "gzip") + enc := gzip.NewWriter(w) + io.WriteString(enc, input) + enc.Close() + } else { + io.WriteString(w, input) + } + + var gotData Data + if err := DecodeJsonPayload(w, &gotData); err != nil { + t.Errorf("DecodeJsonPayload error: %s", err) + } + if !reflect.DeepEqual(expectedData, gotData) { + t.Errorf("DecodeJsonPayload expected: %#v, got %#v", expectedData, gotData) + } +} + +func TestDecodedBodyUnzipped(t *testing.T) { + testDecodedBody(t, false) +} + +func TestDecodedBodyZipped(t *testing.T) { + testDecodedBody(t, true) +} From 66f9ff1fe792619d46ca1e65dff633f2e9df607c Mon Sep 17 00:00:00 2001 From: Naoki Kanatani Date: Thu, 20 Jul 2017 16:30:50 +0900 Subject: [PATCH 51/53] Fix two broken links in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b57663d..1efa406 100644 --- a/README.md +++ b/README.md @@ -1603,8 +1603,8 @@ func main() { Old v1 blog posts: -- [(Blog Post) Introducing Go-Json-Rest] (https://www.ant0ine.com/post/introducing-go-json-rest.html) -- [(Blog Post) Better URL Routing ?] (https://www.ant0ine.com/post/better-url-routing-golang.html) +- [(Blog Post) Introducing Go-Json-Rest](https://www.ant0ine.com/post/introducing-go-json-rest.html) +- [(Blog Post) Better URL Routing ?](https://www.ant0ine.com/post/better-url-routing-golang.html) ## Version 3 release notes From 146678b171c7cb8b28406e7de3cde61f97eeda4e Mon Sep 17 00:00:00 2001 From: Antoine Imbert Date: Wed, 13 Sep 2017 04:11:20 +0000 Subject: [PATCH 52/53] go fmt rest/request_test.go --- rest/request_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/request_test.go b/rest/request_test.go index 7151d27..1467c92 100644 --- a/rest/request_test.go +++ b/rest/request_test.go @@ -1,11 +1,11 @@ package rest import ( + "crypto/tls" "io" "net/http" "strings" "testing" - "crypto/tls" ) func defaultRequest(method string, urlStr string, body io.Reader, t *testing.T) *Request { From ebb33769ae013bd5f518a8bac348c310dea768b8 Mon Sep 17 00:00:00 2001 From: Antoine Imbert Date: Wed, 13 Sep 2017 04:12:08 +0000 Subject: [PATCH 53/53] update travis config for go1.9 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f6ca1c9..40e7466 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,4 @@ go: - 1.6 - 1.7 - 1.8 + - 1.9