/* -*- 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"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()
// the default timeout is for when this connection has not yet processed a // transaction staticconst PRIntervalTime k5Sec = PR_SecondsToInterval(5);
mIdleTimeout = (k5Sec < gHttpHandler->IdleTimeout())
? k5Sec
: gHttpHandler->IdleTimeout();
}
if (!mEverUsedSpdy) {
LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n", this,
mHttp1xTransactionCount));
Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_CONN,
mHttp1xTransactionCount);
nsHttpConnectionInfo* ci = nullptr; if (mTransaction) {
ci = mTransaction->ConnectionInfo();
} if (!ci) {
ci = mConnInfo;
}
MOZ_ASSERT(ci); if (ci->GetIsTrrServiceChannel()) {
mozilla::glean::networking::trr_request_count_per_conn.Get("h1"_ns).Add( static_cast<int32_t>(mHttp1xTransactionCount));
}
}
// See explanation for non-strictness of this operation in // SetSecurityCallbacks.
mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>( "nsHttpConnection::mCallbacks", callbacks, false);
if (rv == NS_ERROR_ALREADY_OPENED) { // Has the interface for TakeSubTransactions() changed?
LOG(
("TakeSubTransactions somehow called after " "nsAHttpTransaction began processing\n"));
MOZ_ASSERT(false, "TakeSubTransactions somehow called after " "nsAHttpTransaction began processing");
mTransaction->Close(NS_ERROR_ABORT); return rv;
}
if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { // Has the interface for TakeSubTransactions() changed?
LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
MOZ_ASSERT(false, "unexpected result from " "nsAHttpTransaction::TakeSubTransactions()");
mTransaction->Close(NS_ERROR_ABORT); return rv;
}
nsresult nsHttpConnection::MoveTransactionsToSpdy(
nsresult status, nsTArray<RefPtr<nsAHttpTransaction> >& list) { if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED
MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
// If this transaction is used to drive websocket, we reset it to put it in // the pending queue. Once we know if the server supports websocket or not, // the pending queue will be processed.
nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); if (trans && trans->IsWebsocketUpgrade()) {
LOG(("nsHttpConnection resetting transaction for websocket upgrade")); // websocket upgrade needs NonSticky for transaction reset
mTransaction->MakeNonSticky();
ResetTransaction(std::move(mTransaction));
mTransaction = nullptr; return NS_OK;
}
// This is ok - treat mTransaction as a single real request. // Wrap the old http transaction into the new spdy session // as the first stream.
LOG(
("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p " "into SpdySession %p\n",
mTransaction.get(), mSpdySession.get()));
nsresult rv = AddTransaction(mTransaction, mPriority); if (NS_FAILED(rv)) { return rv;
}
} else {
int32_t count = list.Length();
if (!mDid0RTTSpdy) {
mSpdySession =
ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, false);
}
if (!mReportedSpdy) {
mReportedSpdy = true; // See bug 1797729. // It's possible that we already have a HTTP/3 connection that can be // coleased with this connection. We should avoid coalescing with the // existing HTTP/3 connection if the transaction doesn't allow to use // HTTP/3.
gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true,
mTransactionDisallowHttp3);
}
// Setting the connection as reused allows some transactions that fail // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code // to handle clean rejections (such as those that arrived after // a server goaway was generated).
mIsReused = true;
// If mTransaction is a muxed object it might represent // several requests. If so, we need to unpack that and // pack them all into a new spdy session.
nsTArray<RefPtr<nsAHttpTransaction> > list;
nsresult status = NS_OK; if (!mDid0RTTSpdy && mTransaction) {
status = TryTakeSubTransactions(list);
if (NS_FAILED(status) && status != NS_ERROR_NOT_IMPLEMENTED) { return;
}
}
if (NeedSpdyTunnel()) {
LOG3(
("nsHttpConnection::StartSpdy %p Connecting To a HTTP/2 " "Proxy and Need Connect", this));
SetTunnelSetupDone();
}
// this is happening after the bootstrap was originally written to. so update // it. if (mTransaction && mTransaction->QueryNullTransaction() &&
(mBootstrappedTimings.secureConnectionStart.IsNull() ||
mBootstrappedTimings.tcpConnectEnd.IsNull())) {
mBootstrappedTimings.secureConnectionStart =
mTransaction->QueryNullTransaction()->GetSecureConnectionStart();
mBootstrappedTimings.tcpConnectEnd =
mTransaction->QueryNullTransaction()->GetTcpConnectEnd();
}
if (hasSecurityInfo) {
mBootstrappedTimings.connectEnd = TimeStamp::Now();
}
if (earlyDataUsed) { // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
LOG(("nsHttpConnection::PostProcessNPNSetup [this=%p] 0rtt failed", this)); if (mTransaction && NS_FAILED(mTransaction->Finish0RTT(true, true))) {
mTransaction->Close(NS_ERROR_NET_RESET);
}
mContentBytesWritten0RTT = 0; if (mDid0RTTSpdy) {
Reset0RttForSpdy();
}
}
if (hasSecurityInfo) { // Telemetry for tls failure rate with and without esni; bool echConfigUsed = false;
mSocketTransport->GetEchConfigUsed(&echConfigUsed);
TlsHandshakeResult result =
echConfigUsed
? (handshakeSucceeded ? TlsHandshakeResult::EchConfigSuccessful
: TlsHandshakeResult::EchConfigFailed)
: (handshakeSucceeded ? TlsHandshakeResult::NoEchConfigSuccessful
: TlsHandshakeResult::NoEchConfigFailed);
Telemetry::Accumulate(Telemetry::ECHCONFIG_SUCCESS_RATE, result);
}
}
void nsHttpConnection::Reset0RttForSpdy() { // Reset the work done by Start0RTTSpdy
mUsingSpdyVersion = SpdyVersion::NONE;
mTransaction = nullptr;
mSpdySession = nullptr; // We have to reset this here, just in case we end up starting spdy again, // so it can actually do everything it needs to do.
mDid0RTTSpdy = false;
}
// called on the socket thread
nsresult nsHttpConnection::Activate(nsAHttpTransaction* trans, uint32_t caps,
int32_t pri) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG1(("nsHttpConnection::Activate [this=%p trans=%p caps=%x]\n", this, trans,
caps));
if (!mExperienced && !trans->IsNullTransaction()) {
mHasFirstHttpTransaction = true; if (mTlsHandshaker->NPNComplete()) {
mExperienced = true;
} if (mBootstrappedTimingsSet) {
mBootstrappedTimingsSet = false;
nsHttpTransaction* hTrans = trans->QueryHttpTransaction(); if (hTrans) {
hTrans->BootstrapTimings(mBootstrappedTimings);
SetUrgentStartPreferred(hTrans->GetClassOfService().Flags() &
nsIClassOfService::UrgentStart);
}
}
mBootstrappedTimings = TimingStruct();
}
if (caps & NS_HTTP_LARGE_KEEPALIVE) {
mDefaultTimeoutFactor = StaticPrefs::network_http_largeKeepaliveFactor();
}
// reset the read timers to wash away any idle time
mLastWriteTime = mLastReadTime = PR_IntervalNow();
// Connection failures are Activated() just like regular transacions. // If we don't have a confirmation of a connected socket then test it // with a write() to get relevant error code. if (NS_FAILED(mErrorBeforeConnect)) {
mSocketOutCondition = mErrorBeforeConnect;
mTransaction = trans;
CloseTransaction(mTransaction, mSocketOutCondition); return mSocketOutCondition;
}
// need to handle HTTP CONNECT tunnels if this is the first time if // we are tunneling through a proxy
nsresult rv = CheckTunnelIsNeeded(); if (NS_FAILED(rv)) goto failed_activation;
// Clear the per activation counter
mCurrentBytesRead = 0;
// The overflow state is not needed between activations
mInputOverflow = nullptr;
if (NS_SUCCEEDED(rv) && mContinueHandshakeDone) {
mContinueHandshakeDone();
}
mContinueHandshakeDone = nullptr;
failed_activation: if (NS_FAILED(rv)) {
mTransaction = nullptr;
}
return rv;
}
nsresult nsHttpConnection::AddTransaction(nsAHttpTransaction* httpTransaction,
int32_t priority) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mSpdySession && (mUsingSpdyVersion != SpdyVersion::NONE), "AddTransaction to live http connection without spdy/quic");
// If this is a wild card nshttpconnection (i.e. a spdy proxy) then // it is important to start the stream using the specific connection // info of the transaction to ensure it is routed on the right tunnel
// Let the transaction know that the tunnel is already established and we // don't need to setup the tunnel again. if (transCI->UsingConnect() && mEverUsedSpdy && mHasTLSTransportLayer) {
httpTransaction->OnProxyConnectComplete(200);
}
LOG(("nsHttpConnection::AddTransaction [this=%p] for %s%s", this,
mSpdySession ? "SPDY" : "QUIC", needTunnel ? " over tunnel" : ""));
if (mSpdySession) { if (!mSpdySession->AddStream(httpTransaction, priority, mCallbacks)) {
MOZ_ASSERT(false); // this cannot happen!
httpTransaction->Close(NS_ERROR_ABORT); return NS_ERROR_FAILURE;
}
}
RefPtr<nsHttpConnection> conn = mSpdySession->CreateTunnelStream(
httpTransaction, mCallbacks, mRtt, aIsWebSocket); // We need to store the refrence of the Http2Session in the tunneled // connection, so when nsHttpConnection::DontReuse is called the Http2Session // can't be reused. if (aIsWebSocket) {
LOG(
("nsHttpConnection::CreateTunnelStream %p Set h2 session %p to " "tunneled conn %p", this, mSpdySession.get(), conn.get()));
conn->mWebSocketHttp2Session = mSpdySession;
}
conn.forget(aHttpConnection); return NS_OK;
}
if (mConnectionState != ConnectionState::CLOSED) {
RecordConnectionCloseTelemetry(reason);
ChangeConnectionState(ConnectionState::CLOSED);
}
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mTlsHandshaker->NotifyClose();
mContinueHandshakeDone = nullptr;
mWebSocketHttp2Session = nullptr; // Ensure TCP keepalive timer is stopped. if (mTCPKeepaliveTransitionTimer) {
mTCPKeepaliveTransitionTimer->Cancel();
mTCPKeepaliveTransitionTimer = nullptr;
} if (mForceSendTimer) {
mForceSendTimer->Cancel();
mForceSendTimer = nullptr;
}
if (!mTrafficCategory.IsEmpty()) {
HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer(); if (hta) {
hta->IncrementHttpConnection(std::move(mTrafficCategory));
MOZ_ASSERT(mTrafficCategory.IsEmpty());
}
}
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
GetTLSSocketControl(getter_AddRefs(tlsSocketControl)); if (tlsSocketControl) {
tlsSocketControl->SetHandshakeCallbackListener(nullptr);
}
if (NS_FAILED(reason)) { if (mIdleMonitoring) EndIdleMonitoring();
// The connection and security errors clear out alt-svc mappings // in case any previously validated ones are now invalid if (((reason == NS_ERROR_NET_RESET) ||
(NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
gHttpHandler->ClearHostMapping(mConnInfo);
} if (mTlsHandshaker->EarlyDataWasAvailable() &&
PossibleZeroRTTRetryError(reason)) {
gHttpHandler->Exclude0RttTcp(mConnInfo);
}
if (mSocketTransport) {
mSocketTransport->SetEventSink(nullptr, nullptr);
// If there are bytes sitting in the input queue then read them // into a junk buffer to avoid generating a tcp rst by closing a // socket with data pending. TLS is a classic case of this where // a Alert record might be superfulous to a clean HTTP/SPDY shutdown. // Never block to do this and limit it to a small amount of data. // During shutdown just be fast! if (mSocketIn && !aIsShutdown && !mInSpdyTunnel) { char buffer[4000];
uint32_t count, total = 0;
nsresult rv; do {
rv = mSocketIn->Read(buffer, 4000, &count); if (NS_SUCCEEDED(rv)) total += count;
} while (NS_SUCCEEDED(rv) && count > 0 && total < 64000);
LOG(("nsHttpConnection::Close drained %d bytes\n", total));
}
// An idle persistent connection should not have data waiting to be read // before a request is sent. Data here is likely a 408 timeout response // which we would deal with later on through the restart logic, but that // path is more expensive than just closing the socket now.
uint64_t dataSize; if (canReuse && mSocketIn && (mUsingSpdyVersion == SpdyVersion::NONE) &&
mHttp1xTransactionCount &&
NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
LOG(
("nsHttpConnection::CanReuse %p %s" "Socket not reusable because read data pending (%" PRIu64 ") on it.\n", this, mConnInfo->Origin(), dataSize));
canReuse = false;
} return canReuse;
}
bool nsHttpConnection::CanDirectlyActivate() { // return true if a new transaction can be addded to ths connection at any // time through Activate(). In practice this means this is a healthy SPDY // connection with room for more concurrent streams.
// returns the number of seconds left before the allowable idle period // expires, or 0 if the period has already expied.
uint32_t nsHttpConnection::TimeToLive() {
LOG(("nsHttpConnection::TTL: %p %s idle %d timeout %d\n", this,
mConnInfo->Origin(), IdleTime(), mIdleTimeout));
// a positive amount of time can be rounded to 0. Because 0 is used // as the expiration signal, round all values from 0 to 1 up to 1. if (!timeToLive) {
timeToLive = 1;
} return timeToLive;
}
bool nsHttpConnection::IsAlive() { if (!mSocketTransport || !mConnectedTransport) returnfalse;
// SocketTransport::IsAlive can run the SSL state machine, so make sure // the NPN options are set before that happens.
mTlsHandshaker->SetupSSL(mInSpdyTunnel, mForcePlainText);
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
NS_ENSURE_ARG_POINTER(trans);
MOZ_ASSERT(responseHead, "No response head?");
if (mInSpdyTunnel) {
DebugOnly<nsresult> rv =
responseHead->SetHeader(nsHttp::X_Firefox_Spdy_Proxy, "true"_ns);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// we won't change our keep-alive policy unless the server has explicitly // told us to do so.
// inspect the connection headers for keep-alive info provided the // transaction completed successfully. In the case of a non-sensical close // and keep-alive favor the close out of conservatism.
// deal with 408 Server Timeouts
uint16_t responseStatus = responseHead->Status(); if (responseStatus == 408) { // timeouts that are not caused by persistent connection reuse should // not be retried for browser compatibility reasons. bug 907800. The // server driven close is implicit in the 408.
explicitClose = true;
explicitKeepAlive = false;
}
if ((responseHead->Version() < HttpVersion::v1_1) ||
(requestHead->Version() < HttpVersion::v1_1)) { // HTTP/1.0 connections are by default NOT persistent
mKeepAlive = explicitKeepAlive;
} else { // HTTP/1.1 connections are by default persistent
mKeepAlive = !explicitClose;
}
mKeepAliveMask = mKeepAlive;
// if this connection is persistent, then the server may send a "Keep-Alive" // header specifying the maximum number of times the connection can be // reused as well as the maximum amount of time the connection can be idle // before the server will close it. we ignore the max reuse count, because // a "keep-alive" connection is by definition capable of being reused, and // we only care about being able to reuse it once. if a timeout is not // specified then we use our advertized timeout value. bool foundKeepAliveMax = false; if (mKeepAlive) {
nsAutoCString keepAlive;
Unused << responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
switch (mState) { case HttpConnectionState::SETTING_UP_TUNNEL: {
nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); // Distinguish SETTING_UP_TUNNEL for proxy or websocket via proxy // See bug 1848013. Do not call HandleTunnelResponse for a tunnel // connection created for WebSocket. if (trans && trans->IsWebsocketUpgrade() &&
(trans->GetProxyConnectResponseCode() == 200 ||
(mForWebSocket && mInSpdyTunnel))) {
HandleWebSocketResponse(requestHead, responseHead, responseStatus);
} else {
HandleTunnelResponse(responseStatus, reset);
} break;
} default: if (requestHead->HasHeader(nsHttp::Upgrade)) {
HandleWebSocketResponse(requestHead, responseHead, responseStatus);
} elseif (responseStatus == 101) { // We got an 101 but we are not asking of a WebSsocket?
Close(NS_ERROR_ABORT);
}
}
void nsHttpConnection::HandleTunnelResponse(uint16_t responseStatus, bool* reset) {
LOG(("nsHttpConnection::HandleTunnelResponse()"));
MOZ_ASSERT(TunnelSetupInProgress());
MOZ_ASSERT(mProxyConnectStream);
MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE, "SPDY NPN Complete while using proxy connect stream"); // If we're doing a proxy connect, we need to check whether or not // it was successful. If so, we have to reset the transaction and step-up // the socket connection if using SSL. Finally, we have to wake up the // socket write request.
mTransaction->OnProxyConnectComplete(responseStatus); if (responseStatus == 200) {
LOG(("proxy CONNECT succeeded! endtoendssl=%d onlyconnect=%d\n", isHttps,
onlyConnect)); // If we're only connecting, we don't need to reset the transaction // state. We need to upgrade the socket now without doing the actual // http request. if (!onlyConnect) {
*reset = true;
}
nsresult rv; if (isHttps) { bool skipSSL = false; if (mConnInfo->UsingHttpsProxy() ||
mTransactionCaps & NS_HTTP_TLS_TUNNEL) {
LOG(("%p SetupSecondaryTLS %s %d\n", this, mConnInfo->Origin(),
mConnInfo->OriginPort()));
SetupSecondaryTLS();
} elseif (onlyConnect) {
MOZ_ASSERT(mConnInfo->UsingOnlyHttpProxy(), "Must be a HTTP proxy");
// We have CONNECT only flag and a HTTP proxy is used here, so we can // just skip setting up SSL. We have to mark this as complete to finish // the transaction and be upgraded.
mTlsHandshaker->SetNPNComplete();
skipSSL = true;
}
if (!skipSSL) {
rv = mTlsHandshaker->InitSSLParams(false, true);
LOG(("InitSSLParams [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
}
}
rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // XXX what if this fails -- need to handle this error
MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
} else {
LOG(("proxy CONNECT failed! endtoendssl=%d onlyconnect=%d\n", isHttps,
onlyConnect));
mTransaction->SetProxyConnectFailed();
}
}
// Don't use persistent connection for Upgrade unless there's an auth failure: // some proxies expect to see auth response on persistent connection. // Also allow persistent conn for h2, as we don't want to waste connections // for multiplexed upgrades. if (responseStatus != 401 && responseStatus != 407 && !mSpdySession) {
LOG(("HTTP Upgrade in play - disable keepalive for http/1.x\n"));
MarkAsDontReuse();
}
// the new Http2StreamWebSocket breaks wpt on // h2 basic authentication 401, due to MakeSticky() work around // so we DontReuse() in this circumstance if (mInSpdyTunnel && (responseStatus == 401 || responseStatus == 407)) {
MarkAsDontReuse(); return;
}
bool nsHttpConnection::IsReused() { if (mIsReused) returntrue; if (!mConsiderReusedAfterInterval) returnfalse;
// ReusedAfter allows a socket to be consider reused only after a certain // interval of time has passed return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
mConsiderReusedAfterInterval;
}
nsresult nsHttpConnection::TakeTransport(nsISocketTransport** aTransport,
nsIAsyncInputStream** aInputStream,
nsIAsyncOutputStream** aOutputStream) { if (mUsingSpdyVersion != SpdyVersion::NONE) return NS_ERROR_FAILURE; if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS; if (!(mSocketTransport && mSocketIn && mSocketOut)) { return NS_ERROR_NOT_INITIALIZED;
}
if (mInputOverflow) mSocketIn = mInputOverflow.forget();
// Change TCP Keepalive frequency to long-lived if currently short-lived. if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) { if (mTCPKeepaliveTransitionTimer) {
mTCPKeepaliveTransitionTimer->Cancel();
mTCPKeepaliveTransitionTimer = nullptr;
}
nsresult rv = StartLongLivedTCPKeepalives();
LOG(
("nsHttpConnection::TakeTransport [%p] calling " "StartLongLivedTCPKeepalives", this)); if (NS_FAILED(rv)) {
LOG(
("nsHttpConnection::TakeTransport [%p] " "StartLongLivedTCPKeepalives failed rv[0x%" PRIx32 "]", this, static_cast<uint32_t>(rv)));
}
}
if (mHasTLSTransportLayer) {
RefPtr<TLSTransportLayer> tlsTransportLayer =
do_QueryObject(mSocketTransport); if (tlsTransportLayer) { // This transport layer is no longer owned by this connection.
tlsTransportLayer->ReleaseOwner();
}
}
uint32_t nsHttpConnection::ReadTimeoutTick(PRIntervalTime now) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// make sure timer didn't tick before Activate() if (!mTransaction) return UINT32_MAX;
// Spdy implements some timeout handling using the SPDY ping frame. if (mSpdySession) { return mSpdySession->ReadTimeoutTick(now);
}
uint32_t nextTickAfter = UINT32_MAX; // Timeout if the response is taking too long to arrive. if (mResponseTimeoutEnabled) {
NS_WARNING_ASSERTION(
gHttpHandler->ResponseTimeoutEnabled(), "Timing out a response, but response timeout is disabled!");
PRIntervalTime initialResponseDelta = now - mLastWriteTime;
if (initialResponseDelta > mTransaction->ResponseTimeout()) {
LOG(("canceling transaction: no response for %ums: timeout is %dms\n",
PR_IntervalToMilliseconds(initialResponseDelta),
PR_IntervalToMilliseconds(mTransaction->ResponseTimeout())));
mResponseTimeoutEnabled = false;
SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT); // This will also close the connection
CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT); return UINT32_MAX;
}
nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
PR_IntervalToSeconds(initialResponseDelta);
nextTickAfter = std::max(nextTickAfter, 1U);
}
if (!mTlsHandshaker->NPNComplete()) { // We can reuse mLastWriteTime here, because it is set when the // connection is activated and only change when a transaction // succesfullu write to the socket and this can only happen after // the TLS handshake is done.
PRIntervalTime initialTLSDelta = now - mLastWriteTime; if (initialTLSDelta >
PR_MillisecondsToInterval(gHttpHandler->TLSHandshakeTimeout())) {
LOG(
("canceling transaction: tls handshake takes too long: tls handshake " "last %ums, timeout is %dms.",
PR_IntervalToMilliseconds(initialTLSDelta),
gHttpHandler->TLSHandshakeTimeout()));
// This will also close the connection
SetCloseReason(ConnectionCloseReason::TLS_TIMEOUT);
CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT); return UINT32_MAX;
}
}
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// the mLastReadTime timestamp is used for finding slowish readers // and can be pretty sensitive. For that reason we actually reset it // when we ask to read (resume recv()) so that when we get called back // with actual read data in OnSocketReadable() we are only measuring // the latency between those two acts and not all the processing that // may get done before the ResumeRecv() call
mLastReadTime = PR_IntervalNow();
if (mSocketIn) { if (mHasTLSTransportLayer) {
RefPtr<TLSTransportLayer> tlsTransportLayer =
do_QueryObject(mSocketTransport); if (tlsTransportLayer) { bool hasDataToRecv = tlsTransportLayer->HasDataToRecv(); if (hasDataToRecv && NS_SUCCEEDED(ForceRecv())) { return NS_OK;
}
Unused << mSocketIn->AsyncWait(this, 0, 0, nullptr); // We have to return an error here to let the underlying layer know this // connection doesn't read any data. return NS_BASE_STREAM_WOULD_BLOCK;
}
} return mSocketIn->AsyncWait(this, 0, 0, nullptr);
}
nsresult nsHttpConnection::MaybeForceSendIO() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); // due to bug 1213084 sometimes real I/O events do not get serviced when // NSPR derived I/O events are ready and this can cause a deadlock with // https over https proxying. Normally we would expect the write callback to // be invoked before this timer goes off, but set it at the old windows // tick interval (kForceDelay) as a backup for those circumstances. staticconst uint32_t kForceDelay = 17; // ms
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mCurrentBytesRead > mMaxBytesRead) mMaxBytesRead = mCurrentBytesRead;
// mask this error code because its not a real error. if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK;
if (mUsingSpdyVersion != SpdyVersion::NONE) {
DontReuse(); // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
mSpdySession->SetCleanShutdown(aIsShutdown);
mUsingSpdyVersion = SpdyVersion::NONE;
mSpdySession = nullptr;
}
if (mTransaction) {
LOG((" closing associated mTransaction"));
mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
if (NS_FAILED(reason) && (reason != NS_BINDING_RETARGETED)) {
Close(reason, aIsShutdown);
}
// flag the connection as reused here for convenience sake. certainly // it might be going away instead ;-)
mIsReused = true;
}
bool nsHttpConnection::CheckCanWrite0RTTData() {
MOZ_ASSERT(mTlsHandshaker->EarlyDataAvailable());
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
GetTLSSocketControl(getter_AddRefs(tlsSocketControl)); if (!tlsSocketControl) { returnfalse;
}
nsCOMPtr<nsITransportSecurityInfo> securityInfo; if (NS_FAILED(
tlsSocketControl->GetSecurityInfo(getter_AddRefs(securityInfo)))) { returnfalse;
} if (!securityInfo) { returnfalse;
}
nsAutoCString negotiatedNPN; // If the following code fails means that the handshake is not done // yet, so continue writing 0RTT data.
nsresult rv = securityInfo->GetNegotiatedNPN(negotiatedNPN); if (NS_FAILED(rv)) { returntrue;
} bool earlyDataAccepted = false;
rv = tlsSocketControl->GetEarlyDataAccepted(&earlyDataAccepted); // If 0RTT data is accepted we can continue writing data, // if it is reject stop writing more data. return NS_SUCCEEDED(rv) && earlyDataAccepted;
}
nsresult nsHttpConnection::OnReadSegment(constchar* buf, uint32_t count,
uint32_t* countRead) {
LOG(("nsHttpConnection::OnReadSegment [this=%p]\n", this)); if (count == 0) { // some ReadSegments implementations will erroneously call the writer // to consume 0 bytes worth of data. we must protect against this case // or else we'd end up closing the socket prematurely.
NS_ERROR("bad ReadSegments implementation"); return NS_ERROR_FAILURE; // stop iterating
}
// If we are waiting for 0RTT Response, check maybe nss has finished // handshake already. // IsAlive() calls drive the handshake and that may cause nss and necko // to be out of sync. if (mTlsHandshaker->EarlyDataAvailable() && !CheckCanWrite0RTTData()) {
MOZ_DIAGNOSTIC_ASSERT(mTlsHandshaker->TlsHandshakeComplitionPending());
LOG(
("nsHttpConnection::OnReadSegment Do not write any data, wait" " for EnsureNPNComplete to be called [this=%p]", this));
*countRead = 0; return NS_BASE_STREAM_WOULD_BLOCK;
}
nsresult rv;
uint32_t transactionBytes; bool again = true;
// Prevent STS thread from being blocked by single OnOutputStreamReady // callback. const uint32_t maxWriteAttempts = 128;
uint32_t writeAttempts = 0;
if (mTransactionCaps & NS_HTTP_CONNECT_ONLY) { if (!mConnInfo->UsingConnect()) { // A CONNECT has been requested for this connection but will never // be performed. This should never happen.
MOZ_ASSERT(false, "proxy connect will never happen");
LOG(("return failure because proxy connect will never happen\n")); return NS_ERROR_FAILURE;
}
if (mState == HttpConnectionState::REQUEST &&
mTlsHandshaker->EnsureNPNComplete()) { // Don't need to check this each write attempt since it is only // updated after OnSocketWritable completes. // We've already done primary tls (if needed) and sent our CONNECT. // If we're doing a CONNECT only request there's no need to write // the http transaction.
LOG(("return NS_BASE_STREAM_CLOSED to make transaction closed\n")); return NS_BASE_STREAM_CLOSED;
}
}
switch (mState) { case HttpConnectionState::SETTING_UP_TUNNEL: if (mConnInfo->UsingHttpsProxy() &&
!mTlsHandshaker->EnsureNPNComplete()) {
MOZ_DIAGNOSTIC_ASSERT(!mTlsHandshaker->EarlyDataAvailable());
mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
} else {
rv = SendConnectRequest(this, &transactionBytes);
} break; default: { // The SSL handshake must be completed before the // transaction->readsegments() processing can proceed because we need to // know how to format the request differently for http/1, http/2, spdy, // etc.. and that is negotiated with NPN/ALPN in the SSL handshake. if (!mTlsHandshaker->EnsureNPNComplete() &&
(!mTlsHandshaker->EarlyDataUsed() ||
mTlsHandshaker->TlsHandshakeComplitionPending())) { // The handshake is not done and we cannot write 0RTT data or nss has // already finished 0RTT data.
mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
} elseif (!mTransaction) {
rv = NS_ERROR_FAILURE;
LOG((" No Transaction In OnSocketWritable\n"));
} elseif (NS_SUCCEEDED(rv)) { // for non spdy sessions let the connection manager know if (!mReportedSpdy && mTlsHandshaker->NPNComplete()) {
mReportedSpdy = true;
MOZ_ASSERT(!mEverUsedSpdy);
gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false, false);
}
LOG((" writing transaction request stream\n"));
rv = mTransaction->ReadSegmentsAgain(this,
nsIOService::gDefaultSegmentSize,
&transactionBytes, &again); if (mTlsHandshaker->EarlyDataUsed()) {
mContentBytesWritten0RTT += transactionBytes; if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { // If an error happens while writting 0RTT data, restart // the transactiions without 0RTT.
mTlsHandshaker->FinishNPNSetup(false, true);
}
} else {
mContentBytesWritten += transactionBytes;
}
}
}
}
// XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
rv = NS_OK;
transactionBytes = 0;
}
if (NS_FAILED(rv)) { // if the transaction didn't want to write any more data, then // wait for the transaction to call ResumeSend. if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
rv = NS_OK;
}
again = false;
} elseif (NS_FAILED(mSocketOutCondition)) { if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) { if (!mTlsHandshaker->EarlyDataCanNotBeUsed()) { // continue writing // We are not going to poll for write if the handshake is in progress, // but early data cannot be used.
rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
}
} else {
rv = mSocketOutCondition;
}
again = false;
} elseif (!transactionBytes) {
rv = NS_OK;
if (mTransaction) { // in case the ReadSegments stack called // CloseTransaction() // // at this point we've written out the entire transaction, and now we // must wait for the server's response. we manufacture a status message // here to reflect the fact that we are waiting. this message will be // trumped (overwritten) if the server responds quickly. //
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_WAITING_FOR, 0);
rv = ResumeRecv(); // start reading
} // When Spdy tunnel is used we need to explicitly set when a request is // done. if ((mState != HttpConnectionState::SETTING_UP_TUNNEL) && !mSpdySession) {
nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); // needed for websocket over h2 (direct) if (!trans || !trans->IsWebsocketUpgrade()) {
mRequestDone = true;
}
}
again = false;
} elseif (writeAttempts >= maxWriteAttempts) {
LOG((" yield for other transactions\n"));
rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
again = false;
} // write more to the socket until error or end-of-request...
} while (again && gHttpHandler->Active());
return rv;
}
nsresult nsHttpConnection::OnWriteSegment(char* buf, uint32_t count,
uint32_t* countWritten) { if (count == 0) { // some WriteSegments implementations will erroneously call the reader // to provide 0 bytes worth of data. we must protect against this case // or else we'd end up closing the socket prematurely.
NS_ERROR("bad WriteSegments implementation"); return NS_ERROR_FAILURE; // stop iterating
}
if ((mTransactionCaps & NS_HTTP_CONNECT_ONLY) && !mConnInfo->UsingConnect()) { // A CONNECT has been requested for this connection but will never // be performed. This should never happen.
MOZ_ASSERT(false, "proxy connect will never happen");
LOG(("return failure because proxy connect will never happen\n")); return NS_ERROR_FAILURE;
}
if (mKeepAliveMask && (delta >= mMaxHangTime)) {
LOG(("max hang time exceeded!\n")); // give the handler a chance to create a new persistent connection to // this host if we've been busy for too long.
mKeepAliveMask = false;
Unused << gHttpHandler->ProcessPendingQ(mConnInfo);
}
// Reduce the estimate of the time since last read by up to 1 RTT to // accommodate exhausted sender TCP congestion windows or minor I/O delays.
mLastReadTime = now;
nsresult rv = NS_OK;
uint32_t n; bool again = true;
do { if (!TunnelSetupInProgress() && !mTlsHandshaker->EnsureNPNComplete()) { // Unless we are setting up a tunnel via CONNECT, prevent reading // from the socket until the results of NPN // negotiation are known (which is determined from the write path). // If the server speaks SPDY it is likely the readable data here is // a spdy settings frame and without NPN it would be misinterpreted // as HTTP/*
LOG(
("nsHttpConnection::OnSocketReadable %p return due to inactive " "tunnel setup but incomplete NPN state\n", this)); if (mTlsHandshaker->EarlyDataAvailable() || mHasTLSTransportLayer) {
rv = ResumeRecv();
} break;
}
mSocketInCondition = NS_OK; if (!mTransaction) {
rv = NS_ERROR_FAILURE;
LOG((" No Transaction In OnSocketWritable\n"));
} else {
rv = mTransaction->WriteSegmentsAgain( this, nsIOService::gDefaultSegmentSize, &n, &again);
}
LOG(("nsHttpConnection::OnSocketReadable %p trans->ws rv=%" PRIx32 " n=%d socketin=%" PRIx32 "\n", this, static_cast<uint32_t>(rv), n, static_cast<uint32_t>(mSocketInCondition))); if (NS_FAILED(rv)) { // if the transaction didn't want to take any more data, then // wait for the transaction to call ResumeRecv. if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
rv = NS_OK;
}
again = false;
} else {
mCurrentBytesRead += n;
mTotalBytesRead += n; if (NS_FAILED(mSocketInCondition)) { // continue waiting for the socket if necessary... if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) {
rv = ResumeRecv();
} else {
rv = mSocketInCondition;
}
again = false;
}
} // read more from the socket until error...
} while (again && gHttpHandler->Active());
nsHttpConnectionInfo* ci = nullptr; if (mTransaction) {
ci = mTransaction->ConnectionInfo();
} if (!ci) {
ci = mConnInfo;
}
MOZ_ASSERT(ci);
--> --------------------
--> maximum size reached
--> --------------------
¤ 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.0.39Bemerkung:
¤
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.