github.com/olljanat/moby@v1.13.1/api/server/httputils/errors.go (about)

     1  package httputils
     2  
     3  import (
     4  	"net/http"
     5  	"strings"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/api/types/versions"
    10  	"github.com/gorilla/mux"
    11  	"google.golang.org/grpc"
    12  )
    13  
    14  // httpStatusError is an interface
    15  // that errors with custom status codes
    16  // implement to tell the api layer
    17  // which response status to set.
    18  type httpStatusError interface {
    19  	HTTPErrorStatusCode() int
    20  }
    21  
    22  // inputValidationError is an interface
    23  // that errors generated by invalid
    24  // inputs can implement to tell the
    25  // api layer to set a 400 status code
    26  // in the response.
    27  type inputValidationError interface {
    28  	IsValidationError() bool
    29  }
    30  
    31  // GetHTTPErrorStatusCode retrieves status code from error message
    32  func GetHTTPErrorStatusCode(err error) int {
    33  	if err == nil {
    34  		logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
    35  		return http.StatusInternalServerError
    36  	}
    37  
    38  	var statusCode int
    39  	errMsg := err.Error()
    40  
    41  	switch e := err.(type) {
    42  	case httpStatusError:
    43  		statusCode = e.HTTPErrorStatusCode()
    44  	case inputValidationError:
    45  		statusCode = http.StatusBadRequest
    46  	default:
    47  		// FIXME: this is brittle and should not be necessary, but we still need to identify if
    48  		// there are errors falling back into this logic.
    49  		// If we need to differentiate between different possible error types,
    50  		// we should create appropriate error types that implement the httpStatusError interface.
    51  		errStr := strings.ToLower(errMsg)
    52  		for _, status := range []struct {
    53  			keyword string
    54  			code    int
    55  		}{
    56  			{"not found", http.StatusNotFound},
    57  			{"no such", http.StatusNotFound},
    58  			{"bad parameter", http.StatusBadRequest},
    59  			{"no command", http.StatusBadRequest},
    60  			{"conflict", http.StatusConflict},
    61  			{"impossible", http.StatusNotAcceptable},
    62  			{"wrong login/password", http.StatusUnauthorized},
    63  			{"unauthorized", http.StatusUnauthorized},
    64  			{"hasn't been activated", http.StatusForbidden},
    65  			{"this node", http.StatusServiceUnavailable},
    66  		} {
    67  			if strings.Contains(errStr, status.keyword) {
    68  				statusCode = status.code
    69  				break
    70  			}
    71  		}
    72  	}
    73  
    74  	if statusCode == 0 {
    75  		statusCode = http.StatusInternalServerError
    76  	}
    77  
    78  	return statusCode
    79  }
    80  
    81  func apiVersionSupportsJSONErrors(version string) bool {
    82  	const firstAPIVersionWithJSONErrors = "1.23"
    83  	return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
    84  }
    85  
    86  // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
    87  // returns it in the response.
    88  func MakeErrorHandler(err error) http.HandlerFunc {
    89  	return func(w http.ResponseWriter, r *http.Request) {
    90  		statusCode := GetHTTPErrorStatusCode(err)
    91  		vars := mux.Vars(r)
    92  		if apiVersionSupportsJSONErrors(vars["version"]) {
    93  			response := &types.ErrorResponse{
    94  				Message: err.Error(),
    95  			}
    96  			WriteJSON(w, statusCode, response)
    97  		} else {
    98  			http.Error(w, grpc.ErrorDesc(err), statusCode)
    99  		}
   100  	}
   101  }