bug 528288 - HTTP Alternate-Protocol header for transitioning from http to spdy r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 02 Dec 2011 10:28:57 -0500
changeset 81179 3f6e6b127b23aa1178c71c8f719c65557c101f01
parent 81178 0eb13ad19d08be53e991cc03e4d70ec4fd9fb995
child 81180 9cf19a02362486574eeb9838347c0859397c4938
push id21564
push usermak77@bonardo.net
push dateSat, 03 Dec 2011 11:10:17 +0000
treeherdermozilla-central@a68c96c1d8e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs528288
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 528288 - HTTP Alternate-Protocol header for transitioning from http to spdy r=honzab patch 2
netwerk/protocol/http/nsHttpAtomList.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -51,16 +51,17 @@
  ******/
 
 HTTP_ATOM(Accept,                    "Accept")
 HTTP_ATOM(Accept_Encoding,           "Accept-Encoding")
 HTTP_ATOM(Accept_Language,           "Accept-Language")
 HTTP_ATOM(Accept_Ranges,             "Accept-Ranges")
 HTTP_ATOM(Age,                       "Age")
 HTTP_ATOM(Allow,                     "Allow")
+HTTP_ATOM(Alternate_Protocol,        "Alternate-Protocol")
 HTTP_ATOM(Authentication,            "Authentication")
 HTTP_ATOM(Authorization,             "Authorization")
 HTTP_ATOM(Cache_Control,             "Cache-Control")
 HTTP_ATOM(Connection,                "Connection")
 HTTP_ATOM(Content_Base,              "Content-Base")
 HTTP_ATOM(Content_Disposition,       "Content-Disposition")
 HTTP_ATOM(Content_Encoding,          "Content-Encoding")
 HTTP_ATOM(Content_Language,          "Content-Language")
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -204,16 +204,28 @@ nsHttpChannel::Connect(bool firstTime)
         // worrisome.
         NS_ASSERTION(NS_SUCCEEDED(rv),
                      "Something is wrong with STS: IsStsURI failed.");
 
         if (NS_SUCCEEDED(rv) && isStsHost) {
             LOG(("nsHttpChannel::Connect() STS permissions found\n"));
             return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
         }
+
+        // Check for a previous SPDY Alternate-Protocol directive
+        if (gHttpHandler->IsSpdyEnabled()) {
+            nsCAutoString hostPort;
+
+            if (NS_SUCCEEDED(mURI->GetHostPort(hostPort)) &&
+                gHttpHandler->ConnMgr()->GetSpdyAlternateProtocol(hostPort)) {
+                LOG(("nsHttpChannel::Connect() Alternate-Protocol found\n"));
+                return AsyncCall(
+                    &nsHttpChannel::HandleAsyncRedirectChannelToHttps);
+            }
+        }
     }
 
     // ensure that we are using a valid hostname
     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
         return NS_ERROR_UNKNOWN_HOST;
 
     // true when called from AsyncOpen
     if (firstTime) {
@@ -4086,16 +4098,26 @@ nsHttpChannel::OnStartRequest(nsIRequest
                  "If we have both pumps, the cache content must be partial");
 
     if (!mSecurityInfo && !mCachePump && mTransaction) {
         // grab the security info from the connection object; the transaction
         // is guaranteed to own a reference to the connection.
         mSecurityInfo = mTransaction->SecurityInfo();
     }
 
+    if (gHttpHandler->IsSpdyEnabled() && !mCachePump && NS_FAILED(mStatus) &&
+        (mLoadFlags & LOAD_REPLACE) && mOriginalURI) {
+        // For sanity's sake we may want to cancel an alternate protocol
+        // redirection involving the original host name
+
+        nsCAutoString hostPort;
+        if (NS_SUCCEEDED(mOriginalURI->GetHostPort(hostPort)))
+            gHttpHandler->ConnMgr()->RemoveSpdyAlternateProtocol(hostPort);
+    }
+
     // don't enter this block if we're reading from the cache...
     if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
         // mTransactionPump doesn't hit OnInputStreamReady and call this until
         // all of the response headers have been acquired, so we can take ownership
         // of them from the transaction.
         mResponseHead = mTransaction->TakeResponseHead();
         // the response head may be null if the transaction was cancelled.  in
         // which case we just need to call OnStartRequest/OnStopRequest.
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -315,16 +315,42 @@ nsHttpConnection::SetupNPN(PRUint8 caps)
             if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
                 LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
                 mNPNComplete = false;
             }
         }
     }
 }
 
+void
+nsHttpConnection::HandleAlternateProtocol(nsHttpResponseHead *responseHead)
+{
+    // Look for the Alternate-Protocol header. Alternate-Protocol is
+    // essentially a way to rediect future transactions from http to
+    // spdy.
+    //
+
+    if (!gHttpHandler->IsSpdyEnabled() || mUsingSpdy)
+        return;
+
+    const char *val = responseHead->PeekHeader(nsHttp::Alternate_Protocol);
+    if (!val)
+        return;
+
+    // The spec allows redirections to any port, but due to concerns over
+    // silently redirecting to stealth ports we only allow port 443
+    //
+    // Alternate-Protocol: 5678:somethingelse, 443:npn-spdy/2
+
+    if (nsHttp::FindToken(val, "443:npn-spdy/2", HTTP_HEADER_VALUE_SEPS)) {
+        LOG(("Connection %p Transaction %p found Alternate-Protocol "
+             "header %s", this, mTransaction.get(), val));
+        gHttpHandler->ConnMgr()->ReportSpdyAlternateProtocol(this);
+    }
+}
 
 nsresult
 nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
                                  PRInt32 priority)
 {
     LOG(("nsHttpConnection::AddTransaction for SPDY"));
 
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
@@ -610,16 +636,19 @@ nsHttpConnection::OnHeadersAvailable(nsA
         }
         else {
             mIdleTimeout = gHttpHandler->SpdyTimeout();
         }
         
         LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout));
     }
 
+    if (!mProxyConnectStream)
+        HandleAlternateProtocol(responseHead);
+
     // if we're doing an SSL proxy connect, then we need to check whether or not
     // the connect was successful.  if so, then we have to reset the transaction
     // and step-up the socket connection to SSL. finally, we have to wake up the
     // socket write request.
     if (mProxyConnectStream) {
         NS_ABORT_IF_FALSE(!mUsingSpdy,
                           "SPDY NPN Complete while using proxy connect stream");
         mProxyConnectStream = 0;
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -171,16 +171,20 @@ private:
     bool     IsAlive();
     bool     SupportsPipelining(nsHttpResponseHead *);
     
     // Makes certain the SSL handshake is complete and NPN negotiation
     // has had a chance to happen
     bool     EnsureNPNComplete();
     void     SetupNPN(PRUint8 caps);
 
+    // Inform the connection manager of any SPDY Alternate-Protocol
+    // redirections
+    void     HandleAlternateProtocol(nsHttpResponseHead *);
+
     // Directly Add a transaction to an active connection for SPDY
     nsresult AddTransaction(nsAHttpTransaction *, PRInt32);
 
 private:
     nsCOMPtr<nsISocketTransport>    mSocketTransport;
     nsCOMPtr<nsIAsyncInputStream>   mSocketIn;
     nsCOMPtr<nsIAsyncOutputStream>  mSocketOut;
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -92,16 +92,17 @@ nsHttpConnectionMgr::nsHttpConnectionMgr
     , mMaxPersistConnsPerProxy(0)
     , mIsShuttingDown(false)
     , mNumActiveConns(0)
     , mNumIdleConns(0)
     , mTimeOfNextWakeUp(LL_MAXUINT)
 {
     LOG(("Creating nsHttpConnectionMgr @%x\n", this));
     mCT.Init();
+    mAlternateProtocolHash.Init(16);
 }
 
 nsHttpConnectionMgr::~nsHttpConnectionMgr()
 {
     LOG(("Destroying nsHttpConnectionMgr @%x\n", this));
 }
 
 nsresult
@@ -461,16 +462,78 @@ nsHttpConnectionMgr::ReportSpdyConnectio
         // A different hostname is the preferred spdy host for this
         // IP address.
         ent->mSpdyRedir = true;
         conn->DontReuse();
     }
     ProcessSpdyPendingQ();
 }
 
+bool
+nsHttpConnectionMgr::GetSpdyAlternateProtocol(nsACString &hostPortKey)
+{
+    // The Alternate Protocol hash is protected under the monitor because
+    // it is read from both the main and the network thread.
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    return mAlternateProtocolHash.Contains(hostPortKey);
+}
+
+void
+nsHttpConnectionMgr::ReportSpdyAlternateProtocol(nsHttpConnection *conn)
+{
+    // For now lets not bypass proxies due to the alternate-protocol header
+    if (conn->ConnectionInfo()->UsingHttpProxy())
+        return;
+
+    nsCString hostPortKey(conn->ConnectionInfo()->Host());
+    if (conn->ConnectionInfo()->Port() != 80) {
+        hostPortKey.Append(NS_LITERAL_CSTRING(":"));
+        hostPortKey.AppendInt(conn->ConnectionInfo()->Port());
+    }
+
+    // The Alternate Protocol hash is protected under the monitor because
+    // it is read from both the main and the network thread.
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // Check to see if this is already present
+    if (mAlternateProtocolHash.Contains(hostPortKey))
+        return;
+    
+    if (mAlternateProtocolHash.mHashTable.entryCount > 2000)
+        PL_DHashTableEnumerate(&mAlternateProtocolHash.mHashTable,
+                               &nsHttpConnectionMgr::TrimAlternateProtocolHash,
+                               this);
+    
+    mAlternateProtocolHash.Put(hostPortKey);
+}
+
+void
+nsHttpConnectionMgr::RemoveSpdyAlternateProtocol(nsACString &hostPortKey)
+{
+    // The Alternate Protocol hash is protected under the monitor because
+    // it is read from both the main and the network thread.
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    return mAlternateProtocolHash.Remove(hostPortKey);
+}
+
+PLDHashOperator
+nsHttpConnectionMgr::TrimAlternateProtocolHash(PLDHashTable *table,
+                                               PLDHashEntryHdr *hdr,
+                                               PRUint32 number,
+                                               void *closure)
+{
+    nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
+    
+    if (self->mAlternateProtocolHash.mHashTable.entryCount > 2000)
+        return PL_DHASH_REMOVE;
+    return PL_DHASH_STOP;
+}
+
 nsHttpConnectionMgr::nsConnectionEntry *
 nsHttpConnectionMgr::GetSpdyPreferred(nsACString &aDottedDecimal)
 {
     if (!gHttpHandler->IsSpdyEnabled() ||
         !gHttpHandler->CoalesceSpdy() ||
         aDottedDecimal.IsEmpty())
         return nsnull;
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -45,16 +45,17 @@
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsISocketTransportService.h"
 #include "nsIDNSListener.h"
+#include "nsHashSets.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 
 class nsHttpPipeline;
 
 //-----------------------------------------------------------------------------
 
@@ -128,16 +129,21 @@ public:
     // connection can be reused then it will be added to the idle list, else
     // it will be closed.
     nsresult ReclaimConnection(nsHttpConnection *conn);
 
     // called to update a parameter after the connection manager has already
     // been initialized.
     nsresult UpdateParam(nsParamName name, PRUint16 value);
 
+    // Lookup/Cancel HTTP->SPDY redirections
+    bool GetSpdyAlternateProtocol(nsACString &key);
+    void ReportSpdyAlternateProtocol(nsHttpConnection *);
+    void RemoveSpdyAlternateProtocol(nsACString &key);
+
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called only on the socket thread.
     //-------------------------------------------------------------------------
 
     // removes the next transaction for the specified connection from the
     // pending transaction queue.
     void AddTransactionToPipeline(nsHttpPipeline *);
 
@@ -400,11 +406,18 @@ private:
 
     //
     // the connection table
     //
     // this table is indexed by connection key.  each entry is a
     // nsConnectionEntry object.
     //
     nsClassHashtable<nsCStringHashKey, nsConnectionEntry> mCT;
+
+    // this table is protected by the monitor
+    nsCStringHashSet mAlternateProtocolHash;
+    static PLDHashOperator TrimAlternateProtocolHash(PLDHashTable *table,
+                                                     PLDHashEntryHdr *hdr,
+                                                     PRUint32 number,
+                                                     void *closure);
 };
 
 #endif // !nsHttpConnectionMgr_h__