bug 1024730 - nsIHttpPushListener r=hurley

co-author: ben brittain <ben@brittain.org>
This commit is contained in:
Patrick McManus 2014-10-21 14:35:41 -04:00
Родитель f5046854b9
Коммит bc4244f5a9
20 изменённых файлов: 579 добавлений и 57 удалений

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

@ -43,6 +43,7 @@ XPIDL_SOURCES += [
'nsIFileStreams.idl',
'nsIFileURL.idl',
'nsIForcePendingChannel.idl',
'nsIHttpPushListener.idl',
'nsIIncrementalDownload.idl',
'nsIInputStreamChannel.idl',
'nsIInputStreamPump.idl',

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

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIHttpChannel;
/**
* nsIHttpPushListener
*
* Used for triggering when a HTTP/2 push is received.
*
*/
[scriptable, uuid(0d6ce59c-ad5d-4520-b4d3-09664868f279)]
interface nsIHttpPushListener : nsISupports
{
/**
* When provided as a notificationCallback to an httpChannel, this.onPush()
* will be invoked when there is a >= Http2 push to that
* channel. The push may be in progress.
*
* The consumer must start the new channel in the usual way by calling
* pushChannel.AsyncOpen with a nsIStreamListener object that
* will receive the normal sequence of OnStartRequest(),
* 0 to N OnDataAvailable(), and onStopRequest().
*
* The new channel can be canceled after the AsyncOpen if it is not wanted.
*
* @param associatedChannel
* the monitor channel that was recieved on
* @param pushChannel
* a channel to the resource which is being pushed
*/
void onPush(in nsIHttpChannel associatedChannel,
in nsIHttpChannel pushChannel);
};

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

@ -579,6 +579,18 @@
{0xb5, 0x27, 0x8a, 0x64, 0x30, 0x56, 0xab, 0xbd} \
}
// component implementing nsIHttpPushListener.
#define NS_HTTPPUSHLISTENER_CONTRACTID \
"@mozilla.org/network/push-listener;1"
#define NS_HTTPPUSHLISTENER_CID \
{ \
0x73cf4430, \
0x5877, \
0x4c6b, \
{0xb8, 0x78, 0x3e, 0xde, 0x5b, 0xc8, 0xef, 0xf1} \
}
#define NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID \
"@mozilla.org/network/http-activity-distributor;1"
#define NS_HTTPACTIVITYDISTRIBUTOR_CID \

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

@ -147,8 +147,11 @@ SpdyPushCache::RegisterPushedStreamSpdy3(nsCString key,
{
LOG3(("SpdyPushCache::RegisterPushedStreamSpdy3 %s 0x%X\n",
key.get(), stream->StreamID()));
if(mHashSpdy3.Get(key))
if(mHashSpdy3.Get(key)) {
LOG3(("SpdyPushCache::RegisterPushedStreamSpdy3 %s 0x%X duplicate key\n",
key.get(), stream->StreamID()));
return false;
}
mHashSpdy3.Put(key, stream);
return true;
}
@ -170,8 +173,11 @@ SpdyPushCache::RegisterPushedStreamSpdy31(nsCString key,
{
LOG3(("SpdyPushCache::RegisterPushedStreamSpdy31 %s 0x%X\n",
key.get(), stream->StreamID()));
if(mHashSpdy31.Get(key))
if(mHashSpdy31.Get(key)) {
LOG3(("SpdyPushCache::RegisterPushedStreamSpdy31 %s 0x%X duplicate key\n",
key.get(), stream->StreamID()));
return false;
}
mHashSpdy31.Put(key, stream);
return true;
}
@ -193,8 +199,11 @@ SpdyPushCache::RegisterPushedStreamHttp2(nsCString key,
{
LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X\n",
key.get(), stream->StreamID()));
if(mHashHttp2.Get(key))
if(mHashHttp2.Get(key)) {
LOG3(("SpdyPushCache::RegisterPushedStreamHttp2 %s 0x%X duplicate key\n",
key.get(), stream->StreamID()));
return false;
}
mHashHttp2.Put(key, stream);
return true;
}

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

@ -16,12 +16,45 @@
#include <algorithm>
#include "Http2Push.h"
#include "nsDependentString.h"
#include "nsHttpChannel.h"
#include "nsIHttpPushListener.h"
#include "nsString.h"
namespace mozilla {
namespace net {
class CallChannelOnPush MOZ_FINAL : public nsRunnable {
public:
CallChannelOnPush(nsIHttpChannelInternal *associatedChannel,
const nsACString &pushedURI,
Http2PushedStream *pushStream)
: mAssociatedChannel(associatedChannel)
, mPushedURI(pushedURI)
, mPushedStream(pushStream)
{
}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<nsHttpChannel> channel;
CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
MOZ_ASSERT(channel);
if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) {
return NS_OK;
}
LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
mPushedStream->OnPushFailed();
return NS_OK;
}
private:
nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
const nsCString mPushedURI;
Http2PushedStream *mPushedStream;
};
//////////////////////////////////////////
// Http2PushedStream
//////////////////////////////////////////
@ -32,10 +65,13 @@ Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction,
uint32_t aID)
:Http2Stream(aTransaction, aSession, 0)
, mConsumerStream(nullptr)
, mAssociatedTransaction(aAssociatedStream->Transaction())
, mBufferedPush(aTransaction)
, mStatus(NS_OK)
, mPushCompleted(false)
, mDeferCleanupOnSuccess(true)
, mDeferCleanupOnPush(false)
, mOnPushFailed(false)
{
LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
mStreamID = aID;
@ -70,6 +106,51 @@ Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer,
return rv;
}
bool
Http2PushedStream::DeferCleanup(nsresult status)
{
LOG3(("Http2PushedStream::DeferCleanup Query %p %x\n", this, status));
if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
LOG3(("Http2PushedStream::DeferCleanup %p %x defer on success\n", this, status));
return true;
}
if (mDeferCleanupOnPush) {
LOG3(("Http2PushedStream::DeferCleanup %p %x defer onPush ref\n", this, status));
return true;
}
if (mConsumerStream) {
LOG3(("Http2PushedStream::DeferCleanup %p %x defer active consumer\n", this, status));
return true;
}
LOG3(("Http2PushedStream::DeferCleanup Query %p %x not deferred\n", this, status));
return false;
}
// return true if channel implements nsIHttpPushListener
bool
Http2PushedStream::TryOnPush()
{
nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction();
if (!trans) {
return false;
}
nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel());
if (!associatedChannel) {
return false;
}
if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
return false;
}
mDeferCleanupOnPush = true;
nsCString uri = Origin() + Path();
NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this));
return true;
}
nsresult
Http2PushedStream::ReadSegments(nsAHttpSegmentReader *,
uint32_t, uint32_t *count)
@ -91,6 +172,13 @@ Http2PushedStream::ReadSegments(nsAHttpSegmentReader *,
return NS_OK;
}
void
Http2PushedStream::SetConsumerStream(Http2Stream *consumer)
{
mConsumerStream = consumer;
mDeferCleanupOnPush = false;
}
bool
Http2PushedStream::GetHashKey(nsCString &key)
{
@ -115,8 +203,13 @@ Http2PushedStream::IsOrphaned(TimeStamp now)
// if session is not transmitting, and is also not connected to a consumer
// stream, and its been like that for too long then it is oprhaned
if (mConsumerStream)
if (mConsumerStream || mDeferCleanupOnPush) {
return false;
}
if (mOnPushFailed) {
return true;
}
bool rv = ((now - mLastRead).ToSeconds() > 30.0);
if (rv) {

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

@ -31,8 +31,11 @@ public:
virtual ~Http2PushedStream() {}
bool GetPushComplete();
Http2Stream *GetConsumerStream() { return mConsumerStream; };
void SetConsumerStream(Http2Stream *aStream) { mConsumerStream = aStream; }
// The consumer stream is the synthetic pull stream hooked up to this push
virtual Http2Stream *GetConsumerStream() MOZ_OVERRIDE { return mConsumerStream; };
void SetConsumerStream(Http2Stream *aStream);
bool GetHashKey(nsCString &key);
// override of Http2Stream
@ -42,16 +45,21 @@ public:
nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI; };
void ConnectPushedStream(Http2Stream *consumer);
bool DeferCleanupOnSuccess() { return mDeferCleanupOnSuccess; }
bool TryOnPush();
virtual bool DeferCleanup(nsresult status) MOZ_OVERRIDE;
void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; }
bool IsOrphaned(TimeStamp now);
void OnPushFailed() { mDeferCleanupOnPush = false; mOnPushFailed = true; }
nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten);
// overload of Http2Stream
virtual bool HasSink() { return !!mConsumerStream; }
nsCString &GetRequestString() { return mRequestString; }
private:
Http2Stream *mConsumerStream; // paired request stream that consumes from
@ -59,6 +67,8 @@ private:
nsCOMPtr<nsILoadGroupConnectionInfo> mLoadGroupCI;
nsAHttpTransaction *mAssociatedTransaction;
Http2PushTransactionBuffer *mBufferedPush;
mozilla::TimeStamp mLastRead;
@ -66,6 +76,17 @@ private:
nsresult mStatus;
bool mPushCompleted; // server push FIN received
bool mDeferCleanupOnSuccess;
// mDeferCleanupOnPush prevents Http2Session::CleanupStream() from
// destroying the push stream on an error code during the period between
// when we need to do OnPush() on another thread and the time it takes
// for that event to create a synthetic pull stream attached to this
// object. That synthetic pull will become mConsuemerStream.
// Ths is essentially a delete protecting reference.
bool mDeferCleanupOnPush;
bool mOnPushFailed;
nsCString mRequestString;
};
class Http2PushTransactionBuffer MOZ_FINAL : public nsAHttpTransaction

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

@ -833,7 +833,7 @@ Http2Session::SendHello()
}
// Advertise the Push RWIN for the session, and on each new pull stream
// send a window update with END_FLOW_CONTROL
// send a window update
CopyAsNetwork16(packet + kFrameHeaderBytes + (6 * numberOfEntries), SETTINGS_TYPE_INITIAL_WINDOW);
CopyAsNetwork32(packet + kFrameHeaderBytes + (6 * numberOfEntries) + 2, mPushAllowance);
numberOfEntries++;
@ -942,9 +942,7 @@ Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
return;
}
Http2PushedStream *pushSource = nullptr;
if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) {
if (aStream->DeferCleanup(aResult)) {
LOG3(("Http2Session::CleanupStream 0x%X deferred\n", aStream->StreamID()));
return;
}
@ -954,11 +952,17 @@ Http2Session::CleanupStream(Http2Stream *aStream, nsresult aResult,
return;
}
pushSource = aStream->PushSource();
Http2PushedStream *pushSource = aStream->PushSource();
if (pushSource) {
// aStream is a synthetic attached to an even push
MOZ_ASSERT(pushSource->GetConsumerStream() == aStream);
MOZ_ASSERT(!aStream->StreamID());
MOZ_ASSERT(!(pushSource->StreamID() & 0x1));
pushSource->SetConsumerStream(nullptr);
}
if (!aStream->RecvdFin() && !aStream->RecvdReset() && aStream->StreamID()) {
LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
aResetCode));
LOG3(("Stream had not processed recv FIN, sending RST code %X\n", aResetCode));
GenerateRstStream(aResetCode, aStream->StreamID());
}
@ -1577,8 +1581,24 @@ Http2Session::RecvPushPromise(Http2Session *self)
new Http2PushTransactionBuffer();
transactionBuffer->SetConnection(self);
Http2PushedStream *pushedStream =
new Http2PushedStream(transactionBuffer, self,
associatedStream, promisedID);
new Http2PushedStream(transactionBuffer, self, associatedStream, promisedID);
self->mDecompressBuffer.Append(self->mInputFrameBuffer + kFrameHeaderBytes + paddingControlBytes + promiseLen,
self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
self->mDecompressBuffer,
pushedStream->GetRequestString());
if (rv == NS_ERROR_NOT_IMPLEMENTED) {
LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
delete pushedStream;
return NS_OK;
}
if (NS_FAILED(rv))
return rv;
// Ownership of the pushed stream is by the transaction hash, just as it
// is for a client initiated stream. Errors that aren't fatal to the
@ -1587,22 +1607,6 @@ Http2Session::RecvPushPromise(Http2Session *self)
self->mStreamTransactionHash.Put(transactionBuffer, pushedStream);
self->mPushedStreams.AppendElement(pushedStream);
self->mDecompressBuffer.Append(self->mInputFrameBuffer + kFrameHeaderBytes + paddingControlBytes + promiseLen,
self->mInputFrameDataSize - paddingControlBytes - promiseLen - paddingLength);
nsAutoCString requestHeaders;
rv = pushedStream->ConvertPushHeaders(&self->mDecompressor,
self->mDecompressBuffer, requestHeaders);
if (rv == NS_ERROR_NOT_IMPLEMENTED) {
LOG3(("Http2Session::PushPromise Semantics not Implemented\n"));
self->GenerateRstStream(REFUSED_STREAM_ERROR, promisedID);
return NS_OK;
}
if (NS_FAILED(rv))
return rv;
if (self->RegisterStreamID(pushedStream, promisedID) == kDeadStreamID) {
LOG3(("Http2Session::RecvPushPromise registerstreamid failed\n"));
self->mGoAwayReason = INTERNAL_ERROR;
@ -1626,19 +1630,28 @@ Http2Session::RecvPushPromise(Http2Session *self)
}
if (!associatedStream->Origin().Equals(pushedStream->Origin())) {
LOG3(("Http2Session::RecvPushPromise pushed stream mismatched origin\n"));
LOG3(("Http2Session::RecvPushPromise %p pushed stream mismatched origin "
"associated origin %s .. pushed origin %s\n", self,
associatedStream->Origin().get(), pushedStream->Origin().get()));
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, REFUSED_STREAM_ERROR);
self->ResetDownstreamState();
return NS_OK;
}
if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
self->ResetDownstreamState();
return NS_OK;
if (pushedStream->TryOnPush()) {
LOG3(("Http2Session::RecvPushPromise %p channel implements nsIHttpPushListener "
"stream %p will not be placed into session cache.\n", self, pushedStream));
} else {
LOG3(("Http2Session::RecvPushPromise %p place stream into session cache\n", self));
if (!cache->RegisterPushedStreamHttp2(key, pushedStream)) {
LOG3(("Http2Session::RecvPushPromise registerPushedStream Failed\n"));
self->CleanupStream(pushedStream, NS_ERROR_FAILURE, INTERNAL_ERROR);
self->ResetDownstreamState();
return NS_OK;
}
}
pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
static_assert(Http2Stream::kWorstPriority >= 0,
"kWorstPriority out of range");
uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -

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

@ -312,15 +312,32 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf,
// from :scheme, :authority, :path
nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
SpdyPushCache *cache = nullptr;
if (loadGroupCI)
if (loadGroupCI) {
loadGroupCI->GetSpdyPushCache(&cache);
}
Http2PushedStream *pushedStream = nullptr;
// If a push stream is attached to the transaction via onPush, match only with that
// one. This occurs when a push was made with in conjunction with a nsIHttpPushListener
nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
if (trans && (pushedStream = trans->TakePushedStream())) {
if (pushedStream->mSession == mSession) {
LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream));
} else {
LOG3(("Pushed Stream match failed due to stream mismatch %p %d %d\n", pushedStream,
pushedStream->mSession->Serial(), mSession->Serial()));
pushedStream->OnPushFailed();
pushedStream = nullptr;
}
}
// we remove the pushedstream from the push cache so that
// it will not be used for another GET. This does not destroy the
// stream itself - that is done when the transactionhash is done with it.
if (cache)
pushedStream = cache->RemovePushedStreamHttp2(hashkey);
if (cache && !pushedStream){
pushedStream = cache->RemovePushedStreamHttp2(hashkey);
}
LOG3(("Pushed Stream Lookup "
"session=%p key=%s loadgroupci=%p cache=%p hit=%p\n",
@ -569,6 +586,17 @@ Http2Stream::AdjustInitialWindow()
return;
}
if (stream->mState == RESERVED_BY_REMOTE) {
// h2-14 prevents sending a window update in this state
return;
}
MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
if (!bump) { // nothing to do
return;
}
uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 4,
mTxInlineFrameUsed, mTxInlineFrameSize);
@ -578,8 +606,6 @@ Http2Stream::AdjustInitialWindow()
Http2Session::FRAME_TYPE_WINDOW_UPDATE,
0, stream->mStreamID);
MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin);
uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow;
mClientReceiveWindow += bump;
bump = PR_htonl(bump);
memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
@ -835,7 +861,7 @@ Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
mTxStreamFrameSize = dataLength;
}
// ConvertHeaders is used to convert the response headers
// ConvertResponseHeaders is used to convert the response headers
// into HTTP/1 format and report some telemetry
nsresult
Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
@ -851,14 +877,14 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
aHeadersIn.Length(),
aHeadersOut, false);
if (NS_FAILED(rv)) {
LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this));
LOG3(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this));
return NS_ERROR_ILLEGAL_VALUE;
}
nsAutoCString statusString;
decompressor->GetStatus(statusString);
if (statusString.IsEmpty()) {
LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this));
LOG3(("Http2Stream::ConvertResponseHeaders %p Error - no status\n", this));
return NS_ERROR_ILLEGAL_VALUE;
}
@ -873,7 +899,7 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
if (httpResponseCode == 101) {
// 8.1.1 of h2 disallows 101.. throw PROTOCOL_ERROR on stream
LOG3(("Http2Stream::ConvertHeaders %p Error - status == 101\n", this));
LOG3(("Http2Stream::ConvertResponseHeaders %p Error - status == 101\n", this));
return NS_ERROR_ILLEGAL_VALUE;
}
@ -901,7 +927,7 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor,
return NS_OK;
}
// ConvertHeaders is used to convert the response headers
// ConvertPushHeaders is used to convert the pushed request headers
// into HTTP/1 format and report some telemetry
nsresult
Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
@ -909,6 +935,7 @@ Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor,
nsACString &aHeadersOut)
{
aHeadersOut.Truncate();
aHeadersOut.SetCapacity(aHeadersIn.Length() + 512);
nsresult rv =
decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()),
aHeadersIn.Length(),
@ -955,6 +982,14 @@ Http2Stream::SetAllHeadersReceived()
return;
}
if (mState == RESERVED_BY_REMOTE) {
// pushed streams needs to wait until headers have
// arrived to open up their window
LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n", this));
mState = OPEN;
AdjustInitialWindow();
}
mAllHeadersReceived = 1;
if (mIsTunnel) {
MapStreamToHttpConnection();

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

@ -47,9 +47,15 @@ public:
virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *);
virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
virtual bool DeferCleanupOnSuccess() { return false; }
virtual bool DeferCleanup(nsresult status) { return false; }
// The consumer stream is the synthetic pull stream hooked up to this stream
// http2PushedStream overrides it
virtual Http2Stream *GetConsumerStream() { return nullptr; };
const nsAFlatCString &Origin() const { return mOrigin; }
const nsAFlatCString &Host() const { return mHeaderHost; }
const nsAFlatCString &Path() const { return mHeaderPath; }
bool RequestBlockedOnRead()
{
@ -125,6 +131,8 @@ public:
virtual ~Http2Stream();
Http2Session *Session() { return mSession; }
protected:
static void CreatePushHashKey(const nsCString &scheme,
const nsCString &hostHeader,

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

@ -86,6 +86,9 @@ typedef uint8_t nsHttpVersion;
// weaker security profiles based on past history
#define NS_HTTP_ALLOW_RSA_FALSESTART (1<<9)
// This flag indicates the transaction should accept associated pushes
#define NS_HTTP_ONPUSH_LISTENER (1<<10)
//-----------------------------------------------------------------------------
// some default values
//-----------------------------------------------------------------------------

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

@ -54,6 +54,7 @@
#include "nsICacheEntryDescriptor.h"
#include "nsICancelable.h"
#include "nsIHttpChannelAuthProvider.h"
#include "nsIHttpChannelInternal.h"
#include "nsIHttpEventSink.h"
#include "nsIPrompt.h"
#include "nsInputStreamPump.h"
@ -67,6 +68,7 @@
#include "mozilla/Telemetry.h"
#include "AlternateServices.h"
#include "InterceptedChannel.h"
#include "nsIHttpPushListener.h"
namespace mozilla { namespace net {
@ -232,6 +234,7 @@ nsHttpChannel::nsHttpChannel()
, mConcurentCacheAccess(0)
, mIsPartialRequest(0)
, mHasAutoRedirectVetoNotifier(0)
, mPushedStream(nullptr)
, mDidReval(false)
{
LOG(("Creating nsHttpChannel [this=%p]\n", this));
@ -813,6 +816,20 @@ nsHttpChannel::SetupTransaction()
mCaps |= NS_HTTP_DISALLOW_SPDY;
}
if (mPushedStream) {
mTransaction->SetPushedStream(mPushedStream);
mPushedStream = nullptr;
}
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks,
mLoadGroup,
NS_GET_IID(nsIHttpPushListener),
getter_AddRefs(pushListener));
if (pushListener) {
mCaps |= NS_HTTP_ONPUSH_LISTENER;
}
nsCOMPtr<nsIAsyncInputStream> responseStream;
rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
mUploadStream, mUploadStreamHasHeaders,
@ -4593,6 +4610,12 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
// we have no macro that covers this case.
if (aIID.Equals(NS_GET_IID(nsHttpChannel)) ) {
AddRef();
*aInstancePtr = this;
return NS_OK;
} else
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
//-----------------------------------------------------------------------------
@ -6484,4 +6507,76 @@ nsHttpChannel::AwaitingCacheCallbacks()
return mCacheEntriesToWaitFor != 0;
}
void
nsHttpChannel::SetPushedStream(Http2PushedStream *stream)
{
MOZ_ASSERT(stream);
MOZ_ASSERT(!mPushedStream);
mPushedStream = stream;
}
nsresult
nsHttpChannel::OnPush(const nsACString &url, Http2PushedStream *pushedStream)
{
MOZ_ASSERT(NS_IsMainThread());
LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks,
mLoadGroup,
NS_GET_IID(nsIHttpPushListener),
getter_AddRefs(pushListener));
MOZ_ASSERT(pushListener);
if (!pushListener) {
LOG(("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
"implement nsIHttpPushListener\n", this));
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIURI> pushResource;
nsresult rv;
// Create a Channel for the Push Resource
rv = NS_NewURI(getter_AddRefs(pushResource), url);
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIChannel> pushChannel;
rv = ioService->NewChannelFromURI(pushResource, getter_AddRefs(pushChannel));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
MOZ_ASSERT(pushHttpChannel);
if (!pushHttpChannel) {
return NS_ERROR_UNEXPECTED;
}
nsRefPtr<nsHttpChannel> channel;
CallQueryInterface(pushHttpChannel, channel.StartAssignment());
MOZ_ASSERT(channel);
if (!channel) {
return NS_ERROR_UNEXPECTED;
}
// new channel needs mrqeuesthead and headers from pushedStream
channel->mRequestHead.ParseHeaderSet(
pushedStream->GetRequestString().BeginWriting());
channel->mLoadGroup = mLoadGroup;
channel->mLoadInfo = mLoadInfo;
channel->mCallbacks = mCallbacks;
// Link the pushed stream with the new channel and call listener
channel->SetPushedStream(pushedStream);
rv = pushListener->OnPush(this, pushHttpChannel);
return rv;
}
} } // namespace mozilla::net

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

@ -32,10 +32,20 @@ class nsISSLStatus;
namespace mozilla { namespace net {
class Http2PushedStream;
//-----------------------------------------------------------------------------
// nsHttpChannel
//-----------------------------------------------------------------------------
// Use to support QI nsIChannel to nsHttpChannel
#define NS_HTTPCHANNEL_IID \
{ \
0x301bf95b, \
0x7bb3, \
0x4ae1, \
{0xa9, 0x71, 0x40, 0xbc, 0xfa, 0x81, 0xde, 0x12} \
}
class nsHttpChannel MOZ_FINAL : public HttpBaseChannel
, public HttpAsyncAborter<nsHttpChannel>
, public nsIStreamListener
@ -67,6 +77,7 @@ public:
NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
NS_DECL_NSITHREADRETARGETABLEREQUEST
NS_DECL_NSIDNSLISTENER
NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
// nsIHttpAuthenticableChannel. We can't use
// NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
@ -95,6 +106,8 @@ public:
uint32_t aProxyResolveFlags,
nsIURI *aProxyURI);
nsresult OnPush(const nsACString &uri, Http2PushedStream *pushedStream);
// Methods HttpBaseChannel didn't implement for us or that we override.
//
// nsIRequest
@ -345,6 +358,8 @@ private:
nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering,
bool checkingAppCacheEntry);
void SetPushedStream(Http2PushedStream *stream);
private:
nsCOMPtr<nsISupports> mSecurityInfo;
nsCOMPtr<nsICancelable> mProxyRequest;
@ -438,6 +453,8 @@ private:
// Needed for accurate DNS timing
nsRefPtr<nsDNSPrefetch> mDNSPrefetch;
Http2PushedStream *mPushedStream;
nsresult WaitForRedirectCallback();
void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
@ -451,6 +468,7 @@ private: // cache telemetry
bool mDidReval;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
} } // namespace mozilla::net
#endif // nsHttpChannel_h__

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

@ -1882,8 +1882,8 @@ nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
nsresult rv;
LOG(("nsHttpConnectionMgr::DispatchTransaction "
"[ent-ci=%s trans=%p caps=%x conn=%p priority=%d]\n",
ent->mConnInfo->HashKey().get(), trans, caps, conn, priority));
"[ent-ci=%s %p trans=%p caps=%x conn=%p priority=%d]\n",
ent->mConnInfo->HashKey().get(), ent, trans, caps, conn, priority));
// It is possible for a rate-paced transaction to be dispatched independent
// of the token bucket when the amount of parallelization has changed or
@ -2046,6 +2046,13 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
trans->SetPendingTime();
Http2PushedStream *pushedStream = trans->GetPushedStream();
if (pushedStream) {
return pushedStream->Session()->
AddStream(trans, trans->Priority(), false, nullptr) ?
NS_OK : NS_ERROR_UNEXPECTED;
}
nsresult rv = NS_OK;
nsHttpConnectionInfo *ci = trans->ConnectionInfo();
MOZ_ASSERT(ci);

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

@ -184,6 +184,25 @@ nsHttpHeaderArray::ParseHeaderLine(const char *line,
return SetHeaderFromNet(atom, nsDependentCString(p, p2 - p));
}
void
nsHttpHeaderArray::ParseHeaderSet(char *buffer)
{
nsHttpAtom hdr;
char *val;
while (buffer) {
char *eof = strchr(buffer, '\r');
if (!eof) {
break;
}
*eof = '\0';
ParseHeaderLine(buffer, &hdr, &val);
buffer = eof + 1;
if (*buffer == '\n') {
buffer++;
}
}
}
void
nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders)
{

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

@ -54,6 +54,8 @@ public:
void Flatten(nsACString &, bool pruneProxyHeaders=false);
void ParseHeaderSet(char *buffer);
uint32_t Count() const { return mHeaders.Length(); }
const char *PeekHeaderAt(uint32_t i, nsHttpAtom &header) const;

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

@ -93,6 +93,7 @@ public:
bool IsHead() const { return EqualsMethod(kMethod_Head); }
bool IsPut() const { return EqualsMethod(kMethod_Put); }
bool IsTrace() const { return EqualsMethod(kMethod_Trace); }
void ParseHeaderSet(char *buffer) { mHeaders.ParseHeaderSet(buffer); }
private:
// All members must be copy-constructable and assignable

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

@ -96,6 +96,7 @@ nsHttpTransaction::nsHttpTransaction()
, mContentLength(-1)
, mContentRead(0)
, mInvalidResponseBytesRead(0)
, mPushedStream(nullptr)
, mChunkedDecoder(nullptr)
, mStatus(NS_OK)
, mPriority(0)
@ -143,6 +144,11 @@ nsHttpTransaction::~nsHttpTransaction()
{
LOG(("Destroying nsHttpTransaction @%p\n", this));
if (mPushedStream) {
mPushedStream->OnPushFailed();
mPushedStream = nullptr;
}
if (mTokenBucketCancel) {
mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
mTokenBucketCancel = nullptr;
@ -225,7 +231,6 @@ nsHttpTransaction::Init(uint32_t caps,
if (NS_SUCCEEDED(rv) && activityDistributorActive) {
// there are some observers registered at activity distributor, gather
// nsISupports for the channel that called Init()
mChannel = do_QueryInterface(eventsink);
LOG(("nsHttpTransaction::Init() " \
"mActivityDistributor is active " \
"this=%p", this));
@ -234,7 +239,7 @@ nsHttpTransaction::Init(uint32_t caps,
activityDistributorActive = false;
mActivityDistributor = nullptr;
}
mChannel = do_QueryInterface(eventsink);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(eventsink);
if (channel) {
bool isInBrowser;

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

@ -15,6 +15,7 @@
#include "nsILoadGroup.h"
#include "nsIInterfaceRequestor.h"
#include "TimingStruct.h"
#include "Http2Push.h"
#ifdef MOZ_WIDGET_GONK
#include "nsINetworkManager.h"
@ -89,6 +90,7 @@ public:
nsISupports *SecurityInfo() { return mSecurityInfo; }
nsIEventTarget *ConsumerTarget() { return mConsumerTarget; }
nsISupports *HttpChannel() { return mChannel; }
void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
@ -137,6 +139,15 @@ public:
nsHttpTransaction *QueryHttpTransaction() MOZ_OVERRIDE { return this; }
Http2PushedStream *GetPushedStream() { return mPushedStream; }
Http2PushedStream *TakePushedStream()
{
Http2PushedStream *r = mPushedStream;
mPushedStream = nullptr;
return r;
}
void SetPushedStream(Http2PushedStream *push) { mPushedStream = push; }
private:
friend class DeleteHttpTransaction;
virtual ~nsHttpTransaction();
@ -222,7 +233,9 @@ private:
// so far been skipped.
uint32_t mInvalidResponseBytesRead;
nsHttpChunkedDecoder *mChunkedDecoder;
Http2PushedStream *mPushedStream;
nsHttpChunkedDecoder *mChunkedDecoder;
TimingStruct mTimings;

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

@ -393,6 +393,86 @@ function test_http2_altsvc() {
chan.asyncOpen(altsvcClientListener, null);
}
var Http2PushApiListener = function() {};
Http2PushApiListener.prototype = {
checksPending: 9, // 4 onDataAvailable and 5 onStop
getInterface: function(aIID) {
return this.QueryInterface(aIID);
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIHttpPushListener) ||
aIID.equals(Ci.nsIStreamListener))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
// nsIHttpPushListener
onPush: function onPush(associatedChannel, pushChannel) {
do_check_eq(associatedChannel.originalURI.spec, "https://localhost:6944/pushapi1");
do_check_eq (pushChannel.getRequestHeader("x-pushed-request"), "true");
pushChannel.asyncOpen(this, pushChannel);
if (pushChannel.originalURI.spec == "https://localhost:6944/pushapi1/2") {
pushChannel.cancel(Components.results.NS_ERROR_ABORT);
}
},
// normal Channel listeners
onStartRequest: function pushAPIOnStart(request, ctx) {
},
onDataAvailable: function pushAPIOnDataAvailable(request, ctx, stream, offset, cnt) {
do_check_neq(ctx.originalURI.spec, "https://localhost:6944/pushapi1/2");
var data = read_stream(stream, cnt);
if (ctx.originalURI.spec == "https://localhost:6944/pushapi1") {
do_check_eq(data[0], '0');
--this.checksPending;
} else if (ctx.originalURI.spec == "https://localhost:6944/pushapi1/1") {
do_check_eq(data[0], '1');
--this.checksPending; // twice
} else if (ctx.originalURI.spec == "https://localhost:6944/pushapi1/3") {
do_check_eq(data[0], '3');
--this.checksPending;
} else {
do_check_eq(true, false);
}
},
onStopRequest: function test_onStopR(request, ctx, status) {
if (ctx.originalURI.spec == "https://localhost:6944/pushapi1/2") {
do_check_eq(request.status, Components.results.NS_ERROR_ABORT);
} else {
do_check_eq(request.status, Components.results.NS_OK);
}
--this.checksPending; // 5 times - one for each push plus the pull
if (!this.checksPending) {
run_next_test();
do_test_finished();
}
}
};
// pushAPI testcase 1 expects
// 1 to pull /pushapi1 with 0
// 2 to see /pushapi1/1 with 1
// 3 to see /pushapi1/1 with 1 (again)
// 4 to see /pushapi1/2 that it will cancel
// 5 to see /pushapi1/3 with 3
function test_http2_pushapi_1() {
var chan = makeChan("https://localhost:6944/pushapi1");
chan.loadGroup = loadGroup;
var listener = new Http2PushApiListener();
chan.notificationCallbacks = listener;
chan.asyncOpen(listener, chan);
}
// hack - the header test resets the multiplex object on the server,
// so make sure header is always run before the multiplex test.
//
@ -413,6 +493,7 @@ var tests = [ test_http2_post_big
, test_http2_multiplex
, test_http2_big
, test_http2_post
, test_http2_pushapi_1
];
var current_test = 0;
@ -483,6 +564,8 @@ var spdy3pref;
var spdypush;
var http2pref;
var tlspref;
var altsvcpref1;
var altsvcpref2;
var loadGroup;

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

@ -75,7 +75,7 @@ var m = {
function handleRequest(req, res) {
var u = url.parse(req.url);
var content = getHttpContent(u.pathname);
var push;
var push, push1, push1a, push2, push3;
if (req.httpVersionMajor === 2) {
res.setHeader('X-Connection-Http2', 'yes');
@ -151,6 +151,54 @@ function handleRequest(req, res) {
content = '<head> <script src="push2.js"/></head>body text';
}
else if (u.pathname === "/pushapi1") {
push1 = res.push(
{ hostname: 'localhost:6944', port: 6944, path : '/pushapi1/1', method : 'GET',
headers: {'x-pushed-request': 'true', 'x-foo' : 'bar'}});
push1.writeHead(200, {
'pushed' : 'yes',
'content-length' : 1,
'subresource' : '1',
'X-Connection-Http2': 'yes'
});
push1.end('1');
push1a = res.push(
{ hostname: 'localhost:6944', port: 6944, path : '/pushapi1/1', method : 'GET',
headers: {'x-foo' : 'bar', 'x-pushed-request': 'true'}});
push1a.writeHead(200, {
'pushed' : 'yes',
'content-length' : 1,
'subresource' : '1a',
'X-Connection-Http2': 'yes'
});
push1a.end('1');
push2 = res.push(
{ hostname: 'localhost:6944', port: 6944, path : '/pushapi1/2', method : 'GET',
headers: {'x-pushed-request': 'true'}});
push2.writeHead(200, {
'pushed' : 'yes',
'subresource' : '2',
'content-length' : 1,
'X-Connection-Http2': 'yes'
});
push2.end('2');
push3 = res.push(
{ hostname: 'localhost:6944', port: 6944, path : '/pushapi1/3', method : 'GET',
headers: {'x-pushed-request': 'true'}});
push3.writeHead(200, {
'pushed' : 'yes',
'content-length' : 1,
'subresource' : '3',
'X-Connection-Http2': 'yes'
});
push3.end('3');
content = '0';
}
else if (u.pathname === "/big") {
content = generateContent(128 * 1024);
var hash = crypto.createHash('md5');