--- a/netwerk/protocol/http/ASpdySession.cpp
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -27,31 +27,32 @@ namespace net {
ASpdySession::ASpdySession()
{
}
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
MOZ_ASSERT(version == HTTP_VERSION_2,
"Unsupported spdy version");
// Don't do a runtime check of IsSpdyV?Enabled() here because pref value
// may have changed since starting negotiation. The selected protocol comes
// from a list provided in the SERVER HELLO filtered by our acceptable
// versions, so there is no risk of the server ignoring our prefs.
Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version);
- return new Http2Session(aTransport, version);
+ return new Http2Session(aTransport, version, attemptingEarlyData);
}
SpdyInformation::SpdyInformation()
{
// highest index of enabled protocols is the
// most preferred for ALPN negotiaton
Version[0] = HTTP_VERSION_2;
VersionString[0] = NS_LITERAL_CSTRING("h2");
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -23,18 +23,19 @@ public:
virtual bool AddStream(nsAHttpTransaction *, int32_t,
bool, nsIInterfaceRequestor *) = 0;
virtual bool CanReuse() = 0;
virtual bool RoomForMoreStreams() = 0;
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
// expects to see are dead (and we may or may not be able to make more),
// or it might just need to wait longer for one of them to become free.
//
// return true if the session takes back ownership of the transaction from
// the connection manager.
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -59,17 +59,17 @@ const uint8_t Http2Session::kMagicHello[
};
#define RETURN_SESSION_ERROR(o,x) \
do { \
(o)->mGoAwayReason = (x); \
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)
, mNextStreamID(3) // 1 is reserved for Updgrade handshakes
, mLastPushedID(0)
, mConcurrentHighWater(0)
, mDownstreamState(BUFFERING_OPENING_SETTINGS)
, mInputFrameBufferSize(kDefaultBufferSize)
@@ -107,16 +107,17 @@ Http2Session::Http2Session(nsISocketTran
, mOutputQueueUsed(0)
, mOutputQueueSent(0)
, mLastReadEpoch(PR_IntervalNow())
, mPingSentEpoch(0)
, mPreviousUsed(false)
, mWaitingForSettingsAck(false)
, mGoAwayOnPush(false)
, mUseH2Deps(false)
+ , mAttemptingEarlyData(attemptingEarlyData)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
static uint64_t sSerial;
mSerial = ++sSerial;
LOG3(("Http2Session::Http2Session %p serial=0x%X\n", this, mSerial));
@@ -496,16 +497,22 @@ Http2Session::SetWriteCallbacks()
{
if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
mConnection->ResumeSend();
}
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,
mOutputQueueUsed);
mOutputQueueSent = 0;
}
void
@@ -513,34 +520,46 @@ Http2Session::FlushOutputQueue()
{
if (!mSegmentReader || !mOutputQueueUsed)
return;
nsresult rv;
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);
LOG3(("Http2Session::FlushOutputQueue %p sz=%d rv=%x actual=%d",
this, avail, rv, countRead));
// Dont worry about errors on write, we will pick this up as a read error too
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.
if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
RealignOutputQueue();
}
}
@@ -550,16 +569,22 @@ Http2Session::DontReuse()
{
LOG3(("Http2Session::DontReuse %p\n", this));
mShouldGoAway = true;
if (!mStreamTransactionHash.Count())
Close(NS_OK);
}
uint32_t
+Http2Session::SpdyVersion()
+{
+ return HTTP_VERSION_2;
+}
+
+uint32_t
Http2Session::GetWriteQueueSize()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
return mReadyForWrite.GetSize();
}
void
@@ -2339,25 +2364,61 @@ Http2Session::ReadSegmentsAgain(nsAHttpS
LOG3(("Http2Session::ReadSegments %p", this));
Http2Stream *stream = static_cast<Http2Stream *>(mReadyForWrite.PopFront());
if (!stream) {
LOG3(("Http2Session %p could not identify a stream to write; suspending.",
this));
FlushOutputQueue();
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 "
"block-input=%d block-output=%d\n", this, stream, stream->StreamID(),
stream->RequestBlockedOnRead(), stream->BlockedOnRwin()));
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
// to flush.
FlushOutputQueue();
// Allow new server reads - that might be data or control information
// (e.g. window updates or http replies) that are responses to these writes
@@ -2897,16 +2958,68 @@ nsresult
Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
uint32_t count, uint32_t *countWritten)
{
bool again = false;
return WriteSegmentsAgain(writer, count, countWritten, &again);
}
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)
{
LOG3(("Http2Session::ProcessConnectedPush %p 0x%X\n",
this, pushConnectedStream->StreamID()));
mSegmentWriter = writer;
nsresult rv = pushConnectedStream->WriteSegments(this, count, countWritten);
@@ -3104,17 +3217,19 @@ Http2Session::Close(nsresult aReason)
goAwayReason = mGoAwayReason;
} else if (NS_SUCCEEDED(aReason)) {
goAwayReason = NO_HTTP_ERROR;
} else if (aReason == NS_ERROR_ILLEGAL_VALUE) {
goAwayReason = PROTOCOL_ERROR;
} else {
goAwayReason = INTERNAL_ERROR;
}
- GenerateGoAway(goAwayReason);
+ if (!mAttemptingEarlyData) {
+ GenerateGoAway(goAwayReason);
+ }
mConnection = nullptr;
mSegmentReader = nullptr;
mSegmentWriter = nullptr;
}
nsHttpConnectionInfo *
Http2Session::ConnectionInfo()
{
@@ -3205,17 +3320,17 @@ Http2Session::OnReadSegment(const char *
FlushOutputQueue();
return NS_OK;
}
nsresult
Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment)
{
- if (mOutputQueueUsed)
+ if (mOutputQueueUsed && !mAttemptingEarlyData)
FlushOutputQueue();
// would there be enough room to buffer this if needed?
if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
return NS_OK;
// if we are using part of our buffers already, try again later unless
// forceCommitment is set.
@@ -3525,22 +3640,28 @@ Http2Session::ALPNCallback(nsISupports *
}
}
return false;
}
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;
return NS_OK;
}
if (!mConnection)
return NS_ERROR_FAILURE;
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -38,22 +38,23 @@ class Http2Session final : public ASpdyS
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSAHTTPTRANSACTION
NS_DECL_NSAHTTPCONNECTION(mConnection)
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
// have this invoked. It might happen sooner depending on the needs of
// other connections.
uint32_t ReadTimeoutTick(PRIntervalTime now) override;
// Idle time represents time since "goodput".. e.g. a data or header frame
@@ -230,16 +231,18 @@ public:
void SendPing() override;
bool MaybeReTunnel(nsAHttpTransaction *) override;
bool UseH2Deps() { return mUseH2Deps; }
// 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:
// These internal states do not correspond to the states of the HTTP/2 specification
enum internalStateType {
BUFFERING_OPENING_SETTINGS,
BUFFERING_FRAME_HEADER,
BUFFERING_CONTROL_FRAME,
@@ -490,16 +493,20 @@ private:
// receive a PUSH_PROMISE, but we have to wait for the SETTINGS ACK before
// we can actually tell the other end to go away. These help us keep track
// of that state so we can behave appropriately.
bool mWaitingForSettingsAck;
bool mGoAwayOnPush;
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 *);
void CreateTunnel(nsHttpTransaction *, nsHttpConnectionInfo *, nsIInterfaceRequestor *);
void RegisterTunnel(Http2Stream *);
void UnRegisterTunnel(Http2Stream *);
uint32_t FindTunnelCount(nsHttpConnectionInfo *);
nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -65,16 +65,17 @@ Http2Stream::Http2Stream(nsAHttpTransact
, mTxInlineFrameUsed(0)
, mTxStreamFrameSize(0)
, mRequestBodyLenRemaining(0)
, mLocalUnacked(0)
, mBlockedOnRwin(false)
, mTotalSent(0)
, mTotalRead(0)
, mPushSource(nullptr)
+ , mAttempting0RTT(false)
, mIsTunnel(false)
, mPlainTextTunnel(false)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
LOG3(("Http2Stream::Http2Stream %p", this));
mServerReceiveWindow = session->GetServerInitialStreamWindow();
@@ -920,17 +921,19 @@ Http2Stream::TransmitFrame(const char *b
"inconsistent stream commitment count");
Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer",
buf, transmittedCount);
*countUsed += mTxStreamFrameSize;
}
- mSession->FlushOutputQueue();
+ if (!mAttempting0RTT) {
+ mSession->FlushOutputQueue();
+ }
// calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
mTxInlineFrameUsed = 0;
mTxStreamFrameSize = 0;
return NS_OK;
@@ -1463,10 +1466,31 @@ void
Http2Stream::MapStreamToHttpConnection()
{
RefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
MOZ_ASSERT(qiTrans);
qiTrans->MapStreamToHttpConnection(mSocketTransport,
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
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -149,16 +149,20 @@ public:
static nsresult MakeOriginURL(const nsACString &origin,
RefPtr<nsStandardURL> &url);
static nsresult MakeOriginURL(const nsACString &scheme,
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,
uint64_t serial,
const nsCSubstring &pathInfo,
nsCString &outOrigin,
nsCString &outKey);
@@ -323,16 +327,18 @@ private:
// For Http2Push
Http2PushedStream *mPushSource;
// Used to store stream data when the transaction channel cannot keep up
// and flow control has not yet kicked in.
SimpleBuffer mSimpleBuffer;
+ bool mAttempting0RTT;
+
/// connect tunnels
public:
bool IsTunnel() { return mIsTunnel; }
private:
void ClearTransactionsBlockedOnTunnel();
void MapStreamToPlainText();
void MapStreamToHttpConnection();
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -211,18 +211,21 @@ public:
}
// This function will be called when a tls handshake has been finished and
// we know whether early-data that was sent has been accepted or not, e.g.
// do we need to restart a transaction. This will be called only if Do0RTT
// returns true.
// 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;
}
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
#define NS_DECL_NSAHTTPTRANSACTION \
void SetConnection(nsAHttpConnection *) override; \
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -82,16 +82,17 @@ nsHttpConnection::nsHttpConnection()
, mTransactionCaps(0)
, mResponseTimeoutEnabled(false)
, mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
, mForceSendPending(false)
, m0RTTChecked(false)
, mWaitingFor0RTTResponse(false)
, mContentBytesWritten0RTT(0)
, mEarlyDataNegotiated(false)
+ , mDid0RTTSpdy(false)
{
LOG(("Creating nsHttpConnection @%p\n", this));
// the default timeout is for when this connection has not yet processed a
// transaction
static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
mIdleTimeout =
(k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
@@ -153,26 +154,123 @@ nsHttpConnection::Init(nsHttpConnectionI
mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(callbacks, false);
mSocketTransport->SetEventSink(this, nullptr);
mSocketTransport->SetSecurityCallbacks(this);
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=%x", this, rv));
+ return;
+ }
+
+ rv = MoveTransactionsToSpdy(rv, list);
+ if (NS_FAILED(rv)) {
+ LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving "
+ "transactions rv=%x", this, 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;
gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true);
}
// Setting the connection as reused allows some transactions that fail
// with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
@@ -180,37 +278,23 @@ nsHttpConnection::StartSpdy(uint8_t spdy
// a server goaway was generated).
mIsReused = true;
// If mTransaction is a pipeline object it might represent
// several requests. If so, we need to unpack that and
// pack them all into a new spdy session.
nsTArray<RefPtr<nsAHttpTransaction> > list;
- nsresult rv = mTransaction->TakeSubTransactions(list);
+ nsresult rv;
+ if (!mDid0RTTSpdy) {
+ rv = TryTakeSubTransactions(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;
- }
-
- 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()) {
LOG3(("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 "
"Proxy and Need Connect", this));
MOZ_ASSERT(mProxyConnectStream);
mProxyConnectStream = nullptr;
@@ -222,45 +306,21 @@ nsHttpConnection::StartSpdy(uint8_t spdy
if (spdyProxy) {
RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo,
wildCardProxyCi, this);
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.
rv = DisableTCPKeepalives();
if (NS_FAILED(rv)) {
LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
"rv[0x%x]", this, rv));
}
@@ -316,57 +376,63 @@ nsHttpConnection::EnsureNPNComplete(nsre
rv = ssl->GetNegotiatedNPN(negotiatedNPN);
if (!m0RTTChecked && (rv == NS_ERROR_NOT_CONNECTED) &&
!mConnInfo->UsingProxy()) {
// There is no ALPN info (yet!). We need to consider doing 0RTT. We
// will do so if there is ALPN information from a previous session
// (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
// check again.
LOG(("nsHttpConnection::EnsureNPNComplete %p - "
"early selected alpn not available, we will try one more time.",
this));
// Let's do DriveHandshake again.
rv = ssl->DriveHandshake();
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
goto npnComplete;
}
// Check NegotiatedNPN first.
rv = ssl->GetNegotiatedNPN(negotiatedNPN);
if (rv == NS_ERROR_NOT_CONNECTED) {
- rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+ rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN);
}
}
if (NS_FAILED(rvEarlyAlpn)) {
LOG(("nsHttpConnection::EnsureNPNComplete %p - "
"early selected alpn not available", this));
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;
}
}
if (rv == NS_ERROR_NOT_CONNECTED) {
if (mWaitingFor0RTTResponse) {
aOut0RTTWriteHandshakeValue = mTransaction->ReadSegments(this,
nsIOService::gDefaultSegmentSize, &aOut0RTTBytesWritten);
if (NS_FAILED(aOut0RTTWriteHandshakeValue) &&
@@ -386,75 +452,97 @@ nsHttpConnection::EnsureNPNComplete(nsre
return false;
}
if (NS_SUCCEEDED(rv)) {
LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
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=%x].",
+ this, earlyDataAccepted ? "has" : "has not", 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;
}
}
int16_t tlsVersion;
ssl->GetSSLVersionUsed(&tlsVersion);
// Send the 0RTT telemetry only for tls1.3
if (tlsVersion > nsISSLSocketControl::TLS_VERSION_1_2) {
Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_NEGOTIATED,
(!mEarlyDataNegotiated) ? TLS_EARLY_DATA_NOT_AVAILABLE
: ((mWaitingFor0RTTResponse) ? TLS_EARLY_DATA_AVAILABLE_AND_USED
: 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))) {
StartSpdy(info->Version[infoIndex]);
}
} else {
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;
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;
}
void
nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -243,16 +243,23 @@ private:
// Makes certain the SSL handshake is complete and NPN negotiation
// has had a chance to happen
bool EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
uint32_t &aOut0RTTBytesWritten);
void SetupSSL();
// 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);
// Used to set TCP keepalives for fast detection of dead connections during
// an initial period, and slower detection for long-lived connections.
nsresult StartShortLivedTCPKeepalives();
nsresult StartLongLivedTCPKeepalives();
@@ -365,14 +372,16 @@ private:
bool mWaitingFor0RTTResponse; // We have are
// sending 0RTT
// data and we
// are waiting
// for the end of
// the handsake.
int64_t mContentBytesWritten0RTT;
bool mEarlyDataNegotiated; //Only used for telemetry
+ nsCString mEarlyNegotiatedALPN;
+ bool mDid0RTTSpdy;
};
} // namespace net
} // namespace mozilla
#endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -2382,17 +2382,17 @@ nsHttpTransaction::Do0RTT()
if (mRequestHead->IsSafeMethod() &&
!mConnection->IsProxyConnectInProgress()) {
m0RTTInProgress = true;
}
return m0RTTInProgress;
}
nsresult
-nsHttpTransaction::Finish0RTT(bool aRestart)
+nsHttpTransaction::Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */)
{
MOZ_ASSERT(m0RTTInProgress);
m0RTTInProgress = false;
if (aRestart) {
// Reset request headers to be sent again.
nsCOMPtr<nsISeekableStream> seekable =
do_QueryInterface(mRequestStream);
if (seekable) {
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -163,17 +163,17 @@ public:
mozilla::TimeStamp GetConnectEnd();
mozilla::TimeStamp GetRequestStart();
mozilla::TimeStamp GetResponseStart();
mozilla::TimeStamp GetResponseEnd();
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();
nsresult Restart();
nsresult RestartInProgress();
char *LocateHttpStart(char *buf, uint32_t len,
bool aAllowPartialMatch);