Last active
July 13, 2016 13:03
-
-
Save nubunto/dc199cd28a0a9a992ff3141c7d94c099 to your computer and use it in GitHub Desktop.
Refactored HTTP error handling/response.
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
package main | |
import ( | |
"encoding/json" | |
"errors" | |
"fmt" | |
"log" | |
"net/http" | |
"time" | |
"github.com/justinas/alice" | |
) | |
type UserResponse struct { | |
User | |
} | |
type Meta struct { | |
Ok bool `json:"ok"` | |
Message string `json:"message"` | |
} | |
type User struct { | |
Name string `json:"name"` | |
Age int `json:"age"` | |
} | |
type Response struct { | |
// HTTP Response Header | |
Code int `json:"-"` | |
// Metadata regarding this response | |
Meta `json:"meta"` | |
// all the possible responses embedded by the Response struct | |
*UserResponse `json:"user,omitempty"` // optional structs must be a pointer + omitempty, so it disappears from json output | |
// Transaction Reference | |
TxnRef int `json:"txn_ref"` | |
// If there was an error with this response, set it here | |
Error error `json:"-"` | |
} | |
func (r *Response) prep() { | |
// sensible defaults go here. | |
// too much code to stuff inside Render. | |
r.Meta = Meta{ | |
Ok: true, | |
Message: "ok", | |
} | |
if r.Error != nil { | |
log.Print(r.Error) | |
r.Meta = Meta{ | |
Ok: false, | |
Message: r.Error.Error(), | |
} | |
} | |
} | |
func (r Response) Render(w http.ResponseWriter) { | |
r.prep() | |
b, err := json.Marshal(r) | |
if err != nil { | |
w.WriteHeader(500) | |
log.Printf("Could not marshal JSON for %#v: %v", r, err) | |
fmt.Fprintf(w, `{"ok": false, message: "Error parsing response JSON."}`) | |
return | |
} | |
w.WriteHeader(r.Code) | |
w.Write(b) | |
} | |
// Since this is all StdLib©, we can take advantage of libraries like Alice | |
// or implemented handlers in net/http | |
func timeoutHandler(h http.Handler) http.Handler { | |
return http.TimeoutHandler(h, 1*time.Second, "timed out") | |
} | |
func root(env *Env, w http.ResponseWriter, r *http.Request) Response { | |
return Response{ | |
Meta: Meta{ | |
Ok: true, | |
Message: "ok", | |
}, | |
// force yourself to think about what http code to return. | |
// good practice? | |
Code: 200, | |
TxnRef: 42454245, | |
} | |
} | |
func user(env *Env, w http.ResponseWriter, r *http.Request) Response { | |
return Response{ | |
UserResponse: &UserResponse{ | |
User: User{ | |
Name: "John Doe", | |
Age: 94, | |
}, | |
}, | |
Code: 200, | |
TxnRef: 123123, | |
} | |
} | |
func errorHandler(env *Env, w http.ResponseWriter, r *http.Request) Response { | |
log.Println(env) | |
return Response{ | |
Code: 400, | |
Error: errors.New("oops. Some random error ocurred because the request was bad"), | |
} | |
} | |
type Env struct { | |
SomeVar string | |
OtherVar string | |
} | |
type Handler struct { | |
*Env | |
H func(e *Env, w http.ResponseWriter, r *http.Request) Response | |
} | |
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
response := h.H(h.Env, w, r) | |
response.Render(w) | |
} | |
func main() { | |
env := &Env{ | |
SomeVar: "Foo", | |
OtherVar: "Bar", | |
} | |
// using std mux + alice, but might as well use Negroni or any other net/http compatible stuff | |
mux := http.NewServeMux() | |
mux.Handle("/", Handler{Env: env, H: root}) | |
mux.Handle("/user", Handler{Env: env, H: user}) | |
mux.Handle("/error", Handler{Env: env, H: errorHandler}) | |
chain := alice.New(timeoutHandler).Then(mux) | |
server := &http.Server{ | |
Addr: ":8080", | |
Handler: chain, | |
} | |
log.Fatal(server.ListenAndServe()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment