github.com/yogeshlonkar/moby@v1.13.2-0.20201203103638-c0b64beaea94/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  			{"needs to be unlocked", http.StatusServiceUnavailable},
    67  			{"certificates have expired", http.StatusServiceUnavailable},
    68  		} {
    69  			if strings.Contains(errStr, status.keyword) {
    70  				statusCode = status.code
    71  				break
    72  			}
    73  		}
    74  	}
    75  
    76  	if statusCode == 0 {
    77  		statusCode = http.StatusInternalServerError
    78  	}
    79  
    80  	return statusCode
    81  }
    82  
    83  func apiVersionSupportsJSONErrors(version string) bool {
    84  	const firstAPIVersionWithJSONErrors = "1.23"
    85  	return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
    86  }
    87  
    88  // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
    89  // returns it in the response.
    90  func MakeErrorHandler(err error) http.HandlerFunc {
    91  	return func(w http.ResponseWriter, r *http.Request) {
    92  		statusCode := GetHTTPErrorStatusCode(err)
    93  		vars := mux.Vars(r)
    94  		if apiVersionSupportsJSONErrors(vars["version"]) {
    95  			response := &types.ErrorResponse{
    96  				Message: err.Error(),
    97  			}
    98  			WriteJSON(w, statusCode, response)
    99  		} else {
   100  			http.Error(w, grpc.ErrorDesc(err), statusCode)
   101  		}
   102  	}
   103  }