Commit f7c2490f authored by justcoding121's avatar justcoding121

remove connection cache causing errors when reusing

parent 3ff60c1d
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Network;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class ConnectionManagerTests
{
private readonly static string[] hostNames
= new string[] { "facebook.com", "youtube.com", "google.com",
"bing.com", "yahoo.com"};
private readonly Random random = new Random();
[TestMethod]
public async Task Simple_Create_Connection_Stress_Test()
{
var tasks = new List<Task>();
var mgr = new TcpConnectionManager();
mgr.ClearIdleConnections(1);
for (int i = 0; i < 1000; i++)
{
foreach (var host in hostNames)
{
tasks.Add(Task.Run(async () =>
{
await Task.Delay(random.Next(0, 10) * 1000);
//get the connection
var httpConnection = await mgr.GetClient(host, 80, false, new Version(1, 1), null, null,
8096, System.Security.Authentication.SslProtocols.Default,
120, null, null);
//simulate a work with the connection
await Task.Delay(random.Next(0, 10) * 1000);
//print total number of connections on cache
Debug.WriteLine(mgr.connectionCache.Count);
Assert.IsNotNull(httpConnection);
Assert.IsNotNull(httpConnection.TcpClient);
Assert.IsTrue(httpConnection.TcpClient.Connected);
//release
await mgr.ReleaseClient(httpConnection);
}));
tasks.Add(Task.Run(async () =>
{
await Task.Delay(random.Next(0, 10) * 1000);
//get the connection
var httpsConnection = await mgr.GetClient(host, 443, true, new Version(1, 1), null, null,
8096, System.Security.Authentication.SslProtocols.Default,
120, null, null);
//simulate a work with the connection
await Task.Delay(random.Next(0, 10) * 1000);
//print total number of connections on cache
Debug.WriteLine(mgr.connectionCache.Count);
Assert.IsNotNull(httpsConnection);
Assert.IsNotNull(httpsConnection.TcpClient);
Assert.IsTrue(httpsConnection.TcpClient.Connected);
//release
await mgr.ReleaseClient(httpsConnection);
}));
}
}
await Task.WhenAll(tasks.ToArray());
mgr.StopClearIdleConnections();
}
}
}
...@@ -51,7 +51,6 @@ ...@@ -51,7 +51,6 @@
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="CertificateManagerTests.cs" /> <Compile Include="CertificateManagerTests.cs" />
<Compile Include="ConnectionManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
internal CachedTcpConnection ServerConnection { get; set; } internal TcpConnection ServerConnection { get; set; }
public Request Request { get; set; } public Request Request { get; set; }
public Response Response { get; set; } public Response Response { get; set; }
...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// Set the tcp connection to server used by this webclient /// Set the tcp connection to server used by this webclient
/// </summary> /// </summary>
/// <param name="Connection"></param> /// <param name="Connection"></param>
internal void SetConnection(CachedTcpConnection Connection) internal void SetConnection(TcpConnection Connection)
{ {
Connection.LastAccess = DateTime.Now; Connection.LastAccess = DateTime.Now;
ServerConnection = Connection; ServerConnection = Connection;
......
...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// An object that holds TcpConnection to a particular server & port /// An object that holds TcpConnection to a particular server & port
/// </summary> /// </summary>
public class CachedTcpConnection public class TcpConnection: IDisposable
{ {
internal string HostName { get; set; } internal string HostName { get; set; }
internal int port { get; set; } internal int port { get; set; }
...@@ -37,9 +37,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -37,9 +37,15 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
internal CachedTcpConnection() internal TcpConnection()
{ {
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
} }
public void Dispose()
{
Stream.Dispose();
TcpClient.Client.Dispose();
}
} }
} }
...@@ -9,7 +9,6 @@ using System.Net.Security; ...@@ -9,7 +9,6 @@ using System.Net.Security;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using System.Threading; using System.Threading;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Security.Authentication; using System.Security.Authentication;
...@@ -18,18 +17,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -18,18 +17,8 @@ namespace Titanium.Web.Proxy.Network
/// <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 TcpConnectionManager internal class TcpConnectionFactory
{ {
/// <summary>
/// Connection cache
/// </summary>
internal Dictionary<string, List<CachedTcpConnection>> connectionCache = new Dictionary<string, List<CachedTcpConnection>>();
/// <summary>
/// A lock to manage concurrency
/// </summary>
SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
/// <summary> /// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version /// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
/// </summary> /// </summary>
...@@ -38,76 +27,18 @@ namespace Titanium.Web.Proxy.Network ...@@ -38,76 +27,18 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="version"></param> /// <param name="version"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<CachedTcpConnection> GetClient(string hostname, int port, bool isHttps, Version version, internal async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version,
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols, ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
int connectionTimeOutSeconds, int connectionTimeOutSeconds,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, RemoteCertificateValidationCallback remoteCertificateValidationCallBack,
LocalCertificateSelectionCallback localCertificateSelectionCallback) LocalCertificateSelectionCallback localCertificateSelectionCallback)
{ {
List<CachedTcpConnection> cachedConnections = null; //not in cache so create and return
CachedTcpConnection cached = null; return await CreateClient(hostname, port, isHttps, version, connectionTimeOutSeconds, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
//Get a unique string to identify this connection
var key = GetConnectionKey(hostname, port, isHttps, version);
while (true)
{
await connectionAccessLock.WaitAsync();
try
{
connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null && cachedConnections.Count > 0)
{
cached = cachedConnections.First();
cachedConnections.Remove(cached);
}
else
{
cached = null;
}
}
finally {
connectionAccessLock.Release();
}
if (cached != null && !cached.TcpClient.Client.IsConnected())
{
cached.TcpClient.Client.Dispose();
cached.TcpClient.Close();
continue;
}
if (cached == null)
{
break;
}
}
if (cached == null)
{
cached = await CreateClient(hostname, port, isHttps, version, connectionTimeOutSeconds, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
}
return cached;
} }
/// <summary>
/// Get a string to identfiy the connection to a particular host, port
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal string GetConnectionKey(string hostname, int port, bool isHttps, Version version)
{
return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor);
}
/// <summary> /// <summary>
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version /// Create connection to a particular host/port optionally with SSL and a particular HTTP version
...@@ -117,7 +48,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -117,7 +48,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="version"></param> /// <param name="version"></param>
/// <returns></returns> /// <returns></returns>
private async Task<CachedTcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version, int connectionTimeOutSeconds, private async Task<TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version, int connectionTimeOutSeconds,
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols, ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback) RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{ {
...@@ -194,11 +125,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -194,11 +125,11 @@ namespace Titanium.Web.Proxy.Network
stream = client.GetStream(); stream = client.GetStream();
} }
} }
client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = connectionTimeOutSeconds * 1000;
return new CachedTcpConnection() return new TcpConnection()
{ {
HostName = hostname, HostName = hostname,
port = port, port = port,
...@@ -210,80 +141,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -210,80 +141,6 @@ namespace Titanium.Web.Proxy.Network
}; };
} }
/// <summary>
/// Returns a Tcp Connection back to cache for reuse by other requests
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
internal async Task ReleaseClient(CachedTcpConnection connection)
{
connection.LastAccess = DateTime.Now;
var key = GetConnectionKey(connection.HostName, connection.port, connection.IsHttps, connection.Version);
await connectionAccessLock.WaitAsync();
try
{
List<CachedTcpConnection> cachedConnections;
connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null)
{
cachedConnections.Add(connection);
}
else
{
connectionCache.Add(key, new List<CachedTcpConnection>() { connection });
}
}
finally {
connectionAccessLock.Release();
}
}
private bool clearConenctions { get; set; }
/// <summary>
/// Stop clearing idle connections
/// </summary>
internal void StopClearIdleConnections()
{
clearConenctions = false;
}
/// <summary>
/// A method to clear idle connections
/// </summary>
internal async void ClearIdleConnections(int connectionCacheTimeOutMinutes)
{
clearConenctions = true;
while (clearConenctions)
{
await connectionAccessLock.WaitAsync();
try
{
var cutOff = DateTime.Now.AddMinutes(-1 * connectionCacheTimeOutMinutes);
connectionCache
.SelectMany(x => x.Value)
.Where(x => x.LastAccess < cutOff)
.ToList()
.ForEach(x => x.TcpClient.Close());
connectionCache.ToList().ForEach(x => x.Value.RemoveAll(y => y.LastAccess < cutOff));
}
finally {
connectionAccessLock.Release();
}
//every minute run this
await Task.Delay(1000 * 60);
}
}
} }
} }
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// A object that manages tcp connection cache to server /// A object that manages tcp connection cache to server
/// </summary> /// </summary>
private TcpConnectionManager tcpConnectionCacheManager { get; set; } private TcpConnectionFactory tcpConnectionCacheManager { get; set; }
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
...@@ -66,11 +66,6 @@ namespace Titanium.Web.Proxy ...@@ -66,11 +66,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool Enable100ContinueBehaviour { get; set; } public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Minutes TCP connection cache to servers to be kept alive from last time it was used
/// </summary>
public int ServerConnectionCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Minutes certificates should be kept in cache when not used /// Minutes certificates should be kept in cache when not used
/// </summary> /// </summary>
...@@ -128,11 +123,10 @@ namespace Titanium.Web.Proxy ...@@ -128,11 +123,10 @@ namespace Titanium.Web.Proxy
{ {
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 120;
ServerConnectionCacheTimeOutMinutes = 3;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionCacheManager = new TcpConnectionManager(); tcpConnectionCacheManager = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager(); firefoxProxySettingsManager = new FireFoxProxySettingsManager();
...@@ -279,7 +273,6 @@ namespace Titanium.Web.Proxy ...@@ -279,7 +273,6 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
tcpConnectionCacheManager.ClearIdleConnections(ServerConnectionCacheTimeOutMinutes);
certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true; proxyRunning = true;
...@@ -312,7 +305,6 @@ namespace Titanium.Web.Proxy ...@@ -312,7 +305,6 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
tcpConnectionCacheManager.StopClearIdleConnections();
certificateCacheManager.StopClearIdleCertificates(); certificateCacheManager.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
......
...@@ -88,14 +88,6 @@ namespace Titanium.Web.Proxy ...@@ -88,14 +88,6 @@ namespace Titanium.Web.Proxy
try try
{ {
//create the Tcp Connection to server and then release it to connection cache
//Just doing what CONNECT request is asking as to do
var tunnelClient = await tcpConnectionCacheManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version,
UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, ConnectionTimeOutSeconds,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
await tcpConnectionCacheManager.ReleaseClient(tunnelClient);
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false); var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
...@@ -218,7 +210,7 @@ namespace Titanium.Web.Proxy ...@@ -218,7 +210,7 @@ namespace Titanium.Web.Proxy
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName)
{ {
CachedTcpConnection connection = null; TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection //Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client) //(assuming HTTP connection is kept alive by client)
...@@ -327,11 +319,11 @@ namespace Titanium.Web.Proxy ...@@ -327,11 +319,11 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders, await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols); httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; break;
} }
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = await tcpConnectionCacheManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version, connection = connection!=null? connection : await tcpConnectionCacheManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version,
UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, ConnectionTimeOutSeconds, new RemoteCertificateValidationCallback(ValidateServerCertificate), UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, ConnectionTimeOutSeconds, new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate)); new LocalCertificateSelectionCallback(SelectClientCertificate));
...@@ -411,11 +403,10 @@ namespace Titanium.Web.Proxy ...@@ -411,11 +403,10 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; break;
} }
//send the tcp connection to server back to connection cache for reuse
await tcpConnectionCacheManager.ReleaseClient(connection);
// read the next request // read the next request
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
...@@ -429,6 +420,12 @@ namespace Titanium.Web.Proxy ...@@ -429,6 +420,12 @@ namespace Titanium.Web.Proxy
} }
if (connection != null)
{
//dispose
connection.Dispose();
}
} }
/// <summary> /// <summary>
......
...@@ -77,8 +77,8 @@ ...@@ -77,8 +77,8 @@
<Compile Include="Http\Request.cs" /> <Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" /> <Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" /> <Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\CachedTcpConnection.cs" /> <Compile Include="Network\TcpConnection.cs" />
<Compile Include="Network\TcpConnectionManager.cs" /> <Compile Include="Network\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
......
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