Repository navigation
test(file_uploads): buffer uploads the router rejects before reading the body #10411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -751,12 +751,14 @@ async fn it_fails_with_file_count_limits() -> Result<(), BoxError> { | |
| .collect::<Vec<_>>(), | ||
| ); | ||
|
|
||
| // Run the test | ||
| // Run the test. The router rejects this as soon as it reads the map, so buffer the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment repeats the explanation on /// Set `buffered` for requests the router rejects before reading the files. See
/// [IntegrationTest::execute_multipart_request].
|
||
| // upload: a streamed one can be reset before the response has been read. | ||
| helper::FileUploadTestServer::builder() | ||
| .config(FILE_CONFIG) | ||
| .handler(make_handler!(helper::always_fail)) | ||
| .request(request) | ||
| .subgraph_mapping("uploads", "/") | ||
| .buffered(true) | ||
| .build() | ||
| .run_test(|response| { | ||
| insta::assert_json_snapshot!(response, @r###" | ||
|
|
@@ -845,6 +847,7 @@ async fn it_fails_invalid_multipart_order() -> Result<(), BoxError> { | |
| .handler(make_handler!(helper::always_fail)) | ||
| .request(request) | ||
| .subgraph_mapping("uploads", "/") | ||
| .buffered(true) | ||
| .build() | ||
| .run_test(|response| { | ||
| insta::assert_json_snapshot!(response, @r###" | ||
|
|
@@ -961,6 +964,7 @@ async fn it_fails_with_no_boundary_in_multipart() -> Result<(), BoxError> { | |
| .handler(make_handler!(helper::always_fail)) | ||
| .request(request) | ||
| .subgraph_mapping("uploads", "/") | ||
| .buffered(true) | ||
| .transformer(strip_boundary) | ||
| .build() | ||
| .run_test(|response| { | ||
|
|
@@ -1028,6 +1032,7 @@ async fn it_fails_incompatible_query_order() -> Result<(), BoxError> { | |
| .request(request) | ||
| .subgraph_mapping("uploads", "/s1") | ||
| .subgraph_mapping("uploads_clone", "/s2") | ||
| .buffered(true) | ||
| .build() | ||
| .run_test(|response| { | ||
| insta::assert_json_snapshot!(response, @r###" | ||
|
|
@@ -1763,6 +1768,7 @@ mod helper { | |
| request: Form, | ||
| subgraph_mappings: HashMap<String, String>, | ||
| transformer: Option<fn(reqwest::Request) -> reqwest::Request>, | ||
| buffered: bool, | ||
| } | ||
|
|
||
| #[buildstructor] | ||
|
|
@@ -1772,20 +1778,26 @@ mod helper { | |
| /// Prefer the builder so that tests are more descriptive. | ||
| /// | ||
| /// See [make_handler] and [create_request]. | ||
| /// | ||
| /// Set `buffered` for requests that the router rejects before reading the whole body, | ||
| /// so that the upload is sent in full before the router responds. See | ||
| /// [IntegrationTest::execute_multipart_request]. | ||
| #[builder] | ||
| pub fn new( | ||
| config: &'static str, | ||
| handler: Router, | ||
| subgraph_mappings: HashMap<String, String>, | ||
| request: Form, | ||
| transformer: Option<fn(reqwest::Request) -> reqwest::Request>, | ||
| buffered: Option<bool>, | ||
| ) -> Self { | ||
| Self { | ||
| config, | ||
| handler, | ||
| request, | ||
| subgraph_mappings, | ||
| transformer, | ||
| buffered: buffered.unwrap_or_default(), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1843,7 +1855,7 @@ mod helper { | |
|
|
||
| // Make the request and pass it into the validator callback | ||
| let (_span, response) = router | ||
| .execute_multipart_request(self.request, self.transformer) | ||
| .execute_multipart_request(self.request, self.transformer, self.buffered) | ||
| .await; | ||
| let response = serde_json::from_slice(&response.bytes().await?)?; | ||
| validation_fn(response); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc says buffering works because the upload "is written before the router can respond". The router can still respond after reading only the first parts. What matters is what happens next. Once the whole body has arrived, the router reads the rest of it and keeps the connection open, so there's no reset. A streamed body is still arriving, so the router closes the connection with data still on the way.
I checked this with a raw-socket client against a local router on macOS. Sent in one write, the 100-file upload left the connection reusable for a second request in 45 out of 45 runs. Sent part by part, the client's write failed and the connection was reset in 15 out of 15. When I held back even the last 100 bytes, the router closed the connection.
Suggested wording: