forked from mirrors/gecko-dev
298 lines
9.9 KiB
C++
298 lines
9.9 KiB
C++
/* -*- 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"
|
|
|
|
#include "Http2Push.h"
|
|
#include "Http2Stream.h"
|
|
#include "nsHttp.h"
|
|
#include "nsHttpConnectionInfo.h"
|
|
#include "nsHttpRequestHead.h"
|
|
#include "nsISocketTransport.h"
|
|
#include "Http2Session.h"
|
|
#include "PSpdyPush.h"
|
|
#include "nsIRequestContext.h"
|
|
#include "nsHttpTransaction.h"
|
|
#include "nsSocketTransportService2.h"
|
|
|
|
namespace mozilla::net {
|
|
|
|
Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction,
|
|
Http2Session* session, int32_t priority, uint64_t bcId)
|
|
: Http2StreamBase((httpTransaction->QueryHttpTransaction())
|
|
? httpTransaction->QueryHttpTransaction()->BrowserId()
|
|
: 0,
|
|
session, priority, bcId),
|
|
mTransaction(httpTransaction) {
|
|
LOG1(("Http2Stream::Http2Stream %p trans=%p", this, httpTransaction));
|
|
}
|
|
|
|
Http2Stream::~Http2Stream() { ClearPushSource(); }
|
|
|
|
void Http2Stream::CloseStream(nsresult reason) {
|
|
// In case we are connected to a push, make sure the push knows we are closed,
|
|
// so it doesn't try to give us any more DATA that comes on it after our
|
|
// close.
|
|
ClearPushSource();
|
|
|
|
mTransaction->Close(reason);
|
|
mSession = nullptr;
|
|
}
|
|
|
|
void Http2Stream::ClearPushSource() {
|
|
if (mPushSource) {
|
|
mPushSource->SetConsumerStream(nullptr);
|
|
mPushSource = nullptr;
|
|
}
|
|
}
|
|
|
|
nsresult Http2Stream::CheckPushCache() {
|
|
nsHttpRequestHead* head = mTransaction->RequestHead();
|
|
|
|
// check the push cache for GET
|
|
if (!head->IsGet()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<Http2Session> session = Session();
|
|
|
|
nsAutoCString authorityHeader;
|
|
nsAutoCString hashkey;
|
|
nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_ASSERT(false);
|
|
return rv;
|
|
}
|
|
|
|
nsAutoCString requestURI;
|
|
head->RequestURI(requestURI);
|
|
|
|
mozilla::OriginAttributes originAttributes;
|
|
mSocketTransport->GetOriginAttributes(&originAttributes);
|
|
|
|
CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"),
|
|
authorityHeader, originAttributes, session->Serial(),
|
|
requestURI, mOrigin, hashkey);
|
|
|
|
// from :scheme, :authority, :path
|
|
nsIRequestContext* requestContext = mTransaction->RequestContext();
|
|
SpdyPushCache* cache = nullptr;
|
|
if (requestContext) {
|
|
cache = requestContext->GetSpdyPushCache();
|
|
}
|
|
|
|
RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
|
|
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 && (pushedStreamWrapper = trans->TakePushedStream()) &&
|
|
(pushedStream = pushedStreamWrapper->GetStream())) {
|
|
RefPtr<Http2Session> pushSession = pushedStream->Session();
|
|
if (pushSession == session) {
|
|
LOG3(
|
|
("Pushed Stream match based on OnPush correlation %p", pushedStream));
|
|
} else {
|
|
LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64
|
|
" %" PRId64 "\n",
|
|
pushedStream, pushSession->Serial(), session->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) {
|
|
pushedStream = cache->RemovePushedStreamHttp2(hashkey);
|
|
}
|
|
|
|
LOG3(
|
|
("Pushed Stream Lookup "
|
|
"session=%p key=%s requestcontext=%p cache=%p hit=%p\n",
|
|
session.get(), hashkey.get(), requestContext, cache, pushedStream));
|
|
|
|
if (pushedStream) {
|
|
LOG3(("Pushed Stream Match located %p id=0x%X key=%s\n", pushedStream,
|
|
pushedStream->StreamID(), hashkey.get()));
|
|
pushedStream->SetConsumerStream(this);
|
|
mPushSource = pushedStream;
|
|
SetSentFin(true);
|
|
AdjustPushedPriority();
|
|
|
|
// There is probably pushed data buffered so trigger a read manually
|
|
// as we can't rely on future network events to do it
|
|
session->ConnectPushedStream(this);
|
|
mOpenGenerated = 1;
|
|
|
|
// if the "mother stream" had TRR, this one is a TRR stream too!
|
|
RefPtr<nsHttpConnectionInfo> ci(Transaction()->ConnectionInfo());
|
|
if (ci && ci->GetIsTrrServiceChannel()) {
|
|
session->IncrementTrrCounter();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t Http2Stream::GetWireStreamId() {
|
|
// >0 even numbered IDs are pushed streams.
|
|
// odd numbered IDs are pulled streams.
|
|
// 0 is the sink for a pushed stream.
|
|
if (!mStreamID) {
|
|
MOZ_ASSERT(mPushSource);
|
|
if (!mPushSource) {
|
|
return 0;
|
|
}
|
|
|
|
MOZ_ASSERT(mPushSource->StreamID());
|
|
MOZ_ASSERT(!(mPushSource->StreamID() & 1)); // is a push stream
|
|
|
|
// If the pushed stream has recvd a FIN, there is no reason to update
|
|
// the window
|
|
if (mPushSource->RecvdFin() || mPushSource->RecvdReset() ||
|
|
(mPushSource->HTTPState() == RESERVED_BY_REMOTE)) {
|
|
return 0;
|
|
}
|
|
return mPushSource->StreamID();
|
|
}
|
|
|
|
if (mState == RESERVED_BY_REMOTE) {
|
|
// h2-14 prevents sending a window update in this state
|
|
return 0;
|
|
}
|
|
return mStreamID;
|
|
}
|
|
|
|
void Http2Stream::AdjustPushedPriority() {
|
|
// >0 even numbered IDs are pushed streams. odd numbered IDs are pulled
|
|
// streams. 0 is the sink for a pushed stream.
|
|
|
|
if (mStreamID || !mPushSource) return;
|
|
|
|
MOZ_ASSERT(mPushSource->StreamID() && !(mPushSource->StreamID() & 1));
|
|
|
|
// If the pushed stream has recvd a FIN, there is no reason to update
|
|
// the window
|
|
if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) return;
|
|
|
|
// Ensure we pick up the right dependency to place the pushed stream under.
|
|
UpdatePriorityDependency();
|
|
|
|
EnsureBuffer(mTxInlineFrame,
|
|
mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
|
|
mTxInlineFrameUsed, mTxInlineFrameSize);
|
|
uint8_t* packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
|
|
mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
|
|
|
|
RefPtr<Http2Session> session = Session();
|
|
session->CreateFrameHeader(packet, 5, Http2Session::FRAME_TYPE_PRIORITY, 0,
|
|
mPushSource->StreamID());
|
|
|
|
mPushSource->SetPriorityDependency(mPriority, mPriorityDependency);
|
|
uint32_t wireDep = PR_htonl(mPriorityDependency);
|
|
memcpy(packet + Http2Session::kFrameHeaderBytes, &wireDep, 4);
|
|
memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
|
|
|
|
LOG3(("AdjustPushedPriority %p id 0x%X to dep %X weight %X\n", this,
|
|
mPushSource->StreamID(), mPriorityDependency, mPriorityWeight));
|
|
}
|
|
|
|
bool Http2Stream::IsReadingFromPushStream() { return !!mPushSource; }
|
|
|
|
nsresult Http2Stream::OnWriteSegment(char* buf, uint32_t count,
|
|
uint32_t* countWritten) {
|
|
LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", this, count,
|
|
mUpstreamState, mStreamID));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_ASSERT(mSegmentWriter);
|
|
|
|
if (mPushSource) {
|
|
nsresult rv;
|
|
rv = mPushSource->GetBufferedData(buf, count, countWritten);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
RefPtr<Http2Session> session = Session();
|
|
session->ConnectPushedStream(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
return Http2StreamBase::OnWriteSegment(buf, count, countWritten);
|
|
}
|
|
|
|
nsresult Http2Stream::CallToReadData(uint32_t count, uint32_t* countRead) {
|
|
return mTransaction->ReadSegments(this, count, countRead);
|
|
}
|
|
|
|
nsresult Http2Stream::CallToWriteData(uint32_t count, uint32_t* countWritten) {
|
|
return mTransaction->WriteSegments(this, count, countWritten);
|
|
}
|
|
|
|
// This is really a headers frame, but open is pretty clear from a workflow pov
|
|
nsresult Http2Stream::GenerateHeaders(nsCString& aCompressedData,
|
|
uint8_t& firstFrameFlags) {
|
|
nsHttpRequestHead* head = mTransaction->RequestHead();
|
|
nsAutoCString requestURI;
|
|
head->RequestURI(requestURI);
|
|
RefPtr<Http2Session> session = Session();
|
|
LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", this,
|
|
mStreamID, session.get(), requestURI.get()));
|
|
|
|
nsAutoCString authorityHeader;
|
|
nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_ASSERT(false);
|
|
return rv;
|
|
}
|
|
|
|
nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
|
|
|
|
nsAutoCString method;
|
|
nsAutoCString path;
|
|
head->Method(method);
|
|
head->Path(path);
|
|
|
|
rv = session->Compressor()->EncodeHeaderBlock(
|
|
mFlatHttpRequestHeaders, method, path, authorityHeader, scheme,
|
|
EmptyCString(), false, aCompressedData);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
int64_t clVal = session->Compressor()->GetParsedContentLength();
|
|
if (clVal != -1) {
|
|
mRequestBodyLenRemaining = clVal;
|
|
}
|
|
|
|
// Determine whether to put the fin bit on the header frame or whether
|
|
// to wait for a data packet to put it on.
|
|
|
|
if (head->IsGet() || head->IsHead()) {
|
|
// for GET and HEAD place the fin bit right on the
|
|
// header packet
|
|
firstFrameFlags |= Http2Session::kFlag_END_STREAM;
|
|
} else if (head->IsPost() || head->IsPut() || head->IsConnect()) {
|
|
// 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
|
|
// to determine whether or not to put fin on headers
|
|
firstFrameFlags |= Http2Session::kFlag_END_STREAM;
|
|
}
|
|
|
|
// The size of the input headers is approximate
|
|
uint32_t ratio =
|
|
aCompressedData.Length() * 100 /
|
|
(11 + requestURI.Length() + mFlatHttpRequestHeaders.Length());
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::net
|