From a6c6ebce8fbdeabeee151511baed0259fc2b7bad Mon Sep 17 00:00:00 2001 From: Zachary Marcantel Date: Tue, 1 Jul 2014 17:09:17 -0500 Subject: [PATCH 1/4] expose status code (or anything) by wrapping http.ResponseWriter --- gin.go | 16 ++++++++-------- response_writer.go | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 response_writer.go diff --git a/gin.go b/gin.go index 197b98ca..3192f6c4 100644 --- a/gin.go +++ b/gin.go @@ -35,7 +35,7 @@ type ( // manage the flow, validate the JSON of a request and render a JSON response for example. Context struct { Req *http.Request - Writer http.ResponseWriter + Writer ResponseWriter Keys map[string]interface{} Errors ErrorMsgs Params httprouter.Params @@ -101,7 +101,7 @@ func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) { handlers := engine.combineHandlers(engine.handlers404) c := engine.createContext(w, req, nil, handlers) if engine.handlers404 == nil { - http.NotFound(c.Writer, c.Req) + http.NotFound(&c.Writer, c.Req) } else { c.Writer.WriteHeader(404) } @@ -125,7 +125,7 @@ func (engine *Engine) ServeFiles(path string, root http.FileSystem) { // ServeHTTP makes the router implement the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { - engine.router.ServeHTTP(w, req) + engine.router.ServeHTTP(&ResponseWriter{w, -1}, req) } func (engine *Engine) Run(addr string) { @@ -138,7 +138,7 @@ func (engine *Engine) Run(addr string) { func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { return &Context{ - Writer: w, + Writer: ResponseWriter{w, -1}, Req: req, index: -1, engine: group.engine, @@ -178,7 +178,7 @@ func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) { p = path.Join(group.prefix, p) handlers = group.combineHandlers(handlers) group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { - group.createContext(w, req, params, handlers).Next() + group.createContext(&ResponseWriter{w, -1}, req, params, handlers).Next() }) } @@ -330,7 +330,7 @@ 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) + http.Error(&c.Writer, err.Error(), 500) } } @@ -344,7 +344,7 @@ 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) + http.Error(&c.Writer, err.Error(), 500) } } @@ -361,7 +361,7 @@ func (c *Context) HTML(code int, name string, data interface{}) { "name": name, "data": data, }) - http.Error(c.Writer, err.Error(), 500) + http.Error(&c.Writer, err.Error(), 500) } } diff --git a/response_writer.go b/response_writer.go new file mode 100644 index 00000000..91a4595e --- /dev/null +++ b/response_writer.go @@ -0,0 +1,19 @@ +package gin + +import ( + "net/http" +) + +type ResponseWriter struct { + http.ResponseWriter + status int +} + +func (w *ResponseWriter) WriteHeader(code int) { + w.status = code + w.ResponseWriter.WriteHeader(code) +} + +func (w *ResponseWriter) Status() int { + return w.status +} From ad80fc58268e5f24e154cfe4aaa00acd2f667759 Mon Sep 17 00:00:00 2001 From: Zach Marcantel Date: Tue, 1 Jul 2014 20:35:25 -0500 Subject: [PATCH 2/4] add c.Writer.Status() readme details and example --- README.md | 4 ++ examples/example_ansi_logger.go | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 examples/example_ansi_logger.go diff --git a/README.md b/README.md index 53efb203..ff8d5dad 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,10 @@ func Logger() gin.HandlerFunc { // after request latency := time.Since(t) log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) } } diff --git a/examples/example_ansi_logger.go b/examples/example_ansi_logger.go new file mode 100644 index 00000000..735fb99b --- /dev/null +++ b/examples/example_ansi_logger.go @@ -0,0 +1,82 @@ +package main + +import ( + "log" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/mgutz/ansi" +) + +var ( + white = ansi.ColorCode("white+h:black") + red = ansi.ColorCode("red+h:black") + green = ansi.ColorCode("green+h:black") + yellow = ansi.ColorCode("yellow+h:black") + blue = ansi.ColorCode("blue+h:black") + reset = ansi.ColorCode("reset") +) + +// +// Example of an extended ansi-colored logger using the +// ctx.Writer.Status() function +func logger(c *gin.Context) { + start := time.Now() + + // 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 + } + + // ... finally, log the fact we got a request + log.Printf("<-- %16s | %6s | %s\n", requester, c.Req.Method, c.Req.URL.Path) + + c.Next() + + var color string + if code := c.Writer.Status(); code >= 200 && code <= 299 { + color = green + } else if code >= 300 && code <= 399 { + color = white + } else if code >= 400 && code <= 499 { + color = yellow + } else { + color = red + } + + log.Printf("--> %s%16s | %6d | %s | %s%s\n", + color, + requester, c.Writer.Status(), time.Since(start), c.Req.URL.Path, + reset, + ) +} + +func main() { + r := gin.New() + r.Use(gin.Recovery()) + r.Use(logger) + // or modify func.logger to return a handler and use: + // r.Use(logger()) + + // Ping test + r.GET("/:code", func(c *gin.Context) { + asInt, err := strconv.ParseInt(c.Params.ByName("code"), 10, 32) + if err != nil { + c.String(400, err.Error()) + } else { + c.String(int(asInt), c.Params.ByName("code")) + } + }) + + // Listen and Server in 0.0.0.0:8080 + r.Run(":8081") +} From d44d2e1aeda24879ca2eb26060c904ab68858f90 Mon Sep 17 00:00:00 2001 From: Zach Marcantel Date: Fri, 4 Jul 2014 00:23:03 -0500 Subject: [PATCH 3/4] add status handling to .Write(data) --- gin.go | 8 ++++---- response_writer.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index 3192f6c4..25402fe2 100644 --- a/gin.go +++ b/gin.go @@ -138,7 +138,7 @@ func (engine *Engine) Run(addr string) { func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context { return &Context{ - Writer: ResponseWriter{w, -1}, + Writer: ResponseWriter{w, StatusUnset}, Req: req, index: -1, engine: group.engine, @@ -327,7 +327,7 @@ func (c *Context) JSON(code int, obj interface{}) { if code >= 0 { c.Writer.WriteHeader(code) } - encoder := json.NewEncoder(c.Writer) + encoder := json.NewEncoder(&c.Writer) if err := encoder.Encode(obj); err != nil { c.Error(err, obj) http.Error(&c.Writer, err.Error(), 500) @@ -341,7 +341,7 @@ func (c *Context) XML(code int, obj interface{}) { if code >= 0 { c.Writer.WriteHeader(code) } - encoder := xml.NewEncoder(c.Writer) + encoder := xml.NewEncoder(&c.Writer) if err := encoder.Encode(obj); err != nil { c.Error(err, obj) http.Error(&c.Writer, err.Error(), 500) @@ -356,7 +356,7 @@ func (c *Context) HTML(code int, name string, data interface{}) { if code >= 0 { c.Writer.WriteHeader(code) } - if err := c.engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { + if err := c.engine.HTMLTemplates.ExecuteTemplate(&c.Writer, name, data); err != nil { c.Error(err, map[string]interface{}{ "name": name, "data": data, diff --git a/response_writer.go b/response_writer.go index 91a4595e..ee6bbe8a 100644 --- a/response_writer.go +++ b/response_writer.go @@ -4,6 +4,10 @@ import ( "net/http" ) +const ( + StatusUnset int = -1 +) + type ResponseWriter struct { http.ResponseWriter status int @@ -14,6 +18,31 @@ func (w *ResponseWriter) WriteHeader(code int) { w.ResponseWriter.WriteHeader(code) } +func (w *ResponseWriter) Write(data []byte) (int, error) { + // net/http.Response.Write only has two options: 200 or 500 + // we will follow that lead and defer to their logic + + // check if the write gave an error and set status accordingly + size, err := w.ResponseWriter.Write(data) + if err != nil { + // error on write, we give a 500 + w.status = http.StatusInternalServerError + } else if w.WasWritten() == false { + // everything went okay and we never set a custom + // status so 200 it is + w.status = http.StatusOK + } + + // can easily tap into Content-Length here with 'size' + return size, err +} + +// returns the status of the given response func (w *ResponseWriter) Status() int { return w.status } + +// return a boolean acknowledging if a status code has all ready been set +func (w *ResponseWriter) WasWritten() bool { + return w.status == StatusUnset +} From 6f7c7fc4e5faa3fabb20a1ab70b3bfec45a53fd1 Mon Sep 17 00:00:00 2001 From: Zach Marcantel Date: Fri, 4 Jul 2014 00:44:40 -0500 Subject: [PATCH 4/4] move example to satisfy travis CI and follow existing --- examples/{example_ansi_logger.go => logging/example_ansi.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{example_ansi_logger.go => logging/example_ansi.go} (100%) diff --git a/examples/example_ansi_logger.go b/examples/logging/example_ansi.go similarity index 100% rename from examples/example_ansi_logger.go rename to examples/logging/example_ansi.go