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  }