123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- package httputils
- import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "golang.org/x/net/context"
- "github.com/Sirupsen/logrus"
- "github.com/docker/distribution/registry/api/errcode"
- "github.com/docker/docker/api"
- "github.com/docker/docker/pkg/version"
- )
- // APIVersionKey is the client's requested API version.
- const APIVersionKey = "api-version"
- // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
- // Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
- type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
- // HijackConnection interrupts the http response writer to get the
- // underlying connection and operate with it.
- func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
- conn, _, err := w.(http.Hijacker).Hijack()
- if err != nil {
- return nil, nil, err
- }
- // Flush the options to make sure the client sets the raw mode
- conn.Write([]byte{})
- return conn, conn, nil
- }
- // CloseStreams ensures that a list for http streams are properly closed.
- func CloseStreams(streams ...interface{}) {
- for _, stream := range streams {
- if tcpc, ok := stream.(interface {
- CloseWrite() error
- }); ok {
- tcpc.CloseWrite()
- } else if closer, ok := stream.(io.Closer); ok {
- closer.Close()
- }
- }
- }
- // CheckForJSON makes sure that the request's Content-Type is application/json.
- func CheckForJSON(r *http.Request) error {
- ct := r.Header.Get("Content-Type")
- // No Content-Type header is ok as long as there's no Body
- if ct == "" {
- if r.Body == nil || r.ContentLength == 0 {
- return nil
- }
- }
- // Otherwise it better be json
- if api.MatchesContentType(ct, "application/json") {
- return nil
- }
- return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
- }
- // ParseForm ensures the request form is parsed even with invalid content types.
- // If we don't do this, POST method without Content-type (even with empty body) will fail.
- func ParseForm(r *http.Request) error {
- if r == nil {
- return nil
- }
- if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
- return err
- }
- return nil
- }
- // ParseMultipartForm ensure the request form is parsed, even with invalid content types.
- func ParseMultipartForm(r *http.Request) error {
- if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
- return err
- }
- return nil
- }
- // WriteError decodes a specific docker error and sends it in the response.
- func WriteError(w http.ResponseWriter, err error) {
- if err == nil || w == nil {
- logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
- return
- }
- statusCode := http.StatusInternalServerError
- errMsg := err.Error()
- // Based on the type of error we get we need to process things
- // slightly differently to extract the error message.
- // In the 'errcode.*' cases there are two different type of
- // error that could be returned. errocode.ErrorCode is the base
- // type of error object - it is just an 'int' that can then be
- // used as the look-up key to find the message. errorcode.Error
- // extends errorcode.Error by adding error-instance specific
- // data, like 'details' or variable strings to be inserted into
- // the message.
- //
- // Ideally, we should just be able to call err.Error() for all
- // cases but the errcode package doesn't support that yet.
- //
- // Additionally, in both errcode cases, there might be an http
- // status code associated with it, and if so use it.
- switch err.(type) {
- case errcode.ErrorCode:
- daError, _ := err.(errcode.ErrorCode)
- statusCode = daError.Descriptor().HTTPStatusCode
- errMsg = daError.Message()
- case errcode.Error:
- // For reference, if you're looking for a particular error
- // then you can do something like :
- // import ( derr "github.com/docker/docker/errors" )
- // if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
- daError, _ := err.(errcode.Error)
- statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
- errMsg = daError.Message
- default:
- // This part of will be removed once we've
- // converted everything over to use the errcode package
- // FIXME: this is brittle and should not be necessary.
- // If we need to differentiate between different possible error types,
- // we should create appropriate error types with clearly defined meaning
- errStr := strings.ToLower(err.Error())
- for keyword, status := range map[string]int{
- "not found": http.StatusNotFound,
- "no such": http.StatusNotFound,
- "bad parameter": http.StatusBadRequest,
- "conflict": http.StatusConflict,
- "impossible": http.StatusNotAcceptable,
- "wrong login/password": http.StatusUnauthorized,
- "hasn't been activated": http.StatusForbidden,
- } {
- if strings.Contains(errStr, keyword) {
- statusCode = status
- break
- }
- }
- }
- if statusCode == 0 {
- statusCode = http.StatusInternalServerError
- }
- http.Error(w, errMsg, statusCode)
- }
- // WriteJSON writes the value v to the http response stream as json with standard json encoding.
- func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(code)
- return json.NewEncoder(w).Encode(v)
- }
- // VersionFromContext returns an API version from the context using APIVersionKey.
- // It panics if the context value does not have version.Version type.
- func VersionFromContext(ctx context.Context) (ver version.Version) {
- if ctx == nil {
- return
- }
- val := ctx.Value(APIVersionKey)
- if val == nil {
- return
- }
- return val.(version.Version)
- }
|