github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/http_handler.go (about)

     1  package routes
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  
     9  	"github.com/rs/zerolog"
    10  
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  
    14  	"github.com/onflow/flow-go/engine/access/rest/models"
    15  	fvmErrors "github.com/onflow/flow-go/fvm/errors"
    16  	"github.com/onflow/flow-go/model/flow"
    17  )
    18  
    19  const MaxRequestSize = 2 << 20 // 2MB
    20  
    21  // HttpHandler is custom http handler implementing custom handler function.
    22  // HttpHandler function allows easier handling of errors and responses as it
    23  // wraps functionality for handling error and responses outside of endpoint handling.
    24  type HttpHandler struct {
    25  	Logger zerolog.Logger
    26  	Chain  flow.Chain
    27  }
    28  
    29  func NewHttpHandler(
    30  	logger zerolog.Logger,
    31  	chain flow.Chain,
    32  ) *HttpHandler {
    33  	return &HttpHandler{
    34  		Logger: logger,
    35  		Chain:  chain,
    36  	}
    37  }
    38  
    39  // VerifyRequest function acts as a wrapper to each request providing common handling functionality
    40  // such as logging, error handling
    41  func (h *HttpHandler) VerifyRequest(w http.ResponseWriter, r *http.Request) error {
    42  	// create a logger
    43  	errLog := h.Logger.With().Str("request_url", r.URL.String()).Logger()
    44  
    45  	// limit requested body size
    46  	r.Body = http.MaxBytesReader(w, r.Body, MaxRequestSize)
    47  	err := r.ParseForm()
    48  	if err != nil {
    49  		h.errorHandler(w, err, errLog)
    50  		return err
    51  	}
    52  	return nil
    53  }
    54  
    55  func (h *HttpHandler) errorHandler(w http.ResponseWriter, err error, errorLogger zerolog.Logger) {
    56  	// rest status type error should be returned with status and user message provided
    57  	var statusErr models.StatusError
    58  	if errors.As(err, &statusErr) {
    59  		h.errorResponse(w, statusErr.Status(), statusErr.UserMessage(), errorLogger)
    60  		return
    61  	}
    62  
    63  	// handle cadence errors
    64  	cadenceError := fvmErrors.Find(err, fvmErrors.ErrCodeCadenceRunTimeError)
    65  	if cadenceError != nil {
    66  		msg := fmt.Sprintf("Cadence error: %s", cadenceError.Error())
    67  		h.errorResponse(w, http.StatusBadRequest, msg, errorLogger)
    68  		return
    69  	}
    70  
    71  	// handle grpc status error returned from the backend calls, we are forwarding the message to the client
    72  	if se, ok := status.FromError(err); ok {
    73  		if se.Code() == codes.NotFound {
    74  			msg := fmt.Sprintf("Flow resource not found: %s", se.Message())
    75  			h.errorResponse(w, http.StatusNotFound, msg, errorLogger)
    76  			return
    77  		}
    78  		if se.Code() == codes.InvalidArgument {
    79  			msg := fmt.Sprintf("Invalid Flow argument: %s", se.Message())
    80  			h.errorResponse(w, http.StatusBadRequest, msg, errorLogger)
    81  			return
    82  		}
    83  		if se.Code() == codes.Internal {
    84  			msg := fmt.Sprintf("Invalid Flow request: %s", se.Message())
    85  			h.errorResponse(w, http.StatusBadRequest, msg, errorLogger)
    86  			return
    87  		}
    88  		if se.Code() == codes.Unavailable {
    89  			msg := fmt.Sprintf("Failed to process request: %s", se.Message())
    90  			h.errorResponse(w, http.StatusServiceUnavailable, msg, errorLogger)
    91  			return
    92  		}
    93  	}
    94  
    95  	// stop going further - catch all error
    96  	msg := "internal server error"
    97  	errorLogger.Error().Err(err).Msg(msg)
    98  	h.errorResponse(w, http.StatusInternalServerError, msg, errorLogger)
    99  }
   100  
   101  // jsonResponse builds a JSON response and send it to the client
   102  func (h *HttpHandler) jsonResponse(w http.ResponseWriter, code int, response interface{}, errLogger zerolog.Logger) {
   103  	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
   104  
   105  	// serialize response to JSON and handler errors
   106  	encodedResponse, err := json.MarshalIndent(response, "", "\t")
   107  	if err != nil {
   108  		w.WriteHeader(http.StatusInternalServerError)
   109  		errLogger.Error().Err(err).Str("response", string(encodedResponse)).Msg("failed to indent response")
   110  		return
   111  	}
   112  
   113  	w.WriteHeader(code)
   114  	// write response to response stream
   115  	_, err = w.Write(encodedResponse)
   116  	if err != nil {
   117  		errLogger.Error().Err(err).Str("response", string(encodedResponse)).Msg("failed to write http response")
   118  	}
   119  }
   120  
   121  // errorResponse sends an HTTP error response to the client with the given return code
   122  // and a model error with the given response message in the response body
   123  func (h *HttpHandler) errorResponse(
   124  	w http.ResponseWriter,
   125  	returnCode int,
   126  	responseMessage string,
   127  	logger zerolog.Logger,
   128  ) {
   129  	// create error response model
   130  	modelError := models.ModelError{
   131  		Code:    int32(returnCode),
   132  		Message: responseMessage,
   133  	}
   134  	h.jsonResponse(w, returnCode, modelError, logger)
   135  }