# HG changeset patch # Parent df08e2b0e5262897b9140096ec172bc1d6d746c6 # User Patrick McManus HTTP Alternate-Protocol response header for transitioning from plaintext http to spdy over ssl via npn on 443 diff --git a/netwerk/protocol/http/nsHttpAtomList.h b/netwerk/protocol/http/nsHttpAtomList.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") diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -196,16 +196,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) { @@ -4047,16 +4059,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. diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -340,16 +340,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"); @@ -634,16 +660,18 @@ nsHttpConnection::OnHeadersAvailable(nsA } else { mIdleTimeout = gHttpHandler->SpdyTimeout(); } LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout)); } + 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; diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -170,16 +170,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 mSocketTransport; nsCOMPtr mSocketIn; nsCOMPtr mSocketOut; diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -92,16 +92,17 @@ nsHttpConnectionMgr::nsHttpConnectionMgr , mMaxPersistConnsPerProxy(0) , mIsShuttingDown(PR_FALSE) , mNumActiveConns(0) , mNumIdleConns(0) , mTimeOfNextWakeUp(LL_MAXUINT) { LOG(("Creating nsHttpConnectionMgr @%x\n", this)); mCT.Init(); + mAlternateProtocolHash.Init(); } nsHttpConnectionMgr::~nsHttpConnectionMgr() { LOG(("Destroying nsHttpConnectionMgr @%x\n", this)); } nsresult @@ -461,16 +462,75 @@ 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.Get(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.Get(hostPortKey)) + return; + + if (mAlternateProtocolHash.Count() > 2000) + mAlternateProtocolHash.Enumerate(TrimAlternateProtocolHash, this); + + mAlternateProtocolHash.Put(hostPortKey, true); +} + +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(const nsACString &key, + bool &val, + void *closure) +{ + nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; + + if (self->mAlternateProtocolHash.Count() > 2000) + return PL_DHASH_REMOVE; + return PL_DHASH_STOP; +} + nsHttpConnectionMgr::nsConnectionEntry * nsHttpConnectionMgr::GetSpdyPreferred(nsACString &aDottedDecimal) { if (!gHttpHandler->IsSpdyEnabled() || !gHttpHandler->CoalesceSpdy() || aDottedDecimal.IsEmpty()) return nsnull; @@ -1256,16 +1316,17 @@ void nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); mCT.Enumerate(ShutdownPassCB, this); // signal shutdown complete ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mAlternateProtocolHash.Clear(); mon.Notify(); } void nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param) { LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -128,16 +128,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 *); @@ -399,11 +404,16 @@ private: // // the connection table // // this table is indexed by connection key. each entry is a // nsConnectionEntry object. // nsClassHashtable mCT; + + // this table is protected by the monitor + nsDataHashtable mAlternateProtocolHash; + static PLDHashOperator TrimAlternateProtocolHash(const nsACString &key, + bool &val, void *closure); }; #endif // !nsHttpConnectionMgr_h__