summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/Http2Push.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/Http2Push.cpp')
-rw-r--r--netwerk/protocol/http/Http2Push.cpp510
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