fix(httputil): improve error handling

Errors are now returned in the best matching format for the
HTTP Accept header.
pull/388/head
Matt Butcher 9 years ago
parent f43ac30fd5
commit 0b0092f24a

@ -31,6 +31,7 @@ package router
import ( import (
"log" "log"
"net/http" "net/http"
"reflect"
"github.com/Masterminds/httputil" "github.com/Masterminds/httputil"
helmhttp "github.com/kubernetes/helm/pkg/httputil" helmhttp "github.com/kubernetes/helm/pkg/httputil"
@ -68,6 +69,7 @@ func NewHandler(c *Context) *Handler {
// //
// The route name is "VERB /ENPOINT/PATH", e.g. "GET /foo". // The route name is "VERB /ENPOINT/PATH", e.g. "GET /foo".
func (h *Handler) Add(route string, fn HandlerFunc) { func (h *Handler) Add(route string, fn HandlerFunc) {
log.Printf("Map %q to %s", route, reflect.ValueOf(fn).Type().Name())
h.routes[route] = fn h.routes[route] = fn
h.paths = append(h.paths, route) h.paths = append(h.paths, route)
h.resolver = httputil.NewResolver(h.paths) h.resolver = httputil.NewResolver(h.paths)
@ -84,6 +86,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fn, ok := h.routes[route] fn, ok := h.routes[route]
if !ok { if !ok {
// This is a 500 because the route was registered, but not here.
helmhttp.Fatal(w, r, "route %s missing", route) helmhttp.Fatal(w, r, "route %s missing", route)
} }

@ -28,6 +28,9 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
) )
// DefaultEncoder is an *AcceptEncoder with the default application/json encoding.
var DefaultEncoder = &AcceptEncoder{DefaultEncoding: "application/json"}
// Encoder takes input and translate it to an expected encoded output. // Encoder takes input and translate it to an expected encoded output.
// //
// Implementations of encoders may use details of the HTTP request and response // Implementations of encoders may use details of the HTTP request and response
@ -40,7 +43,9 @@ type Encoder interface {
// //
// When an encoder fails, it logs any necessary data and then responds to // When an encoder fails, it logs any necessary data and then responds to
// the client. // the client.
Encode(http.ResponseWriter, *http.Request, interface{}) //
// The integer must be a valid http.Status* status code.
Encode(http.ResponseWriter, *http.Request, interface{}, int)
} }
// AcceptEncoder uses the accept headers on a request to determine the response type. // AcceptEncoder uses the accept headers on a request to determine the response type.
@ -56,7 +61,7 @@ type AcceptEncoder struct {
} }
// Encode encodeds the given interface to the first available type in the Accept header. // Encode encodeds the given interface to the first available type in the Accept header.
func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out interface{}) { func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out interface{}, statusCode int) {
a := r.Header.Get("accept") a := r.Header.Get("accept")
fn := encoders[e.DefaultEncoding] fn := encoders[e.DefaultEncoding]
mt := e.DefaultEncoding mt := e.DefaultEncoding
@ -70,6 +75,7 @@ func (e *AcceptEncoder) Encode(w http.ResponseWriter, r *http.Request, out inter
return return
} }
w.Header().Add("content-type", mt) w.Header().Add("content-type", mt)
w.WriteHeader(statusCode)
w.Write(data) w.Write(data)
} }

@ -77,7 +77,7 @@ func TestAcceptEncoder(t *testing.T) {
DefaultEncoding: "application/json", DefaultEncoding: "application/json",
} }
fn := func(w http.ResponseWriter, r *http.Request) { fn := func(w http.ResponseWriter, r *http.Request) {
enc.Encode(w, r, []string{"hello", "world"}) enc.Encode(w, r, []string{"hello", "world"}, http.StatusOK)
} }
s := httptest.NewServer(http.HandlerFunc(fn)) s := httptest.NewServer(http.HandlerFunc(fn))
defer s.Close() defer s.Close()

@ -30,27 +30,46 @@ const (
// LogFatal is for logging 500 errors. Form: Internal Server Error r.Method r.URL message // LogFatal is for logging 500 errors. Form: Internal Server Error r.Method r.URL message
LogFatal = "Internal Server Error: %s %s %s" LogFatal = "Internal Server Error: %s %s %s"
// LogBadRequest logs 400 errors. // LogBadRequest logs 400 errors.
LogBadRequest = "Bad Request: %s %s" LogBadRequest = "Bad Request: %s %s %s"
) )
// Error represents an HTTP error that can be converted to structured types.
//
// For example, and error can be serialized to JSON or YAML. Likewise, the
// string marshal can convert it to a string.
type Error struct {
Msg string `json:"message, omitempty"`
Status string `json:"status"`
}
// Error implements the error interface.
func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Status, e.Msg)
}
// NotFound writes a 404 error to the client and logs an error. // NotFound writes a 404 error to the client and logs an error.
func NotFound(w http.ResponseWriter, r *http.Request) { func NotFound(w http.ResponseWriter, r *http.Request) {
log.Printf(LogNotFound, r.Method, r.URL) msg := fmt.Sprintf(LogNotFound, r.Method, r.URL)
w.WriteHeader(http.StatusNotFound) log.Println(msg)
fmt.Fprintln(w, "File Not Found") writeErr(w, r, msg, http.StatusNotFound)
}
// BadRequest writes an HTTP 400.
func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
log.Printf(LogBadRequest, r.Method, r.URL, err)
writeErr(w, r, err.Error(), http.StatusBadRequest)
} }
func BadRequest(w http.ResponseWriter, r *http.Request) { // writeErr formats and writes the error using the default encoder.
log.Printf(LogNotFound, r.Method, r.URL) func writeErr(w http.ResponseWriter, r *http.Request, msg string, status int) {
w.WriteHeader(http.StatusBadRequest) DefaultEncoder.Encode(w, r, &Error{Status: http.StatusText(status), Msg: msg}, status)
fmt.Fprintln(w, "Bad Request")
} }
// Fatal writes a 500 response to the client and logs the message. // Fatal writes a 500 response to the client and logs the message.
// //
// Additional arguments are past into the the formatter as params to msg. // Additional arguments are past into the the formatter as params to msg.
func Fatal(w http.ResponseWriter, r *http.Request, msg string, v ...interface{}) { func Fatal(w http.ResponseWriter, r *http.Request, msg string, v ...interface{}) {
log.Printf(LogFatal, r.Method, r.URL, fmt.Sprintf(msg, v...)) m := fmt.Sprintf(msg, v...)
w.WriteHeader(http.StatusInternalServerError) log.Printf(LogFatal, r.Method, r.URL, m)
fmt.Fprintln(w, "Internal Server Error") writeErr(w, r, m, http.StatusInternalServerError)
} }

Loading…
Cancel
Save