The unmasked frame payload (owned copy from WebSocketDecoder).
+
The unmasked frame payload (owned copy from WebSocketDecoder).
+When Titanium.Web.Proxy.WebSocketFrame.RelayRaw is set, this is a slice of the original wire bytes to forward
+unchanged, not a decoded payload.
Declaration
@@ -223,7 +225,7 @@
Methods
Edit this page
- View Source
+ View Source
GetText()
@@ -253,7 +255,7 @@ Returns
Edit this page
- View Source
+ View Source
GetText(Encoding)
diff --git a/docs/api/toc.html b/docs/api/toc.html
index 1f47c9fb2..0abf3c616 100644
--- a/docs/api/toc.html
+++ b/docs/api/toc.html
@@ -162,6 +162,9 @@
WebSocketFrameDirection
+
+ WebSocketFrameElidedEventArgs
+
WebSocketFrameInterceptAction
diff --git a/docs/api/toc.json b/docs/api/toc.json
index 4deac3aaa..d50245d4b 100644
--- a/docs/api/toc.json
+++ b/docs/api/toc.json
@@ -1,2 +1,2 @@
-{"items":[{"name":"Titanium.Web.Proxy","href":"Titanium.Web.Proxy.html","topicHref":"Titanium.Web.Proxy.html","topicUid":"Titanium.Web.Proxy","type":"Namespace","items":[{"name":"ClientProcessId","href":"Titanium.Web.Proxy.ClientProcessId.html","topicHref":"Titanium.Web.Proxy.ClientProcessId.html","topicUid":"Titanium.Web.Proxy.ClientProcessId","type":"Class"},{"name":"MitmExclusionDefaults","href":"Titanium.Web.Proxy.MitmExclusionDefaults.html","topicHref":"Titanium.Web.Proxy.MitmExclusionDefaults.html","topicUid":"Titanium.Web.Proxy.MitmExclusionDefaults","type":"Class"},{"name":"MitmExclusionMode","href":"Titanium.Web.Proxy.MitmExclusionMode.html","topicHref":"Titanium.Web.Proxy.MitmExclusionMode.html","topicUid":"Titanium.Web.Proxy.MitmExclusionMode","type":"Enum"},{"name":"ProxyLimits","href":"Titanium.Web.Proxy.ProxyLimits.html","topicHref":"Titanium.Web.Proxy.ProxyLimits.html","topicUid":"Titanium.Web.Proxy.ProxyLimits","type":"Class"},{"name":"ProxyServer","href":"Titanium.Web.Proxy.ProxyServer.html","topicHref":"Titanium.Web.Proxy.ProxyServer.html","topicUid":"Titanium.Web.Proxy.ProxyServer","type":"Class"},{"name":"SystemProxyBypassRuleMode","href":"Titanium.Web.Proxy.SystemProxyBypassRuleMode.html","topicHref":"Titanium.Web.Proxy.SystemProxyBypassRuleMode.html","topicUid":"Titanium.Web.Proxy.SystemProxyBypassRuleMode","type":"Enum"},{"name":"SystemProxyChangeResult","href":"Titanium.Web.Proxy.SystemProxyChangeResult.html","topicHref":"Titanium.Web.Proxy.SystemProxyChangeResult.html","topicUid":"Titanium.Web.Proxy.SystemProxyChangeResult","type":"Struct"},{"name":"SystemProxyLoopbackPlacement","href":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement.html","topicHref":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement.html","topicUid":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement","type":"Enum"},{"name":"SystemProxySettings","href":"Titanium.Web.Proxy.SystemProxySettings.html","topicHref":"Titanium.Web.Proxy.SystemProxySettings.html","topicUid":"Titanium.Web.Proxy.SystemProxySettings","type":"Class"},{"name":"WebSocketDecoder","href":"Titanium.Web.Proxy.WebSocketDecoder.html","topicHref":"Titanium.Web.Proxy.WebSocketDecoder.html","topicUid":"Titanium.Web.Proxy.WebSocketDecoder","type":"Class"},{"name":"WebSocketFrame","href":"Titanium.Web.Proxy.WebSocketFrame.html","topicHref":"Titanium.Web.Proxy.WebSocketFrame.html","topicUid":"Titanium.Web.Proxy.WebSocketFrame","type":"Class"},{"name":"WebSocketFrameEncoder","href":"Titanium.Web.Proxy.WebSocketFrameEncoder.html","topicHref":"Titanium.Web.Proxy.WebSocketFrameEncoder.html","topicUid":"Titanium.Web.Proxy.WebSocketFrameEncoder","type":"Class"},{"name":"WebSocketFrameWriter","href":"Titanium.Web.Proxy.WebSocketFrameWriter.html","topicHref":"Titanium.Web.Proxy.WebSocketFrameWriter.html","topicUid":"Titanium.Web.Proxy.WebSocketFrameWriter","type":"Class"},{"name":"WebSocketProtocolException","href":"Titanium.Web.Proxy.WebSocketProtocolException.html","topicHref":"Titanium.Web.Proxy.WebSocketProtocolException.html","topicUid":"Titanium.Web.Proxy.WebSocketProtocolException","type":"Class"},{"name":"WebsocketOpCode","href":"Titanium.Web.Proxy.WebsocketOpCode.html","topicHref":"Titanium.Web.Proxy.WebsocketOpCode.html","topicUid":"Titanium.Web.Proxy.WebsocketOpCode","type":"Enum"}]},{"name":"Titanium.Web.Proxy.Caching","href":"Titanium.Web.Proxy.Caching.html","topicHref":"Titanium.Web.Proxy.Caching.html","topicUid":"Titanium.Web.Proxy.Caching","type":"Namespace","items":[{"name":"HttpResponseCacheMiddleware","href":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware.html","topicHref":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware.html","topicUid":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware","type":"Class"},{"name":"MemoryHttpResponseCache","href":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache.html","topicHref":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache.html","topicUid":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache","type":"Class"}]},{"name":"Titanium.Web.Proxy.Clusters","href":"Titanium.Web.Proxy.Clusters.html","topicHref":"Titanium.Web.Proxy.Clusters.html","topicUid":"Titanium.Web.Proxy.Clusters","type":"Namespace","items":[{"name":"ClusterManager","href":"Titanium.Web.Proxy.Clusters.ClusterManager.html","topicHref":"Titanium.Web.Proxy.Clusters.ClusterManager.html","topicUid":"Titanium.Web.Proxy.Clusters.ClusterManager","type":"Class"},{"name":"DestinationHealthTracker","href":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker.html","topicHref":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker.html","topicUid":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker","type":"Class"},{"name":"LoadBalancer","href":"Titanium.Web.Proxy.Clusters.LoadBalancer.html","topicHref":"Titanium.Web.Proxy.Clusters.LoadBalancer.html","topicUid":"Titanium.Web.Proxy.Clusters.LoadBalancer","type":"Class"}]},{"name":"Titanium.Web.Proxy.Diagnostics","href":"Titanium.Web.Proxy.Diagnostics.html","topicHref":"Titanium.Web.Proxy.Diagnostics.html","topicUid":"Titanium.Web.Proxy.Diagnostics","type":"Namespace","items":[{"name":"ClientTlsTiming","href":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming","type":"Class"},{"name":"HttpRequestTiming","href":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming","type":"Class"},{"name":"TunnelConnectTiming","href":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming","type":"Class"},{"name":"UpstreamConnectionTiming","href":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming","type":"Class"}]},{"name":"Titanium.Web.Proxy.EventArguments","href":"Titanium.Web.Proxy.EventArguments.html","topicHref":"Titanium.Web.Proxy.EventArguments.html","topicUid":"Titanium.Web.Proxy.EventArguments","type":"Namespace","items":[{"name":"AsyncEventHandler
","href":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html","topicHref":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html","topicUid":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler`1","type":"Delegate"},{"name":"BeforeBodyWriteEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs","type":"Class"},{"name":"BeforeHttpAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs","type":"Class"},{"name":"BeforeQuicAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs","type":"Class"},{"name":"BeforeSslAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs","type":"Class"},{"name":"CertificateSelectionEventArgs","href":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs","type":"Class"},{"name":"CertificateValidationEventArgs","href":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs","type":"Class"},{"name":"MultipartRequestPartSentEventArgs","href":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs","type":"Class"},{"name":"ProxyEventArgsBase","href":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html","topicHref":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html","topicUid":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase","type":"Class"},{"name":"SessionEventArgs","href":"Titanium.Web.Proxy.EventArguments.SessionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.SessionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.SessionEventArgs","type":"Class"},{"name":"SessionEventArgsBase","href":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html","topicHref":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html","topicUid":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase","type":"Class"},{"name":"SocksAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs","type":"Class"},{"name":"TunnelConnectFailureEventArgs","href":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs","type":"Class"},{"name":"TunnelConnectSessionEventArgs","href":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs","type":"Class"},{"name":"WebSocketFrameDirection","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection","type":"Enum"},{"name":"WebSocketFrameInterceptAction","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction","type":"Enum"},{"name":"WebSocketFrameInterceptEventArgs","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs","type":"Class"}]},{"name":"Titanium.Web.Proxy.Exceptions","href":"Titanium.Web.Proxy.Exceptions.html","topicHref":"Titanium.Web.Proxy.Exceptions.html","topicUid":"Titanium.Web.Proxy.Exceptions","type":"Namespace","items":[{"name":"BodyNotFoundException","href":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html","topicHref":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html","topicUid":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException","type":"Class"},{"name":"OutboundDestinationBlockedException","href":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException.html","topicHref":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException.html","topicUid":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException","type":"Class"},{"name":"ProxyAuthorizationException","href":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException","type":"Class"},{"name":"ProxyConnectException","href":"Titanium.Web.Proxy.Exceptions.ProxyConnectException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyConnectException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyConnectException","type":"Class"},{"name":"ProxyException","href":"Titanium.Web.Proxy.Exceptions.ProxyException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyException","type":"Class"},{"name":"ProxyHttpException","href":"Titanium.Web.Proxy.Exceptions.ProxyHttpException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyHttpException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyHttpException","type":"Class"},{"name":"ProxyTimeoutException","href":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException","type":"Class"},{"name":"ProxyTimeoutKind","href":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind","type":"Enum"},{"name":"UpstreamProxyConnectException","href":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException.html","topicHref":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException.html","topicUid":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException","type":"Class"}]},{"name":"Titanium.Web.Proxy.Helpers","href":"Titanium.Web.Proxy.Helpers.html","topicHref":"Titanium.Web.Proxy.Helpers.html","topicUid":"Titanium.Web.Proxy.Helpers","type":"Namespace","items":[{"name":"UnixProxyBypassMapper","href":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper.html","topicHref":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper.html","topicUid":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper","type":"Class"}]},{"name":"Titanium.Web.Proxy.Http","href":"Titanium.Web.Proxy.Http.html","topicHref":"Titanium.Web.Proxy.Http.html","topicUid":"Titanium.Web.Proxy.Http","type":"Namespace","items":[{"name":"ConnectRequest","href":"Titanium.Web.Proxy.Http.ConnectRequest.html","topicHref":"Titanium.Web.Proxy.Http.ConnectRequest.html","topicUid":"Titanium.Web.Proxy.Http.ConnectRequest","type":"Class"},{"name":"ConnectResponse","href":"Titanium.Web.Proxy.Http.ConnectResponse.html","topicHref":"Titanium.Web.Proxy.Http.ConnectResponse.html","topicUid":"Titanium.Web.Proxy.Http.ConnectResponse","type":"Class"},{"name":"HeaderCollection","href":"Titanium.Web.Proxy.Http.HeaderCollection.html","topicHref":"Titanium.Web.Proxy.Http.HeaderCollection.html","topicUid":"Titanium.Web.Proxy.Http.HeaderCollection","type":"Class"},{"name":"HeaderCollection.Enumerator","href":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator.html","topicHref":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator.html","topicUid":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator","type":"Struct"},{"name":"HttpWebClient","href":"Titanium.Web.Proxy.Http.HttpWebClient.html","topicHref":"Titanium.Web.Proxy.Http.HttpWebClient.html","topicUid":"Titanium.Web.Proxy.Http.HttpWebClient","type":"Class"},{"name":"KnownHeader","href":"Titanium.Web.Proxy.Http.KnownHeader.html","topicHref":"Titanium.Web.Proxy.Http.KnownHeader.html","topicUid":"Titanium.Web.Proxy.Http.KnownHeader","type":"Class"},{"name":"KnownHeaders","href":"Titanium.Web.Proxy.Http.KnownHeaders.html","topicHref":"Titanium.Web.Proxy.Http.KnownHeaders.html","topicUid":"Titanium.Web.Proxy.Http.KnownHeaders","type":"Class"},{"name":"ProxyResults","href":"Titanium.Web.Proxy.Http.ProxyResults.html","topicHref":"Titanium.Web.Proxy.Http.ProxyResults.html","topicUid":"Titanium.Web.Proxy.Http.ProxyResults","type":"Class"},{"name":"Request","href":"Titanium.Web.Proxy.Http.Request.html","topicHref":"Titanium.Web.Proxy.Http.Request.html","topicUid":"Titanium.Web.Proxy.Http.Request","type":"Class"},{"name":"RequestResponseBase","href":"Titanium.Web.Proxy.Http.RequestResponseBase.html","topicHref":"Titanium.Web.Proxy.Http.RequestResponseBase.html","topicUid":"Titanium.Web.Proxy.Http.RequestResponseBase","type":"Class"},{"name":"Response","href":"Titanium.Web.Proxy.Http.Response.html","topicHref":"Titanium.Web.Proxy.Http.Response.html","topicUid":"Titanium.Web.Proxy.Http.Response","type":"Class"},{"name":"StreamingProxyResult","href":"Titanium.Web.Proxy.Http.StreamingProxyResult.html","topicHref":"Titanium.Web.Proxy.Http.StreamingProxyResult.html","topicUid":"Titanium.Web.Proxy.Http.StreamingProxyResult","type":"Struct"},{"name":"TunnelType","href":"Titanium.Web.Proxy.Http.TunnelType.html","topicHref":"Titanium.Web.Proxy.Http.TunnelType.html","topicUid":"Titanium.Web.Proxy.Http.TunnelType","type":"Enum"}]},{"name":"Titanium.Web.Proxy.Http.Responses","href":"Titanium.Web.Proxy.Http.Responses.html","topicHref":"Titanium.Web.Proxy.Http.Responses.html","topicUid":"Titanium.Web.Proxy.Http.Responses","type":"Namespace","items":[{"name":"GenericResponse","href":"Titanium.Web.Proxy.Http.Responses.GenericResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.GenericResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.GenericResponse","type":"Class"},{"name":"OkResponse","href":"Titanium.Web.Proxy.Http.Responses.OkResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.OkResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.OkResponse","type":"Class"},{"name":"RedirectResponse","href":"Titanium.Web.Proxy.Http.Responses.RedirectResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.RedirectResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.RedirectResponse","type":"Class"}]},{"name":"Titanium.Web.Proxy.Http3","href":"Titanium.Web.Proxy.Http3.html","topicHref":"Titanium.Web.Proxy.Http3.html","topicUid":"Titanium.Web.Proxy.Http3","type":"Namespace","items":[{"name":"Http3NativeBootstrap","href":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap.html","topicHref":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap.html","topicUid":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap","type":"Class"}]},{"name":"Titanium.Web.Proxy.Logging","href":"Titanium.Web.Proxy.Logging.html","topicHref":"Titanium.Web.Proxy.Logging.html","topicUid":"Titanium.Web.Proxy.Logging","type":"Namespace","items":[{"name":"ProxyLoggingOptions","href":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions.html","topicHref":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions.html","topicUid":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions","type":"Class"}]},{"name":"Titanium.Web.Proxy.Middleware","href":"Titanium.Web.Proxy.Middleware.html","topicHref":"Titanium.Web.Proxy.Middleware.html","topicUid":"Titanium.Web.Proxy.Middleware","type":"Namespace","items":[{"name":"ProxyMiddlewarePipeline","href":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline.html","topicHref":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline.html","topicUid":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline","type":"Class"}]},{"name":"Titanium.Web.Proxy.Models","href":"Titanium.Web.Proxy.Models.html","topicHref":"Titanium.Web.Proxy.Models.html","topicUid":"Titanium.Web.Proxy.Models","type":"Namespace","items":[{"name":"DecryptFailureBypassEntry","href":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html","topicHref":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html","topicUid":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry","type":"Class"},{"name":"ExplicitProxyEndPoint","href":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint","type":"Class"},{"name":"ExternalProxy","href":"Titanium.Web.Proxy.Models.ExternalProxy.html","topicHref":"Titanium.Web.Proxy.Models.ExternalProxy.html","topicUid":"Titanium.Web.Proxy.Models.ExternalProxy","type":"Class"},{"name":"ExternalProxyType","href":"Titanium.Web.Proxy.Models.ExternalProxyType.html","topicHref":"Titanium.Web.Proxy.Models.ExternalProxyType.html","topicUid":"Titanium.Web.Proxy.Models.ExternalProxyType","type":"Enum"},{"name":"HttpHeader","href":"Titanium.Web.Proxy.Models.HttpHeader.html","topicHref":"Titanium.Web.Proxy.Models.HttpHeader.html","topicUid":"Titanium.Web.Proxy.Models.HttpHeader","type":"Class"},{"name":"HttpInterceptionContext","href":"Titanium.Web.Proxy.Models.HttpInterceptionContext.html","topicHref":"Titanium.Web.Proxy.Models.HttpInterceptionContext.html","topicUid":"Titanium.Web.Proxy.Models.HttpInterceptionContext","type":"Struct"},{"name":"IExternalProxy","href":"Titanium.Web.Proxy.Models.IExternalProxy.html","topicHref":"Titanium.Web.Proxy.Models.IExternalProxy.html","topicUid":"Titanium.Web.Proxy.Models.IExternalProxy","type":"Interface"},{"name":"OriginHttpVersionPolicy","href":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy.html","topicHref":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy.html","topicUid":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy","type":"Enum"},{"name":"ProxyAuthenticationContext","href":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext.html","topicHref":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext.html","topicUid":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext","type":"Class"},{"name":"ProxyAuthenticationResult","href":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult.html","topicHref":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult.html","topicUid":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult","type":"Enum"},{"name":"ProxyEndPoint","href":"Titanium.Web.Proxy.Models.ProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.ProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.ProxyEndPoint","type":"Class"},{"name":"ProxyProtocolType","href":"Titanium.Web.Proxy.Models.ProxyProtocolType.html","topicHref":"Titanium.Web.Proxy.Models.ProxyProtocolType.html","topicUid":"Titanium.Web.Proxy.Models.ProxyProtocolType","type":"Enum"},{"name":"SocksProxyEndPoint","href":"Titanium.Web.Proxy.Models.SocksProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.SocksProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.SocksProxyEndPoint","type":"Class"},{"name":"TransparentBaseProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint","type":"Class"},{"name":"TransparentProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint","type":"Class"},{"name":"TransparentQuicProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint","type":"Class"},{"name":"UpstreamHttpProtocol","href":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html","topicHref":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html","topicUid":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol","type":"Enum"},{"name":"WinAuthCredentials","href":"Titanium.Web.Proxy.Models.WinAuthCredentials.html","topicHref":"Titanium.Web.Proxy.Models.WinAuthCredentials.html","topicUid":"Titanium.Web.Proxy.Models.WinAuthCredentials","type":"Class"}]},{"name":"Titanium.Web.Proxy.Network","href":"Titanium.Web.Proxy.Network.html","topicHref":"Titanium.Web.Proxy.Network.html","topicUid":"Titanium.Web.Proxy.Network","type":"Namespace","items":[{"name":"CertificateEngine","href":"Titanium.Web.Proxy.Network.CertificateEngine.html","topicHref":"Titanium.Web.Proxy.Network.CertificateEngine.html","topicUid":"Titanium.Web.Proxy.Network.CertificateEngine","type":"Enum"},{"name":"CertificateKeyAlgorithm","href":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm.html","topicHref":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm.html","topicUid":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm","type":"Enum"},{"name":"CertificateManager","href":"Titanium.Web.Proxy.Network.CertificateManager.html","topicHref":"Titanium.Web.Proxy.Network.CertificateManager.html","topicUid":"Titanium.Web.Proxy.Network.CertificateManager","type":"Class"},{"name":"CertificateOsTrustKind","href":"Titanium.Web.Proxy.Network.CertificateOsTrustKind.html","topicHref":"Titanium.Web.Proxy.Network.CertificateOsTrustKind.html","topicUid":"Titanium.Web.Proxy.Network.CertificateOsTrustKind","type":"Enum"},{"name":"CertificateOsTrustResult","href":"Titanium.Web.Proxy.Network.CertificateOsTrustResult.html","topicHref":"Titanium.Web.Proxy.Network.CertificateOsTrustResult.html","topicUid":"Titanium.Web.Proxy.Network.CertificateOsTrustResult","type":"Class"},{"name":"DefaultCertificateDiskCache","href":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache.html","topicHref":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache.html","topicUid":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache","type":"Class"},{"name":"FirefoxCertificateTrust","href":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust.html","topicHref":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust.html","topicUid":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust","type":"Class"},{"name":"ICertificateCache","href":"Titanium.Web.Proxy.Network.ICertificateCache.html","topicHref":"Titanium.Web.Proxy.Network.ICertificateCache.html","topicUid":"Titanium.Web.Proxy.Network.ICertificateCache","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Network.Quic","href":"Titanium.Web.Proxy.Network.Quic.html","topicHref":"Titanium.Web.Proxy.Network.Quic.html","topicUid":"Titanium.Web.Proxy.Network.Quic","type":"Namespace","items":[{"name":"IOriginalDestinationResolver","href":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver.html","topicHref":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver.html","topicUid":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Options","href":"Titanium.Web.Proxy.Options.html","topicHref":"Titanium.Web.Proxy.Options.html","topicUid":"Titanium.Web.Proxy.Options","type":"Namespace","items":[{"name":"PolicyFamily","href":"Titanium.Web.Proxy.Options.PolicyFamily.html","topicHref":"Titanium.Web.Proxy.Options.PolicyFamily.html","topicUid":"Titanium.Web.Proxy.Options.PolicyFamily","type":"Enum"},{"name":"PolicyMode","href":"Titanium.Web.Proxy.Options.PolicyMode.html","topicHref":"Titanium.Web.Proxy.Options.PolicyMode.html","topicUid":"Titanium.Web.Proxy.Options.PolicyMode","type":"Enum"},{"name":"ProxyPolicyModes","href":"Titanium.Web.Proxy.Options.ProxyPolicyModes.html","topicHref":"Titanium.Web.Proxy.Options.ProxyPolicyModes.html","topicUid":"Titanium.Web.Proxy.Options.ProxyPolicyModes","type":"Class"},{"name":"ProxyProfile","href":"Titanium.Web.Proxy.Options.ProxyProfile.html","topicHref":"Titanium.Web.Proxy.Options.ProxyProfile.html","topicUid":"Titanium.Web.Proxy.Options.ProxyProfile","type":"Enum"},{"name":"ProxyProfileSettings","href":"Titanium.Web.Proxy.Options.ProxyProfileSettings.html","topicHref":"Titanium.Web.Proxy.Options.ProxyProfileSettings.html","topicUid":"Titanium.Web.Proxy.Options.ProxyProfileSettings","type":"Class"},{"name":"ProxyResourceLimits","href":"Titanium.Web.Proxy.Options.ProxyResourceLimits.html","topicHref":"Titanium.Web.Proxy.Options.ProxyResourceLimits.html","topicUid":"Titanium.Web.Proxy.Options.ProxyResourceLimits","type":"Class"},{"name":"ProxyTimeoutOptions","href":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions.html","topicHref":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions.html","topicUid":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions","type":"Class"},{"name":"ResolvedSessionPolicy","href":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy.html","topicHref":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy.html","topicUid":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy","type":"Class"}]},{"name":"Titanium.Web.Proxy.Routing","href":"Titanium.Web.Proxy.Routing.html","topicHref":"Titanium.Web.Proxy.Routing.html","topicUid":"Titanium.Web.Proxy.Routing","type":"Namespace","items":[{"name":"ReverseProxyFastPath","href":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html","topicHref":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html","topicUid":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath","type":"Class"},{"name":"RouteMatcher","href":"Titanium.Web.Proxy.Routing.RouteMatcher.html","topicHref":"Titanium.Web.Proxy.Routing.RouteMatcher.html","topicUid":"Titanium.Web.Proxy.Routing.RouteMatcher","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended","href":"Titanium.Web.Proxy.StreamExtended.html","topicHref":"Titanium.Web.Proxy.StreamExtended.html","topicUid":"Titanium.Web.Proxy.StreamExtended","type":"Namespace","items":[{"name":"ClientHelloInfo","href":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo.html","topicHref":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo.html","topicUid":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo","type":"Class"},{"name":"ServerHelloInfo","href":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo.html","topicHref":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo.html","topicUid":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended.BufferPool","href":"Titanium.Web.Proxy.StreamExtended.BufferPool.html","topicHref":"Titanium.Web.Proxy.StreamExtended.BufferPool.html","topicUid":"Titanium.Web.Proxy.StreamExtended.BufferPool","type":"Namespace","items":[{"name":"IBufferPool","href":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool.html","topicHref":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool.html","topicUid":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool","type":"Interface"}]},{"name":"Titanium.Web.Proxy.StreamExtended.Models","href":"Titanium.Web.Proxy.StreamExtended.Models.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Models.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Models","type":"Namespace","items":[{"name":"SslExtension","href":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended.Network","href":"Titanium.Web.Proxy.StreamExtended.Network.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network","type":"Namespace","items":[{"name":"DataEventArgs","href":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs","type":"Class"},{"name":"IHttpStreamReader","href":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader","type":"Interface"},{"name":"IHttpStreamWriter","href":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter","type":"Interface"},{"name":"ILineStream","href":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream","type":"Interface"},{"name":"IPeekStream","href":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Transforms","href":"Titanium.Web.Proxy.Transforms.html","topicHref":"Titanium.Web.Proxy.Transforms.html","topicUid":"Titanium.Web.Proxy.Transforms","type":"Namespace","items":[{"name":"TransformEngine","href":"Titanium.Web.Proxy.Transforms.TransformEngine.html","topicHref":"Titanium.Web.Proxy.Transforms.TransformEngine.html","topicUid":"Titanium.Web.Proxy.Transforms.TransformEngine","type":"Class"}]}],"memberLayout":"SamePage"}
+{"items":[{"name":"Titanium.Web.Proxy","href":"Titanium.Web.Proxy.html","topicHref":"Titanium.Web.Proxy.html","topicUid":"Titanium.Web.Proxy","type":"Namespace","items":[{"name":"ClientProcessId","href":"Titanium.Web.Proxy.ClientProcessId.html","topicHref":"Titanium.Web.Proxy.ClientProcessId.html","topicUid":"Titanium.Web.Proxy.ClientProcessId","type":"Class"},{"name":"MitmExclusionDefaults","href":"Titanium.Web.Proxy.MitmExclusionDefaults.html","topicHref":"Titanium.Web.Proxy.MitmExclusionDefaults.html","topicUid":"Titanium.Web.Proxy.MitmExclusionDefaults","type":"Class"},{"name":"MitmExclusionMode","href":"Titanium.Web.Proxy.MitmExclusionMode.html","topicHref":"Titanium.Web.Proxy.MitmExclusionMode.html","topicUid":"Titanium.Web.Proxy.MitmExclusionMode","type":"Enum"},{"name":"ProxyLimits","href":"Titanium.Web.Proxy.ProxyLimits.html","topicHref":"Titanium.Web.Proxy.ProxyLimits.html","topicUid":"Titanium.Web.Proxy.ProxyLimits","type":"Class"},{"name":"ProxyServer","href":"Titanium.Web.Proxy.ProxyServer.html","topicHref":"Titanium.Web.Proxy.ProxyServer.html","topicUid":"Titanium.Web.Proxy.ProxyServer","type":"Class"},{"name":"SystemProxyBypassRuleMode","href":"Titanium.Web.Proxy.SystemProxyBypassRuleMode.html","topicHref":"Titanium.Web.Proxy.SystemProxyBypassRuleMode.html","topicUid":"Titanium.Web.Proxy.SystemProxyBypassRuleMode","type":"Enum"},{"name":"SystemProxyChangeResult","href":"Titanium.Web.Proxy.SystemProxyChangeResult.html","topicHref":"Titanium.Web.Proxy.SystemProxyChangeResult.html","topicUid":"Titanium.Web.Proxy.SystemProxyChangeResult","type":"Struct"},{"name":"SystemProxyLoopbackPlacement","href":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement.html","topicHref":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement.html","topicUid":"Titanium.Web.Proxy.SystemProxyLoopbackPlacement","type":"Enum"},{"name":"SystemProxySettings","href":"Titanium.Web.Proxy.SystemProxySettings.html","topicHref":"Titanium.Web.Proxy.SystemProxySettings.html","topicUid":"Titanium.Web.Proxy.SystemProxySettings","type":"Class"},{"name":"WebSocketDecoder","href":"Titanium.Web.Proxy.WebSocketDecoder.html","topicHref":"Titanium.Web.Proxy.WebSocketDecoder.html","topicUid":"Titanium.Web.Proxy.WebSocketDecoder","type":"Class"},{"name":"WebSocketFrame","href":"Titanium.Web.Proxy.WebSocketFrame.html","topicHref":"Titanium.Web.Proxy.WebSocketFrame.html","topicUid":"Titanium.Web.Proxy.WebSocketFrame","type":"Class"},{"name":"WebSocketFrameEncoder","href":"Titanium.Web.Proxy.WebSocketFrameEncoder.html","topicHref":"Titanium.Web.Proxy.WebSocketFrameEncoder.html","topicUid":"Titanium.Web.Proxy.WebSocketFrameEncoder","type":"Class"},{"name":"WebSocketFrameWriter","href":"Titanium.Web.Proxy.WebSocketFrameWriter.html","topicHref":"Titanium.Web.Proxy.WebSocketFrameWriter.html","topicUid":"Titanium.Web.Proxy.WebSocketFrameWriter","type":"Class"},{"name":"WebSocketProtocolException","href":"Titanium.Web.Proxy.WebSocketProtocolException.html","topicHref":"Titanium.Web.Proxy.WebSocketProtocolException.html","topicUid":"Titanium.Web.Proxy.WebSocketProtocolException","type":"Class"},{"name":"WebsocketOpCode","href":"Titanium.Web.Proxy.WebsocketOpCode.html","topicHref":"Titanium.Web.Proxy.WebsocketOpCode.html","topicUid":"Titanium.Web.Proxy.WebsocketOpCode","type":"Enum"}]},{"name":"Titanium.Web.Proxy.Caching","href":"Titanium.Web.Proxy.Caching.html","topicHref":"Titanium.Web.Proxy.Caching.html","topicUid":"Titanium.Web.Proxy.Caching","type":"Namespace","items":[{"name":"HttpResponseCacheMiddleware","href":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware.html","topicHref":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware.html","topicUid":"Titanium.Web.Proxy.Caching.HttpResponseCacheMiddleware","type":"Class"},{"name":"MemoryHttpResponseCache","href":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache.html","topicHref":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache.html","topicUid":"Titanium.Web.Proxy.Caching.MemoryHttpResponseCache","type":"Class"}]},{"name":"Titanium.Web.Proxy.Clusters","href":"Titanium.Web.Proxy.Clusters.html","topicHref":"Titanium.Web.Proxy.Clusters.html","topicUid":"Titanium.Web.Proxy.Clusters","type":"Namespace","items":[{"name":"ClusterManager","href":"Titanium.Web.Proxy.Clusters.ClusterManager.html","topicHref":"Titanium.Web.Proxy.Clusters.ClusterManager.html","topicUid":"Titanium.Web.Proxy.Clusters.ClusterManager","type":"Class"},{"name":"DestinationHealthTracker","href":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker.html","topicHref":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker.html","topicUid":"Titanium.Web.Proxy.Clusters.DestinationHealthTracker","type":"Class"},{"name":"LoadBalancer","href":"Titanium.Web.Proxy.Clusters.LoadBalancer.html","topicHref":"Titanium.Web.Proxy.Clusters.LoadBalancer.html","topicUid":"Titanium.Web.Proxy.Clusters.LoadBalancer","type":"Class"}]},{"name":"Titanium.Web.Proxy.Diagnostics","href":"Titanium.Web.Proxy.Diagnostics.html","topicHref":"Titanium.Web.Proxy.Diagnostics.html","topicUid":"Titanium.Web.Proxy.Diagnostics","type":"Namespace","items":[{"name":"ClientTlsTiming","href":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.ClientTlsTiming","type":"Class"},{"name":"HttpRequestTiming","href":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.HttpRequestTiming","type":"Class"},{"name":"TunnelConnectTiming","href":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.TunnelConnectTiming","type":"Class"},{"name":"UpstreamConnectionTiming","href":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming.html","topicHref":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming.html","topicUid":"Titanium.Web.Proxy.Diagnostics.UpstreamConnectionTiming","type":"Class"}]},{"name":"Titanium.Web.Proxy.EventArguments","href":"Titanium.Web.Proxy.EventArguments.html","topicHref":"Titanium.Web.Proxy.EventArguments.html","topicUid":"Titanium.Web.Proxy.EventArguments","type":"Namespace","items":[{"name":"AsyncEventHandler","href":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html","topicHref":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler-1.html","topicUid":"Titanium.Web.Proxy.EventArguments.AsyncEventHandler`1","type":"Delegate"},{"name":"BeforeBodyWriteEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeBodyWriteEventArgs","type":"Class"},{"name":"BeforeHttpAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeHttpAuthenticateEventArgs","type":"Class"},{"name":"BeforeQuicAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeQuicAuthenticateEventArgs","type":"Class"},{"name":"BeforeSslAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.BeforeSslAuthenticateEventArgs","type":"Class"},{"name":"CertificateSelectionEventArgs","href":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.CertificateSelectionEventArgs","type":"Class"},{"name":"CertificateValidationEventArgs","href":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.CertificateValidationEventArgs","type":"Class"},{"name":"MultipartRequestPartSentEventArgs","href":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.MultipartRequestPartSentEventArgs","type":"Class"},{"name":"ProxyEventArgsBase","href":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html","topicHref":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html","topicUid":"Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase","type":"Class"},{"name":"SessionEventArgs","href":"Titanium.Web.Proxy.EventArguments.SessionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.SessionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.SessionEventArgs","type":"Class"},{"name":"SessionEventArgsBase","href":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html","topicHref":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html","topicUid":"Titanium.Web.Proxy.EventArguments.SessionEventArgsBase","type":"Class"},{"name":"SocksAuthenticateEventArgs","href":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.SocksAuthenticateEventArgs","type":"Class"},{"name":"TunnelConnectFailureEventArgs","href":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.TunnelConnectFailureEventArgs","type":"Class"},{"name":"TunnelConnectSessionEventArgs","href":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.TunnelConnectSessionEventArgs","type":"Class"},{"name":"WebSocketFrameDirection","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection","type":"Enum"},{"name":"WebSocketFrameElidedEventArgs","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs","type":"Class"},{"name":"WebSocketFrameInterceptAction","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction","type":"Enum"},{"name":"WebSocketFrameInterceptEventArgs","href":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs.html","topicHref":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs.html","topicUid":"Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptEventArgs","type":"Class"}]},{"name":"Titanium.Web.Proxy.Exceptions","href":"Titanium.Web.Proxy.Exceptions.html","topicHref":"Titanium.Web.Proxy.Exceptions.html","topicUid":"Titanium.Web.Proxy.Exceptions","type":"Namespace","items":[{"name":"BodyNotFoundException","href":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html","topicHref":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html","topicUid":"Titanium.Web.Proxy.Exceptions.BodyNotFoundException","type":"Class"},{"name":"OutboundDestinationBlockedException","href":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException.html","topicHref":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException.html","topicUid":"Titanium.Web.Proxy.Exceptions.OutboundDestinationBlockedException","type":"Class"},{"name":"ProxyAuthorizationException","href":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyAuthorizationException","type":"Class"},{"name":"ProxyConnectException","href":"Titanium.Web.Proxy.Exceptions.ProxyConnectException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyConnectException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyConnectException","type":"Class"},{"name":"ProxyException","href":"Titanium.Web.Proxy.Exceptions.ProxyException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyException","type":"Class"},{"name":"ProxyHttpException","href":"Titanium.Web.Proxy.Exceptions.ProxyHttpException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyHttpException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyHttpException","type":"Class"},{"name":"ProxyTimeoutException","href":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutException","type":"Class"},{"name":"ProxyTimeoutKind","href":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind.html","topicHref":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind.html","topicUid":"Titanium.Web.Proxy.Exceptions.ProxyTimeoutKind","type":"Enum"},{"name":"UpstreamProxyConnectException","href":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException.html","topicHref":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException.html","topicUid":"Titanium.Web.Proxy.Exceptions.UpstreamProxyConnectException","type":"Class"}]},{"name":"Titanium.Web.Proxy.Helpers","href":"Titanium.Web.Proxy.Helpers.html","topicHref":"Titanium.Web.Proxy.Helpers.html","topicUid":"Titanium.Web.Proxy.Helpers","type":"Namespace","items":[{"name":"UnixProxyBypassMapper","href":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper.html","topicHref":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper.html","topicUid":"Titanium.Web.Proxy.Helpers.UnixProxyBypassMapper","type":"Class"}]},{"name":"Titanium.Web.Proxy.Http","href":"Titanium.Web.Proxy.Http.html","topicHref":"Titanium.Web.Proxy.Http.html","topicUid":"Titanium.Web.Proxy.Http","type":"Namespace","items":[{"name":"ConnectRequest","href":"Titanium.Web.Proxy.Http.ConnectRequest.html","topicHref":"Titanium.Web.Proxy.Http.ConnectRequest.html","topicUid":"Titanium.Web.Proxy.Http.ConnectRequest","type":"Class"},{"name":"ConnectResponse","href":"Titanium.Web.Proxy.Http.ConnectResponse.html","topicHref":"Titanium.Web.Proxy.Http.ConnectResponse.html","topicUid":"Titanium.Web.Proxy.Http.ConnectResponse","type":"Class"},{"name":"HeaderCollection","href":"Titanium.Web.Proxy.Http.HeaderCollection.html","topicHref":"Titanium.Web.Proxy.Http.HeaderCollection.html","topicUid":"Titanium.Web.Proxy.Http.HeaderCollection","type":"Class"},{"name":"HeaderCollection.Enumerator","href":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator.html","topicHref":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator.html","topicUid":"Titanium.Web.Proxy.Http.HeaderCollection.Enumerator","type":"Struct"},{"name":"HttpWebClient","href":"Titanium.Web.Proxy.Http.HttpWebClient.html","topicHref":"Titanium.Web.Proxy.Http.HttpWebClient.html","topicUid":"Titanium.Web.Proxy.Http.HttpWebClient","type":"Class"},{"name":"KnownHeader","href":"Titanium.Web.Proxy.Http.KnownHeader.html","topicHref":"Titanium.Web.Proxy.Http.KnownHeader.html","topicUid":"Titanium.Web.Proxy.Http.KnownHeader","type":"Class"},{"name":"KnownHeaders","href":"Titanium.Web.Proxy.Http.KnownHeaders.html","topicHref":"Titanium.Web.Proxy.Http.KnownHeaders.html","topicUid":"Titanium.Web.Proxy.Http.KnownHeaders","type":"Class"},{"name":"ProxyResults","href":"Titanium.Web.Proxy.Http.ProxyResults.html","topicHref":"Titanium.Web.Proxy.Http.ProxyResults.html","topicUid":"Titanium.Web.Proxy.Http.ProxyResults","type":"Class"},{"name":"Request","href":"Titanium.Web.Proxy.Http.Request.html","topicHref":"Titanium.Web.Proxy.Http.Request.html","topicUid":"Titanium.Web.Proxy.Http.Request","type":"Class"},{"name":"RequestResponseBase","href":"Titanium.Web.Proxy.Http.RequestResponseBase.html","topicHref":"Titanium.Web.Proxy.Http.RequestResponseBase.html","topicUid":"Titanium.Web.Proxy.Http.RequestResponseBase","type":"Class"},{"name":"Response","href":"Titanium.Web.Proxy.Http.Response.html","topicHref":"Titanium.Web.Proxy.Http.Response.html","topicUid":"Titanium.Web.Proxy.Http.Response","type":"Class"},{"name":"StreamingProxyResult","href":"Titanium.Web.Proxy.Http.StreamingProxyResult.html","topicHref":"Titanium.Web.Proxy.Http.StreamingProxyResult.html","topicUid":"Titanium.Web.Proxy.Http.StreamingProxyResult","type":"Struct"},{"name":"TunnelType","href":"Titanium.Web.Proxy.Http.TunnelType.html","topicHref":"Titanium.Web.Proxy.Http.TunnelType.html","topicUid":"Titanium.Web.Proxy.Http.TunnelType","type":"Enum"}]},{"name":"Titanium.Web.Proxy.Http.Responses","href":"Titanium.Web.Proxy.Http.Responses.html","topicHref":"Titanium.Web.Proxy.Http.Responses.html","topicUid":"Titanium.Web.Proxy.Http.Responses","type":"Namespace","items":[{"name":"GenericResponse","href":"Titanium.Web.Proxy.Http.Responses.GenericResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.GenericResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.GenericResponse","type":"Class"},{"name":"OkResponse","href":"Titanium.Web.Proxy.Http.Responses.OkResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.OkResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.OkResponse","type":"Class"},{"name":"RedirectResponse","href":"Titanium.Web.Proxy.Http.Responses.RedirectResponse.html","topicHref":"Titanium.Web.Proxy.Http.Responses.RedirectResponse.html","topicUid":"Titanium.Web.Proxy.Http.Responses.RedirectResponse","type":"Class"}]},{"name":"Titanium.Web.Proxy.Http3","href":"Titanium.Web.Proxy.Http3.html","topicHref":"Titanium.Web.Proxy.Http3.html","topicUid":"Titanium.Web.Proxy.Http3","type":"Namespace","items":[{"name":"Http3NativeBootstrap","href":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap.html","topicHref":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap.html","topicUid":"Titanium.Web.Proxy.Http3.Http3NativeBootstrap","type":"Class"}]},{"name":"Titanium.Web.Proxy.Logging","href":"Titanium.Web.Proxy.Logging.html","topicHref":"Titanium.Web.Proxy.Logging.html","topicUid":"Titanium.Web.Proxy.Logging","type":"Namespace","items":[{"name":"ProxyLoggingOptions","href":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions.html","topicHref":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions.html","topicUid":"Titanium.Web.Proxy.Logging.ProxyLoggingOptions","type":"Class"}]},{"name":"Titanium.Web.Proxy.Middleware","href":"Titanium.Web.Proxy.Middleware.html","topicHref":"Titanium.Web.Proxy.Middleware.html","topicUid":"Titanium.Web.Proxy.Middleware","type":"Namespace","items":[{"name":"ProxyMiddlewarePipeline","href":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline.html","topicHref":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline.html","topicUid":"Titanium.Web.Proxy.Middleware.ProxyMiddlewarePipeline","type":"Class"}]},{"name":"Titanium.Web.Proxy.Models","href":"Titanium.Web.Proxy.Models.html","topicHref":"Titanium.Web.Proxy.Models.html","topicUid":"Titanium.Web.Proxy.Models","type":"Namespace","items":[{"name":"DecryptFailureBypassEntry","href":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html","topicHref":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html","topicUid":"Titanium.Web.Proxy.Models.DecryptFailureBypassEntry","type":"Class"},{"name":"ExplicitProxyEndPoint","href":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.ExplicitProxyEndPoint","type":"Class"},{"name":"ExternalProxy","href":"Titanium.Web.Proxy.Models.ExternalProxy.html","topicHref":"Titanium.Web.Proxy.Models.ExternalProxy.html","topicUid":"Titanium.Web.Proxy.Models.ExternalProxy","type":"Class"},{"name":"ExternalProxyType","href":"Titanium.Web.Proxy.Models.ExternalProxyType.html","topicHref":"Titanium.Web.Proxy.Models.ExternalProxyType.html","topicUid":"Titanium.Web.Proxy.Models.ExternalProxyType","type":"Enum"},{"name":"HttpHeader","href":"Titanium.Web.Proxy.Models.HttpHeader.html","topicHref":"Titanium.Web.Proxy.Models.HttpHeader.html","topicUid":"Titanium.Web.Proxy.Models.HttpHeader","type":"Class"},{"name":"HttpInterceptionContext","href":"Titanium.Web.Proxy.Models.HttpInterceptionContext.html","topicHref":"Titanium.Web.Proxy.Models.HttpInterceptionContext.html","topicUid":"Titanium.Web.Proxy.Models.HttpInterceptionContext","type":"Struct"},{"name":"IExternalProxy","href":"Titanium.Web.Proxy.Models.IExternalProxy.html","topicHref":"Titanium.Web.Proxy.Models.IExternalProxy.html","topicUid":"Titanium.Web.Proxy.Models.IExternalProxy","type":"Interface"},{"name":"OriginHttpVersionPolicy","href":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy.html","topicHref":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy.html","topicUid":"Titanium.Web.Proxy.Models.OriginHttpVersionPolicy","type":"Enum"},{"name":"ProxyAuthenticationContext","href":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext.html","topicHref":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext.html","topicUid":"Titanium.Web.Proxy.Models.ProxyAuthenticationContext","type":"Class"},{"name":"ProxyAuthenticationResult","href":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult.html","topicHref":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult.html","topicUid":"Titanium.Web.Proxy.Models.ProxyAuthenticationResult","type":"Enum"},{"name":"ProxyEndPoint","href":"Titanium.Web.Proxy.Models.ProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.ProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.ProxyEndPoint","type":"Class"},{"name":"ProxyProtocolType","href":"Titanium.Web.Proxy.Models.ProxyProtocolType.html","topicHref":"Titanium.Web.Proxy.Models.ProxyProtocolType.html","topicUid":"Titanium.Web.Proxy.Models.ProxyProtocolType","type":"Enum"},{"name":"SocksProxyEndPoint","href":"Titanium.Web.Proxy.Models.SocksProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.SocksProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.SocksProxyEndPoint","type":"Class"},{"name":"TransparentBaseProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentBaseProxyEndPoint","type":"Class"},{"name":"TransparentProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentProxyEndPoint","type":"Class"},{"name":"TransparentQuicProxyEndPoint","href":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint.html","topicHref":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint.html","topicUid":"Titanium.Web.Proxy.Models.TransparentQuicProxyEndPoint","type":"Class"},{"name":"UpstreamHttpProtocol","href":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html","topicHref":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html","topicUid":"Titanium.Web.Proxy.Models.UpstreamHttpProtocol","type":"Enum"},{"name":"WinAuthCredentials","href":"Titanium.Web.Proxy.Models.WinAuthCredentials.html","topicHref":"Titanium.Web.Proxy.Models.WinAuthCredentials.html","topicUid":"Titanium.Web.Proxy.Models.WinAuthCredentials","type":"Class"}]},{"name":"Titanium.Web.Proxy.Network","href":"Titanium.Web.Proxy.Network.html","topicHref":"Titanium.Web.Proxy.Network.html","topicUid":"Titanium.Web.Proxy.Network","type":"Namespace","items":[{"name":"CertificateEngine","href":"Titanium.Web.Proxy.Network.CertificateEngine.html","topicHref":"Titanium.Web.Proxy.Network.CertificateEngine.html","topicUid":"Titanium.Web.Proxy.Network.CertificateEngine","type":"Enum"},{"name":"CertificateKeyAlgorithm","href":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm.html","topicHref":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm.html","topicUid":"Titanium.Web.Proxy.Network.CertificateKeyAlgorithm","type":"Enum"},{"name":"CertificateManager","href":"Titanium.Web.Proxy.Network.CertificateManager.html","topicHref":"Titanium.Web.Proxy.Network.CertificateManager.html","topicUid":"Titanium.Web.Proxy.Network.CertificateManager","type":"Class"},{"name":"CertificateOsTrustKind","href":"Titanium.Web.Proxy.Network.CertificateOsTrustKind.html","topicHref":"Titanium.Web.Proxy.Network.CertificateOsTrustKind.html","topicUid":"Titanium.Web.Proxy.Network.CertificateOsTrustKind","type":"Enum"},{"name":"CertificateOsTrustResult","href":"Titanium.Web.Proxy.Network.CertificateOsTrustResult.html","topicHref":"Titanium.Web.Proxy.Network.CertificateOsTrustResult.html","topicUid":"Titanium.Web.Proxy.Network.CertificateOsTrustResult","type":"Class"},{"name":"DefaultCertificateDiskCache","href":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache.html","topicHref":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache.html","topicUid":"Titanium.Web.Proxy.Network.DefaultCertificateDiskCache","type":"Class"},{"name":"FirefoxCertificateTrust","href":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust.html","topicHref":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust.html","topicUid":"Titanium.Web.Proxy.Network.FirefoxCertificateTrust","type":"Class"},{"name":"ICertificateCache","href":"Titanium.Web.Proxy.Network.ICertificateCache.html","topicHref":"Titanium.Web.Proxy.Network.ICertificateCache.html","topicUid":"Titanium.Web.Proxy.Network.ICertificateCache","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Network.Quic","href":"Titanium.Web.Proxy.Network.Quic.html","topicHref":"Titanium.Web.Proxy.Network.Quic.html","topicUid":"Titanium.Web.Proxy.Network.Quic","type":"Namespace","items":[{"name":"IOriginalDestinationResolver","href":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver.html","topicHref":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver.html","topicUid":"Titanium.Web.Proxy.Network.Quic.IOriginalDestinationResolver","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Options","href":"Titanium.Web.Proxy.Options.html","topicHref":"Titanium.Web.Proxy.Options.html","topicUid":"Titanium.Web.Proxy.Options","type":"Namespace","items":[{"name":"PolicyFamily","href":"Titanium.Web.Proxy.Options.PolicyFamily.html","topicHref":"Titanium.Web.Proxy.Options.PolicyFamily.html","topicUid":"Titanium.Web.Proxy.Options.PolicyFamily","type":"Enum"},{"name":"PolicyMode","href":"Titanium.Web.Proxy.Options.PolicyMode.html","topicHref":"Titanium.Web.Proxy.Options.PolicyMode.html","topicUid":"Titanium.Web.Proxy.Options.PolicyMode","type":"Enum"},{"name":"ProxyPolicyModes","href":"Titanium.Web.Proxy.Options.ProxyPolicyModes.html","topicHref":"Titanium.Web.Proxy.Options.ProxyPolicyModes.html","topicUid":"Titanium.Web.Proxy.Options.ProxyPolicyModes","type":"Class"},{"name":"ProxyProfile","href":"Titanium.Web.Proxy.Options.ProxyProfile.html","topicHref":"Titanium.Web.Proxy.Options.ProxyProfile.html","topicUid":"Titanium.Web.Proxy.Options.ProxyProfile","type":"Enum"},{"name":"ProxyProfileSettings","href":"Titanium.Web.Proxy.Options.ProxyProfileSettings.html","topicHref":"Titanium.Web.Proxy.Options.ProxyProfileSettings.html","topicUid":"Titanium.Web.Proxy.Options.ProxyProfileSettings","type":"Class"},{"name":"ProxyResourceLimits","href":"Titanium.Web.Proxy.Options.ProxyResourceLimits.html","topicHref":"Titanium.Web.Proxy.Options.ProxyResourceLimits.html","topicUid":"Titanium.Web.Proxy.Options.ProxyResourceLimits","type":"Class"},{"name":"ProxyTimeoutOptions","href":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions.html","topicHref":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions.html","topicUid":"Titanium.Web.Proxy.Options.ProxyTimeoutOptions","type":"Class"},{"name":"ResolvedSessionPolicy","href":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy.html","topicHref":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy.html","topicUid":"Titanium.Web.Proxy.Options.ResolvedSessionPolicy","type":"Class"}]},{"name":"Titanium.Web.Proxy.Routing","href":"Titanium.Web.Proxy.Routing.html","topicHref":"Titanium.Web.Proxy.Routing.html","topicUid":"Titanium.Web.Proxy.Routing","type":"Namespace","items":[{"name":"ReverseProxyFastPath","href":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html","topicHref":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html","topicUid":"Titanium.Web.Proxy.Routing.ReverseProxyFastPath","type":"Class"},{"name":"RouteMatcher","href":"Titanium.Web.Proxy.Routing.RouteMatcher.html","topicHref":"Titanium.Web.Proxy.Routing.RouteMatcher.html","topicUid":"Titanium.Web.Proxy.Routing.RouteMatcher","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended","href":"Titanium.Web.Proxy.StreamExtended.html","topicHref":"Titanium.Web.Proxy.StreamExtended.html","topicUid":"Titanium.Web.Proxy.StreamExtended","type":"Namespace","items":[{"name":"ClientHelloInfo","href":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo.html","topicHref":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo.html","topicUid":"Titanium.Web.Proxy.StreamExtended.ClientHelloInfo","type":"Class"},{"name":"ServerHelloInfo","href":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo.html","topicHref":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo.html","topicUid":"Titanium.Web.Proxy.StreamExtended.ServerHelloInfo","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended.BufferPool","href":"Titanium.Web.Proxy.StreamExtended.BufferPool.html","topicHref":"Titanium.Web.Proxy.StreamExtended.BufferPool.html","topicUid":"Titanium.Web.Proxy.StreamExtended.BufferPool","type":"Namespace","items":[{"name":"IBufferPool","href":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool.html","topicHref":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool.html","topicUid":"Titanium.Web.Proxy.StreamExtended.BufferPool.IBufferPool","type":"Interface"}]},{"name":"Titanium.Web.Proxy.StreamExtended.Models","href":"Titanium.Web.Proxy.StreamExtended.Models.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Models.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Models","type":"Namespace","items":[{"name":"SslExtension","href":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Models.SslExtension","type":"Class"}]},{"name":"Titanium.Web.Proxy.StreamExtended.Network","href":"Titanium.Web.Proxy.StreamExtended.Network.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network","type":"Namespace","items":[{"name":"DataEventArgs","href":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.DataEventArgs","type":"Class"},{"name":"IHttpStreamReader","href":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamReader","type":"Interface"},{"name":"IHttpStreamWriter","href":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IHttpStreamWriter","type":"Interface"},{"name":"ILineStream","href":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.ILineStream","type":"Interface"},{"name":"IPeekStream","href":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream.html","topicHref":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream.html","topicUid":"Titanium.Web.Proxy.StreamExtended.Network.IPeekStream","type":"Interface"}]},{"name":"Titanium.Web.Proxy.Transforms","href":"Titanium.Web.Proxy.Transforms.html","topicHref":"Titanium.Web.Proxy.Transforms.html","topicUid":"Titanium.Web.Proxy.Transforms","type":"Namespace","items":[{"name":"TransformEngine","href":"Titanium.Web.Proxy.Transforms.TransformEngine.html","topicHref":"Titanium.Web.Proxy.Transforms.TransformEngine.html","topicUid":"Titanium.Web.Proxy.Transforms.TransformEngine","type":"Class"}]}],"memberLayout":"SamePage"}
diff --git a/docs/index.json b/docs/index.json
index 6f8f40e84..d78964710 100644
--- a/docs/index.json
+++ b/docs/index.json
@@ -112,12 +112,12 @@
"api/Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html": {
"href": "api/Titanium.Web.Proxy.EventArguments.ProxyEventArgsBase.html",
"title": "Class ProxyEventArgsBase | Titanium Web Proxy",
- "summary": "Class ProxyEventArgsBase The base event arguments Inheritance object EventArgs ProxyEventArgsBase BeforeBodyWriteEventArgs BeforeHttpAuthenticateEventArgs BeforeSslAuthenticateEventArgs CertificateSelectionEventArgs CertificateValidationEventArgs MultipartRequestPartSentEventArgs SessionEventArgsBase SocksAuthenticateEventArgs TunnelConnectFailureEventArgs WebSocketFrameInterceptEventArgs Inherited Members EventArgs.Empty object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public abstract class ProxyEventArgsBase : EventArgs Properties | Edit this page View Source ClientUserData Declaration public object? ClientUserData { get; set; } Property Value Type Description object See Also EventArgs"
+ "summary": "Class ProxyEventArgsBase The base event arguments Inheritance object EventArgs ProxyEventArgsBase BeforeBodyWriteEventArgs BeforeHttpAuthenticateEventArgs BeforeSslAuthenticateEventArgs CertificateSelectionEventArgs CertificateValidationEventArgs MultipartRequestPartSentEventArgs SessionEventArgsBase SocksAuthenticateEventArgs TunnelConnectFailureEventArgs WebSocketFrameElidedEventArgs WebSocketFrameInterceptEventArgs Inherited Members EventArgs.Empty object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public abstract class ProxyEventArgsBase : EventArgs Properties | Edit this page View Source ClientUserData Declaration public object? ClientUserData { get; set; } Property Value Type Description object See Also EventArgs"
},
"api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html": {
"href": "api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html",
"title": "Class SessionEventArgs | Titanium Web Proxy",
- "summary": "Class SessionEventArgs Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. Inheritance object EventArgs ProxyEventArgsBase SessionEventArgsBase SessionEventArgs Implements IDisposable Inherited Members SessionEventArgsBase.BufferPool SessionEventArgsBase.ClientConnectionId SessionEventArgsBase.ServerConnectionId SessionEventArgsBase.Timing SessionEventArgsBase.UpstreamConnectionTiming SessionEventArgsBase.UserData SessionEventArgsBase.ConnectTimeout SessionEventArgsBase.EnableWinAuth SessionEventArgsBase.IsHttps SessionEventArgsBase.ClientLocalEndPoint SessionEventArgsBase.ClientRemoteEndPoint SessionEventArgsBase.ClientEndPoint SessionEventArgsBase.ServerRemoteEndPoint SessionEventArgsBase.ServerIpAddress SessionEventArgsBase.HttpClient SessionEventArgsBase.WebSession SessionEventArgsBase.CustomUpStreamProxy SessionEventArgsBase.CustomUpStreamProxyUsed SessionEventArgsBase.UpstreamDestinationId SessionEventArgsBase.ProxyEndPoint SessionEventArgsBase.LocalEndPoint SessionEventArgsBase.IsTransparent SessionEventArgsBase.IsSocks SessionEventArgsBase.Exception SessionEventArgsBase.Logger SessionEventArgsBase.Dispose() SessionEventArgsBase.OnException(Exception) SessionEventArgsBase.DataSent SessionEventArgsBase.DataReceived SessionEventArgsBase.TerminateSession() ProxyEventArgsBase.ClientUserData EventArgs.Empty object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public class SessionEventArgs : SessionEventArgsBase, IDisposable Properties | Edit this page View Source IdleReadTimeout Per-session override for IdleReadTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? IdleReadTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source IdleWriteTimeout Per-session override for IdleWriteTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? IdleWriteTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source IsPromise Is this session a HTTP/2 promise? Declaration public bool IsPromise { get; } Property Value Type Description bool | Edit this page View Source MaxBufferedBodyBytes Per-session override for MaxBufferedBodyBytes. null uses the server default. Set in BeforeRequest to increase the limit for large uploads/downloads without relaxing the global limit for all requests. Declaration public int? MaxBufferedBodyBytes { get; set; } Property Value Type Description int? | Edit this page View Source MaxWebSocketFramePayloadBytes Per-session override for MaxWebSocketFramePayloadBytes. null uses the server default. Set in BeforeRequest before the WebSocket upgrade completes. Declaration public int? MaxWebSocketFramePayloadBytes { get; set; } Property Value Type Description int? | Edit this page View Source NetworkFailureRetryAttempts Per-session override for NetworkFailureRetryAttempts. null uses the server default. Set to 0 in BeforeRequest for non-idempotent methods (POST, PATCH) to prevent unsafe retries. Declaration public int? NetworkFailureRetryAttempts { get; set; } Property Value Type Description int? | Edit this page View Source OriginHttpVersionPolicy Per-session override for OriginHttpVersionPolicy. null uses the server default (PreserveClientVersion unless the server property was changed). Set in BeforeRequest. Declaration public OriginHttpVersionPolicy? OriginHttpVersionPolicy { get; set; } Property Value Type Description OriginHttpVersionPolicy? | Edit this page View Source ReRequest Should we send the request again ? Declaration public bool ReRequest { get; set; } Property Value Type Description bool | Edit this page View Source RequestTimeout Per-session override for RequestTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? RequestTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source ResponseHeaderTimeout Per-session override for ResponseHeaderTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? ResponseHeaderTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source UpstreamHttpProtocol Per-request outbound protocol version policy. Overrides the connection-level UpstreamHttpProtocol value set during BeforeSslAuthenticate / BeforeQuicAuthenticate for this single request stream only. null uses the connection-level policy (or Auto if none was set). Evaluated in BeforeRequest; changes after that have no effect. On an H3 inbound connection (one client QUIC connection serving many concurrent streams), each stream resolves its outbound protocol independently after BeforeRequest fires, making per-stream protocol overrides possible even though the inbound leg is already QUIC. Declaration public UpstreamHttpProtocol? UpstreamHttpProtocol { get; set; } Property Value Type Description UpstreamHttpProtocol? | Edit this page View Source WebSocketClientWriter Inject frames toward the local client (server→client direction, unmasked). Available only while an intercepted WebSocket relay is active. Declaration public WebSocketFrameWriter? WebSocketClientWriter { get; } Property Value Type Description WebSocketFrameWriter | Edit this page View Source WebSocketDecoder Declaration [Obsolete(\"Use [WebSocketDecoderReceive] instead\")] public WebSocketDecoder WebSocketDecoder { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketDecoderReceive Declaration public WebSocketDecoder WebSocketDecoderReceive { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketDecoderSend Declaration public WebSocketDecoder WebSocketDecoderSend { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketServerWriter Inject frames toward the remote server (client→server direction, masked). Available only while an intercepted WebSocket relay is active. Declaration public WebSocketFrameWriter? WebSocketServerWriter { get; } Property Value Type Description WebSocketFrameWriter Methods | Edit this page View Source Dispose(bool) Declaration protected override void Dispose(bool disposing) Parameters Type Name Description bool disposing Overrides SessionEventArgsBase.Dispose(bool) | Edit this page View Source DrainClientBodyAsync(CancellationToken) Drains (reads and discards) any unread client request body from the underlying stream or connection so the client's keep-alive / multiplexed connection can be reused. This reads the bytes off the wire without buffering them in memory. It is a no-op if the body was already received or the request has no body. Declaration public Task DrainClientBodyAsync(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Returns Type Description Task Remarks Useful when short-circuiting a request (e.g. Respond(Response, bool), RespondStreaming(StreamingProxyResult, bool), or blocking) while the client is uploading a body: draining leaves the client connection in a reusable state. Note the proxy already drains the client body automatically on the normal synthetic-response path, so this is only needed for advanced/manual control. Warning: for an endless chunked request (one that never sends its terminating zero chunk) this will block until the passed cancellationToken is cancelled or the connection closes. | Edit this page View Source DrainServerBodyAsync(CancellationToken) Drains (reads and discards) any unread server response body from the underlying client/server stream or connection so it can be reused. This reads the bytes off the wire without buffering them in memory. It is a no-op if the body was already received or the response has no body. Declaration public Task DrainServerBodyAsync(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Returns Type Description Task Remarks Warning: for an endless chunked response (one that never sends its terminating zero chunk) this will block until the passed cancellationToken is cancelled or the connection closes. In that case prefer closing the connection (e.g. TerminateServerConnection()) instead. | Edit this page View Source GenericResponse(byte[], HttpStatusCode, IDictionary, bool) Before request is made to server respond with the specified byte[], the specified status to client. And then ignore the request. Declaration public void GenericResponse(byte[] result, HttpStatusCode status, IDictionary headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The bytes to sent. HttpStatusCode status The HTTP status code. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(byte[], HttpStatusCode, IEnumerable?, bool) Before request is made to server respond with the specified byte[], the specified status to client. And then ignore the request. Declaration public void GenericResponse(byte[] result, HttpStatusCode status, IEnumerable? headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The bytes to sent. HttpStatusCode status The HTTP status code. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(string, HttpStatusCode, IDictionary?, bool) Before request is made to server respond with the specified HTML string and the specified status to client. And then ignore the request. Declaration public void GenericResponse(string html, HttpStatusCode status, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description string html The html content. HttpStatusCode status The HTTP status code. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(string, HttpStatusCode, IEnumerable?, bool) Before request is made to server respond with the specified HTML string and the specified status to client. And then ignore the request. Declaration public void GenericResponse(string html, HttpStatusCode status, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description string html The html content. HttpStatusCode status The HTTP status code. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GetRequestBody(CancellationToken) Gets the request body as bytes. Declaration public Task GetRequestBody(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The body as bytes. | Edit this page View Source GetRequestBodyAsString(CancellationToken) Gets the request body as string. Declaration public Task GetRequestBodyAsString(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The body as string. | Edit this page View Source GetResponseBody(CancellationToken) Gets the response body as bytes. Declaration public Task GetResponseBody(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The resulting bytes. | Edit this page View Source GetResponseBodyAsString(CancellationToken) Gets the response body as string. Declaration public Task GetResponseBodyAsString(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The string body. | Edit this page View Source Ok(byte[], IDictionary?, bool) Before request is made to server respond with the specified byte[] to client and ignore the request. Declaration public void Ok(byte[] result, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The html content bytes. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(byte[], IEnumerable?, bool) Before request is made to server respond with the specified byte[] to client and ignore the request. Declaration public void Ok(byte[] result, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description byte[] result The html content bytes. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(string, IDictionary?, bool) Before request is made to server respond with the specified HTML string to client and ignore the request. Declaration public void Ok(string html, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description string html HTML content to sent. IDictionary headers HTTP response headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(string, IEnumerable?, bool) Before request is made to server respond with the specified HTML string to client and ignore the request. Declaration public void Ok(string html, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description string html HTML content to sent. IEnumerable headers HTTP response headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Redirect(string, bool) Redirect to provided URL. Declaration public void Redirect(string url, bool closeServerConnection = false) Parameters Type Name Description string url The URL to redirect. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Respond(Response, bool) Respond with given response object to client. Declaration public void Respond(Response response, bool closeServerConnection = false) Parameters Type Name Description Response response The response object. bool closeServerConnection Close the server connection used by request if any? Remarks If the server response was already received, the original server body (if any) is drained (syphoned) so the server connection stays reusable. To avoid reading a large or endless server body, pass closeServerConnection = true (or call TerminateServerConnection()), which closes the connection instead of draining. Note that an HTTP/1.1 connection cannot be both reused and have its body skipped. | Edit this page View Source RespondStreaming(Response, Func, bool) Respond to the client with a streamed body produced on the fly, without buffering the whole body in memory. Use this to serve large or endless bodies (e.g. a multi-gigabyte file or a synthetic server-sent-events stream) from scratch. Declaration public void RespondStreaming(Response response, Func writeBody, bool closeServerConnection = false) Parameters Type Name Description Response response The response object (status and headers). Func writeBody Delegate that writes the body to the provided stream. bool closeServerConnection Close the server connection used by request if any? Remarks Framing is chosen from the response headers: if a Content-Length is set on response the body is written raw (the delegate must write exactly that many bytes); otherwise HTTP/1.1 uses chunked transfer-encoding and HTTP/2/HTTP/3 emit DATA / stream frames. The delegate receives a write-only stream; only a single buffer is in flight at a time, so memory stays bounded regardless of the total size. See Respond(Response, bool) for the server body syphon-vs-close trade-off controlled by closeServerConnection. | Edit this page View Source RespondStreaming(StreamingProxyResult, bool) Respond to the client with a streamed body produced by Stream(HttpStatusCode, string, Func, long?) or File(string, string, HttpStatusCode). Declaration public void RespondStreaming(StreamingProxyResult result, bool closeServerConnection) Parameters Type Name Description StreamingProxyResult result Streaming response metadata and body writer. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source SetRequestBody(byte[]) Sets the request body. Declaration public void SetRequestBody(byte[] body) Parameters Type Name Description byte[] body The request body bytes. | Edit this page View Source SetRequestBodyString(string) Sets the body with the specified string. Declaration public void SetRequestBodyString(string body) Parameters Type Name Description string body The request body string to set. | Edit this page View Source SetResponseBody(byte[]) Set the response body bytes. Declaration public void SetResponseBody(byte[] body) Parameters Type Name Description byte[] body The body bytes to set. | Edit this page View Source SetResponseBodyString(string) Replace the response body with the specified string. Declaration public void SetResponseBodyString(string body) Parameters Type Name Description string body The body string to set. | Edit this page View Source TerminateServerConnection() Terminate the connection to server at the end of this HTTP request/response session. Declaration public void TerminateServerConnection() Events | Edit this page View Source BeforeWebSocketFrame Fired for each WebSocket frame after upgrade when at least one handler is subscribed. Handlers may Forward, Drop, or Replace the frame, optionally with a Delay. Observational DataSent / DataReceived still fire for bytes actually written to the peer. Declaration public event AsyncEventHandler? BeforeWebSocketFrame Event Type Type Description AsyncEventHandler | Edit this page View Source MultipartRequestPartSent Occurs when multipart request part sent. Declaration public event EventHandler? MultipartRequestPartSent Event Type Type Description EventHandler Implements IDisposable"
+ "summary": "Class SessionEventArgs Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. Inheritance object EventArgs ProxyEventArgsBase SessionEventArgsBase SessionEventArgs Implements IDisposable Inherited Members SessionEventArgsBase.BufferPool SessionEventArgsBase.ClientConnectionId SessionEventArgsBase.ServerConnectionId SessionEventArgsBase.Timing SessionEventArgsBase.UpstreamConnectionTiming SessionEventArgsBase.UserData SessionEventArgsBase.ConnectTimeout SessionEventArgsBase.EnableWinAuth SessionEventArgsBase.IsHttps SessionEventArgsBase.ClientLocalEndPoint SessionEventArgsBase.ClientRemoteEndPoint SessionEventArgsBase.ClientEndPoint SessionEventArgsBase.ServerRemoteEndPoint SessionEventArgsBase.ServerIpAddress SessionEventArgsBase.HttpClient SessionEventArgsBase.WebSession SessionEventArgsBase.CustomUpStreamProxy SessionEventArgsBase.CustomUpStreamProxyUsed SessionEventArgsBase.UpstreamDestinationId SessionEventArgsBase.ProxyEndPoint SessionEventArgsBase.LocalEndPoint SessionEventArgsBase.IsTransparent SessionEventArgsBase.IsSocks SessionEventArgsBase.Exception SessionEventArgsBase.Logger SessionEventArgsBase.Dispose() SessionEventArgsBase.OnException(Exception) SessionEventArgsBase.DataSent SessionEventArgsBase.DataReceived SessionEventArgsBase.TerminateSession() ProxyEventArgsBase.ClientUserData EventArgs.Empty object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public class SessionEventArgs : SessionEventArgsBase, IDisposable Properties | Edit this page View Source IdleReadTimeout Per-session override for IdleReadTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? IdleReadTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source IdleWriteTimeout Per-session override for IdleWriteTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? IdleWriteTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source IsPromise Is this session a HTTP/2 promise? Declaration public bool IsPromise { get; } Property Value Type Description bool | Edit this page View Source MaxBufferedBodyBytes Per-session override for MaxBufferedBodyBytes. null uses the server default. Set in BeforeRequest to increase the limit for large uploads/downloads without relaxing the global limit for all requests. Declaration public int? MaxBufferedBodyBytes { get; set; } Property Value Type Description int? | Edit this page View Source MaxWebSocketFramePayloadBytes Per-session override for MaxWebSocketFramePayloadBytes. null uses the server default. Set in BeforeRequest before the WebSocket upgrade completes. Declaration public int? MaxWebSocketFramePayloadBytes { get; set; } Property Value Type Description int? | Edit this page View Source NetworkFailureRetryAttempts Per-session override for NetworkFailureRetryAttempts. null uses the server default. Set to 0 in BeforeRequest for non-idempotent methods (POST, PATCH) to prevent unsafe retries. Declaration public int? NetworkFailureRetryAttempts { get; set; } Property Value Type Description int? | Edit this page View Source OriginHttpVersionPolicy Per-session override for OriginHttpVersionPolicy. null uses the server default (PreserveClientVersion unless the server property was changed). Set in BeforeRequest. Declaration public OriginHttpVersionPolicy? OriginHttpVersionPolicy { get; set; } Property Value Type Description OriginHttpVersionPolicy? | Edit this page View Source ReRequest Should we send the request again ? Declaration public bool ReRequest { get; set; } Property Value Type Description bool | Edit this page View Source RequestTimeout Per-session override for RequestTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? RequestTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source ResponseHeaderTimeout Per-session override for ResponseHeaderTimeoutSeconds. null uses the server default; Zero or negative disables. Declaration public TimeSpan? ResponseHeaderTimeout { get; set; } Property Value Type Description TimeSpan? | Edit this page View Source UpstreamHttpProtocol Per-request outbound protocol version policy. Overrides the connection-level UpstreamHttpProtocol value set during BeforeSslAuthenticate / BeforeQuicAuthenticate for this single request stream only. null uses the connection-level policy (or Auto if none was set). Evaluated in BeforeRequest; changes after that have no effect. On an H3 inbound connection (one client QUIC connection serving many concurrent streams), each stream resolves its outbound protocol independently after BeforeRequest fires, making per-stream protocol overrides possible even though the inbound leg is already QUIC. Declaration public UpstreamHttpProtocol? UpstreamHttpProtocol { get; set; } Property Value Type Description UpstreamHttpProtocol? | Edit this page View Source WebSocketClientWriter Inject frames toward the local client (server→client direction, unmasked). Available only while an intercepted WebSocket relay is active. Declaration public WebSocketFrameWriter? WebSocketClientWriter { get; } Property Value Type Description WebSocketFrameWriter | Edit this page View Source WebSocketDecoder Declaration [Obsolete(\"Use [WebSocketDecoderReceive] instead\")] public WebSocketDecoder WebSocketDecoder { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketDecoderReceive Declaration public WebSocketDecoder WebSocketDecoderReceive { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketDecoderSend Declaration public WebSocketDecoder WebSocketDecoderSend { get; } Property Value Type Description WebSocketDecoder | Edit this page View Source WebSocketServerWriter Inject frames toward the remote server (client→server direction, masked). Available only while an intercepted WebSocket relay is active. Declaration public WebSocketFrameWriter? WebSocketServerWriter { get; } Property Value Type Description WebSocketFrameWriter Methods | Edit this page View Source CanBufferBody(RequestResponseBase) Whether the proxy can buffer message without exceeding the effective MaxBufferedBodyBytes budget. Unknown-length bodies (chunked, HTTP/2 or HTTP/3 without Content-Length, server-sent events) return false: they must be relayed, not accumulated. A non-positive budget means unbounded and returns true. WebSocket upgrades are never buffered. Declaration public bool CanBufferBody(RequestResponseBase message) Parameters Type Name Description RequestResponseBase message Returns Type Description bool | Edit this page View Source Dispose(bool) Declaration protected override void Dispose(bool disposing) Parameters Type Name Description bool disposing Overrides SessionEventArgsBase.Dispose(bool) | Edit this page View Source DrainClientBodyAsync(CancellationToken) Drains (reads and discards) any unread client request body from the underlying stream or connection so the client's keep-alive / multiplexed connection can be reused. This reads the bytes off the wire without buffering them in memory. It is a no-op if the body was already received or the request has no body. Declaration public Task DrainClientBodyAsync(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Returns Type Description Task Remarks Useful when short-circuiting a request (e.g. Respond(Response, bool), RespondStreaming(StreamingProxyResult, bool), or blocking) while the client is uploading a body: draining leaves the client connection in a reusable state. Note the proxy already drains the client body automatically on the normal synthetic-response path, so this is only needed for advanced/manual control. Warning: for an endless chunked request (one that never sends its terminating zero chunk) this will block until the passed cancellationToken is cancelled or the connection closes. | Edit this page View Source DrainServerBodyAsync(CancellationToken) Drains (reads and discards) any unread server response body from the underlying client/server stream or connection so it can be reused. This reads the bytes off the wire without buffering them in memory. It is a no-op if the body was already received or the response has no body. Declaration public Task DrainServerBodyAsync(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Returns Type Description Task Remarks Warning: for an endless chunked response (one that never sends its terminating zero chunk) this will block until the passed cancellationToken is cancelled or the connection closes. In that case prefer closing the connection (e.g. TerminateServerConnection()) instead. | Edit this page View Source GenericResponse(byte[], HttpStatusCode, IDictionary, bool) Before request is made to server respond with the specified byte[], the specified status to client. And then ignore the request. Declaration public void GenericResponse(byte[] result, HttpStatusCode status, IDictionary headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The bytes to sent. HttpStatusCode status The HTTP status code. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(byte[], HttpStatusCode, IEnumerable?, bool) Before request is made to server respond with the specified byte[], the specified status to client. And then ignore the request. Declaration public void GenericResponse(byte[] result, HttpStatusCode status, IEnumerable? headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The bytes to sent. HttpStatusCode status The HTTP status code. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(string, HttpStatusCode, IDictionary?, bool) Before request is made to server respond with the specified HTML string and the specified status to client. And then ignore the request. Declaration public void GenericResponse(string html, HttpStatusCode status, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description string html The html content. HttpStatusCode status The HTTP status code. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GenericResponse(string, HttpStatusCode, IEnumerable?, bool) Before request is made to server respond with the specified HTML string and the specified status to client. And then ignore the request. Declaration public void GenericResponse(string html, HttpStatusCode status, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description string html The html content. HttpStatusCode status The HTTP status code. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source GetRequestBody(CancellationToken) Gets the request body as bytes. Declaration public Task GetRequestBody(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The body as bytes. | Edit this page View Source GetRequestBodyAsString(CancellationToken) Gets the request body as string. Declaration public Task GetRequestBodyAsString(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The body as string. | Edit this page View Source GetResponseBody(CancellationToken) Gets the response body as bytes. Declaration public Task GetResponseBody(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The resulting bytes. | Edit this page View Source GetResponseBodyAsString(CancellationToken) Gets the response body as string. Declaration public Task GetResponseBodyAsString(CancellationToken cancellationToken = default) Parameters Type Name Description CancellationToken cancellationToken Optional cancellation token for this async task. Returns Type Description Task The string body. | Edit this page View Source Ok(byte[], IDictionary?, bool) Before request is made to server respond with the specified byte[] to client and ignore the request. Declaration public void Ok(byte[] result, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description byte[] result The html content bytes. IDictionary headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(byte[], IEnumerable?, bool) Before request is made to server respond with the specified byte[] to client and ignore the request. Declaration public void Ok(byte[] result, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description byte[] result The html content bytes. IEnumerable headers The HTTP headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(string, IDictionary?, bool) Before request is made to server respond with the specified HTML string to client and ignore the request. Declaration public void Ok(string html, IDictionary? headers, bool closeServerConnection = false) Parameters Type Name Description string html HTML content to sent. IDictionary headers HTTP response headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Ok(string, IEnumerable?, bool) Before request is made to server respond with the specified HTML string to client and ignore the request. Declaration public void Ok(string html, IEnumerable? headers = null, bool closeServerConnection = false) Parameters Type Name Description string html HTML content to sent. IEnumerable headers HTTP response headers. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Redirect(string, bool) Redirect to provided URL. Declaration public void Redirect(string url, bool closeServerConnection = false) Parameters Type Name Description string url The URL to redirect. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source Respond(Response, bool) Respond with given response object to client. Declaration public void Respond(Response response, bool closeServerConnection = false) Parameters Type Name Description Response response The response object. bool closeServerConnection Close the server connection used by request if any? Remarks If the server response was already received, the original server body (if any) is drained (syphoned) so the server connection stays reusable. To avoid reading a large or endless server body, pass closeServerConnection = true (or call TerminateServerConnection()), which closes the connection instead of draining. Note that an HTTP/1.1 connection cannot be both reused and have its body skipped. | Edit this page View Source RespondStreaming(Response, Func, bool) Respond to the client with a streamed body produced on the fly, without buffering the whole body in memory. Use this to serve large or endless bodies (e.g. a multi-gigabyte file or a synthetic server-sent-events stream) from scratch. Declaration public void RespondStreaming(Response response, Func writeBody, bool closeServerConnection = false) Parameters Type Name Description Response response The response object (status and headers). Func writeBody Delegate that writes the body to the provided stream. bool closeServerConnection Close the server connection used by request if any? Remarks Framing is chosen from the response headers: if a Content-Length is set on response the body is written raw (the delegate must write exactly that many bytes); otherwise HTTP/1.1 uses chunked transfer-encoding and HTTP/2/HTTP/3 emit DATA / stream frames. The delegate receives a write-only stream; only a single buffer is in flight at a time, so memory stays bounded regardless of the total size. See Respond(Response, bool) for the server body syphon-vs-close trade-off controlled by closeServerConnection. | Edit this page View Source RespondStreaming(StreamingProxyResult, bool) Respond to the client with a streamed body produced by Stream(HttpStatusCode, string, Func, long?) or File(string, string, HttpStatusCode). Declaration public void RespondStreaming(StreamingProxyResult result, bool closeServerConnection) Parameters Type Name Description StreamingProxyResult result Streaming response metadata and body writer. bool closeServerConnection Close the server connection used by request if any? | Edit this page View Source SetRequestBody(byte[]) Sets the request body. Declaration public void SetRequestBody(byte[] body) Parameters Type Name Description byte[] body The request body bytes. | Edit this page View Source SetRequestBodyString(string) Sets the body with the specified string. Declaration public void SetRequestBodyString(string body) Parameters Type Name Description string body The request body string to set. | Edit this page View Source SetResponseBody(byte[]) Set the response body bytes. Declaration public void SetResponseBody(byte[] body) Parameters Type Name Description byte[] body The body bytes to set. | Edit this page View Source SetResponseBodyString(string) Replace the response body with the specified string. Declaration public void SetResponseBodyString(string body) Parameters Type Name Description string body The body string to set. | Edit this page View Source TerminateServerConnection() Terminate the connection to server at the end of this HTTP request/response session. Declaration public void TerminateServerConnection() Events | Edit this page View Source BeforeWebSocketFrame Fired for each WebSocket frame after upgrade when at least one handler is subscribed. Handlers may Forward, Drop, or Replace the frame, optionally with a Delay. Observational DataSent / DataReceived still fire for bytes actually written to the peer. Declaration public event AsyncEventHandler? BeforeWebSocketFrame Event Type Type Description AsyncEventHandler | Edit this page View Source MultipartRequestPartSent Occurs when multipart request part sent. Declaration public event EventHandler? MultipartRequestPartSent Event Type Type Description EventHandler | Edit this page View Source WebSocketFrameElided Fired when WebSocketFrameBudget is Observe and a frame exceeds MaxWebSocketFramePayloadBytes. The payload is relayed unbuffered and is not passed to BeforeWebSocketFrame. Declaration public event AsyncEventHandler? WebSocketFrameElided Event Type Type Description AsyncEventHandler Implements IDisposable"
},
"api/Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html": {
"href": "api/Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html",
@@ -144,6 +144,11 @@
"title": "Enum WebSocketFrameDirection | Titanium Web Proxy",
"summary": "Enum WebSocketFrameDirection Direction of a WebSocket frame relative to the proxy. Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public enum WebSocketFrameDirection Fields Name Description ClientToServer Client → server (must be masked on the wire). ServerToClient Server → client (must not be masked on the wire)."
},
+ "api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html": {
+ "href": "api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html",
+ "title": "Class WebSocketFrameElidedEventArgs | Titanium Web Proxy",
+ "summary": "Class WebSocketFrameElidedEventArgs Metadata for a WebSocket frame that exceeded the payload limit under Observe. The payload itself is not included. Inheritance object EventArgs ProxyEventArgsBase WebSocketFrameElidedEventArgs Inherited Members ProxyEventArgsBase.ClientUserData EventArgs.Empty object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.EventArguments Assembly: Titanium.Web.Proxy.dll Syntax public sealed class WebSocketFrameElidedEventArgs : ProxyEventArgsBase Properties | Edit this page View Source DeclaredPayloadLength Declaration public long DeclaredPayloadLength { get; } Property Value Type Description long | Edit this page View Source Direction Declaration public WebSocketFrameDirection Direction { get; } Property Value Type Description WebSocketFrameDirection | Edit this page View Source IsFinal Declaration public bool IsFinal { get; } Property Value Type Description bool | Edit this page View Source OpCode Declaration public WebsocketOpCode OpCode { get; } Property Value Type Description WebsocketOpCode | Edit this page View Source Session Declaration public SessionEventArgs Session { get; } Property Value Type Description SessionEventArgs"
+ },
"api/Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html": {
"href": "api/Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html",
"title": "Enum WebSocketFrameInterceptAction | Titanium Web Proxy",
@@ -157,7 +162,7 @@
"api/Titanium.Web.Proxy.EventArguments.html": {
"href": "api/Titanium.Web.Proxy.EventArguments.html",
"title": "Namespace Titanium.Web.Proxy.EventArguments | Titanium Web Proxy",
- "summary": "Namespace Titanium.Web.Proxy.EventArguments Classes BeforeBodyWriteEventArgs BeforeHttpAuthenticateEventArgs Fired on a transparent reverse endpoint before handling a cleartext (non-TLS) client session, including prior-knowledge HTTP/2 (h2c). Mirrors the policy knobs on BeforeSslAuthenticateEventArgs without a TLS handshake. BeforeQuicAuthenticateEventArgs Event arguments raised on a TransparentQuicProxyEndPoint before the QUIC TLS handshake completes, analogous to BeforeSslAuthenticateEventArgs for transparent TCP endpoints. Unlike its TCP counterpart, this event does not expose a DecryptSsl property: QUIC always terminates TLS 1.3 at the proxy — there is no pass-through mode. Calling Reject() is the only way to refuse the connection before the handshake finishes. Thread-safety: each inbound QUIC connection fires a dedicated instance of this event on its own accept task. Handlers that mutate shared state must synchronize themselves. Experimental: HTTP/3 support has not yet completed the full interop/soak/fuzz gate process. Suppress TWP001 to opt in. BeforeSslAuthenticateEventArgs This is used in transparent endpoint before authenticating client. CertificateSelectionEventArgs An argument passed on to user for client certificate selection during mutual SSL authentication. CertificateValidationEventArgs An argument passed on to the user for validating the server certificate during SSL authentication. MultipartRequestPartSentEventArgs Class that wraps the multipart sent request arguments. ProxyEventArgsBase The base event arguments SessionEventArgs Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. SessionEventArgsBase Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. SocksAuthenticateEventArgs Context for SOCKS5 username/password authentication on a SocksProxyEndPoint. TunnelConnectFailureEventArgs Raised when optional pre-200 upstream connectivity verification fails for a CONNECT tunnel (EstablishServerConnectionBeforeResponse). Handlers may replace Response with a custom HTTP error before it is written to the client (no TLS has been started yet). TunnelConnectSessionEventArgs A class that wraps the state when a tunnel connect event happen for Explicit endpoints. WebSocketFrameInterceptEventArgs Frame-level interception context for an active WebSocket session. Enums WebSocketFrameDirection Direction of a WebSocket frame relative to the proxy. WebSocketFrameInterceptAction Action taken by a BeforeWebSocketFrame handler. Delegates AsyncEventHandler A generic asynchronous event handler used by the proxy."
+ "summary": "Namespace Titanium.Web.Proxy.EventArguments Classes BeforeBodyWriteEventArgs BeforeHttpAuthenticateEventArgs Fired on a transparent reverse endpoint before handling a cleartext (non-TLS) client session, including prior-knowledge HTTP/2 (h2c). Mirrors the policy knobs on BeforeSslAuthenticateEventArgs without a TLS handshake. BeforeQuicAuthenticateEventArgs Event arguments raised on a TransparentQuicProxyEndPoint before the QUIC TLS handshake completes, analogous to BeforeSslAuthenticateEventArgs for transparent TCP endpoints. Unlike its TCP counterpart, this event does not expose a DecryptSsl property: QUIC always terminates TLS 1.3 at the proxy — there is no pass-through mode. Calling Reject() is the only way to refuse the connection before the handshake finishes. Thread-safety: each inbound QUIC connection fires a dedicated instance of this event on its own accept task. Handlers that mutate shared state must synchronize themselves. Experimental: HTTP/3 support has not yet completed the full interop/soak/fuzz gate process. Suppress TWP001 to opt in. BeforeSslAuthenticateEventArgs This is used in transparent endpoint before authenticating client. CertificateSelectionEventArgs An argument passed on to user for client certificate selection during mutual SSL authentication. CertificateValidationEventArgs An argument passed on to the user for validating the server certificate during SSL authentication. MultipartRequestPartSentEventArgs Class that wraps the multipart sent request arguments. ProxyEventArgsBase The base event arguments SessionEventArgs Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. SessionEventArgsBase Holds info related to a single proxy session (single request/response exchange). Under HTTP/2 and HTTP/3, many sessions share one client connection (one stream each); ending a session ends that request/response exchange, not necessarily the connection. SocksAuthenticateEventArgs Context for SOCKS5 username/password authentication on a SocksProxyEndPoint. TunnelConnectFailureEventArgs Raised when optional pre-200 upstream connectivity verification fails for a CONNECT tunnel (EstablishServerConnectionBeforeResponse). Handlers may replace Response with a custom HTTP error before it is written to the client (no TLS has been started yet). TunnelConnectSessionEventArgs A class that wraps the state when a tunnel connect event happen for Explicit endpoints. WebSocketFrameElidedEventArgs Metadata for a WebSocket frame that exceeded the payload limit under Observe. The payload itself is not included. WebSocketFrameInterceptEventArgs Frame-level interception context for an active WebSocket session. Enums WebSocketFrameDirection Direction of a WebSocket frame relative to the proxy. WebSocketFrameInterceptAction Action taken by a BeforeWebSocketFrame handler. Delegates AsyncEventHandler A generic asynchronous event handler used by the proxy."
},
"api/Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html": {
"href": "api/Titanium.Web.Proxy.Exceptions.BodyNotFoundException.html",
@@ -502,7 +507,7 @@
"api/Titanium.Web.Proxy.Options.PolicyFamily.html": {
"href": "api/Titanium.Web.Proxy.Options.PolicyFamily.html",
"title": "Enum PolicyFamily | Titanium Web Proxy",
- "summary": "Enum PolicyFamily The resource-bound policy families that support an PolicyMode other than Enforce, per the plan's rollout section. Framing, chunk parsing and Content-Length/Transfer-Encoding resolution are deliberately not members of this enum: they are always enforced and never consult a mode, because there is no safe Observe action for an ambiguous or malformed message - see ProxyPolicyModes and AllowAmbiguousFraming for the one explicit, isolated escape hatch from that rule. Namespace: Titanium.Web.Proxy.Options Assembly: Titanium.Web.Proxy.dll Syntax public enum PolicyFamily Fields Name Description AdmissionControl The global and per-endpoint admission gates that bound concurrently admitted client connections. BodyBudget Cumulative whole-body buffering limits (MaxBufferedBodyBytes and the MaxEncodedBodyBytes/MaxDecodedBodyBytes pair) enforced via Titanium.Web.Proxy.Network.Streams.BoundedWriteStream across H1, H2 and H3. DecompressionRatio The compressed-input/decompressed-output byte budgets and the expansion-ratio ceiling (MaxDecompressionRatio) applied while draining a Content-Encoding chain, so a small compressed body cannot expand unboundedly in memory before BodyBudget's own decoded-byte cap would catch it. Today, that decoded-byte cap is exactly what protects this case in practice: the decompression chain writes into the same Titanium.Web.Proxy.Network.Streams.BoundedWriteStream- wrapped target BodyBudget already bounds, so a small compressed body that expands enormously is caught the moment the decoded output crosses that limit, without needing a separately computed ratio. MaxDecompressionRatio and the encoded/decoded byte pair remain reserved for a future, more precise per-stream computation; this family's mode exists now so a profile can name it, but changing it has no additional effect while BodyBudget already covers the same paths. HeaderLimits Header line length, header count and aggregate header-byte limits (MaxHeaderLineBytes/MaxHeaderCount/ MaxHeaderAggregateBytes) intended for the request/response header-block read. Reserved, like DecompressionRatio, for numeric enforcement not yet wired to every header-reading call site; this family's mode exists so a profile can name it ahead of that work landing. The client request-line/header deadline (a different, already-enforced protection - see ProxyServer.ClientHeaderTimeoutSeconds and DeadlineRegistry) is unaffected by this family's mode. Http1ReplaySafety Whether HTTP/1 keep-alive retries (EOF-before-response, malformed status, mid-exchange IOException) and bare CR in header values follow the strict replay-safe rules. Observe (default on Balanced and LegacyCompatible) keeps today's lenient behavior and records a policy breach metric when the strict rule would have fired. Enforce (default on PublicFacing) refuses to replay non-idempotent methods and rejects bare CR/LF/NUL in header values. Http2AbuseBudget HTTP/2 abuse budgets: the open-header-block CONTINUATION frame-count/wall-clock bound and the peer-initiated incomplete-stream-reset budget. Http2RelayValidation Whether HPACK header blocks on the H2↔H2 compressed-relay path (httpInterceptionEnabled = false) are semantically validated per RFC 9113 §8.3. Unlike framing (always enforced, no Observe action), header semantics can be logged without corrupting connection state. Disabled (default on Balanced) skips HPACK decode entirely — maximum throughput when upstream peers are trusted. Observe decodes and records semantic violations without rejecting the stream. Does not mutate headers, so the compressed-relay MutationCount fast path is unaffected when the family is Disabled. Enforce (default on PublicFacing and AllEnforce) decodes and sends GOAWAY(PROTOCOL_ERROR) on violations."
+ "summary": "Enum PolicyFamily The resource-bound policy families that support an PolicyMode other than Enforce, per the plan's rollout section. Framing, chunk parsing and Content-Length/Transfer-Encoding resolution are deliberately not members of this enum: they are always enforced and never consult a mode, because there is no safe Observe action for an ambiguous or malformed message - see ProxyPolicyModes and AllowAmbiguousFraming for the one explicit, isolated escape hatch from that rule. Namespace: Titanium.Web.Proxy.Options Assembly: Titanium.Web.Proxy.dll Syntax public enum PolicyFamily Fields Name Description AdmissionControl The global and per-endpoint admission gates that bound concurrently admitted client connections. BodyBudget Cumulative whole-body buffering limits (MaxBufferedBodyBytes) enforced via Titanium.Web.Proxy.Network.Streams.BoundedWriteStream when a caller materializes a body. Streaming relays do not consult this family. MaxEncodedBodyBytes and MaxDecodedBodyBytes are reserved. They are accepted by option plumbing and are not enforced on a live read path; the enforced whole-body budget is MaxBufferedBodyBytes. DecompressionRatio The compressed-input/decompressed-output byte budgets and the expansion-ratio ceiling (MaxDecompressionRatio) applied while draining a Content-Encoding chain, so a small compressed body cannot expand unboundedly in memory before BodyBudget's own decoded-byte cap would catch it. Today, that decoded-byte cap is exactly what protects this case in practice: the decompression chain writes into the same Titanium.Web.Proxy.Network.Streams.BoundedWriteStream- wrapped target BodyBudget already bounds, so a small compressed body that expands enormously is caught the moment the decoded output crosses that limit, without needing a separately computed ratio. MaxDecompressionRatio and the encoded/decoded byte pair remain reserved for a future, more precise per-stream computation; this family's mode exists now so a profile can name it, but changing it has no additional effect while BodyBudget already covers the same paths. HeaderLimits Header line length, header count and aggregate header-byte limits (MaxHeaderLineBytes/MaxHeaderCount/ MaxHeaderAggregateBytes) intended for the request/response header-block read. Reserved, like DecompressionRatio, for numeric enforcement not yet wired to every header-reading call site; this family's mode exists so a profile can name it ahead of that work landing. The client request-line/header deadline (a different, already-enforced protection - see ProxyServer.ClientHeaderTimeoutSeconds and DeadlineRegistry) is unaffected by this family's mode. Http1ReplaySafety Whether HTTP/1 keep-alive retries (EOF-before-response, malformed status, mid-exchange IOException) and bare CR in header values follow the strict replay-safe rules. Observe (default on Balanced and LegacyCompatible) keeps today's lenient behavior and records a policy breach metric when the strict rule would have fired. Enforce (default on PublicFacing) refuses to replay non-idempotent methods and rejects bare CR/LF/NUL in header values. Http2AbuseBudget HTTP/2 abuse budgets: the open-header-block CONTINUATION frame-count/wall-clock bound and the peer-initiated incomplete-stream-reset budget. Http2RelayValidation Whether HPACK header blocks on the H2↔H2 compressed-relay path (httpInterceptionEnabled = false) are semantically validated per RFC 9113 §8.3. Unlike framing (always enforced, no Observe action), header semantics can be logged without corrupting connection state. Disabled (default on Balanced) skips HPACK decode entirely — maximum throughput when upstream peers are trusted. Observe decodes and records semantic violations without rejecting the stream. Does not mutate headers, so the compressed-relay MutationCount fast path is unaffected when the family is Disabled. Enforce (default on PublicFacing and AllEnforce) decodes and sends GOAWAY(PROTOCOL_ERROR) on violations. WebSocketFrameBudget Per-frame WebSocket payload limit (MaxWebSocketFramePayloadBytes) applied only when BeforeWebSocketFrame is subscribed. Raw relay is unaffected. Enforce (default on every profile) closes the connection with status 1009 before the payload is buffered. Observe relays an oversize frame without materializing it and raises SessionEventArgs.WebSocketFrameElided. Frame hooks are not invoked for that payload, so a content filter must stay on Enforce. Disabled skips the check and will buffer the frame, up to the decoder's structural MaxValue bound."
},
"api/Titanium.Web.Proxy.Options.PolicyMode.html": {
"href": "api/Titanium.Web.Proxy.Options.PolicyMode.html",
@@ -512,7 +517,7 @@
"api/Titanium.Web.Proxy.Options.ProxyPolicyModes.html": {
"href": "api/Titanium.Web.Proxy.Options.ProxyPolicyModes.html",
"title": "Class ProxyPolicyModes | Titanium Web Proxy",
- "summary": "Class ProxyPolicyModes Immutable snapshot of the PolicyMode selected for each PolicyFamily, plus the one deliberately-separate AllowAmbiguousFraming escape hatch. Read live by enforcement call sites through PolicyModes, which the plan's rollout section requires to be a runtime switch: replacing the whole snapshot (see PolicyModes's setter) rather than mutating a field lets an operator drop every family to Observe without redeploying, while every in-flight request that already read the previous snapshot keeps behaving consistently with whichever snapshot it observed. AllowAmbiguousFraming is not a PolicyFamily member and has no corresponding PolicyMode: framing, chunk parsing and Content-Length/Transfer-Encoding resolution have no safe \"detect but let it through\" middle ground, so this is a single named, binary, off-by-default flag that relays malformed framing instead of rejecting it - useful only for security research that needs to observe how a client or origin reacts to smuggling-shaped input through the proxy. No profile sets it: Create(PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode) never accepts it as a parameter, and the only way to turn it on is the explicit WithAllowAmbiguousFramingEnabled() call, so enabling it can never be a side effect of selecting a profile. Inheritance object ProxyPolicyModes Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.Options Assembly: Titanium.Web.Proxy.dll Syntax public sealed class ProxyPolicyModes Properties | Edit this page View Source AllEnforce Every family enforced - today's shipped behavior, and the starting point every profile builds from. Declaration public static ProxyPolicyModes AllEnforce { get; } Property Value Type Description ProxyPolicyModes | Edit this page View Source AllowAmbiguousFraming Off by default and absent from every profile - see the type-level remarks. Never true unless WithAllowAmbiguousFramingEnabled() was called explicitly. Declaration public bool AllowAmbiguousFraming { get; } Property Value Type Description bool | Edit this page View Source this[PolicyFamily] Returns the mode selected for family. Declaration public PolicyMode this[PolicyFamily family] { get; } Parameters Type Name Description PolicyFamily family Property Value Type Description PolicyMode Methods | Edit this page View Source Create(PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode) Builds a snapshot with an explicit mode for every family. AllowAmbiguousFraming starts false. Declaration public static ProxyPolicyModes Create(PolicyMode bodyBudget, PolicyMode decompressionRatio, PolicyMode headerLimits, PolicyMode admissionControl, PolicyMode http2AbuseBudget, PolicyMode http2RelayValidation = PolicyMode.Disabled, PolicyMode http1ReplaySafety = PolicyMode.Observe) Parameters Type Name Description PolicyMode bodyBudget PolicyMode decompressionRatio PolicyMode headerLimits PolicyMode admissionControl PolicyMode http2AbuseBudget PolicyMode http2RelayValidation PolicyMode http1ReplaySafety Returns Type Description ProxyPolicyModes | Edit this page View Source With(PolicyFamily, PolicyMode) Returns a snapshot identical to this one but with mode for family. Declaration public ProxyPolicyModes With(PolicyFamily family, PolicyMode mode) Parameters Type Name Description PolicyFamily family PolicyMode mode Returns Type Description ProxyPolicyModes | Edit this page View Source WithAllObservedExceptDisabled() Returns a snapshot identical to this one but with every family dropped to Observe, except families already Disabled (which stay disabled - Observe would silently turn a family back on). This is the \"drop to Observe without redeploying\" runtime switch the plan's rollout section requires. Declaration public ProxyPolicyModes WithAllObservedExceptDisabled() Returns Type Description ProxyPolicyModes | Edit this page View Source WithAllowAmbiguousFramingEnabled() The single, explicit, isolated act of relaying ambiguous HTTP/1 framing instead of rejecting it. See the type-level remarks; never call this as a side effect of applying a profile. Declaration public ProxyPolicyModes WithAllowAmbiguousFramingEnabled() Returns Type Description ProxyPolicyModes"
+ "summary": "Class ProxyPolicyModes Immutable snapshot of the PolicyMode selected for each PolicyFamily, plus the one deliberately-separate AllowAmbiguousFraming escape hatch. Read live by enforcement call sites through PolicyModes, which the plan's rollout section requires to be a runtime switch: replacing the whole snapshot (see PolicyModes's setter) rather than mutating a field lets an operator drop every family to Observe without redeploying, while every in-flight request that already read the previous snapshot keeps behaving consistently with whichever snapshot it observed. AllowAmbiguousFraming is not a PolicyFamily member and has no corresponding PolicyMode: framing, chunk parsing and Content-Length/Transfer-Encoding resolution have no safe \"detect but let it through\" middle ground, so this is a single named, binary, off-by-default flag that relays malformed framing instead of rejecting it - useful only for security research that needs to observe how a client or origin reacts to smuggling-shaped input through the proxy. No profile sets it: Create(PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode) never accepts it as a parameter, and the only way to turn it on is the explicit WithAllowAmbiguousFramingEnabled() call, so enabling it can never be a side effect of selecting a profile. Inheritance object ProxyPolicyModes Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.Options Assembly: Titanium.Web.Proxy.dll Syntax public sealed class ProxyPolicyModes Properties | Edit this page View Source AllEnforce Every family enforced - today's shipped behavior, and the starting point every profile builds from. Declaration public static ProxyPolicyModes AllEnforce { get; } Property Value Type Description ProxyPolicyModes | Edit this page View Source AllowAmbiguousFraming Off by default and absent from every profile - see the type-level remarks. Never true unless WithAllowAmbiguousFramingEnabled() was called explicitly. Declaration public bool AllowAmbiguousFraming { get; } Property Value Type Description bool | Edit this page View Source this[PolicyFamily] Returns the mode selected for family. Declaration public PolicyMode this[PolicyFamily family] { get; } Parameters Type Name Description PolicyFamily family Property Value Type Description PolicyMode Methods | Edit this page View Source Create(PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode, PolicyMode) Builds a snapshot with an explicit mode for the first seven families. WebSocketFrameBudget starts at Enforce (use With(PolicyFamily, PolicyMode) to change it) so this shipped signature stays binary-compatible. AllowAmbiguousFraming starts false. Declaration public static ProxyPolicyModes Create(PolicyMode bodyBudget, PolicyMode decompressionRatio, PolicyMode headerLimits, PolicyMode admissionControl, PolicyMode http2AbuseBudget, PolicyMode http2RelayValidation = PolicyMode.Disabled, PolicyMode http1ReplaySafety = PolicyMode.Observe) Parameters Type Name Description PolicyMode bodyBudget PolicyMode decompressionRatio PolicyMode headerLimits PolicyMode admissionControl PolicyMode http2AbuseBudget PolicyMode http2RelayValidation PolicyMode http1ReplaySafety Returns Type Description ProxyPolicyModes | Edit this page View Source With(PolicyFamily, PolicyMode) Returns a snapshot identical to this one but with mode for family. Declaration public ProxyPolicyModes With(PolicyFamily family, PolicyMode mode) Parameters Type Name Description PolicyFamily family PolicyMode mode Returns Type Description ProxyPolicyModes | Edit this page View Source WithAllObservedExceptDisabled() Returns a snapshot identical to this one but with every family dropped to Observe, except families already Disabled (which stay disabled - Observe would silently turn a family back on). This is the \"drop to Observe without redeploying\" runtime switch the plan's rollout section requires. Declaration public ProxyPolicyModes WithAllObservedExceptDisabled() Returns Type Description ProxyPolicyModes | Edit this page View Source WithAllowAmbiguousFramingEnabled() The single, explicit, isolated act of relaying ambiguous HTTP/1 framing instead of rejecting it. See the type-level remarks; never call this as a side effect of applying a profile. Declaration public ProxyPolicyModes WithAllowAmbiguousFramingEnabled() Returns Type Description ProxyPolicyModes"
},
"api/Titanium.Web.Proxy.Options.ProxyProfile.html": {
"href": "api/Titanium.Web.Proxy.Options.ProxyProfile.html",
@@ -547,12 +552,12 @@
"api/Titanium.Web.Proxy.ProxyLimits.html": {
"href": "api/Titanium.Web.Proxy.ProxyLimits.html",
"title": "Class ProxyLimits | Titanium Web Proxy",
- "summary": "Class ProxyLimits Reference values for a handful of resource-limit defaults, kept here for documentation and cross-checking purposes. Inheritance object ProxyLimits Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public static class ProxyLimits Remarks These fields are static readonly, not const, so that if a later release changes one of these reference values, a consumer that has not recompiled still picks up the new value from the referenced Titanium.Web.Proxy.dll at load time rather than keeping a stale value inlined by the compiler into their own assembly. These fields cannot be \"overridden by assigning them before starting the proxy\": they are readonly, so external code cannot assign to them at all (that would be a compile error). Only DefaultMaxChunkSizeBytes is actually read by any live code path (by ChunkSizeParser.TryParse's call sites in HttpStream/LimitedStream); every other field below documents a value that is independently hardcoded as the real default on the corresponding ProxyServer (or, for authentication rounds, WinAuthHandler) property, and is not consulted by that property's own default or by any enforcement path. Treat the fields below other than DefaultMaxChunkSizeBytes as documentation of what each corresponding property's shipped default currently is, not as the mechanism that produces it: to change a limit at runtime, set the actual ProxyServer property (e.g. ProxyServer.MaxBufferedBodyBytes), never the field here. Fields | Edit this page View Source DefaultMaxAuthRounds Reference value only - see the type-level remarks. Matches WinAuthHandler.MaxAuthChallengeRounds: maximum number of authentication challenge rounds allowed per request (e.g. NTLM three-way handshake counts as one round; additional 401/407 responses count separately). The two values are independently maintained, not linked. Default: 3. Declaration public static readonly int DefaultMaxAuthRounds Field Value Type Description int | Edit this page View Source DefaultMaxBufferedBodyBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxBufferedBodyBytes: maximum buffered request or response body size for proxied exchanges where full buffering is required (body mutation, authentication retry, etc.). Default: 4 MiB. Declaration public static readonly long DefaultMaxBufferedBodyBytes Field Value Type Description long | Edit this page View Source DefaultMaxChunkSizeBytes Maximum accepted value of a single HTTP/1 chunk-size line (RFC 9112 §7.1), in bytes. The chunk-size grammar itself (1*HEXDIG) has no length ceiling, so this is a proxy-owned safety bound rather than a protocol requirement: it exists to reject the two's-complement chunk- size wrap (an attacker-supplied value like \"ffffffff\" must not decode to a small/negative sentinel) and to avoid ever attempting to allocate or forward a chunk of unbounded size. Set high enough that no legitimate chunk from real-world traffic should reach it. Unlike every other field on this type, this one is actually read live, by every ChunkSizeParser.TryParse call site in HttpStream and LimitedStream. Default: 1 GiB. Declaration public static readonly long DefaultMaxChunkSizeBytes Field Value Type Description long | Edit this page View Source DefaultMaxDecodedHeaderListBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxDecodedHeaderListBytes: maximum total decoded HTTP/2 header list size (sum of name + value + 32 per field). Streams with header lists exceeding that property's configured value are rejected with RST_STREAM(ENHANCE_YOUR_CALM). Default: 64 KiB. Declaration public static readonly int DefaultMaxDecodedHeaderListBytes Field Value Type Description int | Edit this page View Source DefaultMaxWebSocketFramePayloadBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxWebSocketFramePayloadBytes: maximum WebSocket frame payload size the proxy will accept during frame-level interception (validated against the declared length before any payload is buffered). Raw relay (no interception) is not subject to this limit. Default: 16 MiB. Declaration public static readonly int DefaultMaxWebSocketFramePayloadBytes Field Value Type Description int | Edit this page View Source DefaultMaxWebSocketMessageBytes Reference value only - see the type-level remarks. Unlike the other fields on this type, this one does not correspond to any enforced ProxyServer property today: there is no cumulative-reassembled-message-size cap on the WebSocket path, only the per-frame DefaultMaxWebSocketFramePayloadBytes/MaxWebSocketFramePayloadBytes limit. This value is retained as a documented target for that still-unimplemented cumulative budget. Default: 64 MiB. Declaration public static readonly long DefaultMaxWebSocketMessageBytes Field Value Type Description long | Edit this page View Source DefaultViaPseudonym Unused legacy field. ProxyServer.ViaHeaderPseudonym defaults to \"titanium-web-proxy\" (not empty) and does not read this field; nothing in the codebase references it. Retained only so removing it is not itself an undocumented breaking change; do not treat it as authoritative for the real default. Declaration public static readonly string DefaultViaPseudonym Field Value Type Description string"
+ "summary": "Class ProxyLimits Reference values for a handful of resource-limit defaults, kept here for documentation and cross-checking purposes. Inheritance object ProxyLimits Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public static class ProxyLimits Remarks These fields are static readonly, not const, so that if a later release changes one of these reference values, a consumer that has not recompiled still picks up the new value from the referenced Titanium.Web.Proxy.dll at load time rather than keeping a stale value inlined by the compiler into their own assembly. These fields cannot be \"overridden by assigning them before starting the proxy\": they are readonly, so external code cannot assign to them at all (that would be a compile error). Only DefaultMaxChunkSizeBytes is actually read by any live code path (by ChunkSizeParser.TryParse's call sites in HttpStream/LimitedStream); every other field below documents a value that is independently hardcoded as the real default on the corresponding ProxyServer (or, for authentication rounds, WinAuthHandler) property, and is not consulted by that property's own default or by any enforcement path. Treat the fields below other than DefaultMaxChunkSizeBytes as documentation of what each corresponding property's shipped default currently is, not as the mechanism that produces it: to change a limit at runtime, set the actual ProxyServer property (e.g. ProxyServer.MaxBufferedBodyBytes), never the field here. Fields | Edit this page View Source DefaultMaxAuthRounds Reference value only - see the type-level remarks. Matches WinAuthHandler.MaxAuthChallengeRounds: maximum number of authentication challenge rounds allowed per request (e.g. NTLM three-way handshake counts as one round; additional 401/407 responses count separately). The two values are independently maintained, not linked. Default: 3. Declaration public static readonly int DefaultMaxAuthRounds Field Value Type Description int | Edit this page View Source DefaultMaxBufferedBodyBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxBufferedBodyBytes: maximum buffered request or response body size for proxied exchanges where full buffering is required (body mutation, authentication retry, etc.). Streaming relays are not counted against this value. Default: 4 MiB. Declaration public static readonly long DefaultMaxBufferedBodyBytes Field Value Type Description long | Edit this page View Source DefaultMaxChunkSizeBytes Maximum accepted value of a single HTTP/1 chunk-size line (RFC 9112 §7.1), in bytes. The chunk-size grammar itself (1*HEXDIG) has no length ceiling, so this is a proxy-owned safety bound rather than a protocol requirement: it exists to reject the two's-complement chunk- size wrap (an attacker-supplied value like \"ffffffff\" must not decode to a small/negative sentinel) and to avoid ever attempting to allocate or forward a chunk of unbounded size. Set high enough that no legitimate chunk from real-world traffic should reach it. Unlike every other field on this type, this one is actually read live, by every ChunkSizeParser.TryParse call site in HttpStream and LimitedStream. Default: 1 GiB. Declaration public static readonly long DefaultMaxChunkSizeBytes Field Value Type Description long | Edit this page View Source DefaultMaxDecodedHeaderListBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxDecodedHeaderListBytes: maximum total decoded HTTP/2 header list size (sum of name + value + 32 per field). Streams with header lists exceeding that property's configured value are rejected with RST_STREAM(ENHANCE_YOUR_CALM). Default: 64 KiB. Declaration public static readonly int DefaultMaxDecodedHeaderListBytes Field Value Type Description int | Edit this page View Source DefaultMaxWebSocketFramePayloadBytes Reference value only - see the type-level remarks. Matches the current default of ProxyServer.MaxWebSocketFramePayloadBytes: maximum WebSocket frame payload size the proxy will accept during frame-level interception (validated against the declared length before any payload is buffered). Raw relay (no interception) is not subject to this limit. Default: 16 MiB. Declaration public static readonly int DefaultMaxWebSocketFramePayloadBytes Field Value Type Description int | Edit this page View Source DefaultMaxWebSocketMessageBytes Reference value only - see the type-level remarks. Unlike the other fields on this type, this one does not correspond to any enforced ProxyServer property today: there is no cumulative-reassembled-message-size cap on the WebSocket path, only the per-frame DefaultMaxWebSocketFramePayloadBytes/MaxWebSocketFramePayloadBytes limit. This value is retained as a documented target for that still-unimplemented cumulative budget. Default: 64 MiB. Declaration public static readonly long DefaultMaxWebSocketMessageBytes Field Value Type Description long | Edit this page View Source DefaultViaPseudonym Unused legacy field. ProxyServer.ViaHeaderPseudonym defaults to \"titanium-web-proxy\" (not empty) and does not read this field; nothing in the codebase references it. Retained only so removing it is not itself an undocumented breaking change; do not treat it as authoritative for the real default. Declaration public static readonly string DefaultViaPseudonym Field Value Type Description string"
},
"api/Titanium.Web.Proxy.ProxyServer.html": {
"href": "api/Titanium.Web.Proxy.ProxyServer.html",
"title": "Class ProxyServer | Titanium Web Proxy",
- "summary": "Class ProxyServer Translates an HTTP/1.1 client connection onto an h2-only origin (Http2 with AllowHttpProtocolTranslation enabled - see ResolveHttp2ForClientAsync(SessionEventArgsBase, bool, string, int, string, int?, UpstreamHttpProtocol, bool, bool, CancellationToken, bool)), leasing one h2 stream per HTTP/1.1 request from a shared Titanium.Web.Proxy.Http2.Http2OriginConnection via Titanium.Web.Proxy.ProxyServer.Http2OriginConnectionPool rather than opening a new TCP/TLS connection for every request. Inheritance object ProxyServer Implements IDisposable Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public class ProxyServer : IDisposable Remarks This re-implements the HTTP/1.1 client read loop (request line, headers, BeforeRequest, authorization, header preparation, CancelRequest/replaced-response handling) rather than reusing the private HandleHttpSessionRequest/HandleHttpSessionResponse methods, because those methods send/receive over TcpServerConnection.Stream using the raw HTTP/1.1 wire format, which an h2 origin connection cannot speak. This mirrors the precedent set by the h2-to-HTTP/1.1 bridge (Http2ToHttp11BridgeHandler), which similarly bypasses the wire-format-specific machinery for the leg that does not match it. Origin connections are multiplexed across independent HTTP/1.1 clients through Titanium.Web.Proxy.ProxyServer.Http2OriginConnectionPool (fan-in share). Response bodies are delivered via Titanium.Web.Proxy.Http2.Http2OriginConnection streaming writers where available. Constructors | Edit this page View Source ProxyServer(bool, bool, bool) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description bool userTrustRootCertificate When true (the default), EnsureRootCertificate() installs the MITM root into the current-user Personal and Trusted Root stores. Prefer user-only trust for interactive apps; pass false when trust must be fully opt-in. bool machineTrustRootCertificate When true, also trust in the local-machine stores (needs elevation). Defaults to false — machine trust is opt-in for services/admin installs, not for normal desktop use. bool trustRootCertificateAsAdmin When true, attempt elevated trust via UAC (Windows only). Defaults to false. | Edit this page View Source ProxyServer(string?, string?, bool, bool, bool) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description string rootCertificateName Name of the root certificate. string rootCertificateIssuerName Name of the root certificate issuer. bool userTrustRootCertificate When true (the default), EnsureRootCertificate() installs the MITM root into the current-user Personal and Trusted Root stores. Prefer user-only trust for interactive apps; pass false when trust must be fully opt-in. bool machineTrustRootCertificate When true, also trust in the local-machine stores (needs elevation). Defaults to false — machine trust is opt-in for services/admin installs, not for normal desktop use. bool trustRootCertificateAsAdmin When true, attempt elevated trust via UAC (Windows only). Defaults to false. Fields | Edit this page View Source DefaultViaHeaderPseudonym Default Via header pseudonym (RFC 9110 §7.6.3). Used by ViaHeaderPseudonym and by Inspector when Add Via header is enabled. Declaration public const string DefaultViaHeaderPseudonym = \"titanium-web-proxy\" Field Value Type Description string Properties | Edit this page View Source AdmittedClientConnectionCount Number of client connections currently admitted (accepted and past the admission gate, not yet finished being handled), across all TCP-based endpoints. Unlike ClientConnectionCount, this drops to zero as soon as the handler returns, without the trailing TIME_WAIT delay. Declaration public int AdmittedClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source BlockLoopbackDestinations Outbound destination policy hook: when true, every resolved destination IP address is checked for loopback (127.0.0.0/8, ::1, including IPv4-mapped forms such as ::ffff:127.0.0.1) before connecting, and the connection attempt is rejected with an OutboundDestinationBlockedException if it matches. Private (RFC 1918), link-local, and cloud metadata addresses (including 169.254.169.254) remain reachable under this flag alone. Off by default. Use when the proxy must allow intranet destinations (for example 10.*) but must still stop clients from pivoting to the proxy host's own loopback. BlockPrivateNetworkDestinations already covers loopback as part of its broader set; when that flag is enabled, this property is not consulted for the reject. Assigning Profile does not read or clear this property. Enforced on TCP outbound connects only (the same call sites as BlockPrivateNetworkDestinations). Origin HTTP/3 (QUIC) dials a hostname and is not covered. An explicitly configured upstream proxy address is always exempt. Not currently enforced for a SOCKS upstream with ProxyDnsRequests enabled. Declaration public bool BlockLoopbackDestinations { get; set; } Property Value Type Description bool | Edit this page View Source BlockPrivateNetworkDestinations Outbound destination policy hook: when true, every resolved destination IP address is checked against loopback, private (RFC 1918/4193), link-local (which subsumes the 169.254.169.254 cloud metadata endpoint), and other non-globally-routable ranges before connecting, and the connection attempt is rejected with an OutboundDestinationBlockedException if it matches. Off by default: blocking private destinations would break this library's most common configurations, including upstream-proxy chaining to localhost and interception of local development servers. Only enable this when the proxy accepts requests from untrusted clients (an SSRF-relevant deployment), where those same destinations become an attacker-reachable pivot into the host's private network instead of an operator's own intentional configuration. An explicitly configured upstream proxy address (UpStreamHttpProxy, UpStreamHttpsProxy, or a per-session external proxy) is always exempt - that address is operator intent, not attacker-controlled. Checked against the resolved address actually used to connect (no re-resolution afterward, which would make the check a TOCTOU no-op against DNS rebinding). Not currently enforced for a SOCKS upstream with ProxyDnsRequests enabled, since the proxy never resolves the origin itself in that mode and has no address of its own to validate. Declaration public bool BlockPrivateNetworkDestinations { get; set; } Property Value Type Description bool | Edit this page View Source BufferPool The buffer pool used throughout this proxy instance. Set custom implementations by implementing this interface. By default this uses DefaultBufferPool implementation available in StreamExtended library package. Buffer size should be at least 10 bytes. Declaration public IBufferPool BufferPool { get; set; } Property Value Type Description IBufferPool | Edit this page View Source CertificateManager Manages certificates used by this proxy. Declaration public CertificateManager CertificateManager { get; } Property Value Type Description CertificateManager | Edit this page View Source CheckCertificateRevocation Should we check for certificate revocation during SSL authentication to servers Note: If enabled can reduce performance. Defaults to false. Declaration public X509RevocationMode CheckCertificateRevocation { get; set; } Property Value Type Description X509RevocationMode | Edit this page View Source ClientConnectionCount Total number of active TCP client connections. Does not include inbound HTTP/3 (QUIC) clients; see Http3ClientConnectionCount. Declaration public int ClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source ClientHeaderTimeoutSeconds Seconds to wait for a client to finish sending the request line and headers, from the moment this proxy starts reading a new request on the connection. Enforced with a linked CancellationTokenSource around the request-line and header read, not Socket.ReceiveTimeout: that property only bounds a single blocking Receive call, not the asynchronous reads this proxy actually issues, so without this deadline a client that opens a connection and trickles bytes arbitrarily slowly (or stops sending entirely) after the first byte ties up a read loop indefinitely. Default is 0 (disabled), matching every other deadline in this class - no per-session override exists because there is no SessionEventArgs for this request yet at the point this deadline applies. Declaration public int ClientHeaderTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source CompatibilityMode100Continue When true, the proxy immediately responds with a synthetic 100 Continue to any client request carrying Expect: 100-continue, before forwarding the headers to the origin and without waiting for the origin to respond. This breaks the strict handshake (client → proxy 100 → client body → origin body) but prevents the deadlock that occurs with strict clients when Enable100ContinueBehaviour is false (the default). Has no effect when Enable100ContinueBehaviour is true. Default: false. Declaration public bool CompatibilityMode100Continue { get; set; } Property Value Type Description bool | Edit this page View Source ConnectTimeOutSeconds Seconds server connection are to wait for connection to be established. Default value is 20 seconds. Declaration public int ConnectTimeOutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ConnectionTimeOutSeconds Seconds client/server connection are to be kept alive when waiting for read/write to complete. This will also determine the pool eviction time when connection pool is enabled. Default value is 60 seconds. Declaration public int ConnectionTimeOutSeconds { get; set; } Property Value Type Description int | Edit this page View Source CustomUpStreamProxyFailureFunc A callback to provide a chance for an upstream proxy failure to be handled by a new upstream proxy. User should return the ExternalProxy object with valid credentials or null. Declaration public Func>? CustomUpStreamProxyFailureFunc { get; set; } Property Value Type Description Func> | Edit this page View Source DecryptFailureBypassMaxEntries Maximum learned hosts retained (approximate LRU eviction). Default 256. Declaration public int DecryptFailureBypassMaxEntries { get; set; } Property Value Type Description int | Edit this page View Source DecryptFailureBypassThreshold Origin TLS failure strikes required before a host is bypassed on later CONNECTs. Same-CONNECT opaque fallback after an awaited H2 probe failure marks bypass immediately. Default 2. Declaration public int DecryptFailureBypassThreshold { get; set; } Property Value Type Description int | Edit this page View Source DecryptFailureBypassTtl How long a learned decrypt-bypass entry remains valid. Default 30 minutes. Declaration public TimeSpan DecryptFailureBypassTtl { get; set; } Property Value Type Description TimeSpan | Edit this page View Source DnsServerEndPoint DNS server endpoint used by Titanium.Web.Proxy.Http3.Dns.UdpSvcbDnsResolver for HTTPS/SVCB queries. Defaults to the first usable OS-configured plain-UDP DNS server discovered via NetworkInterface. This is a best-effort default and does not honor Windows NRPT, DoH, or VPN split-DNS policy. When no OS-configured DNS server can be discovered, the property reports 0.0.0.0:0 and proactive SVCB discovery is skipped (never falls back to a public third-party resolver). Assign an explicit endpoint to override discovery. Declaration [Experimental(\"TWP001\")] public IPEndPoint DnsServerEndPoint { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source Enable100ContinueBehaviour Does this proxy uses the HTTP protocol 100 continue behaviour strictly? Broken 100 continue implementations on server/client may cause problems if enabled. Defaults to false. Declaration public bool Enable100ContinueBehaviour { get; set; } Property Value Type Description bool | Edit this page View Source EnableConnectionPool Should we enable the server connection pool. Defaults to true. When connection pooling is enabled, instead of creating a new TCP connection to the server for each client TCP connection, we check if an idle server connection is available in our cached pool. If a compatible connection (same destination, scheme, upstream proxy, credentials and negotiated protocol) created from an earlier request is available, we reuse it. Only connections that are safe to reuse under the HTTP protocol are pooled: the response body must be fully received and the connection must be persistent (HTTP/1.1 keep-alive, or an HTTP/1.0 connection that explicitly opted in via \"Connection: keep-alive\"). Connections whose response asked to close, that failed, or that carry connection-oriented authentication state (WinAuth NTLM/Negotiate) or a per-session client certificate are never returned to the shared pool. The ConnectionTimeOutSeconds parameter determines the eviction time for inactive server connections. This reduces TCP (and TLS) connection establishment cost, both in wall clock time and CPU cycles. Set to false to force a fresh server connection for every client connection. Declaration public bool EnableConnectionPool { get; set; } Property Value Type Description bool | Edit this page View Source EnableDecryptFailureBypass When true, the proxy learns hosts whose origin TLS handshake fails under MITM (non-ALPN AuthenticationException, typically bot / TLS-fingerprint rejection) and tunnels subsequent CONNECTs without decrypt. Default false so library and RPS baselines are unchanged. Inspector enables this by default. Success-path cost when on is one dictionary lookup per CONNECT. Declaration public bool EnableDecryptFailureBypass { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttp2 Enable disable HTTP/2 support. Client-facing HTTP/2 is negotiated via TLS ALPN, or as prior-knowledge cleartext h2c on a transparent reverse endpoint (DecryptSsl: false). No Upgrade: h2c. Origin-facing HTTP/2 uses TLS ALPN h2 by default; with ForwardCleartext and Http2, the origin speaks cleartext HTTP/2 prior-knowledge (outbound h2c). A client/server that does not support HTTP/2 transparently falls back to HTTP/1.1 when policy allows. Request/response header and body modification in BeforeRequest/BeforeResponse, chunked trailers, interim (1xx) responses, and the synthetic-response APIs (Ok/Respond/Redirect/GenericResponse/ RespondStreaming) are all supported over HTTP/2, the same as over HTTP/1.x. Not supported: HTTP/2 server push (the wire frames are transcoded but there is no public API to originate a push) and Upgrade: h2c. Explicit-proxy inbound h2c is not implemented. See the protocol support matrix on the wiki for exact, up-to-date HTTP/1.x/HTTP/2 feature coverage. Declaration public bool EnableHttp2 { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttp3 Enable HTTP/3 (QUIC) support. When true: Any TransparentQuicProxyEndPoint is started as a UDP-only QUIC listener for transparent/NAT HTTP/3 interception. Any TransparentProxyEndPoint with EnableHttp3 also listens for HTTP/3 on the same IP:port (TCP H1/H2 + UDP H3) and injects client-facing Alt-Svc. With Auto (default), a cached Alt-Svc / HTTPS/SVCB capability selects outbound HTTP/3 on the next CONNECT or new HTTP/1.1 request. Background QUIC warm-up starts when the cache is filled so that handshake is often already done. An already-open H2↔H2 MITM session is not upgraded mid-connection. Forced Http3 fails closed with no TCP fallback. Requires MsQuic native library and a supported operating-system version (IsSupported). Setting to true with no inbound HTTP/3 endpoint is fine when an explicit/SOCKS/transparent TCP endpoint is present (origin-side QUIC only). A warning is emitted only when EnableHttp3 is set with no client-facing endpoints at all. Default: false (opt-in). Experimental: HTTP/3 support has not yet completed the full interop/soak/fuzz gate process. Suppress TWP001 to opt in; the attribute is removed when the feature graduates to stable. Declaration [Experimental(\"TWP001\")] public bool EnableHttp3 { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttpInterception Forces the full interception path (SessionEventArgs, BeforeRequest, etc.) even when no event handlers are subscribed. Set this when consuming SessionEventArgs for timing or metrics without subscribing to any event. Default: false. Declaration public bool EnableHttpInterception { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttpsSvcbDnsDiscovery When true, the proxy queues a background HTTPS/SVCB RR (DNS type 65) lookup after an Auto-mode capability-cache miss. A positive result (ALPN h3 found) warms Titanium.Web.Proxy.Http3.Http3OriginCapabilityCache for subsequent connections; the CONNECT / request path itself never awaits DNS. Negative results are cached for 1 minute; transient failures use a short backoff. Defaults to true whenever EnableHttp3 is true. Set explicitly to false to disable discovery even when HTTP/3 is enabled — for example, when the configured DNS server is untrusted or unreachable. First-connection HTTP/3 adoption then comes from Alt-Svc. Declaration [Experimental(\"TWP001\")] public bool EnableHttpsSvcbDnsDiscovery { get; set; } Property Value Type Description bool | Edit this page View Source EnableIpv6UnreachableSoftSkip When true (default), after one IPv6 connect failure with NetworkUnreachable (or equivalent), temporarily omit IPv6 addresses from the Happy Eyeballs race for 30 seconds. Reduces first-chance SocketException noise on dual-stack hosts with a broken IPv6 path. Disable if operators require strict IPv6 preference even when the path is unreachable. Declaration public bool EnableIpv6UnreachableSoftSkip { get; set; } Property Value Type Description bool | Edit this page View Source EnableQpackDynamicTable When true, enables RFC 9204 QPACK dynamic table encoding and decoding for inbound HTTP/3 connections. Each connection gets its own Titanium.Web.Proxy.Http3.Qpack.QpackContext with two independent 4096-byte tables (one inbound, one outbound). Defaults to false (static-table-only); existing deployments are unaffected. Declaration [Experimental(\"TWP001\")] public bool EnableQpackDynamicTable { get; set; } Property Value Type Description bool | Edit this page View Source EnableRequestTimingCapture Enables structured request/connection timing capture. When false (the default) no timing objects are allocated and no UtcNow calls are made for timing purposes anywhere in the proxy, so there is zero overhead on the hot path. When enabled, every SessionEventArgsBase exposes a populated Timing (per-request phases: client header read, connection wait, request send, time-to-first-byte, response delivery, total), every upstream connection exposes a populated UpstreamConnectionTiming (reachable from a session via UpstreamConnectionTiming, describing DNS, TCP connect, optional upstream-proxy CONNECT, and TLS handshake durations), and a decrypted TunnelConnectSessionEventArgs exposes the client-facing TLS handshake duration via ClientTlsTiming. Can be toggled at any time; it only affects sessions/connections created after the change, never mutating timing objects already handed out. Defaults to false. Declaration public bool EnableRequestTimingCapture { get; set; } Property Value Type Description bool | Edit this page View Source EnableRfc8441 When true, the proxy enables RFC 8441 WebSocket-over-HTTP/2: Accepts extended CONNECT (:protocol = websocket) from h2 clients and advertises SETTINGS_ENABLE_CONNECT_PROTOCOL=1 to them. Per origin: if the origin is HTTP/2 and advertises RFC 8441 support, DATA frames are relayed directly; if the origin is HTTP/2 and does not, the stream is reset with REFUSED_STREAM; if the origin is HTTP/1.1, the h2→h1 WebSocket upgrade bridge is used. On the HTTP/1.1-client-to-h2-origin translation bridge, translates Upgrade: websocket into extended CONNECT when the origin advertises the setting; otherwise falls back to a dedicated HTTP/1.1 origin connection for that WebSocket. When this property is false, that bridge still returns synthetic 501 Not Implemented for WebSocket upgrades (historical default). Default: false (must opt-in). Declaration public bool EnableRfc8441 { get; set; } Property Value Type Description bool | Edit this page View Source EnableTcpKeepAlive Enable TCP KeepAlive on client and server sockets so NAT/firewall mappings for long-lived CONNECT tunnels are refreshed. Default: true. Declaration public bool EnableTcpKeepAlive { get; set; } Property Value Type Description bool | Edit this page View Source EnableTcpServerConnectionPrefetch Should we enable tcp server connection prefetching? When enabled, as soon as we receive a client connection we concurrently initiate corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request we will have the server connection immediately ready or in the process of getting ready. If a server connection is available in cache then this prefetch task will immediately return with the available connection from cache. Defaults to true. Declaration public bool EnableTcpServerConnectionPrefetch { get; set; } Property Value Type Description bool | Edit this page View Source EnableWinAuth Enable disable Windows Authentication (NTLM/Kerberos). By default SSPI uses the process identity. To authenticate as another user, set WinAuthCredentialsProvider (issue #461). Defaults to false. Declaration public bool EnableWinAuth { get; set; } Property Value Type Description bool | Edit this page View Source EndpointAdmissionRejectionCount Total number of client connections rejected by any endpoint's MaxConcurrentClients since this instance was created. Declaration public long EndpointAdmissionRejectionCount { get; } Property Value Type Description long | Edit this page View Source ForwardToUpstreamGateway Gets or sets a value indicating whether requests will be chained to upstream gateway. Defaults to false. Declaration public bool ForwardToUpstreamGateway { get; set; } Property Value Type Description bool | Edit this page View Source GetCustomUpStreamProxyFunc A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests. User should return the ExternalProxy object with valid credentials. Declaration public Func>? GetCustomUpStreamProxyFunc { get; set; } Property Value Type Description Func> | Edit this page View Source GlobalAdmissionRejectionCount Total number of client connections rejected by MaxConcurrentClientConnections since this instance was created. Declaration public long GlobalAdmissionRejectionCount { get; } Property Value Type Description long | Edit this page View Source Http3ClientConnectionCount Total number of active inbound HTTP/3 (QUIC) client connections. Declaration public int Http3ClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source Http3ServerConnectionCount Total number of active upstream HTTP/3 (QUIC) server connections. These are also included in ServerConnectionCount. Declaration public int Http3ServerConnectionCount { get; } Property Value Type Description int | Edit this page View Source HttpResponseCacheFiller Fills the GET/HEAD response cache from a session that is already on the interception path. Does not subscribe BeforeResponse or AfterResponse, so it does not turn NeedsHttpInterception(ProxyEndPoint) on. Declaration public Action? HttpResponseCacheFiller { get; set; } Property Value Type Description Action | Edit this page View Source IdleReadTimeoutSeconds Seconds of idle time allowed while reading from the origin (stalled header/body waits). Applied via CancelAfter on the active read operation. Default is 0 (disabled). Per-session override: IdleReadTimeout. Declaration public int IdleReadTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source IdleWriteTimeoutSeconds Seconds of idle time allowed while writing to the origin (stalled header/body waits). Applied via CancelAfter on the active write operation. Default is 0 (disabled). Per-session override: IdleWriteTimeout. Declaration public int IdleWriteTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source IgnoreServerCertificateErrors When true, origin TLS certificates that fail OS chain validation are still accepted (MITM of loopback/self-signed/private CAs). Inspector's \"Ignore server certificate errors\" maps here. Default false. A subscribed ServerCertificateValidationCallback still wins. Declaration public bool IgnoreServerCertificateErrors { get; set; } Property Value Type Description bool | Edit this page View Source ListenerBackLog TCP listener accept backlog. Default: 1024 for burst connection handling. Declaration public int ListenerBackLog { get; set; } Property Value Type Description int | Edit this page View Source Logger The live, shared logger used throughout this proxy instance. Reflects the most recent call to ApplyLoggingConfiguration(). Declaration public ILogger Logger { get; } Property Value Type Description ILogger | Edit this page View Source Logging Configuration for this proxy instance's built-in diagnostic logging - the replacement for the removed ExceptionFunc callback. Every exception the proxy catches (even when handled internally and never surfaced to user code) is reported through this logger at an appropriate severity; see ProxyLoggingOptions for the console/file sinks, enable/disable switch, and minimum level. Mutate the returned instance (or assign a new one) at any point; each assignment/mutation you want to take effect must be followed by ApplyLoggingConfiguration() (which Start(bool) also calls automatically, so the configuration active at the moment the proxy starts running is picked up for the run even if you never call it yourself). Calling it again later - including while the proxy is already running - immediately swaps in the new configuration; this is safe because logging never blocks or otherwise affects proxy traffic. Declaration public ProxyLoggingOptions Logging { get; set; } Property Value Type Description ProxyLoggingOptions | Edit this page View Source MaxBufferedBodyBytes Maximum bytes the proxy will buffer for a single request or response body when body buffering is required (body-read hooks, authentication retry, etc.). Bodies larger than this limit are rejected with 413 (upstream request) or connection teardown (upstream response). Set to 0 to disable the limit (not recommended). Default: 4,194,304 (4 MiB). Declaration public int MaxBufferedBodyBytes { get; set; } Property Value Type Description int | Edit this page View Source MaxCachedConnections Maximum number of concurrent connections per remote host in cache. Only meaningful when EnableConnectionPool is true; to disable pooling, set EnableConnectionPool to false rather than setting this to 0 - the pool eviction loop treats a value below 1 as \"evict without limit while holding the pool-wide lock\", which spins indefinitely once the cache for that host is empty and would stall every other connection acquire/release in the process. Rejected outright at assignment so that state cannot be reached. Default value is 128. Declaration public int MaxCachedConnections { get; set; } Property Value Type Description int Exceptions Type Condition ArgumentOutOfRangeException The assigned value is less than 1. | Edit this page View Source MaxConcurrentClientConnections Maximum number of client connections admitted across all TCP-based endpoints at once. null (the default) disables the global admission gate, preserving today's unbounded behavior. When set, a connection beyond this limit is rejected and disposed immediately after accept, before a handler task is even started. Enforced independently of ClientConnectionCount: see Titanium.Web.Proxy.ProxyServer.admittedClientConnectionCount for why. See also MaxConcurrentClients for a per-endpoint cap layered on top of this global one. Declaration public int? MaxConcurrentClientConnections { get; set; } Property Value Type Description int? | Edit this page View Source MaxConcurrentHttp11HttpsOriginCreates Caps concurrent new HTTPS origin TCP/TLS opens on the H2→H1 bridge only (MITM / re-encrypt). Pool hits (warm keep-alive) are uncapped. Cleartext H1 origins are not gated. Default is Clamp(ProcessorCount, 4, 32). Set before the first H2→H1 HTTPS origin open (typically before Start(bool)); changing the value after the create gate has been used has no effect on the live semaphore. Declaration public int MaxConcurrentHttp11HttpsOriginCreates { get; set; } Property Value Type Description int Exceptions Type Condition ArgumentOutOfRangeException The assigned value is less than 1. | Edit this page View Source MaxDecodedHeaderListBytes Maximum decoded HTTP/2 header list size in bytes, using RFC 7541 accounting (name.Length + value.Length + 32 per field). Requests or responses with a decoded header list exceeding this limit will be refused with RST_STREAM(ENHANCE_YOUR_CALM) (code 0xb). Set to 0 to disable the limit (not recommended). Default: 65,536 (64 KiB). Advertised via SETTINGS_MAX_HEADER_LIST_SIZE. Declaration public int MaxDecodedHeaderListBytes { get; set; } Property Value Type Description int | Edit this page View Source MaxWebSocketFramePayloadBytes Maximum WebSocket frame payload size in bytes that the proxy will accept during frame-level interception (i.e. when BeforeWebSocketFrame has at least one subscriber). Frames whose decoded payload exceeds this limit cause the WebSocket connection to be closed with Close code 1009 (Message Too Big). Raw-relay sessions (no BeforeWebSocketFrame subscriber) bypass this check entirely and pass all frames through unvalidated. Default: 16,777,216 (16 MiB). Declaration public int MaxWebSocketFramePayloadBytes { get; set; } Property Value Type Description int | Edit this page View Source NetworkFailureRetryAttempts Number of times to retry upon network failures when connection pool is enabled. Declaration public int NetworkFailureRetryAttempts { get; set; } Property Value Type Description int | Edit this page View Source NoDelay Gets or sets a Boolean value that specifies whether server and client stream Sockets are using the Nagle algorithm. Defaults to true, no nagle algorithm is used. Declaration public bool NoDelay { get; set; } Property Value Type Description bool | Edit this page View Source OriginHttpVersionPolicy Controls which HTTP version is declared to the origin server on the request line, independently of the version the client declared to the proxy. Defaults to PreserveClientVersion, which matches the proxy's historical pass-through behavior exactly. Set to NormalizeToHttp11 to let HTTP/1.0 clients share pooled, persistent origin connections the same way HTTP/1.1 clients already do. This only changes the wire version written to the origin request line - it never changes the client-facing Http.Request.HttpVersion that event handlers observe, nor the version/persistence used to write the response back to the client. Declaration public OriginHttpVersionPolicy OriginHttpVersionPolicy { get; set; } Property Value Type Description OriginHttpVersionPolicy | Edit this page View Source PolicyModes Which resource-bound PolicyFamily is enforced, observed, or disabled, per the plan's rollout section. Read live by each family's enforcement call site - not baked into a per-request snapshot at connection accept time - so assigning a new value here (a whole-object replacement, never a mutation of the previous instance) takes effect for the next check any in-flight or new request makes, without restarting the proxy. This is the \"runtime switch to drop to Observe without redeploying\" the plan requires; see WithAllObservedExceptDisabled() for the one-call way to do that. Defaults to Balanced's modes (resource families enforced, Http2RelayValidation disabled so compressed H2 relay stays the verbatim-HPACK fast path). AllEnforce additionally enforces relay validation and is what PublicFacing applies. Assigning Profile also replaces this value with that profile's bundle; assign PolicyModes afterward to deviate from the selected profile's modes without changing anything else the profile set. Declaration public ProxyPolicyModes PolicyModes { get; set; } Property Value Type Description ProxyPolicyModes | Edit this page View Source Profile The last profile applied via this property's setter, defaulting to Balanced - the profile every field on this instance already starts at, so a fresh new ProxyServer() reports Balanced without needing its setter to run once at construction time. Assigning this property applies its entire ProxyProfileSettings bundle - ResourceLimits, PolicyModes, SupportedSslProtocols, BlockPrivateNetworkDestinations, MaxConcurrentClientConnections and the deadline-seconds properties - as a single atomic assignment, so a reader can never observe a half-applied profile. Assigning any of those properties individually afterward overrides just that one, without reverting the rest of the profile's bundle. Logged once per Start(bool) call, by name only - never with hosts, URLs or secrets, per the plan's rollout section. Declaration public ProxyProfile Profile { get; set; } Property Value Type Description ProxyProfile | Edit this page View Source ProxyAuthenticationRealm Realm used during Proxy Basic Authentication. Declaration public string ProxyAuthenticationRealm { get; set; } Property Value Type Description string | Edit this page View Source ProxyAuthenticationSchemes A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. Works in relation with ProxySchemeAuthenticateFunc. Declaration public IEnumerable ProxyAuthenticationSchemes { get; set; } Property Value Type Description IEnumerable | Edit this page View Source ProxyBasicAuthenticateFunc A callback to authenticate proxy clients via basic authentication. Parameters are username and password as provided by client. Should return true for successful authentication. Declaration public Func>? ProxyBasicAuthenticateFunc { get; set; } Property Value Type Description Func> | Edit this page View Source ProxyEndPoints A list of IpAddress and port this proxy is listening to. Declaration public List ProxyEndPoints { get; set; } Property Value Type Description List | Edit this page View Source ProxyRunning Is the proxy currently running? Declaration public bool ProxyRunning { get; } Property Value Type Description bool | Edit this page View Source ProxySchemeAuthenticateFunc A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc. Parameters are current working session, schemeType, and token as provided by a calling client. Should return success for successful authentication, continuation if the package requests, or failure. Declaration public Func>? ProxySchemeAuthenticateFunc { get; set; } Property Value Type Description Func> | Edit this page View Source RequestTimeoutSeconds Total seconds allowed for a single request/response exchange after BeforeRequest returns (connect, send, wait for headers, and body copy). Default is 0 (disabled). Per-session override: RequestTimeout. Declaration public int RequestTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ResourceLimits The shared, immutable resource-bound snapshot (concurrent-stream cap, CONTINUATION frame-count/wall-clock bounds, peer-initiated incomplete-stream-reset budget, and the other limits described in ProxyResourceLimits) consulted by the HTTP/2 relay so a single proxy-owned value governs both what is enforced and what is advertised to each peer, rather than admitting purely against whatever the origin advertised. Assign a new ProxyResourceLimits (constructed via Create(long, int, long, long?, long?, double?, int?, int, int?, int, TimeSpan, bool, int, int?)) to override the Default snapshot. There is no artificial upper clamp: high-CPU/RAM hosts may pass larger maxCachedConnectionsPerHost, maxConcurrentStreamsPerConnection, etc. as needed. The live TCP pool depth knob MaxCachedConnections remains independently settable and should usually be kept in sync with MaxCachedConnectionsPerHost. Declaration public ProxyResourceLimits ResourceLimits { get; set; } Property Value Type Description ProxyResourceLimits | Edit this page View Source ResponseHeaderContributor Adds response headers on the way to the client without subscribing BeforeResponse. Plus CORS uses this so a listener can stay on H1 terminate-lite. Declaration public Action? ResponseHeaderContributor { get; set; } Property Value Type Description Action | Edit this page View Source ResponseHeaderTimeoutSeconds Seconds to wait for the origin to send the response status line and headers after the request has been sent. Enforced with a linked CancellationTokenSource (not Socket receive timeout alone). When the deadline elapses a ProxyTimeoutException with ResponseHeader is raised (and may be converted to HTTP 504 before any response bytes have been committed to the client). Default is 0 (disabled). WebSocket upgrades, Server-Sent Events, raw tunnels, and sessions that already wrote a response status to the client are exempt; those waits use IdleReadTimeoutSeconds when configured. Per-session override: ResponseHeaderTimeout. Declaration public int ResponseHeaderTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ReuseSocket When true (default), SO_REUSEADDR is requested where Titanium.Web.Proxy.Helpers.RunTime.IsSocketReuseAvailable() reports support (always on Windows; on non-Windows, .NET Core 3+ / compatible runtimes). Declaration public bool ReuseSocket { get; set; } Property Value Type Description bool | Edit this page View Source ReverseProxy Optional reverse-proxy route/cluster configuration. When null (default), Core keeps 6.x ForwardHost behavior with zero added cost on the hot path. Declaration public ReverseProxyOptions? ReverseProxy { get; set; } Property Value Type Description ReverseProxyOptions | Edit this page View Source ServerConnectionCount Total number of active server connections (TCP plus upstream QUIC). For HTTP/3-only upstreams see Http3ServerConnectionCount. Declaration public int ServerConnectionCount { get; } Property Value Type Description int | Edit this page View Source ShouldInterceptHttp Optional per-request/stream predicate consulted only when the global interception gate is active. Return true to use the full SessionEventArgs path; return false to use the fast-forward path. null (the default) intercepts every request — preserving today's behavior. Declaration public Func? ShouldInterceptHttp { get; set; } Property Value Type Description Func | Edit this page View Source SupportedServerSslProtocols Ssl versions offered on outbound HTTPS connections to origins (and upstream proxies). Default None means “use SupportedSslProtocols” (typically TLS 1.2 and 1.3). Set an explicit mask to restrict or expand outbound-only independently of inbound client TLS. Older docs described None as “same as the proxy client.” That coupling is incorrect across protocol translations (e.g. inbound QUIC is always TLS 1.3 while outbound TCP SslStream on macOS SecureTransport cannot offer TLS 1.3). Declaration public SslProtocols SupportedServerSslProtocols { get; set; } Property Value Type Description SslProtocols | Edit this page View Source SupportedSslProtocols List of supported Ssl versions. Defaults to TLS 1.2/1.3 only as of 5.0 - a breaking change from 4.x, which also enabled SSL 3.0/TLS 1.0/1.1. Those legacy, broken-by-design protocols require an explicit opt-in by assigning this property directly (e.g. SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13) if a legacy client/server genuinely requires them. Declaration public SslProtocols SupportedSslProtocols { get; set; } Property Value Type Description SslProtocols | Edit this page View Source TcpTimeWaitSeconds SO_LINGER timeout in seconds applied to client and upstream sockets via LingerOption (enabled with this timeout). This is not the kernel TCP TIME_WAIT duration — TIME_WAIT is controlled by the OS. A positive value means Close may block up to that many seconds flushing send buffers; use 0 for an abortive close (RST). Default is 0 so high-churn proxies avoid TIME_WAIT accumulation; the 1-second connection disposal delay already prefers peer-first close. Declaration public int TcpTimeWaitSeconds { get; set; } Property Value Type Description int | Edit this page View Source ThreadPoolWorkerThread Customize the minimum ThreadPool size (increase it on a server). Defaults to max(ProcessorCount * 2, 16) so short loopback/proxy workloads are not starved while the pool is still ramping workers. Declaration public int ThreadPoolWorkerThread { get; set; } Property Value Type Description int | Edit this page View Source UpStreamEndPoint Local adapter/NIC endpoint where proxy makes request via. Defaults via any IP addresses of this machine. When the resolved destination address family does not match this endpoint, it is ignored so dual-stack destinations can still connect (see UpStreamEndPointIPv4 / UpStreamEndPointIPv6). Declaration public IPEndPoint? UpStreamEndPoint { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamEndPointIPv4 Local bind endpoint used when the resolved upstream destination is IPv4. Takes precedence over UpStreamEndPoint for IPv4 destinations. Declaration public IPEndPoint? UpStreamEndPointIPv4 { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamEndPointIPv6 Local bind endpoint used when the resolved upstream destination is IPv6. Takes precedence over UpStreamEndPoint for IPv6 destinations. Declaration public IPEndPoint? UpStreamEndPointIPv6 { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamHttpProxy External proxy used for Http requests. Declaration public IExternalProxy? UpStreamHttpProxy { get; set; } Property Value Type Description IExternalProxy | Edit this page View Source UpStreamHttpsProxy External proxy used for Https requests. Declaration public IExternalProxy? UpStreamHttpsProxy { get; set; } Property Value Type Description IExternalProxy | Edit this page View Source UpstreamProxyConfigurationScript If set, the upstream proxy will be detected by a script that will be loaded from the provided Uri Declaration public Uri? UpstreamProxyConfigurationScript { get; set; } Property Value Type Description Uri | Edit this page View Source ViaHeaderPseudonym Pseudonym used in Via header fields appended to forwarded requests and responses (RFC 9110 §7.6.3). Defaults to DefaultViaHeaderPseudonym. Set to an empty string to disable Via header injection entirely. Loop detection uses this value: a request arriving with this pseudonym already present in Via is refused with 508 Loop Detected. Declaration public string ViaHeaderPseudonym { get; set; } Property Value Type Description string | Edit this page View Source WinAuthCredentialsProvider Optional per-session credential provider for server 401 WinAuth (NTLM/Negotiate/Kerberos). Return null to use the current process identity (legacy behavior). Do not put plaintext passwords on SessionEventArgs — use this callback instead. Windows SSPI only; ignored on non-Windows platforms. Declaration public Func>? WinAuthCredentialsProvider { get; set; } Property Value Type Description Func> Methods | Edit this page View Source AddEndPoint(ProxyEndPoint) Add a proxy end point. Declaration public void AddEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The proxy endpoint. | Edit this page View Source ApplyLoggingConfiguration() Rebuilds the active logger/logger factory from the current Logging configuration, disposing any previously owned built-in providers. Called automatically from the constructor (with the default configuration) and from Start(bool). Call this explicitly any time after changing Logging and you want the change to take effect immediately - whether the proxy is stopped (e.g. before using CertificateManager directly) or already running. Declaration public void ApplyLoggingConfiguration() | Edit this page View Source ClearDecryptFailureBypass() Clears all learned decrypt-bypass entries. Declaration public void ClearDecryptFailureBypass() | Edit this page View Source DisableAllSystemProxies() Clear all proxy settings for current machine. Declaration public void DisableAllSystemProxies() | Edit this page View Source DisableSystemHttpProxy() Clear HTTP proxy settings of current machine. Declaration public void DisableSystemHttpProxy() | Edit this page View Source DisableSystemHttpsProxy() Clear HTTPS proxy settings of current machine. Declaration public void DisableSystemHttpsProxy() | Edit this page View Source DisableSystemProxy(ProxyProtocolType) Clear the specified proxy setting for current machine. Declaration public void DisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType | Edit this page View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Edit this page View Source Dispose(bool) Declaration [SuppressMessage(\"ApiDesign\", \"RS0016:Add public types and members to the declared API\", Justification = \"Protected Dispose(bool) is required by the standard IDisposable pattern but is not public API.\")] protected virtual void Dispose(bool disposing) Parameters Type Name Description bool disposing | Edit this page View Source ForceDecryptFailureBypass(string) Marks host as actively bypassed (same as a forced learn after origin TLS failure). Raises DecryptFailureBypassChanged when bypass newly becomes active. Declaration public bool ForceDecryptFailureBypass(string host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source GetDecryptFailureBypassEntries() Snapshot of current learned decrypt-bypass entries (may include non-active strikes). Declaration public IReadOnlyList GetDecryptFailureBypassEntries() Returns Type Description IReadOnlyList | Edit this page View Source RemoveDecryptFailureBypass(string) Removes one host from the learned decrypt-bypass cache. Declaration public bool RemoveDecryptFailureBypass(string host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source RemoveEndPoint(ProxyEndPoint) Remove a proxy end point. Will throw error if the end point doesn't exist. Declaration public void RemoveEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The existing endpoint to remove. | Edit this page View Source RestoreOriginalProxySettings() Restores the original proxy settings. Declaration public void RestoreOriginalProxySettings() | Edit this page View Source SetAsSystemHttpProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. | Edit this page View Source SetAsSystemHttpProxy(ExplicitProxyEndPoint, SystemProxySettings) Set the given explicit end point as the default HTTP proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint, SystemProxySettings settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SystemProxySettings settings The Windows system proxy settings. | Edit this page View Source SetAsSystemHttpsProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. | Edit this page View Source SetAsSystemHttpsProxy(ExplicitProxyEndPoint, SystemProxySettings) Set the given explicit end point as the default HTTPS proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint, SystemProxySettings settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SystemProxySettings settings The Windows system proxy settings. | Edit this page View Source SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. | Edit this page View Source SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType, SystemProxySettings?) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType, SystemProxySettings? settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. SystemProxySettings settings The Windows system proxy settings, or null to preserve the current bypass list. | Edit this page View Source SetHttp3Enabled(bool) Enables or disables EnableHttp3. Enabling still requires MsQuic (IsSupported); disabling is always applied. Safe to call while the proxy is running — new origin connections pick up the change. Existing sessions keep the protocol they already negotiated. Declaration public bool SetHttp3Enabled(bool enabled) Parameters Type Name Description bool enabled Returns Type Description bool true when HTTP/3 is enabled after the call. | Edit this page View Source ShouldBypassDecryptForLearnedHost(string?) When EnableDecryptFailureBypass is on and host is actively bypassed, returns true (decrypt should be skipped). Declaration public bool ShouldBypassDecryptForLearnedHost(string? host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source Start(bool) Start this proxy server instance. Transactional: if any endpoint fails to start, every listener this call already started is stopped, the system-upstream-proxy resolver (if this call created one) is disposed, and ProxyRunning is left false before the exception propagates. A caller that catches the exception is left with an instance in exactly the same state as before calling Start(bool), not a partially-bound proxy with some endpoints silently listening. Declaration public void Start(bool changeSystemProxySettings = true) Parameters Type Name Description bool changeSystemProxySettings Whether or not clear any system proxy settings which is pointing to our own endpoint (causing a cycle). E.g due to ungracious proxy shutdown before. | Edit this page View Source Stop() Stop this proxy server instance. Endpoints remain registered so Start(bool) can re-listen on the same ports. In-flight sessions are cancelled; pooled upstream connections are cleared. The connection factory itself stays usable for a subsequent Start (it is only disposed with the proxy). Declaration public void Stop() | Edit this page View Source StopAsync(TimeSpan?) Asynchronously stop this proxy server, cancel in-flight sessions, and wait briefly for client connection count to drain before clearing the upstream pool. Declaration public Task StopAsync(TimeSpan? drainTimeout = null) Parameters Type Name Description TimeSpan? drainTimeout Maximum time to wait for active client handlers to exit after cancellation. Defaults to 5 seconds. Returns Type Description Task | Edit this page View Source TryDisableAllSystemProxies() Clear all OS proxy settings without throwing. Declaration public SystemProxyChangeResult TryDisableAllSystemProxies() Returns Type Description SystemProxyChangeResult | Edit this page View Source TryDisableSystemProxy(ProxyProtocolType) Clear OS proxy for the given protocols without throwing. Declaration public SystemProxyChangeResult TryDisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType Returns Type Description SystemProxyChangeResult | Edit this page View Source TryEnableHttp3IfSupported() Turns on EnableHttp3 when MsQuic is available (IsSupported). Hosts (CLI, Inspector, examples) should call this instead of setting EnableHttp3 blindly. Returns true when HTTP/3 was enabled. Declaration public bool TryEnableHttp3IfSupported() Returns Type Description bool | Edit this page View Source TryRestoreOriginalProxySettings() Restore OS proxy without throwing. Declaration public SystemProxyChangeResult TryRestoreOriginalProxySettings() Returns Type Description SystemProxyChangeResult | Edit this page View Source TrySetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType, SystemProxySettings?) Enable OS system proxy without throwing. Failures are logged and returned so Inspector/CLI can show a status message instead of crashing. Declaration public SystemProxyChangeResult TrySetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType, SystemProxySettings? settings = null) Parameters Type Name Description ExplicitProxyEndPoint endPoint ProxyProtocolType protocolType SystemProxySettings settings Returns Type Description SystemProxyChangeResult Events | Edit this page View Source AfterResponse Intercept after response event from server. Declaration public event AsyncEventHandler? AfterResponse Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeRequest Intercept request event to server. Declaration public event AsyncEventHandler? BeforeRequest Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeResponse Intercept response event from server. Declaration public event AsyncEventHandler? BeforeResponse Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeUpStreamConnectRequest Intercept connect request sent to upstream proxy. Declaration public event AsyncEventHandler? BeforeUpStreamConnectRequest Event Type Type Description AsyncEventHandler | Edit this page View Source ClientCertificateSelectionCallback Event to override client certificate selection during mutual SSL authentication. Declaration public event AsyncEventHandler? ClientCertificateSelectionCallback Event Type Type Description AsyncEventHandler | Edit this page View Source ClientConnectionCountChanged Event occurs when client connection count changed. Declaration public event EventHandler? ClientConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source DecryptFailureBypassChanged Raised when a host becomes actively bypassed (threshold reached or same-CONNECT mark). Handlers must not block; Inspector marshals to the UI thread. Declaration public event EventHandler? DecryptFailureBypassChanged Event Type Type Description EventHandler | Edit this page View Source Http3ClientConnectionCountChanged Event occurs when inbound HTTP/3 client connection count changed. Declaration public event EventHandler? Http3ClientConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source Http3ServerConnectionCountChanged Event occurs when upstream HTTP/3 server connection count changed. Declaration public event EventHandler? Http3ServerConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source OnClientConnectionCreate Customize TcpClient used for client connection upon create. Declaration public event AsyncEventHandler? OnClientConnectionCreate Event Type Type Description AsyncEventHandler | Edit this page View Source OnRequestBodyWrite Intercept request body send event to server. Subscribe to inspect or modify the request body chunk-by-chunk as it streams to the server, without buffering the whole body. Do not combine with SessionEventArgs.GetRequestBody (which buffers). Declaration public event AsyncEventHandler? OnRequestBodyWrite Event Type Type Description AsyncEventHandler | Edit this page View Source OnResponseBodyWrite Intercept response body send event to client. Subscribe to inspect or modify the response body chunk-by-chunk as it streams to the client, without buffering the whole body. Do not combine with SessionEventArgs.GetResponseBody (which buffers). Declaration public event AsyncEventHandler? OnResponseBodyWrite Event Type Type Description AsyncEventHandler | Edit this page View Source OnServerConnectionCreate Customize TcpClient used for server connection upon create. Declaration public event AsyncEventHandler? OnServerConnectionCreate Event Type Type Description AsyncEventHandler | Edit this page View Source ServerCertificateValidationCallback Event to override the default verification logic of remote SSL certificate received during authentication. Declaration public event AsyncEventHandler? ServerCertificateValidationCallback Event Type Type Description AsyncEventHandler | Edit this page View Source ServerConnectionCountChanged Event occurs when server connection count changed. Declaration public event EventHandler? ServerConnectionCountChanged Event Type Type Description EventHandler Implements IDisposable"
+ "summary": "Class ProxyServer Translates an HTTP/1.1 client connection onto an h2-only origin (Http2 with AllowHttpProtocolTranslation enabled - see ResolveHttp2ForClientAsync(SessionEventArgsBase, bool, string, int, string, int?, UpstreamHttpProtocol, bool, bool, CancellationToken, bool)), leasing one h2 stream per HTTP/1.1 request from a shared Titanium.Web.Proxy.Http2.Http2OriginConnection via Titanium.Web.Proxy.ProxyServer.Http2OriginConnectionPool rather than opening a new TCP/TLS connection for every request. Inheritance object ProxyServer Implements IDisposable Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public class ProxyServer : IDisposable Remarks This re-implements the HTTP/1.1 client read loop (request line, headers, BeforeRequest, authorization, header preparation, CancelRequest/replaced-response handling) rather than reusing the private HandleHttpSessionRequest/HandleHttpSessionResponse methods, because those methods send/receive over TcpServerConnection.Stream using the raw HTTP/1.1 wire format, which an h2 origin connection cannot speak. This mirrors the precedent set by the h2-to-HTTP/1.1 bridge (Http2ToHttp11BridgeHandler), which similarly bypasses the wire-format-specific machinery for the leg that does not match it. Origin connections are multiplexed across independent HTTP/1.1 clients through Titanium.Web.Proxy.ProxyServer.Http2OriginConnectionPool (fan-in share). Response bodies are delivered via Titanium.Web.Proxy.Http2.Http2OriginConnection streaming writers where available. Constructors | Edit this page View Source ProxyServer(bool, bool, bool) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description bool userTrustRootCertificate When true (the default), EnsureRootCertificate() installs the MITM root into the current-user Personal and Trusted Root stores. Prefer user-only trust for interactive apps; pass false when trust must be fully opt-in. bool machineTrustRootCertificate When true, also trust in the local-machine stores (needs elevation). Defaults to false — machine trust is opt-in for services/admin installs, not for normal desktop use. bool trustRootCertificateAsAdmin When true, attempt elevated trust via UAC (Windows only). Defaults to false. | Edit this page View Source ProxyServer(string?, string?, bool, bool, bool) Initializes a new instance of ProxyServer class with provided parameters. Declaration public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) Parameters Type Name Description string rootCertificateName Name of the root certificate. string rootCertificateIssuerName Name of the root certificate issuer. bool userTrustRootCertificate When true (the default), EnsureRootCertificate() installs the MITM root into the current-user Personal and Trusted Root stores. Prefer user-only trust for interactive apps; pass false when trust must be fully opt-in. bool machineTrustRootCertificate When true, also trust in the local-machine stores (needs elevation). Defaults to false — machine trust is opt-in for services/admin installs, not for normal desktop use. bool trustRootCertificateAsAdmin When true, attempt elevated trust via UAC (Windows only). Defaults to false. Fields | Edit this page View Source DefaultViaHeaderPseudonym Default Via header pseudonym (RFC 9110 §7.6.3). Used by ViaHeaderPseudonym and by Inspector when Add Via header is enabled. Declaration public const string DefaultViaHeaderPseudonym = \"titanium-web-proxy\" Field Value Type Description string Properties | Edit this page View Source AdmittedClientConnectionCount Number of client connections currently admitted (accepted and past the admission gate, not yet finished being handled), across all TCP-based endpoints. Unlike ClientConnectionCount, this drops to zero as soon as the handler returns, without the trailing TIME_WAIT delay. Declaration public int AdmittedClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source BlockLoopbackDestinations Outbound destination policy hook: when true, every resolved destination IP address is checked for loopback (127.0.0.0/8, ::1, including IPv4-mapped forms such as ::ffff:127.0.0.1) before connecting, and the connection attempt is rejected with an OutboundDestinationBlockedException if it matches. Private (RFC 1918), link-local, and cloud metadata addresses (including 169.254.169.254) remain reachable under this flag alone. Off by default. Use when the proxy must allow intranet destinations (for example 10.*) but must still stop clients from pivoting to the proxy host's own loopback. BlockPrivateNetworkDestinations already covers loopback as part of its broader set; when that flag is enabled, this property is not consulted for the reject. Assigning Profile does not read or clear this property. Enforced on TCP outbound connects only (the same call sites as BlockPrivateNetworkDestinations). Origin HTTP/3 (QUIC) dials a hostname and is not covered. An explicitly configured upstream proxy address is always exempt. Not currently enforced for a SOCKS upstream with ProxyDnsRequests enabled. Declaration public bool BlockLoopbackDestinations { get; set; } Property Value Type Description bool | Edit this page View Source BlockPrivateNetworkDestinations Outbound destination policy hook: when true, every resolved destination IP address is checked against loopback, private (RFC 1918/4193), link-local (which subsumes the 169.254.169.254 cloud metadata endpoint), and other non-globally-routable ranges before connecting, and the connection attempt is rejected with an OutboundDestinationBlockedException if it matches. Off by default: blocking private destinations would break this library's most common configurations, including upstream-proxy chaining to localhost and interception of local development servers. Only enable this when the proxy accepts requests from untrusted clients (an SSRF-relevant deployment), where those same destinations become an attacker-reachable pivot into the host's private network instead of an operator's own intentional configuration. An explicitly configured upstream proxy address (UpStreamHttpProxy, UpStreamHttpsProxy, or a per-session external proxy) is always exempt - that address is operator intent, not attacker-controlled. Checked against the resolved address actually used to connect (no re-resolution afterward, which would make the check a TOCTOU no-op against DNS rebinding). Not currently enforced for a SOCKS upstream with ProxyDnsRequests enabled, since the proxy never resolves the origin itself in that mode and has no address of its own to validate. Declaration public bool BlockPrivateNetworkDestinations { get; set; } Property Value Type Description bool | Edit this page View Source BufferPool The buffer pool used throughout this proxy instance. Set custom implementations by implementing this interface. By default this uses DefaultBufferPool implementation available in StreamExtended library package. Buffer size should be at least 10 bytes. Declaration public IBufferPool BufferPool { get; set; } Property Value Type Description IBufferPool | Edit this page View Source CertificateManager Manages certificates used by this proxy. Declaration public CertificateManager CertificateManager { get; } Property Value Type Description CertificateManager | Edit this page View Source CheckCertificateRevocation Should we check for certificate revocation during SSL authentication to servers Note: If enabled can reduce performance. Defaults to false. Declaration public X509RevocationMode CheckCertificateRevocation { get; set; } Property Value Type Description X509RevocationMode | Edit this page View Source ClientConnectionCount Total number of active TCP client connections. Does not include inbound HTTP/3 (QUIC) clients; see Http3ClientConnectionCount. Declaration public int ClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source ClientHeaderTimeoutSeconds Seconds to wait for a client to finish sending the request line and headers, from the moment this proxy starts reading a new request on the connection. Enforced with a linked CancellationTokenSource around the request-line and header read, not Socket.ReceiveTimeout: that property only bounds a single blocking Receive call, not the asynchronous reads this proxy actually issues, so without this deadline a client that opens a connection and trickles bytes arbitrarily slowly (or stops sending entirely) after the first byte ties up a read loop indefinitely. Default is 0 (disabled), matching every other deadline in this class - no per-session override exists because there is no SessionEventArgs for this request yet at the point this deadline applies. Declaration public int ClientHeaderTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source CompatibilityMode100Continue When true, the proxy immediately responds with a synthetic 100 Continue to any client request carrying Expect: 100-continue, before forwarding the headers to the origin and without waiting for the origin to respond. This breaks the strict handshake (client → proxy 100 → client body → origin body) but prevents the deadlock that occurs with strict clients when Enable100ContinueBehaviour is false (the default). Has no effect when Enable100ContinueBehaviour is true. Default: false. Declaration public bool CompatibilityMode100Continue { get; set; } Property Value Type Description bool | Edit this page View Source ConnectTimeOutSeconds Seconds server connection are to wait for connection to be established. Default value is 20 seconds. Declaration public int ConnectTimeOutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ConnectionTimeOutSeconds Seconds client/server connection are to be kept alive when waiting for read/write to complete. This will also determine the pool eviction time when connection pool is enabled. Default value is 60 seconds. Declaration public int ConnectionTimeOutSeconds { get; set; } Property Value Type Description int | Edit this page View Source CustomUpStreamProxyFailureFunc A callback to provide a chance for an upstream proxy failure to be handled by a new upstream proxy. User should return the ExternalProxy object with valid credentials or null. Declaration public Func>? CustomUpStreamProxyFailureFunc { get; set; } Property Value Type Description Func> | Edit this page View Source DecryptFailureBypassMaxEntries Maximum learned hosts retained (approximate LRU eviction). Default 256. Declaration public int DecryptFailureBypassMaxEntries { get; set; } Property Value Type Description int | Edit this page View Source DecryptFailureBypassThreshold Origin TLS failure strikes required before a host is bypassed on later CONNECTs. Same-CONNECT opaque fallback after an awaited H2 probe failure marks bypass immediately. Default 2. Declaration public int DecryptFailureBypassThreshold { get; set; } Property Value Type Description int | Edit this page View Source DecryptFailureBypassTtl How long a learned decrypt-bypass entry remains valid. Default 30 minutes. Declaration public TimeSpan DecryptFailureBypassTtl { get; set; } Property Value Type Description TimeSpan | Edit this page View Source DnsServerEndPoint DNS server endpoint used by Titanium.Web.Proxy.Http3.Dns.UdpSvcbDnsResolver for HTTPS/SVCB queries. Defaults to the first usable OS-configured plain-UDP DNS server discovered via NetworkInterface. This is a best-effort default and does not honor Windows NRPT, DoH, or VPN split-DNS policy. When no OS-configured DNS server can be discovered, the property reports 0.0.0.0:0 and proactive SVCB discovery is skipped (never falls back to a public third-party resolver). Assign an explicit endpoint to override discovery. Declaration [Experimental(\"TWP001\")] public IPEndPoint DnsServerEndPoint { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source Enable100ContinueBehaviour Does this proxy uses the HTTP protocol 100 continue behaviour strictly? Broken 100 continue implementations on server/client may cause problems if enabled. Defaults to false. Declaration public bool Enable100ContinueBehaviour { get; set; } Property Value Type Description bool | Edit this page View Source EnableConnectionPool Should we enable the server connection pool. Defaults to true. When connection pooling is enabled, instead of creating a new TCP connection to the server for each client TCP connection, we check if an idle server connection is available in our cached pool. If a compatible connection (same destination, scheme, upstream proxy, credentials and negotiated protocol) created from an earlier request is available, we reuse it. Only connections that are safe to reuse under the HTTP protocol are pooled: the response body must be fully received and the connection must be persistent (HTTP/1.1 keep-alive, or an HTTP/1.0 connection that explicitly opted in via \"Connection: keep-alive\"). Connections whose response asked to close, that failed, or that carry connection-oriented authentication state (WinAuth NTLM/Negotiate) or a per-session client certificate are never returned to the shared pool. The ConnectionTimeOutSeconds parameter determines the eviction time for inactive server connections. This reduces TCP (and TLS) connection establishment cost, both in wall clock time and CPU cycles. Set to false to force a fresh server connection for every client connection. Declaration public bool EnableConnectionPool { get; set; } Property Value Type Description bool | Edit this page View Source EnableDecryptFailureBypass When true, the proxy learns hosts whose origin TLS handshake fails under MITM (non-ALPN AuthenticationException, typically bot / TLS-fingerprint rejection) and tunnels subsequent CONNECTs without decrypt. Default false so library and RPS baselines are unchanged. Inspector enables this by default. Success-path cost when on is one dictionary lookup per CONNECT. Declaration public bool EnableDecryptFailureBypass { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttp2 Enable disable HTTP/2 support. Client-facing HTTP/2 is negotiated via TLS ALPN, or as prior-knowledge cleartext h2c on a transparent reverse endpoint (DecryptSsl: false). No Upgrade: h2c. Origin-facing HTTP/2 uses TLS ALPN h2 by default; with ForwardCleartext and Http2, the origin speaks cleartext HTTP/2 prior-knowledge (outbound h2c). A client/server that does not support HTTP/2 transparently falls back to HTTP/1.1 when policy allows. Request/response header and body modification in BeforeRequest/BeforeResponse, chunked trailers, interim (1xx) responses, and the synthetic-response APIs (Ok/Respond/Redirect/GenericResponse/ RespondStreaming) are all supported over HTTP/2, the same as over HTTP/1.x. Not supported: HTTP/2 server push (the wire frames are transcoded but there is no public API to originate a push) and Upgrade: h2c. Explicit-proxy inbound h2c is not implemented. See the protocol support matrix on the wiki for exact, up-to-date HTTP/1.x/HTTP/2 feature coverage. Declaration public bool EnableHttp2 { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttp3 Enable HTTP/3 (QUIC) support. When true: Any TransparentQuicProxyEndPoint is started as a UDP-only QUIC listener for transparent/NAT HTTP/3 interception. Any TransparentProxyEndPoint with EnableHttp3 also listens for HTTP/3 on the same IP:port (TCP H1/H2 + UDP H3) and injects client-facing Alt-Svc. With Auto (default), a cached Alt-Svc / HTTPS/SVCB capability selects outbound HTTP/3 on the next CONNECT or new HTTP/1.1 request. Background QUIC warm-up starts when the cache is filled so that handshake is often already done. An already-open H2↔H2 MITM session is not upgraded mid-connection. Forced Http3 fails closed with no TCP fallback. Requires MsQuic native library and a supported operating-system version (IsSupported). Setting to true with no inbound HTTP/3 endpoint is fine when an explicit/SOCKS/transparent TCP endpoint is present (origin-side QUIC only). A warning is emitted only when EnableHttp3 is set with no client-facing endpoints at all. Default: false (opt-in). Experimental: HTTP/3 support has not yet completed the full interop/soak/fuzz gate process. Suppress TWP001 to opt in; the attribute is removed when the feature graduates to stable. Declaration [Experimental(\"TWP001\")] public bool EnableHttp3 { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttpInterception Forces the full interception path (SessionEventArgs, BeforeRequest, etc.) even when no event handlers are subscribed. Set this when consuming SessionEventArgs for timing or metrics without subscribing to any event. Default: false. Declaration public bool EnableHttpInterception { get; set; } Property Value Type Description bool | Edit this page View Source EnableHttpsSvcbDnsDiscovery When true, the proxy queues a background HTTPS/SVCB RR (DNS type 65) lookup after an Auto-mode capability-cache miss. A positive result (ALPN h3 found) warms Titanium.Web.Proxy.Http3.Http3OriginCapabilityCache for subsequent connections; the CONNECT / request path itself never awaits DNS. Negative results are cached for 1 minute; transient failures use a short backoff. Defaults to true whenever EnableHttp3 is true. Set explicitly to false to disable discovery even when HTTP/3 is enabled — for example, when the configured DNS server is untrusted or unreachable. First-connection HTTP/3 adoption then comes from Alt-Svc. Declaration [Experimental(\"TWP001\")] public bool EnableHttpsSvcbDnsDiscovery { get; set; } Property Value Type Description bool | Edit this page View Source EnableIpv6UnreachableSoftSkip When true (default), after one IPv6 connect failure with NetworkUnreachable (or equivalent), temporarily omit IPv6 addresses from the Happy Eyeballs race for 30 seconds. Reduces first-chance SocketException noise on dual-stack hosts with a broken IPv6 path. Disable if operators require strict IPv6 preference even when the path is unreachable. Declaration public bool EnableIpv6UnreachableSoftSkip { get; set; } Property Value Type Description bool | Edit this page View Source EnableQpackDynamicTable When true, enables RFC 9204 QPACK dynamic table encoding and decoding for inbound HTTP/3 connections. Each connection gets its own Titanium.Web.Proxy.Http3.Qpack.QpackContext with two independent 4096-byte tables (one inbound, one outbound). Defaults to false (static-table-only); existing deployments are unaffected. Declaration [Experimental(\"TWP001\")] public bool EnableQpackDynamicTable { get; set; } Property Value Type Description bool | Edit this page View Source EnableRequestTimingCapture Enables structured request/connection timing capture. When false (the default) no timing objects are allocated and no UtcNow calls are made for timing purposes anywhere in the proxy, so there is zero overhead on the hot path. When enabled, every SessionEventArgsBase exposes a populated Timing (per-request phases: client header read, connection wait, request send, time-to-first-byte, response delivery, total), every upstream connection exposes a populated UpstreamConnectionTiming (reachable from a session via UpstreamConnectionTiming, describing DNS, TCP connect, optional upstream-proxy CONNECT, and TLS handshake durations), and a decrypted TunnelConnectSessionEventArgs exposes the client-facing TLS handshake duration via ClientTlsTiming. Can be toggled at any time; it only affects sessions/connections created after the change, never mutating timing objects already handed out. Defaults to false. Declaration public bool EnableRequestTimingCapture { get; set; } Property Value Type Description bool | Edit this page View Source EnableRfc8441 When true, the proxy enables RFC 8441 WebSocket-over-HTTP/2: Accepts extended CONNECT (:protocol = websocket) from h2 clients and advertises SETTINGS_ENABLE_CONNECT_PROTOCOL=1 to them. Per origin: if the origin is HTTP/2 and advertises RFC 8441 support, DATA frames are relayed directly; if the origin is HTTP/2 and does not, the stream is reset with REFUSED_STREAM; if the origin is HTTP/1.1, the h2→h1 WebSocket upgrade bridge is used. On the HTTP/1.1-client-to-h2-origin translation bridge, translates Upgrade: websocket into extended CONNECT when the origin advertises the setting; otherwise falls back to a dedicated HTTP/1.1 origin connection for that WebSocket. When this property is false, that bridge still returns synthetic 501 Not Implemented for WebSocket upgrades (historical default). Default: false (must opt-in). Declaration public bool EnableRfc8441 { get; set; } Property Value Type Description bool | Edit this page View Source EnableTcpKeepAlive Enable TCP KeepAlive on client and server sockets so NAT/firewall mappings for long-lived CONNECT tunnels are refreshed. Default: true. Declaration public bool EnableTcpKeepAlive { get; set; } Property Value Type Description bool | Edit this page View Source EnableTcpServerConnectionPrefetch Should we enable tcp server connection prefetching? When enabled, as soon as we receive a client connection we concurrently initiate corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request we will have the server connection immediately ready or in the process of getting ready. If a server connection is available in cache then this prefetch task will immediately return with the available connection from cache. Defaults to true. Declaration public bool EnableTcpServerConnectionPrefetch { get; set; } Property Value Type Description bool | Edit this page View Source EnableWinAuth Enable disable Windows Authentication (NTLM/Kerberos). By default SSPI uses the process identity. To authenticate as another user, set WinAuthCredentialsProvider (issue #461). Defaults to false. Declaration public bool EnableWinAuth { get; set; } Property Value Type Description bool | Edit this page View Source EndpointAdmissionRejectionCount Total number of client connections rejected by any endpoint's MaxConcurrentClients since this instance was created. Declaration public long EndpointAdmissionRejectionCount { get; } Property Value Type Description long | Edit this page View Source ForwardToUpstreamGateway Gets or sets a value indicating whether requests will be chained to upstream gateway. Defaults to false. Declaration public bool ForwardToUpstreamGateway { get; set; } Property Value Type Description bool | Edit this page View Source GetCustomUpStreamProxyFunc A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests. User should return the ExternalProxy object with valid credentials. Declaration public Func>? GetCustomUpStreamProxyFunc { get; set; } Property Value Type Description Func> | Edit this page View Source GlobalAdmissionRejectionCount Total number of client connections rejected by MaxConcurrentClientConnections since this instance was created. Declaration public long GlobalAdmissionRejectionCount { get; } Property Value Type Description long | Edit this page View Source Http3ClientConnectionCount Total number of active inbound HTTP/3 (QUIC) client connections. Declaration public int Http3ClientConnectionCount { get; } Property Value Type Description int | Edit this page View Source Http3ServerConnectionCount Total number of active upstream HTTP/3 (QUIC) server connections. These are also included in ServerConnectionCount. Declaration public int Http3ServerConnectionCount { get; } Property Value Type Description int | Edit this page View Source HttpResponseCacheFiller Fills the GET/HEAD response cache from a session that is already on the interception path. Does not subscribe BeforeResponse or AfterResponse, so it does not turn NeedsHttpInterception(ProxyEndPoint) on. Declaration public Action? HttpResponseCacheFiller { get; set; } Property Value Type Description Action | Edit this page View Source IdleReadTimeoutSeconds Seconds of idle time allowed while reading from the origin (stalled header/body waits). Applied via CancelAfter on the active read operation. Default is 0 (disabled). Per-session override: IdleReadTimeout. Declaration public int IdleReadTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source IdleWriteTimeoutSeconds Seconds of idle time allowed while writing to the origin (stalled header/body waits). Applied via CancelAfter on the active write operation. Default is 0 (disabled). Per-session override: IdleWriteTimeout. Declaration public int IdleWriteTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source IgnoreServerCertificateErrors When true, origin TLS certificates that fail OS chain validation are still accepted (MITM of loopback/self-signed/private CAs). Inspector's \"Ignore server certificate errors\" maps here. Default false. A subscribed ServerCertificateValidationCallback still wins. Declaration public bool IgnoreServerCertificateErrors { get; set; } Property Value Type Description bool | Edit this page View Source ListenerBackLog TCP listener accept backlog. Default: 1024 for burst connection handling. Declaration public int ListenerBackLog { get; set; } Property Value Type Description int | Edit this page View Source Logger The live, shared logger used throughout this proxy instance. Reflects the most recent call to ApplyLoggingConfiguration(). Declaration public ILogger Logger { get; } Property Value Type Description ILogger | Edit this page View Source Logging Configuration for this proxy instance's built-in diagnostic logging - the replacement for the removed ExceptionFunc callback. Every exception the proxy catches (even when handled internally and never surfaced to user code) is reported through this logger at an appropriate severity; see ProxyLoggingOptions for the console/file sinks, enable/disable switch, and minimum level. Mutate the returned instance (or assign a new one) at any point; each assignment/mutation you want to take effect must be followed by ApplyLoggingConfiguration() (which Start(bool) also calls automatically, so the configuration active at the moment the proxy starts running is picked up for the run even if you never call it yourself). Calling it again later - including while the proxy is already running - immediately swaps in the new configuration; this is safe because logging never blocks or otherwise affects proxy traffic. Declaration public ProxyLoggingOptions Logging { get; set; } Property Value Type Description ProxyLoggingOptions | Edit this page View Source MaxBufferedBodyBytes Maximum bytes the proxy will buffer for a single request or response body when whole-body buffering is required (GetRequestBody / GetResponseBody, authentication retry, and other paths that materialize the body). Bodies larger than this limit are rejected with 413 (upstream request) or connection teardown (upstream response). Streaming relays (H1, H2, and H3, including bridges) are not counted against this budget: they are bounded by protocol flow control. Set to 0 to disable the limit (not recommended). Default: 4,194,304 (4 MiB). Declaration public int MaxBufferedBodyBytes { get; set; } Property Value Type Description int | Edit this page View Source MaxCachedConnections Maximum number of concurrent connections per remote host in cache. Only meaningful when EnableConnectionPool is true; to disable pooling, set EnableConnectionPool to false rather than setting this to 0 - the pool eviction loop treats a value below 1 as \"evict without limit while holding the pool-wide lock\", which spins indefinitely once the cache for that host is empty and would stall every other connection acquire/release in the process. Rejected outright at assignment so that state cannot be reached. Default value is 128. Declaration public int MaxCachedConnections { get; set; } Property Value Type Description int Exceptions Type Condition ArgumentOutOfRangeException The assigned value is less than 1. | Edit this page View Source MaxConcurrentClientConnections Maximum number of client connections admitted across all TCP-based endpoints at once. null (the default) disables the global admission gate, preserving today's unbounded behavior. When set, a connection beyond this limit is rejected and disposed immediately after accept, before a handler task is even started. Enforced independently of ClientConnectionCount: see Titanium.Web.Proxy.ProxyServer.admittedClientConnectionCount for why. See also MaxConcurrentClients for a per-endpoint cap layered on top of this global one. Declaration public int? MaxConcurrentClientConnections { get; set; } Property Value Type Description int? | Edit this page View Source MaxConcurrentHttp11HttpsOriginCreates Caps concurrent new HTTPS origin TCP/TLS opens on the H2→H1 bridge only (MITM / re-encrypt). Pool hits (warm keep-alive) are uncapped. Cleartext H1 origins are not gated. Default is Clamp(ProcessorCount, 4, 32). Set before the first H2→H1 HTTPS origin open (typically before Start(bool)); changing the value after the create gate has been used has no effect on the live semaphore. Declaration public int MaxConcurrentHttp11HttpsOriginCreates { get; set; } Property Value Type Description int Exceptions Type Condition ArgumentOutOfRangeException The assigned value is less than 1. | Edit this page View Source MaxDecodedHeaderListBytes Maximum decoded HTTP/2 header list size in bytes, using RFC 7541 accounting (name.Length + value.Length + 32 per field). Requests or responses with a decoded header list exceeding this limit will be refused with RST_STREAM(ENHANCE_YOUR_CALM) (code 0xb). Set to 0 to disable the limit (not recommended). Default: 65,536 (64 KiB). Advertised via SETTINGS_MAX_HEADER_LIST_SIZE. Declaration public int MaxDecodedHeaderListBytes { get; set; } Property Value Type Description int | Edit this page View Source MaxWebSocketFramePayloadBytes Maximum WebSocket frame payload size in bytes that the proxy will accept during frame-level interception (i.e. when BeforeWebSocketFrame has at least one subscriber). Frames whose decoded payload exceeds this limit are handled by WebSocketFrameBudget. Enforce (the default) closes the connection with Close code 1009 (Message Too Big) before the payload is buffered. Observe relays the frame unbuffered and raises WebSocketFrameElided instead of invoking BeforeWebSocketFrame. Raw-relay sessions (no BeforeWebSocketFrame subscriber) bypass this check entirely and pass all frames through unvalidated. Default: 16,777,216 (16 MiB). Declaration public int MaxWebSocketFramePayloadBytes { get; set; } Property Value Type Description int | Edit this page View Source NetworkFailureRetryAttempts Number of times to retry upon network failures when connection pool is enabled. Declaration public int NetworkFailureRetryAttempts { get; set; } Property Value Type Description int | Edit this page View Source NoDelay Gets or sets a Boolean value that specifies whether server and client stream Sockets are using the Nagle algorithm. Defaults to true, no nagle algorithm is used. Declaration public bool NoDelay { get; set; } Property Value Type Description bool | Edit this page View Source OriginHttpVersionPolicy Controls which HTTP version is declared to the origin server on the request line, independently of the version the client declared to the proxy. Defaults to PreserveClientVersion, which matches the proxy's historical pass-through behavior exactly. Set to NormalizeToHttp11 to let HTTP/1.0 clients share pooled, persistent origin connections the same way HTTP/1.1 clients already do. This only changes the wire version written to the origin request line - it never changes the client-facing Http.Request.HttpVersion that event handlers observe, nor the version/persistence used to write the response back to the client. Declaration public OriginHttpVersionPolicy OriginHttpVersionPolicy { get; set; } Property Value Type Description OriginHttpVersionPolicy | Edit this page View Source PolicyModes Which resource-bound PolicyFamily is enforced, observed, or disabled, per the plan's rollout section. Read live by each family's enforcement call site - not baked into a per-request snapshot at connection accept time - so assigning a new value here (a whole-object replacement, never a mutation of the previous instance) takes effect for the next check any in-flight or new request makes, without restarting the proxy. This is the \"runtime switch to drop to Observe without redeploying\" the plan requires; see WithAllObservedExceptDisabled() for the one-call way to do that. Defaults to Balanced's modes (resource families enforced, Http2RelayValidation disabled so compressed H2 relay stays the verbatim-HPACK fast path). AllEnforce additionally enforces relay validation and is what PublicFacing applies. Assigning Profile also replaces this value with that profile's bundle; assign PolicyModes afterward to deviate from the selected profile's modes without changing anything else the profile set. Declaration public ProxyPolicyModes PolicyModes { get; set; } Property Value Type Description ProxyPolicyModes | Edit this page View Source Profile The last profile applied via this property's setter, defaulting to Balanced - the profile every field on this instance already starts at, so a fresh new ProxyServer() reports Balanced without needing its setter to run once at construction time. Assigning this property applies its entire ProxyProfileSettings bundle - ResourceLimits, PolicyModes, SupportedSslProtocols, BlockPrivateNetworkDestinations, MaxConcurrentClientConnections and the deadline-seconds properties - as a single atomic assignment, so a reader can never observe a half-applied profile. Assigning any of those properties individually afterward overrides just that one, without reverting the rest of the profile's bundle. Logged once per Start(bool) call, by name only - never with hosts, URLs or secrets, per the plan's rollout section. Declaration public ProxyProfile Profile { get; set; } Property Value Type Description ProxyProfile | Edit this page View Source ProxyAuthenticationRealm Realm used during Proxy Basic Authentication. Declaration public string ProxyAuthenticationRealm { get; set; } Property Value Type Description string | Edit this page View Source ProxyAuthenticationSchemes A collection of scheme types, e.g. basic, NTLM, Kerberos, Negotiate, to return if scheme authentication is required. Works in relation with ProxySchemeAuthenticateFunc. Declaration public IEnumerable ProxyAuthenticationSchemes { get; set; } Property Value Type Description IEnumerable | Edit this page View Source ProxyBasicAuthenticateFunc A callback to authenticate proxy clients via basic authentication. Parameters are username and password as provided by client. Should return true for successful authentication. Declaration public Func>? ProxyBasicAuthenticateFunc { get; set; } Property Value Type Description Func> | Edit this page View Source ProxyEndPoints A list of IpAddress and port this proxy is listening to. Declaration public List ProxyEndPoints { get; set; } Property Value Type Description List | Edit this page View Source ProxyRunning Is the proxy currently running? Declaration public bool ProxyRunning { get; } Property Value Type Description bool | Edit this page View Source ProxySchemeAuthenticateFunc A pluggable callback to authenticate clients by scheme instead of requiring basic authentication through ProxyBasicAuthenticateFunc. Parameters are current working session, schemeType, and token as provided by a calling client. Should return success for successful authentication, continuation if the package requests, or failure. Declaration public Func>? ProxySchemeAuthenticateFunc { get; set; } Property Value Type Description Func> | Edit this page View Source RequestTimeoutSeconds Total seconds allowed for a single request/response exchange after BeforeRequest returns (connect, send, wait for headers, and body copy). Default is 0 (disabled). Per-session override: RequestTimeout. Declaration public int RequestTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ResourceLimits The shared, immutable resource-bound snapshot (concurrent-stream cap, CONTINUATION frame-count/wall-clock bounds, peer-initiated incomplete-stream-reset budget, and the other limits described in ProxyResourceLimits) consulted by the HTTP/2 relay so a single proxy-owned value governs both what is enforced and what is advertised to each peer, rather than admitting purely against whatever the origin advertised. Assign a new ProxyResourceLimits (constructed via Create(long, int, long, long?, long?, double?, int?, int, int?, int, TimeSpan, bool, int, int?)) to override the Default snapshot. There is no artificial upper clamp: high-CPU/RAM hosts may pass larger maxCachedConnectionsPerHost, maxConcurrentStreamsPerConnection, etc. as needed. The live TCP pool depth knob MaxCachedConnections remains independently settable and should usually be kept in sync with MaxCachedConnectionsPerHost. Declaration public ProxyResourceLimits ResourceLimits { get; set; } Property Value Type Description ProxyResourceLimits | Edit this page View Source ResponseHeaderContributor Adds response headers on the way to the client without subscribing BeforeResponse. Plus CORS uses this so a listener can stay on H1 terminate-lite. Declaration public Action? ResponseHeaderContributor { get; set; } Property Value Type Description Action | Edit this page View Source ResponseHeaderTimeoutSeconds Seconds to wait for the origin to send the response status line and headers after the request has been sent. Enforced with a linked CancellationTokenSource (not Socket receive timeout alone). When the deadline elapses a ProxyTimeoutException with ResponseHeader is raised (and may be converted to HTTP 504 before any response bytes have been committed to the client). Default is 0 (disabled). WebSocket upgrades, Server-Sent Events, raw tunnels, and sessions that already wrote a response status to the client are exempt; those waits use IdleReadTimeoutSeconds when configured. Per-session override: ResponseHeaderTimeout. Declaration public int ResponseHeaderTimeoutSeconds { get; set; } Property Value Type Description int | Edit this page View Source ReuseSocket When true (default), SO_REUSEADDR is requested where Titanium.Web.Proxy.Helpers.RunTime.IsSocketReuseAvailable() reports support (always on Windows; on non-Windows, .NET Core 3+ / compatible runtimes). Declaration public bool ReuseSocket { get; set; } Property Value Type Description bool | Edit this page View Source ReverseProxy Optional reverse-proxy route/cluster configuration. When null (default), Core keeps 6.x ForwardHost behavior with zero added cost on the hot path. Declaration public ReverseProxyOptions? ReverseProxy { get; set; } Property Value Type Description ReverseProxyOptions | Edit this page View Source ServerConnectionCount Total number of active server connections (TCP plus upstream QUIC). For HTTP/3-only upstreams see Http3ServerConnectionCount. Declaration public int ServerConnectionCount { get; } Property Value Type Description int | Edit this page View Source ShouldInterceptHttp Optional per-request/stream predicate consulted only when the global interception gate is active. Return true to use the full SessionEventArgs path; return false to use the fast-forward path. null (the default) intercepts every request — preserving today's behavior. Declaration public Func? ShouldInterceptHttp { get; set; } Property Value Type Description Func | Edit this page View Source SupportedServerSslProtocols Ssl versions offered on outbound HTTPS connections to origins (and upstream proxies). Default None means “use SupportedSslProtocols” (typically TLS 1.2 and 1.3). Set an explicit mask to restrict or expand outbound-only independently of inbound client TLS. Older docs described None as “same as the proxy client.” That coupling is incorrect across protocol translations (e.g. inbound QUIC is always TLS 1.3 while outbound TCP SslStream on macOS SecureTransport cannot offer TLS 1.3). Declaration public SslProtocols SupportedServerSslProtocols { get; set; } Property Value Type Description SslProtocols | Edit this page View Source SupportedSslProtocols List of supported Ssl versions. Defaults to TLS 1.2/1.3 only as of 5.0 - a breaking change from 4.x, which also enabled SSL 3.0/TLS 1.0/1.1. Those legacy, broken-by-design protocols require an explicit opt-in by assigning this property directly (e.g. SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13) if a legacy client/server genuinely requires them. Declaration public SslProtocols SupportedSslProtocols { get; set; } Property Value Type Description SslProtocols | Edit this page View Source TcpTimeWaitSeconds SO_LINGER timeout in seconds applied to client and upstream sockets via LingerOption (enabled with this timeout). This is not the kernel TCP TIME_WAIT duration — TIME_WAIT is controlled by the OS. A positive value means Close may block up to that many seconds flushing send buffers; use 0 for an abortive close (RST). Default is 0 so high-churn proxies avoid TIME_WAIT accumulation; the 1-second connection disposal delay already prefers peer-first close. Declaration public int TcpTimeWaitSeconds { get; set; } Property Value Type Description int | Edit this page View Source ThreadPoolWorkerThread Customize the minimum ThreadPool size (increase it on a server). Defaults to max(ProcessorCount * 2, 16) so short loopback/proxy workloads are not starved while the pool is still ramping workers. Declaration public int ThreadPoolWorkerThread { get; set; } Property Value Type Description int | Edit this page View Source UpStreamEndPoint Local adapter/NIC endpoint where proxy makes request via. Defaults via any IP addresses of this machine. When the resolved destination address family does not match this endpoint, it is ignored so dual-stack destinations can still connect (see UpStreamEndPointIPv4 / UpStreamEndPointIPv6). Declaration public IPEndPoint? UpStreamEndPoint { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamEndPointIPv4 Local bind endpoint used when the resolved upstream destination is IPv4. Takes precedence over UpStreamEndPoint for IPv4 destinations. Declaration public IPEndPoint? UpStreamEndPointIPv4 { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamEndPointIPv6 Local bind endpoint used when the resolved upstream destination is IPv6. Takes precedence over UpStreamEndPoint for IPv6 destinations. Declaration public IPEndPoint? UpStreamEndPointIPv6 { get; set; } Property Value Type Description IPEndPoint | Edit this page View Source UpStreamHttpProxy External proxy used for Http requests. Declaration public IExternalProxy? UpStreamHttpProxy { get; set; } Property Value Type Description IExternalProxy | Edit this page View Source UpStreamHttpsProxy External proxy used for Https requests. Declaration public IExternalProxy? UpStreamHttpsProxy { get; set; } Property Value Type Description IExternalProxy | Edit this page View Source UpstreamProxyConfigurationScript If set, the upstream proxy will be detected by a script that will be loaded from the provided Uri Declaration public Uri? UpstreamProxyConfigurationScript { get; set; } Property Value Type Description Uri | Edit this page View Source ViaHeaderPseudonym Pseudonym used in Via header fields appended to forwarded requests and responses (RFC 9110 §7.6.3). Defaults to DefaultViaHeaderPseudonym. Set to an empty string to disable Via header injection entirely. Loop detection uses this value: a request arriving with this pseudonym already present in Via is refused with 508 Loop Detected. Declaration public string ViaHeaderPseudonym { get; set; } Property Value Type Description string | Edit this page View Source WinAuthCredentialsProvider Optional per-session credential provider for server 401 WinAuth (NTLM/Negotiate/Kerberos). Return null to use the current process identity (legacy behavior). Do not put plaintext passwords on SessionEventArgs — use this callback instead. Windows SSPI only; ignored on non-Windows platforms. Declaration public Func>? WinAuthCredentialsProvider { get; set; } Property Value Type Description Func> Methods | Edit this page View Source AddEndPoint(ProxyEndPoint) Add a proxy end point. Declaration public void AddEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The proxy endpoint. | Edit this page View Source ApplyLoggingConfiguration() Rebuilds the active logger/logger factory from the current Logging configuration, disposing any previously owned built-in providers. Called automatically from the constructor (with the default configuration) and from Start(bool). Call this explicitly any time after changing Logging and you want the change to take effect immediately - whether the proxy is stopped (e.g. before using CertificateManager directly) or already running. Declaration public void ApplyLoggingConfiguration() | Edit this page View Source ClearDecryptFailureBypass() Clears all learned decrypt-bypass entries. Declaration public void ClearDecryptFailureBypass() | Edit this page View Source DisableAllSystemProxies() Clear all proxy settings for current machine. Declaration public void DisableAllSystemProxies() | Edit this page View Source DisableSystemHttpProxy() Clear HTTP proxy settings of current machine. Declaration public void DisableSystemHttpProxy() | Edit this page View Source DisableSystemHttpsProxy() Clear HTTPS proxy settings of current machine. Declaration public void DisableSystemHttpsProxy() | Edit this page View Source DisableSystemProxy(ProxyProtocolType) Clear the specified proxy setting for current machine. Declaration public void DisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType | Edit this page View Source Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. Declaration public void Dispose() | Edit this page View Source Dispose(bool) Declaration [SuppressMessage(\"ApiDesign\", \"RS0016:Add public types and members to the declared API\", Justification = \"Protected Dispose(bool) is required by the standard IDisposable pattern but is not public API.\")] protected virtual void Dispose(bool disposing) Parameters Type Name Description bool disposing | Edit this page View Source ForceDecryptFailureBypass(string) Marks host as actively bypassed (same as a forced learn after origin TLS failure). Raises DecryptFailureBypassChanged when bypass newly becomes active. Declaration public bool ForceDecryptFailureBypass(string host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source GetDecryptFailureBypassEntries() Snapshot of current learned decrypt-bypass entries (may include non-active strikes). Declaration public IReadOnlyList GetDecryptFailureBypassEntries() Returns Type Description IReadOnlyList | Edit this page View Source RemoveDecryptFailureBypass(string) Removes one host from the learned decrypt-bypass cache. Declaration public bool RemoveDecryptFailureBypass(string host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source RemoveEndPoint(ProxyEndPoint) Remove a proxy end point. Will throw error if the end point doesn't exist. Declaration public void RemoveEndPoint(ProxyEndPoint endPoint) Parameters Type Name Description ProxyEndPoint endPoint The existing endpoint to remove. | Edit this page View Source RestoreOriginalProxySettings() Restores the original proxy settings. Declaration public void RestoreOriginalProxySettings() | Edit this page View Source SetAsSystemHttpProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. | Edit this page View Source SetAsSystemHttpProxy(ExplicitProxyEndPoint, SystemProxySettings) Set the given explicit end point as the default HTTP proxy server for current machine. Declaration public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint, SystemProxySettings settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SystemProxySettings settings The Windows system proxy settings. | Edit this page View Source SetAsSystemHttpsProxy(ExplicitProxyEndPoint) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. | Edit this page View Source SetAsSystemHttpsProxy(ExplicitProxyEndPoint, SystemProxySettings) Set the given explicit end point as the default HTTPS proxy server for current machine. Declaration public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint, SystemProxySettings settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. SystemProxySettings settings The Windows system proxy settings. | Edit this page View Source SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. | Edit this page View Source SetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType, SystemProxySettings?) Set the given explicit end point as the default proxy server for current machine. Declaration public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType, SystemProxySettings? settings) Parameters Type Name Description ExplicitProxyEndPoint endPoint The explicit endpoint. ProxyProtocolType protocolType The proxy protocol type. SystemProxySettings settings The Windows system proxy settings, or null to preserve the current bypass list. | Edit this page View Source SetHttp3Enabled(bool) Enables or disables EnableHttp3. Enabling still requires MsQuic (IsSupported); disabling is always applied. Safe to call while the proxy is running — new origin connections pick up the change. Existing sessions keep the protocol they already negotiated. Declaration public bool SetHttp3Enabled(bool enabled) Parameters Type Name Description bool enabled Returns Type Description bool true when HTTP/3 is enabled after the call. | Edit this page View Source ShouldBypassDecryptForLearnedHost(string?) When EnableDecryptFailureBypass is on and host is actively bypassed, returns true (decrypt should be skipped). Declaration public bool ShouldBypassDecryptForLearnedHost(string? host) Parameters Type Name Description string host Returns Type Description bool | Edit this page View Source Start(bool) Start this proxy server instance. Transactional: if any endpoint fails to start, every listener this call already started is stopped, the system-upstream-proxy resolver (if this call created one) is disposed, and ProxyRunning is left false before the exception propagates. A caller that catches the exception is left with an instance in exactly the same state as before calling Start(bool), not a partially-bound proxy with some endpoints silently listening. Declaration public void Start(bool changeSystemProxySettings = true) Parameters Type Name Description bool changeSystemProxySettings Whether or not clear any system proxy settings which is pointing to our own endpoint (causing a cycle). E.g due to ungracious proxy shutdown before. | Edit this page View Source Stop() Stop this proxy server instance. Endpoints remain registered so Start(bool) can re-listen on the same ports. In-flight sessions are cancelled; pooled upstream connections are cleared. The connection factory itself stays usable for a subsequent Start (it is only disposed with the proxy). Declaration public void Stop() | Edit this page View Source StopAsync(TimeSpan?) Asynchronously stop this proxy server, cancel in-flight sessions, and wait briefly for client connection count to drain before clearing the upstream pool. Declaration public Task StopAsync(TimeSpan? drainTimeout = null) Parameters Type Name Description TimeSpan? drainTimeout Maximum time to wait for active client handlers to exit after cancellation. Defaults to 5 seconds. Returns Type Description Task | Edit this page View Source TryDisableAllSystemProxies() Clear all OS proxy settings without throwing. Declaration public SystemProxyChangeResult TryDisableAllSystemProxies() Returns Type Description SystemProxyChangeResult | Edit this page View Source TryDisableSystemProxy(ProxyProtocolType) Clear OS proxy for the given protocols without throwing. Declaration public SystemProxyChangeResult TryDisableSystemProxy(ProxyProtocolType protocolType) Parameters Type Name Description ProxyProtocolType protocolType Returns Type Description SystemProxyChangeResult | Edit this page View Source TryEnableHttp3IfSupported() Turns on EnableHttp3 when MsQuic is available (IsSupported). Hosts (CLI, Inspector, examples) should call this instead of setting EnableHttp3 blindly. Returns true when HTTP/3 was enabled. Declaration public bool TryEnableHttp3IfSupported() Returns Type Description bool | Edit this page View Source TryRestoreOriginalProxySettings() Restore OS proxy without throwing. Declaration public SystemProxyChangeResult TryRestoreOriginalProxySettings() Returns Type Description SystemProxyChangeResult | Edit this page View Source TrySetAsSystemProxy(ExplicitProxyEndPoint, ProxyProtocolType, SystemProxySettings?) Enable OS system proxy without throwing. Failures are logged and returned so Inspector/CLI can show a status message instead of crashing. Declaration public SystemProxyChangeResult TrySetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType, SystemProxySettings? settings = null) Parameters Type Name Description ExplicitProxyEndPoint endPoint ProxyProtocolType protocolType SystemProxySettings settings Returns Type Description SystemProxyChangeResult Events | Edit this page View Source AfterResponse Intercept after response event from server. Declaration public event AsyncEventHandler? AfterResponse Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeRequest Intercept request event to server. Declaration public event AsyncEventHandler? BeforeRequest Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeResponse Intercept response event from server. Declaration public event AsyncEventHandler? BeforeResponse Event Type Type Description AsyncEventHandler | Edit this page View Source BeforeUpStreamConnectRequest Intercept connect request sent to upstream proxy. Declaration public event AsyncEventHandler? BeforeUpStreamConnectRequest Event Type Type Description AsyncEventHandler | Edit this page View Source ClientCertificateSelectionCallback Event to override client certificate selection during mutual SSL authentication. Declaration public event AsyncEventHandler? ClientCertificateSelectionCallback Event Type Type Description AsyncEventHandler | Edit this page View Source ClientConnectionCountChanged Event occurs when client connection count changed. Declaration public event EventHandler? ClientConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source DecryptFailureBypassChanged Raised when a host becomes actively bypassed (threshold reached or same-CONNECT mark). Handlers must not block; Inspector marshals to the UI thread. Declaration public event EventHandler? DecryptFailureBypassChanged Event Type Type Description EventHandler | Edit this page View Source Http3ClientConnectionCountChanged Event occurs when inbound HTTP/3 client connection count changed. Declaration public event EventHandler? Http3ClientConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source Http3ServerConnectionCountChanged Event occurs when upstream HTTP/3 server connection count changed. Declaration public event EventHandler? Http3ServerConnectionCountChanged Event Type Type Description EventHandler | Edit this page View Source OnClientConnectionCreate Customize TcpClient used for client connection upon create. Declaration public event AsyncEventHandler? OnClientConnectionCreate Event Type Type Description AsyncEventHandler | Edit this page View Source OnRequestBodyWrite Intercept request body send event to server. Subscribe to inspect or modify the request body chunk-by-chunk as it streams to the server, without buffering the whole body. Do not combine with SessionEventArgs.GetRequestBody (which buffers). Declaration public event AsyncEventHandler? OnRequestBodyWrite Event Type Type Description AsyncEventHandler | Edit this page View Source OnResponseBodyWrite Intercept response body send event to client. Subscribe to inspect or modify the response body chunk-by-chunk as it streams to the client, without buffering the whole body. Do not combine with SessionEventArgs.GetResponseBody (which buffers). Declaration public event AsyncEventHandler? OnResponseBodyWrite Event Type Type Description AsyncEventHandler | Edit this page View Source OnServerConnectionCreate Customize TcpClient used for server connection upon create. Declaration public event AsyncEventHandler? OnServerConnectionCreate Event Type Type Description AsyncEventHandler | Edit this page View Source ServerCertificateValidationCallback Event to override the default verification logic of remote SSL certificate received during authentication. Declaration public event AsyncEventHandler? ServerCertificateValidationCallback Event Type Type Description AsyncEventHandler | Edit this page View Source ServerConnectionCountChanged Event occurs when server connection count changed. Declaration public event EventHandler? ServerConnectionCountChanged Event Type Type Description EventHandler Implements IDisposable"
},
"api/Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html": {
"href": "api/Titanium.Web.Proxy.Routing.ReverseProxyFastPath.html",
@@ -672,7 +677,7 @@
"api/Titanium.Web.Proxy.WebSocketFrame.html": {
"href": "api/Titanium.Web.Proxy.WebSocketFrame.html",
"title": "Class WebSocketFrame | Titanium Web Proxy",
- "summary": "Class WebSocketFrame A single decoded WebSocket frame, as produced by Decode(byte[], int, int). Inheritance object WebSocketFrame Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public class WebSocketFrame Remarks Data is an owned copy of the unmasked payload produced by Decode(byte[], int, int), safe to retain after that call returns. Properties | Edit this page View Source Data The unmasked frame payload (owned copy from WebSocketDecoder). Declaration public ReadOnlyMemory Data { get; } Property Value Type Description ReadOnlyMemory | Edit this page View Source IsFinal Declaration public bool IsFinal { get; } Property Value Type Description bool | Edit this page View Source OpCode Declaration public WebsocketOpCode OpCode { get; } Property Value Type Description WebsocketOpCode Methods | Edit this page View Source GetText() Declaration public string GetText() Returns Type Description string | Edit this page View Source GetText(Encoding) Declaration public string GetText(Encoding encoding) Parameters Type Name Description Encoding encoding Returns Type Description string"
+ "summary": "Class WebSocketFrame A single decoded WebSocket frame, as produced by Decode(byte[], int, int). Inheritance object WebSocketFrame Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.MemberwiseClone() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy Assembly: Titanium.Web.Proxy.dll Syntax public class WebSocketFrame Remarks Data is an owned copy of the unmasked payload produced by Decode(byte[], int, int), safe to retain after that call returns. Properties | Edit this page View Source Data The unmasked frame payload (owned copy from WebSocketDecoder). When Titanium.Web.Proxy.WebSocketFrame.RelayRaw is set, this is a slice of the original wire bytes to forward unchanged, not a decoded payload. Declaration public ReadOnlyMemory Data { get; } Property Value Type Description ReadOnlyMemory | Edit this page View Source IsFinal Declaration public bool IsFinal { get; } Property Value Type Description bool | Edit this page View Source OpCode Declaration public WebsocketOpCode OpCode { get; } Property Value Type Description WebsocketOpCode Methods | Edit this page View Source GetText() Declaration public string GetText() Returns Type Description string | Edit this page View Source GetText(Encoding) Declaration public string GetText(Encoding encoding) Parameters Type Name Description Encoding encoding Returns Type Description string"
},
"api/Titanium.Web.Proxy.WebSocketFrameEncoder.html": {
"href": "api/Titanium.Web.Proxy.WebSocketFrameEncoder.html",
diff --git a/docs/xrefmap.yml b/docs/xrefmap.yml
index 07c017c2e..f6be8c6d7 100644
--- a/docs/xrefmap.yml
+++ b/docs/xrefmap.yml
@@ -1685,6 +1685,19 @@ references:
commentId: E:Titanium.Web.Proxy.EventArguments.SessionEventArgs.BeforeWebSocketFrame
fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgs.BeforeWebSocketFrame
nameWithType: SessionEventArgs.BeforeWebSocketFrame
+- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody(Titanium.Web.Proxy.Http.RequestResponseBase)
+ name: CanBufferBody(RequestResponseBase)
+ href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html#Titanium_Web_Proxy_EventArguments_SessionEventArgs_CanBufferBody_Titanium_Web_Proxy_Http_RequestResponseBase_
+ commentId: M:Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody(Titanium.Web.Proxy.Http.RequestResponseBase)
+ fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody(Titanium.Web.Proxy.Http.RequestResponseBase)
+ nameWithType: SessionEventArgs.CanBufferBody(RequestResponseBase)
+- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody*
+ name: CanBufferBody
+ href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html#Titanium_Web_Proxy_EventArguments_SessionEventArgs_CanBufferBody_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody
+ nameWithType: SessionEventArgs.CanBufferBody
- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgs.Dispose(System.Boolean)
name: Dispose(bool)
href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html#Titanium_Web_Proxy_EventArguments_SessionEventArgs_Dispose_System_Boolean_
@@ -2200,6 +2213,12 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketDecoderSend
nameWithType: SessionEventArgs.WebSocketDecoderSend
+- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketFrameElided
+ name: WebSocketFrameElided
+ href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html#Titanium_Web_Proxy_EventArguments_SessionEventArgs_WebSocketFrameElided
+ commentId: E:Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketFrameElided
+ fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketFrameElided
+ nameWithType: SessionEventArgs.WebSocketFrameElided
- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketServerWriter
name: WebSocketServerWriter
href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html#Titanium_Web_Proxy_EventArguments_SessionEventArgs_WebSocketServerWriter
@@ -2840,6 +2859,77 @@ references:
commentId: F:Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.ServerToClient
fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection.ServerToClient
nameWithType: WebSocketFrameDirection.ServerToClient
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs
+ name: WebSocketFrameElidedEventArgs
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html
+ commentId: T:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs
+ nameWithType: WebSocketFrameElidedEventArgs
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+ name: DeclaredPayloadLength
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_DeclaredPayloadLength
+ commentId: P:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+ nameWithType: WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength*
+ name: DeclaredPayloadLength
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_DeclaredPayloadLength_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+ nameWithType: WebSocketFrameElidedEventArgs.DeclaredPayloadLength
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction
+ name: Direction
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_Direction
+ commentId: P:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction
+ nameWithType: WebSocketFrameElidedEventArgs.Direction
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction*
+ name: Direction
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_Direction_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction
+ nameWithType: WebSocketFrameElidedEventArgs.Direction
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal
+ name: IsFinal
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_IsFinal
+ commentId: P:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal
+ nameWithType: WebSocketFrameElidedEventArgs.IsFinal
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal*
+ name: IsFinal
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_IsFinal_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal
+ nameWithType: WebSocketFrameElidedEventArgs.IsFinal
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode
+ name: OpCode
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_OpCode
+ commentId: P:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode
+ nameWithType: WebSocketFrameElidedEventArgs.OpCode
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode*
+ name: OpCode
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_OpCode_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode
+ nameWithType: WebSocketFrameElidedEventArgs.OpCode
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session
+ name: Session
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_Session
+ commentId: P:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session
+ nameWithType: WebSocketFrameElidedEventArgs.Session
+- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session*
+ name: Session
+ href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.html#Titanium_Web_Proxy_EventArguments_WebSocketFrameElidedEventArgs_Session_
+ commentId: Overload:Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session
+ isSpec: "True"
+ fullName: Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session
+ nameWithType: WebSocketFrameElidedEventArgs.Session
- uid: Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction
name: WebSocketFrameInterceptAction
href: api/Titanium.Web.Proxy.EventArguments.WebSocketFrameInterceptAction.html
@@ -8012,6 +8102,12 @@ references:
commentId: F:Titanium.Web.Proxy.Options.PolicyFamily.Http2RelayValidation
fullName: Titanium.Web.Proxy.Options.PolicyFamily.Http2RelayValidation
nameWithType: PolicyFamily.Http2RelayValidation
+- uid: Titanium.Web.Proxy.Options.PolicyFamily.WebSocketFrameBudget
+ name: WebSocketFrameBudget
+ href: api/Titanium.Web.Proxy.Options.PolicyFamily.html#Titanium_Web_Proxy_Options_PolicyFamily_WebSocketFrameBudget
+ commentId: F:Titanium.Web.Proxy.Options.PolicyFamily.WebSocketFrameBudget
+ fullName: Titanium.Web.Proxy.Options.PolicyFamily.WebSocketFrameBudget
+ nameWithType: PolicyFamily.WebSocketFrameBudget
- uid: Titanium.Web.Proxy.Options.PolicyMode
name: PolicyMode
href: api/Titanium.Web.Proxy.Options.PolicyMode.html
diff --git a/src/Titanium.Cli/Config/ServerConfigApplier.cs b/src/Titanium.Cli/Config/ServerConfigApplier.cs
index e3102bff5..599840515 100644
--- a/src/Titanium.Cli/Config/ServerConfigApplier.cs
+++ b/src/Titanium.Cli/Config/ServerConfigApplier.cs
@@ -345,7 +345,10 @@ private static void ApplyPolicyModes(ProxyServer proxy, PolicyModesConfig? polic
ParsePolicyMode(policy.HeaderLimits, current[PolicyFamily.HeaderLimits]),
ParsePolicyMode(policy.AdmissionControl, current[PolicyFamily.AdmissionControl]),
ParsePolicyMode(policy.Http2AbuseBudget, current[PolicyFamily.Http2AbuseBudget]),
- ParsePolicyMode(policy.Http2RelayValidation, current[PolicyFamily.Http2RelayValidation]));
+ ParsePolicyMode(policy.Http2RelayValidation, current[PolicyFamily.Http2RelayValidation]))
+ .With(
+ PolicyFamily.WebSocketFrameBudget,
+ ParsePolicyMode(policy.WebSocketFrameBudget, current[PolicyFamily.WebSocketFrameBudget]));
if (policy.AllowAmbiguousFraming == true)
{
diff --git a/src/Titanium.Cli/Titanium.Cli.csproj b/src/Titanium.Cli/Titanium.Cli.csproj
index a5b7fce27..2b7d38d57 100644
--- a/src/Titanium.Cli/Titanium.Cli.csproj
+++ b/src/Titanium.Cli/Titanium.Cli.csproj
@@ -7,7 +7,7 @@
latest
enable
false
- 7.0.17
+ 7.0.18
Jehonathan Thomas
Titanium Web Proxy CLI (titanium / twp).
MIT
diff --git a/src/Titanium.Inspector/Services/InspectorBodyLimits.cs b/src/Titanium.Inspector/Services/InspectorBodyLimits.cs
index 538d61e41..20acb01b3 100644
--- a/src/Titanium.Inspector/Services/InspectorBodyLimits.cs
+++ b/src/Titanium.Inspector/Services/InspectorBodyLimits.cs
@@ -19,7 +19,7 @@ public enum BodyCaptureState
/// Known huge Content-Length — not buffered (download must not stall).
NotCaptured = 3,
- /// Endless / SSE-style stream — relayed; preview may fill asynchronously.
+ /// Unknown-length body (chunked / close-delimited / SSE) — relayed; bounded preview fills asynchronously.
Streaming = 4,
}
@@ -84,6 +84,97 @@ public static bool LooksLikeSseContentType(string? contentType) =>
!string.IsNullOrEmpty(contentType)
&& contentType.Contains("text/event-stream", StringComparison.OrdinalIgnoreCase);
+ ///
+ /// Best-effort decode of a captured wire prefix per Content-Encoding (gzip, deflate, br).
+ /// Used for streamed (unknown-length) bodies, which are relayed without the library's
+ /// decompressing buffer. Truncated input is tolerated: whatever decodes is returned, capped
+ /// at . Unknown or undecodable encodings return the input unchanged.
+ ///
+ public static byte[] DecodeCapturedPrefix(byte[] wire, string? contentEncoding)
+ {
+ if (wire.Length == 0 || string.IsNullOrWhiteSpace(contentEncoding))
+ {
+ return wire;
+ }
+
+ // Content-Encoding lists encodings in the order they were applied; undo in reverse.
+ var tokens = contentEncoding.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
+ var current = wire;
+ for (var i = tokens.Length - 1; i >= 0; i--)
+ {
+ var next = TryDecodeOne(current, tokens[i]);
+ if (next is null)
+ {
+ return current;
+ }
+
+ current = next;
+ }
+
+ return current;
+ }
+
+ private static byte[]? TryDecodeOne(byte[] input, string encoding)
+ {
+ if (encoding.Equals("identity", StringComparison.OrdinalIgnoreCase))
+ {
+ return input;
+ }
+
+ if (encoding.Equals("gzip", StringComparison.OrdinalIgnoreCase)
+ || encoding.Equals("x-gzip", StringComparison.OrdinalIgnoreCase))
+ {
+ return ReadDecoded(() => new System.IO.Compression.GZipStream(
+ new MemoryStream(input), System.IO.Compression.CompressionMode.Decompress));
+ }
+
+ if (encoding.Equals("br", StringComparison.OrdinalIgnoreCase))
+ {
+ return ReadDecoded(() => new System.IO.Compression.BrotliStream(
+ new MemoryStream(input), System.IO.Compression.CompressionMode.Decompress));
+ }
+
+ if (encoding.Equals("deflate", StringComparison.OrdinalIgnoreCase))
+ {
+ // RFC 9110 says zlib-wrapped, but some servers send raw deflate.
+ return ReadDecoded(() => new System.IO.Compression.ZLibStream(
+ new MemoryStream(input), System.IO.Compression.CompressionMode.Decompress))
+ ?? ReadDecoded(() => new System.IO.Compression.DeflateStream(
+ new MemoryStream(input), System.IO.Compression.CompressionMode.Decompress));
+ }
+
+ return null;
+ }
+
+ private static byte[]? ReadDecoded(Func open)
+ {
+ try
+ {
+ using var stream = open();
+ using var output = new MemoryStream();
+ var buffer = new byte[16 * 1024];
+ try
+ {
+ int n;
+ while (output.Length < MaxBodyBytes
+ && (n = stream.Read(buffer, 0, (int)Math.Min(buffer.Length, MaxBodyBytes - output.Length))) > 0)
+ {
+ output.Write(buffer, 0, n);
+ }
+ }
+ catch (Exception ex) when (ex is InvalidDataException or IOException)
+ {
+ // Truncated tee prefix (or corrupt data): keep what decoded so far.
+ }
+
+ return output.Length > 0 ? output.ToArray() : null;
+ }
+ catch (Exception ex) when (ex is InvalidDataException or IOException)
+ {
+ return null;
+ }
+ }
+
public static string FormatCaptureBanner(
BodyCaptureState state,
long? originalSize,
diff --git a/src/Titanium.Inspector/Services/InspectorDisplayText.cs b/src/Titanium.Inspector/Services/InspectorDisplayText.cs
new file mode 100644
index 000000000..9b9403f3b
--- /dev/null
+++ b/src/Titanium.Inspector/Services/InspectorDisplayText.cs
@@ -0,0 +1,109 @@
+using System.Globalization;
+using System.Text;
+
+namespace Titanium.Inspector.Services;
+
+///
+/// Bounds the text handed to the Inspect panel TextBoxes. Avalonia lays the whole string
+/// out on the UI thread, and the cost explodes for binary payloads decoded as UTF-8 (every
+/// U+FFFD / control character needs font fallback): a 256 K-character git pack froze the window
+/// for seconds when the Body tab was selected. This is display-only; captured bytes, Save body,
+/// Hex and Copy are unaffected.
+///
+public static class InspectorDisplayText
+{
+ /// Maximum characters shown for text-like content (about 0.1-0.3 s of layout).
+ public const int MaxTextChars = 128 * 1024;
+
+ /// Maximum characters shown for binary-looking content (unreadable beyond the head anyway).
+ public const int MaxBinaryChars = 16 * 1024;
+
+ private const int BinarySampleChars = 8 * 1024;
+ private const int BinaryPercentThreshold = 10;
+
+ ///
+ /// Returns unchanged (same instance) when it is short and renderable;
+ /// otherwise a copy with control / replacement characters shown as '.' and, past the cap,
+ /// truncated with a trailing note.
+ ///
+ public static string ForTextBox(string? text)
+ {
+ if (string.IsNullOrEmpty(text))
+ {
+ return text ?? "";
+ }
+
+ var binary = LooksBinary(text);
+ var limit = binary ? MaxBinaryChars : MaxTextChars;
+ var take = Math.Min(text.Length, limit);
+ if (take < text.Length && char.IsHighSurrogate(text[take - 1]))
+ {
+ take--;
+ }
+
+ var truncated = take < text.Length;
+ if (!truncated && !ContainsUnrenderable(text.AsSpan(0, take)))
+ {
+ return text;
+ }
+
+ var sb = new StringBuilder(take + 128);
+ for (var i = 0; i < take; i++)
+ {
+ var c = text[i];
+ sb.Append(IsUnrenderable(c) ? '.' : c);
+ }
+
+ if (truncated)
+ {
+ sb.AppendLine();
+ sb.Append("… display limited to the first ")
+ .Append(take.ToString("N0", CultureInfo.CurrentCulture))
+ .Append(" of ")
+ .Append(text.Length.ToString("N0", CultureInfo.CurrentCulture))
+ .Append(" characters");
+ if (binary)
+ {
+ sb.Append(" (binary content, non-printable bytes shown as '.')");
+ }
+
+ sb.Append(" — use Save body or the Hex tab for the full content");
+ }
+
+ return sb.ToString();
+ }
+
+ /// True when a sample from the start of is mostly non-printable.
+ internal static bool LooksBinary(string text)
+ {
+ var sample = Math.Min(text.Length, BinarySampleChars);
+ var bad = 0;
+ for (var i = 0; i < sample; i++)
+ {
+ if (IsUnrenderable(text[i]))
+ {
+ bad++;
+ }
+ }
+
+ return bad * 100 >= sample * BinaryPercentThreshold && bad > 0;
+ }
+
+ private static bool ContainsUnrenderable(ReadOnlySpan text)
+ {
+ foreach (var c in text)
+ {
+ if (IsUnrenderable(c))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool IsUnrenderable(char c) =>
+ c == '\uFFFD'
+ || (c < ' ' && c != '\n' && c != '\r' && c != '\t')
+ || (c >= '\u007F' && c <= '\u009F');
+}
diff --git a/src/Titanium.Inspector/Services/InterceptionService.cs b/src/Titanium.Inspector/Services/InterceptionService.cs
index 680b9ba20..437c82b2a 100644
--- a/src/Titanium.Inspector/Services/InterceptionService.cs
+++ b/src/Titanium.Inspector/Services/InterceptionService.cs
@@ -255,6 +255,10 @@ public async Task StartAsync(IPAddress address, int port, CancellationToken canc
// normal browsing (images, JS bundles) and RST'd the H2 stream. 32 MiB still bounds
// memory while covering typical inspected payloads.
_proxy.MaxBufferedBodyBytes = 32 * 1024 * 1024;
+ // Observing must not close an application that sends one large WebSocket frame.
+ _proxy.PolicyModes = _proxy.PolicyModes.With(
+ Titanium.Web.Proxy.Options.PolicyFamily.WebSocketFrameBudget,
+ Titanium.Web.Proxy.Options.PolicyMode.Observe);
ApplyHttpProtocols();
_proxy.BeforeRequest += OnBeforeRequest;
_proxy.BeforeResponse += OnBeforeResponse;
@@ -1660,7 +1664,7 @@ private SessionSnapshot CreateTunnelSnapshot(TunnelConnectSessionEventArgs e, Op
{
// Buffer when safe. GraphQL tools must NOT force GetRequestBody past the skip
// (huge POST would RST HTTP/2 with ENHANCE_YOUR_CALM).
- if (e.HttpClient.Request.HasBody && ShouldBufferBody(e.HttpClient.Request, e, isRequest: true))
+ if (e.HttpClient.Request.HasBody && ShouldBufferBody(e.HttpClient.Request, e))
{
e.HttpClient.Request.KeepBody = true;
await e.GetRequestBody(CancellationToken.None);
@@ -1755,7 +1759,7 @@ private async Task OnBeforeResponse(object sender, SessionEventArgs e)
{
try
{
- if (e.HttpClient.Response.HasBody && ShouldBufferBody(e.HttpClient.Response, e, isRequest: false))
+ if (e.HttpClient.Response.HasBody && ShouldBufferBody(e.HttpClient.Response, e))
{
e.HttpClient.Response.KeepBody = true;
await e.GetResponseBody(CancellationToken.None);
@@ -1806,6 +1810,7 @@ private Task OnAfterResponse(object sender, SessionEventArgs e)
{
if (_live.TryGetValue(e.HttpClient, out var snap))
{
+ FinalizeStreamingRequestBody(snap);
FinalizeStreamingBody(snap);
ApplyTiming(snap, e.Timing, snap.StartedUtc);
SessionUpdated?.Invoke(this, snap);
@@ -2072,18 +2077,33 @@ private static void FillResponse(SessionSnapshot snap, SessionEventArgs e) // NO
snap.SseEvents = SseEventParser.Parse(snap.ResponseBodyText);
}
- if (snap.IsGrpc && !snap.IsTranscoded && bodyBytes is { Length: > 0 })
- {
- snap.GrpcFrames = ProtocolFrameInspectors.ParseGrpcFrames(bodyBytes);
- snap.ProtobufDecodedText = ProtobufMessageDecoder.DecodeWireFormat(bodyBytes);
- }
+ ApplyResponseBodyViews(snap, bodyBytes);
if (snap.IsTranscoded && snap.UpstreamResponseBodyBytes is { Length: > 0 })
{
snap.ProtobufDecodedText = ProtobufMessageDecoder.DecodeWireFormat(snap.UpstreamResponseBodyBytes);
}
+ }
+
+ ///
+ /// Structured views (gRPC frames, protobuf, multipart) derived from the response body preview.
+ /// Runs when the body was buffered, and again once a streamed body's preview is final,
+ /// so unknown-length gRPC / multipart responses keep their decoded views.
+ ///
+ private static void ApplyResponseBodyViews(SessionSnapshot snap, byte[]? bodyBytes)
+ {
+ if (bodyBytes is not { Length: > 0 })
+ {
+ return;
+ }
+
+ if (snap.IsGrpc && !snap.IsTranscoded)
+ {
+ snap.GrpcFrames = ProtocolFrameInspectors.ParseGrpcFrames(bodyBytes);
+ snap.ProtobufDecodedText = ProtobufMessageDecoder.DecodeWireFormat(bodyBytes);
+ }
- if (snap.IsMultipart && bodyBytes is { Length: > 0 })
+ if (snap.IsMultipart)
{
snap.MultipartParts = ProtocolFrameInspectors.ParseMultipart(snap.ContentType, bodyBytes);
}
@@ -2110,6 +2130,13 @@ private void AttachLiveWebSocketFrames(SessionEventArgs e, SessionSnapshot snap)
SessionUpdated?.Invoke(this, snap);
return Task.CompletedTask;
};
+ e.WebSocketFrameElided += (_, args) =>
+ {
+ var direction = args.Direction == WebSocketFrameDirection.ClientToServer ? "Client" : "Server";
+ live.Append(snap, ProtocolFrameInspectors.ElidedFrame(direction, args.OpCode.ToString(), args.DeclaredPayloadLength));
+ SessionUpdated?.Invoke(this, snap);
+ return Task.CompletedTask;
+ };
}
private async Task OnRequestBodyWriteThrottle(object sender, BeforeBodyWriteEventArgs e)
@@ -2123,6 +2150,16 @@ private async Task OnRequestBodyWriteThrottle(object sender, BeforeBodyWriteEven
await Task.Delay(delay, _processResolveCts?.Token ?? CancellationToken.None).ConfigureAwait(false);
}
}
+
+ // Preview tee for unknown-length uploads that were relayed instead of buffered.
+ if (e.Session.HttpClient.Request.IsBodyRead
+ || !_live.TryGetValue(e.Session.HttpClient, out var snap)
+ || snap.RequestBodyCapture != BodyCaptureState.Streaming)
+ {
+ return;
+ }
+
+ TeeRequestChunk(snap, e);
}
private async Task OnResponseBodyWriteThrottle(object sender, BeforeBodyWriteEventArgs e)
@@ -2137,7 +2174,7 @@ private async Task OnResponseBodyWriteThrottle(object sender, BeforeBodyWriteEve
}
}
- // Preview tee only for streamed SSE (not buffered). Do not tee NotCaptured huge downloads.
+ // Preview tee only for relayed unknown-length bodies (not buffered). Do not tee NotCaptured huge downloads.
if (e.Session.HttpClient.Response.IsBodyRead)
{
return;
@@ -2208,7 +2245,7 @@ private static void PublishTeePreview(SessionSnapshot snap)
return;
}
- var bytes = tee.ToArray();
+ var bytes = InspectorBodyLimits.DecodeCapturedPrefix(tee.ToArray(), snap.ResponseContentEncoding);
snap.ResponseBodyBytes = bytes;
snap.ResponseBodyText = InspectorBodyLimits.TruncateText(Encoding.UTF8.GetString(bytes));
if (snap.IsServerSentEvents)
@@ -2229,14 +2266,23 @@ private static void FinalizeStreamingBody(SessionSnapshot snap)
PublishTeePreview(snap);
snap.ResponseBodyStreamOpen = false;
var captured = snap.ResponseBodyBytes?.LongLength ?? 0;
- if (snap.ResponseBytesSeen > captured && captured >= MaxBodyBytes)
+ var seen = snap.ResponseBytesSeen;
+ // Seen counts wire bytes; captured is the (possibly decoded) preview. Either the wire
+ // exceeded the tee cap, or decoding hit the preview cap.
+ if (seen > MaxBodyBytes || (captured >= MaxBodyBytes && captured != seen))
{
snap.ResponseBodyCapture = BodyCaptureState.Truncated;
}
- else if (captured > 0 && snap.ResponseBytesSeen <= MaxBodyBytes)
+ else if (captured > 0)
{
snap.ResponseBodyCapture = BodyCaptureState.Complete;
}
+ else if (seen == 0 && !snap.IsServerSentEvents)
+ {
+ snap.ResponseBodyCapture = BodyCaptureState.None;
+ }
+
+ ApplyResponseBodyViews(snap, snap.ResponseBodyBytes);
snap.ResponseBodyOriginalSize = snap.ResponseBytesSeen > 0
? snap.ResponseBytesSeen
@@ -2252,6 +2298,92 @@ private static void FinalizeStreamingBody(SessionSnapshot snap)
snap.ResponseTeeStream = null;
}
+ private void TeeRequestChunk(SessionSnapshot snap, BeforeBodyWriteEventArgs e)
+ {
+ var chunk = e.BodyBytes;
+ var len = chunk?.Length ?? 0;
+ if (len > 0)
+ {
+ snap.RequestBytesSeen += len;
+ snap.RequestBodyOriginalSize = snap.RequestBytesSeen;
+
+ var tee = snap.RequestTeeStream;
+ if (tee is null)
+ {
+ tee = new MemoryStream(Math.Min(MaxBodyBytes, Math.Max(len, 4096)));
+ snap.RequestTeeStream = tee;
+ }
+
+ if (tee.Length < MaxBodyBytes)
+ {
+ var toWrite = (int)Math.Min(len, MaxBodyBytes - tee.Length);
+ tee.Write(chunk!, 0, toWrite);
+ }
+ }
+
+ if (e.IsLastChunk)
+ {
+ FinalizeStreamingRequestBody(snap);
+ SyncHttpBodySize(snap);
+ SessionUpdated?.Invoke(this, snap);
+ return;
+ }
+
+ var now = DateTime.UtcNow.Ticks;
+ var last = snap.LastRequestTeeUiUtcTicks;
+ if (last != 0 && (now - last) < TimeSpan.FromMilliseconds(InspectorBodyLimits.TeeUiCoalesceMs).Ticks)
+ {
+ return;
+ }
+
+ snap.LastRequestTeeUiUtcTicks = now;
+ PublishRequestTeePreview(snap);
+ SessionUpdated?.Invoke(this, snap);
+ }
+
+ private static void PublishRequestTeePreview(SessionSnapshot snap)
+ {
+ var tee = snap.RequestTeeStream;
+ if (tee is null || tee.Length == 0)
+ {
+ return;
+ }
+
+ var bytes = InspectorBodyLimits.DecodeCapturedPrefix(tee.ToArray(), snap.RequestContentEncoding);
+ snap.RequestBodyBytes = bytes;
+ snap.RequestBodyText = InspectorBodyLimits.TruncateText(Encoding.UTF8.GetString(bytes));
+ }
+
+ private static void FinalizeStreamingRequestBody(SessionSnapshot snap)
+ {
+ if (snap.RequestBodyCapture == BodyCaptureState.Streaming)
+ {
+ PublishRequestTeePreview(snap);
+ var captured = snap.RequestBodyBytes?.LongLength ?? 0;
+ var seen = snap.RequestBytesSeen;
+ if (seen > MaxBodyBytes || (captured >= MaxBodyBytes && captured != seen))
+ {
+ snap.RequestBodyCapture = BodyCaptureState.Truncated;
+ }
+ else if (captured > 0)
+ {
+ snap.RequestBodyCapture = BodyCaptureState.Complete;
+ }
+ else
+ {
+ snap.RequestBodyCapture = BodyCaptureState.None;
+ }
+
+ if (seen > 0)
+ {
+ snap.RequestBodyOriginalSize = seen;
+ }
+ }
+
+ snap.RequestTeeStream?.Dispose();
+ snap.RequestTeeStream = null;
+ }
+
private static void ApplyRequestBodyCapture(SessionSnapshot snap, Request req, byte[]? originalBody)
{
if (originalBody is not null && req.IsBodyRead)
@@ -2269,6 +2401,15 @@ private static void ApplyRequestBodyCapture(SessionSnapshot snap, Request req, b
return;
}
+ if (!req.IsBodyRead && req.ContentLength < 0)
+ {
+ // Unknown length (chunked / H2 without Content-Length) is relayed, not buffered;
+ // a bounded preview is teed from the body-write hook.
+ snap.RequestBodyCapture = BodyCaptureState.Streaming;
+ snap.RequestContentEncoding = req.ContentEncoding;
+ return;
+ }
+
var limit = InspectorBodyLimits.MaxMapLocalFileBytes;
if (req.ContentLength > limit)
{
@@ -2340,12 +2481,22 @@ private static void ApplyResponseBodyCapture(
return;
}
- if (isSse)
+ // Unknown length is never buffered (see ShouldBufferBody): relay + bounded preview tee.
+ if (isSse || (resp.HasBody && !resp.IsBodyRead && resp.ContentLength < 0))
{
- ApplyStreamingResponseBody(snap);
+ ApplyStreamingResponseBody(snap, resp);
return;
}
+ ApplyFiniteUnbufferedResponseBody(snap, resp);
+ }
+
+ ///
+ /// Known-length (or empty) response that was not buffered: either too large to capture, or
+ /// a body we chose to buffer but never read. Neither is previewed.
+ ///
+ private static void ApplyFiniteUnbufferedResponseBody(SessionSnapshot snap, Response resp)
+ {
if (resp.HasBody && resp.ContentLength > InspectorBodyLimits.MaxMapLocalFileBytes)
{
snap.ResponseBodyCapture = BodyCaptureState.NotCaptured;
@@ -2377,8 +2528,9 @@ private static void ApplyBufferedResponseBody(SessionSnapshot snap, byte[] origi
snap.ResponseBodyStreamOpen = false;
}
- private static void ApplyStreamingResponseBody(SessionSnapshot snap)
+ private static void ApplyStreamingResponseBody(SessionSnapshot snap, Response resp)
{
+ snap.ResponseContentEncoding = resp.ContentEncoding;
snap.ResponseBodyCapture = BodyCaptureState.Streaming;
snap.ResponseBodyStreamOpen = true;
snap.BodySize = snap.ResponseBytesSeen > 0 ? snap.ResponseBytesSeen : null;
@@ -2459,43 +2611,16 @@ private static string FormatHeaders(HeaderCollection headers)
}
///
- /// Whole-body buffering for the session grid must not run when Content-Length already
- /// exceeds - that path RSTs HTTP/2 streams
- /// with ENHANCE_YOUR_CALM and breaks the browser download. SSE and WebSocket upgrades are
- /// never buffered (relay + optional 2 MiB tee). Finite unknown-length (chunked) bodies still
- /// buffer up to the limit so gzip JSON can be inspected.
+ /// Whole-body buffering for the session grid is only safe when the size is known up front.
+ /// is a hard abort budget: exceeding it RSTs
+ /// HTTP/2 streams (ENHANCE_YOUR_CALM) or drops the connection. A body with no declared length
+ /// (chunked, H2 without Content-Length, close-delimited, SSE) can exceed it at any point
+ /// regardless of what it is, so it is relayed untouched and previewed through the bounded
+ /// body-write tee instead. Known lengths within the budget are buffered (decompressed) as before;
+ /// a non-positive budget means "unbounded" and always buffers. WebSocket upgrades are never buffered.
///
- private bool ShouldBufferBody(RequestResponseBase message, SessionEventArgs session, bool isRequest)
- {
- if (LooksLikeEndlessStream(message, session, isRequest))
- {
- return false;
- }
-
- var limit = session.MaxBufferedBodyBytes ?? _proxy?.MaxBufferedBodyBytes ?? (4 * 1024 * 1024);
- if (limit <= 0)
- {
- return true;
- }
-
- var contentLength = message.ContentLength;
- return contentLength < 0 || contentLength <= limit;
- }
-
- private static bool LooksLikeEndlessStream(RequestResponseBase message, SessionEventArgs session, bool isRequest)
- {
- if (session.HttpClient.Request.UpgradeToWebSocket)
- {
- return true;
- }
-
- if (!isRequest && InspectorBodyLimits.LooksLikeSseContentType(message.ContentType))
- {
- return true;
- }
-
- return false;
- }
+ private static bool ShouldBufferBody(RequestResponseBase message, SessionEventArgs session) =>
+ session.CanBufferBody(message);
public void Dispose() => EnsureShutdown();
}
diff --git a/src/Titanium.Inspector/Services/ProtocolFrameInspectors.cs b/src/Titanium.Inspector/Services/ProtocolFrameInspectors.cs
index 5e051b2c1..1fa8b9d98 100644
--- a/src/Titanium.Inspector/Services/ProtocolFrameInspectors.cs
+++ b/src/Titanium.Inspector/Services/ProtocolFrameInspectors.cs
@@ -90,6 +90,14 @@ public static WebSocketFrameSnapshot FromLiveFrame(string direction, string opco
PayloadPreview = Preview(data, opcode.Equals("Binary", StringComparison.OrdinalIgnoreCase) ? 2 : 1),
};
+ public static WebSocketFrameSnapshot ElidedFrame(string direction, string opcode, long declaredLength) =>
+ new()
+ {
+ Direction = direction,
+ Opcode = opcode,
+ PayloadPreview = $"{opcode} frame, {declaredLength:N0} bytes, payload not captured",
+ };
+
private static string OpcodeName(int opcode) => opcode switch
{
0 => "Continuation",
diff --git a/src/Titanium.Inspector/Services/SessionDiff.cs b/src/Titanium.Inspector/Services/SessionDiff.cs
index 06a3bc11f..c9eb3eccf 100644
--- a/src/Titanium.Inspector/Services/SessionDiff.cs
+++ b/src/Titanium.Inspector/Services/SessionDiff.cs
@@ -170,8 +170,8 @@ private static int AppendHeaderDiff(StringBuilder sb, string title, string? left
continue;
}
- var later = IndexOf(rightLines, leftLines[i], j + 1);
- if (later >= 0 && later - j <= 8)
+ var later = IndexOf(rightLines, leftLines[i], j + 1, MaxLookahead);
+ if (later >= 0)
{
while (j < later)
{
@@ -182,8 +182,8 @@ private static int AppendHeaderDiff(StringBuilder sb, string title, string? left
continue;
}
- var laterLeft = IndexOf(leftLines, rightLines[j], i + 1);
- if (laterLeft >= 0 && laterLeft - i <= 8)
+ var laterLeft = IndexOf(leftLines, rightLines[j], i + 1, MaxLookahead);
+ if (laterLeft >= 0)
{
while (i < laterLeft)
{
@@ -233,9 +233,18 @@ private static List SplitLines(string text)
.ToList();
}
- private static int IndexOf(List lines, string value, int start)
+ ///
+ /// Lines a match may be ahead of the cursor to still count as an insert/delete. The search is
+ /// bounded to this window (not the rest of the body), keeping the diff linear on large bodies
+ /// where it used to be quadratic and could freeze the UI thread.
+ ///
+ private const int MaxLookahead = 8;
+
+ /// First index in [start, cursor + maxAhead] equal to , else -1.
+ private static int IndexOf(List lines, string value, int start, int maxAhead)
{
- for (var i = start; i < lines.Count; i++)
+ var end = Math.Min(lines.Count, start - 1 + maxAhead + 1);
+ for (var i = start; i < end; i++)
{
if (string.Equals(lines[i], value, StringComparison.Ordinal))
{
diff --git a/src/Titanium.Inspector/Services/SessionInspectors.cs b/src/Titanium.Inspector/Services/SessionInspectors.cs
index 1673a600f..2ffcbe6bb 100644
--- a/src/Titanium.Inspector/Services/SessionInspectors.cs
+++ b/src/Titanium.Inspector/Services/SessionInspectors.cs
@@ -123,7 +123,14 @@ public static string TryFormatJson(string? text)
}
}
- public static byte[]? TryDecompress(byte[]? body, string? contentEncoding)
+ /// Captured (possibly compressed) bytes.
+ /// Content-Encoding header value.
+ ///
+ /// Upper bound on the decompressed size. The Inspect panel decodes on the UI thread, so a small
+ /// compressed body (decompression bomb) must not be allowed to expand without limit.
+ /// Output beyond the bound is dropped; the default keeps the historical unbounded behavior.
+ ///
+ public static byte[]? TryDecompress(byte[]? body, string? contentEncoding, int maxOutputBytes = int.MaxValue)
{
if (body is null || body.Length == 0 || string.IsNullOrEmpty(contentEncoding))
{
@@ -148,7 +155,15 @@ public static string TryFormatJson(string? text)
using (codec)
using (var output = new MemoryStream())
{
- codec.CopyTo(output);
+ if (maxOutputBytes == int.MaxValue)
+ {
+ codec.CopyTo(output);
+ }
+ else
+ {
+ CopyBounded(codec, output, maxOutputBytes);
+ }
+
return output.ToArray();
}
}
@@ -158,6 +173,23 @@ public static string TryFormatJson(string? text)
}
}
+ private static void CopyBounded(Stream source, Stream destination, int maxBytes)
+ {
+ var buffer = new byte[16 * 1024];
+ var remaining = maxBytes;
+ while (remaining > 0)
+ {
+ var read = source.Read(buffer, 0, Math.Min(buffer.Length, remaining));
+ if (read <= 0)
+ {
+ return;
+ }
+
+ destination.Write(buffer, 0, read);
+ remaining -= read;
+ }
+ }
+
///
/// Labeled request + response body text (same section style as Headers).
///
@@ -205,7 +237,7 @@ private static string ResolveBodyText(string? headersText, string? bodyText, byt
var headers = ParseHeaderBlock(headersText);
headers.TryGetValue("Content-Encoding", out var encoding);
- var bytes = TryDecompress(bodyBytes, encoding);
+ var bytes = TryDecompress(bodyBytes, encoding, InspectorBodyLimits.MaxBodyBytes);
if (bytes is { Length: > 0 })
{
return Encoding.UTF8.GetString(bytes);
@@ -218,8 +250,9 @@ private static string ResolveHex(string? headersText, byte[]? bodyBytes)
{
var headers = ParseHeaderBlock(headersText);
headers.TryGetValue("Content-Encoding", out var encoding);
- var bytes = TryDecompress(bodyBytes, encoding);
- var hex = ToHex(bytes);
+ // Hex shows only the first MaxHexBytes; decode one byte more so ToHex still appends its "…".
+ var bytes = TryDecompress(bodyBytes, encoding, InspectorBodyLimits.MaxHexBytes + 1);
+ var hex = ToHex(bytes, InspectorBodyLimits.MaxHexBytes);
return string.IsNullOrEmpty(hex) ? "(empty)" : hex;
}
}
diff --git a/src/Titanium.Inspector/Services/SessionSnapshot.cs b/src/Titanium.Inspector/Services/SessionSnapshot.cs
index b7f177ca0..495107f9f 100644
--- a/src/Titanium.Inspector/Services/SessionSnapshot.cs
+++ b/src/Titanium.Inspector/Services/SessionSnapshot.cs
@@ -203,14 +203,14 @@ public long? ResponseBodyOriginalSize
set => SetField(ref _responseBodyOriginalSize, value);
}
- /// True while an SSE-style response stream is still open.
+ /// True while an unknown-length response stream (SSE, chunked) is still open.
public bool ResponseBodyStreamOpen
{
get => _responseBodyStreamOpen;
set => SetField(ref _responseBodyStreamOpen, value);
}
- /// In-flight SSE tee buffer (not spilled; cleared when finalized).
+ /// In-flight response tee buffer of wire bytes (not spilled; cleared when finalized).
internal MemoryStream? ResponseTeeStream { get; set; }
/// Bytes seen on the response wire while teeing (may exceed preview).
@@ -219,6 +219,21 @@ public bool ResponseBodyStreamOpen
/// UTC ticks of last coalesced SessionUpdated from the tee.
internal long LastTeeUiUtcTicks { get; set; }
+ /// Response Content-Encoding, used to decode the tee preview.
+ internal string? ResponseContentEncoding { get; set; }
+
+ /// In-flight request tee buffer of wire bytes (unknown-length uploads).
+ internal MemoryStream? RequestTeeStream { get; set; }
+
+ /// Bytes seen on the request wire while teeing (may exceed preview).
+ internal long RequestBytesSeen { get; set; }
+
+ /// UTC ticks of last coalesced SessionUpdated from the request tee.
+ internal long LastRequestTeeUiUtcTicks { get; set; }
+
+ /// Request Content-Encoding, used to decode the tee preview.
+ internal string? RequestContentEncoding { get; set; }
+
public int ProcessId
{
get => _processId;
diff --git a/src/Titanium.Inspector/Services/SessionStreamBuffer.cs b/src/Titanium.Inspector/Services/SessionStreamBuffer.cs
index acb162421..80911813f 100644
--- a/src/Titanium.Inspector/Services/SessionStreamBuffer.cs
+++ b/src/Titanium.Inspector/Services/SessionStreamBuffer.cs
@@ -106,10 +106,11 @@ private void RaiseSessionAdded(List batch)
{
// One subscriber list for the whole batch. Re-reading the event per row would drop
// the tail for a handler that unsubscribes on the first snapshot.
- if (SessionAdded is { } handler) // NOSONAR S3264 -- Invoke the captured delegate so the batch shares one list.
- {
- foreach (var snapshot in batch)
- handler(snapshot);
- }
+ var handler = SessionAdded;
+ if (handler is null)
+ return;
+
+ foreach (var snapshot in batch)
+ handler.Invoke(snapshot);
}
}
diff --git a/src/Titanium.Inspector/Titanium.Inspector.csproj b/src/Titanium.Inspector/Titanium.Inspector.csproj
index f66d463cd..1fda90cf1 100644
--- a/src/Titanium.Inspector/Titanium.Inspector.csproj
+++ b/src/Titanium.Inspector/Titanium.Inspector.csproj
@@ -8,7 +8,7 @@
enable
true
false
- 7.0.17
+ 7.0.18
Jehonathan Thomas
Titanium Inspector desktop traffic debugger (PolyForm Noncommercial).
LICENSE
diff --git a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.BodyInspect.cs b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.BodyInspect.cs
index 13cecec3f..aa7836c45 100644
--- a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.BodyInspect.cs
+++ b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.BodyInspect.cs
@@ -296,7 +296,7 @@ private void RefreshBodyInspector()
BodyCaptureHint = BuildBodyCaptureHint(_selected);
HexCaptureHint = BuildHexCaptureHint(_selected);
- SelectedBody = BuildSelectedBodyText(_selected);
+ SelectedBody = InspectorDisplayText.ForTextBox(BuildSelectedBodyText(_selected));
UpdateBodyPreviewImage(_selected);
NotifySaveBodyCanExecute();
}
diff --git a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.Sessions.cs b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.Sessions.cs
index 1495290dd..1977ddda0 100644
--- a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.Sessions.cs
+++ b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.Sessions.cs
@@ -116,7 +116,7 @@ await MarshalToUiAsync(() =>
ComposerHeaders = selected.RequestHeadersText ?? "";
ComposerBody = selected.RequestBodyText ?? "";
ComposerBodyFilePath = null;
- StatusText = selected.RequestBodyCapture is BodyCaptureState.Truncated or BodyCaptureState.NotCaptured
+ StatusText = selected.RequestBodyCapture is BodyCaptureState.Truncated or BodyCaptureState.NotCaptured or BodyCaptureState.Streaming
? "Composer loaded (request body was truncated or not fully captured)"
: "Composer loaded from selected session";
}, _statusRevertCts?.Token ?? CancellationToken.None).ConfigureAwait(false);
@@ -180,7 +180,7 @@ await MarshalToUiAsync(() =>
ComposerHeaders = selected.RequestHeadersText ?? "";
ComposerBody = selected.RequestBodyText ?? "";
ComposerBodyFilePath = null;
- StatusText = selected.RequestBodyCapture is BodyCaptureState.Truncated or BodyCaptureState.NotCaptured
+ StatusText = selected.RequestBodyCapture is BodyCaptureState.Truncated or BodyCaptureState.NotCaptured or BodyCaptureState.Streaming
? "Composer loaded (request body was truncated or not fully captured)"
: "Composer loaded from selected session";
}, StatusCancelToken).ConfigureAwait(false);
@@ -228,13 +228,22 @@ private async Task CopyAsFetchAsync()
}
private async Task DiffSessionsAsync()
{
- if (!TryBuildSessionDiff(out var diff))
+ var selection = ResolveFilterSelection();
+ if (selection.Count != 2)
{
StatusText = "Select exactly two sessions to diff";
return;
}
- SessionDiffText = diff.Text;
+ // Comparing two multi-MB bodies is CPU-bound: keep it off the UI thread when a UI exists.
+ var left = selection[0];
+ var right = selection[1];
+ var diff = Application.Current is null
+ ? SessionDiff.Compare(left, right)
+ : await Task.Run(() => SessionDiff.Compare(left, right), CancellationToken.None);
+
+ // The clipboard gets the full report; the on-screen TextBox is bounded (layout cost).
+ SessionDiffText = InspectorDisplayText.ForTextBox(diff.Text);
await CopyTextToClipboardAsync(diff.Text).ConfigureAwait(false);
ShowSessionDetails = true;
SelectedOuterPaneIndex = 0;
diff --git a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.cs b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.cs
index 339a3530f..58fc28bce 100644
--- a/src/Titanium.Inspector/ViewModels/MainWindowViewModel.cs
+++ b/src/Titanium.Inspector/ViewModels/MainWindowViewModel.cs
@@ -732,7 +732,7 @@ private void WireSessionPipelineHandlers()
if (ReferenceEquals(SelectedSession, snap))
{
UpdateWsFramesVisibility();
- RefreshSelectedInspectors();
+ RefreshSelectedInspectorsCoalesced(snap);
}
});
_interception.DecryptFailureBypassLearned += (_, entry) =>
@@ -2824,9 +2824,55 @@ private void RefreshSelectedInspectors()
_selected.ResponseHeadersText,
_selected.RequestBodyBytes,
_selected.ResponseBodyBytes);
- SelectedFrames = BuildSelectedFramesText(_selected);
- SelectedSseEvents = BuildSelectedSseText(_selected);
- SelectedProtobufDecoded = BuildSelectedProtobufText(_selected);
+ SelectedFrames = InspectorDisplayText.ForTextBox(BuildSelectedFramesText(_selected));
+ SelectedSseEvents = InspectorDisplayText.ForTextBox(BuildSelectedSseText(_selected));
+ SelectedProtobufDecoded = InspectorDisplayText.ForTextBox(BuildSelectedProtobufText(_selected));
+ }
+
+ private long _lastSelectedRefreshTicks;
+ private bool _selectedRefreshPending;
+
+ ///
+ /// Live-update refresh of the selected session. A chatty WebSocket (one update per frame) or a
+ /// streaming body would otherwise rebuild every Inspect text box on the UI thread per event;
+ /// coalesce to with a trailing refresh so
+ /// the final state is always shown. Selection changes still refresh immediately, and without a
+ /// running Avalonia application (unit tests) the refresh stays synchronous.
+ ///
+ private void RefreshSelectedInspectorsCoalesced(SessionSnapshot snap)
+ {
+ if (Application.Current is null)
+ {
+ RefreshSelectedInspectors();
+ return;
+ }
+
+ var interval = TimeSpan.FromMilliseconds(InspectorBodyLimits.TeeUiCoalesceMs);
+ if (_lastSelectedRefreshTicks == 0 || System.Diagnostics.Stopwatch.GetElapsedTime(_lastSelectedRefreshTicks) >= interval)
+ {
+ _lastSelectedRefreshTicks = System.Diagnostics.Stopwatch.GetTimestamp();
+ RefreshSelectedInspectors();
+ return;
+ }
+
+ if (_selectedRefreshPending)
+ {
+ return;
+ }
+
+ _selectedRefreshPending = true;
+ var remaining = interval - System.Diagnostics.Stopwatch.GetElapsedTime(_lastSelectedRefreshTicks);
+ DispatcherTimer.RunOnce(
+ () =>
+ {
+ _selectedRefreshPending = false;
+ if (ReferenceEquals(SelectedSession, snap))
+ {
+ _lastSelectedRefreshTicks = System.Diagnostics.Stopwatch.GetTimestamp();
+ RefreshSelectedInspectors();
+ }
+ },
+ remaining > TimeSpan.Zero ? remaining : TimeSpan.FromMilliseconds(1));
}
private static string BuildSelectedHeadersText(SessionSnapshot selected)
diff --git a/src/Titanium.Plus/Titanium.Plus.csproj b/src/Titanium.Plus/Titanium.Plus.csproj
index 95cfc2ffa..7f6796f05 100644
--- a/src/Titanium.Plus/Titanium.Plus.csproj
+++ b/src/Titanium.Plus/Titanium.Plus.csproj
@@ -7,7 +7,7 @@
enable
True
StrongNameKey.snk
- 7.0.17
+ 7.0.18
Jehonathan Thomas
Titanium Web Proxy Plus advanced features plugin (PolyForm Noncommercial).
LICENSE
diff --git a/src/Titanium.Web.Proxy.Abstractions/Titanium.Web.Proxy.Abstractions.csproj b/src/Titanium.Web.Proxy.Abstractions/Titanium.Web.Proxy.Abstractions.csproj
index 40ada07a3..7612d5453 100644
--- a/src/Titanium.Web.Proxy.Abstractions/Titanium.Web.Proxy.Abstractions.csproj
+++ b/src/Titanium.Web.Proxy.Abstractions/Titanium.Web.Proxy.Abstractions.csproj
@@ -7,7 +7,7 @@
enable
True
StrongNameKey.snk
- 7.0.17
+ 7.0.18
Jehonathan Thomas
Shared contracts for Titanium Web Proxy routing, clusters, middleware, and plugins.
MIT
diff --git a/src/Titanium.Web.Proxy.Configuration/Models/ServerConfig.cs b/src/Titanium.Web.Proxy.Configuration/Models/ServerConfig.cs
index d4c97e1d6..5a5561486 100644
--- a/src/Titanium.Web.Proxy.Configuration/Models/ServerConfig.cs
+++ b/src/Titanium.Web.Proxy.Configuration/Models/ServerConfig.cs
@@ -213,6 +213,8 @@ public sealed class PolicyModesConfig
public string? Http2RelayValidation { get; set; }
+ public string? WebSocketFrameBudget { get; set; }
+
public bool? AllowAmbiguousFraming { get; set; }
}
diff --git a/src/Titanium.Web.Proxy.Configuration/PublicAPI.Unshipped.txt b/src/Titanium.Web.Proxy.Configuration/PublicAPI.Unshipped.txt
index ac6570db5..35ed7997e 100644
--- a/src/Titanium.Web.Proxy.Configuration/PublicAPI.Unshipped.txt
+++ b/src/Titanium.Web.Proxy.Configuration/PublicAPI.Unshipped.txt
@@ -202,6 +202,8 @@ Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.Http2AbuseBudget.get -
Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.Http2AbuseBudget.set -> void
Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.Http2RelayValidation.get -> string?
Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.Http2RelayValidation.set -> void
+Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.WebSocketFrameBudget.get -> string?
+Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.WebSocketFrameBudget.set -> void
Titanium.Web.Proxy.Configuration.Models.PolicyModesConfig.PolicyModesConfig() -> void
Titanium.Web.Proxy.Configuration.Models.PoolingConfig
Titanium.Web.Proxy.Configuration.Models.PoolingConfig.EnableConnectionPool.get -> bool?
diff --git a/src/Titanium.Web.Proxy.Configuration/Titanium.Web.Proxy.Configuration.csproj b/src/Titanium.Web.Proxy.Configuration/Titanium.Web.Proxy.Configuration.csproj
index e58157f06..133a243c3 100644
--- a/src/Titanium.Web.Proxy.Configuration/Titanium.Web.Proxy.Configuration.csproj
+++ b/src/Titanium.Web.Proxy.Configuration/Titanium.Web.Proxy.Configuration.csproj
@@ -7,7 +7,7 @@
enable
True
StrongNameKey.snk
- 7.0.17
+ 7.0.18
Jehonathan Thomas
YAML/JSON configuration binding for Titanium Web Proxy CLI and reverse-proxy documents.
MIT
diff --git a/src/Titanium.Web.Proxy.Configuration/TwpConfigValidator.cs b/src/Titanium.Web.Proxy.Configuration/TwpConfigValidator.cs
index a2006b6f6..40bef3079 100644
--- a/src/Titanium.Web.Proxy.Configuration/TwpConfigValidator.cs
+++ b/src/Titanium.Web.Proxy.Configuration/TwpConfigValidator.cs
@@ -268,6 +268,7 @@ private static void ValidatePolicyModes(PolicyModesConfig? policy, List
RequireKnownPolicy(policy.AdmissionControl, "server.policyModes.admissionControl", errors);
RequireKnownPolicy(policy.Http2AbuseBudget, "server.policyModes.http2AbuseBudget", errors);
RequireKnownPolicy(policy.Http2RelayValidation, "server.policyModes.http2RelayValidation", errors);
+ RequireKnownPolicy(policy.WebSocketFrameBudget, "server.policyModes.webSocketFrameBudget", errors);
}
private static void ValidateUpstream(UpstreamConfig? upstream, List errors)
diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
index 8c9a23d23..c9c8debaf 100644
--- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
@@ -228,6 +228,14 @@ public bool ReRequest
///
public event AsyncEventHandler? BeforeWebSocketFrame; // NOSONAR S3264 -- Public extension event invoked by the WebSocket relay.
+ ///
+ /// Fired when is
+ /// and a frame exceeds
+ /// MaxWebSocketFramePayloadBytes. The payload is relayed unbuffered and is not passed
+ /// to .
+ ///
+ public event AsyncEventHandler? WebSocketFrameElided; // NOSONAR S3264 -- Invoked by the WebSocket relay in Observe mode.
+
///
/// Inject frames toward the remote server (client→server direction, masked).
/// Available only while an intercepted WebSocket relay is active.
@@ -242,6 +250,13 @@ public bool ReRequest
internal bool HasWebSocketFrameInterceptHandler => BeforeWebSocketFrame != null;
+ internal bool HasWebSocketFrameElidedHandler => WebSocketFrameElided != null;
+
+ internal Task InvokeWebSocketFrameElided(WebSocketFrameElidedEventArgs args) =>
+ WebSocketFrameElided != null
+ ? WebSocketFrameElided.InvokeAsync(Server, args, Logger)
+ : Task.CompletedTask;
+
internal Task InvokeBeforeWebSocketFrame(WebSocketFrameInterceptEventArgs args)
{
return BeforeWebSocketFrame != null
@@ -579,6 +594,26 @@ private async Task CopyResponseBodyAsync(IHttpStreamWriter writer, Transformatio
}
}
+ ///
+ /// Whether the proxy can buffer without exceeding the effective
+ /// budget. Unknown-length bodies (chunked, HTTP/2 or HTTP/3
+ /// without Content-Length, server-sent events) return : they must be
+ /// relayed, not accumulated. A non-positive budget means unbounded and returns
+ /// . WebSocket upgrades are never buffered.
+ ///
+ public bool CanBufferBody(RequestResponseBase message)
+ {
+ if (HttpClient.Request.UpgradeToWebSocket)
+ return false;
+
+ var limit = MaxBufferedBodyBytes ?? Server.MaxBufferedBodyBytes;
+ if (limit <= 0)
+ return true;
+
+ var contentLength = message.ContentLength;
+ return contentLength >= 0 && contentLength <= limit;
+ }
+
///
/// Gets the request body as bytes.
///
diff --git a/src/Titanium.Web.Proxy/EventArguments/WebSocketFrameInterceptEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/WebSocketFrameInterceptEventArgs.cs
index c6605fe78..9247fc5ca 100644
--- a/src/Titanium.Web.Proxy/EventArguments/WebSocketFrameInterceptEventArgs.cs
+++ b/src/Titanium.Web.Proxy/EventArguments/WebSocketFrameInterceptEventArgs.cs
@@ -97,3 +97,32 @@ public void Replace(byte[] newData, WebsocketOpCode? opCode = null)
Action = WebSocketFrameInterceptAction.Replace;
}
}
+
+///
+/// Metadata for a WebSocket frame that exceeded the payload limit under
+/// . The payload itself is not included.
+///
+public sealed class WebSocketFrameElidedEventArgs : ProxyEventArgsBase
+{
+ internal WebSocketFrameElidedEventArgs(ProxyServer server, TcpClientConnection clientConnection,
+ SessionEventArgs session, WebSocketFrameDirection direction, WebsocketOpCode opCode, bool isFinal,
+ long declaredPayloadLength)
+ : base(server, clientConnection)
+ {
+ Session = session;
+ Direction = direction;
+ OpCode = opCode;
+ IsFinal = isFinal;
+ DeclaredPayloadLength = declaredPayloadLength;
+ }
+
+ public SessionEventArgs Session { get; }
+
+ public WebSocketFrameDirection Direction { get; }
+
+ public WebsocketOpCode OpCode { get; }
+
+ public bool IsFinal { get; }
+
+ public long DeclaredPayloadLength { get; }
+}
diff --git a/src/Titanium.Web.Proxy/Handlers/Http11ToHttp2BridgeHandler.cs b/src/Titanium.Web.Proxy/Handlers/Http11ToHttp2BridgeHandler.cs
index cdd3b9612..1443aad14 100644
--- a/src/Titanium.Web.Proxy/Handlers/Http11ToHttp2BridgeHandler.cs
+++ b/src/Titanium.Web.Proxy/Handlers/Http11ToHttp2BridgeHandler.cs
@@ -15,7 +15,9 @@
using Titanium.Web.Proxy.Http2;
using Titanium.Web.Proxy.Logging;
using Titanium.Web.Proxy.Models;
+using Titanium.Web.Proxy.Network.Streams;
using Titanium.Web.Proxy.Network.Tcp;
+using Titanium.Web.Proxy.Options;
namespace Titanium.Web.Proxy;
@@ -82,8 +84,7 @@ public partial class ProxyServer
{
var tcp = await EstablishHttp2OriginTcpConnectionAsync(openSession, remoteHostName, remotePort,
connectHost, connectPort, ct);
- return await Http2OriginConnection.CreateAsync(tcp, logger,
- openSession.MaxBufferedBodyBytes ?? MaxBufferedBodyBytes, ct, ResourceLimits);
+ return await Http2OriginConnection.CreateAsync(tcp, logger, ct, ResourceLimits);
};
try
@@ -253,7 +254,7 @@ public partial class ProxyServer
remotePort, connectHost, connectPort);
if (!seedOffered && retainedConnectionTask != null)
{
- await OfferRetainedHttp2OriginSeedAsync(args, poolKey, remoteHostName, remotePort,
+ await OfferRetainedHttp2OriginSeedAsync(poolKey, remoteHostName, remotePort,
connectHost, connectPort, retainedConnectionTask, cancellationToken);
retainedConnectionTask = null;
seedOffered = true;
@@ -288,7 +289,7 @@ await RunHttp11WebSocketHttp11FallbackAsync(args, remoteHostName, remotePort,
remotePort, connectHost, connectPort);
if (!seedOffered && retainedConnectionTask != null)
{
- await OfferRetainedHttp2OriginSeedAsync(args, poolKey, remoteHostName, remotePort,
+ await OfferRetainedHttp2OriginSeedAsync(poolKey, remoteHostName, remotePort,
connectHost, connectPort, retainedConnectionTask, cancellationToken);
retainedConnectionTask = null;
seedOffered = true;
@@ -351,7 +352,7 @@ await RunHttp11ToHttp2ExchangeAsync(args, poolKey, openFactory, remoteHostName,
/// Offers the negotiation-retained TCP seed (when ALPN/h2c is valid) into the shared origin pool
/// as an established . Releases invalid seeds.
///
- private async Task OfferRetainedHttp2OriginSeedAsync(SessionEventArgs args, string poolKey, // NOSONAR S107 -- Parameters kept explicit to avoid allocating options bags on hot bridge/pool paths.
+ private async Task OfferRetainedHttp2OriginSeedAsync(string poolKey, // NOSONAR S107 -- Parameters kept explicit to avoid allocating options bags on hot bridge/pool paths.
string remoteHostName, int remotePort, string? connectHost, int? connectPort, // NOSONAR S1172 -- retained for call-site / overload symmetry with exchange path
Task retainedConnectionTask, CancellationToken cancellationToken)
{
@@ -391,7 +392,7 @@ private async Task OfferRetainedHttp2OriginSeedAsync(SessionEventArgs args, stri
try
{
var created = await Http2OriginConnection.CreateAsync(seedConnection, logger,
- args.MaxBufferedBodyBytes ?? MaxBufferedBodyBytes, cancellationToken, ResourceLimits);
+ cancellationToken, ResourceLimits);
Http2OriginConnectionPool.Offer(poolKey, created);
}
catch (Exception ex)
@@ -691,16 +692,34 @@ private async Task EstablishHttp2OriginTcpConnectionAsync(S
}
else if (response.ContentLength < 0 && !response.IsChunked)
{
- var buffered = new MemoryStream();
- var streamBody = response.StreamBodyWriter;
- response.StreamBodyWriter = null;
- await streamBody(buffered, cancellationToken);
- fastBody = buffered.ToArray();
- response.ContentLength = fastBody.Length;
- await clientStream.WriteResponseWithWireBodyAsync(response, fastBody, cancellationToken);
- response.IsBodyReceived = true;
- response.IsBodySent = true;
- return;
+ if (ClientCannotReceiveChunked(args))
+ {
+ var streamBody = response.StreamBodyWriter;
+ response.StreamBodyWriter = null;
+ fastBody = await BufferUnknownLengthForHttp10Async(args, streamBody, cancellationToken);
+ response.ContentLength = fastBody.Length;
+ await clientStream.WriteResponseWithWireBodyAsync(response, fastBody, cancellationToken);
+ response.IsBodyReceived = true;
+ response.IsBodySent = true;
+ return;
+ }
+
+ if (exchange.OriginBodyComplete)
+ {
+ var buffered = new MemoryStream();
+ var streamBody = response.StreamBodyWriter;
+ response.StreamBodyWriter = null;
+ await streamBody(buffered, cancellationToken);
+ fastBody = buffered.ToArray();
+ response.ContentLength = fastBody.Length;
+ await clientStream.WriteResponseWithWireBodyAsync(response, fastBody, cancellationToken);
+ response.IsBodyReceived = true;
+ response.IsBodySent = true;
+ return;
+ }
+
+ // Still open (SSE, chunked upload result, any long-lived body). Send headers now.
+ response.IsChunked = true;
}
await clientStream.WriteResponseAsync(response, cancellationToken);
@@ -800,18 +819,31 @@ await bodyWriter.CompleteAsync(response.HasTrailingHeaders ? response.TrailingHe
}
else if (response.ContentLength < 0 && !response.IsChunked)
{
- // The h2 origin sent no content-length (END_STREAM delimits its body). Buffer it here -
- // the BoundedBodyPipe behind StreamBodyWriter already enforces MaxBufferedBodyBytes - so
- // the HTTP/1.1 client keeps the Content-Length framing this bridge has always produced.
- // Origin responses that do carry a content-length still stream straight through below.
- var buffered = new MemoryStream();
- var streamBody = response.StreamBodyWriter;
- response.StreamBodyWriter = null;
- await streamBody(buffered, cancellationToken);
- body = buffered.ToArray();
- response.ContentLength = body.Length;
- response.Body = body;
- response.BodyIsWireEncoded = true;
+ if (ClientCannotReceiveChunked(args))
+ {
+ var streamBody = response.StreamBodyWriter;
+ response.StreamBodyWriter = null;
+ body = await BufferUnknownLengthForHttp10Async(args, streamBody, cancellationToken);
+ response.ContentLength = body.Length;
+ response.Body = body;
+ response.BodyIsWireEncoded = true;
+ }
+ else if (exchange.OriginBodyComplete)
+ {
+ // Origin already finished. Drain (bounded by the stream window) and keep Content-Length.
+ var buffered = new MemoryStream();
+ var streamBody = response.StreamBodyWriter;
+ response.StreamBodyWriter = null;
+ await streamBody(buffered, cancellationToken);
+ body = buffered.ToArray();
+ response.ContentLength = body.Length;
+ response.Body = body;
+ response.BodyIsWireEncoded = true;
+ }
+ else
+ {
+ response.IsChunked = true;
+ }
}
await clientStream.WriteResponseAsync(response, cancellationToken);
@@ -834,6 +866,34 @@ await clientStream.WriteBodyAsync(body, response.IsChunked,
response.IsBodySent = true;
}
+ private static bool ClientCannotReceiveChunked(SessionEventArgs args) =>
+ args.HttpClient.Request.HttpVersion == HttpHeader.Version10;
+
+ ///
+ /// HTTP/1.0 has no chunked framing, so an unknown-length origin body has to be buffered.
+ /// The buffer uses the session body budget and , same as
+ /// every other whole-body path.
+ ///
+ private async Task BufferUnknownLengthForHttp10Async(SessionEventArgs args,
+ Func streamBody, CancellationToken cancellationToken)
+ {
+ var limit = args.MaxBufferedBodyBytes ?? MaxBufferedBodyBytes;
+ var mode = PolicyModes[PolicyFamily.BodyBudget];
+ using var buffered = new MemoryStream();
+ Stream target = limit > 0 ? new BoundedWriteStream(buffered, limit, mode) : buffered;
+ try
+ {
+ await streamBody(target, cancellationToken);
+ }
+ finally
+ {
+ if (!ReferenceEquals(target, buffered))
+ await target.DisposeAsync();
+ }
+
+ return buffered.ToArray();
+ }
+
private async Task RunHttp11ToHttp2WebSocketTunnelAsync(SessionEventArgs args,
Http2OriginConnection originConnection, CancellationTokenSource cancellationTokenSource,
CancellationToken cancellationToken)
diff --git a/src/Titanium.Web.Proxy/Http2/Http2DeferredOutboundData.cs b/src/Titanium.Web.Proxy/Http2/Http2DeferredOutboundData.cs
index 16ccbdbf0..4b9eaba59 100644
--- a/src/Titanium.Web.Proxy/Http2/Http2DeferredOutboundData.cs
+++ b/src/Titanium.Web.Proxy/Http2/Http2DeferredOutboundData.cs
@@ -18,11 +18,22 @@ internal sealed class Http2DeferredOutboundData
internal const int MaxFramesPerStream = 4;
///
- /// Hard cap: RST if more DATA arrives while already deferred. Sized for a 256 KiB body under
- /// tight connection-window pressure (peers may emit ~1 KiB DATA frames, not only 16 KiB),
- /// plus END_STREAM / burst margin for early-response duplex and slow-consumer backpressure.
+ /// Node backstop. The memory bound is . Tiny frames
+ /// are coalesced so a peer that sends 1-byte DATA cannot hit this by frame count alone.
///
- internal const int HardMaxFramesPerStream = 256;
+ internal const int HardMaxFramesPerStream = 4096;
+
+ ///
+ /// Bytes of parked DATA allowed per stream before RST_STREAM(ENHANCE_YOUR_CALM).
+ /// Matches the previous worst case of 256 full-size (16 KiB) frames, plus one frame of margin,
+ /// so a slow client is not failed sooner than before. Receive credit stays granted on receipt:
+ /// the queue is drained on the peer's write leg, and reclaiming stream credit from that leg
+ /// would take the other direction's write lock. Flow control on the send window is what stops
+ /// a healthy peer; this cap is the bound if that peer keeps sending anyway.
+ ///
+ internal const int HardMaxDeferredBytesPerStream = (4 * 1024 * 1024) + (16 * 1024);
+
+ private const int CoalesceTarget = 16 * 1024;
private readonly object gate = new();
// LinkedList so a partial drain can AddFirst the remainder without reordering later frames.
@@ -96,7 +107,31 @@ public bool TryEnqueue(int streamId, byte[] rented, int offset, int payloadLengt
roundRobinOrder.Add(streamId);
}
- if (q.Count >= HardMaxFramesPerStream)
+ if (!endStream && q.Count > 0)
+ {
+ var lastNode = q.Last!;
+ var last = lastNode.Value;
+ if (!last.EndStream
+ && last.PayloadLength > 0
+ && last.PayloadLength < CoalesceTarget
+ && payloadLength > 0
+ && last.PayloadLength + payloadLength <= CoalesceTarget)
+ {
+ var combined = last.PayloadLength + payloadLength;
+ var merged = ArrayPool.Shared.Rent(combined);
+ last.Rented.AsSpan(last.Offset, last.PayloadLength).CopyTo(merged);
+ rented.AsSpan(offset, payloadLength).CopyTo(merged.AsSpan(last.PayloadLength));
+ ArrayPool.Shared.Return(last.Rented);
+ ArrayPool.Shared.Return(rented);
+ lastNode.Value = new PendingFrame(merged, 0, combined, endStream: false);
+ return true;
+ }
+ }
+
+ var queuedBytes = 0;
+ foreach (var pending in q)
+ queuedBytes += pending.PayloadLength;
+ if (queuedBytes + payloadLength > HardMaxDeferredBytesPerStream || q.Count >= HardMaxFramesPerStream)
return false;
q.AddLast(new PendingFrame(rented, offset, payloadLength, endStream));
diff --git a/src/Titanium.Web.Proxy/Http2/Http2OriginConnection.cs b/src/Titanium.Web.Proxy/Http2/Http2OriginConnection.cs
index 5b5c8f792..4e4f5adbc 100644
--- a/src/Titanium.Web.Proxy/Http2/Http2OriginConnection.cs
+++ b/src/Titanium.Web.Proxy/Http2/Http2OriginConnection.cs
@@ -65,7 +65,6 @@ internal sealed class Http2OriginConnection : IDisposable
private Stream stream;
private Http2FrameWriter? frameWriter;
private readonly ILogger logger;
- private readonly long maxBufferedBodyBytes;
private readonly ProxyResourceLimits resourceLimits;
private readonly SemaphoreSlim writeLock = new(1, 1);
private readonly Http2FlowController sendFlow = new();
@@ -99,14 +98,13 @@ internal sealed class Http2OriginConnection : IDisposable
private Http2FrameHeader? encodeFrameHeader;
private readonly byte[] encodeFrameHeaderBuffer = new byte[9];
- private Http2OriginConnection(TcpServerConnection connection, ILogger logger, long maxBufferedBodyBytes,
+ private Http2OriginConnection(TcpServerConnection connection, ILogger logger,
ProxyResourceLimits resourceLimits)
{
this.connection = connection;
socket = connection.Stream;
stream = socket;
this.logger = logger;
- this.maxBufferedBodyBytes = maxBufferedBodyBytes;
this.resourceLimits = resourceLimits;
}
@@ -312,10 +310,12 @@ private void TryDisposeIfRetiredAndIdle()
/// always has a real MAX_CONCURRENT_STREAMS/frame-size budget to honor.
///
internal static async Task CreateAsync(TcpServerConnection connection,
- ILogger logger, long maxBufferedBodyBytes, CancellationToken cancellationToken,
+ ILogger logger, CancellationToken cancellationToken,
ProxyResourceLimits? resourceLimits = null)
{
- var instance = new Http2OriginConnection(connection, logger, maxBufferedBodyBytes,
+ // Streaming bodies are bounded by HTTP/2 flow control (stream window × concurrent streams),
+ // not by MaxBufferedBodyBytes. That budget applies only where the library buffers a whole body.
+ var instance = new Http2OriginConnection(connection, logger,
resourceLimits ?? ProxyResourceLimits.Default);
var preface = Http2Helper.ConnectionPreface;
@@ -370,7 +370,7 @@ internal static async Task CreateAsync(TcpServerConnectio
// Allocate InterimChannel only when the caller will drain 1xx (on1xx != null). Passthrough
// bridges wait on HeadersReceived instead — avoids per-request Channel/segment Gen0.
- var pending = new PendingStream(maxBufferedBodyBytes, createInterimChannel: on1xx != null);
+ var pending = new PendingStream(createInterimChannel: on1xx != null);
var streamId = 0;
var streamOpened = false;
var bodyHandedOff = false;
@@ -404,6 +404,7 @@ internal static async Task CreateAsync(TcpServerConnectio
throw new Http2OriginGoAwayException(
$"The origin sent GOAWAY before stream {streamId} could be opened; it was never processed.");
+ pending.AttachCredit(this, streamId);
RegisterOpenedStream(streamId, pending);
sendFlow.RegisterStream(streamId);
streamOpened = true;
@@ -635,7 +636,7 @@ await EnqueueDataWithFlowAsync(streamId, ReadOnlyMemory.Empty, endStream:
response.TrailingHeaders.AddHeader(header);
}
- return new Http2OriginExchange(response, Array.Empty(), trailers);
+ return new Http2OriginExchange(response, Array.Empty(), trailers, pending.IsInboundComplete);
}
finally
{
@@ -903,6 +904,75 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
return Task.CompletedTask;
}
+ ///
+ /// Connection-level credit only. Stream credit for piped bodies is granted from the consumer
+ /// () so a slow client cannot grow the pipe past the stream window.
+ ///
+ private Task FlushConnectionReceiveCreditAsync()
+ {
+ if (pendingConnectionReceiveCredit <= 0)
+ return Task.CompletedTask;
+
+ var connectionBytes = pendingConnectionReceiveCredit;
+ pendingConnectionReceiveCredit = 0;
+ Http2Helper.EnqueueWindowUpdate(Writer, 0, connectionBytes);
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Returns stream-window credit for body bytes the consumer has copied out. Single consumer per
+ /// stream; the batch threshold matches .
+ /// Worst-case buffered bytes are MaxConcurrentStreamsPerConnection (default 1000) times the
+ /// 768 KiB stream window.
+ ///
+ private void OnOriginBodyConsumed(int streamId, PendingStream pending, int bytes)
+ {
+ if (bytes <= 0) return;
+
+ var owedAfter = Interlocked.Add(ref pending.StreamCreditOwed, -bytes);
+ var grant = bytes;
+ if (owedAfter < 0)
+ {
+ grant = bytes + owedAfter;
+ Interlocked.Add(ref pending.StreamCreditOwed, -owedAfter);
+ }
+
+ if (grant <= 0) return;
+ var pendingCredit = Interlocked.Add(ref pending.UnflushedStreamCredit, grant);
+ if (pendingCredit >= Http2Helper.ReceiveCreditBatchThreshold)
+ FlushOriginStreamCredit(streamId, pending, force: false);
+ }
+
+ private void ReturnOriginStreamCredit(int streamId, PendingStream pending, int bytes)
+ {
+ var pendingCredit = Interlocked.Add(ref pending.UnflushedStreamCredit, bytes);
+ if (pendingCredit >= Http2Helper.ReceiveCreditBatchThreshold)
+ FlushOriginStreamCredit(streamId, pending, force: false);
+ }
+
+ private void OnOriginBodyReadComplete(int streamId, PendingStream pending)
+ {
+ var rest = Interlocked.Exchange(ref pending.StreamCreditOwed, 0);
+ if (rest > 0)
+ Interlocked.Add(ref pending.UnflushedStreamCredit, rest);
+ FlushOriginStreamCredit(streamId, pending, force: true);
+ }
+
+ private void FlushOriginStreamCredit(int streamId, PendingStream pending, bool force)
+ {
+ while (true)
+ {
+ var amount = Volatile.Read(ref pending.UnflushedStreamCredit);
+ if (amount <= 0) return;
+ if (!force && amount < Http2Helper.ReceiveCreditBatchThreshold) return;
+ if (Interlocked.CompareExchange(ref pending.UnflushedStreamCredit, 0, amount) != amount)
+ continue;
+ if (StreamTableContains(streamId))
+ Http2Helper.EnqueueWindowUpdate(Writer, streamId, amount);
+ return;
+ }
+ }
+
private async Task ReadLoopAsync(CancellationToken cancellationToken) // NOSONAR S3776 -- This protocol/state-machine path shares mutable parsing or transport state; splitting it further would create disproportionate regression risk.
{
var frameHeaderBuffer = new byte[9];
@@ -1116,6 +1186,7 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
case Http2FrameType.Data:
{
byte[]? rented = null;
+ var withholdStreamCredit = false;
try
{
if (TryGetStream(streamId, out var pendingData))
@@ -1163,12 +1234,15 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
}
else
{
- // Copy out of intake before Advance so BodyPipe may hold the memory
- // if a rare backpressured write yields.
+ // Piped bodies: never block this loop (pause threshold is 0) and do not
+ // return stream credit until the consumer reads. Connection credit stays
+ // on receipt so one slow stream cannot shrink the connection window.
+ withholdStreamCredit = true;
rented = ArrayPool.Shared.Rent(length);
payloadSpan.CopyTo(rented);
intake.Advance(length);
var bodyData = StripDataFramingMemory(rented, length, flags);
+ var accepted = bodyData.IsEmpty;
if (!bodyData.IsEmpty)
{
try
@@ -1181,9 +1255,12 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
}
else
{
- // Preserve per-stream DATA order; unlimited pipes complete sync.
+ // Known-CL overrun is the only reason this awaits; unknown-length
+ // pipes have no pause threshold and complete synchronously.
await writeVt.ConfigureAwait(false);
}
+
+ accepted = true;
}
catch (BodySizeLimitExceededException)
{
@@ -1195,6 +1272,19 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
// Writer already completed (cancelled or stream failed); ignore stale frames.
}
}
+
+ if (accepted && length > 0)
+ {
+ // Only real body bytes wait on the consumer. Padding (and the pad
+ // length byte) never reach the pipe, so a padded or all-padding
+ // stream must get that credit back now or it would stall at the
+ // stream window with nothing for the consumer to read.
+ var dataBytes = bodyData.Length;
+ if (dataBytes > 0)
+ Interlocked.Add(ref pendingData.StreamCreditOwed, dataBytes);
+ if (length > dataBytes)
+ ReturnOriginStreamCredit(streamId, pendingData, length - dataBytes);
+ }
}
}
else
@@ -1208,7 +1298,16 @@ private void ReleaseTunnelBookkeeping(int streamId, PendingStream pending, Semap
ArrayPool.Shared.Return(rented);
}
- if ((flags & Http2FrameFlag.EndStream) != 0)
+ if (withholdStreamCredit)
+ {
+ if (length > 0)
+ pendingConnectionReceiveCredit += length;
+ if (pendingConnectionReceiveCredit >= Http2Helper.ReceiveCreditBatchThreshold)
+ await FlushConnectionReceiveCreditAsync().ConfigureAwait(false);
+ if ((flags & Http2FrameFlag.EndStream) != 0)
+ CompleteStream(streamId);
+ }
+ else if ((flags & Http2FrameFlag.EndStream) != 0)
{
// Tiny-GET hot path: END_STREAM closes the stream — skip stream
// WINDOW_UPDATE and do not force-flush connection credit (was one
@@ -1798,7 +1897,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo
"Origin socket writes must go through Http2FrameWriter; mixed writes corrupt the HTTP/2 byte stream.");
}
- private sealed class PendingStream : IDisposable
+ private sealed class PendingStream : IDisposable, IBodyConsumptionSink
{
///
/// Known Content-Length bodies ≤ 64 KiB are filled here on the ReadLoop (no ).
@@ -1807,7 +1906,6 @@ private sealed class PendingStream : IDisposable
internal const int InlineBodyThresholdBytes = 64 * 1024;
private BoundedBodyPipe? bodyPipe;
- private readonly long maxBodyBytes;
private byte[]? inlineBody;
private int inlineWritten;
private int inboundComplete;
@@ -1852,21 +1950,44 @@ private sealed class PendingStream : IDisposable
internal Response? Response;
internal HeaderCollection? TrailingHeaders;
- internal PendingStream(long maxBodyBytes)
- : this(maxBodyBytes, createInterimChannel: true)
+ /// On-wire DATA bytes read but not yet returned as stream WINDOW_UPDATE.
+ internal int StreamCreditOwed;
+
+ /// Stream credit consumed by the reader and not yet flushed.
+ internal int UnflushedStreamCredit;
+
+ private Http2OriginConnection? creditOwner;
+ private int creditStreamId;
+
+ ///
+ /// Two field stores, no allocation: this runs for every request, including the tiny-GET path
+ /// that never creates a body pipe.
+ ///
+ internal void AttachCredit(Http2OriginConnection owner, int streamId)
+ {
+ creditOwner = owner;
+ creditStreamId = streamId;
+ }
+
+ void IBodyConsumptionSink.OnBytesConsumed(int bytes) =>
+ creditOwner?.OnOriginBodyConsumed(creditStreamId, this, bytes);
+
+ void IBodyConsumptionSink.OnReadCompleted() =>
+ creditOwner?.OnOriginBodyReadComplete(creditStreamId, this);
+
+ internal PendingStream()
+ : this(createInterimChannel: true)
{
}
- /// Max buffered body bytes for .
///
/// When , allocate the 1xx relay channel. Tests and interception paths
/// that expect 1xx use this; passes when
/// on1xx is null.
///
- internal PendingStream(long maxBodyBytes, bool createInterimChannel)
+ internal PendingStream(bool createInterimChannel)
{
IsTunnel = false;
- this.maxBodyBytes = maxBodyBytes;
// BodyPipe is lazy: probe tiny-GET uses InlineBody; streaming / unknown CL creates on demand.
if (createInterimChannel)
{
@@ -1875,10 +1996,9 @@ internal PendingStream(long maxBodyBytes, bool createInterimChannel)
}
}
- private PendingStream(bool isTunnel)
+ private PendingStream(TunnelMarker _)
{
- IsTunnel = isTunnel;
- maxBodyBytes = 0;
+ IsTunnel = true;
// Tunnel streams never buffer a finite HTTP body; BodyPipe unused.
TunnelDataChannel = Channel.CreateBounded(new BoundedChannelOptions(256)
{
@@ -1888,7 +2008,9 @@ private PendingStream(bool isTunnel)
});
}
- internal static PendingStream CreateTunnel() => new(true);
+ internal static PendingStream CreateTunnel() => new(new TunnelMarker());
+
+ private readonly struct TunnelMarker;
///
/// When Content-Length is known and ≤ , allocate a
@@ -1938,7 +2060,12 @@ internal BoundedBodyPipe EnsureBodyPipe()
var existing = bodyPipe;
if (existing != null) return existing;
- var created = new BoundedBodyPipe(maxBodyBytes);
+ var contentLength = Response?.ContentLength ?? -1;
+ // Known length: overrun is a protocol error. Unknown length: no cumulative cap.
+ // Backpressure is off so ReadLoop never waits on the consumer.
+ var created = new BoundedBodyPipe(contentLength > 0 ? contentLength : 0, applyBackpressure: false);
+ if (creditOwner != null)
+ created.ConsumptionSink = this;
var prior = Interlocked.CompareExchange(ref bodyPipe, created, null);
if (prior != null)
{
@@ -2034,11 +2161,13 @@ public void AddHeader(ByteString name, ByteString value, bool sensitive)
/// The fully materialized result of one exchange.
internal sealed class Http2OriginExchange
{
- internal Http2OriginExchange(Response response, byte[] body, HeaderCollection? trailingHeaders)
+ internal Http2OriginExchange(Response response, byte[] body, HeaderCollection? trailingHeaders,
+ bool originBodyComplete = true)
{
Response = response;
Body = body;
TrailingHeaders = trailingHeaders;
+ OriginBodyComplete = originBodyComplete;
}
internal Response Response { get; }
@@ -2046,6 +2175,12 @@ internal Http2OriginExchange(Response response, byte[] body, HeaderCollection? t
internal byte[] Body { get; }
internal HeaderCollection? TrailingHeaders { get; }
+
+ ///
+ /// True when the origin had already finished the body before the caller started delivering it.
+ /// Unknown-length responses that are still open must be framed as chunked instead of buffered.
+ ///
+ internal bool OriginBodyComplete { get; }
}
///
diff --git a/src/Titanium.Web.Proxy/Http3/Http3Frame.cs b/src/Titanium.Web.Proxy/Http3/Http3Frame.cs
index abe86afe5..ef7f235b3 100644
--- a/src/Titanium.Web.Proxy/Http3/Http3Frame.cs
+++ b/src/Titanium.Web.Proxy/Http3/Http3Frame.cs
@@ -18,6 +18,14 @@ internal sealed class Http3Frame
{
private byte[]? rentedPayload;
+ ///
+ /// Default per-frame payload cap used when a caller passes maxPayloadBytes: 0.
+ /// Independent of ProxyServer.MaxBufferedBodyBytes: that budget bounds whole-body
+ /// buffering, not a single HTTP/3 DATA frame. 4 MiB covers legitimate frames without
+ /// renting an attacker-chosen length.
+ ///
+ internal const long DefaultMaxPayloadBytes = 4 * 1024 * 1024;
+
public ulong Type { get; }
public ReadOnlyMemory Payload { get; }
@@ -55,9 +63,12 @@ public void ReturnPayload()
var payloadLength = await Http3VarInt.ReadAsync(stream, cancellationToken)
?? throw new Http3ConnectionException(Http3ErrorCode.FrameError, "Unexpected end of stream reading frame length.");
- if (maxPayloadBytes > 0 && (long)payloadLength > maxPayloadBytes)
+ // 0 means "use the dedicated frame limit", not unlimited. A hostile peer picks
+ // payloadLength; checking before the int cast is what stops the rent.
+ var limit = maxPayloadBytes > 0 ? maxPayloadBytes : DefaultMaxPayloadBytes;
+ if (payloadLength > (ulong)limit || payloadLength > int.MaxValue)
throw new Http3ConnectionException(Http3ErrorCode.ExcessiveLoad,
- $"HTTP/3 frame payload {payloadLength} bytes exceeds limit {maxPayloadBytes}.");
+ $"HTTP/3 frame payload {payloadLength} bytes exceeds limit {limit}.");
if (payloadLength == 0)
return new Http3Frame(frameType.Value, ReadOnlyMemory.Empty, null);
diff --git a/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Http2.cs b/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Http2.cs
index 4340b927e..c60f3284b 100644
--- a/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Http2.cs
+++ b/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Http2.cs
@@ -218,8 +218,7 @@ private static async Task SendHttp2OriginFastWithGoAwayRetr
null, sessionArgs);
}
- return await Http2OriginConnection.CreateAsync(tcp, logger,
- fwd.MaxBufferedBodyBytes, ct, server.ResourceLimits);
+ return await Http2OriginConnection.CreateAsync(tcp, logger, ct, server.ResourceLimits);
}
finally
{
@@ -346,6 +345,13 @@ private static async Task SendHttp2OriginFastWithGoAwayRetr
}
}
+ ///
+ /// Buffers an H3 request body only when a reader was installed and the streaming pump was not.
+ /// Normal interception installs in
+ /// Http3RequestStream, and the TCP bridge prefers that pump, so this fallback is the
+ /// leftover whole-body path. It goes through GetRequestBody, which is bounded by
+ /// MaxBufferedBodyBytes.
+ ///
private static async Task EnsureHttp3BufferedBodyAsync(
SessionEventArgs sessionArgs, CancellationToken cancellationToken)
{
@@ -479,9 +485,7 @@ private readonly record struct Http2OriginTarget(
null, sessionArgs);
}
- return await Http2OriginConnection.CreateAsync(tcp, logger,
- sessionArgs.MaxBufferedBodyBytes ?? server.MaxBufferedBodyBytes, ct,
- server.ResourceLimits);
+ return await Http2OriginConnection.CreateAsync(tcp, logger, ct, server.ResourceLimits);
}, cancellationToken);
}
diff --git a/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Quic.cs b/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Quic.cs
index d6ccabd12..a8b353e4d 100644
--- a/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Quic.cs
+++ b/src/Titanium.Web.Proxy/Http3/Http3OriginBridge.Quic.cs
@@ -254,7 +254,7 @@ await Http3Frame.WriteAsync(originStream, Http3FrameType.Data, body, cancellatio
}
}
- var maxPayload = sessionArgs.MaxBufferedBodyBytes ?? server.MaxBufferedBodyBytes;
+ var maxPayload = Http3Frame.DefaultMaxPayloadBytes;
// Stream large / unknown-length bodies as DATA arrives (TTFB on big HTML). Tiny known-CL
// must materialize first: H1 WriteResponseAsync + StreamBodyWriter emits a header-only
@@ -666,7 +666,7 @@ await Http3Frame.WriteAsync(originStream, Http3FrameType.Headers, encodedHeaders
// Verbatim origin→client frame copy, or MITM capture when clientStream is null.
// Tiny GET: coalesce HEADERS+DATA into one Quic write (origin probe sends both).
const int relayCoalesceMaxBytes = 16 * 1024;
- var maxPayload = Math.Max(fwd.MaxBufferedBodyBytes, server.MaxDecodedHeaderListBytes);
+ var maxPayload = Math.Max(Http3Frame.DefaultMaxPayloadBytes, server.MaxDecodedHeaderListBytes);
var captureForMitm = clientStream is null;
var sawFinalHeaders = false;
while (true)
diff --git a/src/Titanium.Web.Proxy/Logging/ProxyLog.cs b/src/Titanium.Web.Proxy/Logging/ProxyLog.cs
index 1c6bd36de..ee1469c22 100644
--- a/src/Titanium.Web.Proxy/Logging/ProxyLog.cs
+++ b/src/Titanium.Web.Proxy/Logging/ProxyLog.cs
@@ -125,7 +125,7 @@ internal static void EffectiveProfileAtStartup(ILogger logger, Options.ProxyProf
{
if (!logger.IsEnabled(LogLevel.Information)) return;
logger.LogInformation(
- "Starting with profile {Profile} (body={Body}, decompressionRatio={DecompressionRatio}, headerLimits={HeaderLimits}, admission={Admission}, http2AbuseBudget={Http2AbuseBudget}, http2RelayValidation={Http2RelayValidation}, allowAmbiguousFraming={AllowAmbiguousFraming}).",
+ "Starting with profile {Profile} (body={Body}, decompressionRatio={DecompressionRatio}, headerLimits={HeaderLimits}, admission={Admission}, http2AbuseBudget={Http2AbuseBudget}, http2RelayValidation={Http2RelayValidation}, webSocketFrameBudget={WebSocketFrameBudget}, allowAmbiguousFraming={AllowAmbiguousFraming}).",
profile,
policyModes[Options.PolicyFamily.BodyBudget],
policyModes[Options.PolicyFamily.DecompressionRatio],
@@ -133,6 +133,7 @@ internal static void EffectiveProfileAtStartup(ILogger logger, Options.ProxyProf
policyModes[Options.PolicyFamily.AdmissionControl],
policyModes[Options.PolicyFamily.Http2AbuseBudget],
policyModes[Options.PolicyFamily.Http2RelayValidation],
+ policyModes[Options.PolicyFamily.WebSocketFrameBudget],
policyModes.AllowAmbiguousFraming);
}
diff --git a/src/Titanium.Web.Proxy/Network/Streams/BoundedBodyPipe.cs b/src/Titanium.Web.Proxy/Network/Streams/BoundedBodyPipe.cs
index 48475bbcc..edb0b7aa9 100644
--- a/src/Titanium.Web.Proxy/Network/Streams/BoundedBodyPipe.cs
+++ b/src/Titanium.Web.Proxy/Network/Streams/BoundedBodyPipe.cs
@@ -7,6 +7,22 @@
namespace Titanium.Web.Proxy.Network.Streams;
+///
+/// Observes the consumer side of a . Used to return HTTP/2
+/// stream flow-control credit as the client actually reads.
+///
+internal interface IBodyConsumptionSink
+{
+ /// Body bytes just copied out of the pipe, on the consumer thread.
+ void OnBytesConsumed(int bytes);
+
+ ///
+ /// Once, after the reader is completed (success or failure), so the sink can flush any
+ /// credit still owed after a short final read.
+ ///
+ void OnReadCompleted();
+}
+
///
/// A bounded, cancellation-aware pipe wrapping
/// for streaming HTTP body bytes between producer and consumer tasks. Enforces a
@@ -20,19 +36,49 @@ internal sealed class BoundedBodyPipe : IDisposable
private long totalWritten;
private bool disposed;
+ ///
+ /// Optional observer of the consumer side. An interface (not delegates) so attaching it costs no
+ /// per-request closure allocation on the HTTP/2 origin path.
+ ///
+ internal IBodyConsumptionSink? ConsumptionSink { get; set; }
+
///
/// Initializes a new with the given byte limit.
/// of 0 means unlimited.
///
- internal BoundedBodyPipe(long maxBytes = 0)
+ ///
+ /// When , WriteAsync never waits for a reader. Origin response
+ /// pipes use this so the shared HTTP/2 read loop cannot stall sibling streams. Memory is then
+ /// bounded by receive flow control, not by a pause threshold.
+ ///
+ internal BoundedBodyPipe(long maxBytes = 0, bool applyBackpressure = true)
{
this.maxBytes = maxBytes;
- // For unlimited pipes, disable backpressure (pauseWriterThreshold: 0) so that WriteAsync
- // never blocks waiting for a reader. For bounded pipes, cap backpressure at 512 KB or the
- // byte limit, whichever is smaller, to give callers early cancellation feedback.
+ // pauseWriterThreshold 0 still pauses once the pipe holds data (unconsumed >= 0). Origin
+ // response pipes pass applyBackpressure: false and use a threshold the stream window cannot
+ // reach, so ReadLoop's WriteAsync completes synchronously.
+ long pause;
+ long resume;
+ if (!applyBackpressure)
+ {
+ pause = long.MaxValue;
+ resume = long.MaxValue - 1;
+ }
+ else if (maxBytes > 0)
+ {
+ pause = Math.Min(maxBytes, 512 * 1024);
+ resume = Math.Min(maxBytes / 2, 256 * 1024);
+ if (resume >= pause) resume = Math.Max(0, pause - 1);
+ }
+ else
+ {
+ pause = 0;
+ resume = 0;
+ }
+
pipe = new Pipe(new PipeOptions(
- pauseWriterThreshold: maxBytes > 0 ? Math.Min(maxBytes, 512 * 1024) : 0,
- resumeWriterThreshold: maxBytes > 0 ? Math.Min(maxBytes, 256 * 1024) : 0,
+ pauseWriterThreshold: pause,
+ resumeWriterThreshold: resume,
useSynchronizationContext: false));
}
@@ -109,8 +155,37 @@ private async ValueTask WriteAsyncSlow(ValueTask flushVt, int byteC
///
internal async Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default)
{
- await pipe.Reader.CopyToAsync(destination, cancellationToken);
- await pipe.Reader.CompleteAsync();
+ try
+ {
+ while (true)
+ {
+ var result = await pipe.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ var buffer = result.Buffer;
+ if (buffer.Length > 0)
+ {
+ foreach (var segment in buffer)
+ {
+ await destination.WriteAsync(segment, cancellationToken).ConfigureAwait(false);
+ if (segment.Length > 0)
+ ConsumptionSink?.OnBytesConsumed(segment.Length);
+ }
+
+ pipe.Reader.AdvanceTo(buffer.End);
+ }
+ else
+ {
+ pipe.Reader.AdvanceTo(buffer.Start);
+ }
+
+ if (result.IsCompleted || result.IsCanceled)
+ break;
+ }
+ }
+ finally
+ {
+ await pipe.Reader.CompleteAsync().ConfigureAwait(false);
+ ConsumptionSink?.OnReadCompleted();
+ }
}
///
@@ -138,12 +213,15 @@ internal async Task ReadExactAsync(Memory destination, CancellationTo
var toCopy = (int)Math.Min(buffer.Length, destination.Length - offset);
buffer.Slice(0, toCopy).CopyTo(destination.Span.Slice(offset, toCopy));
offset += toCopy;
+ if (toCopy > 0)
+ ConsumptionSink?.OnBytesConsumed(toCopy);
pipe.Reader.AdvanceTo(buffer.GetPosition(toCopy));
if (result.IsCompleted && offset < destination.Length)
break;
}
await pipe.Reader.CompleteAsync().ConfigureAwait(false);
+ ConsumptionSink?.OnReadCompleted();
return offset;
}
diff --git a/src/Titanium.Web.Proxy/Network/Streams/BoundedWriteStream.cs b/src/Titanium.Web.Proxy/Network/Streams/BoundedWriteStream.cs
index d29ac1b95..14254f6db 100644
--- a/src/Titanium.Web.Proxy/Network/Streams/BoundedWriteStream.cs
+++ b/src/Titanium.Web.Proxy/Network/Streams/BoundedWriteStream.cs
@@ -19,8 +19,8 @@ namespace Titanium.Web.Proxy.Network.Streams;
/// and native HTTP/2 client-facing body interception in Http2Helper. Both only bound an
/// individual chunk or DATA frame - per the hardening plan, "per-frame limits are not cumulative
/// limits" - so an attacker sending many small chunks/frames could otherwise accumulate an
-/// unbounded in-memory body. already solves the
-/// same problem for the HTTP/2-to-origin body-streaming path; this type gives the whole-body
+/// unbounded in-memory body. Origin response pipes bound a known Content-Length and use HTTP/2
+/// stream flow control for unknown-length bodies; this type gives the whole-body
/// MemoryStream paths the same cumulative guarantee without adopting a full pipe.
///
///
diff --git a/src/Titanium.Web.Proxy/Options/PolicyFamily.cs b/src/Titanium.Web.Proxy/Options/PolicyFamily.cs
index d5d74d1d9..45b5511f3 100644
--- a/src/Titanium.Web.Proxy/Options/PolicyFamily.cs
+++ b/src/Titanium.Web.Proxy/Options/PolicyFamily.cs
@@ -12,9 +12,15 @@ namespace Titanium.Web.Proxy.Options;
public enum PolicyFamily
{
///
- /// Cumulative whole-body buffering limits (MaxBufferedBodyBytes and the
- /// /
- /// pair) enforced via across H1, H2 and H3.
+ /// Cumulative whole-body buffering limits (MaxBufferedBodyBytes) enforced via
+ /// when a caller materializes a body.
+ /// Streaming relays do not consult this family.
+ ///
+ /// and
+ /// are reserved. They are accepted
+ /// by option plumbing and are not enforced on a live read path; the enforced whole-body
+ /// budget is MaxBufferedBodyBytes.
+ ///
///
BodyBudget,
@@ -98,5 +104,24 @@ public enum PolicyFamily
/// refuses to replay non-idempotent methods and rejects bare CR/LF/NUL in header values.
///
///
- Http1ReplaySafety
+ Http1ReplaySafety,
+
+ ///
+ /// Per-frame WebSocket payload limit (MaxWebSocketFramePayloadBytes) applied only when
+ /// BeforeWebSocketFrame is subscribed. Raw relay is unaffected.
+ ///
+ /// (default on every profile) closes the connection with
+ /// status 1009 before the payload is buffered.
+ ///
+ ///
+ /// relays an oversize frame without materializing it and
+ /// raises SessionEventArgs.WebSocketFrameElided. Frame hooks are not invoked for
+ /// that payload, so a content filter must stay on .
+ ///
+ ///
+ /// skips the check and will buffer the frame, up to the
+ /// decoder's structural bound.
+ ///
+ ///
+ WebSocketFrameBudget
}
diff --git a/src/Titanium.Web.Proxy/Options/ProxyPolicyModes.cs b/src/Titanium.Web.Proxy/Options/ProxyPolicyModes.cs
index 2558e3fad..b3e6c61c7 100644
--- a/src/Titanium.Web.Proxy/Options/ProxyPolicyModes.cs
+++ b/src/Titanium.Web.Proxy/Options/ProxyPolicyModes.cs
@@ -54,7 +54,12 @@ private ProxyPolicyModes(Dictionary modes, bool allowA
/// Returns the mode selected for .
public PolicyMode this[PolicyFamily family] => modes[family];
- /// Builds a snapshot with an explicit mode for every family. starts .
+ ///
+ /// Builds a snapshot with an explicit mode for the first seven families.
+ /// starts at (use
+ /// to change it) so this shipped signature stays binary-compatible.
+ /// starts .
+ ///
public static ProxyPolicyModes Create(
PolicyMode bodyBudget,
PolicyMode decompressionRatio,
@@ -72,7 +77,8 @@ public static ProxyPolicyModes Create(
[PolicyFamily.AdmissionControl] = admissionControl,
[PolicyFamily.Http2AbuseBudget] = http2AbuseBudget,
[PolicyFamily.Http2RelayValidation] = http2RelayValidation,
- [PolicyFamily.Http1ReplaySafety] = http1ReplaySafety
+ [PolicyFamily.Http1ReplaySafety] = http1ReplaySafety,
+ [PolicyFamily.WebSocketFrameBudget] = PolicyMode.Enforce
};
return new ProxyPolicyModes(dict, false);
}
diff --git a/src/Titanium.Web.Proxy/Properties/AssemblyInfo.cs b/src/Titanium.Web.Proxy/Properties/AssemblyInfo.cs
index f63b23397..b62d50043 100644
--- a/src/Titanium.Web.Proxy/Properties/AssemblyInfo.cs
+++ b/src/Titanium.Web.Proxy/Properties/AssemblyInfo.cs
@@ -77,5 +77,5 @@
// file-properties version disagreed with the package it was published in. Keep both of the values
// below equal to (as Major.Minor.Build.0) whenever that property changes.
-[assembly: AssemblyVersion("7.0.17.0")]
-[assembly: AssemblyFileVersion("7.0.17.0")]
+[assembly: AssemblyVersion("7.0.18.0")]
+[assembly: AssemblyFileVersion("7.0.18.0")]
diff --git a/src/Titanium.Web.Proxy/ProxyLimits.cs b/src/Titanium.Web.Proxy/ProxyLimits.cs
index 8bd15816f..a17ae9045 100644
--- a/src/Titanium.Web.Proxy/ProxyLimits.cs
+++ b/src/Titanium.Web.Proxy/ProxyLimits.cs
@@ -40,7 +40,7 @@ public static class ProxyLimits
/// Reference value only - see the type-level remarks. Matches the current default of
/// ProxyServer.MaxBufferedBodyBytes: maximum buffered request or response body size for
/// proxied exchanges where full buffering is required (body mutation, authentication retry,
- /// etc.).
+ /// etc.). Streaming relays are not counted against this value.
/// Default: 4 MiB.
///
public static readonly long DefaultMaxBufferedBodyBytes = 4L * 1024 * 1024;
diff --git a/src/Titanium.Web.Proxy/ProxyServer.cs b/src/Titanium.Web.Proxy/ProxyServer.cs
index 0afeb2355..4e56afb8d 100644
--- a/src/Titanium.Web.Proxy/ProxyServer.cs
+++ b/src/Titanium.Web.Proxy/ProxyServer.cs
@@ -755,18 +755,23 @@ internal Http3.Dns.IHttpsSvcbResolver HttpsSvcbResolver
///
/// Maximum bytes the proxy will buffer for a single request or response body when
- /// body buffering is required (body-read hooks, authentication retry, etc.). Bodies
- /// larger than this limit are rejected with 413 (upstream request) or connection teardown
- /// (upstream response). Set to 0 to disable the limit (not recommended).
- /// Default: 4,194,304 (4 MiB).
+ /// whole-body buffering is required (GetRequestBody / GetResponseBody,
+ /// authentication retry, and other paths that materialize the body). Bodies larger than
+ /// this limit are rejected with 413 (upstream request) or connection teardown (upstream
+ /// response). Streaming relays (H1, H2, and H3, including bridges) are not counted against
+ /// this budget: they are bounded by protocol flow control. Set to 0 to disable the limit
+ /// (not recommended). Default: 4,194,304 (4 MiB).
///
public int MaxBufferedBodyBytes { get; set; } = 4 * 1024 * 1024;
///
/// Maximum WebSocket frame payload size in bytes that the proxy will accept during
/// frame-level interception (i.e. when BeforeWebSocketFrame has at least one
- /// subscriber). Frames whose decoded payload exceeds this limit cause the WebSocket
- /// connection to be closed with Close code 1009 (Message Too Big).
+ /// subscriber). Frames whose decoded payload exceeds this limit are handled by
+ /// .
+ /// (the default) closes the connection with Close code 1009 (Message Too Big) before the
+ /// payload is buffered. relays the frame unbuffered
+ /// and raises WebSocketFrameElided instead of invoking BeforeWebSocketFrame.
/// Raw-relay sessions (no BeforeWebSocketFrame subscriber) bypass this check
/// entirely and pass all frames through unvalidated.
/// Default: 16,777,216 (16 MiB).
diff --git a/src/Titanium.Web.Proxy/PublicAPI.Unshipped.txt b/src/Titanium.Web.Proxy/PublicAPI.Unshipped.txt
index 9fd2277e6..c8c3f66e3 100644
--- a/src/Titanium.Web.Proxy/PublicAPI.Unshipped.txt
+++ b/src/Titanium.Web.Proxy/PublicAPI.Unshipped.txt
@@ -272,3 +272,12 @@ Titanium.Web.Proxy.ProxyServer.ResponseHeaderContributor.get -> System.Action void
Titanium.Web.Proxy.ProxyServer.ShouldBypassDecryptForLearnedHost(string? host) -> bool
static Titanium.Web.Proxy.Network.FirefoxCertificateTrust.LastEnterpriseRootsStep.get -> string?
+Titanium.Web.Proxy.EventArguments.SessionEventArgs.CanBufferBody(Titanium.Web.Proxy.Http.RequestResponseBase! message) -> bool
+Titanium.Web.Proxy.EventArguments.SessionEventArgs.WebSocketFrameElided -> Titanium.Web.Proxy.EventArguments.AsyncEventHandler?
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.DeclaredPayloadLength.get -> long
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Direction.get -> Titanium.Web.Proxy.EventArguments.WebSocketFrameDirection
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.IsFinal.get -> bool
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.OpCode.get -> Titanium.Web.Proxy.WebsocketOpCode
+Titanium.Web.Proxy.EventArguments.WebSocketFrameElidedEventArgs.Session.get -> Titanium.Web.Proxy.EventArguments.SessionEventArgs!
+Titanium.Web.Proxy.Options.PolicyFamily.WebSocketFrameBudget = 7 -> Titanium.Web.Proxy.Options.PolicyFamily
diff --git a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
index 072e45039..9a2cce5e1 100644
--- a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+++ b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
@@ -13,7 +13,7 @@
- 7.0.17
+ 7.0.18