Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 sts=2 et cin: */
/* 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 "nsHttpTransaction.h"
#include <algorithm>
#include <utility>
#include "HttpLog.h"
#include "HTTPSRecordResolver.h"
#include "NSSErrorsService.h"
#include "base/basictypes.h"
#include "mozilla/Components.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/StaticPrefs_network.h"
#include "MockHttpAuth.h"
#include "nsCRT.h"
#include "nsComponentManagerUtils.h" // do_CreateInstance
#include "nsHttpBasicAuth.h"
#include "nsHttpChannel.h"
#include "nsHttpChunkedDecoder.h"
#include "nsHttpDigestAuth.h"
#include "nsHttpHandler.h"
#include "nsHttpNTLMAuth.h"
#ifdef MOZ_AUTH_EXTENSION
# include "nsHttpNegotiateAuth.h"
#endif
#include "nsHttpRequestHead.h"
#include "nsHttpResponseHead.h"
#include "nsICancelable.h"
#include "nsIClassOfService.h"
#include "nsIDNSByTypeRecord.h"
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsIEventTarget.h"
#include "nsIHttpActivityObserver.h"
#include "nsIHttpAuthenticator.h"
#include "nsIInputStream.h"
#include "nsIInputStreamPriority.h"
#include "nsIMultiplexInputStream.h"
#include "nsIOService.h"
#include "nsIPipe.h"
#include "nsIRequestContext.h"
#include "nsISeekableStream.h"
#include "nsITLSSocketControl.h"
#include "nsIThrottledInputChannel.h"
#include "nsITransport.h"
#include "nsMultiplexInputStream.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsSocketTransportService2.h"
#include "nsStringStream.h"
#include "nsTransportUtils.h"
#include "sslerr.h"
#include "SpeculativeTransaction.h"
//-----------------------------------------------------------------------------
// Place a limit on how much non-compliant HTTP can be skipped while
// looking for a response header
#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
using namespace mozilla::net;
namespace mozilla::net {
//-----------------------------------------------------------------------------
// nsHttpTransaction <public>
//-----------------------------------------------------------------------------
nsHttpTransaction::nsHttpTransaction() {
LOG(("Creating nsHttpTransaction @%p\n", this));
#ifdef MOZ_VALGRIND
memset(&mSelfAddr, 0, sizeof(NetAddr));
memset(&mPeerAddr, 0, sizeof(NetAddr));
#endif
mSelfAddr.raw.family = PR_AF_UNSPEC;
mPeerAddr.raw.family = PR_AF_UNSPEC;
}
void nsHttpTransaction::ResumeReading() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (!mReadingStopped) {
return;
}
LOG(("nsHttpTransaction::ResumeReading %p", this));
mReadingStopped = false;
// This with either reengage the limit when still throttled in WriteSegments
// or simply reset to allow unlimeted reading again.
mThrottlingReadAllowance = THROTTLE_NO_LIMIT;
if (mConnection) {
mConnection->TransactionHasDataToRecv(this);
nsresult rv = mConnection->ResumeRecv();
if (NS_FAILED(rv)) {
LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
}
}
}
bool nsHttpTransaction::EligibleForThrottling() const {
return (mClassOfServiceFlags &
(nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle |
nsIClassOfService::Leader | nsIClassOfService::Unblocked)) ==
nsIClassOfService::Throttleable;
}
void nsHttpTransaction::SetClassOfService(ClassOfService cos) {
if (mClosed) {
return;
}
bool wasThrottling = EligibleForThrottling();
mClassOfServiceFlags = cos.Flags();
mClassOfServiceIncremental = cos.Incremental();
bool isThrottling = EligibleForThrottling();
if (mConnection && wasThrottling != isThrottling) {
// Do nothing until we are actually activated. For now
// only remember the throttle flag. Call to UpdateActiveTransaction
// would add this transaction to the list too early.
gHttpHandler->ConnMgr()->UpdateActiveTransaction(this);
if (mReadingStopped && !isThrottling) {
ResumeReading();
}
}
}
class ReleaseOnSocketThread final : public mozilla::Runnable {
public:
explicit ReleaseOnSocketThread(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
: Runnable("ReleaseOnSocketThread"), mDoomed(std::move(aDoomed)) {}
NS_IMETHOD
Run() override {
mDoomed.Clear();
return NS_OK;
}
void Dispatch() {
nsCOMPtr<nsIEventTarget> sts =
do_GetService("@mozilla.org/network/socket-transport-service;1");
Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
}
private:
virtual ~ReleaseOnSocketThread() = default;
nsTArray<nsCOMPtr<nsISupports>> mDoomed;
};
nsHttpTransaction::~nsHttpTransaction() {
LOG(("Destroying nsHttpTransaction @%p\n", this));
if (mPushedStream) {
mPushedStream->OnPushFailed();
mPushedStream = nullptr;
}
if (mTokenBucketCancel) {
mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
mTokenBucketCancel = nullptr;
}
// Force the callbacks and connection to be released right now
{
MutexAutoLock lock(mLock);
mCallbacks = nullptr;
}
mEarlyHintObserver = nullptr;
delete mResponseHead;
delete mChunkedDecoder;
ReleaseBlockingTransaction();
nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
if (mConnection) {
arrayToRelease.AppendElement(mConnection.forget());
}
if (!arrayToRelease.IsEmpty()) {
RefPtr<ReleaseOnSocketThread> r =
new ReleaseOnSocketThread(std::move(arrayToRelease));
r->Dispatch();
}
}
nsresult nsHttpTransaction::Init(
uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead,
nsIInputStream* requestBody, uint64_t requestContentLength,
bool requestBodyHasHeaders, nsIEventTarget* target,
nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink,
uint64_t browserId, HttpTrafficCategory trafficCategory,
nsIRequestContext* requestContext, ClassOfService classOfService,
uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId,
TransactionObserverFunc&& transactionObserver,
OnPushCallback&& aOnPushCallback,
HttpTransactionShell* transWithPushedStream, uint32_t aPushedStreamId) {
nsresult rv;
LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
LOG(
("nsHttpTransaction aborting init because of app"
"shutdown"));
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
MOZ_ASSERT(cinfo);
MOZ_ASSERT(requestHead);
MOZ_ASSERT(target);
MOZ_ASSERT(target->IsOnCurrentThread());
mChannelId = channelId;
mTransactionObserver = std::move(transactionObserver);
mOnPushCallback = std::move(aOnPushCallback);
mBrowserId = browserId;
mTrafficCategory = trafficCategory;
LOG1(("nsHttpTransaction %p SetRequestContext %p\n", this, requestContext));
mRequestContext = requestContext;
SetClassOfService(classOfService);
mResponseTimeoutEnabled = responseTimeoutEnabled;
mInitialRwin = initialRwin;
// create transport event sink proxy. it coalesces consecutive
// events of the same status type.
rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), eventsink,
target);
if (NS_FAILED(rv)) return rv;
mConnInfo = cinfo;
mCallbacks = callbacks;
mConsumerTarget = target;
mCaps = caps;
// eventsink is a nsHttpChannel when we expect "103 Early Hints" responses.
// We expect it in document requests and not e.g. in TRR requests.
mEarlyHintObserver = do_QueryInterface(eventsink);
if (requestHead->IsHead()) {
mNoContent = true;
}
// grab a weak reference to the request head
mRequestHead = requestHead;
mReqHeaderBuf = nsHttp::ConvertRequestHeadToString(
*requestHead, !!requestBody, requestBodyHasHeaders,
cinfo->UsingConnect());
if (LOG1_ENABLED()) {
LOG1(("http request [\n"));
LogHeaders(mReqHeaderBuf.get());
LOG1(("]\n"));
}
// report the request header
if (gHttpHandler->HttpActivityDistributorActivated()) {
nsCString requestBuf(mReqHeaderBuf);
NS_DispatchToMainThread(NS_NewRunnableFunction(
"ObserveHttpActivityWithArgs", [channelId(mChannelId), requestBuf]() {
if (!gHttpHandler) {
return;
}
gHttpHandler->ObserveHttpActivityWithArgs(
HttpActivityArgs(channelId),
NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf);
}));
}
// Create a string stream for the request header buf (the stream holds
// a non-owning reference to the request header data, so we MUST keep
// mReqHeaderBuf around).
nsCOMPtr<nsIInputStream> headers;
rv = NS_NewByteInputStream(getter_AddRefs(headers), mReqHeaderBuf,
NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) return rv;
mHasRequestBody = !!requestBody;
if (mHasRequestBody && !requestContentLength) {
mHasRequestBody = false;
}
requestContentLength += mReqHeaderBuf.Length();
if (mHasRequestBody) {
// wrap the headers and request body in a multiplexed input stream.
nsCOMPtr<nsIMultiplexInputStream> multi;
rv = nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream),
getter_AddRefs(multi));
if (NS_FAILED(rv)) return rv;
rv = multi->AppendStream(headers);
if (NS_FAILED(rv)) return rv;
rv = multi->AppendStream(requestBody);
if (NS_FAILED(rv)) return rv;
// wrap the multiplexed input stream with a buffered input stream, so
// that we write data in the largest chunks possible. this is actually
// necessary to workaround some common server bugs (see bug 137155).
nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multi));
rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream),
stream.forget(),
nsIOService::gDefaultSegmentSize);
if (NS_FAILED(rv)) return rv;
} else {
mRequestStream = headers;
}
nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(eventsink);
if (throttled) {
nsCOMPtr<nsIInputChannelThrottleQueue> queue;
rv = throttled->GetThrottleQueue(getter_AddRefs(queue));
// In case of failure, just carry on without throttling.
if (NS_SUCCEEDED(rv) && queue) {
nsCOMPtr<nsIAsyncInputStream> wrappedStream;
rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
// Failure to throttle isn't sufficient reason to fail
// initialization
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(wrappedStream != nullptr);
LOG(
("nsHttpTransaction::Init %p wrapping input stream using throttle "
"queue %p\n",
this, queue.get()));
mRequestStream = wrappedStream;
}
}
}
// make sure request content-length fits within js MAX_SAFE_INTEGER
mRequestSize = InScriptableRange(requestContentLength)
? static_cast<int64_t>(requestContentLength)
: -1;
// create pipe for response stream
NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true, true,
nsIOService::gDefaultSegmentSize,
nsIOService::gDefaultSegmentCount);
if (transWithPushedStream && aPushedStreamId) {
RefPtr<nsHttpTransaction> trans =
transWithPushedStream->AsHttpTransaction();
MOZ_ASSERT(trans);
mPushedStream = trans->TakePushedStreamById(aPushedStreamId);
}
bool forceUseHTTPSRR = StaticPrefs::network_dns_force_use_https_rr();
if ((StaticPrefs::network_dns_use_https_rr_as_altsvc() &&
!(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) ||
forceUseHTTPSRR) {
nsCOMPtr<nsIEventTarget> target;
Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
if (target) {
if (forceUseHTTPSRR) {
mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
}
mResolver = new HTTPSRecordResolver(this);
nsCOMPtr<nsICancelable> dnsRequest;
rv = mResolver->FetchHTTPSRRInternal(target, getter_AddRefs(dnsRequest));
if (NS_SUCCEEDED(rv)) {
mHTTPSSVCReceivedStage = HTTPSSVC_NOT_PRESENT;
}
{
MutexAutoLock lock(mLock);
mDNSRequest.swap(dnsRequest);
if (NS_FAILED(rv)) {
MakeDontWaitHTTPSRR();
}
}
}
}
RefPtr<nsHttpChannel> httpChannel = do_QueryObject(eventsink);
if (httpChannel) {
RefPtr<WebTransportSessionEventListener> listener =
httpChannel->GetWebTransportSessionEventListener();
if (listener) {
mWebTransportSessionEventListener = std::move(listener);
}
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(httpChannel->GetURI(getter_AddRefs(uri)))) {
mUrl = uri->GetSpecOrDefault();
}
}
return NS_OK;
}
static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer,
nsITimerCallback* aCallback,
uint32_t aTimeout) {
MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(!aTimer);
if (!aTimeout) {
return;
}
NS_NewTimerWithCallback(getter_AddRefs(aTimer), aCallback, aTimeout,
nsITimer::TYPE_ONE_SHOT);
}
void nsHttpTransaction::OnPendingQueueInserted(
const nsACString& aConnectionHashKey) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
{
MutexAutoLock lock(mLock);
mHashKeyOfConnectionEntry.Assign(aConnectionHashKey);
}
// Don't create mHttp3BackupTimer if HTTPS RR is in play.
if (mConnInfo->IsHttp3() && !mOrigConnInfo && !mConnInfo->GetWebTransport()) {
// Backup timer should only be created once.
if (!mHttp3BackupTimerCreated) {
CreateAndStartTimer(mHttp3BackupTimer, this,
StaticPrefs::network_http_http3_backup_timer_delay());
mHttp3BackupTimerCreated = true;
}
}
}
nsresult nsHttpTransaction::AsyncRead(nsIStreamListener* listener,
nsIRequest** pump) {
RefPtr<nsInputStreamPump> transactionPump;
nsresult rv =
nsInputStreamPump::Create(getter_AddRefs(transactionPump), mPipeIn);
NS_ENSURE_SUCCESS(rv, rv);
rv = transactionPump->AsyncRead(listener);
NS_ENSURE_SUCCESS(rv, rv);
transactionPump.forget(pump);
return NS_OK;
}
// This method should only be used on the socket thread
nsAHttpConnection* nsHttpTransaction::Connection() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
return mConnection.get();
}
void nsHttpTransaction::SetH2WSConnRefTaken() {
if (!OnSocketThread()) {
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod("nsHttpTransaction::SetH2WSConnRefTaken", this,
&nsHttpTransaction::SetH2WSConnRefTaken);
gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
return;
}
}
UniquePtr<nsHttpResponseHead> nsHttpTransaction::TakeResponseHead() {
MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
// Lock TakeResponseHead() against main thread
MutexAutoLock lock(mLock);
mResponseHeadTaken = true;
// Even in OnStartRequest() the headers won't be available if we were
// canceled
if (!mHaveAllHeaders) {
NS_WARNING("response headers not available or incomplete");
return nullptr;
}
return WrapUnique(std::exchange(mResponseHead, nullptr));
}
UniquePtr<nsHttpHeaderArray> nsHttpTransaction::TakeResponseTrailers() {
MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
// Lock TakeResponseTrailers() against main thread
MutexAutoLock lock(mLock);
mResponseTrailersTaken = true;
return std::move(mForTakeResponseTrailers);
}
void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; }
nsHttpRequestHead* nsHttpTransaction::RequestHead() { return mRequestHead; }
uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
nsresult nsHttpTransaction::TakeSubTransactions(
nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) {
return NS_ERROR_NOT_IMPLEMENTED;
}
//----------------------------------------------------------------------------
// nsHttpTransaction::nsAHttpTransaction
//----------------------------------------------------------------------------
void nsHttpTransaction::SetConnection(nsAHttpConnection* conn) {
{
MutexAutoLock lock(mLock);
mConnection = conn;
if (mConnection) {
mIsHttp3Used = mConnection->Version() == HttpVersion::v3_0;
}
}
}
void nsHttpTransaction::OnActivated() {
MOZ_ASSERT(OnSocketThread());
if (mActivated) {
return;
}
if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
if (hta) {
hta->IncrementHttpTransaction(mTrafficCategory);
}
if (mConnection) {
mConnection->SetTrafficCategory(mTrafficCategory);
}
}
if (mConnection && mRequestHead &&
mConnection->Version() >= HttpVersion::v2_0) {
// So this is fun. On http/2, we want to send TE: trailers, to be
// spec-compliant. So we add it to the request head here. The fun part
// is that adding a header to the request head at this point has no
// effect on what we send on the wire, as the headers are already
// flattened (in Init()) by the time we get here. So the *real* adding
// of the header happens in the h2 compression code. We still have to
// add the header to the request head here, though, so that devtools can
// show that we sent the header. FUN!
Unused << mRequestHead->SetHeader(nsHttp::TE, "trailers"_ns);
}
mActivated = true;
gHttpHandler->ConnMgr()->AddActiveTransaction(this);
}
void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** cb) {
MutexAutoLock lock(mLock);
nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
tmp.forget(cb);
}
void nsHttpTransaction::SetSecurityCallbacks(
nsIInterfaceRequestor* aCallbacks) {
{
MutexAutoLock lock(mLock);
mCallbacks = aCallbacks;
}
if (gSocketTransportService) {
RefPtr<UpdateSecurityCallbacks> event =
new UpdateSecurityCallbacks(this, aCallbacks);
gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
}
}
void nsHttpTransaction::OnTransportStatus(nsITransport* transport,
nsresult status, int64_t progress) {
LOG1(("nsHttpTransaction::OnTransportStatus [this=%p status=%" PRIx32
" progress=%" PRId64 "]\n",
this, static_cast<uint32_t>(status), progress));
if (status == NS_NET_STATUS_CONNECTED_TO ||
status == NS_NET_STATUS_WAITING_FOR) {
if (mConnection) {
MutexAutoLock lock(mLock);
mConnection->GetSelfAddr(&mSelfAddr);
mConnection->GetPeerAddr(&mPeerAddr);
mResolvedByTRR = mConnection->ResolvedByTRR();
mEffectiveTRRMode = mConnection->EffectiveTRRMode();
mTRRSkipReason = mConnection->TRRSkipReason();
mEchConfigUsed = mConnection->GetEchConfigUsed();
}
}
// If the timing is enabled, and we are not using a persistent connection
// then the requestStart timestamp will be null, so we mark the timestamps
// for domainLookupStart/End and connectStart/End
// If we are using a persistent connection they will remain null,
// and the correct value will be returned in Performance.
if (GetRequestStart().IsNull()) {
if (status == NS_NET_STATUS_RESOLVING_HOST) {
SetDomainLookupStart(TimeStamp::Now(), true);
} else if (status == NS_NET_STATUS_RESOLVED_HOST) {
SetDomainLookupEnd(TimeStamp::Now());
} else if (status == NS_NET_STATUS_CONNECTING_TO) {
SetConnectStart(TimeStamp::Now());
} else if (status == NS_NET_STATUS_CONNECTED_TO) {
TimeStamp tnow = TimeStamp::Now();
SetConnectEnd(tnow, true);
{
MutexAutoLock lock(mLock);
mTimings.tcpConnectEnd = tnow;
}
} else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
{
MutexAutoLock lock(mLock);
mTimings.secureConnectionStart = TimeStamp::Now();
}
} else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
SetConnectEnd(TimeStamp::Now(), false);
} else if (status == NS_NET_STATUS_SENDING_TO) {
// Set the timestamp to Now(), only if it null
SetRequestStart(TimeStamp::Now(), true);
}
}
if (!mTransportSink) return;
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// Need to do this before the STATUS_RECEIVING_FROM check below, to make
// sure that the activity distributor gets told about all status events.
// upon STATUS_WAITING_FOR; report request body sent
if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) {
gHttpHandler->ObserveHttpActivityWithArgs(
HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0, ""_ns);
}
// report the status and progress
gHttpHandler->ObserveHttpActivityWithArgs(
HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
static_cast<uint32_t>(status), PR_Now(), progress, ""_ns);
// nsHttpChannel synthesizes progress events in OnDataAvailable
if (status == NS_NET_STATUS_RECEIVING_FROM) return;
int64_t progressMax;
if (status == NS_NET_STATUS_SENDING_TO) {
// suppress progress when only writing request headers
if (!mHasRequestBody) {
LOG1(
("nsHttpTransaction::OnTransportStatus %p "
"SENDING_TO without request body\n",
this));
return;
}
if (mReader) {
// A mRequestStream method is on the stack - wait.
LOG(
("nsHttpTransaction::OnSocketStatus [this=%p] "
"Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
this));
// its ok to coalesce several of these into one deferred event
mDeferredSendProgress = true;
return;
}
nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mRequestStream);
if (!tellable) {
LOG1(
("nsHttpTransaction::OnTransportStatus %p "
"SENDING_TO without tellable request stream\n",
this));
MOZ_ASSERT(
!mRequestStream,
"mRequestStream should be tellable as it was wrapped in "
"nsBufferedInputStream, which provides the tellable interface even "
"when wrapping non-tellable streams.");
progress = 0;
} else {
int64_t prog = 0;
tellable->Tell(&prog);
progress = prog;
}
// when uploading, we include the request headers in the progress
// notifications.
progressMax = mRequestSize;
} else {
progress = 0;
progressMax = 0;
}
mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
}
bool nsHttpTransaction::IsDone() { return mTransactionDone; }
nsresult nsHttpTransaction::Status() { return mStatus; }
uint32_t nsHttpTransaction::Caps() { return mCaps & ~mCapsToClear; }
void nsHttpTransaction::SetDNSWasRefreshed() {
MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread(),
"SetDNSWasRefreshed on target thread only!");
mCapsToClear |= NS_HTTP_REFRESH_DNS;
}
nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream* stream,
void* closure, const char* buf,
uint32_t offset, uint32_t count,
uint32_t* countRead) {
// For the tracking of sent bytes that we used to do for the networkstats
// API, please see bug 1318883 where it was removed.
nsHttpTransaction* trans = (nsHttpTransaction*)closure;
nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
if (NS_FAILED(rv)) {
trans->MaybeRefreshSecurityInfo();
return rv;
}
LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead));
trans->mSentData = true;
return NS_OK;
}
nsresult nsHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader,
uint32_t count, uint32_t* countRead) {
LOG(("nsHttpTransaction::ReadSegments %p", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mTransactionDone) {
*countRead = 0;
return mStatus;
}
if (!m0RTTInProgress) {
MaybeCancelFallbackTimer();
}
if (!mConnected && !m0RTTInProgress) {
mConnected = true;
MaybeRefreshSecurityInfo();
}
mDeferredSendProgress = false;
mReader = reader;
nsresult rv =
mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
mReader = nullptr;
if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) &&
NS_SUCCEEDED(rv) && (*countRead > 0)) {
LOG(("mEarlyDataDisposition = EARLY_SENT"));
mEarlyDataDisposition = EARLY_SENT;
}
if (mDeferredSendProgress && mConnection) {
// to avoid using mRequestStream concurrently, OnTransportStatus()
// did not report upload status off the ReadSegments() stack from
// nsSocketTransport do it now.
OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0);
}
mDeferredSendProgress = false;
if (mForceRestart) {
// The forceRestart condition was dealt with on the stack, but it did not
// clear the flag because nsPipe in the readsegment stack clears out
// return codes, so we need to use the flag here as a cue to return
// ERETARGETED
if (NS_SUCCEEDED(rv)) {
rv = NS_BINDING_RETARGETED;
}
mForceRestart = false;
}
// if read would block then we need to AsyncWait on the request stream.
// have callback occur on socket thread so we stay synchronized.
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(mRequestStream);
if (asyncIn) {
nsCOMPtr<nsIEventTarget> target;
Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
if (target) {
asyncIn->AsyncWait(this, 0, 0, target);
} else {
NS_ERROR("no socket thread event target");
rv = NS_ERROR_UNEXPECTED;
}
}
}
return rv;
}
nsresult nsHttpTransaction::WritePipeSegment(nsIOutputStream* stream,
void* closure, char* buf,
uint32_t offset, uint32_t count,
uint32_t* countWritten) {
nsHttpTransaction* trans = (nsHttpTransaction*)closure;
if (trans->mTransactionDone) return NS_BASE_STREAM_CLOSED; // stop iterating
// Set the timestamp to Now(), only if it null
trans->SetResponseStart(TimeStamp::Now(), true);
// Bug 1153929 - add checks to fix windows crash
MOZ_ASSERT(trans->mWriter);
if (!trans->mWriter) {
return NS_ERROR_UNEXPECTED;
}
nsresult rv;
//
// OK, now let the caller fill this segment with data.
//
rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
if (NS_FAILED(rv)) {
trans->MaybeRefreshSecurityInfo();
return rv; // caller didn't want to write anything
}
LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans,
*countWritten));
MOZ_ASSERT(*countWritten > 0, "bad writer");
trans->mReceivedData = true;
trans->mTransferSize += *countWritten;
// Let the transaction "play" with the buffer. It is free to modify
// the contents of the buffer and/or modify countWritten.
// - Bytes in HTTP headers don't count towards countWritten, so the input
// side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
// OnInputStreamReady until all headers have been parsed.
//
rv = trans->ProcessData(buf, *countWritten, countWritten);
if (NS_FAILED(rv)) trans->Close(rv);
return rv; // failure code only stops WriteSegments; it is not propagated.
}
bool nsHttpTransaction::ShouldThrottle() {
if (mClassOfServiceFlags & nsIClassOfService::DontThrottle) {
// We deliberately don't touch the throttling window here since
// DontThrottle requests are expected to be long-standing media
// streams and would just unnecessarily block running downloads.
// If we want to ballance bandwidth for media responses against
// running downloads, we need to find something smarter like
// changing the suspend/resume throttling intervals at-runtime.
return false;
}
if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
// We are not obligated to throttle
return false;
}
if (mContentRead < 16000) {
// Let the first bytes go, it may also well be all the content we get
LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64
") this=%p",
mContentRead, this));
return false;
}
if (!(mClassOfServiceFlags & nsIClassOfService::Throttleable) &&
gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
// This is expensive to check (two hashtable lookups) but may help
// freeing connections for active tab transactions.
// Checking this only for transactions that are not explicitly marked
// as throttleable because trackers and (specially) downloads should
// keep throttling even under pressure.
return false;
}
return true;
}
void nsHttpTransaction::DontReuseConnection() {
LOG(("nsHttpTransaction::DontReuseConnection %p\n", this));
if (!OnSocketThread()) {
LOG(("DontReuseConnection %p not on socket thread\n", this));
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod("nsHttpTransaction::DontReuseConnection", this,
&nsHttpTransaction::DontReuseConnection);
gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
return;
}
if (mConnection) {
mConnection->DontReuse();
}
}
nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
uint32_t count,
uint32_t* countWritten) {
LOG(("nsHttpTransaction::WriteSegments %p", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mTransactionDone) {
return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
}
if (ShouldThrottle()) {
if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set
// V1: ThrottlingReadLimit() returns 0
mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit();
}
} else {
mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit
}
if (mThrottlingReadAllowance == 0) { // depleted
if (gHttpHandler->ConnMgr()->CurrentBrowserId() != mBrowserId) {
nsHttp::NotifyActiveTabLoadOptimization();
}
// Must remember that we have to call ResumeRecv() on our connection when
// called back by the conn manager to resume reading.
LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
mReadingStopped = true;
// This makes the underlaying connection or stream wait for explicit resume.
// For h1 this means we stop reading from the socket.
// For h2 this means we stop updating recv window for the stream.
return NS_BASE_STREAM_WOULD_BLOCK;
}
mWriter = writer;
if (!mPipeOut) {
return NS_ERROR_UNEXPECTED;
}
if (mThrottlingReadAllowance > 0) {
LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
this, count, mThrottlingReadAllowance));
count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance));
}
nsresult rv =
mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
mWriter = nullptr;
if (mForceRestart) {
// The forceRestart condition was dealt with on the stack, but it did not
// clear the flag because nsPipe in the writesegment stack clears out
// return codes, so we need to use the flag here as a cue to return
// ERETARGETED
if (NS_SUCCEEDED(rv)) {
rv = NS_BINDING_RETARGETED;
}
mForceRestart = false;
}
// if pipe would block then we need to AsyncWait on it. have callback
// occur on socket thread so we stay synchronized.
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
nsCOMPtr<nsIEventTarget> target;
Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
if (target) {
mPipeOut->AsyncWait(this, 0, 0, target);
mWaitingOnPipeOut = true;
} else {
NS_ERROR("no socket thread event target");
rv = NS_ERROR_UNEXPECTED;
}
} else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {