Skip to content

Commit 63e5ff3

Browse files
apollo_infra: add request max size tests
1 parent 1bf3f26 commit 63e5ff3

3 files changed

Lines changed: 108 additions & 4 deletions

File tree

crates/apollo_infra/src/component_definitions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ where
119119
pub enum ServerError {
120120
#[error("Could not deserialize client request: {0}")]
121121
RequestDeserializationFailure(String),
122+
#[error("Request body too large: {0}")]
123+
RequestBodyTooLarge(String),
122124
}
123125

124126
#[derive(Debug)]

crates/apollo_infra/src/component_server/remote_component_server.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,8 @@ where
190190
match Limited::new(http_request.into_body(), max_request_body_bytes).collect().await {
191191
Ok(collected) => collected.to_bytes(),
192192
Err(err) => {
193-
error!("Failed to collect request body: {err}");
194-
let server_error = ServerError::RequestDeserializationFailure(
195-
"Request body too large".to_string(),
196-
);
193+
warn!("Request body too large: {err}");
194+
let server_error = ServerError::RequestBodyTooLarge(err.to_string());
197195
return Ok(HyperResponse::builder()
198196
.status(StatusCode::PAYLOAD_TOO_LARGE)
199197
.header(CONTENT_TYPE, APPLICATION_OCTET_STREAM)

crates/apollo_infra/src/tests/remote_component_client_server_test.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,3 +734,107 @@ async fn zombie_connection_is_evicted() {
734734
connection is still open"
735735
);
736736
}
737+
738+
/// Server rejects a request whose body exceeds `max_request_body_bytes` with 413 and
739+
/// `ServerError::RequestBodyTooLarge`.
740+
#[tokio::test]
741+
async fn request_body_too_large() {
742+
let mut available_ports = available_ports_factory(unique_u16!());
743+
let a_socket = available_ports.get_next_local_host_socket();
744+
let dummy_b_socket = available_ports.get_next_local_host_socket();
745+
746+
// B client points at a non-existent server; it will never be called because the oversized
747+
// request is rejected at the HTTP layer before any component logic runs.
748+
let b_remote_client = ComponentBClient::new(
749+
RemoteClientConfig::default(),
750+
&dummy_b_socket.ip().to_string(),
751+
dummy_b_socket.port(),
752+
&TEST_REMOTE_CLIENT_METRICS,
753+
);
754+
let component_a = ComponentA::new(Box::new(b_remote_client));
755+
756+
let (tx_a, rx_a) = channel::<RequestWrapper<ComponentARequest, ComponentAResponse>>(32);
757+
let a_local_client = LocalComponentClient::new(tx_a, &TEST_LOCAL_CLIENT_METRICS);
758+
759+
let mut local_server = LocalComponentServer::new(
760+
component_a,
761+
&LocalServerConfig::default(),
762+
rx_a,
763+
&TEST_LOCAL_SERVER_METRICS,
764+
);
765+
task::spawn(async move {
766+
let _ = local_server.start().await;
767+
});
768+
769+
let server_config = RemoteServerConfig { max_request_body_bytes: 1, ..Default::default() };
770+
let mut remote_server = RemoteComponentServer::new(
771+
a_local_client,
772+
server_config,
773+
a_socket.port(),
774+
&TEST_REMOTE_SERVER_METRICS,
775+
);
776+
task::spawn(async move {
777+
let _ = remote_server.start().await;
778+
});
779+
task::yield_now().await;
780+
781+
let uri: Uri = format!("http://[{}]:{}/", a_socket.ip(), a_socket.port()).parse().unwrap();
782+
let http_request = Request::post(uri)
783+
.header(CONTENT_TYPE, APPLICATION_OCTET_STREAM)
784+
.header(REQUEST_ID_HEADER, RequestId::generate().to_string())
785+
.body(Full::new(Bytes::from("x".repeat(1024))))
786+
.unwrap();
787+
let http_response =
788+
Client::builder(TokioExecutor::new()).build_http().request(http_request).await.unwrap();
789+
790+
assert_eq!(http_response.status(), StatusCode::PAYLOAD_TOO_LARGE);
791+
let body_bytes = http_response.into_body().collect().await.unwrap().to_bytes();
792+
let server_error = SerdeWrapper::<ServerError>::wrapper_deserialize(&body_bytes).unwrap();
793+
assert!(matches!(server_error, ServerError::RequestBodyTooLarge(_)));
794+
}
795+
796+
/// Client returns `ResponseParsingFailure` when the server's response body exceeds
797+
/// `max_response_body_bytes`.
798+
#[tokio::test]
799+
async fn response_body_too_large() {
800+
let socket = available_ports_factory(unique_u16!()).get_next_local_host_socket();
801+
task::spawn(async move {
802+
async fn handler(
803+
_http_request: Request<Incoming>,
804+
) -> Result<Response<Full<Bytes>>, Infallible> {
805+
Ok(Response::builder()
806+
.status(StatusCode::OK)
807+
.header(CONTENT_TYPE, APPLICATION_OCTET_STREAM)
808+
.body(Full::new(Bytes::from(vec![0u8; 1024])))
809+
.unwrap())
810+
}
811+
812+
let listener = TcpListener::bind(&socket).await.unwrap();
813+
loop {
814+
let Ok((stream, _)) = listener.accept().await else { continue };
815+
let io = TokioIo::new(stream);
816+
let service = service_fn(|req| async move { handler(req).await });
817+
tokio::spawn(async move {
818+
let _ = Http2ServerBuilder::new(TokioExecutor::new())
819+
.http2()
820+
.serve_connection(io, service)
821+
.await;
822+
});
823+
}
824+
});
825+
task::yield_now().await;
826+
827+
let client_config =
828+
RemoteClientConfig { max_response_body_bytes: 1, retries: 0, ..FAST_FAILING_CLIENT_CONFIG };
829+
let client = ComponentAClient::new(
830+
client_config,
831+
&socket.ip().to_string(),
832+
socket.port(),
833+
&TEST_REMOTE_CLIENT_METRICS,
834+
);
835+
836+
let Err(error) = client.a_get_value().await else {
837+
panic!("Expected an error");
838+
};
839+
assert!(matches!(error, ClientError::ResponseParsingFailure(_)), "unexpected error: {error}");
840+
}

0 commit comments

Comments
 (0)