QUIC - chromium server push support.
chromium specific parts of push promise rendezvous.
BUG=
Review URL: https://codereview.chromium.org/1692253004
Cr-Commit-Position: refs/heads/master@{#378284}
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 01796c4..329d583c 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -139,10 +139,12 @@
use_closing_stream_(false),
crypto_config_(CryptoTestUtils::ProofVerifierForTesting()),
read_buffer_(new IOBufferWithSize(4096)),
- connection_id_(2),
+ promise_id_(kServerDataStreamId1),
stream_id_(kClientDataStreamId1),
+ connection_id_(2),
maker_(GetParam(), connection_id_, &clock_, kDefaultServerHostName),
- random_generator_(0) {
+ random_generator_(0),
+ response_offset_(0) {
IPAddress ip(192, 0, 2, 33);
peer_addr_ = IPEndPoint(ip, 443);
self_addr_ = IPEndPoint(ip, 8435);
@@ -247,6 +249,25 @@
stream_.reset(use_closing_stream_
? new AutoClosingStream(session_->GetWeakPtr())
: new QuicHttpStream(session_->GetWeakPtr()));
+
+ promised_stream_.reset(use_closing_stream_
+ ? new AutoClosingStream(session_->GetWeakPtr())
+ : new QuicHttpStream(session_->GetWeakPtr()));
+
+ push_promise_[":path"] = "/bar";
+ push_promise_[":authority"] = "www.example.org";
+ push_promise_[":version"] = "HTTP/1.1";
+ push_promise_[":method"] = "GET";
+ push_promise_[":scheme"] = "https";
+
+ promised_response_[":status"] = "200 OK";
+ promised_response_[":version"] = "HTTP/1.1";
+ promised_response_["content-type"] = "text/plain";
+
+ promise_url_ = SpdyUtils::GetUrlFromHeaderBlock(push_promise_);
+
+ serialized_push_promise_ =
+ SpdyUtils::SerializeUncompressedHeaders(push_promise_);
}
void SetRequest(const std::string& method,
@@ -260,14 +281,39 @@
response_data_ = body;
}
+ scoped_ptr<QuicEncryptedPacket> InnerConstructDataPacket(
+ QuicPacketNumber packet_number,
+ QuicStreamId stream_id,
+ bool should_include_version,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data) {
+ return maker_.MakeDataPacket(packet_number, stream_id,
+ should_include_version, fin, offset, data);
+ }
+
scoped_ptr<QuicEncryptedPacket> ConstructDataPacket(
QuicPacketNumber packet_number,
bool should_include_version,
bool fin,
QuicStreamOffset offset,
base::StringPiece data) {
- return maker_.MakeDataPacket(packet_number, stream_id_,
- should_include_version, fin, offset, data);
+ return InnerConstructDataPacket(packet_number, stream_id_,
+ should_include_version, fin, offset, data);
+ }
+
+ scoped_ptr<QuicEncryptedPacket> InnerConstructRequestHeadersPacket(
+ QuicPacketNumber packet_number,
+ QuicStreamId stream_id,
+ bool should_include_version,
+ bool fin,
+ RequestPriority request_priority,
+ size_t* spdy_headers_frame_length) {
+ SpdyPriority priority =
+ ConvertRequestPriorityToQuicPriority(request_priority);
+ return maker_.MakeRequestHeadersPacket(
+ packet_number, stream_id, should_include_version, fin, priority,
+ request_headers_, spdy_headers_frame_length);
}
scoped_ptr<QuicEncryptedPacket> ConstructRequestHeadersPacket(
@@ -275,20 +321,27 @@
bool fin,
RequestPriority request_priority,
size_t* spdy_headers_frame_length) {
- SpdyPriority priority =
- ConvertRequestPriorityToQuicPriority(request_priority);
- return maker_.MakeRequestHeadersPacket(
- packet_number, stream_id_, kIncludeVersion, fin, priority,
- request_headers_, spdy_headers_frame_length);
+ return InnerConstructRequestHeadersPacket(
+ packet_number, stream_id_, kIncludeVersion, fin, request_priority,
+ spdy_headers_frame_length);
+ }
+
+ scoped_ptr<QuicEncryptedPacket> InnerConstructResponseHeadersPacket(
+ QuicPacketNumber packet_number,
+ QuicStreamId stream_id,
+ bool fin,
+ size_t* spdy_headers_frame_length) {
+ return maker_.MakeResponseHeadersPacket(
+ packet_number, stream_id, !kIncludeVersion, fin, response_headers_,
+ spdy_headers_frame_length, &response_offset_);
}
scoped_ptr<QuicEncryptedPacket> ConstructResponseHeadersPacket(
QuicPacketNumber packet_number,
bool fin,
size_t* spdy_headers_frame_length) {
- return maker_.MakeResponseHeadersPacket(
- packet_number, stream_id_, !kIncludeVersion, fin, response_headers_,
- spdy_headers_frame_length);
+ return InnerConstructResponseHeadersPacket(packet_number, stream_id_, fin,
+ spdy_headers_frame_length);
}
scoped_ptr<QuicEncryptedPacket> ConstructRstStreamPacket(
@@ -304,6 +357,12 @@
QUIC_STREAM_CANCELLED);
}
+ scoped_ptr<QuicEncryptedPacket> ConstructRstStreamVaryMismatchPacket(
+ QuicPacketNumber packet_number) {
+ return maker_.MakeRstPacket(packet_number, !kIncludeVersion, promise_id_,
+ QUIC_PROMISE_VARY_MISMATCH);
+ }
+
scoped_ptr<QuicEncryptedPacket> ConstructAckAndRstStreamPacket(
QuicPacketNumber packet_number) {
return maker_.MakeAckAndRstPacket(packet_number, !kIncludeVersion,
@@ -319,6 +378,14 @@
!kIncludeCongestionFeedback);
}
+ void ReceivePromise(QuicStreamId id) {
+ QuicChromiumClientStream* stream =
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
+ stream->OnStreamHeaders(serialized_push_promise_);
+
+ stream->OnPromiseHeadersComplete(id, serialized_push_promise_.size());
+ }
+
BoundNetLog net_log_;
bool use_closing_stream_;
MockSendAlgorithm* send_algorithm_;
@@ -343,9 +410,17 @@
std::string response_data_;
QuicClientPushPromiseIndex push_promise_index_;
+ // For server push testing
+ scoped_ptr<QuicHttpStream> promised_stream_;
+ SpdyHeaderBlock push_promise_;
+ SpdyHeaderBlock promised_response_;
+ const QuicStreamId promise_id_;
+ string promise_url_;
+ string serialized_push_promise_;
+ const QuicStreamId stream_id_;
+
private:
const QuicConnectionId connection_id_;
- const QuicStreamId stream_id_;
QuicTestPacketMaker maker_;
IPEndPoint self_addr_;
IPEndPoint peer_addr_;
@@ -354,6 +429,7 @@
MockCryptoClientStreamFactory crypto_client_stream_factory_;
scoped_ptr<StaticSocketDataProvider> socket_data_;
std::vector<PacketToWrite> writes_;
+ QuicStreamOffset response_offset_;
};
INSTANTIATE_TEST_CASE_P(Version,
@@ -1029,5 +1105,425 @@
stream_->SendRequest(headers_, &response_, callback_.callback()));
}
+TEST_P(QuicHttpStreamTest, ServerPushGetRequest) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+ ReceivePromise(promise_id_);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ request_.url = GURL(promise_url_);
+
+ // Make the second stream that will exercise the first step of the
+ // server push rendezvous mechanism.
+ EXPECT_EQ(OK,
+ promised_stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+
+ // Receive the promised response headers.
+ response_headers_ = promised_response_;
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 1, promise_id_, false, &spdy_response_headers_frame_length));
+
+ // Receive the promised response body.
+ const char kResponseBody[] = "Hello world!";
+ ProcessPacket(
+ InnerConstructDataPacket(2, promise_id_, false, kFin, 0, kResponseBody));
+
+ // Now sending a matching request will have successful rendezvous
+ // with the promised stream.
+ EXPECT_EQ(OK, promised_stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+
+ EXPECT_EQ(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ promise_id_);
+
+ // The headers will be immediately available.
+ EXPECT_EQ(OK, promised_stream_->ReadResponseHeaders(callback_.callback()));
+
+ // As will be the body.
+ EXPECT_EQ(
+ static_cast<int>(strlen(kResponseBody)),
+ promised_stream_->ReadResponseBody(
+ read_buffer_.get(), read_buffer_->size(), callback_.callback()));
+ EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+
+ EXPECT_EQ(0, stream_->GetTotalSentBytes());
+ EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
+ EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
+ strlen(kResponseBody)),
+ promised_stream_->GetTotalReceivedBytes());
+}
+
+TEST_P(QuicHttpStreamTest, ServerPushGetRequestSlowResponse) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+ ReceivePromise(promise_id_);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ request_.url = GURL(promise_url_);
+
+ // Make the second stream that will exercise the first step of the
+ // server push rendezvous mechanism.
+ EXPECT_EQ(OK,
+ promised_stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+
+ // Now sending a matching request will rendezvous with the promised
+ // stream, but pending secondary validation.
+ EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
+ headers_, &response_, callback_.callback()));
+
+ // Receive the promised response headers.
+ response_headers_ = promised_response_;
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 1, promise_id_, false, &spdy_response_headers_frame_length));
+
+ // Receive the promised response body.
+ const char kResponseBody[] = "Hello world!";
+ ProcessPacket(
+ InnerConstructDataPacket(2, promise_id_, false, kFin, 0, kResponseBody));
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Rendezvous should have succeeded now, so the promised stream
+ // should point at our push stream, and we should be able read
+ // headers and data from it.
+ EXPECT_EQ(OK, callback_.WaitForResult());
+
+ EXPECT_EQ(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ promise_id_);
+
+ EXPECT_EQ(OK, promised_stream_->ReadResponseHeaders(callback_.callback()));
+
+ EXPECT_EQ(
+ static_cast<int>(strlen(kResponseBody)),
+ promised_stream_->ReadResponseBody(
+ read_buffer_.get(), read_buffer_->size(), callback_.callback()));
+
+ // Callback should return
+ EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+
+ EXPECT_EQ(0, stream_->GetTotalSentBytes());
+ EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
+ EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
+ strlen(kResponseBody)),
+ promised_stream_->GetTotalReceivedBytes());
+}
+
+TEST_P(QuicHttpStreamTest, ServerPushCrossOriginOK) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+
+ push_promise_[":authority"] = "mail.example.org";
+ promise_url_ = SpdyUtils::GetUrlFromHeaderBlock(push_promise_);
+ serialized_push_promise_ =
+ SpdyUtils::SerializeUncompressedHeaders(push_promise_);
+
+ ReceivePromise(promise_id_);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ request_.url = GURL(promise_url_);
+
+ // Make the second stream that will exercise the first step of the
+ // server push rendezvous mechanism.
+ EXPECT_EQ(OK,
+ promised_stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+
+ // Receive the promised response headers.
+ response_headers_ = promised_response_;
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 1, promise_id_, false, &spdy_response_headers_frame_length));
+
+ // Receive the promised response body.
+ const char kResponseBody[] = "Hello world!";
+ ProcessPacket(
+ InnerConstructDataPacket(2, promise_id_, false, kFin, 0, kResponseBody));
+
+ // Now sending a matching request will have successful rendezvous
+ // with the promised stream.
+ EXPECT_EQ(OK, promised_stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+
+ EXPECT_EQ(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ promise_id_);
+
+ // The headers will be immediately available.
+ EXPECT_EQ(OK, promised_stream_->ReadResponseHeaders(callback_.callback()));
+
+ // As will be the body.
+ EXPECT_EQ(
+ static_cast<int>(strlen(kResponseBody)),
+ promised_stream_->ReadResponseBody(
+ read_buffer_.get(), read_buffer_->size(), callback_.callback()));
+ EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+
+ EXPECT_EQ(0, stream_->GetTotalSentBytes());
+ EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
+ EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
+ strlen(kResponseBody)),
+ promised_stream_->GetTotalReceivedBytes());
+}
+
+TEST_P(QuicHttpStreamTest, ServerPushCrossOriginFail) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+ push_promise_[":authority"] = "www.notexample.org";
+ promise_url_ = SpdyUtils::GetUrlFromHeaderBlock(push_promise_);
+ serialized_push_promise_ =
+ SpdyUtils::SerializeUncompressedHeaders(push_promise_);
+
+ ReceivePromise(promise_id_);
+ // The promise will have been rejected because the cert doesn't
+ // match.
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicHttpStreamTest, ServerPushVaryCheckOK) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ push_promise_["accept-encoding"] = "gzip";
+ serialized_push_promise_ =
+ SpdyUtils::SerializeUncompressedHeaders(push_promise_);
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+ ReceivePromise(promise_id_);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ request_.url = GURL(promise_url_);
+
+ // Make the second stream that will exercise the first step of the
+ // server push rendezvous mechanism.
+ EXPECT_EQ(OK,
+ promised_stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+
+ headers_.SetHeader("accept-encoding", "gzip");
+
+ // Now sending a matching request will rendezvous with the promised
+ // stream, but pending secondary validation.
+ EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
+ headers_, &response_, callback_.callback()));
+
+ // Receive the promised response headers.
+ promised_response_["vary"] = "accept-encoding";
+ response_headers_ = promised_response_;
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 1, promise_id_, false, &spdy_response_headers_frame_length));
+
+ // Receive the promised response body.
+ const char kResponseBody[] = "Hello world!";
+ ProcessPacket(
+ InnerConstructDataPacket(2, promise_id_, false, kFin, 0, kResponseBody));
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Rendezvous should have succeeded now, so the promised stream
+ // should point at our push stream, and we should be able read
+ // headers and data from it.
+ EXPECT_EQ(OK, callback_.WaitForResult());
+
+ EXPECT_EQ(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ promise_id_);
+
+ EXPECT_EQ(OK, promised_stream_->ReadResponseHeaders(callback_.callback()));
+
+ EXPECT_EQ(
+ static_cast<int>(strlen(kResponseBody)),
+ promised_stream_->ReadResponseBody(
+ read_buffer_.get(), read_buffer_->size(), callback_.callback()));
+
+ // Callback should return
+ EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+
+ EXPECT_EQ(0, stream_->GetTotalSentBytes());
+ EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
+ EXPECT_EQ(0, promised_stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
+ strlen(kResponseBody)),
+ promised_stream_->GetTotalReceivedBytes());
+}
+
+TEST_P(QuicHttpStreamTest, ServerPushVaryCheckFail) {
+ SetRequest("GET", "/", DEFAULT_PRIORITY);
+ request_headers_[":scheme"] = "https";
+ request_headers_[":path"] = "/bar";
+ request_headers_["accept-encoding"] = "sdch";
+
+ size_t spdy_request_header_frame_length;
+ AddWrite(ConstructRstStreamVaryMismatchPacket(1));
+ AddWrite(InnerConstructRequestHeadersPacket(
+ 2, stream_id_ + 2, !kIncludeVersion, kFin, DEFAULT_PRIORITY,
+ &spdy_request_header_frame_length));
+ AddWrite(ConstructAckPacket(3, 3, 1));
+ AddWrite(ConstructRstStreamCancelledPacket(4));
+ Initialize();
+
+ // Initialize the first stream, for receiving the promise on.
+ request_.method = "GET";
+ request_.url = GURL("http://www.example.org/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY, net_log_,
+ callback_.callback()));
+
+ push_promise_["accept-encoding"] = "gzip";
+ serialized_push_promise_ =
+ SpdyUtils::SerializeUncompressedHeaders(push_promise_);
+
+ // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
+ // packet, but does it matter?
+ ReceivePromise(promise_id_);
+ EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ request_.url = GURL(promise_url_);
+
+ // Make the second stream that will exercise the first step of the
+ // server push rendezvous mechanism.
+ EXPECT_EQ(OK,
+ promised_stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+
+ headers_.SetHeader("accept-encoding", "sdch");
+
+ // Now sending a matching request will rendezvous with the promised
+ // stream, but pending secondary validation.
+ EXPECT_EQ(ERR_IO_PENDING, promised_stream_->SendRequest(
+ headers_, &response_, callback_.callback()));
+
+ // Receive the promised response headers.
+ promised_response_["vary"] = "accept-encoding";
+ response_headers_ = promised_response_;
+ size_t spdy_response_headers_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 1, promise_id_, false, &spdy_response_headers_frame_length));
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Rendezvous should have failed due to vary mismatch, so the
+ // promised stream should have been aborted, and instead we have a
+ // new, regular client initiated stream.
+ EXPECT_EQ(OK, callback_.WaitForResult());
+
+ // Not a server-initiated stream.
+ EXPECT_NE(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ promise_id_);
+
+ // Instead, a new client-initiated stream.
+ EXPECT_EQ(
+ QuicHttpStreamPeer::GetQuicChromiumClientStream(promised_stream_.get())
+ ->id(),
+ stream_id_ + 2);
+
+ // After rendezvous failure, the push stream has been cancelled.
+ EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+ // The rest of the test verifies that the retried as
+ // client-initiated version of |promised_stream_| works as intended.
+
+ // Ack the request.
+ ProcessPacket(ConstructAckPacket(2, 0, 0));
+
+ SetResponse("404 Not Found", std::string());
+ size_t spdy_response_header_frame_length;
+ ProcessPacket(InnerConstructResponseHeadersPacket(
+ 3, stream_id_ + 2, kFin, &spdy_response_header_frame_length));
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, promised_stream_->ReadResponseHeaders(callback_.callback()));
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(404, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+ EXPECT_FALSE(response_.response_time.is_null());
+ EXPECT_FALSE(response_.request_time.is_null());
+
+ // There is no body, so this should return immediately.
+ EXPECT_EQ(
+ 0, promised_stream_->ReadResponseBody(
+ read_buffer_.get(), read_buffer_->size(), callback_.callback()));
+ EXPECT_TRUE(promised_stream_->IsResponseBodyComplete());
+
+ stream_->Close(true);
+
+ EXPECT_TRUE(AtEof());
+
+ // QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
+ // headers and payload.
+ EXPECT_EQ(static_cast<int64_t>(spdy_request_header_frame_length),
+ promised_stream_->GetTotalSentBytes());
+ EXPECT_EQ(static_cast<int64_t>(spdy_response_header_frame_length),
+ promised_stream_->GetTotalReceivedBytes());
+}
+
} // namespace test
} // namespace net