github.com/prysmaticlabs/prysm@v1.4.4/shared/gateway/api_middleware.go (about) 1 package gateway 2 3 import ( 4 "net/http" 5 "reflect" 6 7 "github.com/gorilla/mux" 8 ) 9 10 // ApiProxyMiddleware is a proxy between an Ethereum consensus API HTTP client and grpc-gateway. 11 // The purpose of the proxy is to handle HTTP requests and gRPC responses in such a way that: 12 // - Ethereum consensus API requests can be handled by grpc-gateway correctly 13 // - gRPC responses can be returned as spec-compliant Ethereum consensus API responses 14 type ApiProxyMiddleware struct { 15 GatewayAddress string 16 ProxyAddress string 17 EndpointCreator EndpointFactory 18 router *mux.Router 19 } 20 21 // EndpointFactory is responsible for creating new instances of Endpoint values. 22 type EndpointFactory interface { 23 Create(path string) (*Endpoint, error) 24 Paths() []string 25 IsNil() bool 26 } 27 28 // Endpoint is a representation of an API HTTP endpoint that should be proxied by the middleware. 29 type Endpoint struct { 30 Path string // The path of the HTTP endpoint. 31 PostRequest interface{} // The struct corresponding to the JSON structure used in a POST request. 32 GetRequestURLLiterals []string // Names of URL parameters that should not be base64-encoded. 33 GetRequestQueryParams []QueryParam // Query parameters of the GET request. 34 GetResponse interface{} // The struct corresponding to the JSON structure used in a GET response. 35 Err ErrorJson // The struct corresponding to the error that should be returned in case of a request failure. 36 Hooks HookCollection // A collection of functions that can be invoked at various stages of the request/response cycle. 37 } 38 39 // QueryParam represents a single query parameter's metadata. 40 type QueryParam struct { 41 Name string 42 Hex bool 43 Enum bool 44 } 45 46 // Hook is a function that can be invoked at various stages of the request/response cycle, leading to custom behaviour for a specific endpoint. 47 type Hook = func(endpoint Endpoint, w http.ResponseWriter, req *http.Request) ErrorJson 48 49 // CustomHandler is a function that can be invoked at the very beginning of the request, 50 // essentially replacing the whole default request/response logic with custom logic for a specific endpoint. 51 type CustomHandler = func(m *ApiProxyMiddleware, endpoint Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) 52 53 // HookCollection contains handlers/hooks that can be used to amend the default request/response cycle with custom logic for a specific endpoint. 54 type HookCollection struct { 55 CustomHandlers []CustomHandler 56 OnPostStart []Hook 57 OnPostDeserializeRequestBodyIntoContainer []Hook 58 } 59 60 // fieldProcessor applies the processing function f to a value when the tag is present on the field. 61 type fieldProcessor struct { 62 tag string 63 f func(value reflect.Value) error 64 } 65 66 // Run starts the proxy, registering all proxy endpoints on ApiProxyMiddleware.ProxyAddress. 67 func (m *ApiProxyMiddleware) Run() error { 68 m.router = mux.NewRouter() 69 70 for _, path := range m.EndpointCreator.Paths() { 71 m.handleApiPath(path, m.EndpointCreator) 72 } 73 74 return http.ListenAndServe(m.ProxyAddress, m.router) 75 } 76 77 func (m *ApiProxyMiddleware) handleApiPath(path string, endpointFactory EndpointFactory) { 78 m.router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { 79 endpoint, err := endpointFactory.Create(path) 80 if err != nil { 81 errJson := InternalServerErrorWithMessage(err, "could not create endpoint") 82 WriteError(w, errJson, nil) 83 } 84 85 for _, handler := range endpoint.Hooks.CustomHandlers { 86 if handler(m, *endpoint, w, req) { 87 return 88 } 89 } 90 91 if req.Method == "POST" { 92 for _, hook := range endpoint.Hooks.OnPostStart { 93 if errJson := hook(*endpoint, w, req); errJson != nil { 94 WriteError(w, errJson, nil) 95 return 96 } 97 } 98 99 if errJson := DeserializeRequestBodyIntoContainer(req.Body, endpoint.PostRequest); errJson != nil { 100 WriteError(w, errJson, nil) 101 return 102 } 103 for _, hook := range endpoint.Hooks.OnPostDeserializeRequestBodyIntoContainer { 104 if errJson := hook(*endpoint, w, req); errJson != nil { 105 WriteError(w, errJson, nil) 106 return 107 } 108 } 109 110 if errJson := ProcessRequestContainerFields(endpoint.PostRequest); errJson != nil { 111 WriteError(w, errJson, nil) 112 return 113 } 114 if errJson := SetRequestBodyToRequestContainer(endpoint.PostRequest, req); errJson != nil { 115 WriteError(w, errJson, nil) 116 return 117 } 118 } 119 120 if errJson := m.PrepareRequestForProxying(*endpoint, req); errJson != nil { 121 WriteError(w, errJson, nil) 122 return 123 } 124 grpcResponse, errJson := ProxyRequest(req) 125 if errJson != nil { 126 WriteError(w, errJson, nil) 127 return 128 } 129 grpcResponseBody, errJson := ReadGrpcResponseBody(grpcResponse.Body) 130 if errJson != nil { 131 WriteError(w, errJson, nil) 132 return 133 } 134 if errJson := DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil { 135 WriteError(w, errJson, nil) 136 return 137 } 138 139 var responseJson []byte 140 if endpoint.Err.Msg() != "" { 141 HandleGrpcResponseError(endpoint.Err, grpcResponse, w) 142 return 143 } else if !GrpcResponseIsStatusCodeOnly(req, endpoint.GetResponse) { 144 if errJson := DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, endpoint.GetResponse); errJson != nil { 145 WriteError(w, errJson, nil) 146 return 147 } 148 if errJson := ProcessMiddlewareResponseFields(endpoint.GetResponse); errJson != nil { 149 WriteError(w, errJson, nil) 150 return 151 } 152 var errJson ErrorJson 153 responseJson, errJson = SerializeMiddlewareResponseIntoJson(endpoint.GetResponse) 154 if errJson != nil { 155 WriteError(w, errJson, nil) 156 return 157 } 158 } 159 160 if errJson := WriteMiddlewareResponseHeadersAndBody(req, grpcResponse, responseJson, w); errJson != nil { 161 WriteError(w, errJson, nil) 162 return 163 } 164 if errJson := Cleanup(grpcResponse.Body); errJson != nil { 165 WriteError(w, errJson, nil) 166 return 167 } 168 }) 169 }