Plumb 103 responses from QuicHttpStream
This CL adds 1xx responses support to QuicChromiumClientStream, mainly
to plumb 103 Early Hints.
The basic approach is similar to quic::QuicSpdyClientStream. When
OnInitialHeadersComplete() is called, QuicChromiumClientStream parses
the ":status" pseudo-header then perform the followings:
* If status is invalid, reset the stream.
* Otherwise, if status is non-informational (>=200), treat the header as
the final response.
* Otherwise, if status == 101, reset the stream.
* Otherwise, if status == 103, notify the corresponding handle that an
Early Hints is available.
* Otherwise, ignore the informational response.
The handle (QuicChroimiumClientStream::Handle) delivers Early Hints to
the owner via ReadInitialHeaders(). In production code, the owner of the
handle is QuicHttpStream, which is used by HttpNetworkTransaction.
The reason the handle doesn't provide a separate method like
ReadEarlyHints() is that HttpNetworkTransaction handles Early Hints in
DoREadHeadersComplete(), which indirectly calls
QuicChromiumClientStream::Handle::ReadInitialHeaders().
Bug: 1096414
Change-Id: I1ba0c8d5f2fba39ee691d05e0363972ae9428bc0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2759798
Commit-Queue: Kenichi Ishibashi <[email protected]>
Reviewed-by: Bence Béky <[email protected]>
Cr-Commit-Position: refs/heads/master@{#864173}
diff --git a/net/quic/quic_chromium_client_stream.cc b/net/quic/quic_chromium_client_stream.cc
index 9675697..ad86cd3 100644
--- a/net/quic/quic_chromium_client_stream.cc
+++ b/net/quic/quic_chromium_client_stream.cc
@@ -15,6 +15,7 @@
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
#include "net/log/net_log_event_type.h"
#include "net/quic/quic_chromium_client_session.h"
#include "net/quic/quic_http_utils.h"
@@ -61,6 +62,17 @@
}
}
+void QuicChromiumClientStream::Handle::OnEarlyHintsAvailable() {
+ if (!read_headers_callback_)
+ return; // Wait for ReadInitialHeaders to be called.
+
+ DCHECK(read_headers_buffer_);
+ int rv = stream_->DeliverEarlyHints(read_headers_buffer_);
+ DCHECK_NE(ERR_IO_PENDING, rv);
+
+ ResetAndRun(std::move(read_headers_callback_), rv);
+}
+
void QuicChromiumClientStream::Handle::OnInitialHeadersAvailable() {
if (!read_headers_callback_)
return; // Wait for ReadInitialHeaders to be called.
@@ -160,12 +172,19 @@
if (!stream_)
return net_error_;
- int rv = stream_->DeliverInitialHeaders(header_block);
+ // Check Early Hints first.
+ int rv = stream_->DeliverEarlyHints(header_block);
+ if (rv != ERR_IO_PENDING) {
+ return rv;
+ }
+
+ rv = stream_->DeliverInitialHeaders(header_block);
if (rv != ERR_IO_PENDING) {
return rv;
}
read_headers_buffer_ = header_block;
+ DCHECK(!read_headers_callback_);
SetCallback(std::move(callback), &read_headers_callback_);
return ERR_IO_PENDING;
}
@@ -463,6 +482,7 @@
bool fin,
size_t frame_len,
const quic::QuicHeaderList& header_list) {
+ DCHECK(!initial_headers_arrived_);
quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
spdy::Http2HeaderBlock header_block;
@@ -475,6 +495,43 @@
return;
}
+ // Handle informational response. If the response is an Early Hints response,
+ // deliver the response to the owner of the handle. Otherwise ignore the
+ // response.
+ // TODO(crbug.com/1096414): Fix tests and treat an empty list as an error.
+ // Some tests fail when we treat an empty header list as error. All valid
+ // responses must have the ":status" field.
+ if (!header_list.empty()) {
+ int response_code;
+ if (!ParseHeaderStatusCode(header_block, &response_code)) {
+ DLOG(ERROR) << "Received invalid response code: '"
+ << header_block[":status"].as_string() << "' on stream "
+ << id();
+ Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+
+ if (response_code == HTTP_SWITCHING_PROTOCOLS) {
+ DLOG(ERROR) << "Received forbidden 101 response code on stream " << id();
+ Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD);
+ return;
+ }
+
+ if (response_code >= 100 && response_code < 200) {
+ set_headers_decompressed(false);
+ ConsumeHeaderList();
+ if (response_code == HTTP_EARLY_HINTS) {
+ early_hints_.emplace_back(std::move(header_block), frame_len);
+ if (handle_)
+ handle_->OnEarlyHintsAvailable();
+ } else {
+ DVLOG(1) << "Ignore informational response " << response_code
+ << " on stream" << id();
+ }
+ return;
+ }
+ }
+
ConsumeHeaderList();
session_->OnInitialHeadersComplete(id(), header_block);
@@ -679,6 +736,30 @@
handle_->OnTrailingHeadersAvailable();
}
+int QuicChromiumClientStream::DeliverEarlyHints(
+ spdy::Http2HeaderBlock* headers) {
+ if (early_hints_.empty()) {
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(!headers_delivered_);
+
+ EarlyHints& hints = early_hints_.front();
+ *headers = std::move(hints.headers);
+ size_t frame_len = hints.frame_len;
+ early_hints_.pop_front();
+
+ net_log_.AddEvent(
+ NetLogEventType::
+ QUIC_CHROMIUM_CLIENT_STREAM_READ_EARLY_HINTS_RESPONSE_HEADERS,
+ [&](NetLogCaptureMode capture_mode) {
+ return QuicResponseNetLogParams(id(), fin_received(), headers,
+ capture_mode);
+ });
+
+ return frame_len;
+}
+
int QuicChromiumClientStream::DeliverInitialHeaders(
spdy::Http2HeaderBlock* headers) {
if (!initial_headers_arrived_) {