github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/apimiddleware/custom_handlers.go (about) 1 package apimiddleware 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "strconv" 12 "strings" 13 14 "github.com/prysmaticlabs/prysm/beacon-chain/rpc/eth/v1/events" 15 "github.com/prysmaticlabs/prysm/shared/gateway" 16 "github.com/prysmaticlabs/prysm/shared/grpcutils" 17 "github.com/r3labs/sse" 18 ) 19 20 type sszConfig struct { 21 sszPath string 22 fileName string 23 responseJson sszResponseJson 24 } 25 26 func handleGetBeaconStateSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) { 27 config := sszConfig{ 28 sszPath: "/eth/v1/debug/beacon/states/{state_id}/ssz", 29 fileName: "beacon_state.ssz", 30 responseJson: &beaconStateSSZResponseJson{}, 31 } 32 return handleGetSSZ(m, endpoint, w, req, config) 33 } 34 35 func handleGetBeaconBlockSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) { 36 config := sszConfig{ 37 sszPath: "/eth/v1/beacon/blocks/{block_id}/ssz", 38 fileName: "beacon_block.ssz", 39 responseJson: &blockSSZResponseJson{}, 40 } 41 return handleGetSSZ(m, endpoint, w, req, config) 42 } 43 44 func handleGetSSZ( 45 m *gateway.ApiProxyMiddleware, 46 endpoint gateway.Endpoint, 47 w http.ResponseWriter, 48 req *http.Request, 49 config sszConfig, 50 ) (handled bool) { 51 if !sszRequested(req) { 52 return false 53 } 54 55 if errJson := prepareSSZRequestForProxying(m, endpoint, req, config.sszPath); errJson != nil { 56 gateway.WriteError(w, errJson, nil) 57 return true 58 } 59 grpcResponse, errJson := gateway.ProxyRequest(req) 60 if errJson != nil { 61 gateway.WriteError(w, errJson, nil) 62 return true 63 } 64 grpcResponseBody, errJson := gateway.ReadGrpcResponseBody(grpcResponse.Body) 65 if errJson != nil { 66 gateway.WriteError(w, errJson, nil) 67 return true 68 } 69 if errJson := gateway.DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil { 70 gateway.WriteError(w, errJson, nil) 71 return true 72 } 73 if endpoint.Err.Msg() != "" { 74 gateway.HandleGrpcResponseError(endpoint.Err, grpcResponse, w) 75 return true 76 } 77 if errJson := gateway.DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, config.responseJson); errJson != nil { 78 gateway.WriteError(w, errJson, nil) 79 return true 80 } 81 responseSsz, errJson := serializeMiddlewareResponseIntoSSZ(config.responseJson.SSZData()) 82 if errJson != nil { 83 gateway.WriteError(w, errJson, nil) 84 return true 85 } 86 if errJson := writeSSZResponseHeaderAndBody(grpcResponse, w, responseSsz, config.fileName); errJson != nil { 87 gateway.WriteError(w, errJson, nil) 88 return true 89 } 90 if errJson := gateway.Cleanup(grpcResponse.Body); errJson != nil { 91 gateway.WriteError(w, errJson, nil) 92 return true 93 } 94 95 return true 96 } 97 98 func sszRequested(req *http.Request) bool { 99 accept, ok := req.Header["Accept"] 100 if !ok { 101 return false 102 } 103 for _, v := range accept { 104 if v == "application/octet-stream" { 105 return true 106 } 107 } 108 return false 109 } 110 111 func prepareSSZRequestForProxying(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, req *http.Request, sszPath string) gateway.ErrorJson { 112 req.URL.Scheme = "http" 113 req.URL.Host = m.GatewayAddress 114 req.RequestURI = "" 115 req.URL.Path = sszPath 116 return gateway.HandleURLParameters(endpoint.Path, req, []string{}) 117 } 118 119 func serializeMiddlewareResponseIntoSSZ(data string) (sszResponse []byte, errJson gateway.ErrorJson) { 120 // Serialize the SSZ part of the deserialized value. 121 b, err := base64.StdEncoding.DecodeString(data) 122 if err != nil { 123 return nil, gateway.InternalServerErrorWithMessage(err, "could not decode response body into base64") 124 } 125 return b, nil 126 } 127 128 func writeSSZResponseHeaderAndBody(grpcResp *http.Response, w http.ResponseWriter, responseSsz []byte, fileName string) gateway.ErrorJson { 129 var statusCodeHeader string 130 for h, vs := range grpcResp.Header { 131 // We don't want to expose any gRPC metadata in the HTTP response, so we skip forwarding metadata headers. 132 if strings.HasPrefix(h, "Grpc-Metadata") { 133 if h == "Grpc-Metadata-"+grpcutils.HttpCodeMetadataKey { 134 statusCodeHeader = vs[0] 135 } 136 } else { 137 for _, v := range vs { 138 w.Header().Set(h, v) 139 } 140 } 141 } 142 if statusCodeHeader != "" { 143 code, err := strconv.Atoi(statusCodeHeader) 144 if err != nil { 145 return gateway.InternalServerErrorWithMessage(err, "could not parse status code") 146 } 147 w.WriteHeader(code) 148 } else { 149 w.WriteHeader(grpcResp.StatusCode) 150 } 151 w.Header().Set("Content-Length", strconv.Itoa(len(responseSsz))) 152 w.Header().Set("Content-Type", "application/octet-stream") 153 w.Header().Set("Content-Disposition", "attachment; filename="+fileName) 154 w.WriteHeader(grpcResp.StatusCode) 155 if _, err := io.Copy(w, ioutil.NopCloser(bytes.NewReader(responseSsz))); err != nil { 156 return gateway.InternalServerErrorWithMessage(err, "could not write response message") 157 } 158 return nil 159 } 160 161 func handleEvents(m *gateway.ApiProxyMiddleware, _ gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) { 162 sseClient := sse.NewClient("http://" + m.GatewayAddress + req.URL.RequestURI()) 163 eventChan := make(chan *sse.Event) 164 165 // We use grpc-gateway as the server side of events, not the sse library. 166 // Because of this subscribing to streams doesn't work as intended, resulting in each event being handled by all subscriptions. 167 // To handle events properly, we subscribe just once using a placeholder value ('events') and handle all topics inside this subscription. 168 if err := sseClient.SubscribeChan("events", eventChan); err != nil { 169 gateway.WriteError(w, gateway.InternalServerError(err), nil) 170 sseClient.Unsubscribe(eventChan) 171 return 172 } 173 174 errJson := receiveEvents(eventChan, w, req) 175 if errJson != nil { 176 gateway.WriteError(w, errJson, nil) 177 } 178 179 sseClient.Unsubscribe(eventChan) 180 return true 181 } 182 183 func receiveEvents(eventChan <-chan *sse.Event, w http.ResponseWriter, req *http.Request) gateway.ErrorJson { 184 for { 185 select { 186 case msg := <-eventChan: 187 var data interface{} 188 189 switch strings.TrimSpace(string(msg.Event)) { 190 case events.HeadTopic: 191 data = &eventHeadJson{} 192 case events.BlockTopic: 193 data = &receivedBlockDataJson{} 194 case events.AttestationTopic: 195 data = &attestationJson{} 196 197 // Data received in the event does not fit the expected event stream output. 198 // We extract the underlying attestation from event data 199 // and assign the attestation back to event data for further processing. 200 eventData := &aggregatedAttReceivedDataJson{} 201 if err := json.Unmarshal(msg.Data, eventData); err != nil { 202 return gateway.InternalServerError(err) 203 } 204 attData, err := json.Marshal(eventData.Aggregate) 205 if err != nil { 206 return gateway.InternalServerError(err) 207 } 208 msg.Data = attData 209 case events.VoluntaryExitTopic: 210 data = &signedVoluntaryExitJson{} 211 case events.FinalizedCheckpointTopic: 212 data = &eventFinalizedCheckpointJson{} 213 case events.ChainReorgTopic: 214 data = &eventChainReorgJson{} 215 case "error": 216 data = &eventErrorJson{} 217 default: 218 return &gateway.DefaultErrorJson{ 219 Message: fmt.Sprintf("Event type '%s' not supported", strings.TrimSpace(string(msg.Event))), 220 Code: http.StatusInternalServerError, 221 } 222 } 223 224 if errJson := writeEvent(msg, w, data); errJson != nil { 225 return errJson 226 } 227 if errJson := flushEvent(w); errJson != nil { 228 return errJson 229 } 230 case <-req.Context().Done(): 231 return nil 232 } 233 } 234 } 235 236 func writeEvent(msg *sse.Event, w http.ResponseWriter, data interface{}) gateway.ErrorJson { 237 if err := json.Unmarshal(msg.Data, data); err != nil { 238 return gateway.InternalServerError(err) 239 } 240 if errJson := gateway.ProcessMiddlewareResponseFields(data); errJson != nil { 241 return errJson 242 } 243 dataJson, errJson := gateway.SerializeMiddlewareResponseIntoJson(data) 244 if errJson != nil { 245 return errJson 246 } 247 248 w.Header().Set("Content-Type", "text/event-stream") 249 250 if _, err := w.Write([]byte("event: ")); err != nil { 251 return gateway.InternalServerError(err) 252 } 253 if _, err := w.Write(msg.Event); err != nil { 254 return gateway.InternalServerError(err) 255 } 256 if _, err := w.Write([]byte("\ndata: ")); err != nil { 257 return gateway.InternalServerError(err) 258 } 259 if _, err := w.Write(dataJson); err != nil { 260 return gateway.InternalServerError(err) 261 } 262 if _, err := w.Write([]byte("\n\n")); err != nil { 263 return gateway.InternalServerError(err) 264 } 265 266 return nil 267 } 268 269 func flushEvent(w http.ResponseWriter) gateway.ErrorJson { 270 flusher, ok := w.(http.Flusher) 271 if !ok { 272 return &gateway.DefaultErrorJson{Message: fmt.Sprintf("Flush not supported in %T", w), Code: http.StatusInternalServerError} 273 } 274 flusher.Flush() 275 return nil 276 }