/* -*- 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"
// 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)
// 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)));
}
}
}
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)) {}
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
LOG(
("nsHttpTransaction aborting init because of app" "shutdown")); return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
// 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;
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;
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;
// 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;
}
}
}
// This method should only be used on the socket thread
nsAHttpConnection* nsHttpTransaction::Connection() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); return mConnection.get();
}
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;
}
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);
}
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);
} elseif (status == NS_NET_STATUS_RESOLVED_HOST) {
SetDomainLookupEnd(TimeStamp::Now());
} elseif (status == NS_NET_STATUS_CONNECTING_TO) {
SetConnectStart(TimeStamp::Now());
} elseif (status == NS_NET_STATUS_CONNECTED_TO) {
TimeStamp tnow = TimeStamp::Now();
SetConnectEnd(tnow, true);
{
MutexAutoLock lock(mLock);
mTimings.tcpConnectEnd = tnow;
}
} elseif (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
{
MutexAutoLock lock(mLock);
mTimings.secureConnectionStart = TimeStamp::Now();
}
} elseif (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
SetConnectEnd(TimeStamp::Now(), false);
} elseif (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;
}
nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream* stream, void* closure, constchar* 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;
}
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;
}
}
}
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
}
// 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. returnfalse;
}
if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) { // We are not obligated to throttle returnfalse;
}
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)); returnfalse;
}
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. returnfalse;
}
returntrue;
}
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;
}
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));
}
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;
}
} elseif (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {
MOZ_ASSERT(count >= *countWritten);
mThrottlingReadAllowance -= *countWritten;
}
// Note that it's possible that we can't get any usable record here. For // example, when http3 connection is failed, we won't select records with // http3 alpn.
// If not all records have echConfig, we'll directly fallback to the origin // server. if (!aAllRecordsHaveEchConfig) { returnfalse;
}
// Take the records behind the failed one and put them into mRecordsForRetry. for (constauto& record : records) {
nsAutoCString name;
record->GetName(name);
nsAutoCString alpn;
nsresult rv = record->GetSelectedAlpn(alpn);
if (name == aFailedDomainName) { // If the record has no alpn or the alpn is already tried, we skip this // record. if (NS_FAILED(rv) || alpn == aFailedAlpn) { continue;
}
}
mRecordsForRetry.InsertElementAt(0, record);
}
// Set mHTTPSSVCRecord to null to avoid this function being executed twice.
mHTTPSSVCRecord = nullptr; return !mRecordsForRetry.IsEmpty();
}
if (fastFallbackRecord && aEchConfigUsed) {
nsAutoCString echConfig;
Unused << fastFallbackRecord->GetEchConfig(echConfig); if (echConfig.IsEmpty()) {
fastFallbackRecord = nullptr;
}
}
if (!fastFallbackRecord) { if (aEchConfigUsed) {
LOG(
("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record " "can be used", this)); return nullptr;
}
if (!echConfigUsed) {
LOG((" echConfig is not used, fallback to origin conn info"));
useOrigConnInfoToRetry(); return;
}
Telemetry::HistogramID id = Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT; auto updateCount = MakeScopeExit([&] { auto entry = mEchRetryCounterMap.Lookup(id);
MOZ_ASSERT(entry, "table not initialized"); if (entry) {
*entry += 1;
}
});
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
failedConnInfo->SetEchConfig(EmptyCString());
failedConnInfo.swap(mConnInfo);
id = Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT; return;
}
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
MOZ_ASSERT(mConnection);
nsCOMPtr<nsITLSSocketControl> socketControl; if (mConnection) {
mConnection->GetTLSSocketControl(getter_AddRefs(socketControl));
}
MOZ_ASSERT(socketControl);
nsAutoCString retryEchConfig; if (socketControl &&
NS_SUCCEEDED(socketControl->GetRetryEchConfig(retryEchConfig))) {
MOZ_ASSERT(!retryEchConfig.IsEmpty());
failedConnInfo->SetEchConfig(retryEchConfig);
failedConnInfo.swap(mConnInfo);
}
id = Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT; return;
}
// Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but // also for all failure cases. if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) ||
NS_FAILED(aReason)) {
LOG((" Got SSL_ERROR_ECH_FAILED, try other records")); if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) {
id = Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT;
} if (mRecordsForRetry.IsEmpty()) { if (mHTTPSSVCRecord) { bool allRecordsHaveEchConfig = true; if (!PrepareSVCBRecordsForRetry(failedConnInfo->GetRoutedHost(),
failedConnInfo->GetNPNToken(),
allRecordsHaveEchConfig)) {
LOG(
(" Can't find other records with echConfig, " "allRecordsHaveEchConfig=%d",
allRecordsHaveEchConfig)); if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
!allRecordsHaveEchConfig) {
useOrigConnInfoToRetry();
} return;
}
} else {
LOG((" No available records to retry")); if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed()) {
useOrigConnInfoToRetry();
} return;
}
}
if (LOG5_ENABLED()) {
LOG(("SvcDomainName to retry: [")); for (constauto& r : mRecordsForRetry) {
nsAutoCString name;
r->GetName(name);
nsAutoCString alpn;
r->GetSelectedAlpn(alpn);
LOG((" name=%s alpn=%s", name.get(), alpn.get()));
}
LOG(("]"));
}
if (!mClosed) {
gHttpHandler->ConnMgr()->RemoveActiveTransaction(this);
mActivated = false;
}
if (mDNSRequest) {
mDNSRequest->Cancel(NS_ERROR_ABORT);
mDNSRequest = nullptr;
}
MaybeCancelFallbackTimer();
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); if (reason == NS_BINDING_RETARGETED) {
LOG((" close %p skipped due to ERETARGETED\n", this)); return;
}
if (mClosed) {
LOG((" already closed\n")); return;
}
NotifyTransactionObserver(reason);
if (mTokenBucketCancel) {
mTokenBucketCancel->Cancel(reason);
mTokenBucketCancel = nullptr;
}
// report the reponse is complete if not already reported if (!mResponseIsComplete) {
gHttpHandler->ObserveHttpActivityWithArgs(
HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(), static_cast<uint64_t>(mContentRead), ""_ns);
}
// report that this transaction is closing
gHttpHandler->ObserveHttpActivityWithArgs(
HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
// we must no longer reference the connection! find out if the // connection was being reused before letting it go. bool connReused = false; bool isHttp2or3 = false; if (mConnection) {
connReused = mConnection->IsReused();
isHttp2or3 = mConnection->Version() >= HttpVersion::v2_0; if (!mConnected) {
MaybeRefreshSecurityInfo();
}
}
mConnected = false;
// When mDoNotRemoveAltSvc is true, this means we want to keep the AltSvc in // in the conncetion info. In this case, let's not apply HTTPS RR retry logic // to make sure this transaction can be restarted with the same conncetion // info. bool shouldRestartTransactionForHTTPSRR =
mOrigConnInfo && AllowedErrorForHTTPSRRFallback(reason) &&
!mDoNotRemoveAltSvc;
// // if the connection was reset or closed before we wrote any part of the // request or if we wrote the request but didn't receive any part of the // response and the connection was being reused, then we can (and really // should) assume that we wrote to a stale connection and we must therefore // repeat the request over a new connection. // // We have decided to retry not only in case of the reused connections, but // all safe methods(bug 1236277). // // NOTE: the conditions under which we will automatically retry the HTTP // request have to be carefully selected to avoid duplication of the // request from the point-of-view of the server. such duplication could // have dire consequences including repeated purchases, etc. // // NOTE: because of the way SSL proxy CONNECT is implemented, it is // possible that the transaction may have received data without having // sent any data. for this reason, mSendData == FALSE does not imply // mReceivedData == FALSE. (see bug 203057 for more info.) // // Never restart transactions that are marked as sticky to their conenction. // We use that capability to identify transactions bound to connection based // authentication. Reissuing them on a different connections will break // this bondage. Major issue may arise when there is an NTLM message auth // header on the transaction and we send it to a different NTLM authenticated // connection. It will break that connection and also confuse the channel's // auth provider, beliving the cached credentials are wrong and asking for // the password mistakenly again from the user. if ((reason == NS_ERROR_NET_RESET || reason == NS_OK ||
reason ==
psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
ShouldRestartOn0RttError(reason) ||
shouldRestartTransactionForHTTPSRR) &&
(!(mCaps & NS_HTTP_STICKY_CONNECTION) ||
(mCaps & NS_HTTP_CONNECTION_RESTARTABLE) ||
(mEarlyDataDisposition == EARLY_425))) { if (mForceRestart) {
SetRestartReason(TRANSACTION_RESTART_FORCED); if (NS_SUCCEEDED(Restart())) { if (mResponseHead) {
mResponseHead->Reset();
}
mContentRead = 0;
mContentLength = -1; delete mChunkedDecoder;
mChunkedDecoder = nullptr;
mHaveStatusLine = false;
mHaveAllHeaders = false;
mHttpResponseMatched = false;
mResponseIsComplete = false;
mDidContentStart = false;
mNoContent = false;
mSentData = false;
mReceivedData = false;
mSupportsHTTP3 = false;
LOG(("transaction force restarted\n")); return;
}
}
mDoNotTryEarlyData = true;
// reallySentData is meant to separate the instances where data has // been sent by this transaction but buffered at a higher level while // a TLS session (perhaps via a tunnel) is setup. bool reallySentData =
mSentData && (!mConnection || mConnection->BytesWritten());
// If this is true, it means we failed to use the HTTPSSVC connection info // to connect to the server. We need to retry with the original connection // info.
shouldRestartTransactionForHTTPSRR &= !reallySentData;
if (reason ==
psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
PossibleZeroRTTRetryError(reason) ||
(!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) ||
!reallySentData || connReused)) ||
shouldRestartTransactionForHTTPSRR) { if (shouldRestartTransactionForHTTPSRR) {
MaybeReportFailedSVCDomain(reason, mConnInfo);
PrepareConnInfoForRetry(reason);
mDontRetryWithDirectRoute = true;
LOG(
("transaction will be restarted with the fallback connection info " "key=%s",
mConnInfo ? mConnInfo->HashKey().get() : "None"));
}
if (shouldRestartTransactionForHTTPSRR) { auto toRestartReason =
[](nsresult aStatus) -> TRANSACTION_RESTART_REASON { if (aStatus == NS_ERROR_NET_RESET) { return TRANSACTION_RESTART_HTTPS_RR_NET_RESET;
} if (aStatus == NS_ERROR_CONNECTION_REFUSED) { return TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED;
} if (aStatus == NS_ERROR_UNKNOWN_HOST) { return TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST;
} if (aStatus == NS_ERROR_NET_TIMEOUT) { return TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT;
} if (psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aStatus))) { return TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR;
}
MOZ_ASSERT_UNREACHABLE("Unexpected reason"); return TRANSACTION_RESTART_OTHERS;
};
SetRestartReason(toRestartReason(reason));
} elseif (!reallySentData) {
SetRestartReason(TRANSACTION_RESTART_NO_DATA_SENT);
} elseif (reason == psm::GetXPCOMFromNSSError(
SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) {
SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA);
} elseif (PossibleZeroRTTRetryError(reason)) {
SetRestartReason(TRANSACTION_RESTART_POSSIBLE_0RTT_ERROR);
} // if restarting fails, then we must proceed to close the pipe, // which will notify the channel that the transaction failed. // Note that when echConfig is enabled, it's possible that we don't have a // usable connection info to retry. if (mConnInfo && NS_SUCCEEDED(Restart())) { return;
} // mConnInfo could be set to null in PrepareConnInfoForRetry() when we // can't find an available https rr to retry. We have to set mConnInfo // back to mOrigConnInfo to make sure no crash when mConnInfo being // accessed again. if (!mConnInfo) {
mConnInfo.swap(mOrigConnInfo);
MOZ_ASSERT(mConnInfo);
}
}
}
if (!mResponseIsComplete && NS_SUCCEEDED(reason) && isHttp2or3) { // Responses without content-length header field are still complete if // they are transfered over http2 or http3 and the stream is properly // closed.
mResponseIsComplete = true;
}
if (reason == NS_ERROR_NET_RESET && mResponseIsComplete && isHttp2or3) { // See bug 1940663. When using HTTP/2 or HTTP/3, receiving the // NS_ERROR_NET_RESET error code indicates that the connection intends // to restart this transaction. However, if the transaction has already // completed and we've passed the point of restarting, we should avoid // propagating the error code and overwrite it to NS_OK. // // TODO: Refactor the mechanism by which a connection instructs a // transaction to restart. This will allow us to remove this hack.
LOG(("Transaction is already done, overriding error code to NS_OK"));
reason = NS_OK;
}
if ((mHttpResponseCode / 100 == 2) && (mHttpVersion >= HttpVersion::v1_1)) {
FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing(); if (clevel >= FRAMECHECK_BARELY) { // If clevel == FRAMECHECK_STRICT mark any incomplete response as // partial. // if clevel == FRAMECHECK_BARELY: 1) mark a chunked-encoded response // that do not ends on exactly a chunk boundary as partial; We are not // strict about the last 0-size chunk and do not mark as parial // responses that do not have the last 0-size chunk but do end on a // chunk boundary. (check mChunkedDecoder->GetChunkRemaining() != 0) // 2) mark a transfer that is partial and it is not chunk-encoded or // gzip-encoded or other content-encoding as partial. (check // !mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) // if clevel == FRAMECHECK_STRICT_CHUNKED mark a chunked-encoded // response that ends on exactly a chunk boundary also as partial. // Here a response must have the last 0-size chunk. if ((clevel == FRAMECHECK_STRICT) ||
(mChunkedDecoder && (mChunkedDecoder->GetChunkRemaining() ||
(clevel == FRAMECHECK_STRICT_CHUNKED))) ||
(!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) {
reason = NS_ERROR_NET_PARTIAL_TRANSFER;
LOG(("Partial transfer, incomplete HTTP response received: %s",
mChunkedDecoder ? "broken chunk" : "c-l underrun"));
}
}
}
if (mConnection) { // whether or not we generate an error for the transaction // bad framing means we don't want a pconn
mConnection->DontReuse();
}
}
bool relConn = true; if (NS_SUCCEEDED(reason)) { // the server has not sent the final \r\n terminating the header // section, and there may still be a header line unparsed. let's make // sure we parse the remaining header line, and then hopefully, the // response will be usable (see bug 88792). if (!mHaveAllHeaders) { char data[] = "\n\n";
uint32_t unused = 0; // If we have a partial line already, we actually need two \ns to finish // the headers section.
Unused << ParseHead(data, mLineBuf.IsEmpty() ? 1 : 2, &unused);
// honor the sticky connection flag... if (mCaps & NS_HTTP_STICKY_CONNECTION) {
LOG((" keeping the connection because of STICKY_CONNECTION flag"));
relConn = false;
}
// if the proxy connection has failed, we want the connection be held // to allow the upper layers (think nsHttpChannel) to close it when // the failure is unrecoverable. // we can't just close it here, because mProxyConnectFailed is to a general // flag and is also set for e.g. 407 which doesn't mean to kill the // connection, specifically when connection oriented auth may be involved. if (mProxyConnectFailed) {
LOG((" keeping the connection because of mProxyConnectFailed"));
relConn = false;
}
// Use mOrigConnInfo as an indicator that this transaction is completed // successfully with an HTTPSSVC record. if (mOrigConnInfo) {
Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
HTTPSSVC_CONNECTION_OK);
}
}
// mTimings.responseEnd is normally recorded based on the end of a // HTTP delimiter such as chunked-encodings or content-length. However, // EOF or an error still require an end time be recorded.
if (isHttp2or3 &&
reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) { // Change reason to NS_ERROR_ABORT, so we avoid showing a missleading // error page tthat TLS1.0 is disabled. H2 or H3 is used here so the // TLS version is not a problem.
reason = NS_ERROR_ABORT;
}
mStatus = reason;
mTransactionDone = true; // forcibly flag the transaction as complete
mClosed = true; if (mResolver) {
mResolver->Close();
mResolver = nullptr;
}
ReleaseBlockingTransaction();
// release some resources that we no longer need
mRequestStream = nullptr;
mReqHeaderBuf.Truncate();
mLineBuf.Truncate(); if (mChunkedDecoder) { delete mChunkedDecoder;
mChunkedDecoder = nullptr;
}
for (constauto& entry : mEchRetryCounterMap) {
Telemetry::Accumulate(static_cast<Telemetry::HistogramID>(entry.GetKey()),
entry.GetData());
}
// closing this pipe triggers the channel's OnStopRequest method.
mPipeOut->CloseWithStatus(reason);
}
nsresult nsHttpTransaction::Restart() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// limit the number of restart attempts - bug 92224 if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
LOG(("reached max request attempts, failing transaction @%p\n", this)); return NS_ERROR_NET_RESET;
}
LOG(("restarting transaction @%p\n", this));
if (mRequestHead) { // Dispatching on a new connection better w/o an ambient connection proxy // auth request header to not confuse the proxy authenticator.
nsAutoCString proxyAuth; if (NS_SUCCEEDED(
mRequestHead->GetHeader(nsHttp::Proxy_Authorization, proxyAuth)) &&
IsStickyAuthSchemeAt(proxyAuth)) {
Unused << mRequestHead->ClearHeader(nsHttp::Proxy_Authorization);
}
}
// rewind streams in case we already wrote out the request
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.