diff options
Diffstat (limited to 'netwerk/protocol/http/Http2Push.cpp')
-rw-r--r-- | netwerk/protocol/http/Http2Push.cpp | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/netwerk/protocol/http/Http2Push.cpp b/netwerk/protocol/http/Http2Push.cpp new file mode 100644 index 000000000..b6fc485e2 --- /dev/null +++ b/netwerk/protocol/http/Http2Push.cpp @@ -0,0 +1,510 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* 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/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" + +// Log on level :5, instead of default :4. +#undef LOG +#define LOG(args) LOG5(args) +#undef LOG_ENABLED +#define LOG_ENABLED() LOG5_ENABLED() + +#include <algorithm> + +#include "Http2Push.h" +#include "nsHttpChannel.h" +#include "nsIHttpPushListener.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +class CallChannelOnPush final : public Runnable { + public: + CallChannelOnPush(nsIHttpChannelInternal *associatedChannel, + const nsACString &pushedURI, + Http2PushedStream *pushStream) + : mAssociatedChannel(associatedChannel) + , mPushedURI(pushedURI) + , mPushedStream(pushStream) + { + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<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 +////////////////////////////////////////// + +Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction, + Http2Session *aSession, + Http2Stream *aAssociatedStream, + 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; + MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream + mBufferedPush->SetPushStream(this); + mRequestContext = aAssociatedStream->RequestContext(); + mLastRead = TimeStamp::Now(); + SetPriority(aAssociatedStream->Priority() + 1); +} + +bool +Http2PushedStream::GetPushComplete() +{ + return mPushCompleted; +} + +nsresult +Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, uint32_t *countWritten) +{ + nsresult rv = Http2Stream::WriteSegments(writer, count, countWritten); + if (NS_SUCCEEDED(rv) && *countWritten) { + mLastRead = TimeStamp::Now(); + } + + if (rv == NS_BASE_STREAM_CLOSED) { + mPushCompleted = true; + rv = NS_OK; // this is what a normal HTTP transaction would do + } + if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) + mStatus = rv; + 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; +} + +// side effect free static method to determine if Http2Stream implements nsIHttpPushListener +bool +Http2PushedStream::TestOnPush(Http2Stream *stream) +{ + if (!stream) { + return false; + } + nsAHttpTransaction *abstractTransaction = stream->Transaction(); + if (!abstractTransaction) { + return false; + } + nsHttpTransaction *trans = abstractTransaction->QueryHttpTransaction(); + if (!trans) { + return false; + } + nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel()); + if (!associatedChannel) { + return false; + } + return (trans->Caps() & NS_HTTP_ONPUSH_LISTENER); +} + +nsresult +Http2PushedStream::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t, uint32_t *count) +{ + nsresult rv = NS_OK; + *count = 0; + + switch (mUpstreamState) { + case GENERATING_HEADERS: + // The request headers for this has been processed, so we need to verify + // that :authority, :scheme, and :path MUST be present. :method MUST NOT be + // present + CreatePushHashKey(mHeaderScheme, mHeaderHost, + mSession->Serial(), mHeaderPath, + mOrigin, mHashKey); + + LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get())); + + // the write side of a pushed transaction just involves manipulating a little state + SetSentFin(true); + Http2Stream::mRequestHeadersDone = 1; + Http2Stream::mOpenGenerated = 1; + Http2Stream::ChangeState(UPSTREAM_COMPLETE); + break; + + case UPSTREAM_COMPLETE: + // Let's just clear the stream's transmit buffer by pushing it into + // the session. This is probably a window adjustment. + LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID)); + mSegmentReader = reader; + rv = TransmitFrame(nullptr, nullptr, true); + mSegmentReader = nullptr; + break; + + case GENERATING_BODY: + case SENDING_BODY: + case SENDING_FIN_STREAM: + default: + break; + } + + return rv; +} + +void +Http2PushedStream::AdjustInitialWindow() +{ + LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID)); + if (mConsumerStream) { + LOG3(("Http2PushStream::AdjustInitialWindow %p 0x%X " + "calling super consumer %p 0x%X\n", this, + mStreamID, mConsumerStream, mConsumerStream->StreamID())); + Http2Stream::AdjustInitialWindow(); + // Http2PushedStream::ReadSegments is needed to call TransmitFrame() + // and actually get this information into the session bytestream + mSession->TransactionHasDataToWrite(this); + } + // Otherwise, when we get hooked up, the initial window will get bumped + // anyway, so we're good to go. +} + +void +Http2PushedStream::SetConsumerStream(Http2Stream *consumer) +{ + mConsumerStream = consumer; + mDeferCleanupOnPush = false; +} + +bool +Http2PushedStream::GetHashKey(nsCString &key) +{ + if (mHashKey.IsEmpty()) + return false; + + key = mHashKey; + return true; +} + +void +Http2PushedStream::ConnectPushedStream(Http2Stream *stream) +{ + mSession->ConnectPushedStream(stream); +} + +bool +Http2PushedStream::IsOrphaned(TimeStamp now) +{ + MOZ_ASSERT(!now.IsNull()); + + // 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 || mDeferCleanupOnPush) { + return false; + } + + if (mOnPushFailed) { + return true; + } + + bool rv = ((now - mLastRead).ToSeconds() > 30.0); + if (rv) { + LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n", + mStreamID, (now - mLastRead).ToSeconds())); + } + return rv; +} + +nsresult +Http2PushedStream::GetBufferedData(char *buf, + uint32_t count, uint32_t *countWritten) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + if (!*countWritten) + rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK; + + return rv; +} + +////////////////////////////////////////// +// Http2PushTransactionBuffer +// This is the nsAHttpTransction owned by the stream when the pushed +// stream has not yet been matched with a pull request +////////////////////////////////////////// + +NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer) + +Http2PushTransactionBuffer::Http2PushTransactionBuffer() + : mStatus(NS_OK) + , mRequestHead(nullptr) + , mPushStream(nullptr) + , mIsDone(false) + , mBufferedHTTP1Size(kDefaultBufferSize) + , mBufferedHTTP1Used(0) + , mBufferedHTTP1Consumed(0) +{ + mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size); +} + +Http2PushTransactionBuffer::~Http2PushTransactionBuffer() +{ + delete mRequestHead; +} + +void +Http2PushTransactionBuffer::SetConnection(nsAHttpConnection *conn) +{ +} + +nsAHttpConnection * +Http2PushTransactionBuffer::Connection() +{ + return nullptr; +} + +void +Http2PushTransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB) +{ + *outCB = nullptr; +} + +void +Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport, + nsresult status, int64_t progress) +{ +} + +nsHttpConnectionInfo * +Http2PushTransactionBuffer::ConnectionInfo() +{ + if (!mPushStream) { + return nullptr; + } + if (!mPushStream->Transaction()) { + return nullptr; + } + MOZ_ASSERT(mPushStream->Transaction() != this); + return mPushStream->Transaction()->ConnectionInfo(); +} + +bool +Http2PushTransactionBuffer::IsDone() +{ + return mIsDone; +} + +nsresult +Http2PushTransactionBuffer::Status() +{ + return mStatus; +} + +uint32_t +Http2PushTransactionBuffer::Caps() +{ + return 0; +} + +void +Http2PushTransactionBuffer::SetDNSWasRefreshed() +{ +} + +uint64_t +Http2PushTransactionBuffer::Available() +{ + return mBufferedHTTP1Used - mBufferedHTTP1Consumed; +} + +nsresult +Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t count, uint32_t *countRead) +{ + *countRead = 0; + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, uint32_t *countWritten) +{ + if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) { + EnsureBuffer(mBufferedHTTP1,mBufferedHTTP1Size + kDefaultBufferSize, + mBufferedHTTP1Used, mBufferedHTTP1Size); + } + + count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used); + nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used], + count, countWritten); + if (NS_SUCCEEDED(rv)) { + mBufferedHTTP1Used += *countWritten; + } + else if (rv == NS_BASE_STREAM_CLOSED) { + mIsDone = true; + } + + if (Available() || mIsDone) { + Http2Stream *consumer = mPushStream->GetConsumerStream(); + + if (consumer) { + LOG3(("Http2PushTransactionBuffer::WriteSegments notifying connection " + "consumer data available 0x%X [%u] done=%d\n", + mPushStream->StreamID(), Available(), mIsDone)); + mPushStream->ConnectPushedStream(consumer); + } + } + + return rv; +} + +uint32_t +Http2PushTransactionBuffer::Http1xTransactionCount() +{ + return 0; +} + +nsHttpRequestHead * +Http2PushTransactionBuffer::RequestHead() +{ + if (!mRequestHead) + mRequestHead = new nsHttpRequestHead(); + return mRequestHead; +} + +nsresult +Http2PushTransactionBuffer::TakeSubTransactions( + nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +Http2PushTransactionBuffer::SetProxyConnectFailed() +{ +} + +void +Http2PushTransactionBuffer::Close(nsresult reason) +{ + mStatus = reason; + mIsDone = true; +} + +nsresult +Http2PushTransactionBuffer::AddTransaction(nsAHttpTransaction *trans) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t +Http2PushTransactionBuffer::PipelineDepth() +{ + return 0; +} + +nsresult +Http2PushTransactionBuffer::SetPipelinePosition(int32_t position) +{ + return NS_OK; +} + +int32_t +Http2PushTransactionBuffer::PipelinePosition() +{ + return 1; +} + +nsresult +Http2PushTransactionBuffer::GetBufferedData(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + *countWritten = std::min(count, static_cast<uint32_t>(Available())); + if (*countWritten) { + memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten); + mBufferedHTTP1Consumed += *countWritten; + } + + // If all the data has been consumed then reset the buffer + if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) { + mBufferedHTTP1Consumed = 0; + mBufferedHTTP1Used = 0; + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla |