...

Source file src/github.com/grpc-ecosystem/grpc-gateway/runtime/errors.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/runtime

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/grpc-ecosystem/grpc-gateway/internal"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/grpclog"
    12  	"google.golang.org/grpc/status"
    13  )
    14  
    15  // HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
    16  // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
    17  func HTTPStatusFromCode(code codes.Code) int {
    18  	switch code {
    19  	case codes.OK:
    20  		return http.StatusOK
    21  	case codes.Canceled:
    22  		return http.StatusRequestTimeout
    23  	case codes.Unknown:
    24  		return http.StatusInternalServerError
    25  	case codes.InvalidArgument:
    26  		return http.StatusBadRequest
    27  	case codes.DeadlineExceeded:
    28  		return http.StatusGatewayTimeout
    29  	case codes.NotFound:
    30  		return http.StatusNotFound
    31  	case codes.AlreadyExists:
    32  		return http.StatusConflict
    33  	case codes.PermissionDenied:
    34  		return http.StatusForbidden
    35  	case codes.Unauthenticated:
    36  		return http.StatusUnauthorized
    37  	case codes.ResourceExhausted:
    38  		return http.StatusTooManyRequests
    39  	case codes.FailedPrecondition:
    40  		// Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
    41  		return http.StatusBadRequest
    42  	case codes.Aborted:
    43  		return http.StatusConflict
    44  	case codes.OutOfRange:
    45  		return http.StatusBadRequest
    46  	case codes.Unimplemented:
    47  		return http.StatusNotImplemented
    48  	case codes.Internal:
    49  		return http.StatusInternalServerError
    50  	case codes.Unavailable:
    51  		return http.StatusServiceUnavailable
    52  	case codes.DataLoss:
    53  		return http.StatusInternalServerError
    54  	}
    55  
    56  	grpclog.Infof("Unknown gRPC error code: %v", code)
    57  	return http.StatusInternalServerError
    58  }
    59  
    60  var (
    61  	// HTTPError replies to the request with an error.
    62  	//
    63  	// HTTPError is called:
    64  	//  - From generated per-endpoint gateway handler code, when calling the backend results in an error.
    65  	//  - From gateway runtime code, when forwarding the response message results in an error.
    66  	//
    67  	// The default value for HTTPError calls the custom error handler configured on the ServeMux via the
    68  	// WithProtoErrorHandler serve option if that option was used, calling GlobalHTTPErrorHandler otherwise.
    69  	//
    70  	// To customize the error handling of a particular ServeMux instance, use the WithProtoErrorHandler
    71  	// serve option.
    72  	//
    73  	// To customize the error format for all ServeMux instances not using the WithProtoErrorHandler serve
    74  	// option, set GlobalHTTPErrorHandler to a custom function.
    75  	//
    76  	// Setting this variable directly to customize error format is deprecated.
    77  	HTTPError = MuxOrGlobalHTTPError
    78  
    79  	// GlobalHTTPErrorHandler is the HTTPError handler for all ServeMux instances not using the
    80  	// WithProtoErrorHandler serve option.
    81  	//
    82  	// You can set a custom function to this variable to customize error format.
    83  	GlobalHTTPErrorHandler = DefaultHTTPError
    84  
    85  	// OtherErrorHandler handles gateway errors from parsing and routing client requests for all
    86  	// ServeMux instances not using the WithProtoErrorHandler serve option.
    87  	//
    88  	// It returns the following error codes: StatusMethodNotAllowed StatusNotFound StatusBadRequest
    89  	//
    90  	// To customize parsing and routing error handling of a particular ServeMux instance, use the
    91  	// WithProtoErrorHandler serve option.
    92  	//
    93  	// To customize parsing and routing error handling of all ServeMux instances not using the
    94  	// WithProtoErrorHandler serve option, set a custom function to this variable.
    95  	OtherErrorHandler = DefaultOtherErrorHandler
    96  )
    97  
    98  // MuxOrGlobalHTTPError uses the mux-configured error handler, falling back to GlobalErrorHandler.
    99  func MuxOrGlobalHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
   100  	if mux.protoErrorHandler != nil {
   101  		mux.protoErrorHandler(ctx, mux, marshaler, w, r, err)
   102  	} else {
   103  		GlobalHTTPErrorHandler(ctx, mux, marshaler, w, r, err)
   104  	}
   105  }
   106  
   107  // DefaultHTTPError is the default implementation of HTTPError.
   108  // If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
   109  // If otherwise, it replies with http.StatusInternalServerError.
   110  //
   111  // The response body returned by this function is a JSON object,
   112  // which contains a member whose key is "error" and whose value is err.Error().
   113  func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
   114  	const fallback = `{"error": "failed to marshal error message"}`
   115  
   116  	s, ok := status.FromError(err)
   117  	if !ok {
   118  		s = status.New(codes.Unknown, err.Error())
   119  	}
   120  
   121  	w.Header().Del("Trailer")
   122  	w.Header().Del("Transfer-Encoding")
   123  
   124  	contentType := marshaler.ContentType()
   125  	// Check marshaler on run time in order to keep backwards compatibility
   126  	// An interface param needs to be added to the ContentType() function on
   127  	// the Marshal interface to be able to remove this check
   128  	if typeMarshaler, ok := marshaler.(contentTypeMarshaler); ok {
   129  		pb := s.Proto()
   130  		contentType = typeMarshaler.ContentTypeFromMessage(pb)
   131  	}
   132  	w.Header().Set("Content-Type", contentType)
   133  
   134  	body := &internal.Error{
   135  		Error:   s.Message(),
   136  		Message: s.Message(),
   137  		Code:    int32(s.Code()),
   138  		Details: s.Proto().GetDetails(),
   139  	}
   140  
   141  	buf, merr := marshaler.Marshal(body)
   142  	if merr != nil {
   143  		grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
   144  		w.WriteHeader(http.StatusInternalServerError)
   145  		if _, err := io.WriteString(w, fallback); err != nil {
   146  			grpclog.Infof("Failed to write response: %v", err)
   147  		}
   148  		return
   149  	}
   150  
   151  	md, ok := ServerMetadataFromContext(ctx)
   152  	if !ok {
   153  		grpclog.Infof("Failed to extract ServerMetadata from context")
   154  	}
   155  
   156  	handleForwardResponseServerMetadata(w, mux, md)
   157  
   158  	// RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
   159  	// Unless the request includes a TE header field indicating "trailers"
   160  	// is acceptable, as described in Section 4.3, a server SHOULD NOT
   161  	// generate trailer fields that it believes are necessary for the user
   162  	// agent to receive.
   163  	var wantsTrailers bool
   164  
   165  	if te := r.Header.Get("TE"); strings.Contains(strings.ToLower(te), "trailers") {
   166  		wantsTrailers = true
   167  		handleForwardResponseTrailerHeader(w, md)
   168  		w.Header().Set("Transfer-Encoding", "chunked")
   169  	}
   170  
   171  	st := HTTPStatusFromCode(s.Code())
   172  	w.WriteHeader(st)
   173  	if _, err := w.Write(buf); err != nil {
   174  		grpclog.Infof("Failed to write response: %v", err)
   175  	}
   176  
   177  	if wantsTrailers {
   178  		handleForwardResponseTrailer(w, md)
   179  	}
   180  }
   181  
   182  // DefaultOtherErrorHandler is the default implementation of OtherErrorHandler.
   183  // It simply writes a string representation of the given error into "w".
   184  func DefaultOtherErrorHandler(w http.ResponseWriter, _ *http.Request, msg string, code int) {
   185  	http.Error(w, msg, code)
   186  }
   187  

View as plain text