Example #1
0
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)
}