mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-03 08:37:37 +00:00
2dfe291cf9
* initial commit wip * setting protos for fee recipient * updating proto based on specs * updating apimiddleware * generated APIs * updating proto to fit spec * fixing naming of fields * fixing endpoint_factory and associated structs * fixing imports * adding in custom http types to grpc gateway * adding import options * changing package option * still testing protos * adding to bazel * testing dependency changes * more tests * more tests * more tests * more tests * more tests * more tests * testing changing repo dep * testing deps * testing deps * testing deps * testing deps * testing deps * testing deps * reverting * testing import * testing bazel * bazel test * testing * testing * testing import * updating generated proto code * wip set fee recipient by pubkey * adding list fee recipient logic * fixing thrown error * fixing bug with API * fee recipient delete function * updating generated proto logic * fixing deposit api and adding postman tests * fixing proto imports * fixing unit tests and checksums * fixing test * adding unit tests * fixing bazel * Update validator/rpc/standard_api.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * Update validator/rpc/standard_api.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> * resolving review comments * fixing return * Update config/validator/service/proposer-settings.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/rpc/standard_api.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * updating proto * updating message name * fixing imports * updating based on review comments * adding middleware unit tests * capitalizing errors * using error instead of errorf * fixing missed unit test variable rename * fixing format variable * fixing unit test * Update validator/rpc/standard_api.go * Update validator/rpc/standard_api.go Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
266 lines
9.0 KiB
Go
266 lines
9.0 KiB
Go
package apimiddleware
|
|
|
|
import (
|
|
"net/http"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// ApiProxyMiddleware is a proxy between an Ethereum consensus API HTTP client and grpc-gateway.
|
|
// The purpose of the proxy is to handle HTTP requests and gRPC responses in such a way that:
|
|
// - Ethereum consensus API requests can be handled by grpc-gateway correctly
|
|
// - gRPC responses can be returned as spec-compliant Ethereum consensus API responses
|
|
type ApiProxyMiddleware struct {
|
|
GatewayAddress string
|
|
EndpointCreator EndpointFactory
|
|
Timeout time.Duration
|
|
router *mux.Router
|
|
}
|
|
|
|
// EndpointFactory is responsible for creating new instances of Endpoint values.
|
|
type EndpointFactory interface {
|
|
Create(path string) (*Endpoint, error)
|
|
Paths() []string
|
|
IsNil() bool
|
|
}
|
|
|
|
// Endpoint is a representation of an API HTTP endpoint that should be proxied by the middleware.
|
|
type Endpoint struct {
|
|
Path string // The path of the HTTP endpoint.
|
|
GetResponse interface{} // The struct corresponding to the JSON structure used in a GET response.
|
|
PostRequest interface{} // The struct corresponding to the JSON structure used in a POST request.
|
|
PostResponse interface{} // The struct corresponding to the JSON structure used in a POST response.
|
|
DeleteRequest interface{} // The struct corresponding to the JSON structure used in a DELETE request.
|
|
DeleteResponse interface{} // The struct corresponding to the JSON structure used in a DELETE response.
|
|
RequestURLLiterals []string // Names of URL parameters that should not be base64-encoded.
|
|
RequestQueryParams []QueryParam // Query parameters of the request.
|
|
Err ErrorJson // The struct corresponding to the error that should be returned in case of a request failure.
|
|
Hooks HookCollection // A collection of functions that can be invoked at various stages of the request/response cycle.
|
|
CustomHandlers []CustomHandler // Functions that will be executed instead of the default request/response behaviour.
|
|
}
|
|
|
|
// RunDefault expresses whether the default processing logic should be carried out after running a pre hook.
|
|
type RunDefault bool
|
|
|
|
// DefaultEndpoint returns an Endpoint with default configuration, e.g. DefaultErrorJson for error handling.
|
|
func DefaultEndpoint() Endpoint {
|
|
return Endpoint{
|
|
Err: &DefaultErrorJson{},
|
|
}
|
|
}
|
|
|
|
// QueryParam represents a single query parameter's metadata.
|
|
type QueryParam struct {
|
|
Name string
|
|
Hex bool
|
|
Enum bool
|
|
}
|
|
|
|
// CustomHandler is a function that can be invoked at the very beginning of the request,
|
|
// essentially replacing the whole default request/response logic with custom logic for a specific endpoint.
|
|
type CustomHandler = func(m *ApiProxyMiddleware, endpoint Endpoint, w http.ResponseWriter, req *http.Request) (handled bool)
|
|
|
|
// HookCollection contains hooks that can be used to amend the default request/response cycle with custom logic for a specific endpoint.
|
|
type HookCollection struct {
|
|
OnPreDeserializeRequestBodyIntoContainer func(endpoint *Endpoint, w http.ResponseWriter, req *http.Request) (RunDefault, ErrorJson)
|
|
OnPostDeserializeRequestBodyIntoContainer func(endpoint *Endpoint, w http.ResponseWriter, req *http.Request) ErrorJson
|
|
OnPreDeserializeGrpcResponseBodyIntoContainer func([]byte, interface{}) (RunDefault, ErrorJson)
|
|
OnPreSerializeMiddlewareResponseIntoJson func(interface{}) (RunDefault, []byte, ErrorJson)
|
|
}
|
|
|
|
// fieldProcessor applies the processing function f to a value when the tag is present on the field.
|
|
type fieldProcessor struct {
|
|
tag string
|
|
f func(value reflect.Value) error
|
|
}
|
|
|
|
// Run starts the proxy, registering all proxy endpoints.
|
|
func (m *ApiProxyMiddleware) Run(gatewayRouter *mux.Router) {
|
|
for _, path := range m.EndpointCreator.Paths() {
|
|
gatewayRouter.HandleFunc(path, m.WithMiddleware(path))
|
|
}
|
|
m.router = gatewayRouter
|
|
}
|
|
|
|
// ServeHTTP for the proxy middleware.
|
|
func (m *ApiProxyMiddleware) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
m.router.ServeHTTP(w, req)
|
|
}
|
|
|
|
// WithMiddleware wraps the given endpoint handler with the middleware logic.
|
|
func (m *ApiProxyMiddleware) WithMiddleware(path string) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
endpoint, err := m.EndpointCreator.Create(path)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("Could not create endpoint for path: %s", path)
|
|
return
|
|
}
|
|
|
|
for _, handler := range endpoint.CustomHandlers {
|
|
if handler(m, *endpoint, w, req) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.Method == "POST" {
|
|
if errJson := handlePostRequestForEndpoint(endpoint, w, req); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
if req.Method == "DELETE" && req.Body != http.NoBody {
|
|
if errJson := handleDeleteRequestForEndpoint(endpoint, req); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
if errJson := m.PrepareRequestForProxying(*endpoint, req); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
grpcResp, errJson := m.ProxyRequest(req)
|
|
if errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
grpcRespBody, errJson := ReadGrpcResponseBody(grpcResp.Body)
|
|
if errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
|
|
var respJson []byte
|
|
if !GrpcResponseIsEmpty(grpcRespBody) {
|
|
respHasError, errJson := HandleGrpcResponseError(endpoint.Err, grpcResp, grpcRespBody, w)
|
|
if errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
if respHasError {
|
|
return
|
|
}
|
|
|
|
var resp interface{}
|
|
if req.Method == "GET" {
|
|
resp = endpoint.GetResponse
|
|
} else if req.Method == "DELETE" {
|
|
resp = endpoint.DeleteResponse
|
|
} else {
|
|
resp = endpoint.PostResponse
|
|
}
|
|
if errJson := deserializeGrpcResponseBodyIntoContainerWrapped(endpoint, grpcRespBody, resp); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
if errJson := ProcessMiddlewareResponseFields(resp); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
|
|
respJson, errJson = serializeMiddlewareResponseIntoJsonWrapped(endpoint, respJson, resp)
|
|
if errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
if errJson := WriteMiddlewareResponseHeadersAndBody(grpcResp, respJson, w); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
if errJson := Cleanup(grpcResp.Body); errJson != nil {
|
|
WriteError(w, errJson, nil)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func handlePostRequestForEndpoint(endpoint *Endpoint, w http.ResponseWriter, req *http.Request) ErrorJson {
|
|
if errJson := deserializeRequestBodyIntoContainerWrapped(endpoint, req, w); errJson != nil {
|
|
return errJson
|
|
}
|
|
if errJson := ProcessRequestContainerFields(endpoint.PostRequest); errJson != nil {
|
|
return errJson
|
|
}
|
|
return SetRequestBodyToRequestContainer(endpoint.PostRequest, req)
|
|
}
|
|
|
|
func handleDeleteRequestForEndpoint(endpoint *Endpoint, req *http.Request) ErrorJson {
|
|
if errJson := DeserializeRequestBodyIntoContainer(req.Body, endpoint.DeleteRequest); errJson != nil {
|
|
return errJson
|
|
}
|
|
if errJson := ProcessRequestContainerFields(endpoint.DeleteRequest); errJson != nil {
|
|
return errJson
|
|
}
|
|
return SetRequestBodyToRequestContainer(endpoint.DeleteRequest, req)
|
|
}
|
|
|
|
func deserializeRequestBodyIntoContainerWrapped(endpoint *Endpoint, req *http.Request, w http.ResponseWriter) ErrorJson {
|
|
runDefault := true
|
|
if endpoint.Hooks.OnPreDeserializeRequestBodyIntoContainer != nil {
|
|
run, errJson := endpoint.Hooks.OnPreDeserializeRequestBodyIntoContainer(endpoint, w, req)
|
|
if errJson != nil {
|
|
return errJson
|
|
}
|
|
if !run {
|
|
runDefault = false
|
|
}
|
|
}
|
|
if runDefault {
|
|
if errJson := DeserializeRequestBodyIntoContainer(req.Body, endpoint.PostRequest); errJson != nil {
|
|
return errJson
|
|
}
|
|
}
|
|
if endpoint.Hooks.OnPostDeserializeRequestBodyIntoContainer != nil {
|
|
if errJson := endpoint.Hooks.OnPostDeserializeRequestBodyIntoContainer(endpoint, w, req); errJson != nil {
|
|
return errJson
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deserializeGrpcResponseBodyIntoContainerWrapped(endpoint *Endpoint, grpcResponseBody []byte, resp interface{}) ErrorJson {
|
|
runDefault := true
|
|
if endpoint.Hooks.OnPreDeserializeGrpcResponseBodyIntoContainer != nil {
|
|
run, errJson := endpoint.Hooks.OnPreDeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, resp)
|
|
if errJson != nil {
|
|
return errJson
|
|
}
|
|
if !run {
|
|
runDefault = false
|
|
}
|
|
}
|
|
if runDefault {
|
|
if errJson := DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, resp); errJson != nil {
|
|
return errJson
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func serializeMiddlewareResponseIntoJsonWrapped(endpoint *Endpoint, respJson []byte, resp interface{}) ([]byte, ErrorJson) {
|
|
runDefault := true
|
|
var errJson ErrorJson
|
|
if endpoint.Hooks.OnPreSerializeMiddlewareResponseIntoJson != nil {
|
|
var run RunDefault
|
|
run, respJson, errJson = endpoint.Hooks.OnPreSerializeMiddlewareResponseIntoJson(resp)
|
|
if errJson != nil {
|
|
return nil, errJson
|
|
}
|
|
if !run {
|
|
runDefault = false
|
|
}
|
|
}
|
|
if runDefault {
|
|
respJson, errJson = SerializeMiddlewareResponseIntoJson(resp)
|
|
if errJson != nil {
|
|
return nil, errJson
|
|
}
|
|
}
|
|
return respJson, nil
|
|
}
|