From f48311900ead67638ccddf09cb4b198c15589f7f Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Fri, 13 Sep 2019 20:42:56 +1000 Subject: [PATCH] Restructured response builder to give YAML or JSON when SSZ is not available, not just JSON. --- beacon_node/rest_api/src/beacon.rs | 18 ++--- beacon_node/rest_api/src/error.rs | 2 + beacon_node/rest_api/src/metrics.rs | 2 +- beacon_node/rest_api/src/network.rs | 12 ++-- beacon_node/rest_api/src/node.rs | 4 +- beacon_node/rest_api/src/response_builder.rs | 72 +++++++++++++------- beacon_node/rest_api/src/spec.rs | 6 +- beacon_node/rest_api/src/validator.rs | 8 +-- 8 files changed, 76 insertions(+), 48 deletions(-) diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 159337de4..13f52dc9a 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -55,7 +55,7 @@ pub fn get_head(req: Request) -> ApiResult previous_justified_block_root: chain_head.beacon_state.previous_justified_checkpoint.root, }; - ResponseBuilder::new(&req).body(&head) + ResponseBuilder::new(&req)?.body(&head) } #[derive(Serialize, Encode)] @@ -102,7 +102,7 @@ pub fn get_block(req: Request) -> ApiResult beacon_block: block, }; - ResponseBuilder::new(&req).body(&response) + ResponseBuilder::new(&req)?.body(&response) } /// HTTP handler to return a `BeaconBlock` root at a given `slot`. @@ -116,13 +116,13 @@ pub fn get_block_root(req: Request) -> ApiR ApiError::NotFound(format!("Unable to find BeaconBlock for slot {:?}", target)) })?; - ResponseBuilder::new(&req).body(&root) + ResponseBuilder::new(&req)?.body(&root) } /// HTTP handler to return the `Fork` of the current head. pub fn get_fork(req: Request) -> ApiResult { let beacon_chain = get_beacon_chain_from_request::(&req)?; - ResponseBuilder::new(&req).body(&beacon_chain.head().beacon_state.fork) + ResponseBuilder::new(&req)?.body(&beacon_chain.head().beacon_state.fork) } /// HTTP handler to return the set of validators for an `Epoch` @@ -157,7 +157,7 @@ pub fn get_validators(req: Request) -> ApiR .cloned() .collect(); - ResponseBuilder::new(&req).body(&active_vals) + ResponseBuilder::new(&req)?.body(&active_vals) } #[derive(Serialize, Encode)] @@ -210,7 +210,7 @@ pub fn get_state(req: Request) -> ApiResult beacon_state: state, }; - ResponseBuilder::new(&req).body(&response) + ResponseBuilder::new(&req)?.body(&response) } /// HTTP handler to return a `BeaconState` root at a given `slot`. @@ -225,7 +225,7 @@ pub fn get_state_root(req: Request) -> ApiR let root = state_root_at_slot(&beacon_chain, slot)?; - ResponseBuilder::new(&req).body(&root) + ResponseBuilder::new(&req)?.body(&root) } /// HTTP handler to return the highest finalized slot. @@ -237,7 +237,7 @@ pub fn get_current_finalized_checkpoint( let checkpoint = head_state.finalized_checkpoint.clone(); - ResponseBuilder::new(&req).body(&checkpoint) + ResponseBuilder::new(&req)?.body(&checkpoint) } /// HTTP handler to return a `BeaconState` at the genesis block. @@ -246,5 +246,5 @@ pub fn get_genesis_state(req: Request) -> A let (_root, state) = state_at_slot(&beacon_chain, Slot::new(0))?; - ResponseBuilder::new(&req).body(&state) + ResponseBuilder::new(&req)?.body(&state) } diff --git a/beacon_node/rest_api/src/error.rs b/beacon_node/rest_api/src/error.rs index 26cf7ba1f..e52ba4af6 100644 --- a/beacon_node/rest_api/src/error.rs +++ b/beacon_node/rest_api/src/error.rs @@ -9,6 +9,7 @@ pub enum ApiError { NotImplemented(String), InvalidQueryParams(String), NotFound(String), + UnsupportedType(String), ImATeapot(String), // Just in case. } @@ -22,6 +23,7 @@ impl ApiError { ApiError::NotImplemented(desc) => (StatusCode::NOT_IMPLEMENTED, desc), ApiError::InvalidQueryParams(desc) => (StatusCode::BAD_REQUEST, desc), ApiError::NotFound(desc) => (StatusCode::NOT_FOUND, desc), + ApiError::UnsupportedType(desc) => (StatusCode::UNSUPPORTED_MEDIA_TYPE, desc), ApiError::ImATeapot(desc) => (StatusCode::IM_A_TEAPOT, desc), } } diff --git a/beacon_node/rest_api/src/metrics.rs b/beacon_node/rest_api/src/metrics.rs index 33437a534..e9d98434e 100644 --- a/beacon_node/rest_api/src/metrics.rs +++ b/beacon_node/rest_api/src/metrics.rs @@ -62,6 +62,6 @@ pub fn get_prometheus(req: Request) -> ApiR .unwrap(); String::from_utf8(buffer) - .map(|string| ResponseBuilder::new(&req).body_text(string)) + .map(|string| ResponseBuilder::new(&req)?.body_text(string)) .map_err(|e| ApiError::ServerError(format!("Failed to encode prometheus info: {:?}", e)))? } diff --git a/beacon_node/rest_api/src/network.rs b/beacon_node/rest_api/src/network.rs index afbddde84..f193ef8ea 100644 --- a/beacon_node/rest_api/src/network.rs +++ b/beacon_node/rest_api/src/network.rs @@ -15,7 +15,7 @@ pub fn get_listen_addresses(req: Request) -> ApiResul .get::>>() .expect("The network service should always be there, we put it there"); let multiaddresses: Vec = network.listen_multiaddrs(); - ResponseBuilder::new(&req).body_json(&multiaddresses) + ResponseBuilder::new(&req)?.body_no_ssz(&multiaddresses) } /// HTTP handler to return the network port the client is listening on. @@ -27,7 +27,7 @@ pub fn get_listen_port(req: Request) -> ApiResult { .get::>>() .expect("The network service should always be there, we put it there") .clone(); - ResponseBuilder::new(&req).body(&network.listen_port()) + ResponseBuilder::new(&req)?.body(&network.listen_port()) } /// HTTP handler to return the Discv5 ENR from the client's libp2p service. @@ -38,7 +38,7 @@ pub fn get_enr(req: Request) -> ApiResult { .extensions() .get::>>() .expect("The network service should always be there, we put it there"); - ResponseBuilder::new(&req).body_json(&network.local_enr().to_base64()) + ResponseBuilder::new(&req)?.body_no_ssz(&network.local_enr().to_base64()) } /// HTTP handler to return the `PeerId` from the client's libp2p service. @@ -49,7 +49,7 @@ pub fn get_peer_id(req: Request) -> ApiResult { .extensions() .get::>>() .expect("The network service should always be there, we put it there"); - ResponseBuilder::new(&req).body_json(&network.local_peer_id().to_base58()) + ResponseBuilder::new(&req)?.body_no_ssz(&network.local_peer_id().to_base58()) } /// HTTP handler to return the number of peers connected in the client's libp2p service. @@ -58,7 +58,7 @@ pub fn get_peer_count(req: Request) -> ApiResult { .extensions() .get::>>() .expect("The network service should always be there, we put it there"); - ResponseBuilder::new(&req).body(&network.connected_peers()) + ResponseBuilder::new(&req)?.body(&network.connected_peers()) } /// HTTP handler to return the list of peers connected to the client's libp2p service. @@ -74,5 +74,5 @@ pub fn get_peer_list(req: Request) -> ApiResult { .iter() .map(PeerId::to_string) .collect(); - ResponseBuilder::new(&req).body_json(&connected_peers) + ResponseBuilder::new(&req)?.body_no_ssz(&connected_peers) } diff --git a/beacon_node/rest_api/src/node.rs b/beacon_node/rest_api/src/node.rs index cb1b28df7..882edcfd5 100644 --- a/beacon_node/rest_api/src/node.rs +++ b/beacon_node/rest_api/src/node.rs @@ -7,11 +7,11 @@ use version; /// Read the version string from the current Lighthouse build. pub fn get_version(req: Request) -> ApiResult { - ResponseBuilder::new(&req).body_json(&version::version()) + ResponseBuilder::new(&req)?.body_no_ssz(&version::version()) } /// Read the genesis time from the current beacon chain state. pub fn get_genesis_time(req: Request) -> ApiResult { let beacon_chain = get_beacon_chain_from_request::(&req)?; - ResponseBuilder::new(&req).body(&beacon_chain.head().beacon_state.genesis_time) + ResponseBuilder::new(&req)?.body(&beacon_chain.head().beacon_state.genesis_time) } diff --git a/beacon_node/rest_api/src/response_builder.rs b/beacon_node/rest_api/src/response_builder.rs index 31f717697..793360cd2 100644 --- a/beacon_node/rest_api/src/response_builder.rs +++ b/beacon_node/rest_api/src/response_builder.rs @@ -8,6 +8,7 @@ pub enum Encoding { JSON, SSZ, YAML, + TEXT, } pub struct ResponseBuilder { @@ -15,22 +16,55 @@ pub struct ResponseBuilder { } impl ResponseBuilder { - pub fn new(req: &Request) -> Self { - let encoding = match req.headers().get(header::CONTENT_TYPE) { - Some(h) if h == "application/ssz" => Encoding::SSZ, - Some(h) if h == "application/yaml" => Encoding::YAML, + pub fn new(req: &Request) -> Result { + let content_header = req + .headers() + .get(header::CONTENT_TYPE) + .map_or(Ok(""), |h| h.to_str()) + .map_err(|e| { + ApiError::InvalidQueryParams(format!( + "The content-type header contains invalid characters: {:?}", + e + )) + }) + .map(|h| String::from(h))?; + + let encoding = match content_header { + ref h if h.starts_with("application/ssz") => Encoding::SSZ, + ref h if h.starts_with("application/yaml") => Encoding::YAML, + ref h if h.starts_with("text/plain") => Encoding::TEXT, _ => Encoding::JSON, }; - - Self { encoding } + Ok(Self { encoding }) } pub fn body(self, item: &T) -> ApiResult { + match self.encoding { + Encoding::SSZ => Response::builder() + .status(StatusCode::OK) + .header("content-type", "application/ssz") + .body(Body::from(item.as_ssz_bytes())) + .map_err(|e| ApiError::ServerError(format!("Failed to build response: {:?}", e))), + _ => self.body_no_ssz(item), + } + } + + pub fn body_no_ssz(self, item: &T) -> ApiResult { let (body, content_type) = match self.encoding { - Encoding::JSON => { - return self.body_json(item); + Encoding::JSON => ( + Body::from(serde_json::to_string(&item).map_err(|e| { + ApiError::ServerError(format!( + "Unable to serialize response body as JSON: {:?}", + e + )) + })?), + "application/json", + ), + Encoding::SSZ => { + return Err(ApiError::UnsupportedType( + "Response cannot be encoded as SSZ.".into(), + )); } - Encoding::SSZ => (Body::from(item.as_ssz_bytes()), "application/ssz"), Encoding::YAML => ( Body::from(serde_yaml::to_string(&item).map_err(|e| { ApiError::ServerError(format!( @@ -38,8 +72,13 @@ impl ResponseBuilder { e )) })?), - "application/ssz", + "application/yaml", ), + Encoding::TEXT => { + return Err(ApiError::UnsupportedType( + "Response cannot be encoded as plain text.".into(), + )); + } }; Response::builder() @@ -49,19 +88,6 @@ impl ResponseBuilder { .map_err(|e| ApiError::ServerError(format!("Failed to build response: {:?}", e))) } - pub fn body_json(self, item: &T) -> ApiResult { - Response::builder() - .status(StatusCode::OK) - .header("content-type", "application/json") - .body(Body::from(serde_json::to_string(&item).map_err(|e| { - ApiError::ServerError(format!( - "Unable to serialize response body as JSON: {:?}", - e - )) - })?)) - .map_err(|e| ApiError::ServerError(format!("Failed to build response: {:?}", e))) - } - pub fn body_text(self, text: String) -> ApiResult { Response::builder() .status(StatusCode::OK) diff --git a/beacon_node/rest_api/src/spec.rs b/beacon_node/rest_api/src/spec.rs index 55a139f16..083ff5ad4 100644 --- a/beacon_node/rest_api/src/spec.rs +++ b/beacon_node/rest_api/src/spec.rs @@ -11,7 +11,7 @@ use types::EthSpec; /// HTTP handler to return the full spec object. pub fn get_spec(req: Request) -> ApiResult { let beacon_chain = get_beacon_chain_from_request::(&req)?; - ResponseBuilder::new(&req).body_json(&beacon_chain.spec) + ResponseBuilder::new(&req)?.body_no_ssz(&beacon_chain.spec) } /// HTTP handler to return the full Eth2Config object. @@ -21,10 +21,10 @@ pub fn get_eth2_config(req: Request) -> Api .get::>() .ok_or_else(|| ApiError::ServerError("Eth2Config extension missing".to_string()))?; - ResponseBuilder::new(&req).body_json(eth2_config.as_ref()) + ResponseBuilder::new(&req)?.body_no_ssz(eth2_config.as_ref()) } /// HTTP handler to return the full spec object. pub fn get_slots_per_epoch(req: Request) -> ApiResult { - ResponseBuilder::new(&req).body(&T::EthSpec::slots_per_epoch()) + ResponseBuilder::new(&req)?.body(&T::EthSpec::slots_per_epoch()) } diff --git a/beacon_node/rest_api/src/validator.rs b/beacon_node/rest_api/src/validator.rs index d9d55bad4..b79466b4d 100644 --- a/beacon_node/rest_api/src/validator.rs +++ b/beacon_node/rest_api/src/validator.rs @@ -152,7 +152,7 @@ pub fn get_validator_duties(req: Request) - duties.append(&mut vec![duty]); } - ResponseBuilder::new(&req).body_json(&duties) + ResponseBuilder::new(&req)?.body_no_ssz(&duties) } /// HTTP Handler to produce a new BeaconBlock from the current state, ready to be signed by a validator. @@ -188,7 +188,7 @@ pub fn get_new_beacon_block(req: Request) - )) })?; - ResponseBuilder::new(&req).body(&new_block) + ResponseBuilder::new(&req)?.body(&new_block) } /// HTTP Handler to publish a BeaconBlock, which has been signed by a validator. @@ -246,7 +246,7 @@ pub fn publish_beacon_block(req: Request) - } } }).and_then(|_| { - response_builder.body_json(&()) + response_builder?.body_no_ssz(&()) })) } @@ -354,5 +354,5 @@ pub fn get_new_attestation(req: Request) -> signature: AggregateSignature::new(), }; - ResponseBuilder::new(&req).body(&attestation) + ResponseBuilder::new(&req)?.body(&attestation) }