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 }