github.com/koko1123/flow-go-1@v0.29.6/engine/access/rest/handler.go (about)

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