| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cmath> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/compiler_specific.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_file_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "net/base/auth.h" |
| #include "net/base/chunked_upload_data_stream.h" |
| #include "net/base/completion_once_callback.h" |
| #include "net/base/elements_upload_data_stream.h" |
| #include "net/base/features.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/base/proxy_delegate.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/request_priority.h" |
| #include "net/base/test_proxy_delegate.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/base/upload_file_element_reader.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_auth_scheme.h" |
| #include "net/http/http_network_session.h" |
| #include "net/http/http_network_session_peer.h" |
| #include "net/http/http_network_transaction.h" |
| #include "net/http/http_proxy_connect_job.h" |
| #include "net/http/http_response_info.h" |
| #include "net/http/http_server_properties.h" |
| #include "net/http/http_transaction_test_util.h" |
| #include "net/http/test_upload_data_stream_not_allow_http1.h" |
| #include "net/http/transport_security_state.h" |
| #include "net/log/net_log_event_type.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/proxy_resolution/configured_proxy_resolution_service.h" |
| #include "net/socket/next_proto.h" |
| #include "net/socket/socket_tag.h" |
| #include "net/spdy/buffered_spdy_framer.h" |
| #include "net/spdy/spdy_http_stream.h" |
| #include "net/spdy/spdy_http_utils.h" |
| #include "net/spdy/spdy_session.h" |
| #include "net/spdy/spdy_session_pool.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "net/ssl/ssl_connection_status_flags.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/spdy/core/spdy_protocol.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "net/websockets/websocket_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/platform_test.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| //----------------------------------------------------------------------------- |
| |
| namespace net { |
| |
| namespace { |
| |
| using testing::Each; |
| using testing::Eq; |
| |
| const int32_t kBufferSize = SpdyHttpStream::kRequestBodyBufferSize; |
| |
| } // namespace |
| |
| const char kPushedUrl[] = "https://www.example.org/foo.dat"; |
| |
| class SpdyNetworkTransactionTest : public TestWithTaskEnvironment { |
| protected: |
| SpdyNetworkTransactionTest() |
| : TestWithTaskEnvironment( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME), |
| default_url_(kDefaultUrl), |
| host_port_pair_(HostPortPair::FromURL(default_url_)) {} |
| |
| ~SpdyNetworkTransactionTest() override { |
| // UploadDataStream may post a deletion task back to the message loop on |
| // destruction. |
| upload_data_stream_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetUp() override { |
| request_.method = "GET"; |
| request_.url = GURL(kDefaultUrl); |
| request_.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| struct TransactionHelperResult { |
| int rv; |
| std::string status_line; |
| std::string response_data; |
| HttpResponseInfo response_info; |
| }; |
| |
| // A helper class that handles all the initial npn/ssl setup. |
| class NormalSpdyTransactionHelper { |
| public: |
| NormalSpdyTransactionHelper( |
| const HttpRequestInfo& request, |
| RequestPriority priority, |
| const NetLogWithSource& log, |
| std::unique_ptr<SpdySessionDependencies> session_deps) |
| : request_(request), |
| priority_(priority), |
| session_deps_(session_deps.get() == nullptr |
| ? std::make_unique<SpdySessionDependencies>() |
| : std::move(session_deps)), |
| log_(log) { |
| session_deps_->net_log = log.net_log(); |
| session_ = |
| SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); |
| } |
| |
| ~NormalSpdyTransactionHelper() { |
| // Any test which doesn't close the socket by sending it an EOF will |
| // have a valid session left open, which leaks the entire session pool. |
| // This is just fine - in fact, some of our tests intentionally do this |
| // so that we can check consistency of the SpdySessionPool as the test |
| // finishes. If we had put an EOF on the socket, the SpdySession would |
| // have closed and we wouldn't be able to check the consistency. |
| |
| // Forcefully close existing sessions here. |
| session()->spdy_session_pool()->CloseAllSessions(); |
| } |
| |
| void RunPreTestSetup() { |
| // We're now ready to use SSL-npn SPDY. |
| trans_ = |
| std::make_unique<HttpNetworkTransaction>(priority_, session_.get()); |
| } |
| |
| // Start the transaction, read some data, finish. |
| void RunDefaultTest() { |
| if (!StartDefaultTest()) |
| return; |
| FinishDefaultTest(); |
| } |
| |
| bool StartDefaultTest() { |
| output_.rv = trans_->Start(&request_, callback_.callback(), log_); |
| |
| // We expect an IO Pending or some sort of error. |
| EXPECT_LT(output_.rv, 0); |
| return output_.rv == ERR_IO_PENDING; |
| } |
| |
| void FinishDefaultTest() { |
| output_.rv = callback_.WaitForResult(); |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| if (output_.rv != OK) { |
| session_->spdy_session_pool()->CloseCurrentSessions(ERR_ABORTED); |
| return; |
| } |
| |
| // Verify responses. |
| const HttpResponseInfo* response = trans_->GetResponseInfo(); |
| ASSERT_TRUE(response); |
| ASSERT_TRUE(response->headers); |
| EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP2, |
| response->connection_info); |
| EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine()); |
| EXPECT_TRUE(response->was_fetched_via_spdy); |
| EXPECT_TRUE(response->was_alpn_negotiated); |
| EXPECT_EQ("127.0.0.1", response->remote_endpoint.ToStringWithoutPort()); |
| EXPECT_EQ(443, response->remote_endpoint.port()); |
| output_.status_line = response->headers->GetStatusLine(); |
| output_.response_info = *response; // Make a copy so we can verify. |
| output_.rv = ReadTransaction(trans_.get(), &output_.response_data); |
| } |
| |
| void FinishDefaultTestWithoutVerification() { |
| output_.rv = callback_.WaitForResult(); |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| if (output_.rv != OK) |
| session_->spdy_session_pool()->CloseCurrentSessions(ERR_ABORTED); |
| } |
| |
| void WaitForCallbackToComplete() { output_.rv = callback_.WaitForResult(); } |
| |
| // Most tests will want to call this function. In particular, the MockReads |
| // should end with an empty read, and that read needs to be processed to |
| // ensure proper deletion of the spdy_session_pool. |
| void VerifyDataConsumed() { |
| for (const SocketDataProvider* provider : data_vector_) { |
| EXPECT_TRUE(provider->AllReadDataConsumed()); |
| EXPECT_TRUE(provider->AllWriteDataConsumed()); |
| } |
| } |
| |
| // Occasionally a test will expect to error out before certain reads are |
| // processed. In that case we want to explicitly ensure that the reads were |
| // not processed. |
| void VerifyDataNotConsumed() { |
| for (const SocketDataProvider* provider : data_vector_) { |
| EXPECT_FALSE(provider->AllReadDataConsumed()); |
| EXPECT_FALSE(provider->AllWriteDataConsumed()); |
| } |
| } |
| |
| void RunToCompletion(SocketDataProvider* data) { |
| RunPreTestSetup(); |
| AddData(data); |
| RunDefaultTest(); |
| VerifyDataConsumed(); |
| } |
| |
| void RunToCompletionWithSSLData( |
| SocketDataProvider* data, |
| std::unique_ptr<SSLSocketDataProvider> ssl_provider) { |
| RunPreTestSetup(); |
| AddDataWithSSLSocketDataProvider(data, std::move(ssl_provider)); |
| RunDefaultTest(); |
| VerifyDataConsumed(); |
| } |
| |
| void AddData(SocketDataProvider* data) { |
| auto ssl_provider = std::make_unique<SSLSocketDataProvider>(ASYNC, OK); |
| ssl_provider->ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| AddDataWithSSLSocketDataProvider(data, std::move(ssl_provider)); |
| } |
| |
| void AddDataWithSSLSocketDataProvider( |
| SocketDataProvider* data, |
| std::unique_ptr<SSLSocketDataProvider> ssl_provider) { |
| data_vector_.push_back(data); |
| if (ssl_provider->next_proto == kProtoUnknown) |
| ssl_provider->next_proto = kProtoHTTP2; |
| |
| session_deps_->socket_factory->AddSSLSocketDataProvider( |
| ssl_provider.get()); |
| ssl_vector_.push_back(std::move(ssl_provider)); |
| |
| session_deps_->socket_factory->AddSocketDataProvider(data); |
| } |
| |
| HttpNetworkTransaction* trans() { return trans_.get(); } |
| void ResetTrans() { trans_.reset(); } |
| const TransactionHelperResult& output() { return output_; } |
| HttpNetworkSession* session() const { return session_.get(); } |
| SpdySessionDependencies* session_deps() { return session_deps_.get(); } |
| |
| private: |
| typedef std::vector<SocketDataProvider*> DataVector; |
| typedef std::vector<std::unique_ptr<SSLSocketDataProvider>> SSLVector; |
| typedef std::vector<std::unique_ptr<SocketDataProvider>> AlternateVector; |
| const HttpRequestInfo request_; |
| const RequestPriority priority_; |
| std::unique_ptr<SpdySessionDependencies> session_deps_; |
| std::unique_ptr<HttpNetworkSession> session_; |
| TransactionHelperResult output_; |
| SSLVector ssl_vector_; |
| TestCompletionCallback callback_; |
| std::unique_ptr<HttpNetworkTransaction> trans_; |
| DataVector data_vector_; |
| const NetLogWithSource log_; |
| }; |
| |
| void ConnectStatusHelperWithExpectedStatus(const MockRead& status, |
| int expected_status); |
| |
| void ConnectStatusHelper(const MockRead& status); |
| |
| HttpRequestInfo CreateGetPushRequest() const WARN_UNUSED_RESULT { |
| HttpRequestInfo request; |
| request.method = "GET"; |
| request.url = GURL(kPushedUrl); |
| request.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| return request; |
| } |
| |
| void UsePostRequest() { |
| ASSERT_FALSE(upload_data_stream_); |
| std::vector<std::unique_ptr<UploadElementReader>> element_readers; |
| element_readers.push_back(std::make_unique<UploadBytesElementReader>( |
| kUploadData, kUploadDataSize)); |
| upload_data_stream_ = std::make_unique<ElementsUploadDataStream>( |
| std::move(element_readers), 0); |
| |
| request_.method = "POST"; |
| request_.upload_data_stream = upload_data_stream_.get(); |
| } |
| |
| void UseFilePostRequest() { |
| ASSERT_FALSE(upload_data_stream_); |
| base::FilePath file_path; |
| CHECK(base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &file_path)); |
| CHECK_EQ(static_cast<int>(kUploadDataSize), |
| base::WriteFile(file_path, kUploadData, kUploadDataSize)); |
| |
| std::vector<std::unique_ptr<UploadElementReader>> element_readers; |
| element_readers.push_back(std::make_unique<UploadFileElementReader>( |
| base::ThreadTaskRunnerHandle::Get().get(), file_path, 0, |
| kUploadDataSize, base::Time())); |
| upload_data_stream_ = std::make_unique<ElementsUploadDataStream>( |
| std::move(element_readers), 0); |
| |
| request_.method = "POST"; |
| request_.upload_data_stream = upload_data_stream_.get(); |
| request_.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| void UseUnreadableFilePostRequest() { |
| ASSERT_FALSE(upload_data_stream_); |
| base::FilePath file_path; |
| CHECK(base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &file_path)); |
| CHECK_EQ(static_cast<int>(kUploadDataSize), |
| base::WriteFile(file_path, kUploadData, kUploadDataSize)); |
| CHECK(base::MakeFileUnreadable(file_path)); |
| |
| std::vector<std::unique_ptr<UploadElementReader>> element_readers; |
| element_readers.push_back(std::make_unique<UploadFileElementReader>( |
| base::ThreadTaskRunnerHandle::Get().get(), file_path, 0, |
| kUploadDataSize, base::Time())); |
| upload_data_stream_ = std::make_unique<ElementsUploadDataStream>( |
| std::move(element_readers), 0); |
| |
| request_.method = "POST"; |
| request_.upload_data_stream = upload_data_stream_.get(); |
| } |
| |
| void UseComplexPostRequest() { |
| ASSERT_FALSE(upload_data_stream_); |
| const int kFileRangeOffset = 1; |
| const int kFileRangeLength = 3; |
| CHECK_LT(kFileRangeOffset + kFileRangeLength, kUploadDataSize); |
| |
| base::FilePath file_path; |
| CHECK(base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &file_path)); |
| CHECK_EQ(static_cast<int>(kUploadDataSize), |
| base::WriteFile(file_path, kUploadData, kUploadDataSize)); |
| |
| std::vector<std::unique_ptr<UploadElementReader>> element_readers; |
| element_readers.push_back(std::make_unique<UploadBytesElementReader>( |
| kUploadData, kFileRangeOffset)); |
| element_readers.push_back(std::make_unique<UploadFileElementReader>( |
| base::ThreadTaskRunnerHandle::Get().get(), file_path, kFileRangeOffset, |
| kFileRangeLength, base::Time())); |
| element_readers.push_back(std::make_unique<UploadBytesElementReader>( |
| kUploadData + kFileRangeOffset + kFileRangeLength, |
| kUploadDataSize - (kFileRangeOffset + kFileRangeLength))); |
| upload_data_stream_ = std::make_unique<ElementsUploadDataStream>( |
| std::move(element_readers), 0); |
| |
| request_.method = "POST"; |
| request_.upload_data_stream = upload_data_stream_.get(); |
| } |
| |
| void UseChunkedPostRequest() { |
| ASSERT_FALSE(upload_chunked_data_stream_); |
| upload_chunked_data_stream_ = std::make_unique<ChunkedUploadDataStream>(0); |
| request_.method = "POST"; |
| request_.upload_data_stream = upload_chunked_data_stream_.get(); |
| } |
| |
| // Read the result of a particular transaction, knowing that we've got |
| // multiple transactions in the read pipeline; so as we read, we may have |
| // to skip over data destined for other transactions while we consume |
| // the data for |trans|. |
| int ReadResult(HttpNetworkTransaction* trans, std::string* result) { |
| const int kSize = 3000; |
| |
| int bytes_read = 0; |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kSize); |
| TestCompletionCallback callback; |
| while (true) { |
| int rv = trans->Read(buf.get(), kSize, callback.callback()); |
| if (rv == ERR_IO_PENDING) { |
| rv = callback.WaitForResult(); |
| } else if (rv <= 0) { |
| break; |
| } |
| result->append(buf->data(), rv); |
| bytes_read += rv; |
| } |
| return bytes_read; |
| } |
| |
| void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) { |
| // This lengthy block is reaching into the pool to dig out the active |
| // session. Once we have the session, we verify that the streams are |
| // all closed and not leaked at this point. |
| SpdySessionKey key(HostPortPair::FromURL(request_.url), |
| ProxyServer::Direct(), PRIVACY_MODE_DISABLED, |
| SpdySessionKey::IsProxySession::kFalse, SocketTag(), |
| request_.network_isolation_key, |
| false /* disable_secure_dns */); |
| HttpNetworkSession* session = helper.session(); |
| base::WeakPtr<SpdySession> spdy_session = |
| session->spdy_session_pool()->FindAvailableSession( |
| key, /* enable_ip_based_pooling = */ true, |
| /* is_websocket = */ false, log_); |
| ASSERT_TRUE(spdy_session); |
| EXPECT_EQ(0u, num_active_streams(spdy_session)); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams(spdy_session)); |
| } |
| |
| void RunServerPushTest(SequencedSocketData* data, |
| HttpResponseInfo* response, |
| HttpResponseInfo* push_response, |
| const std::string& expected) { |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, |
| nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(data); |
| |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| // Start the transaction with basic parameters. |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = callback.WaitForResult(); |
| |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Request the pushed path. |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| HttpRequestInfo request = CreateGetPushRequest(); |
| rv = trans2.Start(&request, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The data for the pushed path may be coming in more than 1 frame. Compile |
| // the results into a single string. |
| |
| // Read the server push body. |
| std::string result2; |
| ReadResult(&trans2, &result2); |
| // Read the response body. |
| std::string result; |
| ReadResult(trans, &result); |
| |
| // Verify that we consumed all test data. |
| EXPECT_TRUE(data->AllReadDataConsumed()); |
| EXPECT_TRUE(data->AllWriteDataConsumed()); |
| |
| LoadTimingInfo load_timing_info; |
| EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info)); |
| EXPECT_TRUE(load_timing_info.push_start.is_null()); |
| EXPECT_TRUE(load_timing_info.push_end.is_null()); |
| |
| LoadTimingInfo load_timing_info2; |
| EXPECT_TRUE(trans2.GetLoadTimingInfo(&load_timing_info2)); |
| EXPECT_FALSE(load_timing_info2.push_start.is_null()); |
| EXPECT_FALSE(load_timing_info2.push_end.is_null()); |
| |
| // Verify that the received push data is same as the expected push data. |
| EXPECT_EQ(result2.compare(expected), 0) << "Received data: " |
| << result2 |
| << "||||| Expected data: " |
| << expected; |
| |
| // Verify the response HEADERS. |
| // Copy the response info, because trans goes away. |
| *response = *trans->GetResponseInfo(); |
| *push_response = *trans2.GetResponseInfo(); |
| |
| VerifyStreamsClosed(helper); |
| } |
| |
| void RunBrokenPushTest(SequencedSocketData* data, int expected_rv) { |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, |
| nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(data); |
| |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| // Start the transaction with basic parameters. |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = callback.WaitForResult(); |
| EXPECT_EQ(expected_rv, rv); |
| |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that we consumed all test data. |
| EXPECT_TRUE(data->AllReadDataConsumed()); |
| EXPECT_TRUE(data->AllWriteDataConsumed()); |
| |
| if (expected_rv == OK) { |
| // Expected main request to succeed, even if push failed. |
| HttpResponseInfo response = *trans->GetResponseInfo(); |
| EXPECT_TRUE(response.headers); |
| EXPECT_EQ("HTTP/1.1 200", response.headers->GetStatusLine()); |
| } |
| } |
| |
| static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper, |
| int result) { |
| helper->ResetTrans(); |
| } |
| |
| static void StartTransactionCallback(HttpNetworkSession* session, |
| GURL url, |
| NetLogWithSource log, |
| int result) { |
| HttpRequestInfo request; |
| HttpNetworkTransaction trans(DEFAULT_PRIORITY, session); |
| TestCompletionCallback callback; |
| request.method = "GET"; |
| request.url = url; |
| request.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| int rv = trans.Start(&request, callback.callback(), log); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| callback.WaitForResult(); |
| } |
| |
| ChunkedUploadDataStream* upload_chunked_data_stream() { |
| return upload_chunked_data_stream_.get(); |
| } |
| |
| size_t num_active_streams(base::WeakPtr<SpdySession> session) { |
| return session->active_streams_.size(); |
| } |
| |
| static size_t num_unclaimed_pushed_streams( |
| base::WeakPtr<SpdySession> session) { |
| return session->pool_->push_promise_index()->CountStreamsForSession( |
| session.get()); |
| } |
| |
| static bool has_unclaimed_pushed_stream_for_url( |
| base::WeakPtr<SpdySession> session, |
| const GURL& url) { |
| return session->pool_->push_promise_index()->FindStream( |
| url, session.get()) != kNoPushedStreamFound; |
| } |
| |
| static spdy::SpdyStreamId spdy_stream_hi_water_mark( |
| base::WeakPtr<SpdySession> session) { |
| return session->stream_hi_water_mark_; |
| } |
| |
| base::RepeatingClosure FastForwardByCallback(base::TimeDelta delta) { |
| return base::BindRepeating(&SpdyNetworkTransactionTest::FastForwardBy, |
| base::Unretained(this), delta); |
| } |
| |
| const GURL default_url_; |
| const HostPortPair host_port_pair_; |
| HttpRequestInfo request_; |
| SpdyTestUtil spdy_util_; |
| const NetLogWithSource log_; |
| |
| private: |
| std::unique_ptr<ChunkedUploadDataStream> upload_chunked_data_stream_; |
| std::unique_ptr<UploadDataStream> upload_data_stream_; |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| // Verify HttpNetworkTransaction constructor. |
| TEST_F(SpdyNetworkTransactionTest, Constructor) { |
| auto session_deps = std::make_unique<SpdySessionDependencies>(); |
| std::unique_ptr<HttpNetworkSession> session( |
| SpdySessionDependencies::SpdyCreateSession(session_deps.get())); |
| auto trans = |
| std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get()); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, Get) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = {CreateMockWrite(req, 0)}; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, SetPriority) { |
| for (bool set_priority_before_starting_transaction : {true, false}) { |
| SpdyTestUtil spdy_test_util; |
| spdy::SpdySerializedFrame req( |
| spdy_test_util.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = {CreateMockWrite(req, 0)}; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_test_util.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body( |
| spdy_test_util.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = {CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, HIGHEST, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| |
| if (set_priority_before_starting_transaction) { |
| helper.trans()->SetPriority(LOWEST); |
| EXPECT_TRUE(helper.StartDefaultTest()); |
| } else { |
| EXPECT_TRUE(helper.StartDefaultTest()); |
| helper.trans()->SetPriority(LOWEST); |
| } |
| |
| helper.FinishDefaultTest(); |
| helper.VerifyDataConsumed(); |
| |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| } |
| |
| // Test that changing the request priority of an existing stream triggers |
| // sending PRIORITY frames in case there are multiple open streams and their |
| // relative priorities change. |
| TEST_F(SpdyNetworkTransactionTest, SetPriorityOnExistingStream) { |
| const char* kUrl2 = "https://www.example.org/bar"; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, HIGHEST)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyGet(kUrl2, 3, MEDIUM)); |
| spdy::SpdySerializedFrame priority1( |
| spdy_util_.ConstructSpdyPriority(3, 0, MEDIUM, true)); |
| spdy::SpdySerializedFrame priority2( |
| spdy_util_.ConstructSpdyPriority(1, 3, LOWEST, true)); |
| MockWrite writes[] = {CreateMockWrite(req1, 0), CreateMockWrite(req2, 2), |
| CreateMockWrite(priority1, 4), |
| CreateMockWrite(priority2, 5)}; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body1(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| MockRead reads[] = {CreateMockRead(resp1, 1), CreateMockRead(resp2, 3), |
| CreateMockRead(body1, 6), CreateMockRead(body2, 7), |
| MockRead(ASYNC, 0, 8)}; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, HIGHEST, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| EXPECT_TRUE(helper.StartDefaultTest()); |
| |
| // Open HTTP/2 connection and create first stream. |
| base::RunLoop().RunUntilIdle(); |
| |
| HttpNetworkTransaction trans2(MEDIUM, helper.session()); |
| HttpRequestInfo request2; |
| request2.url = GURL(kUrl2); |
| request2.method = "GET"; |
| request2.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| TestCompletionCallback callback2; |
| int rv = trans2.Start(&request2, callback2.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| // Create second stream. |
| base::RunLoop().RunUntilIdle(); |
| |
| // First request has HIGHEST priority, second request has MEDIUM priority. |
| // Changing the priority of the first request to LOWEST changes their order, |
| // and therefore triggers sending PRIORITY frames. |
| helper.trans()->SetPriority(LOWEST); |
| |
| helper.FinishDefaultTest(); |
| helper.VerifyDataConsumed(); |
| |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| |
| rv = callback2.WaitForResult(); |
| ASSERT_THAT(rv, IsOk()); |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| ASSERT_TRUE(response2); |
| ASSERT_TRUE(response2->headers); |
| EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP2, |
| response2->connection_info); |
| EXPECT_EQ("HTTP/1.1 200", response2->headers->GetStatusLine()); |
| } |
| |
| // Create two requests: a lower priority one first, then a higher priority one. |
| // Test that the second request gets sent out first. |
| TEST_F(SpdyNetworkTransactionTest, RequestsOrderedByPriority) { |
| const char* kUrl2 = "https://www.example.org/foo"; |
| |
| // First send second request on stream 1, then first request on stream 3. |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(kUrl2, 1, HIGHEST)); |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOW)); |
| MockWrite writes[] = {CreateMockWrite(req2, 0), CreateMockWrite(req1, 1)}; |
| |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(1, "stream 1", true)); |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(3, "stream 3", true)); |
| MockRead reads[] = {CreateMockRead(resp2, 2), CreateMockRead(body2, 3), |
| CreateMockRead(resp1, 4), CreateMockRead(body1, 5), |
| MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, LOW, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| |
| // Create HTTP/2 connection. This is necessary because starting the first |
| // transaction does not create the connection yet, so the second request |
| // could not use the same connection, whereas running the message loop after |
| // starting the first transaction would call Socket::Write() with the first |
| // HEADERS frame, so the second transaction could not get ahead of it. |
| SpdySessionKey key(HostPortPair("www.example.org", 443), |
| ProxyServer::Direct(), PRIVACY_MODE_DISABLED, |
| SpdySessionKey::IsProxySession::kFalse, SocketTag(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| auto spdy_session = CreateSpdySession(helper.session(), key, log_); |
| EXPECT_TRUE(spdy_session); |
| |
| // Start first transaction. |
| EXPECT_TRUE(helper.StartDefaultTest()); |
| |
| // Start second transaction. |
| HttpNetworkTransaction trans2(HIGHEST, helper.session()); |
| HttpRequestInfo request2; |
| request2.url = GURL(kUrl2); |
| request2.method = "GET"; |
| request2.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| TestCompletionCallback callback2; |
| int rv = trans2.Start(&request2, callback2.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| // Complete first transaction and verify results. |
| helper.FinishDefaultTest(); |
| helper.VerifyDataConsumed(); |
| |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("stream 3", out.response_data); |
| |
| // Complete second transaction and verify results. |
| rv = callback2.WaitForResult(); |
| ASSERT_THAT(rv, IsOk()); |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| ASSERT_TRUE(response2); |
| ASSERT_TRUE(response2->headers); |
| EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP2, |
| response2->connection_info); |
| EXPECT_EQ("HTTP/1.1 200", response2->headers->GetStatusLine()); |
| std::string response_data; |
| ReadTransaction(&trans2, &response_data); |
| EXPECT_EQ("stream 1", response_data); |
| } |
| |
| // Test that already enqueued HEADERS frames are reordered if their relative |
| // priority changes. |
| TEST_F(SpdyNetworkTransactionTest, QueuedFramesReorderedOnPriorityChange) { |
| const char* kUrl2 = "https://www.example.org/foo"; |
| const char* kUrl3 = "https://www.example.org/bar"; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, DEFAULT_PRIORITY)); |
| spdy::SpdySerializedFrame req3(spdy_util_.ConstructSpdyGet(kUrl3, 3, MEDIUM)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyGet(kUrl2, 5, LOWEST)); |
| MockWrite writes[] = {MockWrite(ASYNC, ERR_IO_PENDING, 0), |
| CreateMockWrite(req1, 1), CreateMockWrite(req3, 2), |
| CreateMockWrite(req2, 3)}; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp3( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 5)); |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(1, "stream 1", true)); |
| spdy::SpdySerializedFrame body3( |
| spdy_util_.ConstructSpdyDataFrame(3, "stream 3", true)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(5, "stream 5", true)); |
| MockRead reads[] = {CreateMockRead(resp1, 4), CreateMockRead(body1, 5), |
| CreateMockRead(resp3, 6), CreateMockRead(body3, 7), |
| CreateMockRead(resp2, 8), CreateMockRead(body2, 9), |
| MockRead(ASYNC, 0, 10)}; |
| |
| SequencedSocketData data(reads, writes); |
| // Priority of first request does not matter, because Socket::Write() will be |
| // called with its HEADERS frame before the other requests start. |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| EXPECT_TRUE(helper.StartDefaultTest()); |
| |
| // Open HTTP/2 connection, create HEADERS frame for first request, and call |
| // Socket::Write() with that frame. After this, no other request can get |
| // ahead of the first one. |
| base::RunLoop().RunUntilIdle(); |
| |
| HttpNetworkTransaction trans2(HIGHEST, helper.session()); |
| HttpRequestInfo request2; |
| request2.url = GURL(kUrl2); |
| request2.method = "GET"; |
| request2.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| TestCompletionCallback callback2; |
| int rv = trans2.Start(&request2, callback2.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| HttpNetworkTransaction trans3(MEDIUM, helper.session()); |
| HttpRequestInfo request3; |
| request3.url = GURL(kUrl3); |
| request3.method = "GET"; |
| request3.traffic_annotation = |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS); |
| TestCompletionCallback callback3; |
| rv = trans3.Start(&request3, callback3.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| // Create HEADERS frames for second and third request and enqueue them in |
| // SpdyWriteQueue with their original priorities. Writing of the first |
| // HEADERS frame to the socked still has not completed. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Second request is of HIGHEST, third of MEDIUM priority. Changing second |
| // request to LOWEST changes their relative order. This should result in |
| // already enqueued frames being reordered within SpdyWriteQueue. |
| trans2.SetPriority(LOWEST); |
| |
| // Complete async write of the first HEADERS frame. |
| data.Resume(); |
| |
| helper.FinishDefaultTest(); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("stream 1", out.response_data); |
| |
| rv = callback2.WaitForResult(); |
| ASSERT_THAT(rv, IsOk()); |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| ASSERT_TRUE(response2); |
| ASSERT_TRUE(response2->headers); |
| EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP2, |
| response2->connection_info); |
| EXPECT_EQ("HTTP/1.1 200", response2->headers->GetStatusLine()); |
| std::string response_data; |
| ReadTransaction(&trans2, &response_data); |
| EXPECT_EQ("stream 5", response_data); |
| |
| rv = callback3.WaitForResult(); |
| ASSERT_THAT(rv, IsOk()); |
| const HttpResponseInfo* response3 = trans3.GetResponseInfo(); |
| ASSERT_TRUE(response3); |
| ASSERT_TRUE(response3->headers); |
| EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP2, |
| response3->connection_info); |
| EXPECT_EQ("HTTP/1.1 200", response3->headers->GetStatusLine()); |
| ReadTransaction(&trans3, &response_data); |
| EXPECT_EQ("stream 3", response_data); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, GetAtEachPriority) { |
| for (RequestPriority p = MINIMUM_PRIORITY; p <= MAXIMUM_PRIORITY; |
| p = RequestPriority(p + 1)) { |
| SpdyTestUtil spdy_test_util; |
| |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_test_util.ConstructSpdyGet(nullptr, 0, 1, p)); |
| MockWrite writes[] = {CreateMockWrite(req, 0)}; |
| |
| spdy::SpdyPriority spdy_prio = 0; |
| EXPECT_TRUE(GetSpdyPriority(req, &spdy_prio)); |
| // this repeats the RequestPriority-->spdy::SpdyPriority mapping from |
| // spdy::SpdyFramer::ConvertRequestPriorityToSpdyPriority to make |
| // sure it's being done right. |
| switch (p) { |
| case HIGHEST: |
| EXPECT_EQ(0, spdy_prio); |
| break; |
| case MEDIUM: |
| EXPECT_EQ(1, spdy_prio); |
| break; |
| case LOW: |
| EXPECT_EQ(2, spdy_prio); |
| break; |
| case LOWEST: |
| EXPECT_EQ(3, spdy_prio); |
| break; |
| case IDLE: |
| EXPECT_EQ(4, spdy_prio); |
| break; |
| case THROTTLED: |
| EXPECT_EQ(5, spdy_prio); |
| break; |
| default: |
| FAIL(); |
| } |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_test_util.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body( |
| spdy_test_util.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, p, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| } |
| |
| // Start three gets simultaniously; making sure that multiplexed |
| // streams work properly. |
| |
| // This can't use the TransactionHelper method, since it only |
| // handles a single transaction, and finishes them as soon |
| // as it launches them. |
| |
| // TODO(gavinp): create a working generalized TransactionHelper that |
| // can allow multiple streams in flight. |
| |
| TEST_F(SpdyNetworkTransactionTest, ThreeGets) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| |
| spdy::SpdySerializedFrame req3( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 5, LOWEST)); |
| spdy::SpdySerializedFrame resp3( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 5)); |
| spdy::SpdySerializedFrame body3(spdy_util_.ConstructSpdyDataFrame(5, false)); |
| spdy::SpdySerializedFrame fbody3(spdy_util_.ConstructSpdyDataFrame(5, true)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(req2, 3), |
| CreateMockWrite(req3, 6), |
| }; |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| CreateMockRead(resp2, 4), CreateMockRead(body2, 5), |
| CreateMockRead(resp3, 7), CreateMockRead(body3, 8), |
| |
| CreateMockRead(fbody, 9), CreateMockRead(fbody2, 10), |
| CreateMockRead(fbody3, 11), |
| |
| MockRead(ASYNC, 0, 12), // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| SequencedSocketData data_placeholder1; |
| SequencedSocketData data_placeholder2; |
| |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| // We require placeholder data because three get requests are sent out at |
| // the same time which results in three sockets being connected. The first |
| // on will negotiate SPDY and will be used for all requests. |
| helper.AddData(&data_placeholder1); |
| helper.AddData(&data_placeholder2); |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| TestCompletionCallback callback3; |
| |
| HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans3(DEFAULT_PRIORITY, helper.session()); |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans3.Start(&request_, callback3.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| out.rv = callback3.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| |
| trans2.GetResponseInfo(); |
| |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| helper.VerifyDataConsumed(); |
| EXPECT_THAT(out.rv, IsOk()); |
| |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, TwoGetsLateBinding) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(req2, 3), |
| }; |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| CreateMockRead(resp2, 4), CreateMockRead(body2, 5), |
| CreateMockRead(fbody, 6), CreateMockRead(fbody2, 7), |
| MockRead(ASYNC, 0, 8), // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| |
| MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); |
| SequencedSocketData data_placeholder; |
| data_placeholder.set_connect_data(never_finishing_connect); |
| |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| // We require placeholder data because two requests are sent out at |
| // the same time which results in two sockets being connected. The first |
| // on will negotiate SPDY and will be used for all requests. |
| helper.AddData(&data_placeholder); |
| HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| out.rv = callback2.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| EXPECT_TRUE(response2->headers); |
| EXPECT_TRUE(response2->was_fetched_via_spdy); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(&trans2, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, TwoGetsLateBindingFromPreconnect) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(req2, 3), |
| }; |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| CreateMockRead(resp2, 4), CreateMockRead(body2, 5), |
| CreateMockRead(fbody, 6), CreateMockRead(fbody2, 7), |
| MockRead(ASYNC, 0, 8), // EOF |
| }; |
| SequencedSocketData preconnect_data(reads, writes); |
| |
| MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING); |
| |
| SequencedSocketData data_placeholder; |
| data_placeholder.set_connect_data(never_finishing_connect); |
| |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&preconnect_data); |
| // We require placeholder data because 3 connections are attempted (first is |
| // the preconnect, 2nd and 3rd are the never finished connections. |
| helper.AddData(&data_placeholder); |
| helper.AddData(&data_placeholder); |
| |
| HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| |
| // Preconnect the first. |
| HttpStreamFactory* http_stream_factory = |
| helper.session()->http_stream_factory(); |
| |
| http_stream_factory->PreconnectStreams(1, request_); |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| out.rv = callback2.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| EXPECT_TRUE(response2->headers); |
| EXPECT_TRUE(response2->was_fetched_via_spdy); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(&trans2, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| // Similar to ThreeGets above, however this test adds a SETTINGS |
| // frame. The SETTINGS frame is read during the IO loop waiting on |
| // the first transaction completion, and sets a maximum concurrent |
| // stream limit of 1. This means that our IO loop exists after the |
| // second transaction completes, so we can assert on read_index(). |
| TEST_F(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) { |
| // Construct the request. |
| // Each request fully completes before the next starts. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy_util_.UpdateWithStreamDestruction(1); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| spdy_util_.UpdateWithStreamDestruction(3); |
| |
| spdy::SpdySerializedFrame req3( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 5, LOWEST)); |
| spdy::SpdySerializedFrame resp3( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 5)); |
| spdy::SpdySerializedFrame body3(spdy_util_.ConstructSpdyDataFrame(5, false)); |
| spdy::SpdySerializedFrame fbody3(spdy_util_.ConstructSpdyDataFrame(5, true)); |
| |
| spdy::SettingsMap settings; |
| const uint32_t max_concurrent_streams = 1; |
| settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = max_concurrent_streams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(settings)); |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(settings_ack, 5), |
| CreateMockWrite(req2, 6), CreateMockWrite(req3, 10), |
| }; |
| |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 1), |
| CreateMockRead(resp, 2), |
| CreateMockRead(body, 3), |
| CreateMockRead(fbody, 4), |
| CreateMockRead(resp2, 7), |
| CreateMockRead(body2, 8), |
| CreateMockRead(fbody2, 9), |
| CreateMockRead(resp3, 11), |
| CreateMockRead(body3, 12), |
| CreateMockRead(fbody3, 13), |
| |
| MockRead(ASYNC, 0, 14), // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| TransactionHelperResult out; |
| { |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, |
| nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans3(DEFAULT_PRIORITY, helper.session()); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| TestCompletionCallback callback3; |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| // Run transaction 1 through quickly to force a read of our SETTINGS |
| // frame. |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| out.rv = trans3.Start(&request_, callback3.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| out.rv = callback2.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| out.rv = callback3.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| ASSERT_TRUE(response1); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(&trans2, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response3 = trans3.GetResponseInfo(); |
| out.status_line = response3->headers->GetStatusLine(); |
| out.response_info = *response3; |
| out.rv = ReadTransaction(&trans3, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| helper.VerifyDataConsumed(); |
| } |
| EXPECT_THAT(out.rv, IsOk()); |
| } |
| |
| // Similar to ThreeGetsWithMaxConcurrent above, however this test adds |
| // a fourth transaction. The third and fourth transactions have |
| // different data ("hello!" vs "hello!hello!") and because of the |
| // user specified priority, we expect to see them inverted in |
| // the response from the server. |
| TEST_F(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy_util_.UpdateWithStreamDestruction(1); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| spdy_util_.UpdateWithStreamDestruction(3); |
| |
| spdy::SpdySerializedFrame req4( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 5, HIGHEST)); |
| spdy::SpdySerializedFrame resp4( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 5)); |
| spdy::SpdySerializedFrame fbody4(spdy_util_.ConstructSpdyDataFrame(5, true)); |
| spdy_util_.UpdateWithStreamDestruction(5); |
| |
| spdy::SpdySerializedFrame req3( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 7, LOWEST)); |
| spdy::SpdySerializedFrame resp3( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 7)); |
| spdy::SpdySerializedFrame body3(spdy_util_.ConstructSpdyDataFrame(7, false)); |
| spdy::SpdySerializedFrame fbody3(spdy_util_.ConstructSpdyDataFrame(7, true)); |
| |
| spdy::SettingsMap settings; |
| const uint32_t max_concurrent_streams = 1; |
| settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = max_concurrent_streams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(settings)); |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(settings_ack, 5), |
| // By making these synchronous, it guarantees that they are not *started* |
| // before their sequence number, which in turn verifies that only a single |
| // request is in-flight at a time. |
| CreateMockWrite(req2, 6, SYNCHRONOUS), |
| CreateMockWrite(req4, 10, SYNCHRONOUS), |
| CreateMockWrite(req3, 13, SYNCHRONOUS), |
| }; |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 1), |
| CreateMockRead(resp, 2), |
| CreateMockRead(body, 3), |
| CreateMockRead(fbody, 4), |
| CreateMockRead(resp2, 7), |
| CreateMockRead(body2, 8), |
| CreateMockRead(fbody2, 9), |
| CreateMockRead(resp4, 11), |
| CreateMockRead(fbody4, 12), |
| CreateMockRead(resp3, 14), |
| CreateMockRead(body3, 15), |
| CreateMockRead(fbody3, 16), |
| |
| MockRead(ASYNC, 0, 17), // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| |
| HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans3(DEFAULT_PRIORITY, helper.session()); |
| HttpNetworkTransaction trans4(HIGHEST, helper.session()); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| TestCompletionCallback callback3; |
| TestCompletionCallback callback4; |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| // Run transaction 1 through quickly to force a read of our SETTINGS frame. |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| // Finish async network reads and writes associated with |trans1|. |
| base::RunLoop().RunUntilIdle(); |
| |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans3.Start(&request_, callback3.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| out.rv = trans4.Start(&request_, callback4.callback(), log_); |
| ASSERT_THAT(out.rv, IsError(ERR_IO_PENDING)); |
| |
| out.rv = callback2.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| out.rv = callback3.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(&trans2, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| // notice: response3 gets two hellos, response4 gets one |
| // hello, so we know dequeuing priority was respected. |
| const HttpResponseInfo* response3 = trans3.GetResponseInfo(); |
| out.status_line = response3->headers->GetStatusLine(); |
| out.response_info = *response3; |
| out.rv = ReadTransaction(&trans3, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| out.rv = callback4.WaitForResult(); |
| EXPECT_THAT(out.rv, IsOk()); |
| const HttpResponseInfo* response4 = trans4.GetResponseInfo(); |
| out.status_line = response4->headers->GetStatusLine(); |
| out.response_info = *response4; |
| out.rv = ReadTransaction(&trans4, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| helper.VerifyDataConsumed(); |
| EXPECT_THAT(out.rv, IsOk()); |
| } |
| |
| // Similar to ThreeGetsMaxConcurrrent above, however, this test |
| // deletes a session in the middle of the transaction to ensure |
| // that we properly remove pendingcreatestream objects from |
| // the spdy_session |
| TEST_F(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fbody(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy_util_.UpdateWithStreamDestruction(1); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, false)); |
| spdy::SpdySerializedFrame fbody2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| |
| spdy::SettingsMap settings; |
| const uint32_t max_concurrent_streams = 1; |
| settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = max_concurrent_streams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(settings)); |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(settings_ack, 5), |
| CreateMockWrite(req2, 6), |
| }; |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 1), CreateMockRead(resp, 2), |
| CreateMockRead(body, 3), CreateMockRead(fbody, 4), |
| CreateMockRead(resp2, 7), CreateMockRead(body2, 8), |
| CreateMockRead(fbody2, 9), MockRead(ASYNC, 0, 10), // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| auto trans1 = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, |
| helper.session()); |
| auto trans2 = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, |
| helper.session()); |
| auto trans3 = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, |
| helper.session()); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| TestCompletionCallback callback3; |
| |
| out.rv = trans1->Start(&request_, callback1.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| // Run transaction 1 through quickly to force a read of our SETTINGS frame. |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| out.rv = trans2->Start(&request_, callback2.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| out.rv = trans3->Start(&request_, callback3.callback(), log_); |
| trans3.reset(); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| out.rv = callback2.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response1 = trans1->GetResponseInfo(); |
| ASSERT_TRUE(response1); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(trans1.get(), &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| |
| const HttpResponseInfo* response2 = trans2->GetResponseInfo(); |
| ASSERT_TRUE(response2); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(trans2.get(), &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!hello!", out.response_data); |
| helper.VerifyDataConsumed(); |
| EXPECT_THAT(out.rv, IsOk()); |
| } |
| |
| namespace { |
| |
| // A helper class that will delete |transaction| on error when the callback is |
| // invoked. |
| class KillerCallback : public TestCompletionCallbackBase { |
| public: |
| explicit KillerCallback(HttpNetworkTransaction* transaction) |
| : transaction_(transaction) {} |
| |
| ~KillerCallback() override = default; |
| |
| CompletionOnceCallback callback() { |
| return base::BindOnce(&KillerCallback::OnComplete, base::Unretained(this)); |
| } |
| |
| private: |
| void OnComplete(int result) { |
| if (result < 0) |
| delete transaction_; |
| |
| SetResult(result); |
| } |
| |
| HttpNetworkTransaction* transaction_; |
| }; |
| |
| } // namespace |
| |
| // Similar to ThreeGetsMaxConcurrrentDelete above, however, this test |
| // closes the socket while we have a pending transaction waiting for |
| // a pending stream creation. http://crbug.com/52901 |
| TEST_F(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentSocketClose) { |
| // Construct the request. Each stream uses a different priority to provide |
| // more useful failure information if the requests are made in an unexpected |
| // order. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, HIGHEST)); |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame fin_body( |
| spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy_util_.UpdateWithStreamDestruction(1); |
| |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| |
| spdy::SettingsMap settings; |
| const uint32_t max_concurrent_streams = 1; |
| settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = max_concurrent_streams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(settings)); |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| |
| MockWrite writes[] = {CreateMockWrite(req, 0), |
| CreateMockWrite(settings_ack, 6), |
| CreateMockWrite(req2, 7)}; |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 1), CreateMockRead(resp, 2), |
| CreateMockRead(body, 3), |
| // Delay the request here. For this test to pass, the three HTTP streams |
| // have to be created in order, but SpdySession doesn't actually guarantee |
| // that (See note in SpdySession::ProcessPendingStreamRequests). As a |
| // workaround, delay finishing up the first stream until the second and |
| // third streams are waiting in the SPDY stream request queue. |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(fin_body, 5), |
| CreateMockRead(resp2, 8), |
| // The exact error does not matter, but some errors, such as |
| // ERR_CONNECTION_RESET, may trigger a retry, which this test does not |
| // account for. |
| MockRead(ASYNC, ERR_SSL_BAD_RECORD_MAC_ALERT, 9), // Abort! |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| SequencedSocketData data_placeholder; |
| |
| TransactionHelperResult out; |
| NormalSpdyTransactionHelper helper(request_, HIGHEST, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| // We require placeholder data because three get requests are sent out, so |
| // there needs to be three sets of SSL connection data. |
| helper.AddData(&data_placeholder); |
| helper.AddData(&data_placeholder); |
| HttpNetworkTransaction trans1(HIGHEST, helper.session()); |
| HttpNetworkTransaction trans2(MEDIUM, helper.session()); |
| HttpNetworkTransaction* trans3( |
| new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session())); |
| |
| TestCompletionCallback callback1; |
| TestCompletionCallback callback2; |
| KillerCallback callback3(trans3); |
| |
| out.rv = trans1.Start(&request_, callback1.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| // Run transaction 1 through quickly to force a read of our SETTINGS frame. |
| out.rv = callback1.WaitForResult(); |
| ASSERT_THAT(out.rv, IsOk()); |
| |
| out.rv = trans2.Start(&request_, callback2.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| out.rv = trans3->Start(&request_, callback3.callback(), log_); |
| ASSERT_EQ(out.rv, ERR_IO_PENDING); |
| |
| // Run until both transactions are in the SpdySession's queue, waiting for the |
| // final request to complete. |
| base::RunLoop().RunUntilIdle(); |
| data.Resume(); |
| |
| out.rv = callback3.WaitForResult(); |
| EXPECT_THAT(out.rv, IsError(ERR_SSL_BAD_RECORD_MAC_ALERT)); |
| |
| const HttpResponseInfo* response1 = trans1.GetResponseInfo(); |
| ASSERT_TRUE(response1); |
| EXPECT_TRUE(response1->headers); |
| EXPECT_TRUE(response1->was_fetched_via_spdy); |
| out.status_line = response1->headers->GetStatusLine(); |
| out.response_info = *response1; |
| out.rv = ReadTransaction(&trans1, &out.response_data); |
| EXPECT_THAT(out.rv, IsOk()); |
| |
| const HttpResponseInfo* response2 = trans2.GetResponseInfo(); |
| ASSERT_TRUE(response2); |
| out.status_line = response2->headers->GetStatusLine(); |
| out.response_info = *response2; |
| out.rv = ReadTransaction(&trans2, &out.response_data); |
| EXPECT_THAT(out.rv, IsError(ERR_SSL_BAD_RECORD_MAC_ALERT)); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| // Test that a simple PUT request works. |
| TEST_F(SpdyNetworkTransactionTest, Put) { |
| // Setup the request. |
| request_.method = "PUT"; |
| |
| spdy::SpdyHeaderBlock put_headers( |
| spdy_util_.ConstructPutHeaderBlock(kDefaultUrl, 0)); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(put_headers), LOWEST, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| } |
| |
| // Test that a simple HEAD request works. |
| TEST_F(SpdyNetworkTransactionTest, Head) { |
| // Setup the request. |
| request_.method = "HEAD"; |
| |
| spdy::SpdyHeaderBlock head_headers( |
| spdy_util_.ConstructHeadHeaderBlock(kDefaultUrl, 0)); |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyHeaders( |
| 1, std::move(head_headers), LOWEST, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| } |
| |
| // Test that a simple POST works. |
| TEST_F(SpdyNetworkTransactionTest, Post) { |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kUploadDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 1), // POST upload frame |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 2), CreateMockRead(body, 3), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UsePostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| // Test that a POST with a file works. |
| TEST_F(SpdyNetworkTransactionTest, FilePost) { |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kUploadDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 1), // POST upload frame |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 2), CreateMockRead(body, 3), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UseFilePostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| // Test that a POST with a unreadable file fails. |
| TEST_F(SpdyNetworkTransactionTest, UnreadableFilePost) { |
| MockWrite writes[] = { |
| MockWrite(ASYNC, 0, 0) // EOF |
| }; |
| MockRead reads[] = { |
| MockRead(ASYNC, 0, 1) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UseUnreadableFilePostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| helper.RunDefaultTest(); |
| |
| base::RunLoop().RunUntilIdle(); |
| helper.VerifyDataNotConsumed(); |
| EXPECT_THAT(helper.output().rv, IsError(ERR_ACCESS_DENIED)); |
| } |
| |
| // Test that a complex POST works. |
| TEST_F(SpdyNetworkTransactionTest, ComplexPost) { |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kUploadDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 1), // POST upload frame |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 2), CreateMockRead(body, 3), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UseComplexPostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| // Test that a chunked POST works. |
| TEST_F(SpdyNetworkTransactionTest, ChunkedPost) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructChunkedSpdyPost(nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 1), |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 2), CreateMockRead(body, 3), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UseChunkedPostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| |
| // These chunks get merged into a single frame when being sent. |
| const int kFirstChunkSize = kUploadDataSize/2; |
| upload_chunked_data_stream()->AppendData(kUploadData, kFirstChunkSize, false); |
| upload_chunked_data_stream()->AppendData( |
| kUploadData + kFirstChunkSize, kUploadDataSize - kFirstChunkSize, true); |
| |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ(kUploadData, out.response_data); |
| } |
| |
| // Test that a chunked POST works with chunks appended after transaction starts. |
| TEST_F(SpdyNetworkTransactionTest, DelayedChunkedPost) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructChunkedSpdyPost(nullptr, 0)); |
| spdy::SpdySerializedFrame chunk1(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame chunk2(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| spdy::SpdySerializedFrame chunk3(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(chunk1, 1), |
| CreateMockWrite(chunk2, 2), CreateMockWrite(chunk3, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 4), CreateMockRead(chunk1, 5), |
| CreateMockRead(chunk2, 6), CreateMockRead(chunk3, 7), |
| MockRead(ASYNC, 0, 8) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| UseChunkedPostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| |
| upload_chunked_data_stream()->AppendData(kUploadData, kUploadDataSize, false); |
| |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| ASSERT_TRUE(helper.StartDefaultTest()); |
| |
| base::RunLoop().RunUntilIdle(); |
| upload_chunked_data_stream()->AppendData(kUploadData, kUploadDataSize, false); |
| base::RunLoop().RunUntilIdle(); |
| upload_chunked_data_stream()->AppendData(kUploadData, kUploadDataSize, true); |
| |
| helper.FinishDefaultTest(); |
| helper.VerifyDataConsumed(); |
| |
| std::string expected_response; |
| expected_response += kUploadData; |
| expected_response += kUploadData; |
| expected_response += kUploadData; |
| |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ(expected_response, out.response_data); |
| } |
| |
| // Test that a POST without any post data works. |
| TEST_F(SpdyNetworkTransactionTest, NullPost) { |
| // Setup the request. |
| request_.method = "POST"; |
| // Create an empty UploadData. |
| request_.upload_data_stream = nullptr; |
| |
| // When request.upload_data_stream is NULL for post, content-length is |
| // expected to be 0. |
| spdy::SpdyHeaderBlock req_block( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, 0)); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(req_block), LOWEST, true)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| // Test that a simple POST works. |
| TEST_F(SpdyNetworkTransactionTest, EmptyPost) { |
| // Create an empty UploadDataStream. |
| std::vector<std::unique_ptr<UploadElementReader>> element_readers; |
| ElementsUploadDataStream stream(std::move(element_readers), 0); |
| |
| // Setup the request. |
| request_.method = "POST"; |
| request_.upload_data_stream = &stream; |
| |
| const uint64_t kContentLength = 0; |
| |
| spdy::SpdyHeaderBlock req_block( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kContentLength)); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(req_block), LOWEST, true)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| } |
| |
| // While we're doing a post, the server sends the reply before upload completes. |
| TEST_F(SpdyNetworkTransactionTest, ResponseBeforePostCompletes) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructChunkedSpdyPost(nullptr, 0)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 3), |
| }; |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| // Write the request headers, and read the complete response |
| // while still waiting for chunked request data. |
| SequencedSocketData data(reads, writes); |
| UseChunkedPostRequest(); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| |
| ASSERT_TRUE(helper.StartDefaultTest()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Process the request headers, response headers, and response body. |
| // The request body is still in flight. |
| const HttpResponseInfo* response = helper.trans()->GetResponseInfo(); |
| EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine()); |
| |
| // Finish sending the request body. |
| upload_chunked_data_stream()->AppendData(kUploadData, kUploadDataSize, true); |
| helper.WaitForCallbackToComplete(); |
| EXPECT_THAT(helper.output().rv, IsOk()); |
| |
| std::string response_body; |
| EXPECT_THAT(ReadTransaction(helper.trans(), &response_body), IsOk()); |
| EXPECT_EQ(kUploadData, response_body); |
| |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| helper.VerifyDataConsumed(); |
| } |
| |
| // The client upon cancellation tries to send a RST_STREAM frame. The mock |
| // socket causes the TCP write to return zero. This test checks that the client |
| // tries to queue up the RST_STREAM frame again. |
| TEST_F(SpdyNetworkTransactionTest, SocketWriteReturnsZero) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0, SYNCHRONOUS), |
| MockWrite(SYNCHRONOUS, nullptr, 0, 2), |
| CreateMockWrite(rst, 3, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, nullptr, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| helper.StartDefaultTest(); |
| EXPECT_THAT(helper.output().rv, IsError(ERR_IO_PENDING)); |
| |
| helper.WaitForCallbackToComplete(); |
| EXPECT_THAT(helper.output().rv, IsOk()); |
| |
| helper.ResetTrans(); |
| base::RunLoop().RunUntilIdle(); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| // Test that the transaction doesn't crash when we don't have a reply. |
| TEST_F(SpdyNetworkTransactionTest, ResponseWithoutHeaders) { |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(body, 1), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 2), |
| }; |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsError(ERR_HTTP2_PROTOCOL_ERROR)); |
| } |
| |
| // Test that the transaction doesn't crash when we get two replies on the same |
| // stream ID. See http://crbug.com/45639. |
| TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 4), |
| }; |
| |
| spdy::SpdySerializedFrame resp0( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp0, 1), CreateMockRead(resp1, 2), |
| CreateMockRead(body, 3), MockRead(ASYNC, 0, 5) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = callback.WaitForResult(); |
| EXPECT_THAT(rv, IsOk()); |
| |
| const HttpResponseInfo* response = trans->GetResponseInfo(); |
| ASSERT_TRUE(response); |
| EXPECT_TRUE(response->headers); |
| EXPECT_TRUE(response->was_fetched_via_spdy); |
| std::string response_data; |
| rv = ReadTransaction(trans, &response_data); |
| EXPECT_THAT(rv, IsError(ERR_HTTP2_PROTOCOL_ERROR)); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, ResetReplyWithTransferEncoding) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 2), |
| }; |
| |
| const char* const headers[] = { |
| "transfer-encoding", "chunked" |
| }; |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(headers, 1, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 3), |
| MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsError(ERR_HTTP2_PROTOCOL_ERROR)); |
| |
| helper.session()->spdy_session_pool()->CloseAllSessions(); |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, ResetPushWithTransferEncoding) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_PROTOCOL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 5), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| const char* const headers[] = { |
| "transfer-encoding", "chunked" |
| }; |
| spdy::SpdySerializedFrame push(spdy_util_.ConstructSpdyPush( |
| headers, base::size(headers) / 2, 2, 1, "https://www.example.org/1")); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(push, 2), CreateMockRead(body, 4), |
| MockRead(ASYNC, 0, 6) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunToCompletion(&data); |
| TransactionHelperResult out = helper.output(); |
| EXPECT_THAT(out.rv, IsOk()); |
| EXPECT_EQ("HTTP/1.1 200", out.status_line); |
| EXPECT_EQ("hello!", out.response_data); |
| |
| helper.session()->spdy_session_pool()->CloseAllSessions(); |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, CancelledTransaction) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(req), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead reads[] = { |
| CreateMockRead(resp), |
| // This following read isn't used by the test, except during the |
| // RunUntilIdle() call at the end since the SpdySession survives the |
| // HttpNetworkTransaction and still tries to continue Read()'ing. Any |
| // MockRead will do here. |
| MockRead(ASYNC, 0, 0) // EOF |
| }; |
| |
| StaticSocketDataProvider data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| helper.ResetTrans(); // Cancel the transaction. |
| |
| // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
| // MockClientSocketFactory) are still alive. |
| base::RunLoop().RunUntilIdle(); |
| helper.VerifyDataNotConsumed(); |
| } |
| |
| // Verify that the client sends a Rst Frame upon cancelling the stream. |
| TEST_F(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0, SYNCHRONOUS), |
| CreateMockWrite(rst, 2, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, nullptr, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| TestCompletionCallback callback; |
| |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(callback.GetResult(rv), IsOk()); |
| |
| helper.ResetTrans(); |
| base::RunLoop().RunUntilIdle(); |
| |
| helper.VerifyDataConsumed(); |
| } |
| |
| // Verify that the client can correctly deal with the user callback attempting |
| // to start another transaction on a session that is closing down. See |
| // http://crbug.com/47455 |
| TEST_F(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = {CreateMockWrite(req)}; |
| MockWrite writes2[] = {CreateMockWrite(req, 0), |
| MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 3)}; |
| |
| // The indicated length of this frame is longer than its actual length. When |
| // the session receives an empty frame after this one, it shuts down the |
| // session, and calls the read callback with the incomplete data. |
| const uint8_t kGetBodyFrame2[] = { |
| 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, |
| 0x07, 'h', 'e', 'l', 'l', 'o', '!', |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), // Force a pause |
| MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), |
| base::size(kGetBodyFrame2), 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause |
| MockRead(ASYNC, nullptr, 0, 5), // EOF |
| }; |
| MockRead reads2[] = { |
| CreateMockRead(resp, 1), MockRead(ASYNC, nullptr, 0, 2), // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| SequencedSocketData data2(reads2, writes2); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| helper.AddData(&data2); |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| // Start the transaction with basic parameters. |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = callback.WaitForResult(); |
| |
| const int kSize = 3000; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kSize); |
| rv = trans->Read( |
| buf.get(), kSize, |
| base::BindOnce(&SpdyNetworkTransactionTest::StartTransactionCallback, |
| helper.session(), default_url_, log_)); |
| ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); |
| // This forces an err_IO_pending, which sets the callback. |
| data.Resume(); |
| data.RunUntilPaused(); |
| |
| // This finishes the read. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| helper.VerifyDataConsumed(); |
| } |
| |
| // Verify that the client can correctly deal with the user callback deleting |
| // the transaction. Failures will usually be flagged by thread and/or memory |
| // checking tools. See http://crbug.com/46925 |
| TEST_F(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = {CreateMockWrite(req, 0)}; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), // Force a pause |
| CreateMockRead(body, 3), MockRead(ASYNC, nullptr, 0, 4), // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| NormalSpdyTransactionHelper helper(request_, DEFAULT_PRIORITY, log_, nullptr); |
| helper.RunPreTestSetup(); |
| helper.AddData(&data); |
| HttpNetworkTransaction* trans = helper.trans(); |
| |
| // Start the transaction with basic parameters. |
| TestCompletionCallback callback; |
| int rv = trans->Start(&request_, callback.callback(), log_); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = callback.WaitForResult(); |
| |
| // Setup a user callback which will delete the session, and clear out the |
| // memory holding the stream object. Note that the callback deletes trans. |
| const int kSize = 3000; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kSize); |
| rv = trans->Read( |
| buf.get(), kSize, |
| base::BindOnce(&SpdyNetworkTransactionTest::DeleteSessionCallback, |
| base::Unretained(&helper))); |
| ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); |
| data.Resume(); |
| |
| // Finish running rest of tasks. |
| base::RunLoop().RunUntilIdle(); |
| helper.VerifyDataConsumed(); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, TestRawHeaderSizeSuccessfullRequest) { |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| headers["user-agent"] = ""; |
| headers["accept-encoding"] = "gzip, deflate"; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(headers), LOWEST, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| spdy::SpdySerializedFrame response_body_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "should not include", true)); |
| |
| MockRead response_headers(CreateMockRead(resp, 1)); |
| MockRead reads[] = { |
| response_headers, CreateMockRead(response_body_frame, 2), |
| MockRead(ASYNC, nullptr, 0, 3) // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| |
| TestDelegate delegate; |
| SpdyURLRequestContext spdy_url_request_context; |
| TestNetworkDelegate network_delegate; |
| spdy_url_request_context.set_network_delegate(&network_delegate); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ssl_data.next_proto = kProtoHTTP2; |
| |
| std::unique_ptr<URLRequest> request(spdy_url_request_context.CreateRequest( |
| GURL(kDefaultUrl), DEFAULT_PRIORITY, &delegate, |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider(&ssl_data); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data); |
| |
| request->Start(); |
| base::RunLoop().Run(); |
| |
| EXPECT_LT(0, request->GetTotalSentBytes()); |
| EXPECT_LT(0, request->GetTotalReceivedBytes()); |
| EXPECT_EQ(response_headers.data_len, request->raw_header_size()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, |
| TestRawHeaderSizeSuccessfullPushHeadersFirst) { |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| headers["user-agent"] = ""; |
| headers["accept-encoding"] = "gzip, deflate"; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(headers), LOWEST, true)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority, 2), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame response_body_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "should not include", true)); |
| |
| spdy::SpdyHeaderBlock push_headers; |
| push_headers[":method"] = "GET"; |
| spdy_util_.AddUrlToHeaderBlock(std::string(kDefaultUrl) + "b.dat", |
| &push_headers); |
| |
| spdy::SpdySerializedFrame push_init_frame( |
| spdy_util_.ConstructSpdyPushPromise(1, 2, std::move(push_headers))); |
| |
| spdy::SpdySerializedFrame push_headers_frame( |
| spdy_util_.ConstructSpdyPushHeaders(2, nullptr, 0)); |
| |
| spdy::SpdySerializedFrame push_body_frame( |
| spdy_util_.ConstructSpdyDataFrame(2, "should not include either", false)); |
| |
| MockRead push_init_read(CreateMockRead(push_init_frame, 1)); |
| MockRead response_headers(CreateMockRead(resp, 5)); |
| // raw_header_size() will contain the size of the push promise frame |
| // initialization. |
| int expected_response_headers_size = |
| response_headers.data_len + push_init_read.data_len; |
| |
| MockRead reads[] = { |
| push_init_read, |
| CreateMockRead(push_headers_frame, 3), |
| CreateMockRead(push_body_frame, 4), |
| response_headers, |
| CreateMockRead(response_body_frame, 6), |
| MockRead(ASYNC, 0, 7) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| |
| TestDelegate delegate; |
| SpdyURLRequestContext spdy_url_request_context; |
| TestNetworkDelegate network_delegate; |
| spdy_url_request_context.set_network_delegate(&network_delegate); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ssl_data.next_proto = kProtoHTTP2; |
| |
| std::unique_ptr<URLRequest> request(spdy_url_request_context.CreateRequest( |
| GURL(kDefaultUrl), DEFAULT_PRIORITY, &delegate, |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider(&ssl_data); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data); |
| |
| request->Start(); |
| base::RunLoop().Run(); |
| |
| EXPECT_LT(0, request->GetTotalSentBytes()); |
| EXPECT_LT(0, request->GetTotalReceivedBytes()); |
| EXPECT_EQ(expected_response_headers_size, request->raw_header_size()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, RedirectGetRequest) { |
| SpdyURLRequestContext spdy_url_request_context; |
| // Use a different port to avoid trying to reuse the initial H2 session. |
| const char kRedirectUrl[] = "https://www.foo.com:8080/index.php"; |
| |
| SSLSocketDataProvider ssl_provider0(ASYNC, OK); |
| ssl_provider0.next_proto = kProtoHTTP2; |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider( |
| &ssl_provider0); |
| |
| spdy::SpdyHeaderBlock headers0( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| headers0["user-agent"] = ""; |
| headers0["accept-encoding"] = "gzip, deflate"; |
| |
| spdy::SpdySerializedFrame req0( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(headers0), LOWEST, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes0[] = {CreateMockWrite(req0, 0), CreateMockWrite(rst, 2)}; |
| |
| const char* const kExtraHeaders[] = {"location", kRedirectUrl}; |
| spdy::SpdySerializedFrame resp0(spdy_util_.ConstructSpdyReplyError( |
| "301", kExtraHeaders, base::size(kExtraHeaders) / 2, 1)); |
| MockRead reads0[] = {CreateMockRead(resp0, 1), MockRead(ASYNC, 0, 3)}; |
| |
| SequencedSocketData data0(reads0, writes0); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data0); |
| |
| SSLSocketDataProvider ssl_provider1(ASYNC, OK); |
| ssl_provider1.next_proto = kProtoHTTP2; |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider( |
| &ssl_provider1); |
| |
| SpdyTestUtil spdy_util1; |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util1.ConstructGetHeaderBlock(kRedirectUrl)); |
| headers1["user-agent"] = ""; |
| headers1["accept-encoding"] = "gzip, deflate"; |
| spdy::SpdySerializedFrame req1( |
| spdy_util1.ConstructSpdyHeaders(1, std::move(headers1), LOWEST, true)); |
| MockWrite writes1[] = {CreateMockWrite(req1, 0)}; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util1.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body1(spdy_util1.ConstructSpdyDataFrame(1, true)); |
| MockRead reads1[] = {CreateMockRead(resp1, 1), CreateMockRead(body1, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| SequencedSocketData data1(reads1, writes1); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data1); |
| |
| TestDelegate delegate; |
| |
| std::unique_ptr<URLRequest> request = spdy_url_request_context.CreateRequest( |
| default_url_, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request->Start(); |
| delegate.RunUntilRedirect(); |
| |
| EXPECT_EQ(1, delegate.received_redirect_count()); |
| |
| request->FollowDeferredRedirect(base::nullopt /* removed_headers */, |
| base::nullopt /* modified_headers */); |
| delegate.RunUntilComplete(); |
| |
| EXPECT_EQ(1, delegate.response_started_count()); |
| EXPECT_FALSE(delegate.received_data_before_response()); |
| EXPECT_THAT(delegate.request_status(), IsOk()); |
| EXPECT_EQ("hello!", delegate.data_received()); |
| |
| // Pump the message loop to allow read data to be consumed. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data0.AllReadDataConsumed()); |
| EXPECT_TRUE(data0.AllWriteDataConsumed()); |
| EXPECT_TRUE(data1.AllReadDataConsumed()); |
| EXPECT_TRUE(data1.AllWriteDataConsumed()); |
| } |
| |
| TEST_F(SpdyNetworkTransactionTest, RedirectServerPush) { |
| const char redirected_url[] = "https://www.foo.com/index.php"; |
| SpdyURLRequestContext spdy_url_request_context; |
| |
| SSLSocketDataProvider ssl_provider0(ASYNC, OK); |
| ssl_provider0.next_proto = kProtoHTTP2; |
| ssl_provider0.ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ASSERT_TRUE(ssl_provider0.ssl_info.cert); |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider( |
| &ssl_provider0); |
| |
| spdy::SpdyHeaderBlock headers0( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| headers0["user-agent"] = ""; |
| headers0["accept-encoding"] = "gzip, deflate"; |
| spdy::SpdySerializedFrame req0( |
| spdy_util_.ConstructSpdyHeaders(1, std::move(headers0), LOWEST, true)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = {CreateMockWrite(req0, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 5)}; |
| |
| spdy::SpdySerializedFrame resp0( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame push(spdy_util_.ConstructSpdyPush( |
| nullptr, 0, 2, 1, kPushedUrl, "301", redirected_url)); |
| spdy::SpdySerializedFrame body0(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = {CreateMockRead(resp0, 1), CreateMockRead(push, 2), |
| CreateMockRead(body0, 4), MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data0(reads, writes); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data0); |
| |
| SSLSocketDataProvider ssl_provider1(ASYNC, OK); |
| ssl_provider1.next_proto = kProtoHTTP2; |
| spdy_url_request_context.socket_factory().AddSSLSocketDataProvider( |
| &ssl_provider1); |
| |
| SpdyTestUtil spdy_util1; |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util1.ConstructGetHeaderBlock(redirected_url)); |
| headers1["user-agent"] = ""; |
| headers1["accept-encoding"] = "gzip, deflate"; |
| spdy::SpdySerializedFrame req1( |
| spdy_util1.ConstructSpdyHeaders(1, std::move(headers1), LOWEST, true)); |
| MockWrite writes1[] = {CreateMockWrite(req1, 0)}; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util1.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body1(spdy_util1.ConstructSpdyDataFrame(1, true)); |
| MockRead reads1[] = {CreateMockRead(resp1, 1), CreateMockRead(body1, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| SequencedSocketData data1(reads1, writes1); |
| spdy_url_request_context.socket_factory().AddSocketDataProvider(&data1); |
| |
| TestDelegate delegate0; |
| std::unique_ptr<URLRequest> request = spdy_url_request_context.CreateRequest( |
| default_url_, DEFAULT_PRIORITY, &delegate0, TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| request->Start(); |
| delegate0.RunUntilComplete(); |
| |
| EXPECT_EQ(0, delegate0.received_redirect_count()); |
| EXPECT_EQ("hello!", delegate0.data_received()); |
| |
| TestDelegate delegate1; |
| std::unique_ptr<URLRequest> request1 = spdy_url_request_context.CreateRequest( |
| GURL(kPushedUrl), DEFAULT_PRIORITY, &delegate1, |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| request1->Start(); |
| delegate1.RunUntilRedirect(); |
| EXPECT_EQ(1, delegate1. |