mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2025-02-17 23:43:17 +03:00
1183 lines
43 KiB
C++
1183 lines
43 KiB
C++
// 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 "net/spdy/spdy_http_stream.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <string>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/stl_util.h"
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
#include "crypto/ec_private_key.h"
|
|
#include "crypto/ec_signature_creator.h"
|
|
#include "crypto/signature_creator.h"
|
|
#include "net/base/chunked_upload_data_stream.h"
|
|
#include "net/base/load_timing_info.h"
|
|
#include "net/base/load_timing_info_test_util.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/cert/asn1_util.h"
|
|
#include "net/http/http_request_info.h"
|
|
#include "net/http/http_response_headers.h"
|
|
#include "net/http/http_response_info.h"
|
|
#include "net/log/net_log_with_source.h"
|
|
#include "net/log/test_net_log.h"
|
|
#include "net/socket/socket_tag.h"
|
|
#include "net/socket/socket_test_util.h"
|
|
#include "net/spdy/spdy_http_utils.h"
|
|
#include "net/spdy/spdy_test_util_common.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/traffic_annotation/network_traffic_annotation_test_helper.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace net {
|
|
|
|
namespace test {
|
|
|
|
namespace {
|
|
|
|
// Tests the load timing of a stream that's connected and is not the first
|
|
// request sent on a connection.
|
|
void TestLoadTimingReused(const HttpStream& stream) {
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
|
|
|
|
EXPECT_TRUE(load_timing_info.socket_reused);
|
|
EXPECT_NE(NetLogSource::kInvalidId, load_timing_info.socket_log_id);
|
|
|
|
ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
|
|
ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
|
|
}
|
|
|
|
// Tests the load timing of a stream that's connected and using a fresh
|
|
// connection.
|
|
void TestLoadTimingNotReused(const HttpStream& stream) {
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
|
|
|
|
EXPECT_FALSE(load_timing_info.socket_reused);
|
|
EXPECT_NE(NetLogSource::kInvalidId, load_timing_info.socket_log_id);
|
|
|
|
ExpectConnectTimingHasTimes(
|
|
load_timing_info.connect_timing,
|
|
CONNECT_TIMING_HAS_DNS_TIMES | CONNECT_TIMING_HAS_SSL_TIMES);
|
|
ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
|
|
}
|
|
|
|
class ReadErrorUploadDataStream : public UploadDataStream {
|
|
public:
|
|
enum class FailureMode { SYNC, ASYNC };
|
|
|
|
explicit ReadErrorUploadDataStream(FailureMode mode)
|
|
: UploadDataStream(true, 0), async_(mode) {}
|
|
|
|
private:
|
|
void CompleteRead() { UploadDataStream::OnReadCompleted(ERR_FAILED); }
|
|
|
|
// UploadDataStream implementation:
|
|
int InitInternal(const NetLogWithSource& net_log) override { return OK; }
|
|
|
|
int ReadInternal(IOBuffer* buf, int buf_len) override {
|
|
if (async_ == FailureMode::ASYNC) {
|
|
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
|
FROM_HERE, base::BindOnce(&ReadErrorUploadDataStream::CompleteRead,
|
|
weak_factory_.GetWeakPtr()));
|
|
return ERR_IO_PENDING;
|
|
}
|
|
return ERR_FAILED;
|
|
}
|
|
|
|
void ResetInternal() override {}
|
|
|
|
const FailureMode async_;
|
|
|
|
base::WeakPtrFactory<ReadErrorUploadDataStream> weak_factory_{this};
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ReadErrorUploadDataStream);
|
|
};
|
|
|
|
class CancelStreamCallback : public TestCompletionCallbackBase {
|
|
public:
|
|
explicit CancelStreamCallback(SpdyHttpStream* stream) : stream_(stream) {}
|
|
|
|
CompletionOnceCallback callback() {
|
|
return base::BindOnce(&CancelStreamCallback::CancelStream,
|
|
base::Unretained(this));
|
|
}
|
|
|
|
private:
|
|
void CancelStream(int result) {
|
|
stream_->Cancel();
|
|
SetResult(result);
|
|
}
|
|
|
|
SpdyHttpStream* stream_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class SpdyHttpStreamTest : public TestWithTaskEnvironment {
|
|
public:
|
|
SpdyHttpStreamTest()
|
|
: url_(kDefaultUrl),
|
|
host_port_pair_(HostPortPair::FromURL(url_)),
|
|
key_(host_port_pair_,
|
|
ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(),
|
|
NetworkIsolationKey(),
|
|
false /* disable_secure_dns */),
|
|
ssl_(SYNCHRONOUS, OK) {
|
|
session_deps_.net_log = &net_log_;
|
|
}
|
|
|
|
~SpdyHttpStreamTest() override = default;
|
|
|
|
protected:
|
|
void TearDown() override {
|
|
crypto::ECSignatureCreator::SetFactoryForTesting(nullptr);
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_TRUE(sequenced_data_->AllReadDataConsumed());
|
|
EXPECT_TRUE(sequenced_data_->AllWriteDataConsumed());
|
|
}
|
|
|
|
// Initializes the session using SequencedSocketData.
|
|
void InitSession(base::span<const MockRead> reads,
|
|
base::span<const MockWrite> writes) {
|
|
sequenced_data_ = std::make_unique<SequencedSocketData>(reads, writes);
|
|
session_deps_.socket_factory->AddSocketDataProvider(sequenced_data_.get());
|
|
|
|
ssl_.ssl_info.cert =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
|
|
ASSERT_TRUE(ssl_.ssl_info.cert);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_);
|
|
|
|
http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
|
|
session_ = CreateSpdySession(http_session_.get(), key_, NetLogWithSource());
|
|
}
|
|
|
|
SpdyTestUtil spdy_util_;
|
|
TestNetLog net_log_;
|
|
SpdySessionDependencies session_deps_;
|
|
const GURL url_;
|
|
const HostPortPair host_port_pair_;
|
|
const SpdySessionKey key_;
|
|
std::unique_ptr<SequencedSocketData> sequenced_data_;
|
|
std::unique_ptr<HttpNetworkSession> http_session_;
|
|
base::WeakPtr<SpdySession> session_;
|
|
|
|
private:
|
|
MockECSignatureCreatorFactory ec_signature_creator_factory_;
|
|
SSLSocketDataProvider ssl_;
|
|
};
|
|
|
|
TEST_F(SpdyHttpStreamTest, SendRequest) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0),
|
|
};
|
|
spdy::SpdySerializedFrame resp(
|
|
spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 1), MockRead(SYNCHRONOUS, 0, 2) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "GET";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
// Make sure getting load timing information the stream early does not crash.
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
|
|
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, true, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
|
|
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
|
|
|
|
callback.WaitForResult();
|
|
|
|
// Can get timing information once the stream connects.
|
|
TestLoadTimingNotReused(*http_stream);
|
|
|
|
// Because we abandoned the stream, we don't expect to find a session in the
|
|
// pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
TestLoadTimingNotReused(*http_stream);
|
|
http_stream->Close(true);
|
|
// Test that there's no crash when trying to get the load timing after the
|
|
// stream has been closed.
|
|
TestLoadTimingNotReused(*http_stream);
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size()), http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, RequestInfoDestroyedBeforeRead) {
|
|
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
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
std::unique_ptr<HttpRequestInfo> request =
|
|
std::make_unique<HttpRequestInfo>();
|
|
request->method = "GET";
|
|
request->url = url_;
|
|
request->traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
|
|
ASSERT_THAT(
|
|
http_stream->InitializeStream(request.get(), true, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_LE(0, callback.WaitForResult());
|
|
|
|
TestLoadTimingNotReused(*http_stream);
|
|
LoadTimingInfo load_timing_info;
|
|
EXPECT_TRUE(http_stream->GetLoadTimingInfo(&load_timing_info));
|
|
|
|
// Perform all async reads.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Destroy the request info before Read starts.
|
|
request.reset();
|
|
|
|
// Read stream to completion.
|
|
scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(1);
|
|
ASSERT_EQ(0,
|
|
http_stream->ReadResponseBody(buf.get(), 1, callback.callback()));
|
|
|
|
// Stream 1 has been read to completion.
|
|
TestLoadTimingNotReused(*http_stream);
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size()), http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + body.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, LoadTimingTwoRequests) {
|
|
spdy::SpdySerializedFrame req1(
|
|
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST));
|
|
spdy::SpdySerializedFrame req2(
|
|
spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req1, 0), CreateMockWrite(req2, 1),
|
|
};
|
|
spdy::SpdySerializedFrame resp1(
|
|
spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
|
|
spdy::SpdySerializedFrame body1(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
spdy::SpdySerializedFrame resp2(
|
|
spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3));
|
|
spdy::SpdySerializedFrame body2(
|
|
spdy_util_.ConstructSpdyDataFrame(3, "", true));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp1, 2), CreateMockRead(body1, 3),
|
|
CreateMockRead(resp2, 4), CreateMockRead(body2, 5),
|
|
MockRead(ASYNC, 0, 6) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
HttpRequestInfo request1;
|
|
request1.method = "GET";
|
|
request1.url = url_;
|
|
request1.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
TestCompletionCallback callback1;
|
|
HttpResponseInfo response1;
|
|
HttpRequestHeaders headers1;
|
|
NetLogWithSource net_log;
|
|
auto http_stream1 = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
|
|
HttpRequestInfo request2;
|
|
request2.method = "GET";
|
|
request2.url = url_;
|
|
request2.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
TestCompletionCallback callback2;
|
|
HttpResponseInfo response2;
|
|
HttpRequestHeaders headers2;
|
|
auto http_stream2 = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
|
|
// First write.
|
|
ASSERT_THAT(http_stream1->InitializeStream(&request1, true, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
EXPECT_THAT(
|
|
http_stream1->SendRequest(headers1, &response1, callback1.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_LE(0, callback1.WaitForResult());
|
|
|
|
TestLoadTimingNotReused(*http_stream1);
|
|
LoadTimingInfo load_timing_info1;
|
|
LoadTimingInfo load_timing_info2;
|
|
EXPECT_TRUE(http_stream1->GetLoadTimingInfo(&load_timing_info1));
|
|
EXPECT_FALSE(http_stream2->GetLoadTimingInfo(&load_timing_info2));
|
|
|
|
// Second write.
|
|
ASSERT_THAT(http_stream2->InitializeStream(&request2, true, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
EXPECT_THAT(
|
|
http_stream2->SendRequest(headers2, &response2, callback2.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_LE(0, callback2.WaitForResult());
|
|
|
|
// Perform all async reads.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
TestLoadTimingReused(*http_stream2);
|
|
EXPECT_TRUE(http_stream2->GetLoadTimingInfo(&load_timing_info2));
|
|
EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
|
|
|
|
// Read stream 1 to completion, before making sure we can still read load
|
|
// timing from both streams.
|
|
scoped_refptr<IOBuffer> buf1 = base::MakeRefCounted<IOBuffer>(1);
|
|
ASSERT_EQ(
|
|
0, http_stream1->ReadResponseBody(buf1.get(), 1, callback1.callback()));
|
|
|
|
// Stream 1 has been read to completion.
|
|
TestLoadTimingNotReused(*http_stream1);
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req1.size()),
|
|
http_stream1->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp1.size() + body1.size()),
|
|
http_stream1->GetTotalReceivedBytes());
|
|
|
|
// Stream 2 still has queued body data.
|
|
TestLoadTimingReused(*http_stream2);
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req2.size()),
|
|
http_stream2->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp2.size() + body2.size()),
|
|
http_stream2->GetTotalReceivedBytes());
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, SendChunkedPost) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame body(
|
|
spdy_util_.ConstructSpdyDataFrame(1, kUploadData,
|
|
/*fin=*/true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), // request
|
|
CreateMockWrite(body, 1) // POST upload frame
|
|
};
|
|
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 2), CreateMockRead(body, 3, SYNCHRONOUS),
|
|
MockRead(SYNCHRONOUS, 0, 4) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
const int kFirstChunkSize = kUploadDataSize/2;
|
|
upload_stream.AppendData(kUploadData, kFirstChunkSize, false);
|
|
upload_stream.AppendData(kUploadData + kFirstChunkSize,
|
|
kUploadDataSize - kFirstChunkSize, true);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
EXPECT_THAT(http_stream.SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + body.size()),
|
|
http_stream.GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + body.size()),
|
|
http_stream.GetTotalReceivedBytes());
|
|
|
|
// Because the server closed the connection, we there shouldn't be a session
|
|
// in the pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
// This unittest tests the request callback is properly called and handled.
|
|
TEST_F(SpdyHttpStreamTest, SendChunkedPostLastEmpty) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), // request
|
|
CreateMockWrite(chunk, 1),
|
|
};
|
|
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 2), CreateMockRead(chunk, 3, SYNCHRONOUS),
|
|
MockRead(SYNCHRONOUS, 0, 4) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
upload_stream.AppendData(nullptr, 0, true);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
EXPECT_THAT(http_stream.SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk.size()),
|
|
http_stream.GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + chunk.size()),
|
|
http_stream.GetTotalReceivedBytes());
|
|
|
|
// Because the server closed the connection, there shouldn't be a session
|
|
// in the pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, ConnectionClosedDuringChunkedPost) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame body(
|
|
spdy_util_.ConstructSpdyDataFrame(1, kUploadData,
|
|
/*fin=*/false));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), // Request
|
|
CreateMockWrite(body, 1) // First POST upload frame
|
|
};
|
|
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
MockRead reads[] = {
|
|
MockRead(ASYNC, ERR_CONNECTION_CLOSED, 2) // Server hangs up early.
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
// Append first chunk.
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, false);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
EXPECT_THAT(http_stream.SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
EXPECT_THAT(callback.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + body.size()),
|
|
http_stream.GetTotalSentBytes());
|
|
EXPECT_EQ(0, http_stream.GetTotalReceivedBytes());
|
|
|
|
// Because the server closed the connection, we there shouldn't be a session
|
|
// in the pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// Appending a second chunk now should not result in a crash.
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, true);
|
|
// Appending data is currently done synchronously, but seems best to be
|
|
// paranoid.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// The total sent and received bytes should be unchanged.
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + body.size()),
|
|
http_stream.GetTotalSentBytes());
|
|
EXPECT_EQ(0, http_stream.GetTotalReceivedBytes());
|
|
}
|
|
|
|
// Test to ensure the SpdyStream state machine does not get confused when a
|
|
// chunk becomes available while a write is pending.
|
|
TEST_F(SpdyHttpStreamTest, DelayedSendChunkedPost) {
|
|
const char kUploadData1[] = "12345678";
|
|
const int kUploadData1Size = base::size(kUploadData1) - 1;
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk1(spdy_util_.ConstructSpdyDataFrame(1, false));
|
|
spdy::SpdySerializedFrame chunk2(
|
|
spdy_util_.ConstructSpdyDataFrame(1, kUploadData1, false));
|
|
spdy::SpdySerializedFrame chunk3(spdy_util_.ConstructSpdyDataFrame(1, true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0),
|
|
CreateMockWrite(chunk1, 1), // POST upload frames
|
|
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
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, false);
|
|
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpRequestHeaders headers;
|
|
HttpResponseInfo response;
|
|
// This will attempt to Write() the initial request and headers, which will
|
|
// complete asynchronously.
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// Complete the initial request write and the first chunk.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_FALSE(callback.have_result());
|
|
|
|
// Now append the final two chunks which will enqueue two more writes.
|
|
upload_stream.AppendData(kUploadData1, kUploadData1Size, false);
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, true);
|
|
|
|
// Finish writing all the chunks and do all reads.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_TRUE(callback.have_result());
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk1.size() + chunk2.size() +
|
|
chunk3.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + chunk1.size() + chunk2.size() +
|
|
chunk3.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
|
|
// Check response headers.
|
|
ASSERT_THAT(http_stream->ReadResponseHeaders(callback.callback()), IsOk());
|
|
|
|
// Check |chunk1| response.
|
|
scoped_refptr<IOBuffer> buf1 =
|
|
base::MakeRefCounted<IOBuffer>(kUploadDataSize);
|
|
ASSERT_EQ(kUploadDataSize,
|
|
http_stream->ReadResponseBody(
|
|
buf1.get(), kUploadDataSize, callback.callback()));
|
|
EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
|
|
|
|
// Check |chunk2| response.
|
|
scoped_refptr<IOBuffer> buf2 =
|
|
base::MakeRefCounted<IOBuffer>(kUploadData1Size);
|
|
ASSERT_EQ(kUploadData1Size,
|
|
http_stream->ReadResponseBody(
|
|
buf2.get(), kUploadData1Size, callback.callback()));
|
|
EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
|
|
|
|
// Check |chunk3| response.
|
|
scoped_refptr<IOBuffer> buf3 =
|
|
base::MakeRefCounted<IOBuffer>(kUploadDataSize);
|
|
ASSERT_EQ(kUploadDataSize,
|
|
http_stream->ReadResponseBody(
|
|
buf3.get(), kUploadDataSize, callback.callback()));
|
|
EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
|
|
|
|
ASSERT_TRUE(response.headers.get());
|
|
ASSERT_EQ(200, response.headers->response_code());
|
|
}
|
|
|
|
// Test that the SpdyStream state machine can handle sending a final empty data
|
|
// frame when uploading a chunked data stream.
|
|
TEST_F(SpdyHttpStreamTest, DelayedSendChunkedPostWithEmptyFinalDataFrame) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk1(spdy_util_.ConstructSpdyDataFrame(1, false));
|
|
spdy::SpdySerializedFrame chunk2(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0),
|
|
CreateMockWrite(chunk1, 1), // POST upload frames
|
|
CreateMockWrite(chunk2, 2),
|
|
};
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 3), CreateMockRead(chunk1, 4),
|
|
CreateMockRead(chunk2, 5), MockRead(ASYNC, 0, 6) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, false);
|
|
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpRequestHeaders headers;
|
|
HttpResponseInfo response;
|
|
// This will attempt to Write() the initial request and headers, which will
|
|
// complete asynchronously.
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// Complete the initial request write and the first chunk.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_FALSE(callback.have_result());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk1.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(0, http_stream->GetTotalReceivedBytes());
|
|
|
|
// Now end the stream with an empty data frame and the FIN set.
|
|
upload_stream.AppendData(nullptr, 0, true);
|
|
|
|
// Finish writing the final frame, and perform all reads.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_TRUE(callback.have_result());
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
// Check response headers.
|
|
ASSERT_THAT(http_stream->ReadResponseHeaders(callback.callback()), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk1.size() + chunk2.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + chunk1.size() + chunk2.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
|
|
// Check |chunk1| response.
|
|
scoped_refptr<IOBuffer> buf1 =
|
|
base::MakeRefCounted<IOBuffer>(kUploadDataSize);
|
|
ASSERT_EQ(kUploadDataSize,
|
|
http_stream->ReadResponseBody(
|
|
buf1.get(), kUploadDataSize, callback.callback()));
|
|
EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
|
|
|
|
// Check |chunk2| response.
|
|
ASSERT_EQ(0,
|
|
http_stream->ReadResponseBody(
|
|
buf1.get(), kUploadDataSize, callback.callback()));
|
|
|
|
ASSERT_TRUE(response.headers.get());
|
|
ASSERT_EQ(200, response.headers->response_code());
|
|
}
|
|
|
|
// Test that the SpdyStream state machine handles a chunked upload with no
|
|
// payload. Unclear if this is a case worth supporting.
|
|
TEST_F(SpdyHttpStreamTest, ChunkedPostWithEmptyPayload) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), CreateMockWrite(chunk, 1),
|
|
};
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 2), CreateMockRead(chunk, 3),
|
|
MockRead(ASYNC, 0, 4) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
upload_stream.AppendData("", 0, true);
|
|
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
TestCompletionCallback callback;
|
|
HttpRequestHeaders headers;
|
|
HttpResponseInfo response;
|
|
// This will attempt to Write() the initial request and headers, which will
|
|
// complete asynchronously.
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// Complete writing request, followed by a FIN.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_TRUE(callback.have_result());
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + chunk.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
|
|
// Check response headers.
|
|
ASSERT_THAT(http_stream->ReadResponseHeaders(callback.callback()), IsOk());
|
|
|
|
// Check |chunk| response.
|
|
scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(1);
|
|
ASSERT_EQ(0,
|
|
http_stream->ReadResponseBody(
|
|
buf.get(), 1, callback.callback()));
|
|
|
|
ASSERT_TRUE(response.headers.get());
|
|
ASSERT_EQ(200, response.headers->response_code());
|
|
}
|
|
|
|
// Test case for https://crbug.com/50058.
|
|
TEST_F(SpdyHttpStreamTest, SpdyURLTest) {
|
|
const char* const full_url = "https://www.example.org/foo?query=what#anchor";
|
|
const char* const base_url = "https://www.example.org/foo?query=what";
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructSpdyGet(base_url, 1, LOWEST));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0),
|
|
};
|
|
spdy::SpdySerializedFrame resp(
|
|
spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 1), MockRead(SYNCHRONOUS, 0, 2) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "GET";
|
|
request.url = GURL(full_url);
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, true, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
EXPECT_EQ(base_url, http_stream->stream()->url().spec());
|
|
|
|
callback.WaitForResult();
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size()), http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
|
|
// Because we abandoned the stream, we don't expect to find a session in the
|
|
// pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
// Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
|
|
// made available is handled correctly.
|
|
TEST_F(SpdyHttpStreamTest, DelayedSendChunkedPostWithWindowUpdate) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk1(spdy_util_.ConstructSpdyDataFrame(1, true));
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), CreateMockWrite(chunk1, 1),
|
|
};
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
spdy::SpdySerializedFrame window_update(
|
|
spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize));
|
|
MockRead reads[] = {
|
|
CreateMockRead(window_update, 2), MockRead(ASYNC, ERR_IO_PENDING, 3),
|
|
CreateMockRead(resp, 4), CreateMockRead(chunk1, 5),
|
|
MockRead(ASYNC, 0, 6) // EOF
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
ASSERT_THAT(upload_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
NetLogWithSource net_log;
|
|
auto http_stream = std::make_unique<SpdyHttpStream>(
|
|
session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream->InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
HttpRequestHeaders headers;
|
|
HttpResponseInfo response;
|
|
// This will attempt to Write() the initial request and headers, which will
|
|
// complete asynchronously.
|
|
TestCompletionCallback callback;
|
|
EXPECT_THAT(http_stream->SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// Complete the initial request write and first chunk.
|
|
base::RunLoop().RunUntilIdle();
|
|
ASSERT_FALSE(callback.have_result());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size()), http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(0, http_stream->GetTotalReceivedBytes());
|
|
|
|
upload_stream.AppendData(kUploadData, kUploadDataSize, true);
|
|
|
|
// Verify that the window size has decreased.
|
|
ASSERT_TRUE(http_stream->stream() != nullptr);
|
|
EXPECT_NE(static_cast<int>(kDefaultInitialWindowSize),
|
|
http_stream->stream()->send_window_size());
|
|
|
|
// Read window update.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
ASSERT_TRUE(callback.have_result());
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk1.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
// The window update is not counted in the total received bytes.
|
|
EXPECT_EQ(0, http_stream->GetTotalReceivedBytes());
|
|
|
|
// Verify the window update.
|
|
ASSERT_TRUE(http_stream->stream() != nullptr);
|
|
EXPECT_EQ(static_cast<int>(kDefaultInitialWindowSize),
|
|
http_stream->stream()->send_window_size());
|
|
|
|
// Read rest of data.
|
|
sequenced_data_->Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_EQ(static_cast<int64_t>(req.size() + chunk1.size()),
|
|
http_stream->GetTotalSentBytes());
|
|
EXPECT_EQ(static_cast<int64_t>(resp.size() + chunk1.size()),
|
|
http_stream->GetTotalReceivedBytes());
|
|
|
|
// Check response headers.
|
|
ASSERT_THAT(http_stream->ReadResponseHeaders(callback.callback()), IsOk());
|
|
|
|
// Check |chunk1| response.
|
|
scoped_refptr<IOBuffer> buf1 =
|
|
base::MakeRefCounted<IOBuffer>(kUploadDataSize);
|
|
ASSERT_EQ(kUploadDataSize,
|
|
http_stream->ReadResponseBody(
|
|
buf1.get(), kUploadDataSize, callback.callback()));
|
|
EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
|
|
|
|
ASSERT_TRUE(response.headers.get());
|
|
ASSERT_EQ(200, response.headers->response_code());
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, DataReadErrorSynchronous) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
|
|
// Server receives spdy::ERROR_CODE_INTERNAL_ERROR on client's internal
|
|
// failure. The failure is a reading error in this case caused by
|
|
// UploadDataStream::Read().
|
|
spdy::SpdySerializedFrame rst_frame(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_INTERNAL_ERROR));
|
|
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0, SYNCHRONOUS), // Request
|
|
CreateMockWrite(rst_frame, 1, SYNCHRONOUS) // Reset frame
|
|
};
|
|
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
|
|
MockRead reads[] = {
|
|
CreateMockRead(resp, 2), MockRead(SYNCHRONOUS, 0, 3),
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ReadErrorUploadDataStream upload_data_stream(
|
|
ReadErrorUploadDataStream::FailureMode::SYNC);
|
|
ASSERT_THAT(upload_data_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_data_stream;
|
|
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
int result = http_stream.SendRequest(headers, &response, callback.callback());
|
|
EXPECT_THAT(callback.GetResult(result), IsError(ERR_FAILED));
|
|
|
|
// Run posted SpdyHttpStream::ResetStreamInternal() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Because the server has not closed the connection yet, there shouldn't be
|
|
// a stream but a session in the pool
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
TEST_F(SpdyHttpStreamTest, DataReadErrorAsynchronous) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
|
|
// Server receives spdy::ERROR_CODE_INTERNAL_ERROR on client's internal
|
|
// failure. The failure is a reading error in this case caused by
|
|
// UploadDataStream::Read().
|
|
spdy::SpdySerializedFrame rst_frame(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_INTERNAL_ERROR));
|
|
|
|
MockWrite writes[] = {
|
|
CreateMockWrite(req, 0), // Request
|
|
CreateMockWrite(rst_frame, 1) // Reset frame
|
|
};
|
|
|
|
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0));
|
|
|
|
MockRead reads[] = {
|
|
MockRead(ASYNC, 0, 2),
|
|
};
|
|
|
|
InitSession(reads, writes);
|
|
|
|
ReadErrorUploadDataStream upload_data_stream(
|
|
ReadErrorUploadDataStream::FailureMode::ASYNC);
|
|
ASSERT_THAT(upload_data_stream.Init(TestCompletionCallback().callback(),
|
|
NetLogWithSource()),
|
|
IsOk());
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
request.upload_data_stream = &upload_data_stream;
|
|
|
|
TestCompletionCallback callback;
|
|
HttpResponseInfo response;
|
|
HttpRequestHeaders headers;
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
int result = http_stream.SendRequest(headers, &response, callback.callback());
|
|
EXPECT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_THAT(callback.GetResult(result), IsError(ERR_FAILED));
|
|
|
|
// Run posted SpdyHttpStream::ResetStreamInternal() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Because the server has closed the connection, there shouldn't be a session
|
|
// in the pool anymore.
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
// Regression test for https://crbug.com/622447.
|
|
TEST_F(SpdyHttpStreamTest, RequestCallbackCancelsStream) {
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util_.ConstructChunkedSpdyPost(nullptr, 0));
|
|
spdy::SpdySerializedFrame chunk(
|
|
spdy_util_.ConstructSpdyDataFrame(1, "", true));
|
|
spdy::SpdySerializedFrame rst(
|
|
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
|
|
MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(chunk, 1),
|
|
CreateMockWrite(rst, 2)};
|
|
MockRead reads[] = {MockRead(ASYNC, 0, 3)};
|
|
InitSession(reads, writes);
|
|
|
|
HttpRequestInfo request;
|
|
request.method = "POST";
|
|
request.url = url_;
|
|
request.traffic_annotation =
|
|
MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
|
|
ChunkedUploadDataStream upload_stream(0);
|
|
request.upload_data_stream = &upload_stream;
|
|
|
|
TestCompletionCallback upload_callback;
|
|
ASSERT_THAT(
|
|
upload_stream.Init(upload_callback.callback(), NetLogWithSource()),
|
|
IsOk());
|
|
upload_stream.AppendData("", 0, true);
|
|
|
|
NetLogWithSource net_log;
|
|
SpdyHttpStream http_stream(session_, kNoPushedStreamFound, net_log.source());
|
|
ASSERT_THAT(http_stream.InitializeStream(&request, false, DEFAULT_PRIORITY,
|
|
net_log, CompletionOnceCallback()),
|
|
IsOk());
|
|
|
|
CancelStreamCallback callback(&http_stream);
|
|
HttpRequestHeaders headers;
|
|
HttpResponseInfo response;
|
|
// This will attempt to Write() the initial request and headers, which will
|
|
// complete asynchronously.
|
|
EXPECT_THAT(http_stream.SendRequest(headers, &response, callback.callback()),
|
|
IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
|
|
// The callback cancels |http_stream|.
|
|
EXPECT_THAT(callback.WaitForResult(), IsOk());
|
|
|
|
// Finish async network reads/writes.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key_));
|
|
}
|
|
|
|
// TODO(willchan): Write a longer test for SpdyStream that exercises all
|
|
// methods.
|
|
|
|
} // namespace test
|
|
|
|
} // namespace net
|