bug 378637 part 14 - https proxying for spdy31 and http2 r=hurley

--HG--
extra : rebase_source : 03a62d69b08f725c85f092b182a95de9c4eb7288
This commit is contained in:
Patrick McManus 2014-05-16 11:46:13 -04:00
Родитель 0a3b1e638a
Коммит d52c2946ac
10 изменённых файлов: 508 добавлений и 88 удалений

Просмотреть файл

@ -898,7 +898,7 @@ nsresult
Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput,
const nsACString &method, const nsACString &path,
const nsACString &host, const nsACString &scheme,
nsACString &output)
bool connectForm, nsACString &output)
{
mAlternateReferenceSet.Clear();
mImpliedReferenceSet.Clear();
@ -917,10 +917,15 @@ Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput,
}
// colon headers first
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false);
if (!connectForm) {
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false);
} else {
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false);
ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false);
}
// now the non colon headers
const char *beginBuffer = nvInput.BeginReading();

Просмотреть файл

@ -164,7 +164,7 @@ public:
nsresult EncodeHeaderBlock(const nsCString &nvInput,
const nsACString &method, const nsACString &path,
const nsACString &host, const nsACString &scheme,
nsACString &output);
bool connectForm, nsACString &output);
int64_t GetParsedContentLength() { return mParsedContentLength; } // -1 on not found

Просмотреть файл

@ -386,23 +386,23 @@ Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction,
return false;
}
// assert that
// a] in the case we have a connection, that the new transaction connection
// is either undefined or on the same connection
// b] in the case we don't have a connection, that the new transaction
// connection is defined so we can adopt it
MOZ_ASSERT((mConnection && (!aHttpTransaction->Connection() ||
mConnection == aHttpTransaction->Connection())) ||
(!mConnection && aHttpTransaction->Connection()));
if (!mConnection) {
mConnection = aHttpTransaction->Connection();
}
aHttpTransaction->SetConnection(this);
if (aUseTunnel) {
LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel",
this, aHttpTransaction));
DispatchOnTunnel(aHttpTransaction, aCallbacks);
return true;
}
Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority);
LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)",
this, stream, mNextStreamID));
LOG3(("Http2Session::AddStream session=%p stream=%p serial=%u "
"NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
mStreamTransactionHash.Put(aHttpTransaction, stream);
@ -991,6 +991,10 @@ Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult)
RemoveStreamFromQueues(aStream);
if (aStream->IsTunnel()) {
UnRegisterTunnel(aStream);
}
// Send the stream the close() indication
aStream->Close(aResult);
}
@ -1138,7 +1142,11 @@ Http2Session::ResponseHeadersComplete()
// only do this once, afterwards ignore trailers
if (mInputFrameDataStream->AllHeadersReceived())
return NS_OK;
mInputFrameDataStream->SetAllHeadersReceived(true);
nsresult rv = mInputFrameDataStream->SetAllHeadersReceived(true);
if (NS_FAILED(rv)) {
return rv;
}
// The stream needs to see flattened http headers
// Uncompressed http/2 format headers currently live in
@ -1146,11 +1154,23 @@ Http2Session::ResponseHeadersComplete()
// mFlatHTTPResponseHeaders via ConvertHeaders()
mFlatHTTPResponseHeadersOut = 0;
nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
mDecompressBuffer,
mFlatHTTPResponseHeaders);
if (NS_FAILED(rv))
rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor,
mDecompressBuffer,
mFlatHTTPResponseHeaders);
if (rv == NS_ERROR_ABORT) {
LOG(("Http2Session::ResponseHeadersComplete ConvertResponseHeaders aborted\n"));
if (mInputFrameDataStream->IsTunnel()) {
gHttpHandler->ConnMgr()->CancelTransactions(
mInputFrameDataStream->Transaction()->ConnectionInfo(),
NS_ERROR_CONNECTION_REFUSED);
}
CleanupStream(mInputFrameDataStream, rv, CANCEL_ERROR);
ResetDownstreamState();
return NS_OK;
}
else if (NS_FAILED(rv)) {
return rv;
}
ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS);
return NS_OK;
@ -2278,6 +2298,7 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
mNeedsCleanup = nullptr; /* just in case */
Http2Stream *stream = mInputFrameDataStream;
mSegmentWriter = writer;
rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
mSegmentWriter = nullptr;
@ -2288,7 +2309,6 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
// This will happen when the transaction figures out it is EOF, generally
// due to a content-length match being made. Return OK from this function
// otherwise the whole session would be torn down.
Http2Stream *stream = mInputFrameDataStream;
// if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
// back to PROCESSING_DATA_FRAME where we came from
@ -2302,8 +2322,8 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer,
this, stream, stream ? stream->StreamID() : 0,
mNeedsCleanup, rv));
CleanupStream(stream, NS_OK, CANCEL_ERROR);
MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
mNeedsCleanup = nullptr; /* just in case */
MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup == stream);
mNeedsCleanup = nullptr;
return NS_OK;
}
@ -2794,6 +2814,80 @@ Http2Session::ConnectPushedStream(Http2Stream *stream)
ForceRecv();
}
uint32_t
Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
uint32_t rv = 0;
mTunnelHash.Get(aConnInfo->HashKey(), &rv);
return rv;
}
void
Http2Session::RegisterTunnel(Http2Stream *aTunnel)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
uint32_t newcount = FindTunnelCount(ci) + 1;
mTunnelHash.Remove(ci->HashKey());
mTunnelHash.Put(ci->HashKey(), newcount);
LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]",
this, aTunnel, newcount, ci->HashKey().get()));
}
void
Http2Session::UnRegisterTunnel(Http2Stream *aTunnel)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
MOZ_ASSERT(FindTunnelCount(ci));
uint32_t newcount = FindTunnelCount(ci) - 1;
mTunnelHash.Remove(ci->HashKey());
if (newcount) {
mTunnelHash.Put(ci->HashKey(), newcount);
}
LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
this, aTunnel, newcount, ci->HashKey().get()));
}
void
Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
nsIInterfaceRequestor *aCallbacks)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
MOZ_ASSERT(trans);
LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
aHttpTransaction->SetConnection(nullptr);
// this transaction has done its work of setting up a tunnel, let
// the connection manager queue it if necessary
trans->SetDontRouteViaWildCard(true);
if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s",
this, ci->HashKey().get()));
nsRefPtr<SpdyConnectTransaction> connectTrans =
new SpdyConnectTransaction(ci, aCallbacks,
trans->Caps(), trans, this);
AddStream(connectTrans, trans->Priority(),
false, nullptr);
Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
MOZ_ASSERT(tunnel);
RegisterTunnel(tunnel);
}
// requeue it. The connection manager is responsible for actually putting
// this on the tunnel connection with the specific ci now that it
// has DontRouteViaWildCard set.
trans->EnableKeepAlive();
gHttpHandler->InitiateTransaction(trans, trans->Priority());
}
nsresult
Http2Session::BufferOutput(const char *buf,
uint32_t count,
@ -2894,6 +2988,12 @@ Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller)
mReadyForWrite.Push(stream);
SetWriteCallbacks();
// NSPR poll will not poll the network if there are non system PR_FileDesc's
// that are ready - so we can get into a deadlock waiting for the system IO
// to come back here if we don't force the send loop manually.
ForceSend();
}
void
@ -2905,6 +3005,7 @@ Http2Session::TransactionHasDataToWrite(Http2Stream *stream)
mReadyForWrite.Push(stream);
SetWriteCallbacks();
ForceSend();
}
bool

Просмотреть файл

@ -25,6 +25,7 @@ namespace net {
class Http2PushedStream;
class Http2Stream;
class nsHttpTransaction;
class Http2Session MOZ_FINAL : public ASpdySession
, public nsAHttpConnection
@ -445,6 +446,15 @@ private:
// by the load group and the serial number can be used as part of the cache key
// to make sure streams aren't shared across sessions.
uint64_t mSerial;
private:
/// connect tunnels
void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
void RegisterTunnel(Http2Stream *);
void UnRegisterTunnel(Http2Stream *);
uint32_t FindTunnelCount(nsHttpConnectionInfo *);
nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
};
} // namespace mozilla::net

Просмотреть файл

@ -19,6 +19,7 @@
#include "Http2Session.h"
#include "Http2Stream.h"
#include "Http2Push.h"
#include "TunnelUtils.h"
#include "mozilla/Telemetry.h"
#include "nsAlgorithm.h"
@ -67,6 +68,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
, mTotalSent(0)
, mTotalRead(0)
, mPushSource(nullptr)
, mIsTunnel(false)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@ -96,6 +98,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction,
Http2Stream::~Http2Stream()
{
ClearTransactionsBlockedOnTunnel();
mStreamID = Http2Session::kDeadStreamID;
}
@ -141,6 +144,9 @@ Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
rv = mTransaction->ReadSegments(this, count, countRead);
mSegmentReader = nullptr;
LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %x read=%d\n",
this, rv, *countRead));
// Check to see if the transaction's request could be written out now.
// If not, mark the stream for callback when writing can proceed.
if (NS_SUCCEEDED(rv) &&
@ -165,7 +171,7 @@ Http2Stream::ReadSegments(nsAHttpSegmentReader *reader,
if (!mBlockedOnRwin &&
!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, "
"mUpstreamState=%x",this, mStreamID, mUpstreamState));
"mUpstreamState=%x\n",this, mStreamID, mUpstreamState));
if (mSentFin) {
ChangeState(UPSTREAM_COMPLETE);
} else {
@ -268,6 +274,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
this, avail, mUpstreamState));
mFlatHttpRequestHeaders.Append(buf, avail);
nsHttpRequestHead *head = mTransaction->RequestHead();
// We can use the simple double crlf because firefox is the
// only client we are parsing
@ -290,17 +297,17 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
*countUsed = avail - (oldLen - endHeader) + 4;
mAllHeadersSent = 1;
nsAutoCString hostHeader;
nsAutoCString authorityHeader;
nsAutoCString hashkey;
mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
head->GetHeader(nsHttp::Host, authorityHeader);
CreatePushHashKey(NS_LITERAL_CSTRING("https"),
hostHeader, mSession->Serial(),
mTransaction->RequestHead()->RequestURI(),
CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
authorityHeader, mSession->Serial(),
head->RequestURI(),
mOrigin, hashkey);
// check the push cache for GET
if (mTransaction->RequestHead()->IsGet()) {
if (head->IsGet()) {
// from :scheme, :authority, :path
nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
SpdyPushCache *cache = nullptr;
@ -347,7 +354,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
LOG3(("Stream ID 0x%X [session=%p] for URI %s\n",
mStreamID, mSession,
nsCString(mTransaction->RequestHead()->RequestURI()).get()));
nsCString(head->RequestURI()).get()));
if (mStreamID >= 0x80000000) {
// streamID must fit in 31 bits. Evading This is theoretically possible
@ -366,28 +373,46 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
// of HTTP/2 headers by writing to mTxInlineFrame{sz}
nsCString compressedData;
nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
if (head->IsConnect()) {
MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
mIsTunnel = true;
mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
// Our normal authority has an implicit port, best to use an
// explicit one with a tunnel
nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
if (!ci) {
return NS_ERROR_UNEXPECTED;
}
authorityHeader = ci->GetHost();
authorityHeader.AppendLiteral(":");
authorityHeader.AppendInt(ci->Port());
}
mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
mTransaction->RequestHead()->Method(),
mTransaction->RequestHead()->RequestURI(),
hostHeader,
NS_LITERAL_CSTRING("https"),
head->Method(),
head->RequestURI(),
authorityHeader,
scheme,
head->IsConnect(),
compressedData);
// Determine whether to put the fin bit on the header frame or whether
// to wait for a data packet to put it on.
uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
if (mTransaction->RequestHead()->IsGet() ||
mTransaction->RequestHead()->IsConnect() ||
mTransaction->RequestHead()->IsHead()) {
// for GET, CONNECT, and HEAD place the fin bit right on the
if (head->IsGet() ||
head->IsHead()) {
// for GET and HEAD place the fin bit right on the
// header packet
SetSentFin(true);
firstFrameFlags |= Http2Session::kFlag_END_STREAM;
} else if (mTransaction->RequestHead()->IsPost() ||
mTransaction->RequestHead()->IsPut() ||
mTransaction->RequestHead()->IsOptions()) {
} else if (head->IsPost() ||
head->IsPut() ||
head->IsConnect() ||
head->IsOptions()) {
// place fin in a data frame even for 0 length messages for iterop
} else if (!mRequestBodyLenRemaining) {
// for other HTTP extension methods, rely on the content-length
@ -473,7 +498,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
// The size of the input headers is approximate
uint32_t ratio =
compressedData.Length() * 100 /
(11 + mTransaction->RequestHead()->RequestURI().Length() +
(11 + head->RequestURI().Length() +
mFlatHttpRequestHeaders.Length());
const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
@ -599,6 +624,10 @@ void
Http2Stream::UpdateTransportReadEvents(uint32_t count)
{
mTotalRead += count;
if (!mSocketTransport) {
return;
}
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_RECEIVING_FROM,
mTotalRead);
@ -831,6 +860,14 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
return NS_ERROR_ILLEGAL_VALUE;
}
if (mIsTunnel) {
nsresult errcode;
if (status.ToInteger(&errcode) != 200) {
LOG3(("Http2Stream %p Tunnel not 200", this));
return NS_ERROR_ABORT;
}
}
if (aHeadersIn.Length() && aHeadersOut.Length()) {
Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
uint32_t ratio =
@ -841,6 +878,11 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
aHeadersIn.Truncate();
aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n"));
LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
if (mIsTunnel) {
aHeadersOut.Truncate();
LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n",
this, mStreamID));
}
return NS_OK;
}
@ -891,6 +933,13 @@ Http2Stream::Close(nsresult reason)
mTransaction->Close(reason);
}
nsresult
Http2Stream::SetAllHeadersReceived(bool aStatus)
{
mAllHeadersReceived = aStatus ? 1 : 0;
return NS_OK;
}
bool
Http2Stream::AllowFlowControlledWrite()
{
@ -1057,11 +1106,15 @@ Http2Stream::OnReadSegment(const char *buf,
mSession->DecrementServerSessionWindow(dataLength);
mServerReceiveWindow -= dataLength;
LOG3(("Http2Stream %p id %x request len remaining %d, "
"count avail %d, chunk used %d",
LOG3(("Http2Stream %p id %x request len remaining %u, "
"count avail %u, chunk used %u",
this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
if (dataLength > mRequestBodyLenRemaining)
if (!dataLength && mRequestBodyLenRemaining) {
return NS_BASE_STREAM_WOULD_BLOCK;
}
if (dataLength > mRequestBodyLenRemaining) {
return NS_ERROR_UNEXPECTED;
}
mRequestBodyLenRemaining -= dataLength;
GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
ChangeState(SENDING_BODY);
@ -1127,6 +1180,28 @@ Http2Stream::OnWriteSegment(char *buf,
return NS_OK;
}
/// connect tunnels
void
Http2Stream::ClearTransactionsBlockedOnTunnel()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
if (!mIsTunnel) {
return;
}
gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
}
void
Http2Stream::MapStreamToHttpConnection()
{
nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
MOZ_ASSERT(qiTrans);
qiTrans->MapStreamToHttpConnection(mSocketTransport,
mTransaction->ConnectionInfo());
}
} // namespace mozilla::net
} // namespace mozilla

Просмотреть файл

@ -81,12 +81,15 @@ public:
void SetCountAsActive(bool aStatus) { mCountAsActive = aStatus ? 1 : 0; }
bool CountAsActive() { return mCountAsActive; }
void SetAllHeadersReceived(bool aStatus) { mAllHeadersReceived = aStatus ? 1 : 0; }
// returns failure if stream cannot be made ready and stream
// should be canceled
nsresult SetAllHeadersReceived(bool aStatus);
bool AllHeadersReceived() { return mAllHeadersReceived; }
void UpdateTransportSendEvents(uint32_t count);
void UpdateTransportReadEvents(uint32_t count);
// NS_ERROR_ABORT terminates stream, other failure terminates session
nsresult ConvertResponseHeaders(Http2Decompressor *, nsACString &, nsACString &);
nsresult ConvertPushHeaders(Http2Decompressor *, nsACString &, nsACString &);
@ -234,7 +237,8 @@ private:
// place the fin flag on the last data packet instead of waiting
// for a stream closed indication. Relying on stream close results
// in an extra 0-length runt packet and seems to have some interop
// problems with the google servers.
// problems with the google servers. Connect does rely on stream
// close by setting this to the max value.
int64_t mRequestBodyLenRemaining;
uint32_t mPriority;
@ -267,6 +271,15 @@ private:
// For Http2Push
Http2PushedStream *mPushSource;
/// connect tunnels
public:
bool IsTunnel() { return mIsTunnel; }
private:
void ClearTransactionsBlockedOnTunnel();
void MapStreamToHttpConnection();
bool mIsTunnel;
};
} // namespace mozilla::net

Просмотреть файл

@ -351,23 +351,23 @@ SpdySession31::AddStream(nsAHttpTransaction *aHttpTransaction,
return false;
}
// assert that
// a] in the case we have a connection, that the new transaction connection
// is either undefined or on the same connection
// b] in the case we don't have a connection, that the new transaction
// connection is defined so we can adopt it
MOZ_ASSERT((mConnection && (!aHttpTransaction->Connection() ||
mConnection == aHttpTransaction->Connection())) ||
(!mConnection && aHttpTransaction->Connection()));
if (!mConnection) {
mConnection = aHttpTransaction->Connection();
}
aHttpTransaction->SetConnection(this);
if (aUseTunnel) {
LOG3(("SpdySession31::AddStream session=%p trans=%p OnTunnel",
this, aHttpTransaction));
DispatchOnTunnel(aHttpTransaction, aCallbacks);
return true;
}
SpdyStream31 *stream = new SpdyStream31(aHttpTransaction, this, aPriority);
LOG3(("SpdySession31::AddStream session=%p stream=%p NextID=0x%X (tentative)",
this, stream, mNextStreamID));
LOG3(("SpdySession31::AddStream session=%p stream=%p serial=%u "
"NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID));
mStreamTransactionHash.Put(aHttpTransaction, stream);
@ -946,6 +946,10 @@ SpdySession31::CloseStream(SpdyStream31 *aStream, nsresult aResult)
RemoveStreamFromQueues(aStream);
if (aStream->IsTunnel()) {
UnRegisterTunnel(aStream);
}
// Send the stream the close() indication
aStream->Close(aResult);
}
@ -1067,7 +1071,13 @@ SpdySession31::HandleSynStream(SpdySession31 *self)
self->mPushedStreams.AppendElement(pushedStream);
// The pushed stream is unidirectional so it is fully open immediately
pushedStream->SetFullyOpen();
rv = pushedStream->SetFullyOpen();
if (NS_FAILED(rv)) {
LOG(("SpdySession31::HandleSynStream pushedstream fully open failed\n"));
self->CleanupStream(pushedStream, rv, RST_CANCEL);
self->ResetDownstreamState();
return NS_OK;
}
// Uncompress the response headers into a stream specific buffer, leaving them
// in spdy format for the time being.
@ -1140,10 +1150,10 @@ SpdySession31::HandleSynReply(SpdySession31 *self)
return NS_ERROR_ILLEGAL_VALUE;
}
LOG3(("SpdySession31::HandleSynReply %p lookup via streamID in syn_reply.\n",
self));
uint32_t streamID =
PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
LOG3(("SpdySession31::HandleSynReply %p lookup via streamID 0x%X in syn_reply.\n",
self, streamID));
nsresult rv = self->SetInputFrameDataStream(streamID);
if (NS_FAILED(rv))
return rv;
@ -1206,7 +1216,19 @@ SpdySession31::HandleSynReply(SpdySession31 *self)
self->ResetDownstreamState();
return NS_OK;
}
self->mInputFrameDataStream->SetFullyOpen();
rv = self->mInputFrameDataStream->SetFullyOpen();
if (NS_FAILED(rv)) {
LOG(("SpdySession31::HandleSynReply SetFullyOpen failed\n"));
if (self->mInputFrameDataStream->IsTunnel()) {
gHttpHandler->ConnMgr()->CancelTransactions(
self->mInputFrameDataStream->Transaction()->ConnectionInfo(),
NS_ERROR_CONNECTION_REFUSED);
}
self->CleanupStream(self->mInputFrameDataStream, rv, RST_CANCEL);
self->ResetDownstreamState();
return NS_OK;
}
self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN;
self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize);
@ -2067,6 +2089,7 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer,
MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
mNeedsCleanup = nullptr; /* just in case */
SpdyStream31 *stream = mInputFrameDataStream;
mSegmentWriter = writer;
rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
mSegmentWriter = nullptr;
@ -2077,7 +2100,6 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer,
// This will happen when the transaction figures out it is EOF, generally
// due to a content-length match being made. Return OK from this function
// otherwise the whole session would be torn down.
SpdyStream31 *stream = mInputFrameDataStream;
// if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state
// back to PROCESSING_DATA_FRAME where we came from
@ -2091,8 +2113,8 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer,
this, stream, stream ? stream->StreamID() : 0,
mNeedsCleanup, rv));
CleanupStream(stream, NS_OK, RST_CANCEL);
MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
mNeedsCleanup = nullptr; /* just in case */
MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup == stream);
mNeedsCleanup = nullptr;
return NS_OK;
}
@ -2579,6 +2601,79 @@ SpdySession31::ConnectPushedStream(SpdyStream31 *stream)
ForceRecv();
}
uint32_t
SpdySession31::FindTunnelCount(nsHttpConnectionInfo *aConnInfo)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
uint32_t rv = 0;
mTunnelHash.Get(aConnInfo->HashKey(), &rv);
return rv;
}
void
SpdySession31::RegisterTunnel(SpdyStream31 *aTunnel)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
uint32_t newcount = FindTunnelCount(ci) + 1;
mTunnelHash.Remove(ci->HashKey());
mTunnelHash.Put(ci->HashKey(), newcount);
LOG3(("SpdySession31::RegisterTunnel %p stream=%p tunnels=%d [%s]",
this, aTunnel, newcount, ci->HashKey().get()));
}
void
SpdySession31::UnRegisterTunnel(SpdyStream31 *aTunnel)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo();
MOZ_ASSERT(FindTunnelCount(ci));
uint32_t newcount = FindTunnelCount(ci) - 1;
mTunnelHash.Remove(ci->HashKey());
if (newcount) {
mTunnelHash.Put(ci->HashKey(), newcount);
}
LOG3(("SpdySession31::UnRegisterTunnel %p stream=%p tunnels=%d [%s]",
this, aTunnel, newcount, ci->HashKey().get()));
}
void
SpdySession31::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction,
nsIInterfaceRequestor *aCallbacks)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction();
nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo();
MOZ_ASSERT(trans);
LOG3(("SpdySession31::DispatchOnTunnel %p trans=%p", this, trans));
aHttpTransaction->SetConnection(nullptr);
// this transaction has done its work of setting up a tunnel, let
// the connection manager queue it if necessary
trans->SetDontRouteViaWildCard(true);
if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
LOG3(("SpdySession31::DispatchOnTunnel %p create on new tunnel %s",
this, ci->HashKey().get()));
nsRefPtr<SpdyConnectTransaction> connectTrans =
new SpdyConnectTransaction(ci, aCallbacks,
trans->Caps(), trans, this);
AddStream(connectTrans, trans->Priority(),
false, nullptr);
SpdyStream31 *tunnel = mStreamTransactionHash.Get(connectTrans);
MOZ_ASSERT(tunnel);
RegisterTunnel(tunnel);
}
// requeue it. The connection manager is responsible for actually putting
// this on the tunnel connection with the specific ci now that it
// has DontRouteViaWildCard set.
trans->EnableKeepAlive();
gHttpHandler->InitiateTransaction(trans, trans->Priority());
}
nsresult
SpdySession31::BufferOutput(const char *buf,
uint32_t count,
@ -2616,6 +2711,11 @@ SpdySession31::TransactionHasDataToWrite(nsAHttpTransaction *caller)
mReadyForWrite.Push(stream);
SetWriteCallbacks();
// NSPR poll will not poll the network if there are non system PR_FileDesc's
// that are ready - so we can get into a deadlock waiting for the system IO
// to come back here if we don't force the send loop manually.
ForceSend();
}
void
@ -2627,6 +2727,7 @@ SpdySession31::TransactionHasDataToWrite(SpdyStream31 *stream)
mReadyForWrite.Push(stream);
SetWriteCallbacks();
ForceSend();
}
bool

Просмотреть файл

@ -23,6 +23,7 @@ namespace mozilla { namespace net {
class SpdyPushedStream31;
class SpdyStream31;
class nsHttpTransaction;
class SpdySession31 MOZ_FINAL : public ASpdySession
, public nsAHttpConnection
@ -402,6 +403,15 @@ private:
// by the load group and the serial number can be used as part of the cache key
// to make sure streams aren't shared across sessions.
uint64_t mSerial;
private:
/// connect tunnels
void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
void RegisterTunnel(SpdyStream31 *);
void UnRegisterTunnel(SpdyStream31 *);
uint32_t FindTunnelCount(nsHttpConnectionInfo *);
nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
};
}} // namespace mozilla::net

Просмотреть файл

@ -24,6 +24,7 @@
#include "SpdyPush31.h"
#include "SpdySession31.h"
#include "SpdyStream31.h"
#include "TunnelUtils.h"
#include <algorithm>
@ -68,6 +69,7 @@ SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction,
, mTotalSent(0)
, mTotalRead(0)
, mPushSource(nullptr)
, mIsTunnel(false)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@ -82,6 +84,7 @@ SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction,
SpdyStream31::~SpdyStream31()
{
ClearTransactionsBlockedOnTunnel();
mStreamID = SpdySession31::kDeadStreamID;
}
@ -119,7 +122,8 @@ SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader,
mSegmentReader = reader;
rv = mTransaction->ReadSegments(this, count, countRead);
mSegmentReader = nullptr;
LOG3(("SpdyStream31::ReadSegments %p trans readsegments rv %x read=%d\n",
this, rv, *countRead));
// Check to see if the transaction's request could be written out now.
// If not, mark the stream for callback when writing can proceed.
if (NS_SUCCEEDED(rv) &&
@ -144,7 +148,8 @@ SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader,
if (!mBlockedOnRwin &&
!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
LOG3(("SpdyStream31::ReadSegments %p 0x%X: Sending request data complete, "
"mUpstreamState=%x",this, mStreamID, mUpstreamState));
"mUpstreamState=%x finondata=%d",this, mStreamID,
mUpstreamState, mSentFinOnData));
if (mSentFinOnData) {
ChangeState(UPSTREAM_COMPLETE);
}
@ -464,24 +469,51 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf,
// headers at this same level so it is not necessary to do so here.
const char *methodHeader = mTransaction->RequestHead()->Method().get();
LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader));
// The header block length
uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */
uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */
if (mTransaction->RequestHead()->IsConnect()) {
mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
} else {
++count; // :scheme used if not connect
}
CompressToFrame(count);
// :method, :path, :version comprise a HTTP/1 request line, so send those first
// to make life easy for any gateways
CompressToFrame(NS_LITERAL_CSTRING(":method"));
CompressToFrame(methodHeader, strlen(methodHeader));
CompressToFrame(NS_LITERAL_CSTRING(":path"));
CompressToFrame(mTransaction->RequestHead()->RequestURI());
if (!mTransaction->RequestHead()->IsConnect()) {
CompressToFrame(mTransaction->RequestHead()->RequestURI());
} else {
MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
mIsTunnel = true;
// Connect places host:port in :path. Don't use default port.
nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
if (!ci) {
return NS_ERROR_UNEXPECTED;
}
nsAutoCString route;
route = ci->GetHost();
route.AppendLiteral(":");
route.AppendInt(ci->Port());
CompressToFrame(route);
}
CompressToFrame(NS_LITERAL_CSTRING(":version"));
CompressToFrame(versionHeader);
CompressToFrame(NS_LITERAL_CSTRING(":host"));
CompressToFrame(hostHeader);
CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"));
if (!mTransaction->RequestHead()->IsConnect()) {
// no :scheme with connect
CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"));
}
hdrHash.Enumerate(hdrHashEnumerate, this);
CompressFlushFrame();
@ -496,9 +528,8 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf,
// to wait for a data packet to put it on.
if (mTransaction->RequestHead()->IsGet() ||
mTransaction->RequestHead()->IsConnect() ||
mTransaction->RequestHead()->IsHead()) {
// for GET, CONNECT, and HEAD place the fin bit right on the
// for GET and HEAD place the fin bit right on the
// syn stream packet
mSentFinOnData = 1;
@ -506,6 +537,7 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf,
}
else if (mTransaction->RequestHead()->IsPost() ||
mTransaction->RequestHead()->IsPut() ||
mTransaction->RequestHead()->IsConnect() ||
mTransaction->RequestHead()->IsOptions()) {
// place fin in a data frame even for 0 length messages, I've seen
// the google gateway be unhappy with fin-on-syn for 0 length POST
@ -600,6 +632,9 @@ void
SpdyStream31::UpdateTransportReadEvents(uint32_t count)
{
mTotalRead += count;
if (!mSocketTransport) {
return;
}
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_RECEIVING_FROM,
@ -1262,6 +1297,12 @@ SpdyStream31::ConvertHeaders(nsACString &aHeadersOut)
mDecompressBufferSize = 0;
mDecompressBufferUsed = 0;
if (mIsTunnel) {
aHeadersOut.Truncate();
LOG(("SpdyStream31::ConvertHeaders %p 0x%X headers removed for tunnel\n",
this, mStreamID));
}
return NS_OK;
}
@ -1327,6 +1368,38 @@ SpdyStream31::CompressFlushFrame()
ExecuteCompress(Z_SYNC_FLUSH);
}
bool
SpdyStream31::GetFullyOpen()
{
return mFullyOpen;
}
nsresult
SpdyStream31::SetFullyOpen()
{
MOZ_ASSERT(!mFullyOpen);
mFullyOpen = 1;
if (mIsTunnel) {
nsDependentCSubstring statusSubstring;
nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
statusSubstring);
if (NS_SUCCEEDED(rv)) {
nsCString status(statusSubstring);
nsresult errcode;
if (status.ToInteger(&errcode) != 200) {
LOG3(("SpdyStream31::SetFullyOpen %p Tunnel not 200", this));
return NS_ERROR_FAILURE;
}
LOG3(("SpdyStream31::SetFullyOpen %p Tunnel 200 OK", this));
}
MapStreamToHttpConnection();
ClearTransactionsBlockedOnTunnel();
}
return NS_OK;
}
void
SpdyStream31::Close(nsresult reason)
{
@ -1420,11 +1493,15 @@ SpdyStream31::OnReadSegment(const char *buf,
mRemoteWindow -= dataLength;
mSession->DecrementRemoteSessionWindow(dataLength);
LOG3(("SpdyStream31 %p id %x request len remaining %d, "
"count avail %d, chunk used %d",
LOG3(("SpdyStream31 %p id %x request len remaining %u, "
"count avail %u, chunk used %u",
this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
if (dataLength > mRequestBodyLenRemaining)
if (!dataLength && mRequestBodyLenRemaining) {
return NS_BASE_STREAM_WOULD_BLOCK;
}
if (dataLength > mRequestBodyLenRemaining) {
return NS_ERROR_UNEXPECTED;
}
mRequestBodyLenRemaining -= dataLength;
GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
ChangeState(SENDING_REQUEST_BODY);
@ -1490,6 +1567,27 @@ SpdyStream31::OnWriteSegment(char *buf,
return NS_OK;
}
/// connect tunnels
void
SpdyStream31::ClearTransactionsBlockedOnTunnel()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
if (!mIsTunnel) {
return;
}
gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
}
void
SpdyStream31::MapStreamToHttpConnection()
{
nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
MOZ_ASSERT(qiTrans);
qiTrans->MapStreamToHttpConnection(mSocketTransport,
mTransaction->ConnectionInfo());
}
} // namespace mozilla::net
} // namespace mozilla

Просмотреть файл

@ -34,13 +34,10 @@ public:
return static_cast<bool>(mRequestBlockedOnRead);
}
// returns false if called more than once
bool GetFullyOpen() {return mFullyOpen;}
void SetFullyOpen()
{
MOZ_ASSERT(!mFullyOpen);
mFullyOpen = 1;
}
bool GetFullyOpen();
// returns failure if stream cannot be made ready and stream
// should be canceled
nsresult SetFullyOpen();
bool HasRegisteredID() { return mStreamID != 0; }
@ -215,7 +212,8 @@ private:
// place the fin flag on the last data packet instead of waiting
// for a stream closed indication. Relying on stream close results
// in an extra 0-length runt packet and seems to have some interop
// problems with the google servers.
// problems with the google servers. Connect does rely on stream
// close by setting this to the max value.
int64_t mRequestBodyLenRemaining;
// based on nsISupportsPriority definitions
@ -248,6 +246,15 @@ private:
// For SpdyPush
SpdyPushedStream31 *mPushSource;
/// connect tunnels
public:
bool IsTunnel() { return mIsTunnel; }
private:
void ClearTransactionsBlockedOnTunnel();
void MapStreamToHttpConnection();
bool mIsTunnel;
};
}} // namespace mozilla::net