Commit bcafc79d authored by justcoding121's avatar justcoding121 Committed by GitHub

Merge pull request #98 from justcoding121/master

sync with master
parents 6453375e 4456a224
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Network;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class CertificateManagerTests
{
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_Certificate_Stress_Test()
{
var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium","Titanium Root Certificate Authority");
mgr.ClearIdleCertificates(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 certificate = await mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate);
}));
}
}
await Task.WhenAll(tasks.ToArray());
mgr.StopClearIdleCertificates();
}
}
}
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b517e3d0-d03b-436f-ab03-34ba0d5321af")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B517E3D0-D03B-436F-AB03-34BA0D5321AF}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.UnitTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
......@@ -29,6 +29,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A
.build\default.ps1 = .build\default.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789-D348-49CF-8B67-5E99D50EDF64}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -43,12 +47,17 @@ Global
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="isHttps"></param>
/// <returns></returns>
internal static async Task SendRaw(Stream clientStream, string httpCmd, Dictionary<string, HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps, SslProtocols supportedProtocols)
int tunnelPort, bool isHttps, SslProtocols supportedProtocols, int connectionTimeOutSeconds)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -83,6 +83,12 @@ namespace Titanium.Web.Proxy.Helpers
}
}
tunnelClient.SendTimeout = connectionTimeOutSeconds * 1000;
tunnelClient.ReceiveTimeout = connectionTimeOutSeconds * 1000;
tunnelStream.ReadTimeout = connectionTimeOutSeconds * 1000;
tunnelStream.WriteTimeout = connectionTimeOutSeconds * 1000;
Task sendRelay;
//Now async relay all server=>client & client=>server data
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Connection to server
/// </summary>
internal TcpConnectionCache 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(TcpConnectionCache Connection)
internal void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
......@@ -170,6 +170,7 @@ namespace Titanium.Web.Proxy.Http
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
......@@ -177,10 +178,12 @@ namespace Titanium.Web.Proxy.Http
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
......@@ -193,6 +196,7 @@ namespace Titanium.Web.Proxy.Http
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
......
......@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public class TcpConnectionCache
public class TcpConnection: IDisposable
{
internal string HostName { get; set; }
internal int port { get; set; }
......@@ -37,9 +37,18 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnectionCache()
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
public void Dispose()
{
Stream.Close();
Stream.Dispose();
TcpClient.Client.Close();
TcpClient.Close();
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 TcpConnectionCacheManager
internal class TcpConnectionFactory
{
/// <summary>
/// Connection cache
/// </summary>
Dictionary<string, List<TcpConnectionCache>> connectionCache = new Dictionary<string, List<TcpConnectionCache>>();
/// <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,81 +27,18 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal async Task<TcpConnectionCache> 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,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{
List<TcpConnectionCache> cachedConnections = null;
TcpConnectionCache cached = null;
//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);
int connectionTimeOutSeconds,
RemoteCertificateValidationCallback remoteCertificateValidationCallBack,
LocalCertificateSelectionCallback localCertificateSelectionCallback)
{
//not in cache so create and return
return await CreateClient(hostname, port, isHttps, version, connectionTimeOutSeconds, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
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, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
}
if (cachedConnections == null || cachedConnections.Count() <= 2)
{
var task = CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback)
.ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); });
}
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
......@@ -122,7 +48,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
private async Task<TcpConnectionCache> CreateClient(string hostname, int port, bool isHttps, Version version,
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)
{
......@@ -137,7 +63,7 @@ namespace Titanium.Web.Proxy.Network
if (upStreamHttpsProxy != null)
{
client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port);
stream = (Stream)client.GetStream();
stream = client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
{
......@@ -164,7 +90,7 @@ namespace Titanium.Web.Proxy.Network
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
stream = client.GetStream();
}
try
......@@ -174,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);
stream = (Stream)sslStream;
stream = sslStream;
}
catch
{
......@@ -191,16 +117,22 @@ namespace Titanium.Web.Proxy.Network
if (upStreamHttpProxy != null)
{
client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port);
stream = (Stream)client.GetStream();
stream = client.GetStream();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
stream = client.GetStream();
}
}
return new TcpConnectionCache()
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection()
{
HostName = hostname,
port = port,
......@@ -212,80 +144,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(TcpConnectionCache connection)
{
connection.LastAccess = DateTime.Now;
var key = GetConnectionKey(connection.HostName, connection.port, connection.IsHttps, connection.Version);
await connectionAccessLock.WaitAsync();
try
{
List<TcpConnectionCache> cachedConnections;
connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null)
{
cachedConnections.Add(connection);
}
else
{
connectionCache.Add(key, new List<TcpConnectionCache>() { 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);
}
}
}
}
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
......@@ -13,6 +14,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
......
......@@ -24,9 +24,9 @@ namespace Titanium.Web.Proxy
private CertificateManager certificateCacheManager { get; set; }
/// <summary>
/// A object that manages tcp connection cache to server
/// A object that creates tcp connection to server
/// </summary>
private TcpConnectionCacheManager tcpConnectionCacheManager { get; set; }
private TcpConnectionFactory tcpConnectionFactory { get; set; }
/// <summary>
/// Manage system proxy settings
......@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Minutes TCP connection cache to servers to be kept alive when in idle state
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int ConnectionCacheTimeOutMinutes { get; set; }
public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; }
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Intercept request to server
......@@ -122,11 +122,11 @@ namespace Titanium.Web.Proxy
public ProxyServer()
{
//default values
ConnectionCacheTimeOutMinutes = 3;
ConnectionTimeOutSeconds = 120;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionCacheManager = new TcpConnectionCacheManager();
tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager();
......@@ -273,7 +273,6 @@ namespace Titanium.Web.Proxy
Listen(endPoint);
}
tcpConnectionCacheManager.ClearIdleConnections(ConnectionCacheTimeOutMinutes);
certificateCacheManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true;
......@@ -306,7 +305,6 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear();
tcpConnectionCacheManager.StopClearIdleConnections();
certificateCacheManager.StopClearIdleCertificates();
proxyRunning = false;
......
......@@ -19,14 +19,19 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the requesr
/// Handle the request
/// </summary>
partial class ProxyServer
{
//This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{
Stream clientStream = client.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream);
var clientStreamWriter = new StreamWriter(clientStream);
......@@ -84,13 +89,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, new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
await tcpConnectionCacheManager.ReleaseClient(tunnelClient);
sslStream = new SslStream(clientStream, true);
var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
......@@ -129,7 +127,7 @@ namespace Titanium.Web.Proxy
//Just relay the request/response without decrypting it
await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false, SupportedSslProtocols);
false, SupportedSslProtocols, ConnectionTimeOutSeconds);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return;
......@@ -145,11 +143,15 @@ namespace Titanium.Web.Proxy
}
}
//This is called when requests are routed through router to this endpoint
//For ssl requests
//This is called when this proxy acts as a reverse proxy (like a real http server)
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private async void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null;
X509Certificate2 certificate = null;
......@@ -209,7 +211,7 @@ namespace Titanium.Web.Proxy
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName)
{
TcpConnectionCache connection = null;
TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
......@@ -243,17 +245,7 @@ namespace Titanium.Web.Proxy
}
}
#if DEBUG
//Just ignore local requests while Debugging
//Its annoying
if (httpCmd.Contains("localhost"))
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
break;
}
#endif
//Read the request headers
//Read the request headers in to unique and non-unique header collections
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{
......@@ -261,10 +253,12 @@ namespace Titanium.Web.Proxy
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey(newHeader.Name))
{
args.WebSession.Request.NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (args.WebSession.Request.RequestHeaders.ContainsKey(newHeader.Name))
{
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
......@@ -277,6 +271,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
args.WebSession.Request.RequestHeaders.Add(newHeader.Name, newHeader);
......@@ -316,14 +311,15 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.UpgradeToWebSocket)
{
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols, ConnectionTimeOutSeconds);
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,
UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
connection = connection!=null? connection : await tcpConnectionFactory.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));
args.WebSession.Request.RequestLocked = true;
......@@ -402,12 +398,9 @@ 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();
......@@ -420,6 +413,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\TcpConnectionCache.cs" />
<Compile Include="Network\TcpConnectionCacheManager.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