/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ #ifndef nsHttpTransaction_h__ #define nsHttpTransaction_h__ #include "nsHttp.h" #include "nsAHttpTransaction.h" #include "HttpTransactionShell.h" #include "nsAHttpConnection.h" #include "EventTokenBucket.h" #include "nsCOMPtr.h" #include "nsIAsyncOutputStream.h" #include "nsThreadUtils.h" #include "nsIInterfaceRequestor.h" #include "nsIAsyncOutputStream.h" #include "nsITimer.h" #include "TimingStruct.h" #include "Http2Push.h" #include "mozilla/net/DNS.h" #include "mozilla/net/NeckoChannelParams.h" #include "ARefBase.h" //----------------------------------------------------------------------------- class nsIHttpActivityObserver; class nsIDNSHTTPSSVCRecord; class nsIEventTarget; class nsIInputStream; class nsIOutputStream; class nsIRequestContext; class nsISVCBRecord; namespace mozilla { namespace net { class HTTPSRecordResolver; class nsHttpChunkedDecoder; class nsHttpHeaderArray; class nsHttpRequestHead; class nsHttpResponseHead; class NullHttpTransaction; class SpdyConnectTransaction; //----------------------------------------------------------------------------- // nsHttpTransaction represents a single HTTP transaction. It is thread-safe, // intended to run on the socket thread. //----------------------------------------------------------------------------- class nsHttpTransaction final : public nsAHttpTransaction, public HttpTransactionShell, public ATokenBucketEvent, public nsIInputStreamCallback, public nsIOutputStreamCallback, public ARefBase, public nsITimerCallback { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSAHTTPTRANSACTION NS_DECL_HTTPTRANSACTIONSHELL NS_DECL_NSIINPUTSTREAMCALLBACK NS_DECL_NSIOUTPUTSTREAMCALLBACK NS_DECL_NSITIMERCALLBACK nsHttpTransaction(); void OnActivated() override; // attributes nsHttpResponseHead* ResponseHead() { return mHaveAllHeaders ? mResponseHead : nullptr; } nsIEventTarget* ConsumerTarget() { return mConsumerTarget; } // Called to set/find out if the transaction generated a complete response. void SetResponseIsComplete() { mResponseIsComplete = true; } void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; } void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; } void MakeNonSticky() override { mCaps &= ~NS_HTTP_STICKY_CONNECTION; } void MakeDontWaitHTTPSSVC() { mCaps &= ~NS_HTTP_WAIT_HTTPSSVC_RESULT; } // SetPriority() may only be used by the connection manager. void SetPriority(int32_t priority) { mPriority = priority; } int32_t Priority() { return mPriority; } void PrintDiagnostics(nsCString& log); // Sets mPendingTime to the current time stamp or to a null time stamp (if now // is false) void SetPendingTime(bool now = true) { if (!now && !mPendingTime.IsNull()) { // Remember how long it took. We will use this vaule to record // TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3 telemetry, but we need to wait // for the response headers. mPendingDurationTime = TimeStamp::Now() - mPendingTime; } mPendingTime = now ? TimeStamp::Now() : TimeStamp(); } const TimeStamp GetPendingTime() { return mPendingTime; } // overload of nsAHttpTransaction::RequestContext() nsIRequestContext* RequestContext() override { return mRequestContext.get(); } void DispatchedAsBlocking(); void RemoveDispatchedAsBlocking(); void DisableSpdy() override; void DisableHttp3() override; nsHttpTransaction* QueryHttpTransaction() override { return this; } already_AddRefed GetPushedStream() { return do_AddRef(mPushedStream); } already_AddRefed TakePushedStream() { return mPushedStream.forget(); } uint32_t InitialRwin() const { return mInitialRwin; }; bool ChannelPipeFull() { return mWaitingOnPipeOut; } // Locked methods to get and set timing info void BootstrapTimings(TimingStruct times); void SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); void SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); void SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); void SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); void SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); [[nodiscard]] bool Do0RTT() override; [[nodiscard]] nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override; // After Finish0RTT early data may have failed but the caller did not request // restart - this indicates that state for dev tools void Refused0RTT(); [[nodiscard]] bool CanDo0RTT() override; [[nodiscard]] nsresult RestartOnFastOpenError() override; uint64_t TopLevelOuterContentWindowId() override { return mTopLevelOuterContentWindowId; } void SetFastOpenStatus(uint8_t aStatus) override; void SetHttpTrailers(nsCString& aTrailers); bool IsWebsocketUpgrade(); void SetH2WSTransaction(SpdyConnectTransaction*); void OnProxyConnectComplete(int32_t aResponseCode) override; void SetFlat407Headers(const nsACString& aHeaders); // This is only called by Http2PushedStream::TryOnPush when a new pushed // stream is available. The newly added stream will be taken by another // transaction. void OnPush(Http2PushedStreamWrapper* aStream); void UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo); void SetClassOfService(uint32_t cos); virtual nsresult OnHTTPSRRAvailable( nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord, nsISVCBRecord* aHighestPriorityRecord) override; private: friend class DeleteHttpTransaction; virtual ~nsHttpTransaction(); [[nodiscard]] nsresult Restart(); char* LocateHttpStart(char* buf, uint32_t len, bool aAllowPartialMatch); [[nodiscard]] nsresult ParseLine(nsACString& line); [[nodiscard]] nsresult ParseLineSegment(char* seg, uint32_t len); [[nodiscard]] nsresult ParseHead(char*, uint32_t count, uint32_t* countRead); [[nodiscard]] nsresult HandleContentStart(); [[nodiscard]] nsresult HandleContent(char*, uint32_t count, uint32_t* contentRead, uint32_t* contentRemaining); [[nodiscard]] nsresult ProcessData(char*, uint32_t, uint32_t*); void DeleteSelfOnConsumerThread(); void ReleaseBlockingTransaction(); [[nodiscard]] static nsresult ReadRequestSegment(nsIInputStream*, void*, const char*, uint32_t, uint32_t, uint32_t*); [[nodiscard]] static nsresult WritePipeSegment(nsIOutputStream*, void*, char*, uint32_t, uint32_t, uint32_t*); bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; } bool ResponseTimeoutEnabled() const final; void ReuseConnectionOnRestartOK(bool reuseOk) override { mReuseOnRestart = reuseOk; } // Called right after we parsed the response head. Checks for connection // based authentication schemes in reponse headers for WWW and Proxy // authentication. If such is found in any of them, NS_HTTP_STICKY_CONNECTION // is set in mCaps. We need the sticky flag be set early to keep the // connection from very start of the authentication process. void CheckForStickyAuthScheme(); void CheckForStickyAuthSchemeAt(nsHttpAtom const& header); bool IsStickyAuthSchemeAt(nsACString const& auth); // Called from WriteSegments. Checks for conditions whether to throttle // reading the content. When this returns true, WriteSegments returns // WOULD_BLOCK. bool ShouldThrottle(); void NotifyTransactionObserver(nsresult reason); // When echConfig is enabled, this function put other available records // in mRecordsForRetry. Returns true when mRecordsForRetry is not empty, // otherwise returns false. bool PrepareSVCBRecordsForRetry(const nsACString& aFailedDomainName, bool& aAllRecordsHaveEchConfig); // This function setups a new connection info for restarting this transaction. void PrepareConnInfoForRetry(nsresult aReason); // This function is used to select the next non http3 record and is only // executed when the fast fallback timer is triggered. already_AddRefed PrepareFastFallbackConnInfo( bool aEchConfigUsed); void MaybeReportFailedSVCDomain(nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo); already_AddRefed TakePushedStreamById( uint32_t aStreamId); // IMPORTANT: when adding new values, always add them to the end, otherwise // it will mess up telemetry. enum HTTPSSVC_CONNECTION_FAILED_REASON : uint32_t { HTTPSSVC_CONNECTION_OK = 0, HTTPSSVC_CONNECTION_UNKNOWN_HOST = 1, HTTPSSVC_CONNECTION_UNREACHABLE = 2, HTTPSSVC_CONNECTION_421_RECEIVED = 3, HTTPSSVC_CONNECTION_SECURITY_ERROR = 4, HTTPSSVC_CONNECTION_NO_USABLE_RECORD = 5, HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED = 6, HTTPSSVC_CONNECTION_OTHERS = 7, }; HTTPSSVC_CONNECTION_FAILED_REASON ErrorCodeToFailedReason( nsresult aErrorCode); void OnHttp3BackupTimer(); void OnBackupConnectionReady(); void OnFastFallbackTimer(); void HandleFallback(nsHttpConnectionInfo* aFallbackConnInfo); void MaybeCancelFallbackTimer(); private: class UpdateSecurityCallbacks : public Runnable { public: UpdateSecurityCallbacks(nsHttpTransaction* aTrans, nsIInterfaceRequestor* aCallbacks) : Runnable("net::nsHttpTransaction::UpdateSecurityCallbacks"), mTrans(aTrans), mCallbacks(aCallbacks) {} NS_IMETHOD Run() override { if (mTrans->mConnection) mTrans->mConnection->SetSecurityCallbacks(mCallbacks); return NS_OK; } private: RefPtr mTrans; nsCOMPtr mCallbacks; }; Mutex mLock; nsCOMPtr mCallbacks; nsCOMPtr mTransportSink; nsCOMPtr mConsumerTarget; nsCOMPtr mSecurityInfo; nsCOMPtr mPipeIn; nsCOMPtr mPipeOut; nsCOMPtr mRequestContext; uint64_t mChannelId; nsCOMPtr mActivityDistributor; nsCString mReqHeaderBuf; // flattened request headers nsCOMPtr mRequestStream; int64_t mRequestSize; RefPtr mConnection; RefPtr mConnInfo; // This is only set in UpdateConnectionInfo() when we have received a SVCB RR. // When echConfig is not used and the connection is failed, this transaction // will be restarted with this origin connection info directly. // When echConfig is enabled, there are two cases below. // 1. If all records have echConfig, we will retry other records except the // failed one. In the case all other records with echConfig are failed and the // pref network.dns.echconfig.fallback_to_origin_when_all_failed is true, this // origin connection info will be used. // 2. If only some records have echConfig and some not, we always fallback to // this origin conn info. RefPtr mOrigConnInfo; nsHttpRequestHead* mRequestHead; // weak ref nsHttpResponseHead* mResponseHead; // owning pointer nsAHttpSegmentReader* mReader; nsAHttpSegmentWriter* mWriter; nsCString mLineBuf; // may contain a partial line int64_t mContentLength; // equals -1 if unknown int64_t mContentRead; // count of consumed content bytes Atomic mTransferSize; // count of received bytes // After a 304/204 or other "no-content" style response we will skip over // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next // response header to deal with servers that actually sent a response // body where they should not have. This member tracks how many bytes have // so far been skipped. uint32_t mInvalidResponseBytesRead; RefPtr mPushedStream; uint32_t mInitialRwin; nsHttpChunkedDecoder* mChunkedDecoder; TimingStruct mTimings; nsresult mStatus; int16_t mPriority; uint16_t mRestartCount; // the number of times this transaction has been restarted uint32_t mCaps; HttpVersion mHttpVersion; uint16_t mHttpResponseCode; nsCString mFlat407Headers; uint32_t mCurrentHttpResponseHeaderSize; int32_t const THROTTLE_NO_LIMIT = -1; // This can have 3 possible values: // * THROTTLE_NO_LIMIT - this means the transaction is not in any way limited // to read the response, this is the default // * a positive number - a limit is set because the transaction is obligated // to throttle the response read, this is decresed with // every piece of data the transaction receives // * zero - when the transaction depletes the limit for reading, this makes it // stop reading and return WOULD_BLOCK from WriteSegments; // transaction then waits for a call of ResumeReading that resets // this member back to THROTTLE_NO_LIMIT int32_t mThrottlingReadAllowance; // mCapsToClear holds flags that should be cleared in mCaps, e.g. unset // NS_HTTP_REFRESH_DNS when DNS refresh request has completed to avoid // redundant requests on the network. The member itself is atomic, but // access to it from the networking thread may happen either before or // after the main thread modifies it. To deal with raciness, only unsetting // bitfields should be allowed: 'lost races' will thus err on the // conservative side, e.g. by going ahead with a 2nd DNS refresh. Atomic mCapsToClear; Atomic mResponseIsComplete; Atomic mClosed; // True iff WriteSegments was called while this transaction should be // throttled (stop reading) Used to resume read on unblock of reading. Conn // manager is responsible for calling back to resume reading. bool mReadingStopped; // state flags, all logically boolean, but not packed together into a // bitfield so as to avoid bitfield-induced races. See bug 560579. bool mConnected; bool mActivated; bool mHaveStatusLine; bool mHaveAllHeaders; bool mTransactionDone; bool mDidContentStart; bool mNoContent; // expecting an empty entity body bool mSentData; bool mReceivedData; bool mStatusEventPending; bool mHasRequestBody; bool mProxyConnectFailed; bool mHttpResponseMatched; bool mPreserveStream; bool mDispatchedAsBlocking; bool mResponseTimeoutEnabled; bool mForceRestart; bool mReuseOnRestart; bool mContentDecoding; bool mContentDecodingCheck; bool mDeferredSendProgress; bool mWaitingOnPipeOut; bool mIsHttp3Used = false; bool mDoNotRemoveAltSvc; // mClosed := transaction has been explicitly closed // mTransactionDone := transaction ran to completion or was interrupted // mResponseComplete := transaction ran to completion // For Restart-In-Progress Functionality bool mReportedStart; bool mReportedResponseHeader; // protected by nsHttp::GetLock() bool mResponseHeadTaken; UniquePtr mForTakeResponseTrailers; bool mResponseTrailersTaken; // Set when this transaction was restarted by call to Restart(). Used to tell // the http channel to reset proxy authentication. Atomic mRestarted; // The time when the transaction was submitted to the Connection Manager TimeStamp mPendingTime; TimeDuration mPendingDurationTime; uint64_t mTopLevelOuterContentWindowId; // For Rate Pacing via an EventTokenBucket public: // called by the connection manager to run this transaction through the // token bucket. If the token bucket admits the transaction immediately it // returns true. The function is called repeatedly until it returns true. bool TryToRunPacedRequest(); // ATokenBucketEvent pure virtual implementation. Called by the token bucket // when the transaction is ready to run. If this happens asynchrounously to // token bucket submission the transaction just posts an event that causes // the pending transaction queue to be rerun (and TryToRunPacedRequest() to // be run again. void OnTokenBucketAdmitted() override; // ATokenBucketEvent // CancelPacing() can be used to tell the token bucket to remove this // transaction from the list of pending transactions. This is used when a // transaction is believed to be HTTP/1 (and thus subject to rate pacing) // but later can be dispatched via spdy (not subject to rate pacing). void CancelPacing(nsresult reason); // Called by the connetion manager on the socket thread when reading for this // previously throttled transaction has to be resumed. void ResumeReading(); // This examins classification of this transaction whether the Throttleable // class has been set while Leader, Unblocked, DontThrottle has not. bool EligibleForThrottling() const; private: bool mSubmittedRatePacing; bool mPassedRatePacing; bool mSynchronousRatePaceRequest; nsCOMPtr mTokenBucketCancel; public: uint32_t ClassOfService() { return mClassOfService; } private: Atomic mClassOfService; public: // setting TunnelProvider to non-null means the transaction should only // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a // generic wild card one). That means in the specific case of carrying this // transaction on an HTTP/2 tunnel it will only be dispatched onto an // existing tunnel instead of triggering creation of a new one. // The tunnel provider is used for ASpdySession::MaybeReTunnel() checks. void SetTunnelProvider(ASpdySession* provider) { mTunnelProvider = provider; } ASpdySession* TunnelProvider() { return mTunnelProvider; } nsIInterfaceRequestor* SecurityCallbacks() { return mCallbacks; } // Called when this transaction is inserted in the pending queue. void OnPendingQueueInserted(); private: RefPtr mTunnelProvider; TransactionObserverFunc mTransactionObserver; NetAddr mSelfAddr; NetAddr mPeerAddr; bool mResolvedByTRR; bool mEchConfigUsed = false; bool m0RTTInProgress; bool mDoNotTryEarlyData; enum { EARLY_NONE, EARLY_SENT, EARLY_ACCEPTED, EARLY_425 } mEarlyDataDisposition; uint8_t mFastOpenStatus; // H2 websocket support RefPtr mH2WSTransaction; HttpTrafficCategory mTrafficCategory; bool mThroughCaptivePortal; Atomic mProxyConnectResponseCode; OnPushCallback mOnPushCallback; nsDataHashtable> mIDToStreamMap; nsCOMPtr mDNSRequest; Atomic mHTTPSSVCReceivedStage; bool m421Received = false; nsCOMPtr mHTTPSSVCRecord; nsTArray> mRecordsForRetry; bool mDontRetryWithDirectRoute = false; bool mFastFallbackTriggered = false; bool mAllRecordsInH3ExcludedListBefore = false; bool mHttp3BackupTimerCreated = false; nsCOMPtr mFastFallbackTimer; nsCOMPtr mHttp3BackupTimer; RefPtr mBackupConnInfo; RefPtr mResolver; // IMPORTANT: when adding new values, always add them to the end, otherwise // it will mess up telemetry. enum TRANSACTION_RESTART_REASON : uint32_t { TRANSACTION_RESTART_NONE = 0, // The transacion was not restarted. TRANSACTION_RESTART_FORCED = 1, // The transaction was forced to restart. TRANSACTION_RESTART_HTTPSSVC_INVOLVED = 2, TRANSACTION_RESTART_NO_DATA_SENT = 3, TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA = 4, TRANSACTION_RESTART_OTHERS = 5, }; nsDataHashtable mEchRetryCounterMap; bool mSupportsHTTP3 = false; }; } // namespace net } // namespace mozilla #endif // nsHttpTransaction_h__