Skip to content

Instantly share code, notes, and snippets.

@ppanyukov
Last active June 18, 2019 17:06
Show Gist options
  • Save ppanyukov/2eb5819e663fba8af3e350f1de83251a to your computer and use it in GitHub Desktop.
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 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