1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
# httperr
[](https://godoc.org/github.com/crewjam/httperr)
[](https://travis-ci.org/crewjam/httperr)
Package httperr provides utilities for handling error conditions in http
clients and servers.
## Client
This package provides an http.Client that returns errors for requests that return
a status code >= 400. It lets you turn code like this:
```golang
func GetFoo() {
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("api call failed: %d", resp.StatusCode)
}
// ....
}
```
Into code like this:
```golang
func GetFoo() {
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := httperr.Client().Do(req)
if err != nil {
return nil, err
}
// ....
}
```
Wow, three whole lines. Life changing, eh? But wait, there's more!
You can have the client parse structured errors returned from an API:
```golang
type APIError struct {
Message string `json:"message"`
Code string `json:"code"`
}
func (a APIError) Error() string {
// APIError must implement the Error interface
return fmt.Sprintf("%s (code %d)", a.Message, a.Code)
}
func GetFoo() {
client := httperr.Client(http.DefaultClient, httperr.JSON(APIError{}))
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := client.Do(req)
if err != nil {
// If the server returned a status code >= 400, and the response was valid
// JSON for APIError, then err is an *APIErr.
return nil, err
}
// ....
}
```
## Server
Error handling in Go's http.Handler and http.HandlerFunc can be tricky. I often found myself wishing that we could just return an `err` and be done with things.
This package provides an adapter function which turns:
```golang
func (s *Server) getUser(w http.ResponseWriter, r *http.Request) {
remoteUser, err := s.Auth.RequireUser(w, r)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
user, err := s.Storage.Get(remoteUser.Name)
if err != nil {
log.Printf("ERROR: cannot fetch user: %s", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
```
Into this:
```golang
func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error {
remoteUser, err := s.Auth.RequireUser(w, r)
if err != nil {
return httperr.Unauthorized
}
user, err := s.Storage.Get(remoteUser.Name)
if err != nil {
return err
}
return json.NewEncoder(w).Encode(user)
}
```
Life changing? Probably not, but it seems to remove a lot of redundancy and make control flow in web servers simpler.
You can also wrap your calls with middleware that allow you to provide custom handling of errors that are returned from your handlers, but also >= 400 status codes issued by handlers that don't return errors.
```golang
htmlErrorTmpl := template.Must(template.New("err").Parse(errorTemplate))
handler := httperr.Middleware{
OnError: func(w http.ResponseWriter, r *http.Request, err error) error {
log.Printf("REQUEST ERROR: %s", err)
if acceptHeaderContainsTextHTML(r) {
htmlErrorTmpl.Execute(w, struct{ Error error }{Error: err})
return nil // nil means we've handled the error
}
return err // fall back to the default
},
Handler: httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
if r.Method != "POST" {
return httperr.MethodNotAllowed
}
var reqBody RequestBody
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
return httperr.Public{
StatusCode: http.StatusBadRequest,
Err: err,
}
}
if reqBody.Count <= 0 {
// The client won't see this, instead OnError will be called with a httperr.Response containing
// the response. The OnError function can decide to write the error, or replace it with it's own.
w.WriteHeader(http.StatusConflict)
fmt.Fprintln(w, "an obscure internal error happened, but the user doesn't want to see this.")
return nil
}
// ...
return nil
}),
}
```
|