Commit c75ad1a5 authored by Honfika's avatar Honfika

prefetch fix

parent 37d1d6a2
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<value>True</value> <value>True</value>
</setting> </setting>
<setting name="EnableTcpServerConnectionPrefetch" serializeAs="String"> <setting name="EnableTcpServerConnectionPrefetch" serializeAs="String">
<value>False</value> <value>True</value>
</setting> </setting>
<setting name="EnableWinAuth" serializeAs="String"> <setting name="EnableWinAuth" serializeAs="String">
<value>False</value> <value>False</value>
......
...@@ -95,7 +95,7 @@ namespace WindowsServiceExample.Properties ...@@ -95,7 +95,7 @@ namespace WindowsServiceExample.Properties
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")] [global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool EnableTcpServerConnectionPrefetch public bool EnableTcpServerConnectionPrefetch
{ {
get get
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<Value Profile="(Default)">True</Value> <Value Profile="(Default)">True</Value>
</Setting> </Setting>
<Setting Name="EnableTcpServerConnectionPrefetch" Type="System.Boolean" Scope="Application"> <Setting Name="EnableTcpServerConnectionPrefetch" Type="System.Boolean" Scope="Application">
<Value Profile="(Default)">False</Value> <Value Profile="(Default)">True</Value>
</Setting> </Setting>
<Setting Name="EnableWinAuth" Type="System.Boolean" Scope="Application"> <Setting Name="EnableWinAuth" Type="System.Boolean" Scope="Application">
<Value Profile="(Default)">False</Value> <Value Profile="(Default)">False</Value>
......
...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy ...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy
var clientStream = new HttpClientStream(clientConnection, clientConnection.GetStream(), BufferPool, cancellationToken); var clientStream = new HttpClientStream(clientConnection, clientConnection.GetStream(), BufferPool, cancellationToken);
Task<TcpServerConnection>? prefetchConnectionTask = null; Task<TcpServerConnection?>? prefetchConnectionTask = null;
bool closeServerConnection = false; bool closeServerConnection = false;
try try
...@@ -145,13 +145,16 @@ namespace Titanium.Web.Proxy ...@@ -145,13 +145,16 @@ namespace Titanium.Web.Proxy
// todo: this is a hack, because Titanium does not support HTTP protocol changing currently // todo: this is a hack, because Titanium does not support HTTP protocol changing currently
var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs,
true, SslExtensions.Http2ProtocolAsList, true, SslExtensions.Http2ProtocolAsList,
true, cancellationToken); true, true, cancellationToken);
http2Supported = connection.NegotiatedApplicationProtocol == if (connection != null)
SslApplicationProtocol.Http2; {
http2Supported = connection.NegotiatedApplicationProtocol ==
SslApplicationProtocol.Http2;
// release connection back to pool instead of closing when connection pool is enabled. // release connection back to pool instead of closing when connection pool is enabled.
await tcpConnectionFactory.Release(connection, true); await tcpConnectionFactory.Release(connection, true);
}
} }
catch (Exception) catch (Exception)
{ {
...@@ -162,22 +165,11 @@ namespace Titanium.Web.Proxy ...@@ -162,22 +165,11 @@ namespace Titanium.Web.Proxy
if (EnableTcpServerConnectionPrefetch) if (EnableTcpServerConnectionPrefetch)
{ {
IPAddress[]? ipAddresses = null; // don't pass cancellation token here
try // it could cause floating server connections when client exits
{ prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
// make sure the host can be resolved before creating the prefetch task true, null, false, true,
ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host); CancellationToken.None);
}
catch (SocketException) { }
if (ipAddresses != null && ipAddresses.Length > 0)
{
// don't pass cancellation token here
// it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
true, null, false,
CancellationToken.None);
}
} }
string connectHostname = requestLine.RequestUri.GetString(); string connectHostname = requestLine.RequestUri.GetString();
...@@ -272,9 +264,9 @@ namespace Titanium.Web.Proxy ...@@ -272,9 +264,9 @@ namespace Titanium.Web.Proxy
// create new connection to server. // create new connection to server.
// If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
// this connection should not be HTTPS. // this connection should not be HTTPS.
var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, var connection = (await tcpConnectionFactory.GetServerConnection(this, connectArgs,
true, null, true, null,
true, cancellationToken); true, false, cancellationToken))!;
try try
{ {
...@@ -349,9 +341,9 @@ namespace Titanium.Web.Proxy ...@@ -349,9 +341,9 @@ namespace Titanium.Web.Proxy
throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
} }
var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, var connection = (await tcpConnectionFactory.GetServerConnection(this, connectArgs,
true, SslExtensions.Http2ProtocolAsList, true, SslExtensions.Http2ProtocolAsList,
true, cancellationToken); true, false, cancellationToken))!;
try try
{ {
#if NETSTANDARD2_1 #if NETSTANDARD2_1
......
...@@ -154,7 +154,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -154,7 +154,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol }; applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
} }
return GetServerConnection(proxyServer, session, isConnect, applicationProtocols, noCache, cancellationToken); return GetServerConnection(proxyServer, session, isConnect, applicationProtocols, noCache, false, cancellationToken)!;
} }
/// <summary> /// <summary>
...@@ -165,10 +165,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -165,10 +165,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocols"></param> /// <param name="applicationProtocols"></param>
/// <param name="noCache">if set to <c>true</c> [no cache].</param> /// <param name="noCache">if set to <c>true</c> [no cache].</param>
/// <param name="prefetch">if set to <c>true</c> [prefetch].</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer proxyServer, SessionEventArgsBase session, bool isConnect, internal async Task<TcpServerConnection?> GetServerConnection(ProxyServer proxyServer, SessionEventArgsBase session, bool isConnect,
List<SslApplicationProtocol>? applicationProtocols, bool noCache, CancellationToken cancellationToken) List<SslApplicationProtocol>? applicationProtocols, bool noCache, bool prefetch, CancellationToken cancellationToken)
{ {
var customUpStreamProxy = session.CustomUpStreamProxy; var customUpStreamProxy = session.CustomUpStreamProxy;
...@@ -208,7 +209,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -208,7 +209,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
var upStreamEndPoint = session.HttpClient.UpStreamEndPoint ?? proxyServer.UpStreamEndPoint; var upStreamEndPoint = session.HttpClient.UpStreamEndPoint ?? proxyServer.UpStreamEndPoint;
var upStreamProxy = customUpStreamProxy ?? (isHttps ? proxyServer.UpStreamHttpsProxy : proxyServer.UpStreamHttpProxy); var upStreamProxy = customUpStreamProxy ?? (isHttps ? proxyServer.UpStreamHttpsProxy : proxyServer.UpStreamHttpProxy);
return await GetServerConnection(proxyServer, host, port, session.HttpClient.Request.HttpVersion, isHttps, return await GetServerConnection(proxyServer, host, port, session.HttpClient.Request.HttpVersion, isHttps,
applicationProtocols, isConnect, session, upStreamEndPoint, upStreamProxy, noCache, cancellationToken); applicationProtocols, isConnect, session, upStreamEndPoint, upStreamProxy, noCache, prefetch, cancellationToken);
} }
/// <summary> /// <summary>
...@@ -225,12 +226,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -225,12 +226,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="noCache">Not from cache/create new connection.</param> /// <param name="noCache">Not from cache/create new connection.</param>
/// <param name="prefetch">if set to <c>true</c> [prefetch].</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer proxyServer, string remoteHostName, int remotePort, internal async Task<TcpServerConnection?> GetServerConnection(ProxyServer proxyServer, string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol>? applicationProtocols, bool isConnect, Version httpVersion, bool isHttps, List<SslApplicationProtocol>? applicationProtocols, bool isConnect,
SessionEventArgsBase sessionArgs, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy, SessionEventArgsBase sessionArgs, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy,
bool noCache, CancellationToken cancellationToken) bool noCache, bool prefetch, CancellationToken cancellationToken)
{ {
var sslProtocol = sessionArgs.ClientConnection.SslProtocol; var sslProtocol = sessionArgs.ClientConnection.SslProtocol;
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort, var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
...@@ -259,7 +261,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -259,7 +261,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol,
applicationProtocols, isConnect, proxyServer, sessionArgs, upStreamEndPoint, externalProxy, cacheKey, cancellationToken); applicationProtocols, isConnect, proxyServer, sessionArgs, upStreamEndPoint, externalProxy, cacheKey, prefetch, cancellationToken);
return connection; return connection;
} }
...@@ -279,12 +281,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -279,12 +281,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="cacheKey">The connection cache key</param> /// <param name="cacheKey">The connection cache key</param>
/// <param name="prefetch">if set to <c>true</c> [prefetch].</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpServerConnection> createServerConnection(string remoteHostName, int remotePort, private async Task<TcpServerConnection?> createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, SslProtocols sslProtocol, List<SslApplicationProtocol>? applicationProtocols, bool isConnect, Version httpVersion, bool isHttps, SslProtocols sslProtocol, List<SslApplicationProtocol>? applicationProtocols, bool isConnect,
ProxyServer proxyServer, SessionEventArgsBase sessionArgs, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy, string cacheKey, ProxyServer proxyServer, SessionEventArgsBase sessionArgs, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy, string cacheKey,
CancellationToken cancellationToken) bool prefetch, CancellationToken cancellationToken)
{ {
// deny connection to proxy end points to avoid infinite connection loop. // deny connection to proxy end points to avoid infinite connection loop.
if (Server.ProxyEndPoints.Any(x => x.Port == remotePort) if (Server.ProxyEndPoints.Any(x => x.Port == remotePort)
...@@ -350,6 +353,11 @@ retry: ...@@ -350,6 +353,11 @@ retry:
var ipAddresses = await Dns.GetHostAddressesAsync(hostname); var ipAddresses = await Dns.GetHostAddressesAsync(hostname);
if (ipAddresses == null || ipAddresses.Length == 0) if (ipAddresses == null || ipAddresses.Length == 0)
{ {
if (prefetch)
{
return null;
}
throw new Exception($"Could not resolve the hostname {hostname}"); throw new Exception($"Could not resolve the hostname {hostname}");
} }
...@@ -486,10 +494,15 @@ retry: ...@@ -486,10 +494,15 @@ retry:
{ {
sessionArgs.CustomUpStreamProxyUsed = newUpstreamProxy; sessionArgs.CustomUpStreamProxyUsed = newUpstreamProxy;
sessionArgs.TimeLine["Retrying Upstream Proxy Connection"] = DateTime.UtcNow; sessionArgs.TimeLine["Retrying Upstream Proxy Connection"] = DateTime.UtcNow;
return await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, applicationProtocols, isConnect, proxyServer, sessionArgs, upStreamEndPoint, externalProxy, cacheKey, cancellationToken); return await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, applicationProtocols, isConnect, proxyServer, sessionArgs, upStreamEndPoint, externalProxy, cacheKey, prefetch, cancellationToken);
} }
} }
if (prefetch)
{
return null;
}
throw new Exception($"Could not establish connection to {hostname}", lastException); throw new Exception($"Could not establish connection to {hostname}", lastException);
} }
...@@ -502,7 +515,7 @@ retry: ...@@ -502,7 +515,7 @@ retry:
stream = new HttpServerStream(new NetworkStream(tcpServerSocket, true), proxyServer.BufferPool, cancellationToken); stream = new HttpServerStream(new NetworkStream(tcpServerSocket, true), proxyServer.BufferPool, cancellationToken);
if ((externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http) && (isConnect || isHttps)) if (externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http && (isConnect || isHttps))
{ {
var authority = $"{remoteHostName}:{remotePort}".GetByteString(); var authority = $"{remoteHostName}:{remotePort}".GetByteString();
var connectRequest = new ConnectRequest(authority) var connectRequest = new ConnectRequest(authority)
...@@ -658,7 +671,7 @@ retry: ...@@ -658,7 +671,7 @@ retry:
} }
} }
internal async Task Release(Task<TcpServerConnection>? connectionCreateTask, bool closeServerConnection) internal async Task Release(Task<TcpServerConnection?>? connectionCreateTask, bool closeServerConnection)
{ {
if (connectionCreateTask == null) if (connectionCreateTask == null)
{ {
......
...@@ -182,9 +182,9 @@ namespace Titanium.Web.Proxy ...@@ -182,9 +182,9 @@ namespace Titanium.Web.Proxy
/// corresponding server connection process using CONNECT hostname or SNI hostname on a separate task so that after parsing client request /// 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. /// 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. /// If a server connection is available in cache then this prefetch task will immediately return with the available connection from cache.
/// Defaults to false. /// Defaults to true.
/// </summary> /// </summary>
public bool EnableTcpServerConnectionPrefetch { get; set; } = false; public bool EnableTcpServerConnectionPrefetch { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a Boolean value that specifies whether server and client stream Sockets are using the Nagle algorithm. /// Gets or sets a Boolean value that specifies whether server and client stream Sockets are using the Nagle algorithm.
......
...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy ...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy
/// <param name="isHttps">Is HTTPS</param> /// <param name="isHttps">Is HTTPS</param>
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, HttpClientStream clientStream, private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, HttpClientStream clientStream,
CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null, CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
Task<TcpServerConnection>? prefetchConnectionTask = null, bool isHttps = false) Task<TcpServerConnection?>? prefetchConnectionTask = null, bool isHttps = false)
{ {
var connectRequest = connectArgs?.HttpClient.ConnectRequest; var connectRequest = connectArgs?.HttpClient.ConnectRequest;
......
...@@ -91,10 +91,10 @@ namespace Titanium.Web.Proxy ...@@ -91,10 +91,10 @@ namespace Titanium.Web.Proxy
else else
{ {
var sessionArgs = new SessionEventArgs(this, endPoint, clientStream, null, cancellationTokenSource); var sessionArgs = new SessionEventArgs(this, endPoint, clientStream, null, cancellationTokenSource);
var connection = await tcpConnectionFactory.GetServerConnection(this, httpsHostName, port, var connection = (await tcpConnectionFactory.GetServerConnection(this, httpsHostName, port,
HttpHeader.VersionUnknown, false, null, HttpHeader.VersionUnknown, false, null,
true, sessionArgs, UpStreamEndPoint, true, sessionArgs, UpStreamEndPoint,
UpStreamHttpsProxy, true, cancellationToken); UpStreamHttpsProxy, true, false, cancellationToken))!;
try try
{ {
......
...@@ -7,13 +7,16 @@ namespace Titanium.Web.Proxy.IntegrationTests.Setup ...@@ -7,13 +7,16 @@ namespace Titanium.Web.Proxy.IntegrationTests.Setup
{ {
public class TestProxyServer : IDisposable public class TestProxyServer : IDisposable
{ {
public ProxyServer ProxyServer { get; private set; } public ProxyServer ProxyServer { get; }
public int ListeningPort => ProxyServer.ProxyEndPoints[0].Port; public int ListeningPort => ProxyServer.ProxyEndPoints[0].Port;
public CertificateManager CertificateManager => ProxyServer.CertificateManager; public CertificateManager CertificateManager => ProxyServer.CertificateManager;
public TestProxyServer(bool isReverseProxy, ProxyServer upStreamProxy = null) public TestProxyServer(bool isReverseProxy, ProxyServer upStreamProxy = null)
{ {
ProxyServer = new ProxyServer(); ProxyServer = new ProxyServer();
var explicitEndPoint = isReverseProxy ? var explicitEndPoint = isReverseProxy ?
(ProxyEndPoint)new TransparentProxyEndPoint(IPAddress.Any, 0, true) : (ProxyEndPoint)new TransparentProxyEndPoint(IPAddress.Any, 0, true) :
new ExplicitProxyEndPoint(IPAddress.Any, 0, true); new ExplicitProxyEndPoint(IPAddress.Any, 0, true);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment