diff --git a/netwerk/protocol/http/Makefile.in b/netwerk/protocol/http/Makefile.in index 4f3155387cb..b02ae6fc183 100644 --- a/netwerk/protocol/http/Makefile.in +++ b/netwerk/protocol/http/Makefile.in @@ -112,6 +112,7 @@ CPPSRCS = \ HttpChannelParentListener.cpp \ SpdySession.cpp \ SpdyStream.cpp \ + NullHttpTransaction.cpp \ $(NULL) LOCAL_INCLUDES = \ diff --git a/netwerk/protocol/http/NullHttpTransaction.cpp b/netwerk/protocol/http/NullHttpTransaction.cpp new file mode 100644 index 00000000000..77c97120677 --- /dev/null +++ b/netwerk/protocol/http/NullHttpTransaction.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "NullHttpTransaction.h" +#include "nsProxyRelease.h" +#include "nsHttpHandler.h" + +namespace mozilla { +namespace net { + +NS_IMPL_THREADSAFE_ISUPPORTS0(NullHttpTransaction); + +NullHttpTransaction::NullHttpTransaction(nsHttpConnectionInfo *ci, + nsIInterfaceRequestor *callbacks, + nsIEventTarget *target, + PRUint8 caps) + : mStatus(NS_OK) + , mCaps(caps) + , mCallbacks(callbacks) + , mEventTarget(target) + , mConnectionInfo(ci) + , mRequestHead(nsnull) +{ +} + +NullHttpTransaction::~NullHttpTransaction() +{ + if (mCallbacks) { + nsIInterfaceRequestor *cbs = nsnull; + mCallbacks.swap(cbs); + NS_ProxyRelease(mEventTarget, cbs); + } + delete mRequestHead; +} + +void +NullHttpTransaction::SetConnection(nsAHttpConnection *conn) +{ + mConnection = conn; +} + +nsAHttpConnection * +NullHttpTransaction::Connection() +{ + return mConnection.get(); +} + +void +NullHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **outCB, + nsIEventTarget **outTarget) +{ + nsCOMPtr copyCB(mCallbacks); + *outCB = copyCB; + copyCB.forget(); + + if (outTarget) { + nsCOMPtr copyET(mEventTarget); + *outTarget = copyET; + copyET.forget(); + } +} + +void +NullHttpTransaction::OnTransportStatus(nsITransport* transport, + nsresult status, PRUint64 progress) +{ +} + +bool +NullHttpTransaction::IsDone() +{ + return true; +} + +nsresult +NullHttpTransaction::Status() +{ + return mStatus; +} + +PRUint32 +NullHttpTransaction::Available() +{ + return 0; +} + +nsresult +NullHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, PRUint32 *countRead) +{ + *countRead = 0; + return NS_BASE_STREAM_CLOSED; +} + +nsresult +NullHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, PRUint32 *countWritten) +{ + *countWritten = 0; + return NS_BASE_STREAM_CLOSED; +} + +PRUint32 +NullHttpTransaction::Http1xTransactionCount() +{ + return 0; +} + +nsHttpRequestHead * +NullHttpTransaction::RequestHead() +{ + // We suport a requesthead at all so that a CONNECT tunnel transaction + // can obtain a Host header from it, but we lazy-popualate that header. + + if (!mRequestHead) { + mRequestHead = new nsHttpRequestHead(); + + nsCAutoString hostHeader; + nsCString host(mConnectionInfo->GetHost()); + nsresult rv = nsHttpHandler::GenerateHostPort(host, + mConnectionInfo->Port(), + hostHeader); + if (NS_SUCCEEDED(rv)) + mRequestHead->SetHeader(nsHttp::Host, hostHeader); + + // CONNECT tunnels may also want Proxy-Authorization but that is a lot + // harder to determine, so for now we will let those connections fail in + // the NullHttpTransaction and let them be retried from the pending queue + // with a bound transcation + } + + return mRequestHead; +} + +nsresult +NullHttpTransaction::TakeSubTransactions( + nsTArray > &outTransactions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +NullHttpTransaction::SetSSLConnectFailed() +{ +} + +void +NullHttpTransaction::Close(nsresult reason) +{ + mStatus = reason; + mConnection = nsnull; +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/NullHttpTransaction.h b/netwerk/protocol/http/NullHttpTransaction.h new file mode 100644 index 00000000000..7ed5a4f278e --- /dev/null +++ b/netwerk/protocol/http/NullHttpTransaction.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_NullHttpTransaction_h +#define mozilla_net_NullHttpTransaction_h + +#include "nsAHttpTransaction.h" +#include "nsAHttpConnection.h" +#include "nsIInterfaceRequestor.h" +#include "nsIEventTarget.h" +#include "nsHttpConnectionInfo.h" +#include "nsHttpRequestHead.h" + +// This is the minimal nsAHttpTransaction implementation. A NullHttpTransaction +// can be used to drive connection level semantics (such as SSL handshakes +// tunnels) so that a nsHttpConnection becomes fully established in +// anticiation of a real transaction needing to use it soon. + +namespace mozilla { namespace net { + +class NullHttpTransaction : public nsAHttpTransaction +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + + NullHttpTransaction(nsHttpConnectionInfo *ci, + nsIInterfaceRequestor *callbacks, + nsIEventTarget *target, + PRUint8 caps); + ~NullHttpTransaction(); + + PRUint8 Caps() { return mCaps; } + nsHttpConnectionInfo *ConnectionInfo() { return mConnectionInfo; } + +private: + + nsresult mStatus; + PRUint8 mCaps; + nsRefPtr mConnection; + nsCOMPtr mCallbacks; + nsCOMPtr mEventTarget; + nsRefPtr mConnectionInfo; + nsHttpRequestHead *mRequestHead; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_NullHttpTransaction_h diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 9a6485a81dd..63355320739 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -70,6 +70,7 @@ #include "nsDOMError.h" #include "nsAlgorithm.h" #include "sampler.h" +#include "NullHttpTransaction.h" using namespace mozilla; @@ -230,6 +231,16 @@ nsHttpChannel::Connect(bool firstTime) // true when called from AsyncOpen if (firstTime) { + + // Before we take the latency hit of dealing with the cache, try and + // get the TCP (and SSL) handshakes going so they can overlap. + nsCOMPtr callbacks; + NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, + getter_AddRefs(callbacks)); + if (callbacks) + gHttpHandler->SpeculativeConnect(mConnectionInfo, + callbacks, NS_GetCurrentThread()); + // are we offline? bool offline = gIOService->IsOffline(); if (offline) diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 6d6e6f29056..4e491f52f37 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -400,10 +400,9 @@ nsHttpConnection::SetupNPN(PRUint8 caps) mNPNComplete = true; if (mConnInfo->UsingSSL() && - !(caps & NS_HTTP_DISALLOW_SPDY) && - !mConnInfo->UsingHttpProxy() && - gHttpHandler->IsSpdyEnabled()) { - LOG(("nsHttpConnection::Init Setting up SPDY Negotiation")); + !mConnInfo->UsingHttpProxy()) { + LOG(("nsHttpConnection::SetupNPN Setting up " + "Next Protocol Negotiation")); nsCOMPtr securityInfo; nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); @@ -416,7 +415,12 @@ nsHttpConnection::SetupNPN(PRUint8 caps) return; nsTArray protocolArray; - protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2")); + if (gHttpHandler->IsSpdyEnabled() && + !(caps & NS_HTTP_DISALLOW_SPDY)) { + LOG(("nsHttpConnection::SetupNPN Allow SPDY NPN selection")); + protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2")); + } + protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK")); @@ -1101,7 +1105,7 @@ nsHttpConnection::OnSocketWritable() rv, n, mSocketOutCondition)); // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. - if (rv == NS_BASE_STREAM_CLOSED) { + if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) { rv = NS_OK; n = 0; } diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp index d7442047319..2b5286248a1 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp +++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp @@ -91,8 +91,6 @@ nsHttpConnectionInfo* nsHttpConnectionInfo::Clone() const { nsHttpConnectionInfo* clone = new nsHttpConnectionInfo(mHost, mPort, mProxyInfo, mUsingSSL); - if (!clone) - return nsnull; // Make sure the anonymous flag is transferred! clone->SetAnonymous(mHashKey.CharAt(2) == 'A'); diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index fa0e43620df..26f37485de7 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -98,6 +98,7 @@ public: SetOriginServer(nsDependentCString(host), port); } + // OK to treat this as an infalible allocation nsHttpConnectionInfo* Clone() const; const char *ProxyHost() const { return mProxyInfo ? mProxyInfo->Host().get() : nsnull; } diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 4c4a07b21af..2471a18d466 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -53,6 +53,7 @@ #include "mozilla/Telemetry.h" using namespace mozilla; +using namespace mozilla::net; // defined by the socket transport service while active extern PRThread *gSocketThread; @@ -335,6 +336,24 @@ nsHttpConnectionMgr::ClosePersistentConnections() return PostEvent(&nsHttpConnectionMgr::OnMsgClosePersistentConnections); } +nsresult +nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci, + nsIInterfaceRequestor *callbacks, + nsIEventTarget *target) +{ + LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n", + ci->HashKey().get())); + + nsRefPtr trans = + new NullHttpTransaction(ci, callbacks, target, 0); + + nsresult rv = + PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, trans); + if (NS_SUCCEEDED(rv)) + trans.forget(); + return rv; +} + nsresult nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) { @@ -1053,6 +1072,22 @@ nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key, return PL_DHASH_NEXT; } +bool +nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // If this host is trying to negotiate a SPDY session right now, + // don't create any new ssl connections until the result of the + // negotiation is known. + + return ent->mConnInfo->UsingSSL() && + gHttpHandler->IsSpdyEnabled() && + !ent->mConnInfo->UsingHttpProxy() && + (!ent->mTestedSpdy || ent->mUsingSpdy) && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length()); +} + void nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, nsHttpTransaction *trans, @@ -1115,19 +1150,25 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, // does not create new transports under any circumstances. if (onlyReusedConnection) return; - - if (gHttpHandler->IsSpdyEnabled() && - ent->mConnInfo->UsingSSL() && - !ent->mConnInfo->UsingHttpProxy()) - { - // If this host is trying to negotiate a SPDY session right now, - // don't create any new connections until the result of the - // negotiation is known. - - if ((!ent->mTestedSpdy || ent->mUsingSpdy) && - (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) + + PRUint32 halfOpenLength = ent->mHalfOpens.Length(); + for (PRUint32 i = 0; i < halfOpenLength; i++) { + if (ent->mHalfOpens[i]->IsSpeculative()) { + // We've found a speculative connection in the half + // open list. Remove the speculative bit from it and that + // connection can later be used for this transaction + // (or another one in the pending queue) - we don't + // need to open a new connection here. + LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]\n" + "Found a speculative half open connection\n", + ent->mConnInfo->HashKey().get())); + ent->mHalfOpens[i]->SetSpeculative(false); return; + } } + + if (RestrictConnections(ent)) + return; // Check if we need to purge an idle connection. Note that we may have // removed one above; if so, this will be a no-op. We do this before @@ -1153,7 +1194,7 @@ nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, ent->mConnInfo->Host(), ent->mCoalescingKey.get(), ent, ent->mUsingSpdy)); - nsresult rv = CreateTransport(ent, trans); + nsresult rv = CreateTransport(ent, trans, trans->Caps(), false); if (NS_FAILED(rv)) trans->Close(rv); return; @@ -1193,15 +1234,19 @@ nsHttpConnectionMgr::RecvdConnect() nsresult nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, - nsHttpTransaction *trans) + nsAHttpTransaction *trans, + PRUint8 caps, + bool speculative) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - nsRefPtr sock = new nsHalfOpenSocket(ent, trans); + nsRefPtr sock = new nsHalfOpenSocket(ent, trans, caps); nsresult rv = sock->SetupPrimaryStreams(); NS_ENSURE_SUCCESS(rv, rv); ent->mHalfOpens.AppendElement(sock); + if (speculative) + sock->SetSpeculative(true); return NS_OK; } @@ -1227,6 +1272,28 @@ nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, return rv; } + return DispatchAbstractTransaction(ent, aTrans, caps, conn, priority); +} + + +// Use this method for dispatching nsAHttpTransction's. It can only safely be +// used upon first use of a connection when NPN has not negotiated SPDY vs +// HTTP/1 yet as multiplexing onto an existing SPDY session requires a +// concrete nsHttpTransaction +nsresult +nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent, + nsAHttpTransaction *aTrans, + PRUint8 caps, + nsHttpConnection *conn, + PRInt32 priority) +{ + NS_ABORT_IF_FALSE(!conn->UsingSpdy(), + "Spdy Must Not Use DispatchAbstractTransaction"); + LOG(("nsHttpConnectionMgr::DispatchAbstractTransaction " + "[ci=%s trans=%x caps=%x conn=%x]\n", + ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); + nsresult rv; + nsConnectionHandle *handle = new nsConnectionHandle(conn); if (!handle) return NS_ERROR_OUT_OF_MEMORY; @@ -1310,6 +1377,19 @@ nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, return true; } +nsHttpConnectionMgr::nsConnectionEntry * +nsHttpConnectionMgr::GetOrCreateConnectionEntry(nsHttpConnectionInfo *ci) +{ + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (ent) + return ent; + + nsHttpConnectionInfo *clone = ci->Clone(); + ent = new nsConnectionEntry(clone); + mCT.Put(ci->HashKey(), ent); + return ent; +} + nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) { @@ -1328,16 +1408,7 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) nsHttpConnectionInfo *ci = trans->ConnectionInfo(); NS_ASSERTION(ci, "no connection info"); - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - if (!ent) { - nsHttpConnectionInfo *clone = ci->Clone(); - if (!clone) - return NS_ERROR_OUT_OF_MEMORY; - ent = new nsConnectionEntry(clone); - if (!ent) - return NS_ERROR_OUT_OF_MEMORY; - mCT.Put(ci->HashKey(), ent); - } + nsConnectionEntry *ent = GetOrCreateConnectionEntry(ci); // SPDY coalescing of hostnames means we might redirect from this // connection entry onto the preferred one. @@ -1599,6 +1670,26 @@ nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) mCT.Enumerate(ClosePersistentConnectionsCB, this); } +void +nsHttpConnectionMgr::OnMsgSpeculativeConnect(PRInt32, void *param) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsRefPtr trans = + dont_AddRef(static_cast(param)); + + LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n", + trans->ConnectionInfo()->HashKey().get())); + + nsConnectionEntry *ent = + GetOrCreateConnectionEntry(trans->ConnectionInfo()); + + if (!ent->mIdleConns.Length() && !RestrictConnections(ent) && + !AtActiveConnectionLimit(ent, trans->Caps())) { + CreateTransport(ent, trans, trans->Caps(), true); + } +} + void nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) { @@ -1889,9 +1980,12 @@ NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpConnectionMgr::nsHalfOpenSocket, nsHttpConnectionMgr:: nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent, - nsHttpTransaction *trans) + nsAHttpTransaction *trans, + PRUint8 caps) : mEnt(ent), - mTransaction(trans) + mTransaction(trans), + mCaps(caps), + mSpeculative(false) { NS_ABORT_IF_FALSE(ent && trans, "constructor with null arguments"); LOG(("Creating nsHalfOpenSocket [this=%p trans=%p ent=%s]\n", @@ -1941,10 +2035,10 @@ nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport, NS_ENSURE_SUCCESS(rv, rv); PRUint32 tmpFlags = 0; - if (mTransaction->Caps() & NS_HTTP_REFRESH_DNS) + if (mCaps & NS_HTTP_REFRESH_DNS) tmpFlags = nsISocketTransport::BYPASS_CACHE; - if (mTransaction->Caps() & NS_HTTP_LOAD_ANONYMOUS) + if (mCaps & NS_HTTP_LOAD_ANONYMOUS) tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT; // For backup connections, we disable IPv6. That's because some users have @@ -2036,7 +2130,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() PRUint16 timeout = gHttpHandler->GetIdleSynTimeout(); NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd"); - if (timeout) { + if (timeout && !mTransaction->IsDone()) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. // We do this because a lost SYN takes a very long time @@ -2099,7 +2193,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::Notify(nsITimer *timer) NS_ABORT_IF_FALSE(timer == mSynTimer, "wrong timer"); if (!gHttpHandler->ConnMgr()-> - AtActiveConnectionLimit(mEnt, mTransaction->Caps())) { + AtActiveConnectionLimit(mEnt, mCaps)) { SetupBackupStreams(); } @@ -2166,13 +2260,13 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) // if this is still in the pending list, remove it and dispatch it index = mEnt->mPendingQ.IndexOf(mTransaction); if (index != -1) { + NS_ABORT_IF_FALSE(!mSpeculative, + "Speculative Half Open found mTranscation"); + nsRefPtr temp = dont_AddRef(mEnt->mPendingQ[index]); mEnt->mPendingQ.RemoveElementAt(index); - nsHttpTransaction *temp = mTransaction; - NS_RELEASE(temp); gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); - rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction, - mTransaction->Caps(), - conn); + rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, + mCaps, conn); } else { // this transaction was dispatched off the pending q before all the @@ -2191,8 +2285,31 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) // minimum granularity we can expect a server to be timing out with. conn->SetIsReusedAfter(950); - nsRefPtr copy(conn); // because onmsg*() expects to drop a reference - gHttpHandler->ConnMgr()->OnMsgReclaimConnection(NS_OK, conn.forget().get()); + // if we are using ssl and no other transactions are waiting right now, + // then form a null transaction to drive the SSL handshake to + // completion. Afterwards the connection will be 100% ready for the next + // transaction to use it. + if (mEnt->mConnInfo->UsingSSL() && !mEnt->mPendingQ.Length()) { + LOG(("nsHalfOpenSocket::OnOutputStreamReady null transaction will " + "be used to finish SSL handshake on conn %p\n", conn.get())); + nsRefPtr trans = + new NullHttpTransaction(mEnt->mConnInfo, + callbacks, callbackTarget, + mCaps & ~NS_HTTP_ALLOW_PIPELINING); + + gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); + rv = gHttpHandler->ConnMgr()-> + DispatchAbstractTransaction(mEnt, trans, mCaps, conn, 0); + } + else { + // otherwise just put this in the persistent connection pool + LOG(("nsHalfOpenSocket::OnOutputStreamReady no transaction match " + "returning conn %p to pool\n", conn.get())); + nsRefPtr copy(conn); + // forget() to effectively addref because onmsg*() will drop a ref + gHttpHandler->ConnMgr()->OnMsgReclaimConnection( + NS_OK, conn.forget().get()); + } } return rv; diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index 78a8adbd796..095a372557d 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -42,6 +42,7 @@ #include "nsHttpConnectionInfo.h" #include "nsHttpConnection.h" #include "nsHttpTransaction.h" +#include "NullHttpTransaction.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsClassHashtable.h" @@ -125,6 +126,16 @@ public: // transport service is not available when the connection manager is down. nsresult GetSocketThreadTarget(nsIEventTarget **); + // called to indicate a transaction for the connectionInfo is likely coming + // soon. The connection manager may use this information to start a TCP + // and/or SSL level handshake for that resource immediately so that it is + // ready when the transaction is submitted. No obligation is taken on by the + // connection manager, nor is the submitter obligated to actually submit a + // real transaction for this connectionInfo. + nsresult SpeculativeConnect(nsHttpConnectionInfo *, + nsIInterfaceRequestor *, + nsIEventTarget *); + // called when a connection is done processing a transaction. if the // connection can be reused then it will be added to the idle list, else // it will be closed. @@ -251,7 +262,8 @@ private: NS_DECL_NSITIMERCALLBACK nsHalfOpenSocket(nsConnectionEntry *ent, - nsHttpTransaction *trans); + nsAHttpTransaction *trans, + PRUint8 caps); ~nsHalfOpenSocket(); nsresult SetupStreams(nsISocketTransport **, @@ -264,14 +276,27 @@ private: void CancelBackupTimer(); void Abandon(); - nsHttpTransaction *Transaction() { return mTransaction; } + nsAHttpTransaction *Transaction() { return mTransaction; } + + bool IsSpeculative() { return mSpeculative; } + void SetSpeculative(bool val) { mSpeculative = val; } private: nsConnectionEntry *mEnt; - nsRefPtr mTransaction; + nsRefPtr mTransaction; nsCOMPtr mSocketTransport; nsCOMPtr mStreamOut; nsCOMPtr mStreamIn; + PRUint8 mCaps; + + // mSpeculative is set if the socket was created from + // SpeculativeConnect(). It is cleared when a transaction would normally + // start a new connection from scratch but instead finds this one in + // the half open list and claims it for its own use. (which due to + // the vagaries of scheduling from the pending queue might not actually + // match up - but it prevents a speculative connection from opening + // more connections that are needed.) + bool mSpeculative; // for syn retry nsCOMPtr mSynTimer; @@ -312,18 +337,24 @@ private: static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr &, void *); bool ProcessPendingQForEntry(nsConnectionEntry *); bool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); + bool RestrictConnections(nsConnectionEntry *); void GetConnection(nsConnectionEntry *, nsHttpTransaction *, bool, nsHttpConnection **); nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, PRUint8 caps, nsHttpConnection *); + nsresult DispatchAbstractTransaction(nsConnectionEntry *, + nsAHttpTransaction *, PRUint8 caps, + nsHttpConnection *, PRInt32); bool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTargetIfOnline(); void ClosePersistentConnections(nsConnectionEntry *ent); - nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *); + nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *, + PRUint8, bool); void AddActiveConn(nsHttpConnection *, nsConnectionEntry *); void StartedConnect(); void RecvdConnect(); + nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *); // Manage the preferred spdy connection entry for this address nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry); @@ -394,6 +425,7 @@ private: void OnMsgCancelTransaction (PRInt32, void *); void OnMsgProcessPendingQ (PRInt32, void *); void OnMsgPruneDeadConnections (PRInt32, void *); + void OnMsgSpeculativeConnect (PRInt32, void *); void OnMsgReclaimConnection (PRInt32, void *); void OnMsgUpdateParam (PRInt32, void *); void OnMsgClosePersistentConnections (PRInt32, void *); diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index b1d02acb9e2..b0c44782a6a 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -181,6 +181,13 @@ public: return mConnMgr->GetSocketThreadTarget(target); } + nsresult SpeculativeConnect(nsHttpConnectionInfo *ci, + nsIInterfaceRequestor *callbacks, + nsIEventTarget *target) + { + return mConnMgr->SpeculativeConnect(ci, callbacks, target); + } + // for anything that wants to know if we're in private browsing mode. bool InPrivateBrowsingMode();