summaryrefslogtreecommitdiffstats
path: root/net/spdy
diff options
context:
space:
mode:
authorsatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-04 07:54:39 +0000
committersatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-04 07:54:39 +0000
commitbf96f533df6515f9ddea3278515a77ab81c00263 (patch)
tree72ba664e83a3a5880b1b16bd379d041c2e85e368 /net/spdy
parent26c2f823d194dc69819b7def92f920f0ec861df5 (diff)
downloadchromium_src-bf96f533df6515f9ddea3278515a77ab81c00263.zip
chromium_src-bf96f533df6515f9ddea3278515a77ab81c00263.tar.gz
chromium_src-bf96f533df6515f9ddea3278515a77ab81c00263.tar.bz2
Add chunked uploads support to SPDY
As part of this, I had to move the chunked encoding part from UploadData::Element::SetChunk to HttpStreamParser::DoSendBody as SPDY doesn't have this encoded format and UploadData needs to serve both. BUG=none TEST=net_unittests (2 new tests added) Review URL: http://codereview.chromium.org/6292013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@76892 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r--net/spdy/spdy_http_stream.cc31
-rw-r--r--net/spdy/spdy_http_stream.h5
-rw-r--r--net/spdy/spdy_http_stream_unittest.cc66
-rw-r--r--net/spdy/spdy_http_utils.cc4
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc50
-rw-r--r--net/spdy/spdy_proxy_client_socket.cc7
-rw-r--r--net/spdy/spdy_proxy_client_socket.h3
-rw-r--r--net/spdy/spdy_session.cc2
-rw-r--r--net/spdy/spdy_stream.cc24
-rw-r--r--net/spdy/spdy_stream.h18
-rw-r--r--net/spdy/spdy_stream_unittest.cc5
-rw-r--r--net/spdy/spdy_test_util.cc29
-rw-r--r--net/spdy/spdy_test_util.h7
13 files changed, 227 insertions, 24 deletions
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc
index e07578e..51a2efd 100644
--- a/net/spdy/spdy_http_stream.cc
+++ b/net/spdy/spdy_http_stream.cc
@@ -177,6 +177,11 @@ void SpdyHttpStream::SetConnectionReused() {
// SPDY doesn't need an indicator here.
}
+void SpdyHttpStream::set_chunk_callback(ChunkCallback* callback) {
+ if (request_body_stream_ != NULL)
+ request_body_stream_->set_chunk_callback(callback);
+}
+
int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
UploadDataStream* request_body,
HttpResponseInfo* response,
@@ -200,7 +205,7 @@ int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
CHECK(!request_body_stream_.get());
if (request_body) {
- if (request_body->size())
+ if (request_body->size() || request_body->is_chunked())
request_body_stream_.reset(request_body);
else
delete request_body;
@@ -264,17 +269,33 @@ bool SpdyHttpStream::OnSendHeadersComplete(int status) {
int SpdyHttpStream::OnSendBody() {
CHECK(request_body_stream_.get());
+
int buf_len = static_cast<int>(request_body_stream_->buf_len());
if (!buf_len)
return OK;
- return stream_->WriteStreamData(request_body_stream_->buf(), buf_len,
- spdy::DATA_FLAG_FIN);
+ bool is_chunked = request_body_stream_->is_chunked();
+ // TODO(satish): For non-chunked POST data, we set DATA_FLAG_FIN for all
+ // blocks of data written out. This is wrong if the POST data was larger than
+ // UploadDataStream::kBufSize as that is the largest buffer that
+ // UploadDataStream returns at a time and we'll be setting the FIN flag for
+ // each block of data written out.
+ bool eof = !is_chunked || request_body_stream_->IsOnLastChunk();
+ return stream_->WriteStreamData(
+ request_body_stream_->buf(), buf_len,
+ eof ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE);
}
-bool SpdyHttpStream::OnSendBodyComplete(int status) {
+int SpdyHttpStream::OnSendBodyComplete(int status, bool* eof) {
CHECK(request_body_stream_.get());
+
request_body_stream_->MarkConsumedAndFillBuffer(status);
- return request_body_stream_->eof();
+ *eof = request_body_stream_->eof();
+ if (!*eof &&
+ request_body_stream_->is_chunked() &&
+ !request_body_stream_->buf_len())
+ return ERR_IO_PENDING;
+
+ return OK;
}
int SpdyHttpStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response,
diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h
index 6e78379..bdae342 100644
--- a/net/spdy/spdy_http_stream.h
+++ b/net/spdy/spdy_http_stream.h
@@ -69,13 +69,14 @@ class SpdyHttpStream : public SpdyStream::Delegate, public HttpStream {
// SpdyStream::Delegate methods:
virtual bool OnSendHeadersComplete(int status);
virtual int OnSendBody();
- virtual bool OnSendBodyComplete(int status);
+ virtual int OnSendBodyComplete(int status, bool* eof);
virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
base::Time response_time,
int status);
virtual void OnDataReceived(const char* buffer, int bytes);
virtual void OnDataSent(int length);
virtual void OnClose(int status);
+ virtual void set_chunk_callback(ChunkCallback* callback);
private:
FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, FlowControlStallResume);
@@ -127,6 +128,8 @@ class SpdyHttpStream : public SpdyStream::Delegate, public HttpStream {
// Is this spdy stream direct to the origin server (or to a proxy).
bool direct_;
+ bool send_last_chunk_;
+
DISALLOW_COPY_AND_ASSIGN(SpdyHttpStream);
};
diff --git a/net/spdy/spdy_http_stream_unittest.cc b/net/spdy/spdy_http_stream_unittest.cc
index 1688871..bee345a 100644
--- a/net/spdy/spdy_http_stream_unittest.cc
+++ b/net/spdy/spdy_http_stream_unittest.cc
@@ -93,7 +93,69 @@ TEST_F(SpdyHttpStreamTest, SendRequest) {
// Because we abandoned the stream, we don't expect to find a session in the
// pool anymore.
- EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyHttpStreamTest, SendChunkedPost) {
+ EnableCompression(false);
+ SpdySession::SetSSLMode(false);
+ UploadDataStream::set_merge_chunks(false);
+
+ scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ CreateMockWrite(*chunk1, 2), // POST upload frames
+ CreateMockWrite(*chunk2, 3),
+ };
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 4),
+ CreateMockRead(*chunk1, 5),
+ CreateMockRead(*chunk2, 5),
+ MockRead(false, 0, 6) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+ host_port_pair));
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data = new UploadData();
+ request.upload_data->set_is_chunked(true);
+ request.upload_data->AppendChunk(kUploadData, kUploadDataSize, false);
+ request.upload_data->AppendChunk(kUploadData, kUploadDataSize, true);
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ SpdyHttpStream http_stream(session_.get(), true);
+ ASSERT_EQ(
+ OK,
+ http_stream.InitializeStream(&request, net_log, NULL));
+
+ UploadDataStream* upload_stream =
+ UploadDataStream::Create(request.upload_data, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+ headers, upload_stream, &response, &callback));
+ EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+ // This triggers the MockWrite and read 2
+ callback.WaitForResult();
+
+ // This triggers read 3. The empty read causes the session to shut down.
+ data()->CompleteRead();
+ MessageLoop::current()->RunAllPending();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
EXPECT_TRUE(data()->at_read_eof());
EXPECT_TRUE(data()->at_write_eof());
}
@@ -151,7 +213,7 @@ TEST_F(SpdyHttpStreamTest, SpdyURLTest) {
// Because we abandoned the stream, we don't expect to find a session in the
// pool anymore.
- EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(pair));
+ EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
EXPECT_TRUE(data()->at_read_eof());
EXPECT_TRUE(data()->at_write_eof());
}
diff --git a/net/spdy/spdy_http_utils.cc b/net/spdy/spdy_http_utils.cc
index bf7376a..a3b7e54 100644
--- a/net/spdy/spdy_http_utils.cc
+++ b/net/spdy/spdy_http_utils.cc
@@ -83,8 +83,10 @@ void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
HttpRequestHeaders::Iterator it(request_headers);
while (it.GetNext()) {
std::string name = StringToLowerASCII(it.name());
- if (name == "connection" || name == "proxy-connection")
+ if (name == "connection" || name == "proxy-connection" ||
+ name == "transfer-encoding") {
continue;
+ }
if (headers->find(name) == headers->end()) {
(*headers)[name] = it.value();
} else {
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc
index 84e19d6..94ef61d 100644
--- a/net/spdy/spdy_network_transaction_unittest.cc
+++ b/net/spdy/spdy_network_transaction_unittest.cc
@@ -40,6 +40,7 @@ class SpdyNetworkTransactionTest
EnableCompression(false);
google_get_request_initialized_ = false;
google_post_request_initialized_ = false;
+ google_chunked_post_request_initialized_ = false;
}
virtual void TearDown() {
@@ -350,6 +351,21 @@ class SpdyNetworkTransactionTest
return google_post_request_;
}
+ const HttpRequestInfo& CreateChunkedPostRequest() {
+ if (!google_chunked_post_request_initialized_) {
+ google_chunked_post_request_.method = "POST";
+ google_chunked_post_request_.url = GURL(kDefaultURL);
+ google_chunked_post_request_.upload_data = new UploadData();
+ google_chunked_post_request_.upload_data->set_is_chunked(true);
+ google_chunked_post_request_.upload_data->AppendChunk(
+ kUploadData, kUploadDataSize, false);
+ google_chunked_post_request_.upload_data->AppendChunk(
+ kUploadData, kUploadDataSize, true);
+ google_chunked_post_request_initialized_ = true;
+ }
+ return google_chunked_post_request_;
+ }
+
// 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
@@ -454,8 +470,10 @@ class SpdyNetworkTransactionTest
private:
bool google_get_request_initialized_;
bool google_post_request_initialized_;
+ bool google_chunked_post_request_initialized_;
HttpRequestInfo google_get_request_;
HttpRequestInfo google_post_request_;
+ HttpRequestInfo google_chunked_post_request_;
HttpRequestInfo google_get_push_request_;
};
@@ -1525,6 +1543,38 @@ TEST_P(SpdyNetworkTransactionTest, Post) {
EXPECT_EQ("hello!", out.response_data);
}
+// Test that a chunked POST works.
+TEST_P(SpdyNetworkTransactionTest, ChunkedPost) {
+ UploadDataStream::set_merge_chunks(false);
+ scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*chunk1),
+ CreateMockWrite(*chunk2),
+ };
+
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*chunk1),
+ CreateMockRead(*chunk2),
+ MockRead(true, 0, 0) // EOF
+ };
+
+ scoped_refptr<DelayedSocketData> data(
+ new DelayedSocketData(2, reads, arraysize(reads),
+ writes, arraysize(writes)));
+ NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+ BoundNetLog(), GetParam());
+ helper.RunToCompletion(data.get());
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+}
+
// Test that a POST without any post data works.
TEST_P(SpdyNetworkTransactionTest, NullPost) {
// Setup the request
diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc
index 452ee3b..f451868 100644
--- a/net/spdy/spdy_proxy_client_socket.cc
+++ b/net/spdy/spdy_proxy_client_socket.cc
@@ -404,11 +404,11 @@ int SpdyProxyClientSocket::OnSendBody() {
return ERR_UNEXPECTED;
}
-bool SpdyProxyClientSocket::OnSendBodyComplete(int status) {
+int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
// Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
// OnSendBodyComplete() should never be called.
NOTREACHED();
- return false;
+ return ERR_UNEXPECTED;
}
int SpdyProxyClientSocket::OnResponseReceived(
@@ -498,4 +498,7 @@ void SpdyProxyClientSocket::OnClose(int status) {
write_callback->Run(ERR_CONNECTION_CLOSED);
}
+void SpdyProxyClientSocket::set_chunk_callback(ChunkCallback* /*callback*/) {
+}
+
} // namespace net
diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h
index f66283e1..69261d3 100644
--- a/net/spdy/spdy_proxy_client_socket.h
+++ b/net/spdy/spdy_proxy_client_socket.h
@@ -88,13 +88,14 @@ class SpdyProxyClientSocket : public ProxyClientSocket,
// SpdyStream::Delegate methods:
virtual bool OnSendHeadersComplete(int status);
virtual int OnSendBody();
- virtual bool OnSendBodyComplete(int status);
+ virtual int OnSendBodyComplete(int status, bool* eof);
virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
base::Time response_time,
int status);
virtual void OnDataReceived(const char* data, int length);
virtual void OnDataSent(int length);
virtual void OnClose(int status);
+ virtual void set_chunk_callback(ChunkCallback* /*callback*/);
private:
enum State {
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index c268ee4..9776aff 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -636,7 +636,7 @@ void SpdySession::OnWriteComplete(int result) {
// size.
if (result > 0) {
result = in_flight_write_.buffer()->size();
- DCHECK_GT(result, static_cast<int>(spdy::SpdyFrame::size()));
+ DCHECK_GE(result, static_cast<int>(spdy::SpdyFrame::size()));
result -= static_cast<int>(spdy::SpdyFrame::size());
}
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 3900993..e948b33 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -112,6 +112,8 @@ void SpdyStream::PushedStreamReplayData() {
}
void SpdyStream::DetachDelegate() {
+ if (delegate_)
+ delegate_->set_chunk_callback(NULL);
delegate_ = NULL;
if (!closed())
Cancel();
@@ -348,13 +350,22 @@ void SpdyStream::OnWriteComplete(int bytes) {
DoLoop(bytes);
}
+void SpdyStream::OnChunkAvailable() {
+ DCHECK(io_state_ == STATE_SEND_HEADERS || io_state_ == STATE_SEND_BODY ||
+ io_state_ == STATE_SEND_BODY_COMPLETE);
+ if (io_state_ == STATE_SEND_BODY)
+ OnWriteComplete(0);
+}
+
void SpdyStream::OnClose(int status) {
io_state_ = STATE_DONE;
response_status_ = status;
Delegate* delegate = delegate_;
delegate_ = NULL;
- if (delegate)
+ if (delegate) {
+ delegate->set_chunk_callback(NULL);
delegate->OnClose(status);
+ }
}
void SpdyStream::Cancel() {
@@ -367,6 +378,9 @@ void SpdyStream::Cancel() {
}
int SpdyStream::SendRequest(bool has_upload_data) {
+ if (delegate_)
+ delegate_->set_chunk_callback(this);
+
// Pushed streams do not send any data, and should always be in STATE_OPEN or
// STATE_DONE. However, we still want to return IO_PENDING to mimic non-push
// behavior.
@@ -545,17 +559,17 @@ int SpdyStream::DoSendBodyComplete(int result) {
if (result < 0)
return result;
- CHECK_NE(result, 0);
-
if (!delegate_)
return ERR_UNEXPECTED;
- if (!delegate_->OnSendBodyComplete(result))
+ bool eof = false;
+ result = delegate_->OnSendBodyComplete(result, &eof);
+ if (!eof)
io_state_ = STATE_SEND_BODY;
else
io_state_ = STATE_WAITING_FOR_RESPONSE;
- return OK;
+ return result;
}
int SpdyStream::DoOpen(int result) {
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index 9a055c2..6d6b09c 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -16,6 +16,7 @@
#include "googleurl/src/gurl.h"
#include "net/base/bandwidth_metrics.h"
#include "net/base/io_buffer.h"
+#include "net/base/upload_data.h"
#include "net/base/net_log.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
@@ -34,7 +35,9 @@ class SSLInfo;
// a SpdyNetworkTransaction) will maintain a reference to the stream. When
// initiated by the server, only the SpdySession will maintain any reference,
// until such a time as a client object requests a stream for the path.
-class SpdyStream : public base::RefCounted<SpdyStream> {
+class SpdyStream
+ : public base::RefCounted<SpdyStream>,
+ public ChunkCallback {
public:
// Delegate handles protocol specific behavior of spdy stream.
class Delegate {
@@ -50,9 +53,10 @@ class SpdyStream : public base::RefCounted<SpdyStream> {
virtual int OnSendBody() = 0;
// Called when data has been sent. |status| indicates network error
- // or number of bytes has been sent.
- // Returns true if no more data to be sent.
- virtual bool OnSendBodyComplete(int status) = 0;
+ // or number of bytes that has been sent. On return, |eof| is set to true
+ // if no more data is available to send in the request body.
+ // Returns network error code. OK when it successfully sent data.
+ virtual int OnSendBodyComplete(int status, bool* eof) = 0;
// Called when the SYN_STREAM, SYN_REPLY, or HEADERS frames are received.
// Normal streams will receive a SYN_REPLY and optional HEADERS frames.
@@ -73,6 +77,9 @@ class SpdyStream : public base::RefCounted<SpdyStream> {
// Called when SpdyStream is closed.
virtual void OnClose(int status) = 0;
+ // Sets the callback to be invoked when a new chunk is available to upload.
+ virtual void set_chunk_callback(ChunkCallback* callback) = 0;
+
protected:
friend class base::RefCounted<Delegate>;
virtual ~Delegate() {}
@@ -222,6 +229,9 @@ class SpdyStream : public base::RefCounted<SpdyStream> {
// true.
GURL GetUrl() const;
+ // ChunkCallback methods.
+ virtual void OnChunkAvailable();
+
private:
enum State {
STATE_NONE,
diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc
index 532c79f..a7b4fac 100644
--- a/net/spdy/spdy_stream_unittest.cc
+++ b/net/spdy/spdy_stream_unittest.cc
@@ -51,9 +51,9 @@ class TestSpdyStreamDelegate : public SpdyStream::Delegate {
ADD_FAILURE() << "OnSendBody should not be called";
return ERR_UNEXPECTED;
}
- virtual bool OnSendBodyComplete(int status) {
+ virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
ADD_FAILURE() << "OnSendBodyComplete should not be called";
- return true;
+ return ERR_UNEXPECTED;
}
virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response,
@@ -80,6 +80,7 @@ class TestSpdyStreamDelegate : public SpdyStream::Delegate {
callback_ = NULL;
callback->Run(OK);
}
+ virtual void set_chunk_callback(net::ChunkCallback *) {}
bool send_headers_completed() const { return send_headers_completed_; }
const linked_ptr<spdy::SpdyHeaderBlock>& response() const {
diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc
index 6342b16..7e1711a 100644
--- a/net/spdy/spdy_test_util.cc
+++ b/net/spdy/spdy_test_util.cc
@@ -672,6 +672,35 @@ spdy::SpdyFrame* ConstructSpdyPost(int64 content_length,
arraysize(post_headers));
}
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+ int extra_header_count) {
+ const char* post_headers[] = {
+ "method",
+ "POST",
+ "url",
+ "/",
+ "host",
+ "www.google.com",
+ "scheme",
+ "http",
+ "version",
+ "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ spdy::SYN_STREAM,
+ spdy::CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers));
+}
+
// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
// |extra_headers| are the extra header-value pairs, which typically
// will vary the most between calls.
diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h
index 7bd78b6..4cfed13 100644
--- a/net/spdy/spdy_test_util.h
+++ b/net/spdy/spdy_test_util.h
@@ -280,6 +280,13 @@ spdy::SpdyFrame* ConstructSpdyPost(int64 content_length,
const char* const extra_headers[],
int extra_header_count);
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+spdy::SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+ int extra_header_count);
+
// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
// |extra_headers| are the extra header-value pairs, which typically
// will vary the most between calls.