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;
...@@ -173,10 +174,9 @@ namespace Titanium.Web.Proxy ...@@ -173,10 +174,9 @@ namespace Titanium.Web.Proxy
#if NETCOREAPP2_1 #if NETCOREAPP2_1
clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif #endif
// 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,40 +201,41 @@ namespace Titanium.Web.Proxy ...@@ -201,40 +201,41 @@ 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;
if (available > 0)
{ {
int available = clientStream.Available; // send the buffered data
if (available > 0) var data = BufferPool.GetBuffer(BufferSize);
try
{ {
// send the buffered data // clientStream.Available sbould be at most BufferSize because it is using the same buffer size
var data = BufferPool.GetBuffer(BufferSize); await clientStream.ReadAsync(data, 0, available, cancellationToken);
await connection.StreamWriter.WriteAsync(data, 0, available, true,
try cancellationToken);
{ }
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size finally
await clientStream.ReadAsync(data, 0, available, cancellationToken); {
await connection.StreamWriter.WriteAsync(data, 0, available, true, BufferPool.ReturnBuffer(data);
cancellationToken);
}
finally
{
BufferPool.ReturnBuffer(data);
}
} }
var serverHelloInfo =
await SslTools.PeekServerHello(connection.Stream, cancellationToken);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, var serverHelloInfo =
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, await SslTools.PeekServerHello(connection.Stream, cancellationToken);
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
connectArgs.CancellationTokenSource, ExceptionFunc);
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
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,39 +88,38 @@ namespace Titanium.Web.Proxy ...@@ -87,39 +88,38 @@ 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;
int available = clientStream.Available;
if (available > 0)
{ {
var serverStream = connection.GetStream(); // send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
int available = clientStream.Available; try
if (available > 0) {
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await serverStream.WriteAsync(data, 0, available, cancellationToken);
await serverStream.FlushAsync(cancellationToken);
}
finally
{ {
// send the buffered data BufferPool.ReturnBuffer(data);
var data = BufferPool.GetBuffer(BufferSize);
try
{
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await serverStream.WriteAsync(data, 0, available, cancellationToken);
await serverStream.FlushAsync(cancellationToken);
}
finally
{
BufferPool.ReturnBuffer(data);
}
} }
}
////var serverHelloInfo = await SslTools.PeekServerHello(serverStream); await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
null, null, cancellationTokenSource, ExceptionFunc);
tcpConnectionFactory.Release(connection);
return;
await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
null, null, cancellationTokenSource, ExceptionFunc);
}
} }
} }
......
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