Пример #1
0
// checkHTTPStatus examines an HTTP response and returns an error if
// it is not successful.
func checkHTTPStatus(resp *http.Response) error {
	if len(resp.Status) > 0 && resp.Status[0] == '2' {
		return nil
	}

	// Always collect the entire body; we will need it as a fallback
	// and can only parse it once.
	var body []byte
	var err error
	if resp.Body != nil {
		body, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
	}

	// Take a shot at decoding it as a better error
	var errResp restdata.ErrorResponse
	contentType := resp.Header.Get("Content-Type")
	err2 := restdata.Decode(contentType, bytes.NewReader(body), &errResp)
	if err2 == nil {
		// Given that we decoded that successfully, return the
		// server-provided error
		return errResp.ToError()
	}

	return ErrorHTTP{Response: resp, Body: string(body)}
}
Пример #2
0
// Do performs some HTTP action.  If in is non-nil, the request data is
// serialized and sent as the body of, for instance, a POST request.
// If out is non-nil, the response data (if any) is deserialized into
// this object, which must be of pointer type.
func (r *resource) Do(method string, url *url.URL, in, out interface{}) (err error) {
	json := &codec.JsonHandle{}

	// Set up the body as serialized JSON, if there is one
	var body io.Reader
	if in != nil {
		reader, writer := io.Pipe()
		encoder := codec.NewEncoder(writer, json)
		finished := make(chan error)
		go func() {
			err := encoder.Encode(in)
			err = firstError(err, writer.Close())
			finished <- err
		}()
		defer func() {
			err = firstError(err, <-finished)
		}()
		body = reader
	}

	// Create the request and set headers
	req, err := http.NewRequest(method, url.String(), body)
	if err != nil {
		return err
	}
	if in != nil {
		req.Header.Set("Content-Type", restdata.V1JSONMediaType)
	}
	if out != nil {
		req.Header.Set("Accept", restdata.V1JSONMediaType)
	}

	// Actually do the request
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	// If the response included a body, clean up afterwards
	if resp.Body != nil {
		defer func() {
			err = firstError(err, resp.Body.Close())
		}()
	}

	// Check the response code
	if err = checkHTTPStatus(resp); err != nil {
		return err
	}

	// If there is both a body and a requested output,
	// decode it
	if resp.Body != nil && out != nil {
		contentType := resp.Header.Get("Content-Type")
		err = restdata.Decode(contentType, resp.Body, out)
	}

	return err // may be nil
}
Пример #3
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)
}