Last active
June 18, 2019 17:06
-
-
Save ppanyukov/2eb5819e663fba8af3e350f1de83251a to your computer and use it in GitHub Desktop.
Golang: Demo of panic recovery in HTTP handlers and sending HTTP 500
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This example demonstrates how panics in handlers can be recovered, | |
// and HTTP 500 responses sent to the client when panic happens. | |
// | |
// Since the built-in http.ResponseWriter cannot be reset, any panic | |
// can lead to half-arsed responses in the buffer or worse. | |
// Also, since our panic handler needs to completely replace the response, | |
// we need our own fully buffered ResponseWriter. | |
// | |
// This demo implements such a buffered response writer as HttpBuffer. | |
// | |
package main | |
import ( | |
"bytes" | |
"fmt" | |
"log" | |
"net/http" | |
) | |
// HttpBuffer is a fully buffered implementation of http.ResponseWriter. | |
// Main use case is to intercept panics and replaced responses with HTTP 500. | |
// Since this implements http.ResponseWriter, it can be given to the http | |
// handlers instead of the default one. | |
type HttpBuffer struct { | |
statusCode int | |
headerMap http.Header | |
body *bytes.Buffer | |
} | |
// NewHttpBuffer creates new buffered http.ReponseWriter with 200 status code | |
// and empty headers. | |
func NewHttpBuffer() *HttpBuffer { | |
return &HttpBuffer{ | |
headerMap: make(http.Header), | |
body: new(bytes.Buffer), | |
statusCode: 200, | |
} | |
} | |
// SetStatusCode sets the status code of the response. | |
func (hb *HttpBuffer) SetStatusCode(code int) { | |
hb.statusCode = code | |
} | |
// GetStatusCode gets the status code of the response. | |
func (hb *HttpBuffer) GetStatusCode() int { | |
return hb.statusCode | |
} | |
// Send writes and flushes the full response to the outgoing response writer. | |
// This normally can be done only once per HTTP response. | |
// Content-Length header will be added. | |
// The buffer can be reused for the subsequent responses with or without modifications. | |
func (hb *HttpBuffer) Send(r http.ResponseWriter) error { | |
bodyBytes := hb.body.Bytes() | |
bodyLength := fmt.Sprintf("%d", len(bodyBytes)) | |
// Copy headers | |
targetHeaders := r.Header() | |
for k, vals := range hb.Header() { | |
for _, v := range vals { | |
targetHeaders.Add(k, v) | |
} | |
} | |
// Set content-length | |
targetHeaders.Set("Content-Length", bodyLength) | |
// Send over the headers with status | |
r.WriteHeader(hb.statusCode) | |
// Write the body | |
_, err := r.Write(hb.body.Bytes()) | |
// Flush response if writer supports it | |
if flusher, ok := r.(http.Flusher); ok { | |
flusher.Flush() | |
} | |
return err | |
} | |
// http.ReponseWriter implementation | |
// WriteHeader is from http.ReponseWriter interface. | |
// Calls SetStatusCode. Can be called multiple times as we | |
// buffer everything. | |
func (hb *HttpBuffer) WriteHeader(statusCode int) { | |
hb.SetStatusCode(statusCode) | |
} | |
// Header is from http.ReponseWriter interface | |
// Allows to set/delete etc response headers. | |
func (hb *HttpBuffer) Header() http.Header { | |
return hb.headerMap | |
} | |
// Write is from http.ReponseWriter interface. | |
// All writes are buffered in memory until Send method is called. | |
func (hb *HttpBuffer) Write(b []byte) (int, error) { | |
return hb.body.Write(b) | |
} | |
///////////////////////////////////////////////////////////////////////////////// | |
// MAIN | |
// | |
// Noddy HTTP server which panics in the handler every second request. | |
// This gets intercepted and HTTP 500 is issued to the client. | |
// | |
///////////////////////////////////////////////////////////////////////////////// | |
var reqNumber = 0 | |
func handler(w http.ResponseWriter, r *http.Request) { | |
defer func() { | |
if re := recover(); re != nil { | |
buffer := NewHttpBuffer() | |
buffer.SetStatusCode(http.StatusInternalServerError) | |
fmt.Fprintf(buffer, "500 - Something bad happened: %v\n", re) | |
buffer.Send(w) | |
} | |
}() | |
buffer := NewHttpBuffer() | |
fmt.Fprintf(buffer, "Hi there, I love %s!\n", r.URL.Path[1:]) | |
// panic every second request | |
reqNumber++ | |
if reqNumber%2 == 0 { | |
panic("hello panic!") | |
} | |
buffer.Send(w) | |
} | |
func main() { | |
http.HandleFunc("/", handler) | |
log.Fatal(http.ListenAndServe(":8080", nil)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment