func (h *resourceHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { var ( ctx *context in, out interface{} err error status int responseType string ) // Recover from panics by sending an HTTP error. defer func() { if recovered := recover(); recovered != nil { response := restdata.ErrorResponse{} response.FromPanic(recovered) writeAResponse(resp, http.StatusInternalServerError, restdata.V1JSONMediaType, response, toJSON) } }() // Start by trying to come up with a response type, even before // trying to parse the input. This determines what format an // error message could be sent back as. if err == nil { // Errors here by default are in the header setup status = http.StatusBadRequest responseType, err = negotiateResponse(req) if err != nil { // Gotta pick something responseType = restdata.V1JSONMediaType } } // Get bits from URL parameters if err == nil { ctx, err = h.Context(req) } // Read the (JSON?) body, if it's there if err == nil && (req.Method == "PUT" || req.Method == "POST") { // Make a new object of the same type as h.In in = reflect.Zero(reflect.TypeOf(h.Representation)).Interface() // Then decode the message body into that object contentType := req.Header.Get("Content-Type") err = restdata.Decode(contentType, req.Body, &in) } // Actually call the handler method if err == nil { // We will return this if the method is unexpected or // we don't have a handler for it err = errMethodNotAllowed{Method: req.Method} // If anything else goes wrong here, it's an error in // client code status = http.StatusInternalServerError switch req.Method { case "GET", "HEAD": if h.Get != nil { out, err = h.Get(ctx) } case "PUT": if h.Put != nil { out, err = h.Put(ctx, in) } case "POST": if h.Post != nil { out, err = h.Post(ctx, in) } case "DELETE": if h.Delete != nil { out, err = h.Delete(ctx) } } } // Fix up the final result based on what we know. if err != nil { // Pick a better status code if we know of one if errS, hasStatus := err.(restdata.ErrorStatus); hasStatus { status = errS.HTTPStatus() } resp := restdata.ErrorResponse{Error: "error", Message: err.Error()} resp.FromError(err) // Remap well-known coordinate errors out = resp } else if out == nil { status = http.StatusNoContent } else if created, isCreated := out.(responseCreated); isCreated { status = http.StatusCreated if created.Location != "" { resp.Header().Set("Location", created.Location) } if req.Method == "HEAD" { out = nil } else { out = created.Body } } else { status = http.StatusOK if req.Method == "HEAD" { out = nil } } // Come up with a function to write the response. If setting // this up fails it could produce another error. :-/ It is // also possible for the actual writer to fail, but by the // point this happens we've already written an HTTP status // line, so we're not necessarily doing better than panicking. responseWriters := map[string]func(interface{}) ([]byte, error){ restdata.V1JSONMediaType: toJSON, } responseWriter, understood := responseWriters[typeMap[responseType]] if !understood { // We shouldn't get here, because it implies response // type negotiation failed...but here we are responseWriter = responseWriters[restdata.V1JSONMediaType] status = http.StatusInternalServerError out = restdata.ErrorResponse{Error: "error", Message: "Invalid response type " + responseType} } writeAResponse(resp, status, responseType, out, responseWriter) }