Commit 0ff6870d authored by justcoding121's avatar justcoding121

Prepare for TCP connection pool

parent 24b0ffbd
...@@ -136,10 +136,11 @@ namespace Titanium.Web.Proxy ...@@ -136,10 +136,11 @@ namespace Titanium.Web.Proxy
{ {
// test server HTTP/2 support // test server HTTP/2 support
// 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
using (var connection = await GetServerConnection(connectArgs, true, SslExtensions.Http2ProtocolAsList, cancellationToken)) var connection = await GetServerConnection(connectArgs, true,
{ SslExtensions.Http2ProtocolAsList, cancellationToken);
http2Supproted = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2; http2Supproted = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2;
} tcpConnectionFactory.Release(connection);
} }
SslStream sslStream = null; SslStream sslStream = null;
...@@ -176,7 +177,6 @@ namespace Titanium.Web.Proxy ...@@ -176,7 +177,6 @@ namespace Titanium.Web.Proxy
// HTTPS server created - we can now decrypt the client's traffic // HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
} }
catch (Exception e) catch (Exception e)
...@@ -201,8 +201,9 @@ namespace Titanium.Web.Proxy ...@@ -201,8 +201,9 @@ namespace Titanium.Web.Proxy
if (!decryptSsl || !isClientHello) if (!decryptSsl || !isClientHello)
{ {
// create new connection // create new connection
using (var connection = await GetServerConnection(connectArgs, true, clientConnection.NegotiatedApplicationProtocol, cancellationToken)) var connection = await GetServerConnection(connectArgs, true,
{ clientConnection.NegotiatedApplicationProtocol, cancellationToken);
if (isClientHello) if (isClientHello)
{ {
int available = clientStream.Available; int available = clientStream.Available;
...@@ -233,8 +234,8 @@ namespace Titanium.Web.Proxy ...@@ -233,8 +234,8 @@ namespace Titanium.Web.Proxy
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, ExceptionFunc); connectArgs.CancellationTokenSource, ExceptionFunc);
}
tcpConnectionFactory.Release(connection);
return; return;
} }
} }
...@@ -265,8 +266,9 @@ namespace Titanium.Web.Proxy ...@@ -265,8 +266,9 @@ namespace Titanium.Web.Proxy
} }
// create new connection // create new connection
using (var connection = await GetServerConnection(connectArgs, true, SslExtensions.Http2ProtocolAsList, cancellationToken)) var connection = await GetServerConnection(connectArgs, true, SslExtensions.Http2ProtocolAsList,
{ cancellationToken);
await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken); await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken);
await connection.StreamWriter.WriteLineAsync(cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken);
await connection.StreamWriter.WriteLineAsync("SM", cancellationToken); await connection.StreamWriter.WriteLineAsync("SM", cancellationToken);
...@@ -278,7 +280,7 @@ namespace Titanium.Web.Proxy ...@@ -278,7 +280,7 @@ namespace Titanium.Web.Proxy
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc); connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
#endif #endif
} tcpConnectionFactory.Release(connection);
} }
} }
......
...@@ -77,5 +77,6 @@ namespace Titanium.Web.Proxy.Models ...@@ -77,5 +77,6 @@ namespace Titanium.Web.Proxy.Models
{ {
return $"{HostName}:{Port}"; return $"{HostName}:{Port}";
} }
} }
} }
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
...@@ -13,30 +15,75 @@ using Titanium.Web.Proxy.Models; ...@@ -13,30 +15,75 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server /// A class that manages Tcp Connection to server used by this proxy server.
/// </summary> /// </summary>
internal class TcpConnectionFactory internal class TcpConnectionFactory
{ {
//private readonly ConcurrentDictionary<string, List<TcpServerConnection>> cache
// = new ConcurrentDictionary<string, List<TcpServerConnection>>();
/// <summary>
/// Gets a TCP connection to server from connection pool.
/// </summary>
/// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion">The http version to use.</param>
/// <param name="isHttps">Is this a HTTPS request.</param>
/// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer">The current ProxyServer instance.</param>
/// <param name="upStreamEndPoint">The upstream endpoint to make request via.</param>
/// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
internal async Task<TcpServerConnection> GetClient(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken)
{
//TODO fix cacheKey with all possible properties that uniquely identify a connection
//var cacheKey = $"{remoteHostName}{remotePort}{httpVersion}" +
// $"{isHttps}{isConnect}";
//if (cache.TryGetValue(cacheKey, out var existingConnections))
//{
// var recentConnection = existingConnections.Last();
// existingConnections.RemoveAt(existingConnections.Count - 1);
// //TODO check if connection is still active before returning
// return recentConnection;
//}
var connection = await CreateClient(remoteHostName, remotePort, httpVersion, isHttps,
applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken);
//connection.CacheKey = cacheKey;
return connection;
}
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName">The remote hostname.</param>
/// <param name="remotePort"></param> /// <param name="remotePort">The remote port.</param>
/// <param name="httpVersion"></param> /// <param name="httpVersion">The http version to use.</param>
/// <param name="decryptSsl"></param> /// <param name="isHttps">Is this a HTTPS request.</param>
/// <param name="applicationProtocols"></param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
/// <param name="isConnect"></param> /// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="proxyServer"></param> /// <param name="proxyServer">The current ProxyServer instance.</param>
/// <param name="upStreamEndPoint"></param> /// <param name="upStreamEndPoint">The upstream endpoint to make request via.</param>
/// <param name="externalProxy"></param> /// <param name="externalProxy">The external proxy to make request via.</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpServerConnection> CreateClient(string remoteHostName, int remotePort, private async Task<TcpServerConnection> CreateClient(string remoteHostName, int remotePort,
Version httpVersion, bool decryptSsl, List<SslApplicationProtocol> applicationProtocols, bool isConnect, Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
// check if external proxy is set for HTTP/HTTPS // check if external proxy is set for HTTP/HTTPS
...@@ -59,12 +106,14 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -59,12 +106,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
try try
{ {
tcpClient = new TcpClient(upStreamEndPoint); tcpClient = new TcpClient(upStreamEndPoint)
{
ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
SendBufferSize = proxyServer.BufferSize,
ReceiveBufferSize = proxyServer.BufferSize
};
tcpClient.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.SendBufferSize = proxyServer.BufferSize;
tcpClient.ReceiveBufferSize = proxyServer.BufferSize;
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false); await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
...@@ -80,7 +129,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -80,7 +129,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferSize); stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || decryptSsl)) if (useUpstreamProxy && (isConnect || isHttps))
{ {
var writer = new HttpRequestWriter(stream, proxyServer.BufferSize); var writer = new HttpRequestWriter(stream, proxyServer.BufferSize);
var connectRequest = new ConnectRequest var connectRequest = new ConnectRequest
...@@ -113,18 +162,20 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -113,18 +162,20 @@ namespace Titanium.Web.Proxy.Network.Tcp
await stream.ReadAndIgnoreAllLinesAsync(cancellationToken); await stream.ReadAndIgnoreAllLinesAsync(cancellationToken);
} }
if (decryptSsl) if (isHttps)
{ {
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate, var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate); proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize); stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize);
var options = new SslClientAuthenticationOptions(); var options = new SslClientAuthenticationOptions
options.ApplicationProtocols = applicationProtocols; {
options.TargetHost = remoteHostName; ApplicationProtocols = applicationProtocols,
options.ClientCertificates = null; TargetHost = remoteHostName,
options.EnabledSslProtocols = proxyServer.SupportedSslProtocols; ClientCertificates = null,
options.CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation; EnabledSslProtocols = proxyServer.SupportedSslProtocols,
CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation
};
await sslStream.AuthenticateAsClientAsync(options, cancellationToken); await sslStream.AuthenticateAsClientAsync(options, cancellationToken);
#if NETCOREAPP2_1 #if NETCOREAPP2_1
negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
...@@ -141,11 +192,12 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -141,11 +192,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
return new TcpServerConnection(proxyServer, tcpClient) return new TcpServerConnection(proxyServer, tcpClient)
{ {
//CacheKey = cacheKey,
UpStreamProxy = externalProxy, UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint, UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName, HostName = remoteHostName,
Port = remotePort, Port = remotePort,
IsHttps = decryptSsl, IsHttps = isHttps,
NegotiatedApplicationProtocol = negotiatedApplicationProtocol, NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
UseUpstreamProxy = useUpstreamProxy, UseUpstreamProxy = useUpstreamProxy,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferSize), StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferSize),
...@@ -153,5 +205,29 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -153,5 +205,29 @@ namespace Titanium.Web.Proxy.Network.Tcp
Version = httpVersion Version = httpVersion
}; };
} }
/// <summary>
/// Release connection back to cache.
/// </summary>
/// <param name="connection">The Tcp server connection to return.</param>
internal void Release(TcpServerConnection connection)
{
//while (true)
//{
// if (cache.TryGetValue(connection.Key, out var existingConnections))
// {
// existingConnections.Add(connection);
// break;
// }
// if (cache.TryAdd(connection.Key, new List<TcpServerConnection> { connection }))
// {
// break;
// };
//}
connection?.Dispose();
}
} }
} }
...@@ -63,6 +63,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -63,6 +63,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
/// <summary>
/// The cache key used to uniquely identify this connection properties
/// </summary>
internal string CacheKey { get; set; }
/// <summary> /// <summary>
/// Dispose. /// Dispose.
/// </summary> /// </summary>
......
...@@ -268,7 +268,7 @@ namespace Titanium.Web.Proxy ...@@ -268,7 +268,7 @@ namespace Titanium.Web.Proxy
} }
finally finally
{ {
serverConnection?.Dispose(); tcpConnectionFactory.Release(serverConnection);
} }
} }
...@@ -398,7 +398,7 @@ namespace Titanium.Web.Proxy ...@@ -398,7 +398,7 @@ namespace Titanium.Web.Proxy
args.CustomUpStreamProxyUsed = customUpStreamProxy; args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await tcpConnectionFactory.CreateClient( return await tcpConnectionFactory.GetClient(
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion, args.WebSession.Request.HttpVersion,
......
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
...@@ -87,14 +88,12 @@ namespace Titanium.Web.Proxy ...@@ -87,14 +88,12 @@ namespace Titanium.Web.Proxy
else else
{ {
// create new connection // create new connection
var connection = new TcpClient(UpStreamEndPoint); var connection = await tcpConnectionFactory.GetClient(httpsHostName, endPoint.Port,
await connection.ConnectAsync(httpsHostName, endPoint.Port); null, false, new List<SslApplicationProtocol> { clientConnection.NegotiatedApplicationProtocol },
connection.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; true, this, UpStreamEndPoint, UpStreamHttpsProxy, cancellationToken);
connection.SendTimeout = ConnectionTimeOutSeconds * 1000;
using (connection)
{ var serverStream = connection.Stream;
var serverStream = connection.GetStream();
int available = clientStream.Available; int available = clientStream.Available;
if (available > 0) if (available > 0)
...@@ -115,11 +114,12 @@ namespace Titanium.Web.Proxy ...@@ -115,11 +114,12 @@ namespace Titanium.Web.Proxy
} }
} }
////var serverHelloInfo = await SslTools.PeekServerHello(serverStream);
await TcpHelper.SendRaw(clientStream, serverStream, BufferSize, await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
null, null, cancellationTokenSource, ExceptionFunc); null, null, cancellationTokenSource, ExceptionFunc);
}
tcpConnectionFactory.Release(connection);
return;
} }
} }
......
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