-
Notifications
You must be signed in to change notification settings - Fork 937
/
api_middleware.go
169 lines (148 loc) · 5.79 KB
/
api_middleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package gateway
import (
"net/http"
"reflect"
"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
ProxyAddress string
EndpointCreator EndpointFactory
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.
PostRequest interface{} // The struct corresponding to the JSON structure used in a POST request.
GetRequestURLLiterals []string // Names of URL parameters that should not be base64-encoded.
GetRequestQueryParams []QueryParam // Query parameters of the GET request.
GetResponse interface{} // The struct corresponding to the JSON structure used in a GET response.
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.
}
// QueryParam represents a single query parameter's metadata.
type QueryParam struct {
Name string
Hex bool
Enum bool
}
// Hook is a function that can be invoked at various stages of the request/response cycle, leading to custom behaviour for a specific endpoint.
type Hook = func(endpoint Endpoint, w http.ResponseWriter, req *http.Request) ErrorJson
// 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 handlers/hooks that can be used to amend the default request/response cycle with custom logic for a specific endpoint.
type HookCollection struct {
CustomHandlers []CustomHandler
OnPostStart []Hook
OnPostDeserializeRequestBodyIntoContainer []Hook
}
// 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 on ApiProxyMiddleware.ProxyAddress.
func (m *ApiProxyMiddleware) Run() error {
m.router = mux.NewRouter()
for _, path := range m.EndpointCreator.Paths() {
m.handleApiPath(path, m.EndpointCreator)
}
return http.ListenAndServe(m.ProxyAddress, m.router)
}
func (m *ApiProxyMiddleware) handleApiPath(path string, endpointFactory EndpointFactory) {
m.router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
endpoint, err := endpointFactory.Create(path)
if err != nil {
errJson := InternalServerErrorWithMessage(err, "could not create endpoint")
WriteError(w, errJson, nil)
}
for _, handler := range endpoint.Hooks.CustomHandlers {
if handler(m, *endpoint, w, req) {
return
}
}
if req.Method == "POST" {
for _, hook := range endpoint.Hooks.OnPostStart {
if errJson := hook(*endpoint, w, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := DeserializeRequestBodyIntoContainer(req.Body, endpoint.PostRequest); errJson != nil {
WriteError(w, errJson, nil)
return
}
for _, hook := range endpoint.Hooks.OnPostDeserializeRequestBodyIntoContainer {
if errJson := hook(*endpoint, w, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := ProcessRequestContainerFields(endpoint.PostRequest); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := SetRequestBodyToRequestContainer(endpoint.PostRequest, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := m.PrepareRequestForProxying(*endpoint, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
grpcResponse, errJson := ProxyRequest(req)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
grpcResponseBody, errJson := ReadGrpcResponseBody(grpcResponse.Body)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil {
WriteError(w, errJson, nil)
return
}
var responseJson []byte
if endpoint.Err.Msg() != "" {
HandleGrpcResponseError(endpoint.Err, grpcResponse, w)
return
} else if !GrpcResponseIsStatusCodeOnly(req, endpoint.GetResponse) {
if errJson := DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, endpoint.GetResponse); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := ProcessMiddlewareResponseFields(endpoint.GetResponse); errJson != nil {
WriteError(w, errJson, nil)
return
}
var errJson ErrorJson
responseJson, errJson = SerializeMiddlewareResponseIntoJson(endpoint.GetResponse)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := WriteMiddlewareResponseHeadersAndBody(req, grpcResponse, responseJson, w); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := Cleanup(grpcResponse.Body); errJson != nil {
WriteError(w, errJson, nil)
return
}
})
}