summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http')
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp5
-rw-r--r--netwerk/protocol/http/ASpdySession.h3
-rw-r--r--netwerk/protocol/http/Http2Session.cpp143
-rw-r--r--netwerk/protocol/http/Http2Session.h9
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp26
-rw-r--r--netwerk/protocol/http/Http2Stream.h6
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h5
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp233
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h9
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp69
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h4
11 files changed, 426 insertions, 86 deletions
diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp
index 6bd54c7c0..f22c326d1 100644
--- a/netwerk/protocol/http/ASpdySession.cpp
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -32,7 +32,8 @@ ASpdySession::~ASpdySession() = default;
ASpdySession *
ASpdySession::NewSpdySession(uint32_t version,
- nsISocketTransport *aTransport)
+ nsISocketTransport *aTransport,
+ bool attemptingEarlyData)
{
// This is a necko only interface, so we can enforce version
// requests as a precondition
@@ -46,7 +47,7 @@ ASpdySession::NewSpdySession(uint32_t version,
Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version);
- return new Http2Session(aTransport, version);
+ return new Http2Session(aTransport, version, attemptingEarlyData);
}
SpdyInformation::SpdyInformation()
diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h
index e116d423b..a3db61d15 100644
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -28,8 +28,9 @@ public:
virtual PRIntervalTime IdleTime() = 0;
virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0;
virtual void DontReuse() = 0;
+ virtual uint32_t SpdyVersion() = 0;
- static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *);
+ static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *, bool);
// MaybeReTunnel() is called by the connection manager when it cannot
// dispatch a tunneled transaction. That might be because the tunnels it
diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp
index a2721017d..e1440d6a4 100644
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -64,7 +64,7 @@ do { \
return NS_ERROR_ILLEGAL_VALUE; \
} while (0)
-Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version)
+Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version, bool attemptingEarlyData)
: mSocketTransport(aSocketTransport)
, mSegmentReader(nullptr)
, mSegmentWriter(nullptr)
@@ -112,6 +112,7 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t versio
, mWaitingForSettingsAck(false)
, mGoAwayOnPush(false)
, mUseH2Deps(false)
+ , mAttemptingEarlyData(attemptingEarlyData)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -501,6 +502,12 @@ Http2Session::SetWriteCallbacks()
void
Http2Session::RealignOutputQueue()
{
+ if (mAttemptingEarlyData) {
+ // We can't realign right now, because we may need what's in there if early
+ // data fails.
+ return;
+ }
+
mOutputQueueUsed -= mOutputQueueSent;
memmove(mOutputQueueBuffer.get(),
mOutputQueueBuffer.get() + mOutputQueueSent,
@@ -518,6 +525,14 @@ Http2Session::FlushOutputQueue()
uint32_t countRead;
uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
+ if (!avail && mAttemptingEarlyData) {
+ // This is kind of a hack, but there are cases where we'll have already
+ // written the data we want whlie doing early data, but we get called again
+ // with a reader, and we need to avoid calling the reader when there's
+ // nothing for it to read.
+ return;
+ }
+
rv = mSegmentReader->
OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
&countRead);
@@ -528,14 +543,18 @@ Http2Session::FlushOutputQueue()
if (NS_FAILED(rv))
return;
+ mOutputQueueSent += countRead;
+
+ if (mAttemptingEarlyData) {
+ return;
+ }
+
if (countRead == avail) {
mOutputQueueUsed = 0;
mOutputQueueSent = 0;
return;
}
- mOutputQueueSent += countRead;
-
// If the output queue is close to filling up and we have sent out a good
// chunk of data from the beginning then realign it.
@@ -555,6 +574,12 @@ Http2Session::DontReuse()
}
uint32_t
+Http2Session::SpdyVersion()
+{
+ return HTTP_VERSION_2;
+}
+
+uint32_t
Http2Session::GetWriteQueueSize()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2248,6 +2273,8 @@ Http2Session::OnTransportStatus(nsITransport* aTransport,
case NS_NET_STATUS_RESOLVED_HOST:
case NS_NET_STATUS_CONNECTING_TO:
case NS_NET_STATUS_CONNECTED_TO:
+ case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
+ case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
{
Http2Stream *target = mStreamIDHash.Get(1);
nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr;
@@ -2320,9 +2347,44 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
if (!stream) {
LOG3(("Http2Session %p could not identify a stream to write; suspending.",
this));
+ uint32_t availBeforeFlush = mOutputQueueUsed - mOutputQueueSent;
FlushOutputQueue();
+ uint32_t availAfterFlush = mOutputQueueUsed - mOutputQueueSent;
+ if (availBeforeFlush != availAfterFlush) {
+ LOG3(("Http2Session %p ResumeRecv After early flush in ReadSegments", this));
+ Unused << ResumeRecv();
+ }
SetWriteCallbacks();
- return NS_BASE_STREAM_WOULD_BLOCK;
+ if (mAttemptingEarlyData) {
+ // We can still try to send our preamble as early-data
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ }
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ uint32_t earlyDataUsed = 0;
+ if (mAttemptingEarlyData) {
+ if (!stream->Do0RTT()) {
+ LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X",
+ this, stream, stream->StreamID()));
+ FlushOutputQueue();
+ SetWriteCallbacks();
+ // We can still send our preamble
+ *countRead = mOutputQueueUsed - mOutputQueueSent;
+ return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (!m0RTTStreams.Contains(stream->StreamID())) {
+ m0RTTStreams.AppendElement(stream->StreamID());
+ }
+
+ // Need to adjust this to only take as much as we can fit in with the
+ // preamble/settings/priority stuff
+ count -= (mOutputQueueUsed - mOutputQueueSent);
+
+ // Keep track of this to add it into countRead later, as
+ // stream->ReadSegments will likely change the value of mOutputQueueUsed.
+ earlyDataUsed = mOutputQueueUsed - mOutputQueueSent;
}
LOG3(("Http2Session %p will write from Http2Stream %p 0x%X "
@@ -2331,6 +2393,13 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader,
rv = stream->ReadSegments(this, count, countRead);
+ if (earlyDataUsed) {
+ // Do this here because countRead could get reset somewhere down the rabbit
+ // hole of stream->ReadSegments, and we want to make sure we return the
+ // proper value to our caller.
+ *countRead += earlyDataUsed;
+ }
+
// Not every permutation of stream->ReadSegents produces data (and therefore
// tries to flush the output queue) - SENDING_FIN_STREAM can be an example
// of that. But we might still have old data buffered that would be good
@@ -2887,6 +2956,58 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
}
nsresult
+Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged)
+{
+ MOZ_ASSERT(mAttemptingEarlyData);
+ LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this,
+ aRestart, aAlpnChanged));
+
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for
+ // both arguments because as long as the alpn token stayed the same, we can
+ // just reuse what we have in our buffer to send instead of having to have
+ // the transaction rewind and read it all over again. We only need to rewind
+ // the transaction if we're switching to a new protocol, because our buffer
+ // won't get used in that case.
+ Http2Stream *stream = mStreamIDHash.Get(m0RTTStreams[i]);
+ if (stream) {
+ stream->Finish0RTT(aAlpnChanged, aAlpnChanged);
+ }
+ }
+
+ if (aRestart) {
+ // 0RTT failed
+ if (aAlpnChanged) {
+ // This is a slightly more involved case - we need to get all our streams/
+ // transactions back in the queue so they can restart as http/1
+
+ // These must be set this way to ensure we gracefully restart all streams
+ mGoAwayID = 0;
+ mCleanShutdown = true;
+
+ // Close takes care of the rest of our work for us. The reason code here
+ // doesn't matter, as we aren't actually going to send a GOAWAY frame, but
+ // we use NS_ERROR_NET_RESET as it's closest to the truth.
+ Close(NS_ERROR_NET_RESET);
+ } else {
+ // This is the easy case - early data failed, but we're speaking h2, so
+ // we just need to rewind to the beginning of the preamble and try again.
+ mOutputQueueSent = 0;
+ }
+ } else {
+ // 0RTT succeeded
+ // Make sure we look for any incoming data in repsonse to our early data.
+ ResumeRecv();
+ }
+
+ mAttemptingEarlyData = false;
+ m0RTTStreams.Clear();
+ RealignOutputQueue();
+
+ return NS_OK;
+}
+
+nsresult
Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream,
nsAHttpSegmentWriter * writer,
uint32_t count, uint32_t *countWritten)
@@ -3094,7 +3215,9 @@ Http2Session::Close(nsresult aReason)
} else {
goAwayReason = INTERNAL_ERROR;
}
- GenerateGoAway(goAwayReason);
+ if (!mAttemptingEarlyData) {
+ GenerateGoAway(goAwayReason);
+ }
mConnection = nullptr;
mSegmentReader = nullptr;
mSegmentWriter = nullptr;
@@ -3144,7 +3267,7 @@ Http2Session::OnReadSegment(const char *buf,
// If we can release old queued data then we can try and write the new
// data directly to the network without using the output queue at all
- if (mOutputQueueUsed)
+ if (mOutputQueueUsed && !mAttemptingEarlyData)
FlushOutputQueue();
if (!mOutputQueueUsed && mSegmentReader) {
@@ -3515,12 +3638,18 @@ Http2Session::ALPNCallback(nsISupports *securityInfo)
nsresult
Http2Session::ConfirmTLSProfile()
{
- if (mTLSProfileConfirmed)
+ if (mTLSProfileConfirmed) {
return NS_OK;
+ }
LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
this, mConnection.get()));
+ if (mAttemptingEarlyData) {
+ LOG3(("Http2Session::ConfirmTLSProfile %p temporarily passing due to early data\n", this));
+ return NS_OK;
+ }
+
if (!gHttpHandler->EnforceHttp2TlsProfile()) {
LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this));
mTLSProfileConfirmed = true;
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
index 60986381b..b4ddeb5ec 100644
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -43,12 +43,13 @@ public:
NS_DECL_NSAHTTPSEGMENTREADER
NS_DECL_NSAHTTPSEGMENTWRITER
- Http2Session(nsISocketTransport *, uint32_t version);
+ Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData);
bool AddStream(nsAHttpTransaction *, int32_t,
bool, nsIInterfaceRequestor *) override;
bool CanReuse() override { return !mShouldGoAway && !mClosed; }
bool RoomForMoreStreams() override;
+ uint32_t SpdyVersion() override;
// When the connection is active this is called up to once every 1 second
// return the interval (in seconds) that the connection next wants to
@@ -235,6 +236,8 @@ public:
// overload of nsAHttpTransaction
nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final;
nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final;
+ bool Do0RTT() override final { return true; }
+ nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) override final;
private:
@@ -492,6 +495,10 @@ private:
bool mUseH2Deps;
+ bool mAttemptingEarlyData;
+ // The ID(s) of the stream(s) that we are getting 0RTT data from.
+ nsTArray<uint32_t> m0RTTStreams;
+
private:
/// connect tunnels
void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp
index 5c562557c..f49c1f138 100644
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -70,6 +70,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
, mTotalSent(0)
, mTotalRead(0)
, mPushSource(nullptr)
+ , mAttempting0RTT(false)
, mIsTunnel(false)
, mPlainTextTunnel(false)
{
@@ -925,7 +926,9 @@ Http2Stream::TransmitFrame(const char *buf,
*countUsed += mTxStreamFrameSize;
}
- mSession->FlushOutputQueue();
+ if (!mAttempting0RTT) {
+ mSession->FlushOutputQueue();
+ }
// calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
@@ -1468,5 +1471,26 @@ Http2Stream::MapStreamToHttpConnection()
mTransaction->ConnectionInfo());
}
+// -----------------------------------------------------------------------------
+// mirror nsAHttpTransaction
+// -----------------------------------------------------------------------------
+
+bool
+Http2Stream::Do0RTT()
+{
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = true;
+ return mTransaction->Do0RTT();
+}
+
+nsresult
+Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged)
+{
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = false;
+ return mTransaction->Finish0RTT(aRestart, aAlpnChanged);
+}
+
+
} // namespace net
} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
index 452db5fe0..968461ee4 100644
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -154,6 +154,10 @@ public:
const nsACString &origin,
RefPtr<nsStandardURL> &url);
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored);
+
protected:
static void CreatePushHashKey(const nsCString &scheme,
const nsCString &hostHeader,
@@ -328,6 +332,8 @@ private:
// and flow control has not yet kicked in.
SimpleBuffer mSimpleBuffer;
+ bool mAttempting0RTT;
+
/// connect tunnels
public:
bool IsTunnel() { return mIsTunnel; }
diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h
index 7e42d191a..df998699a 100644
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -216,8 +216,11 @@ public:
// If aRestart parameter is true we need to restart the transaction,
// otherwise the erly-data has been accepted and we can continue the
// transaction.
+ // If aAlpnChanged is true (and we were assuming http/2), we'll need to take
+ // the transactions out of the session, rewind them all, and start them back
+ // over as http/1 transactions
// The function will return success or failure of the transaction restart.
- virtual nsresult Finish0RTT(bool aRestart) {
+ virtual nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) {
return NS_ERROR_NOT_IMPLEMENTED;
}
};
diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp
index 916d1249c..95a06fd5c 100644
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -87,6 +87,7 @@ nsHttpConnection::nsHttpConnection()
, mWaitingFor0RTTResponse(false)
, mContentBytesWritten0RTT(0)
, mEarlyDataNegotiated(false)
+ , mDid0RTTSpdy(false)
{
LOG(("Creating nsHttpConnection @%p\n", this));
@@ -158,16 +159,113 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info,
return NS_OK;
}
+nsresult
+nsHttpConnection::TryTakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &list)
+{
+ nsresult rv = mTransaction->TakeSubTransactions(list);
+
+ if (rv == NS_ERROR_ALREADY_OPENED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing\n"));
+ MOZ_ASSERT(false,
+ "TakeSubTransactions somehow called after "
+ "nsAHttpTransaction began processing");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ // Has the interface for TakeSubTransactions() changed?
+ LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
+ MOZ_ASSERT(false,
+ "unexpected result from "
+ "nsAHttpTransaction::TakeSubTransactions()");
+ mTransaction->Close(NS_ERROR_ABORT);
+ return rv;
+ }
+
+ return rv;
+}
+
+nsresult
+nsHttpConnection::MoveTransactionsToSpdy(nsresult status, nsTArray<RefPtr<nsAHttpTransaction> > &list)
+{
+ if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED
+ MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
+
+ // This is ok - treat mTransaction as a single real request.
+ // Wrap the old http transaction into the new spdy session
+ // as the first stream.
+ LOG(("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p "
+ "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
+ nsresult rv = AddTransaction(mTransaction, mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ int32_t count = list.Length();
+
+ LOG(("nsHttpConnection::MoveTransactionsToSpdy moving transaction list len=%d "
+ "into SpdySession %p\n", count, mSpdySession.get()));
+
+ if (!count) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+
+ for (int32_t index = 0; index < count; ++index) {
+ nsresult rv = AddTransaction(list[index], mPriority);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsHttpConnection::Start0RTTSpdy(uint8_t spdyVersion)
+{
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p]", this));
+ mDid0RTTSpdy = true;
+ mUsingSpdyVersion = spdyVersion;
+ mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport,
+ true);
+
+ nsTArray<RefPtr<nsAHttpTransaction> > list;
+ nsresult rv = TryTakeSubTransactions(list);
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed taking "
+ "subtransactions rv=%" PRIx32 , this, static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ rv = MoveTransactionsToSpdy(rv, list);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving "
+ "transactions rv=%" PRIx32 , this, static_cast<uint32_t>(rv)));
+ return;
+ }
+
+ mTransaction = mSpdySession;
+}
+
void
nsHttpConnection::StartSpdy(uint8_t spdyVersion)
{
- LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
+ LOG(("nsHttpConnection::StartSpdy [this=%p, mDid0RTTSpdy=%d]\n", this, mDid0RTTSpdy));
- MOZ_ASSERT(!mSpdySession);
+ MOZ_ASSERT(!mSpdySession || mDid0RTTSpdy);
mUsingSpdyVersion = spdyVersion;
mEverUsedSpdy = true;
- mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport);
+
+ if (!mDid0RTTSpdy) {
+ mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport,
+ false);
+ }
if (!mReportedSpdy) {
mReportedSpdy = true;
@@ -185,27 +283,13 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion)
// pack them all into a new spdy session.
nsTArray<RefPtr<nsAHttpTransaction> > list;
- nsresult rv = mTransaction->TakeSubTransactions(list);
-
- if (rv == NS_ERROR_ALREADY_OPENED) {
- // Has the interface for TakeSubTransactions() changed?
- LOG(("TakeSubTransactions somehow called after "
- "nsAHttpTransaction began processing\n"));
- MOZ_ASSERT(false,
- "TakeSubTransactions somehow called after "
- "nsAHttpTransaction began processing");
- mTransaction->Close(NS_ERROR_ABORT);
- return;
- }
+ nsresult rv = NS_OK;
+ if (!mDid0RTTSpdy) {
+ rv = TryTakeSubTransactions(list);
- if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
- // Has the interface for TakeSubTransactions() changed?
- LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
- MOZ_ASSERT(false,
- "unexpected result from "
- "nsAHttpTransaction::TakeSubTransactions()");
- mTransaction->Close(NS_ERROR_ABORT);
- return;
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
+ return;
+ }
}
if (NeedSpdyTunnel()) {
@@ -227,35 +311,11 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion)
mConnInfo = wildCardProxyCi;
}
- if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED
- MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
-
- // This is ok - treat mTransaction as a single real request.
- // Wrap the old http transaction into the new spdy session
- // as the first stream.
- LOG(("nsHttpConnection::StartSpdy moves single transaction %p "
- "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
- rv = AddTransaction(mTransaction, mPriority);
+ if (!mDid0RTTSpdy) {
+ rv = MoveTransactionsToSpdy(rv, list);
if (NS_FAILED(rv)) {
return;
}
- } else {
- int32_t count = list.Length();
-
- LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
- "into SpdySession %p\n", count, mSpdySession.get()));
-
- if (!count) {
- mTransaction->Close(NS_ERROR_ABORT);
- return;
- }
-
- for (int32_t index = 0; index < count; ++index) {
- rv = AddTransaction(list[index], mPriority);
- if (NS_FAILED(rv)) {
- return;
- }
- }
}
// Disable TCP Keepalives - use SPDY ping instead.
@@ -313,6 +373,13 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
if (NS_FAILED(rv))
goto npnComplete;
+ if (!m0RTTChecked) {
+ // We reuse m0RTTChecked. We want to send this status only once.
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_TLS_HANDSHAKE_STARTING,
+ 0);
+ }
+
rv = ssl->GetNegotiatedNPN(negotiatedNPN);
if (!m0RTTChecked && (rv == NS_ERROR_NOT_CONNECTED) &&
!mConnInfo->UsingProxy()) {
@@ -321,8 +388,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
// (AlpnEarlySelection), we are using HTTP/1, and the request data can
// be safely retried.
m0RTTChecked = true;
- nsAutoCString earlyNegotiatedNPN;
- nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN);
if (NS_FAILED(rvEarlyAlpn)) {
// if ssl->DriveHandshake() has never been called the value
// for AlpnEarlySelection is still not set. So call it here and
@@ -339,7 +405,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
// Check NegotiatedNPN first.
rv = ssl->GetNegotiatedNPN(negotiatedNPN);
if (rv == NS_ERROR_NOT_CONNECTED) {
- rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN);
}
}
@@ -349,19 +415,26 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
mEarlyDataNegotiated = false;
} else {
LOG(("nsHttpConnection::EnsureNPNComplete %p -"
- "early selected alpn: %s", this, earlyNegotiatedNPN.get()));
+ "early selected alpn: %s", this, mEarlyNegotiatedALPN.get()));
uint32_t infoIndex;
const SpdyInformation *info = gHttpHandler->SpdyInfo();
- // We are doing 0RTT only with Http/1 right now!
- if (NS_FAILED(info->GetNPNIndex(earlyNegotiatedNPN, &infoIndex))) {
+ if (NS_FAILED(info->GetNPNIndex(mEarlyNegotiatedALPN, &infoIndex))) {
+ // This is the HTTP/1 case.
// Check if early-data is allowed for this transaction.
if (mTransaction->Do0RTT()) {
LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We "
- "can do 0RTT!", this));
+ "can do 0RTT (http/1)!", this));
mWaitingFor0RTTResponse = true;
}
- mEarlyDataNegotiated = true;
+ } else {
+ // We have h2, we can at least 0-RTT the preamble and opening
+ // SETTINGS, etc, and maybe some of the first request
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - Starting "
+ "0RTT for h2!", this));
+ mWaitingFor0RTTResponse = true;
+ Start0RTTSpdy(info->Version[infoIndex]);
}
+ mEarlyDataNegotiated = true;
}
}
@@ -391,16 +464,17 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
mTLSFilter ? " [Double Tunnel]" : ""));
- bool ealyDataAccepted = false;
+ bool earlyDataAccepted = false;
if (mWaitingFor0RTTResponse) {
// Check if early data has been accepted.
- rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted);
+ rv = ssl->GetEarlyDataAccepted(&earlyDataAccepted);
LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
- "that was sent during 0RTT %s been accepted.",
- this, ealyDataAccepted ? "has" : "has not"));
+ "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].",
+ this, earlyDataAccepted ? "has" : "has not", static_cast<uint32_t>(rv)));
if (NS_FAILED(rv) ||
- NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) {
+ NS_FAILED(mTransaction->Finish0RTT(!earlyDataAccepted, negotiatedNPN != mEarlyNegotiatedALPN))) {
+ LOG(("nsHttpConection::EnsureNPNComplete [this=%p] closing transaction %p", this, mTransaction.get()));
mTransaction->Close(NS_ERROR_NET_RESET);
goto npnComplete;
}
@@ -416,16 +490,17 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
: TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
if (mWaitingFor0RTTResponse) {
Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
- ealyDataAccepted);
+ earlyDataAccepted);
}
- if (ealyDataAccepted) {
+ if (earlyDataAccepted) {
Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
mContentBytesWritten0RTT);
}
}
mWaitingFor0RTTResponse = false;
- if (!ealyDataAccepted) {
+ if (!earlyDataAccepted) {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] early data not accepted", this));
uint32_t infoIndex;
const SpdyInformation *info = gHttpHandler->SpdyInfo();
if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
@@ -435,21 +510,45 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes "
"has been sent during 0RTT.", this, mContentBytesWritten0RTT));
mContentBytesWritten = mContentBytesWritten0RTT;
+ if (mSpdySession) {
+ // We had already started 0RTT-spdy, now we need to fully set up
+ // spdy, since we know we're sticking with it.
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - finishing "
+ "StartSpdy for 0rtt spdy session %p", this, mSpdySession.get()));
+ StartSpdy(mSpdySession->SpdyVersion());
+ }
}
Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
}
npnComplete:
- LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] setting complete to true", this));
mNPNComplete = true;
+
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_TLS_HANDSHAKE_ENDED,
+ 0);
if (mWaitingFor0RTTResponse) {
+ // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
mWaitingFor0RTTResponse = false;
- if (NS_FAILED(mTransaction->Finish0RTT(true))) {
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] 0rtt failed", this));
+ if (NS_FAILED(mTransaction->Finish0RTT(true, negotiatedNPN != mEarlyNegotiatedALPN))) {
mTransaction->Close(NS_ERROR_NET_RESET);
}
mContentBytesWritten0RTT = 0;
}
+
+ if (mDid0RTTSpdy && negotiatedNPN != mEarlyNegotiatedALPN) {
+ // Reset the work done by Start0RTTSpdy
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] resetting Start0RTTSpdy", this));
+ mUsingSpdyVersion = 0;
+ mTransaction = nullptr;
+ mSpdySession = nullptr;
+ // We have to reset this here, just in case we end up starting spdy again,
+ // so it can actually do everything it needs to do.
+ mDid0RTTSpdy = false;
+ }
return true;
}
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
index 783b080b3..1f8500d75 100644
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -248,6 +248,13 @@ private:
// Start the Spdy transaction handler when NPN indicates spdy/*
void StartSpdy(uint8_t versionLevel);
+ // Like the above, but do the bare minimum to do 0RTT data, so we can back
+ // it out, if necessary
+ void Start0RTTSpdy(uint8_t versionLevel);
+
+ // Helpers for Start*Spdy
+ nsresult TryTakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction> > &list);
+ nsresult MoveTransactionsToSpdy(nsresult status, nsTArray<RefPtr<nsAHttpTransaction> > &list);
// Directly Add a transaction to an active connection for SPDY
nsresult AddTransaction(nsAHttpTransaction *, int32_t);
@@ -370,6 +377,8 @@ private:
// the handsake.
int64_t mContentBytesWritten0RTT;
bool mEarlyDataNegotiated; //Only used for telemetry
+ nsCString mEarlyNegotiatedALPN;
+ bool mDid0RTTSpdy;
};
} // namespace net
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
index ee3a88489..bc182c6cd 100644
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -39,6 +39,8 @@
#include "nsIOService.h"
#include "nsIRequestContext.h"
#include "nsIHttpAuthenticator.h"
+#include "NSSErrorsService.h"
+#include "sslerr.h"
#include <algorithm>
#ifdef MOZ_WIDGET_GONK
@@ -144,6 +146,7 @@ nsHttpTransaction::nsHttpTransaction()
, mIsInIsolatedMozBrowser(false)
, mClassOfService(0)
, m0RTTInProgress(false)
+ , mTransportStatus(NS_OK)
{
LOG(("Creating nsHttpTransaction @%p\n", this));
gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
@@ -550,6 +553,50 @@ nsHttpTransaction::OnTransportStatus(nsITransport* transport,
LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%lld]\n",
this, status, progress));
+ // A transaction can given to multiple HalfOpen sockets (this is a bug in
+ // nsHttpConnectionMgr). We are going to fix it here as a work around to be
+ // able to uplift it.
+ switch(status) {
+ case NS_NET_STATUS_RESOLVING_HOST:
+ if (mTransportStatus != NS_OK) {
+ LOG(("nsHttpTransaction::OnSocketStatus - ignore socket events "
+ "from backup transport"));
+ return;
+ }
+ break;
+ case NS_NET_STATUS_RESOLVED_HOST:
+ if (mTransportStatus != NS_NET_STATUS_RESOLVING_HOST &&
+ mTransportStatus != NS_OK) {
+ LOG(("nsHttpTransaction::OnSocketStatus - ignore socket events "
+ "from backup transport"));
+ return;
+ }
+ break;
+ case NS_NET_STATUS_CONNECTING_TO:
+ if (mTransportStatus != NS_NET_STATUS_RESOLVING_HOST &&
+ mTransportStatus != NS_NET_STATUS_RESOLVED_HOST &&
+ mTransportStatus != NS_OK) {
+ LOG(("nsHttpTransaction::OnSocketStatus - ignore socket events "
+ "from backup transport"));
+ return;
+ }
+ break;
+ case NS_NET_STATUS_CONNECTED_TO:
+ if (mTransportStatus != NS_NET_STATUS_RESOLVING_HOST &&
+ mTransportStatus != NS_NET_STATUS_RESOLVED_HOST &&
+ mTransportStatus != NS_NET_STATUS_CONNECTING_TO &&
+ mTransportStatus != NS_OK) {
+ LOG(("nsHttpTransaction::OnSocketStatus - ignore socket events "
+ "from backup transport"));
+ return;
+ }
+ break;
+ default:
+ LOG(("nsHttpTransaction::OnSocketStatus - a new event"));
+ }
+
+ mTransportStatus = status;
+
if (status == NS_NET_STATUS_CONNECTED_TO ||
status == NS_NET_STATUS_WAITING_FOR) {
nsISocketTransport *socketTransport =
@@ -574,7 +621,9 @@ nsHttpTransaction::OnTransportStatus(nsITransport* transport,
} else if (status == NS_NET_STATUS_CONNECTING_TO) {
SetConnectStart(TimeStamp::Now());
} else if (status == NS_NET_STATUS_CONNECTED_TO) {
- SetConnectEnd(TimeStamp::Now());
+ SetConnectEnd(TimeStamp::Now(), true);
+ } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
+ SetConnectEnd(TimeStamp::Now(), false);
}
}
@@ -998,7 +1047,9 @@ nsHttpTransaction::Close(nsresult reason)
// connection. It will break that connection and also confuse the channel's
// auth provider, beliving the cached credentials are wrong and asking for
// the password mistakenly again from the user.
- if ((reason == NS_ERROR_NET_RESET || reason == NS_OK) &&
+ if ((reason == NS_ERROR_NET_RESET ||
+ reason == NS_OK ||
+ reason == psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) &&
(!(mCaps & NS_HTTP_STICKY_CONNECTION) || (mCaps & NS_HTTP_CONNECTION_RESTARTABLE))) {
if (mForceRestart && NS_SUCCEEDED(Restart())) {
@@ -1027,9 +1078,10 @@ nsHttpTransaction::Close(nsresult reason)
bool reallySentData =
mSentData && (!mConnection || mConnection->BytesWritten());
- if (!mReceivedData &&
+ if (reason == psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ (!mReceivedData &&
((mRequestHead && mRequestHead->IsSafeMethod()) ||
- !reallySentData || connReused)) {
+ !reallySentData || connReused))) {
// if restarting fails, then we must proceed to close the pipe,
// which will notify the channel that the transaction failed.
@@ -1327,6 +1379,8 @@ nsHttpTransaction::Restart()
}
}
+ mTransportStatus = NS_OK;
+
return gHttpHandler->InitiateTransaction(this, mPriority);
}
@@ -2440,8 +2494,9 @@ nsHttpTransaction::Do0RTT()
}
nsresult
-nsHttpTransaction::Finish0RTT(bool aRestart)
+nsHttpTransaction::Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */)
{
+ LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart, aAlpnChanged));
MOZ_ASSERT(m0RTTInProgress);
m0RTTInProgress = false;
if (aRestart) {
@@ -2453,6 +2508,10 @@ nsHttpTransaction::Finish0RTT(bool aRestart)
} else {
return NS_ERROR_FAILURE;
}
+ } else if (!mConnected) {
+ // this is code that was skipped in ::ReadSegments while in 0RTT
+ mConnected = true;
+ mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
}
return NS_OK;
}
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
index ab0b267a7..788d9c7b3 100644
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -168,7 +168,7 @@ public:
int64_t GetTransferSize() { return mTransferSize; }
bool Do0RTT() override;
- nsresult Finish0RTT(bool aRestart) override;
+ nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override;
private:
friend class DeleteHttpTransaction;
virtual ~nsHttpTransaction();
@@ -479,6 +479,8 @@ private:
NetAddr mPeerAddr;
bool m0RTTInProgress;
+
+ nsresult mTransportStatus;
};
} // namespace net