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 @@
</Choose>
<ItemGroup>
<Compile Include="CertificateManagerTests.cs" />
<Compile Include="ConnectionManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Connection to server
/// </summary>
internal CachedTcpConnection ServerConnection { get; set; }
internal TcpConnection ServerConnection { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="Connection"></param>
internal void SetConnection(CachedTcpConnection Connection)
internal void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
......
......@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public class CachedTcpConnection
public class TcpConnection: IDisposable
{
internal string HostName { get; set; }
internal int port { get; set; }
......@@ -37,9 +37,15 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal DateTime LastAccess { get; set; }
internal CachedTcpConnection()
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
public void Dispose()
{
Stream.Dispose();
TcpClient.Client.Dispose();
}
}
}
......@@ -9,7 +9,6 @@ using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
......@@ -18,18 +17,8 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </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>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
/// </summary>
......@@ -38,76 +27,18 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <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,
int connectionTimeOutSeconds,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack,
LocalCertificateSelectionCallback localCertificateSelectionCallback)
{
List<CachedTcpConnection> cachedConnections = null;
CachedTcpConnection cached = null;
//Get a unique string to identify this connection
var key = GetConnectionKey(hostname, port, isHttps, version);
{
//not in cache so create and return
return await CreateClient(hostname, port, isHttps, version, connectionTimeOutSeconds, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
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>
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version
......@@ -117,7 +48,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <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,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{
......@@ -194,11 +125,11 @@ namespace Titanium.Web.Proxy.Network
stream = client.GetStream();
}
}
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
return new CachedTcpConnection()
return new TcpConnection()
{
HostName = hostname,
port = port,
......@@ -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
/// <summary>
/// A object that manages tcp connection cache to server
/// </summary>
private TcpConnectionManager tcpConnectionCacheManager { get; set; }
private TcpConnectionFactory tcpConnectionCacheManager { get; set; }
/// <summary>
/// Manage system proxy settings
......@@ -66,11 +66,6 @@ namespace Titanium.Web.Proxy
/// </summary>
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>
/// Minutes certificates should be kept in cache when not used
/// </summary>
......@@ -128,11 +123,10 @@ namespace Titanium.Web.Proxy
{
//default values
ConnectionTimeOutSeconds = 120;
ServerConnectionCacheTimeOutMinutes = 3;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionCacheManager = new TcpConnectionManager();
tcpConnectionCacheManager = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager();
......@@ -279,7 +273,6 @@ namespace Titanium.Web.Proxy
Listen(endPoint);
}
tcpConnectionCacheManager.ClearIdleConnections(ServerConnectionCacheTimeOutMinutes);
certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true;
......@@ -312,7 +305,6 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear();
tcpConnectionCacheManager.StopClearIdleConnections();
certificateCacheManager.StopClearIdleCertificates();
proxyRunning = false;
......
......@@ -88,14 +88,6 @@ namespace Titanium.Web.Proxy
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);
var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
......@@ -218,7 +210,7 @@ namespace Titanium.Web.Proxy
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName)
{
CachedTcpConnection connection = null;
TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
......@@ -327,11 +319,11 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
break;
}
//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),
new LocalCertificateSelectionCallback(SelectClientCertificate));
......@@ -411,11 +403,10 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Response.ResponseKeepAlive == false)
{
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
httpCmd = await clientStreamReader.ReadLineAsync();
......@@ -429,6 +420,12 @@ namespace Titanium.Web.Proxy
}
if (connection != null)
{
//dispose
connection.Dispose();
}
}
/// <summary>
......
......@@ -77,8 +77,8 @@
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\CachedTcpConnection.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Network\TcpConnection.cs" />
<Compile Include="Network\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.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