2180 lines
88 KiB
C++
2180 lines
88 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/quic/quic_http_stream.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/strcat.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "base/time/default_tick_clock.h"
|
|
#include "base/time/time.h"
|
|
#include "net/base/chunked_upload_data_stream.h"
|
|
#include "net/base/elements_upload_data_stream.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/load_flags.h"
|
|
#include "net/base/load_timing_info.h"
|
|
#include "net/base/load_timing_info_test_util.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/base/upload_bytes_element_reader.h"
|
|
#include "net/dns/public/host_resolver_results.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/http/http_response_headers.h"
|
|
#include "net/http/transport_security_state.h"
|
|
#include "net/log/net_log.h"
|
|
#include "net/log/net_log_event_type.h"
|
|
#include "net/log/test_net_log.h"
|
|
#include "net/log/test_net_log_util.h"
|
|
#include "net/quic/address_utils.h"
|
|
#include "net/quic/crypto/proof_verifier_chromium.h"
|
|
#include "net/quic/mock_crypto_client_stream_factory.h"
|
|
#include "net/quic/quic_chromium_alarm_factory.h"
|
|
#include "net/quic/quic_chromium_connection_helper.h"
|
|
#include "net/quic/quic_chromium_packet_reader.h"
|
|
#include "net/quic/quic_chromium_packet_writer.h"
|
|
#include "net/quic/quic_context.h"
|
|
#include "net/quic/quic_crypto_client_config_handle.h"
|
|
#include "net/quic/quic_http_utils.h"
|
|
#include "net/quic/quic_server_info.h"
|
|
#include "net/quic/quic_stream_factory.h"
|
|
#include "net/quic/quic_test_packet_maker.h"
|
|
#include "net/quic/quic_test_packet_printer.h"
|
|
#include "net/quic/test_quic_crypto_client_config_handle.h"
|
|
#include "net/quic/test_task_runner.h"
|
|
#include "net/socket/socket_performance_watcher.h"
|
|
#include "net/socket/socket_test_util.h"
|
|
#include "net/spdy/spdy_http_utils.h"
|
|
#include "net/ssl/ssl_config_service_defaults.h"
|
|
#include "net/test/cert_test_util.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/congestion_control/send_algorithm_interface.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_protocol.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_decrypter.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_encrypter.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/http/spdy_server_push_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/quic_connection.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/quic_write_blocked_list.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/core/tls_client_handshaker.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/platform/api/quic_flags.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_clock.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_connection_id_generator.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_random.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/qpack/qpack_test_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_connection_peer.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_spdy_session_peer.h"
|
|
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.h"
|
|
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.h"
|
|
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h"
|
|
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
|
|
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "url/scheme_host_port.h"
|
|
#include "url/url_constants.h"
|
|
|
|
using std::string;
|
|
using testing::_;
|
|
using testing::AnyNumber;
|
|
using testing::Return;
|
|
|
|
namespace net::test {
|
|
namespace {
|
|
|
|
const char kUploadData[] = "Really nifty data!";
|
|
const char kDefaultServerHostName[] = "www.example.org";
|
|
const uint16_t kDefaultServerPort = 443;
|
|
|
|
struct TestParams {
|
|
quic::ParsedQuicVersion version;
|
|
bool enable_quic_priority_incremental_support;
|
|
};
|
|
|
|
// Used by ::testing::PrintToStringParamName().
|
|
std::string PrintToString(const TestParams& p) {
|
|
return base::StrCat({ParsedQuicVersionToString(p.version), "_",
|
|
(p.enable_quic_priority_incremental_support ? "" : "No"),
|
|
"Incremental"});
|
|
}
|
|
|
|
std::vector<TestParams> GetTestParams() {
|
|
std::vector<TestParams> params;
|
|
quic::ParsedQuicVersionVector all_supported_versions =
|
|
AllSupportedQuicVersions();
|
|
for (const auto& version : all_supported_versions) {
|
|
params.push_back(TestParams{version, false});
|
|
params.push_back(TestParams{version, true});
|
|
}
|
|
return params;
|
|
}
|
|
|
|
// Returns true if |params| is a dict, has an entry with key "headers", that
|
|
// entry is a list of strings, which when interpreted as colon-separated
|
|
// key-value pairs has exactly one entry with |key| and that entry has value
|
|
// |expected_value|.
|
|
bool CheckHeader(const base::Value::Dict& params,
|
|
base::StringPiece key,
|
|
base::StringPiece expected_value) {
|
|
const base::Value::List* headers = params.FindList("headers");
|
|
if (!headers) {
|
|
return false;
|
|
}
|
|
|
|
std::string header_prefix = base::StrCat({key, ": "});
|
|
std::string expected_header = base::StrCat({header_prefix, expected_value});
|
|
|
|
bool header_found = false;
|
|
for (const auto& header_value : *headers) {
|
|
const std::string* header = header_value.GetIfString();
|
|
if (!header) {
|
|
return false;
|
|
}
|
|
if (base::StartsWith(*header, header_prefix)) {
|
|
if (header_found) {
|
|
return false;
|
|
}
|
|
if (*header != expected_header) {
|
|
return false;
|
|
}
|
|
header_found = true;
|
|
}
|
|
}
|
|
return header_found;
|
|
}
|
|
|
|
class TestQuicConnection : public quic::QuicConnection {
|
|
public:
|
|
TestQuicConnection(const quic::ParsedQuicVersionVector& versions,
|
|
quic::QuicConnectionId connection_id,
|
|
IPEndPoint address,
|
|
QuicChromiumConnectionHelper* helper,
|
|
QuicChromiumAlarmFactory* alarm_factory,
|
|
quic::QuicPacketWriter* writer,
|
|
quic::ConnectionIdGeneratorInterface& generator)
|
|
: quic::QuicConnection(connection_id,
|
|
quic::QuicSocketAddress(),
|
|
ToQuicSocketAddress(address),
|
|
helper,
|
|
alarm_factory,
|
|
writer,
|
|
true /* owns_writer */,
|
|
quic::Perspective::IS_CLIENT,
|
|
versions,
|
|
generator) {}
|
|
|
|
void SetSendAlgorithm(quic::SendAlgorithmInterface* send_algorithm) {
|
|
quic::test::QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
|
|
}
|
|
};
|
|
|
|
// UploadDataStream that always returns errors on data read.
|
|
class ReadErrorUploadDataStream : public UploadDataStream {
|
|
public:
|
|
enum class FailureMode { SYNC, ASYNC };
|
|
|
|
explicit ReadErrorUploadDataStream(FailureMode mode)
|
|
: UploadDataStream(true, 0), async_(mode) {}
|
|
|
|
ReadErrorUploadDataStream(const ReadErrorUploadDataStream&) = delete;
|
|
ReadErrorUploadDataStream& operator=(const ReadErrorUploadDataStream&) =
|
|
delete;
|
|
|
|
~ReadErrorUploadDataStream() override = default;
|
|
|
|
private:
|
|
void CompleteRead() { UploadDataStream::OnReadCompleted(ERR_FAILED); }
|
|
|
|
// UploadDataStream implementation:
|
|
int InitInternal(const NetLogWithSource& net_log) override { return OK; }
|
|
|
|
int ReadInternal(IOBuffer* buf, int buf_len) override {
|
|
if (async_ == FailureMode::ASYNC) {
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&ReadErrorUploadDataStream::CompleteRead,
|
|
weak_factory_.GetWeakPtr()));
|
|
return ERR_IO_PENDING;
|
|
}
|
|
return ERR_FAILED;
|
|
}
|
|
|
|
void ResetInternal() override {}
|
|
|
|
const FailureMode async_;
|
|
|
|
base::WeakPtrFactory<ReadErrorUploadDataStream> weak_factory_{this};
|
|
};
|
|
|
|
// A helper class that will delete |stream| when the callback is invoked.
|
|
class DeleteStreamCallback : public TestCompletionCallbackBase {
|
|
public:
|
|
explicit DeleteStreamCallback(std::unique_ptr<QuicHttpStream> stream)
|
|
: stream_(std::move(stream)) {}
|
|
|
|
CompletionOnceCallback callback() {
|
|
return base::BindOnce(&DeleteStreamCallback::DeleteStream,
|
|
base::Unretained(this));
|
|
}
|
|
|
|
private:
|
|
void DeleteStream(int result) {
|
|
stream_.reset();
|
|
SetResult(result);
|
|
}
|
|
|
|
std::unique_ptr<QuicHttpStream> stream_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class QuicHttpStreamPeer {
|
|
public:
|
|
static QuicChromiumClientStream::Handle* GetQuicChromiumClientStream(
|
|
QuicHttpStream* stream) {
|
|
return stream->stream_.get();
|
|
}
|
|
};
|
|
|
|
class QuicHttpStreamTest : public ::testing::TestWithParam<TestParams>,
|
|
public WithTaskEnvironment {
|
|
public:
|
|
void CloseStream(QuicHttpStream* stream, int /*rv*/) { stream->Close(false); }
|
|
|
|
protected:
|
|
static const bool kFin = true;
|
|
static const bool kIncludeVersion = true;
|
|
|
|
// Holds a packet to be written to the wire, and the IO mode that should
|
|
// be used by the mock socket when performing the write.
|
|
struct PacketToWrite {
|
|
PacketToWrite(IoMode mode, std::unique_ptr<quic::QuicReceivedPacket> packet)
|
|
: mode(mode), packet(std::move(packet)) {}
|
|
PacketToWrite(IoMode mode, int rv) : mode(mode), rv(rv) {}
|
|
|
|
IoMode mode;
|
|
int rv;
|
|
std::unique_ptr<quic::QuicReceivedPacket> packet;
|
|
};
|
|
|
|
QuicHttpStreamTest()
|
|
: version_(GetParam().version),
|
|
crypto_config_(
|
|
quic::test::crypto_test_utils::ProofVerifierForTesting()),
|
|
read_buffer_(base::MakeRefCounted<IOBufferWithSize>(4096)),
|
|
promise_id_(GetNthServerInitiatedUnidirectionalStreamId(0)),
|
|
stream_id_(GetNthClientInitiatedBidirectionalStreamId(0)),
|
|
connection_id_(quic::test::TestConnectionId(2)),
|
|
client_maker_(version_,
|
|
connection_id_,
|
|
&clock_,
|
|
kDefaultServerHostName,
|
|
quic::Perspective::IS_CLIENT,
|
|
true),
|
|
server_maker_(version_,
|
|
connection_id_,
|
|
&clock_,
|
|
kDefaultServerHostName,
|
|
quic::Perspective::IS_SERVER,
|
|
false),
|
|
printer_(version_) {
|
|
scoped_feature_list_.InitWithFeatureState(
|
|
features::kPriorityIncremental,
|
|
GetParam().enable_quic_priority_incremental_support);
|
|
FLAGS_quic_enable_http3_grease_randomness = false;
|
|
quic::QuicEnableVersion(version_);
|
|
IPAddress ip(192, 0, 2, 33);
|
|
peer_addr_ = IPEndPoint(ip, 443);
|
|
self_addr_ = IPEndPoint(ip, 8435);
|
|
clock_.AdvanceTime(quic::QuicTime::Delta::FromMilliseconds(20));
|
|
request_.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
}
|
|
|
|
~QuicHttpStreamTest() override {
|
|
session_->CloseSessionOnError(ERR_ABORTED, quic::QUIC_INTERNAL_ERROR,
|
|
quic::ConnectionCloseBehavior::SILENT_CLOSE);
|
|
}
|
|
|
|
// Adds a packet to the list of expected writes.
|
|
void AddWrite(std::unique_ptr<quic::QuicReceivedPacket> packet) {
|
|
writes_.emplace_back(SYNCHRONOUS, std::move(packet));
|
|
}
|
|
|
|
void AddWrite(IoMode mode, int rv) { writes_.emplace_back(mode, rv); }
|
|
|
|
// Returns the packet to be written at position |pos|.
|
|
quic::QuicReceivedPacket* GetWrite(size_t pos) {
|
|
return writes_[pos].packet.get();
|
|
}
|
|
|
|
bool AtEof() {
|
|
return socket_data_->AllReadDataConsumed() &&
|
|
socket_data_->AllWriteDataConsumed();
|
|
}
|
|
|
|
void ProcessPacket(std::unique_ptr<quic::QuicReceivedPacket> packet) {
|
|
connection_->ProcessUdpPacket(ToQuicSocketAddress(self_addr_),
|
|
ToQuicSocketAddress(peer_addr_), *packet);
|
|
}
|
|
|
|
// Configures the test fixture to use the list of expected writes.
|
|
void Initialize() {
|
|
mock_writes_ = std::make_unique<MockWrite[]>(writes_.size());
|
|
for (size_t i = 0; i < writes_.size(); i++) {
|
|
if (writes_[i].packet == nullptr) {
|
|
mock_writes_[i] = MockWrite(writes_[i].mode, writes_[i].rv, i);
|
|
} else {
|
|
mock_writes_[i] = MockWrite(writes_[i].mode, writes_[i].packet->data(),
|
|
writes_[i].packet->length());
|
|
}
|
|
}
|
|
|
|
socket_data_ = std::make_unique<StaticSocketDataProvider>(
|
|
base::span<MockRead>(),
|
|
base::make_span(mock_writes_.get(), writes_.size()));
|
|
socket_data_->set_printer(&printer_);
|
|
|
|
auto socket = std::make_unique<MockUDPClientSocket>(socket_data_.get(),
|
|
NetLog::Get());
|
|
socket->Connect(peer_addr_);
|
|
runner_ = base::MakeRefCounted<TestTaskRunner>(&clock_);
|
|
send_algorithm_ = new quic::test::MockSendAlgorithm();
|
|
EXPECT_CALL(*send_algorithm_, InRecovery()).WillRepeatedly(Return(false));
|
|
EXPECT_CALL(*send_algorithm_, InSlowStart()).WillRepeatedly(Return(false));
|
|
EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
|
|
.Times(testing::AtLeast(1));
|
|
EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _, _, _))
|
|
.Times(AnyNumber());
|
|
EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
|
|
.WillRepeatedly(Return(quic::kMaxOutgoingPacketSize));
|
|
EXPECT_CALL(*send_algorithm_, PacingRate(_))
|
|
.WillRepeatedly(Return(quic::QuicBandwidth::Zero()));
|
|
EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true));
|
|
EXPECT_CALL(*send_algorithm_, BandwidthEstimate())
|
|
.WillRepeatedly(Return(quic::QuicBandwidth::Zero()));
|
|
EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
|
|
EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber());
|
|
EXPECT_CALL(*send_algorithm_, GetCongestionControlType())
|
|
.Times(AnyNumber());
|
|
helper_ = std::make_unique<QuicChromiumConnectionHelper>(
|
|
&clock_, &random_generator_);
|
|
alarm_factory_ =
|
|
std::make_unique<QuicChromiumAlarmFactory>(runner_.get(), &clock_);
|
|
|
|
connection_ = new TestQuicConnection(
|
|
quic::test::SupportedVersions(version_), connection_id_, peer_addr_,
|
|
helper_.get(), alarm_factory_.get(),
|
|
new QuicChromiumPacketWriter(
|
|
socket.get(),
|
|
base::SingleThreadTaskRunner::GetCurrentDefault().get()),
|
|
connection_id_generator_);
|
|
connection_->set_visitor(&visitor_);
|
|
connection_->SetSendAlgorithm(send_algorithm_);
|
|
|
|
// Load a certificate that is valid for *.example.org
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
|
|
EXPECT_TRUE(test_cert.get());
|
|
|
|
verify_details_.cert_verify_result.verified_cert = test_cert;
|
|
verify_details_.cert_verify_result.is_issued_by_known_root = true;
|
|
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details_);
|
|
|
|
base::TimeTicks dns_end = base::TimeTicks::Now();
|
|
base::TimeTicks dns_start = dns_end - base::Milliseconds(1);
|
|
session_ = std::make_unique<QuicChromiumClientSession>(
|
|
connection_, std::move(socket),
|
|
/*stream_factory=*/nullptr, &crypto_client_stream_factory_, &clock_,
|
|
&transport_security_state_, &ssl_config_service_,
|
|
base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
|
|
QuicSessionKey(kDefaultServerHostName, kDefaultServerPort,
|
|
PRIVACY_MODE_DISABLED, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
|
|
/*require_dns_https_alpn=*/false),
|
|
/*require_confirmation=*/false,
|
|
/*migrate_session_early_v2=*/false,
|
|
/*migrate_session_on_network_change_v2=*/false,
|
|
/*default_network=*/handles::kInvalidNetworkHandle,
|
|
quic::QuicTime::Delta::FromMilliseconds(
|
|
kDefaultRetransmittableOnWireTimeout.InMilliseconds()),
|
|
/*migrate_idle_session=*/false, /*allow_port_migration=*/false,
|
|
kDefaultIdleSessionMigrationPeriod, kMaxTimeOnNonDefaultNetwork,
|
|
kMaxMigrationsToNonDefaultNetworkOnWriteError,
|
|
kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
|
|
kQuicYieldAfterPacketsRead,
|
|
quic::QuicTime::Delta::FromMilliseconds(
|
|
kQuicYieldAfterDurationMilliseconds),
|
|
/*cert_verify_flags=*/0, quic::test::DefaultQuicConfig(),
|
|
std::make_unique<TestQuicCryptoClientConfigHandle>(&crypto_config_),
|
|
"CONNECTION_UNKNOWN", dns_start, dns_end,
|
|
std::make_unique<quic::QuicClientPushPromiseIndex>(), nullptr,
|
|
base::DefaultTickClock::GetInstance(),
|
|
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
|
|
/*socket_performance_watcher=*/nullptr, HostResolverEndpointResult(),
|
|
NetLog::Get());
|
|
session_->Initialize();
|
|
|
|
// Blackhole QPACK decoder stream instead of constructing mock writes.
|
|
session_->qpack_decoder()->set_qpack_stream_sender_delegate(
|
|
&noop_qpack_stream_sender_delegate_);
|
|
|
|
TestCompletionCallback callback;
|
|
session_->CryptoConnect(callback.callback());
|
|
stream_ = std::make_unique<QuicHttpStream>(
|
|
session_->CreateHandle(
|
|
url::SchemeHostPort(url::kHttpsScheme, "www.example.org", 443)),
|
|
/*dns_aliases=*/std::set<std::string>());
|
|
promised_stream_ = std::make_unique<QuicHttpStream>(
|
|
session_->CreateHandle(
|
|
url::SchemeHostPort(url::kHttpsScheme, "www.example.org", 443)),
|
|
/*dns_aliases=*/std::set<std::string>());
|
|
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";
|
|
promised_response_[":version"] = "HTTP/1.1";
|
|
promised_response_["content-type"] = "text/plain";
|
|
|
|
promise_url_ =
|
|
quic::SpdyServerPushUtils::GetPromisedUrlFromHeaders(push_promise_);
|
|
}
|
|
|
|
void SetRequest(const string& method,
|
|
const string& path,
|
|
RequestPriority priority) {
|
|
request_headers_ = client_maker_.GetRequestHeaders(method, "https", path);
|
|
}
|
|
|
|
void SetResponse(const string& status, const string& body) {
|
|
response_headers_ = server_maker_.GetResponseHeaders(status);
|
|
response_data_ = body;
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructClientDataPacket(
|
|
uint64_t packet_number,
|
|
bool should_include_version,
|
|
bool fin,
|
|
absl::string_view data) {
|
|
return client_maker_.MakeDataPacket(packet_number, stream_id_,
|
|
should_include_version, fin, data);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructServerDataPacket(
|
|
uint64_t packet_number,
|
|
bool should_include_version,
|
|
bool fin,
|
|
absl::string_view data) {
|
|
return server_maker_.MakeDataPacket(packet_number, stream_id_,
|
|
should_include_version, fin, data);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> InnerConstructRequestHeadersPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicStreamId stream_id,
|
|
bool should_include_version,
|
|
bool fin,
|
|
RequestPriority request_priority,
|
|
size_t* spdy_headers_frame_length) {
|
|
spdy::SpdyPriority priority =
|
|
ConvertRequestPriorityToQuicPriority(request_priority);
|
|
return client_maker_.MakeRequestHeadersPacket(
|
|
packet_number, stream_id, should_include_version, fin, priority,
|
|
std::move(request_headers_), spdy_headers_frame_length);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket>
|
|
ConstructRequestHeadersAndDataFramesPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicStreamId stream_id,
|
|
bool should_include_version,
|
|
bool fin,
|
|
RequestPriority request_priority,
|
|
size_t* spdy_headers_frame_length,
|
|
const std::vector<std::string>& data_writes) {
|
|
spdy::SpdyPriority priority =
|
|
ConvertRequestPriorityToQuicPriority(request_priority);
|
|
return client_maker_.MakeRequestHeadersAndMultipleDataFramesPacket(
|
|
packet_number, stream_id, should_include_version, fin, priority,
|
|
std::move(request_headers_), spdy_headers_frame_length, data_writes);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructRequestAndRstPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicStreamId stream_id,
|
|
bool should_include_version,
|
|
bool fin,
|
|
RequestPriority request_priority,
|
|
size_t* spdy_headers_frame_length,
|
|
quic::QuicRstStreamErrorCode error_code) {
|
|
spdy::SpdyPriority priority =
|
|
ConvertRequestPriorityToQuicPriority(request_priority);
|
|
return client_maker_.MakeRequestHeadersAndRstPacket(
|
|
packet_number, stream_id, should_include_version, fin, priority,
|
|
std::move(request_headers_), spdy_headers_frame_length, error_code);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> InnerConstructResponseHeadersPacket(
|
|
uint64_t packet_number,
|
|
quic::QuicStreamId stream_id,
|
|
bool fin,
|
|
size_t* spdy_headers_frame_length) {
|
|
return server_maker_.MakeResponseHeadersPacket(
|
|
packet_number, stream_id, !kIncludeVersion, fin,
|
|
std::move(response_headers_), spdy_headers_frame_length);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructResponseHeadersPacket(
|
|
uint64_t packet_number,
|
|
bool fin,
|
|
size_t* spdy_headers_frame_length) {
|
|
return InnerConstructResponseHeadersPacket(packet_number, stream_id_, fin,
|
|
spdy_headers_frame_length);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructResponseTrailersPacket(
|
|
uint64_t packet_number,
|
|
bool fin,
|
|
spdy::Http2HeaderBlock trailers,
|
|
size_t* spdy_headers_frame_length) {
|
|
return server_maker_.MakeResponseHeadersPacket(
|
|
packet_number, stream_id_, !kIncludeVersion, fin, std::move(trailers),
|
|
spdy_headers_frame_length);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructClientRstStreamErrorPacket(
|
|
uint64_t packet_number,
|
|
bool include_version) {
|
|
return client_maker_.MakeRstPacket(packet_number, include_version,
|
|
stream_id_,
|
|
quic::QUIC_ERROR_PROCESSING_STREAM);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructAckAndRstStreamPacket(
|
|
uint64_t packet_number) {
|
|
return client_maker_.MakeAckAndRstPacket(packet_number, !kIncludeVersion,
|
|
stream_id_,
|
|
quic::QUIC_STREAM_CANCELLED, 2, 1);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructClientAckPacket(
|
|
uint64_t packet_number,
|
|
uint64_t largest_received,
|
|
uint64_t smallest_received) {
|
|
return client_maker_.MakeAckPacket(packet_number, largest_received,
|
|
smallest_received);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructServerAckPacket(
|
|
uint64_t packet_number,
|
|
uint64_t largest_received,
|
|
uint64_t smallest_received,
|
|
uint64_t least_unacked) {
|
|
return server_maker_.MakeAckPacket(packet_number, largest_received,
|
|
smallest_received, least_unacked);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructInitialSettingsPacket() {
|
|
return client_maker_.MakeInitialSettingsPacket(1);
|
|
}
|
|
|
|
std::unique_ptr<quic::QuicReceivedPacket> ConstructInitialSettingsPacket(
|
|
int packet_number) {
|
|
return client_maker_.MakeInitialSettingsPacket(packet_number);
|
|
}
|
|
|
|
std::string ConstructDataHeader(size_t body_len) {
|
|
quiche::QuicheBuffer buffer = quic::HttpEncoder::SerializeDataFrameHeader(
|
|
body_len, quiche::SimpleBufferAllocator::Get());
|
|
return std::string(buffer.data(), buffer.size());
|
|
}
|
|
|
|
void ReceivePromise(quic::QuicStreamId id) {
|
|
auto headers = quic::test::AsHeaderList(push_promise_);
|
|
QuicChromiumClientStream::Handle* stream =
|
|
QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
|
|
stream->OnPromiseHeaderList(id, headers.uncompressed_header_bytes(),
|
|
headers);
|
|
}
|
|
|
|
void ExpectLoadTimingValid(const LoadTimingInfo& load_timing_info,
|
|
bool session_reused) {
|
|
EXPECT_EQ(session_reused, load_timing_info.socket_reused);
|
|
if (session_reused) {
|
|
ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
|
|
} else {
|
|
ExpectConnectTimingHasTimes(
|
|
load_timing_info.connect_timing,
|
|
CONNECT_TIMING_HAS_SSL_TIMES | CONNECT_TIMING_HAS_DNS_TIMES);
|
|
}
|
|
ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
|
|
}
|
|
|
|
quic::QuicStreamId GetNthClientInitiatedBidirectionalStreamId(int n) {
|
|
return quic::test::GetNthClientInitiatedBidirectionalStreamId(
|
|
version_.transport_version, n);
|
|
}
|
|
|
|
quic::QuicStreamId GetNthServerInitiatedUnidirectionalStreamId(int n) {
|
|
return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
|
|
version_.transport_version, n);
|
|
}
|
|
|
|
quic::test::QuicFlagSaver saver_;
|
|
|
|
const quic::ParsedQuicVersion version_;
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
|
|
NetLogWithSource net_log_with_source_{
|
|
NetLogWithSource::Make(NetLog::Get(), NetLogSourceType::NONE)};
|
|
RecordingNetLogObserver net_log_observer_;
|
|
scoped_refptr<TestTaskRunner> runner_;
|
|
std::unique_ptr<MockWrite[]> mock_writes_;
|
|
quic::MockClock clock_;
|
|
std::unique_ptr<QuicChromiumConnectionHelper> helper_;
|
|
std::unique_ptr<QuicChromiumAlarmFactory> alarm_factory_;
|
|
testing::StrictMock<quic::test::MockQuicConnectionVisitor> visitor_;
|
|
std::unique_ptr<UploadDataStream> upload_data_stream_;
|
|
std::unique_ptr<QuicHttpStream> stream_;
|
|
TransportSecurityState transport_security_state_;
|
|
SSLConfigServiceDefaults ssl_config_service_;
|
|
|
|
// Must outlive `send_algorithm_` and `connection_`.
|
|
std::unique_ptr<QuicChromiumClientSession> session_;
|
|
raw_ptr<quic::test::MockSendAlgorithm> send_algorithm_;
|
|
raw_ptr<TestQuicConnection> connection_;
|
|
|
|
quic::QuicCryptoClientConfig crypto_config_;
|
|
TestCompletionCallback callback_;
|
|
HttpRequestInfo request_;
|
|
HttpRequestHeaders headers_;
|
|
HttpResponseInfo response_;
|
|
scoped_refptr<IOBufferWithSize> read_buffer_;
|
|
spdy::Http2HeaderBlock request_headers_;
|
|
spdy::Http2HeaderBlock response_headers_;
|
|
string request_data_;
|
|
string response_data_;
|
|
|
|
// For server push testing
|
|
std::unique_ptr<QuicHttpStream> promised_stream_;
|
|
spdy::Http2HeaderBlock push_promise_;
|
|
spdy::Http2HeaderBlock promised_response_;
|
|
const quic::QuicStreamId promise_id_;
|
|
string promise_url_;
|
|
const quic::QuicStreamId stream_id_;
|
|
|
|
const quic::QuicConnectionId connection_id_;
|
|
QuicTestPacketMaker client_maker_;
|
|
QuicTestPacketMaker server_maker_;
|
|
IPEndPoint self_addr_;
|
|
IPEndPoint peer_addr_;
|
|
quic::test::MockRandom random_generator_{0};
|
|
ProofVerifyDetailsChromium verify_details_;
|
|
MockCryptoClientStreamFactory crypto_client_stream_factory_;
|
|
std::unique_ptr<StaticSocketDataProvider> socket_data_;
|
|
QuicPacketPrinter printer_;
|
|
std::vector<PacketToWrite> writes_;
|
|
quic::test::MockConnectionIdGenerator connection_id_generator_;
|
|
quic::test::NoopQpackStreamSenderDelegate noop_qpack_stream_sender_delegate_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(VersionIncludeStreamDependencySequence,
|
|
QuicHttpStreamTest,
|
|
::testing::ValuesIn(GetTestParams()),
|
|
::testing::PrintToStringParamName());
|
|
|
|
TEST_P(QuicHttpStreamTest, RenewStreamForAuth) {
|
|
Initialize();
|
|
EXPECT_EQ(nullptr, stream_->RenewStreamForAuth());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, CanReuseConnection) {
|
|
Initialize();
|
|
EXPECT_FALSE(stream_->CanReuseConnection());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, DisableConnectionMigrationForStream) {
|
|
request_.load_flags |= LOAD_DISABLE_CONNECTION_MIGRATION_TO_CELLULAR;
|
|
Initialize();
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
QuicChromiumClientStream::Handle* client_stream =
|
|
QuicHttpStreamPeer::GetQuicChromiumClientStream(stream_.get());
|
|
EXPECT_FALSE(client_stream->can_migrate_to_cellular_network());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, GetRequest) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_header_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_header_frame_length));
|
|
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
// Make sure getting load timing from the stream early does not crash.
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
SetResponse("404", string());
|
|
size_t spdy_response_header_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, kFin, &spdy_response_header_frame_length));
|
|
|
|
// Now that the headers have been processed, the callback will return.
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
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,
|
|
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback_.callback()));
|
|
EXPECT_TRUE(stream_->IsResponseBodyComplete());
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
|
|
ExpectLoadTimingValid(load_timing_info, /*session_reused=*/false);
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
|
|
// headers and payload.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_header_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_response_header_frame_length),
|
|
stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, LoadTimingTwoRequests) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_header_frame_length;
|
|
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_header_frame_length));
|
|
|
|
// SetRequest() again for second request as |request_headers_| was moved.
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(1),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_header_frame_length));
|
|
AddWrite(
|
|
ConstructClientAckPacket(packet_number++, 3, 1)); // Ack the responses.
|
|
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
// Start first request.
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Start a second request.
|
|
QuicHttpStream stream2(session_->CreateHandle(url::SchemeHostPort(
|
|
url::kHttpsScheme, "www.example.org", 443)),
|
|
{} /* dns_aliases */);
|
|
TestCompletionCallback callback2;
|
|
stream2.RegisterRequest(&request_);
|
|
EXPECT_EQ(
|
|
OK, stream2.InitializeStream(true, DEFAULT_PRIORITY, net_log_with_source_,
|
|
callback2.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream2.SendRequest(headers_, &response_, callback2.callback()));
|
|
|
|
// Ack both requests.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
size_t spdy_response_header_frame_length;
|
|
SetResponse("200", string());
|
|
ProcessPacket(InnerConstructResponseHeadersPacket(
|
|
2, GetNthClientInitiatedBidirectionalStreamId(0), kFin,
|
|
&spdy_response_header_frame_length));
|
|
|
|
// Now that the headers have been processed, the callback will return.
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
EXPECT_EQ(200, response_.headers->response_code());
|
|
|
|
// There is no body, so this should return immediately.
|
|
EXPECT_EQ(0,
|
|
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback_.callback()));
|
|
EXPECT_TRUE(stream_->IsResponseBodyComplete());
|
|
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_TRUE(stream_->GetLoadTimingInfo(&load_timing_info));
|
|
ExpectLoadTimingValid(load_timing_info, /*session_reused=*/false);
|
|
|
|
// SetResponse() again for second request as |response_headers_| was moved.
|
|
SetResponse("200", string());
|
|
EXPECT_THAT(stream2.ReadResponseHeaders(callback2.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
ProcessPacket(InnerConstructResponseHeadersPacket(
|
|
3, GetNthClientInitiatedBidirectionalStreamId(1), kFin,
|
|
&spdy_response_header_frame_length));
|
|
|
|
EXPECT_THAT(callback2.WaitForResult(), IsOk());
|
|
|
|
// There is no body, so this should return immediately.
|
|
EXPECT_EQ(0,
|
|
stream2.ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback2.callback()));
|
|
EXPECT_TRUE(stream2.IsResponseBodyComplete());
|
|
|
|
LoadTimingInfo load_timing_info2;
|
|
EXPECT_TRUE(stream2.GetLoadTimingInfo(&load_timing_info2));
|
|
ExpectLoadTimingValid(load_timing_info2, /*session_reused=*/true);
|
|
}
|
|
|
|
// QuicHttpStream does not currently support trailers. It should ignore
|
|
// trailers upon receiving them.
|
|
TEST_P(QuicHttpStreamTest, GetRequestWithTrailers) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_header_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_header_frame_length));
|
|
AddWrite(
|
|
ConstructClientAckPacket(packet_number++, 3, 1)); // Ack the data packet.
|
|
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
SetResponse("200", string());
|
|
|
|
// Send the response headers.
|
|
size_t spdy_response_header_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_header_frame_length));
|
|
// Now that the headers have been processed, the callback will return.
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
ASSERT_TRUE(response_.headers.get());
|
|
EXPECT_EQ(200, 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());
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, !kFin, header + kResponseBody));
|
|
spdy::Http2HeaderBlock trailers;
|
|
size_t spdy_trailers_frame_length;
|
|
trailers["foo"] = "bar";
|
|
ProcessPacket(ConstructResponseTrailersPacket(4, kFin, std::move(trailers),
|
|
&spdy_trailers_frame_length));
|
|
|
|
// Make sure trailers are processed.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
|
|
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback_.callback()));
|
|
EXPECT_TRUE(stream_->IsResponseBodyComplete());
|
|
|
|
EXPECT_EQ(OK,
|
|
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_header_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_response_header_frame_length +
|
|
strlen(kResponseBody) + header.length() +
|
|
+spdy_trailers_frame_length),
|
|
stream_->GetTotalReceivedBytes());
|
|
// Check that NetLog was filled as expected.
|
|
auto entries = net_log_observer_.GetEntries();
|
|
size_t pos = ExpectLogContainsSomewhere(
|
|
entries, /*min_offset=*/0,
|
|
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
|
|
NetLogEventPhase::NONE);
|
|
pos = ExpectLogContainsSomewhere(
|
|
entries, /*min_offset=*/pos,
|
|
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
|
|
NetLogEventPhase::NONE);
|
|
ExpectLogContainsSomewhere(
|
|
entries, /*min_offset=*/pos,
|
|
NetLogEventType::QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS,
|
|
NetLogEventPhase::NONE);
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, ElideHeadersInNetLog) {
|
|
Initialize();
|
|
|
|
net_log_observer_.SetObserverCaptureMode(NetLogCaptureMode::kDefault);
|
|
|
|
// Send first request.
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
headers_.SetHeader(HttpRequestHeaders::kCookie, "secret");
|
|
|
|
size_t spdy_request_header_frame_length;
|
|
int outgoing_packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(outgoing_packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
outgoing_packet_number++, stream_id_, kIncludeVersion, kFin,
|
|
DEFAULT_PRIORITY, &spdy_request_header_frame_length));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_THAT(
|
|
stream_->InitializeStream(true, DEFAULT_PRIORITY, net_log_with_source_,
|
|
callback_.callback()),
|
|
IsOk());
|
|
EXPECT_THAT(stream_->SendRequest(headers_, &response_, callback_.callback()),
|
|
IsOk());
|
|
int incoming_packet_number = 1;
|
|
ProcessPacket(ConstructServerAckPacket(incoming_packet_number++, 1, 1,
|
|
1)); // Ack the request.
|
|
|
|
// Process first response.
|
|
SetResponse("200", string());
|
|
response_headers_["set-cookie"] = "secret";
|
|
size_t spdy_response_header_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
incoming_packet_number++, kFin, &spdy_response_header_frame_length));
|
|
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"));
|
|
EXPECT_TRUE(response_.headers->HasHeaderValue("set-cookie", "secret"));
|
|
|
|
net_log_observer_.SetObserverCaptureMode(
|
|
NetLogCaptureMode::kIncludeSensitive);
|
|
|
|
// Send second request.
|
|
quic::QuicStreamId stream_id = GetNthClientInitiatedBidirectionalStreamId(1);
|
|
request_.url = GURL("https://www.example.org/foo");
|
|
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
outgoing_packet_number++, stream_id, kIncludeVersion, kFin,
|
|
DEFAULT_PRIORITY, &spdy_request_header_frame_length));
|
|
|
|
auto stream = std::make_unique<QuicHttpStream>(
|
|
session_->CreateHandle(
|
|
url::SchemeHostPort(url::kHttpsScheme, "www.example.org/foo", 443)),
|
|
/*dns_aliases=*/std::set<std::string>());
|
|
stream->RegisterRequest(&request_);
|
|
EXPECT_THAT(
|
|
stream->InitializeStream(true, DEFAULT_PRIORITY, net_log_with_source_,
|
|
callback_.callback()),
|
|
IsOk());
|
|
EXPECT_THAT(stream->SendRequest(headers_, &response_, callback_.callback()),
|
|
IsOk());
|
|
ProcessPacket(ConstructServerAckPacket(incoming_packet_number++, 1, 1,
|
|
1)); // Ack the request.
|
|
|
|
// Process second response.
|
|
SetResponse("200", string());
|
|
response_headers_["set-cookie"] = "secret";
|
|
ProcessPacket(InnerConstructResponseHeadersPacket(
|
|
incoming_packet_number++, stream_id, kFin,
|
|
&spdy_response_header_frame_length));
|
|
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"));
|
|
EXPECT_TRUE(response_.headers->HasHeaderValue("set-cookie", "secret"));
|
|
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
// Check that sensitive header value were stripped
|
|
// for the first transaction (logged with NetLogCaptureMode::kDefault)
|
|
// but not for the second (logged with NetLogCaptureMode::kIncludeSensitive).
|
|
auto entries =
|
|
net_log_observer_.GetEntriesWithType(NetLogEventType::HTTP3_HEADERS_SENT);
|
|
ASSERT_EQ(2u, entries.size());
|
|
EXPECT_TRUE(
|
|
CheckHeader(entries[0].params, "cookie", "[6 bytes were stripped]"));
|
|
EXPECT_TRUE(CheckHeader(entries[1].params, "cookie", "secret"));
|
|
|
|
entries = net_log_observer_.GetEntriesWithType(
|
|
NetLogEventType::HTTP3_HEADERS_DECODED);
|
|
ASSERT_EQ(2u, entries.size());
|
|
EXPECT_TRUE(
|
|
CheckHeader(entries[0].params, "set-cookie", "[6 bytes were stripped]"));
|
|
EXPECT_TRUE(CheckHeader(entries[1].params, "set-cookie", "secret"));
|
|
}
|
|
|
|
// Regression test for http://crbug.com/288128
|
|
TEST_P(QuicHttpStreamTest, GetRequestLargeResponse) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
response_headers_[":status"] = "200";
|
|
response_headers_[":version"] = "HTTP/1.1";
|
|
response_headers_["content-type"] = "text/plain";
|
|
response_headers_["big6"] = string(1000, 'x'); // Lots of x's.
|
|
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, kFin, &spdy_response_headers_frame_length));
|
|
|
|
// Now that the headers have been processed, the callback will return.
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
ASSERT_TRUE(response_.headers.get());
|
|
EXPECT_EQ(200, response_.headers->response_code());
|
|
EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
|
|
|
|
// There is no body, so this should return immediately.
|
|
EXPECT_EQ(0,
|
|
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),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length),
|
|
stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
// Regression test for http://crbug.com/409101
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendRequest) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
session_->connection()->CloseConnection(
|
|
quic::QUIC_NO_ERROR, "test", quic::ConnectionCloseBehavior::SILENT_CLOSE);
|
|
|
|
EXPECT_EQ(ERR_CONNECTION_CLOSED,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
EXPECT_EQ(0, stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
// Regression test for http://crbug.com/584441
|
|
TEST_P(QuicHttpStreamTest, GetSSLInfoAfterSessionClosed) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
SSLInfo ssl_info;
|
|
EXPECT_FALSE(ssl_info.is_valid());
|
|
stream_->GetSSLInfo(&ssl_info);
|
|
EXPECT_TRUE(ssl_info.is_valid());
|
|
|
|
session_->connection()->CloseConnection(
|
|
quic::QUIC_NO_ERROR, "test", quic::ConnectionCloseBehavior::SILENT_CLOSE);
|
|
|
|
SSLInfo ssl_info2;
|
|
stream_->GetSSLInfo(&ssl_info2);
|
|
EXPECT_TRUE(ssl_info2.is_valid());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, GetAlternativeService) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
AlternativeService alternative_service;
|
|
EXPECT_TRUE(stream_->GetAlternativeService(&alternative_service));
|
|
EXPECT_EQ(AlternativeService(kProtoQUIC, "www.example.org", 443),
|
|
alternative_service);
|
|
|
|
session_->connection()->CloseConnection(
|
|
quic::QUIC_NO_ERROR, "test", quic::ConnectionCloseBehavior::SILENT_CLOSE);
|
|
|
|
AlternativeService alternative_service2;
|
|
EXPECT_TRUE(stream_->GetAlternativeService(&alternative_service2));
|
|
EXPECT_EQ(AlternativeService(kProtoQUIC, "www.example.org", 443),
|
|
alternative_service2);
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, LogGranularQuicConnectionError) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
AddWrite(ConstructAckAndRstStreamPacket(3));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
quic::QuicConnectionCloseFrame frame;
|
|
frame.quic_error_code = quic::QUIC_PEER_GOING_AWAY;
|
|
session_->connection()->OnConnectionCloseFrame(frame);
|
|
|
|
NetErrorDetails details;
|
|
EXPECT_EQ(quic::QUIC_NO_ERROR, details.quic_connection_error);
|
|
stream_->PopulateNetErrorDetails(&details);
|
|
EXPECT_EQ(quic::QUIC_PEER_GOING_AWAY, details.quic_connection_error);
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, LogGranularQuicErrorIfHandshakeNotConfirmed) {
|
|
// By default the test setup defaults handshake to be confirmed. Manually set
|
|
// it to be not confirmed.
|
|
crypto_client_stream_factory_.set_handshake_mode(
|
|
MockCryptoClientStream::ZERO_RTT);
|
|
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
client_maker_.SetEncryptionLevel(quic::ENCRYPTION_ZERO_RTT);
|
|
client_maker_.SetEncryptionLevel(quic::ENCRYPTION_ZERO_RTT);
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
quic::QuicConnectionCloseFrame frame;
|
|
frame.quic_error_code = quic::QUIC_PEER_GOING_AWAY;
|
|
session_->connection()->OnConnectionCloseFrame(frame);
|
|
|
|
NetErrorDetails details;
|
|
stream_->PopulateNetErrorDetails(&details);
|
|
EXPECT_EQ(quic::QUIC_PEER_GOING_AWAY, details.quic_connection_error);
|
|
}
|
|
|
|
// Regression test for http://crbug.com/409871
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeReadResponseHeaders) {
|
|
SetRequest("GET", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
session_->connection()->CloseConnection(
|
|
quic::QUIC_NO_ERROR, "test", quic::ConnectionCloseBehavior::SILENT_CLOSE);
|
|
|
|
EXPECT_NE(OK, stream_->ReadResponseHeaders(callback_.callback()));
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
|
|
// headers and payload.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SendPostRequest) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
|
|
std::string header = ConstructDataHeader(strlen(kUploadData));
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
|
|
Initialize();
|
|
|
|
std::vector<std::unique_ptr<UploadElementReader>> element_readers;
|
|
element_readers.push_back(std::make_unique<UploadBytesElementReader>(
|
|
kUploadData, strlen(kUploadData)));
|
|
upload_data_stream_ =
|
|
std::make_unique<ElementsUploadDataStream>(std::move(element_readers), 0);
|
|
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(CompletionOnceCallback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack both packets in the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
// Send the response headers (but not the body).
|
|
SetResponse("200", string());
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_headers_frame_length));
|
|
|
|
// The headers have already arrived.
|
|
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"));
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header2 = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, kFin, header2 + kResponseBody));
|
|
// Since the body has already arrived, this should return immediately.
|
|
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
|
|
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback_.callback()));
|
|
EXPECT_EQ(0,
|
|
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, SendPostRequestAndReceiveSoloFin) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
std::string header = ConstructDataHeader(strlen(kUploadData));
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
|
|
Initialize();
|
|
|
|
std::vector<std::unique_ptr<UploadElementReader>> element_readers;
|
|
element_readers.push_back(std::make_unique<UploadBytesElementReader>(
|
|
kUploadData, strlen(kUploadData)));
|
|
upload_data_stream_ =
|
|
std::make_unique<ElementsUploadDataStream>(std::move(element_readers), 0);
|
|
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(CompletionOnceCallback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack both packets in the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
// Send the response headers (but not the body).
|
|
SetResponse("200", string());
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_headers_frame_length));
|
|
|
|
// The headers have already arrived.
|
|
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"));
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header2 = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, !kFin, header2 + kResponseBody));
|
|
// Since the body has already arrived, this should return immediately.
|
|
EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
|
|
stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
|
|
callback_.callback()));
|
|
ProcessPacket(ConstructServerDataPacket(4, false, kFin, ""));
|
|
EXPECT_EQ(0,
|
|
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, SendChunkedPostRequest) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t chunk_size = strlen(kUploadData);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
std::string header = ConstructDataHeader(chunk_size);
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
AddWrite(ConstructClientDataPacket(packet_number++, kIncludeVersion, kFin,
|
|
{header + kUploadData}));
|
|
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
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_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
|
|
// Ack both packets in the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
// Send the response headers (but not the body).
|
|
SetResponse("200", string());
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_headers_frame_length));
|
|
|
|
// The headers have already arrived.
|
|
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"));
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header2 = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, kFin, header2 + kResponseBody));
|
|
|
|
// Since the body has already arrived, this should return immediately.
|
|
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) * 2 + header.length() * 2),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
|
|
strlen(kResponseBody) + header2.length()),
|
|
stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SendChunkedPostRequestWithFinalEmptyDataPacket) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t chunk_size = strlen(kUploadData);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
std::string header = ConstructDataHeader(chunk_size);
|
|
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
AddWrite(
|
|
ConstructClientDataPacket(packet_number++, kIncludeVersion, kFin, ""));
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
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_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
chunked_upload_stream->AppendData(nullptr, 0, true);
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
// Send the response headers (but not the body).
|
|
SetResponse("200", string());
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_headers_frame_length));
|
|
|
|
// The headers have already arrived.
|
|
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"));
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header2 = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, kFin, header2 + kResponseBody));
|
|
|
|
// The body has arrived, but it is delivered asynchronously
|
|
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, SendChunkedPostRequestWithOneEmptyDataPacket) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
AddWrite(
|
|
ConstructClientDataPacket(packet_number++, kIncludeVersion, kFin, ""));
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
chunked_upload_stream->AppendData(nullptr, 0, true);
|
|
EXPECT_THAT(callback_.WaitForResult(), IsOk());
|
|
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
|
|
// Send the response headers (but not the body).
|
|
SetResponse("200", string());
|
|
size_t spdy_response_headers_frame_length;
|
|
ProcessPacket(ConstructResponseHeadersPacket(
|
|
2, !kFin, &spdy_response_headers_frame_length));
|
|
|
|
// The headers have already arrived.
|
|
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"));
|
|
|
|
// Send the response body.
|
|
const char kResponseBody[] = "Hello world!";
|
|
std::string header = ConstructDataHeader(strlen(kResponseBody));
|
|
ProcessPacket(
|
|
ConstructServerDataPacket(3, false, kFin, header + kResponseBody));
|
|
|
|
// The body has arrived, but it is delivered asynchronously
|
|
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),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_response_headers_frame_length +
|
|
strlen(kResponseBody) + header.length()),
|
|
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;
|
|
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
|
|
std::string header = ConstructDataHeader(chunk_size);
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
AddWrite(ConstructClientAckPacket(packet_number++, 3, 1));
|
|
AddWrite(client_maker_.MakeAckAndRstPacket(
|
|
packet_number++, true, stream_id_, quic::QUIC_STREAM_NO_ERROR, 4, 1,
|
|
/* 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());
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_THAT(
|
|
stream_->InitializeStream(false, DEFAULT_PRIORITY, net_log_with_source_,
|
|
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", 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));
|
|
|
|
// The server uses a STOP_SENDING frame to notify the client that it does not
|
|
// need any further data to fully process the request.
|
|
ProcessPacket(server_maker_.MakeStopSendingPacket(
|
|
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;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
AddWrite(ConstructAckAndRstStreamPacket(packet_number++));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(
|
|
base::BindOnce(&QuicHttpStreamTest::CloseStream,
|
|
base::Unretained(this), stream_.get())),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
// Send the response with a body.
|
|
SetResponse("404", "hello world!");
|
|
// In the course of processing this packet, the QuicHttpStream close itself.
|
|
size_t response_size = 0;
|
|
ProcessPacket(ConstructResponseHeadersPacket(2, !kFin, &response_size));
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
|
|
// headers and payload.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
// The stream was closed after receiving the headers.
|
|
EXPECT_EQ(static_cast<int64_t>(response_size),
|
|
stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, Priority) {
|
|
SetRequest("GET", "/", MEDIUM);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, kFin, MEDIUM, &spdy_request_headers_frame_length));
|
|
Initialize();
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, MEDIUM, net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
EXPECT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Ack the request.
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
EXPECT_THAT(stream_->ReadResponseHeaders(callback_.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
// Send the response with a body.
|
|
SetResponse("404", "hello world!");
|
|
size_t response_size = 0;
|
|
ProcessPacket(ConstructResponseHeadersPacket(2, kFin, &response_size));
|
|
|
|
EXPECT_EQ(OK, callback_.WaitForResult());
|
|
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes currently only includes the
|
|
// headers and payload.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(response_size),
|
|
stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SessionClosedDuringDoLoop) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
std::string header = ConstructDataHeader(strlen(kUploadData));
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
|
|
// Second data write will result in a synchronous failure which will close
|
|
// the session.
|
|
AddWrite(SYNCHRONOUS, ERR_FAILED);
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
size_t chunk_size = strlen(kUploadData);
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
QuicHttpStream* stream = stream_.get();
|
|
DeleteStreamCallback delete_stream_callback(std::move(stream_));
|
|
// SendRequest() completes asynchronously after the final chunk is added.
|
|
// Error does not surface yet since packet write is triggered by a packet
|
|
// flusher that tries to bundle request body writes.
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream->SendRequest(headers_, &response_, callback_.callback()));
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
int rv = callback_.WaitForResult();
|
|
EXPECT_EQ(OK, rv);
|
|
// Error will be surfaced once an attempt to read the response occurs.
|
|
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
|
|
stream->ReadResponseHeaders(callback_.callback()));
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendHeadersComplete) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
AddWrite(ConstructInitialSettingsPacket());
|
|
AddWrite(SYNCHRONOUS, ERR_FAILED);
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Error will be surfaced once |upload_data_stream| triggers the next write.
|
|
size_t chunk_size = strlen(kUploadData);
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR, callback_.WaitForResult());
|
|
|
|
EXPECT_LE(0, stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendHeadersCompleteReadResponse) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
AddWrite(ConstructInitialSettingsPacket());
|
|
AddWrite(SYNCHRONOUS, ERR_FAILED);
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
|
|
size_t chunk_size = strlen(kUploadData);
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(OK,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
// Error will be surfaced once an attempt to read the response occurs.
|
|
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
|
|
stream_->ReadResponseHeaders(callback_.callback()));
|
|
|
|
EXPECT_LE(0, stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendBodyComplete) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
AddWrite(SYNCHRONOUS, ERR_FAILED);
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
size_t chunk_size = strlen(kUploadData);
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
// Error does not surface yet since packet write is triggered by a packet
|
|
// flusher that tries to bundle request body writes.
|
|
ASSERT_EQ(OK, callback_.WaitForResult());
|
|
// Error will be surfaced once an attempt to read the response occurs.
|
|
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
|
|
stream_->ReadResponseHeaders(callback_.callback()));
|
|
|
|
EXPECT_LE(0, stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendBundledBodyComplete) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
std::string header = ConstructDataHeader(strlen(kUploadData));
|
|
AddWrite(ConstructRequestHeadersAndDataFramesPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, {header, kUploadData}));
|
|
|
|
AddWrite(SYNCHRONOUS, ERR_FAILED);
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0);
|
|
auto* chunked_upload_stream =
|
|
static_cast<ChunkedUploadDataStream*>(upload_data_stream_.get());
|
|
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
|
|
size_t chunk_size = strlen(kUploadData);
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, false);
|
|
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
ASSERT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
ASSERT_EQ(ERR_IO_PENDING,
|
|
stream_->SendRequest(headers_, &response_, callback_.callback()));
|
|
|
|
chunked_upload_stream->AppendData(kUploadData, chunk_size, true);
|
|
|
|
// Error does not surface yet since packet write is triggered by a packet
|
|
// flusher that tries to bundle request body writes.
|
|
ASSERT_EQ(OK, callback_.WaitForResult());
|
|
// Error will be surfaced once an attempt to read the response occurs.
|
|
ASSERT_EQ(ERR_QUIC_PROTOCOL_ERROR,
|
|
stream_->ReadResponseHeaders(callback_.callback()));
|
|
|
|
EXPECT_LE(0, stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, DataReadErrorSynchronous) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(ConstructRequestAndRstPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length, quic::QUIC_ERROR_PROCESSING_STREAM));
|
|
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ReadErrorUploadDataStream>(
|
|
ReadErrorUploadDataStream::FailureMode::SYNC);
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
int result = stream_->SendRequest(headers_, &response_, callback_.callback());
|
|
EXPECT_THAT(result, IsError(ERR_FAILED));
|
|
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes includes only headers.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, DataReadErrorAsynchronous) {
|
|
SetRequest("POST", "/", DEFAULT_PRIORITY);
|
|
size_t spdy_request_headers_frame_length;
|
|
int packet_number = 1;
|
|
AddWrite(ConstructInitialSettingsPacket(packet_number++));
|
|
AddWrite(InnerConstructRequestHeadersPacket(
|
|
packet_number++, GetNthClientInitiatedBidirectionalStreamId(0),
|
|
kIncludeVersion, !kFin, DEFAULT_PRIORITY,
|
|
&spdy_request_headers_frame_length));
|
|
AddWrite(
|
|
ConstructClientRstStreamErrorPacket(packet_number++, !kIncludeVersion));
|
|
|
|
Initialize();
|
|
|
|
upload_data_stream_ = std::make_unique<ReadErrorUploadDataStream>(
|
|
ReadErrorUploadDataStream::FailureMode::ASYNC);
|
|
request_.method = "POST";
|
|
request_.url = GURL("https://www.example.org/");
|
|
request_.upload_data_stream = upload_data_stream_.get();
|
|
ASSERT_EQ(OK, request_.upload_data_stream->Init(
|
|
TestCompletionCallback().callback(), NetLogWithSource()));
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(false, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
|
|
int result = stream_->SendRequest(headers_, &response_, callback_.callback());
|
|
|
|
ProcessPacket(ConstructServerAckPacket(1, 1, 1, 1));
|
|
SetResponse("200", string());
|
|
|
|
EXPECT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(callback_.GetResult(result), IsError(ERR_FAILED));
|
|
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
// QuicHttpStream::GetTotalSent/ReceivedBytes includes only headers.
|
|
EXPECT_EQ(static_cast<int64_t>(spdy_request_headers_frame_length),
|
|
stream_->GetTotalSentBytes());
|
|
EXPECT_EQ(0, stream_->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_P(QuicHttpStreamTest, GetAcceptChViaAlps) {
|
|
AddWrite(ConstructInitialSettingsPacket());
|
|
Initialize();
|
|
|
|
base::HistogramTester histogram_tester;
|
|
|
|
session_->OnAcceptChFrameReceivedViaAlps(
|
|
{{{"https://www.example.org", "Sec-CH-UA-Platform"}}});
|
|
|
|
request_.method = "GET";
|
|
request_.url = GURL("https://www.example.org/foo");
|
|
|
|
stream_->RegisterRequest(&request_);
|
|
EXPECT_EQ(OK, stream_->InitializeStream(true, DEFAULT_PRIORITY,
|
|
net_log_with_source_,
|
|
callback_.callback()));
|
|
EXPECT_EQ("Sec-CH-UA-Platform", stream_->GetAcceptChViaAlps());
|
|
EXPECT_TRUE(AtEof());
|
|
|
|
histogram_tester.ExpectBucketCount(
|
|
"Net.QuicSession.AcceptChFrameReceivedViaAlps", 1, 1);
|
|
histogram_tester.ExpectTotalCount(
|
|
"Net.QuicSession.AcceptChFrameReceivedViaAlps", 1);
|
|
histogram_tester.ExpectBucketCount("Net.QuicSession.AcceptChForOrigin", 1, 1);
|
|
histogram_tester.ExpectTotalCount("Net.QuicSession.AcceptChForOrigin", 1);
|
|
}
|
|
|
|
} // namespace net::test
|