summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-24 06:04:12 +0000
committerakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-24 06:04:12 +0000
commit57d2dfa3c2de8b33fc5ca2555897f70c8c769429 (patch)
tree841fd25d0ed79b34cbde5a7438ba52d33a542c22 /net
parent5225cdaa6b88b35a663f2b8fec47e921959b087f (diff)
downloadchromium_src-57d2dfa3c2de8b33fc5ca2555897f70c8c769429.zip
chromium_src-57d2dfa3c2de8b33fc5ca2555897f70c8c769429.tar.gz
chromium_src-57d2dfa3c2de8b33fc5ca2555897f70c8c769429.tar.bz2
Use a dummy HttpStream for proxy redirects
We synthesize the response headers and ignore the body anyway, so there's no point to using a real one. This lets us avoid having to call SetDelegate() twice on a SpdyStream, so remove support for that. BUG=252433 Review URL: https://chromiumcodereview.appspot.com/17068018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208144 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_network_transaction_unittest.cc1
-rw-r--r--net/http/http_proxy_client_socket.cc15
-rw-r--r--net/http/http_proxy_client_socket.h5
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy2_unittest.cc1
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy3_unittest.cc1
-rw-r--r--net/http/proxy_connect_redirect_http_stream.cc122
-rw-r--r--net/http/proxy_connect_redirect_http_stream.h72
-rw-r--r--net/net.gyp2
-rw-r--r--net/spdy/spdy_http_stream.cc12
-rw-r--r--net/spdy/spdy_http_stream.h12
-rw-r--r--net/spdy/spdy_http_stream_unittest.cc11
-rw-r--r--net/spdy/spdy_proxy_client_socket.cc18
-rw-r--r--net/spdy/spdy_proxy_client_socket.h5
-rw-r--r--net/spdy/spdy_stream.cc8
-rw-r--r--net/spdy/spdy_stream.h3
15 files changed, 253 insertions, 35 deletions
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 54f58a7..fa36e6b 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -5722,6 +5722,7 @@ TEST_P(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaSpdyProxy) {
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
MockWrite data_writes[] = {
CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
+ CreateMockWrite(*goaway.get(), 3, SYNCHRONOUS),
};
static const char* const kExtraHeaders[] = {
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
index 9352312..81f6ff4 100644
--- a/net/http/http_proxy_client_socket.cc
+++ b/net/http/http_proxy_client_socket.cc
@@ -19,6 +19,7 @@
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_stream_parser.h"
+#include "net/http/proxy_connect_redirect_http_stream.h"
#include "net/socket/client_socket_handle.h"
namespace net {
@@ -51,6 +52,7 @@ HttpProxyClientSocket::HttpProxyClientSocket(
using_spdy_(using_spdy),
protocol_negotiated_(protocol_negotiated),
is_https_proxy_(is_https_proxy),
+ redirect_has_load_timing_info_(false),
net_log_(transport_socket->socket()->NetLog()) {
// Synthesize the bits of a request that we actually use.
request_.url = request_url;
@@ -99,8 +101,8 @@ const HttpResponseInfo* HttpProxyClientSocket::GetConnectResponseInfo() const {
}
HttpStream* HttpProxyClientSocket::CreateConnectResponseStream() {
- return new HttpBasicStream(transport_.release(),
- http_stream_parser_.release(), false);
+ return new ProxyConnectRedirectHttpStream(
+ redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL);
}
@@ -466,8 +468,15 @@ int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
// sanitize the response. This still allows a rogue HTTPS proxy to
// redirect an HTTPS site load to a similar-looking site, but no longer
// allows it to impersonate the site the user requested.
- if (is_https_proxy_ && SanitizeProxyRedirect(&response_, request_.url))
+ if (is_https_proxy_ && SanitizeProxyRedirect(&response_, request_.url)) {
+ bool is_connection_reused = http_stream_parser_->IsConnectionReused();
+ redirect_has_load_timing_info_ =
+ transport_->GetLoadTimingInfo(
+ is_connection_reused, &redirect_load_timing_info_);
+ transport_.reset();
+ http_stream_parser_.reset();
return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
+ }
// We're not using an HTTPS proxy, or we couldn't sanitize the redirect.
LogBlockedTunnelResponse();
diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h
index 6cab666..fca054f 100644
--- a/net/http/http_proxy_client_socket.h
+++ b/net/http/http_proxy_client_socket.h
@@ -11,6 +11,7 @@
#include "base/memory/ref_counted.h"
#include "net/base/completion_callback.h"
#include "net/base/host_port_pair.h"
+#include "net/base/load_timing_info.h"
#include "net/base/net_log.h"
#include "net/http/http_auth_controller.h"
#include "net/http/http_request_headers.h"
@@ -158,6 +159,10 @@ class HttpProxyClientSocket : public ProxyClientSocket {
std::string request_line_;
HttpRequestHeaders request_headers_;
+ // Used only for redirects.
+ bool redirect_has_load_timing_info_;
+ LoadTimingInfo redirect_load_timing_info_;
+
const BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocket);
diff --git a/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
index f6558ed..57f3668 100644
--- a/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc
@@ -555,6 +555,7 @@ TEST_P(HttpProxyClientSocketPoolSpdy2Test, TunnelSetupRedirect) {
MockWrite spdy_writes[] = {
CreateMockWrite(*req, 0, ASYNC),
+ CreateMockWrite(*rst, 3, ASYNC),
};
const char* const responseHeaders[] = {
diff --git a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
index 167cd3f..a1436ef 100644
--- a/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
+++ b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc
@@ -556,6 +556,7 @@ TEST_P(HttpProxyClientSocketPoolSpdy3Test, TunnelSetupRedirect) {
MockWrite spdy_writes[] = {
CreateMockWrite(*req, 0, ASYNC),
+ CreateMockWrite(*rst, 3, ASYNC),
};
const char* const responseHeaders[] = {
diff --git a/net/http/proxy_connect_redirect_http_stream.cc b/net/http/proxy_connect_redirect_http_stream.cc
new file mode 100644
index 0000000..f30f33c
--- /dev/null
+++ b/net/http/proxy_connect_redirect_http_stream.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 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/http/proxy_connect_redirect_http_stream.h"
+
+#include <cstddef>
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+ProxyConnectRedirectHttpStream::ProxyConnectRedirectHttpStream(
+ LoadTimingInfo* load_timing_info)
+ : has_load_timing_info_(load_timing_info != NULL) {
+ if (has_load_timing_info_)
+ load_timing_info_ = *load_timing_info;
+}
+
+ProxyConnectRedirectHttpStream::~ProxyConnectRedirectHttpStream() {}
+
+int ProxyConnectRedirectHttpStream::InitializeStream(
+ const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+int ProxyConnectRedirectHttpStream::SendRequest(
+ const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+int ProxyConnectRedirectHttpStream::ReadResponseHeaders(
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+const HttpResponseInfo*
+ProxyConnectRedirectHttpStream::GetResponseInfo() const {
+ NOTREACHED();
+ return NULL;
+}
+
+int ProxyConnectRedirectHttpStream::ReadResponseBody(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+void ProxyConnectRedirectHttpStream::Close(bool not_reusable) {}
+
+bool ProxyConnectRedirectHttpStream::IsResponseBodyComplete() const {
+ NOTREACHED();
+ return true;
+}
+
+bool ProxyConnectRedirectHttpStream::CanFindEndOfResponse() const {
+ return true;
+}
+
+bool ProxyConnectRedirectHttpStream::IsConnectionReused() const {
+ NOTREACHED();
+ return false;
+}
+
+void ProxyConnectRedirectHttpStream::SetConnectionReused() {
+ NOTREACHED();
+}
+
+bool ProxyConnectRedirectHttpStream::IsConnectionReusable() const {
+ NOTREACHED();
+ return false;
+}
+
+bool ProxyConnectRedirectHttpStream::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ if (!has_load_timing_info_)
+ return false;
+
+ *load_timing_info = load_timing_info_;
+ return true;
+}
+
+void ProxyConnectRedirectHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+ NOTREACHED();
+}
+
+void ProxyConnectRedirectHttpStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ NOTREACHED();
+}
+
+bool ProxyConnectRedirectHttpStream::IsSpdyHttpStream() const {
+ NOTREACHED();
+ return false;
+}
+
+void ProxyConnectRedirectHttpStream::Drain(HttpNetworkSession* session) {
+ NOTREACHED();
+}
+
+UploadProgress ProxyConnectRedirectHttpStream::GetUploadProgress() const {
+ NOTREACHED();
+ return UploadProgress();
+}
+
+HttpStream* ProxyConnectRedirectHttpStream::RenewStreamForAuth() {
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace
diff --git a/net/http/proxy_connect_redirect_http_stream.h b/net/http/proxy_connect_redirect_http_stream.h
new file mode 100644
index 0000000..f39ec76
--- /dev/null
+++ b/net/http/proxy_connect_redirect_http_stream.h
@@ -0,0 +1,72 @@
+// Copyright 2013 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.
+
+#ifndef NET_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
+#define NET_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/load_timing_info.h"
+#include "net/http/http_stream.h"
+
+namespace net {
+
+// A dummy HttpStream with no body used when a redirect is returned
+// from a proxy.
+class ProxyConnectRedirectHttpStream : public HttpStream {
+ public:
+ // |load_timing_info| is the info that should be returned by
+ // GetLoadTimingInfo(), or NULL if there is none. Does not take
+ // ownership of |load_timing_info|.
+ explicit ProxyConnectRedirectHttpStream(LoadTimingInfo* load_timing_info);
+ virtual ~ProxyConnectRedirectHttpStream();
+
+ // All functions below are expected not to be called except for the
+ // marked one.
+
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual int ReadResponseBody(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // This function may be called.
+ virtual void Close(bool not_reusable) OVERRIDE;
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+
+ // This function may be called.
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+
+ virtual bool IsConnectionReused() const OVERRIDE;
+ virtual void SetConnectionReused() OVERRIDE;
+ virtual bool IsConnectionReusable() const OVERRIDE;
+
+ // This function may be called.
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+
+ private:
+ bool has_load_timing_info_;
+ LoadTimingInfo load_timing_info_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
diff --git a/net/net.gyp b/net/net.gyp
index 1d6d0d0..ee09c60 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -605,6 +605,8 @@
'http/partial_data.h',
'http/proxy_client_socket.h',
'http/proxy_client_socket.cc',
+ 'http/proxy_connect_redirect_http_stream.h',
+ 'http/proxy_connect_redirect_http_stream.cc',
'http/transport_security_state.cc',
'http/transport_security_state.h',
'http/transport_security_state_static.h',
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc
index e10aaa6..9241424 100644
--- a/net/spdy/spdy_http_stream.cc
+++ b/net/spdy/spdy_http_stream.cc
@@ -25,8 +25,7 @@
namespace net {
-SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session,
- bool direct)
+SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session, bool direct)
: weak_factory_(this),
spdy_session_(spdy_session),
stream_closed_(false),
@@ -39,13 +38,8 @@ SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session,
request_body_buf_size_(0),
buffered_read_callback_pending_(false),
more_read_data_pending_(false),
- direct_(direct) {}
-
-void SpdyHttpStream::InitializeWithExistingStream(
- const base::WeakPtr<SpdyStream>& spdy_stream) {
- stream_ = spdy_stream;
- stream_->SetDelegate(this);
- response_headers_received_ = true;
+ direct_(direct) {
+ DCHECK(spdy_session_);
}
SpdyHttpStream::~SpdyHttpStream() {
diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h
index 1ad8576..1ce0290 100644
--- a/net/spdy/spdy_http_stream.h
+++ b/net/spdy/spdy_http_stream.h
@@ -30,18 +30,10 @@ class UploadDataStream;
class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
public HttpStream {
public:
- // |spdy_session| may be NULL, but in that case some functions must
- // not be called (see comments below).
+ // |spdy_session| must not be NULL.
SpdyHttpStream(SpdySession* spdy_session, bool direct);
virtual ~SpdyHttpStream();
- // Initializes this SpdyHttpStream by wrapping an existing
- // SpdyStream. In particular, this must be called instead of
- // InitializeStream() if a NULL SpdySession was passed into the
- // constructor.
- void InitializeWithExistingStream(
- const base::WeakPtr<SpdyStream>& spdy_stream);
-
SpdyStream* stream() { return stream_.get(); }
// Cancels any callbacks from being invoked and deletes the stream.
@@ -49,8 +41,6 @@ class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
// HttpStream implementation.
- // Must not be called if a NULL SpdySession was passed into the
- // constructor.
virtual int InitializeStream(const HttpRequestInfo* request_info,
RequestPriority priority,
const BoundNetLog& net_log,
diff --git a/net/spdy/spdy_http_stream_unittest.cc b/net/spdy/spdy_http_stream_unittest.cc
index 88b279f..5c29484 100644
--- a/net/spdy/spdy_http_stream_unittest.cc
+++ b/net/spdy/spdy_http_stream_unittest.cc
@@ -176,7 +176,16 @@ TEST_P(SpdyHttpStreamTest, GetUploadProgressBeforeInitialization) {
if (GetParam() > kProtoSPDY3)
return;
- SpdyHttpStream stream(NULL, false);
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), NULL, 0, host_port_pair));
+
+ SpdyHttpStream stream(session_, false);
UploadProgress progress = stream.GetUploadProgress();
EXPECT_EQ(0u, progress.size());
EXPECT_EQ(0u, progress.position());
diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc
index 2c8ea6f..641637a 100644
--- a/net/spdy/spdy_proxy_client_socket.cc
+++ b/net/spdy/spdy_proxy_client_socket.cc
@@ -19,6 +19,7 @@
#include "net/http/http_auth_cache.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_response_headers.h"
+#include "net/http/proxy_connect_redirect_http_stream.h"
#include "net/spdy/spdy_http_utils.h"
namespace net {
@@ -42,6 +43,8 @@ SpdyProxyClientSocket::SpdyProxyClientSocket(
auth_handler_factory)),
user_buffer_len_(0),
write_buffer_len_(0),
+ was_ever_used_(false),
+ redirect_has_load_timing_info_(false),
weak_factory_(this),
net_log_(BoundNetLog::Make(spdy_stream->net_log().net_log(),
NetLog::SOURCE_PROXY_CLIENT_SOCKET)) {
@@ -98,8 +101,8 @@ NextProto SpdyProxyClientSocket::GetProtocolNegotiated() const {
}
HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() {
- DCHECK(response_stream_.get());
- return response_stream_.release();
+ return new ProxyConnectRedirectHttpStream(
+ redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL);
}
// Sends a SYN_STREAM frame to the proxy with a CONNECT request
@@ -405,14 +408,9 @@ int SpdyProxyClientSocket::DoReadReplyComplete(int result) {
// Try to return a sanitized response so we can follow auth redirects.
// If we can't, fail the tunnel connection.
if (SanitizeProxyRedirect(&response_, request_.url)) {
- // Immediately hand off our SpdyStream to a newly created
- // SpdyHttpStream so that any subsequent SpdyFrames are processed in
- // the context of the HttpStream, not the socket.
- DCHECK(spdy_stream_.get());
- base::WeakPtr<SpdyStream> stream = spdy_stream_;
- spdy_stream_.reset();
- response_stream_.reset(new SpdyHttpStream(NULL, false));
- response_stream_->InitializeWithExistingStream(stream);
+ redirect_has_load_timing_info_ =
+ spdy_stream_->GetLoadTimingInfo(&redirect_load_timing_info_);
+ spdy_stream_->DetachDelegate();
next_state_ = STATE_DISCONNECTED;
return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
} else {
diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h
index d80e1d8..be0ed69 100644
--- a/net/spdy/spdy_proxy_client_socket.h
+++ b/net/spdy/spdy_proxy_client_socket.h
@@ -13,6 +13,7 @@
#include "base/memory/weak_ptr.h"
#include "net/base/completion_callback.h"
#include "net/base/host_port_pair.h"
+#include "net/base/load_timing_info.h"
#include "net/base/net_log.h"
#include "net/http/http_auth_controller.h"
#include "net/http/http_request_headers.h"
@@ -159,7 +160,9 @@ class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket,
// True if the transport socket has ever sent data.
bool was_ever_used_;
- scoped_ptr<SpdyHttpStream> response_stream_;
+ // Used only for redirects.
+ bool redirect_has_load_timing_info_;
+ LoadTimingInfo redirect_load_timing_info_;
base::WeakPtrFactory<SpdyProxyClientSocket> weak_factory_;
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 190e175..2c2749b 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -125,6 +125,7 @@ SpdyStream::~SpdyStream() {
}
void SpdyStream::SetDelegate(Delegate* delegate) {
+ CHECK(!delegate_);
CHECK(delegate);
delegate_ = delegate;
@@ -659,6 +660,13 @@ GURL SpdyStream::GetUrl() const {
type_ == SPDY_PUSH_STREAM);
}
+bool SpdyStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ if (stream_id_ == 0)
+ return false;
+
+ return session_->GetLoadTimingInfo(stream_id_, load_timing_info);
+}
+
void SpdyStream::OnGetDomainBoundCertComplete(int result) {
DCHECK_EQ(io_state_, STATE_GET_DOMAIN_BOUND_CERT_COMPLETE);
DoLoop(result);
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index 28eaf40..b410226 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -32,6 +32,7 @@ namespace net {
class AddressList;
class IPEndPoint;
+struct LoadTimingInfo;
class SSLCertRequestInfo;
class SSLInfo;
class SpdySession;
@@ -325,6 +326,8 @@ class NET_EXPORT_PRIVATE SpdyStream {
int response_status() const { return response_status_; }
+ bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const;
+
// Returns true if the URL for this stream is known.
bool HasUrl() const;