Add test for stream reset before body is sent.
Add QuicHttpStreamTest to verify that RESET_STREAM with H3_NO_ERROR
before request data is sent is handled properly. From
https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-http-message-exchanges:
"A server can send a complete response prior to the client sending an
entire request [...] The error code H3_NO_ERROR SHOULD be used when
requesting that the client stop sending on the request stream. Clients
MUST NOT discard complete responses as a result of having their request
terminated abruptly [...]"
Change-Id: I4ff90a8b75af312ae207c5adfcc6a0b8e83caedf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2185394
Reviewed-by: David Schinazi <[email protected]>
Commit-Queue: Bence Béky <[email protected]>
Cr-Commit-Position: refs/heads/master@{#766675}
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 7be5dadf..bcb1def7 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -1601,6 +1601,105 @@
stream_->GetTotalReceivedBytes());
}
+TEST_P(QuicHttpStreamTest, SendChunkedPostRequestAbortedByResetStream) {
+ SetRequest("POST", "/", DEFAULT_PRIORITY);
+ size_t chunk_size = strlen(kUploadData);
+ size_t spdy_request_headers_frame_length;
+ int packet_number = 1;
+
+ if (version_.UsesHttp3()) {
+ AddWrite(ConstructInitialSettingsPacket(packet_number++));
+ }
+
+ std::string header = ConstructDataHeader(chunk_size);
+ if (version_.HasIetfQuicFrames()) {
+ AddWrite(ConstructRequestHeadersAndDataFramesPacket(
+ packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+ kIncludeVersion, !kFin, DEFAULT_PRIORITY, 0,
+ &spdy_request_headers_frame_length, {header, kUploadData}));
+ AddWrite(ConstructClientAckPacket(packet_number++, 3, 1, 2));
+ AddWrite(client_maker_.MakeRstPacket(
+ packet_number++,
+ /* include_version = */ true, stream_id_, quic::QUIC_STREAM_NO_ERROR,
+ /* include_stop_sending_if_v99 = */ false));
+ } else {
+ AddWrite(ConstructRequestHeadersAndDataFramesPacket(
+ packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
+ kIncludeVersion, !kFin, DEFAULT_PRIORITY, 0,
+ &spdy_request_headers_frame_length, {kUploadData}));
+ AddWrite(ConstructClientAckPacket(packet_number++, 3, 1, 2));
+ AddWrite(client_maker_.MakeAckAndRstPacket(
+ packet_number++,
+ /* include_version = */ false, stream_id_,
+ quic::QUIC_RST_ACKNOWLEDGEMENT, 4, 1, 1,
+ /* send_feedback = */ false,
+ /* include_stop_sending_if_v99 = */ false));
+ }
+
+ Initialize();
+
+ upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
+ auto* chunked_upload_stream =
+ static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
+ chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
+
+ request_.method = "POST";
+ request_.url = GURL("https://www.example.org/");
+ request_.upload_data_stream = upload_data_stream_.get();
+ ASSERT_THAT(request_.upload_data_stream->Init(
+ TestCompletionCallback().callback(), NetLogWithSource()),
+ IsOk());
+ ASSERT_THAT(stream_->InitializeStream(&request_, false, DEFAULT_PRIORITY,
+ net_log_.bound(), callback_.callback()),
+ IsOk());
+ ASSERT_THAT(stream_->SendRequest(headers_, &response_, callback_.callback()),
+ IsError(ERR_IO_PENDING));
+
+ // Ack both packets in the request.
+ ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
+
+ // Send the response headers (but not the body).
+ SetResponse("200 OK", string());
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(ConstructResponseHeadersPacket(
+ 2, !kFin, &spdy_response_headers_frame_length));
+
+ // Send the response body.
+ const char kResponseBody[] = "Hello world!";
+ std::string header2 = ConstructDataHeader(strlen(kResponseBody));
+ ProcessPacket(
+ ConstructServerDataPacket(3, false, kFin, header2 + kResponseBody));
+
+ // Server resets stream with H3_NO_ERROR before request body is complete.
+ ProcessPacket(server_maker_.MakeRstPacket(4, /* include_version = */ false,
+ stream_id_,
+ quic::QUIC_STREAM_NO_ERROR));
+
+ // Finish feeding request body to QuicHttpStream. Data will be discarded.
+ chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
+ EXPECT_THAT(callback_.WaitForResult(), IsOk());
+
+ // Verify response.
+ EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()), IsOk());
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(200, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+ ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
+ stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+ callback_.callback()));
+ EXPECT_TRUE(stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+
+ // QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
+ // headers and payload.
+ EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length +
+ strlen(kUploadData) + header.length()),
+ stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
+ strlen(kResponseBody) + header2.length()),
+ stream_->GetTotalReceivedBytes());
+}
+
TEST_P(QuicHttpStreamTest, DestroyedEarly) {
SetRequest("GET", "/", DEFAULT_PRIORITY);
size_t spdy_request_headers_frame_length;