Commit c167c681 authored by justcoding121's avatar justcoding121 Committed by justcoding121

#184 add connection count; TODO: Find out why large number of client connections are active

parent 82b82598
......@@ -106,6 +106,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Connections:" + (sender as ProxyServer).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
......
......@@ -55,10 +55,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
Stream?.Close();
Stream?.Dispose();
StreamReader?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close();
TcpClient?.Close();
}
}
}
......@@ -133,10 +133,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection
{
UpStreamHttpProxy = externalHttpProxy,
......
......@@ -226,6 +226,8 @@ namespace Titanium.Web.Proxy
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
public int ClientConnectionCount { get; private set; }
/// <summary>
/// Constructor
/// </summary>
......@@ -495,7 +497,7 @@ namespace Titanium.Web.Proxy
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint) endPoint.Listener.LocalEndpoint).Port;
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
......@@ -560,7 +562,7 @@ namespace Titanium.Web.Proxy
/// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint) asyn.AsyncState;
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
......@@ -586,6 +588,8 @@ namespace Titanium.Web.Proxy
{
Task.Run(async () =>
{
ClientConnectionCount++;
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
......@@ -598,16 +602,15 @@ namespace Titanium.Web.Proxy
}
}
finally
{
if (tcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close();
}
tcpClient?.Close();
ClientConnectionCount--;
}
});
}
......
......@@ -33,9 +33,10 @@ namespace Titanium.Web.Proxy
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine};
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
Uri httpRemoteUri;
try
{
//read the first line HTTP command
......@@ -119,7 +120,7 @@ namespace Titanium.Web.Proxy
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine};
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
catch
{
......@@ -156,7 +157,9 @@ namespace Titanium.Web.Proxy
}
catch (Exception)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter, null);
}
}
......@@ -187,21 +190,24 @@ namespace Titanium.Web.Proxy
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine};
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
//HTTPS server created - we can now decrypt the client's traffic
}
catch (Exception)
{
sslStream.Dispose();
Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
Dispose(sslStream,
clientStreamReader,
clientStreamWriter, null);
return;
}
}
else
{
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine};
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
//now read the request line
......@@ -212,7 +218,8 @@ namespace Titanium.Web.Proxy
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
}
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool closeConnection)
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args,
ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool closeConnection)
{
try
{
......@@ -241,7 +248,10 @@ namespace Titanium.Web.Proxy
args.IsHttps, SupportedSslProtocols,
ValidateServerCertificate,
SelectClientCertificate,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint);
customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy,
args.ProxyClient.ClientStream,
UpStreamEndPoint);
}
args.WebSession.Request.RequestLocked = true;
......@@ -249,8 +259,12 @@ namespace Titanium.Web.Proxy
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
}
//if expect continue is enabled then send the headers first
......@@ -314,28 +328,50 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed)
{
await HandleHttpSessionResponse(args);
var result = await HandleHttpSessionResponse(args);
//already disposed inside above method
if (result == false)
{
return false;
}
}
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
}
if (closeConnection)
{
//dispose
connection?.Dispose();
Dispose(args.ProxyClient.ClientStream,
args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return false;
}
return true;
}
/// <summary>
......@@ -356,28 +392,30 @@ namespace Titanium.Web.Proxy
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{
TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
while (true)
{
if (string.IsNullOrEmpty(httpCmd))
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
null);
break;
}
var args =
new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
{
ProxyClient = {TcpClient = client},
WebSession = {ConnectHeaders = connectHeaders}
ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders }
};
args.WebSession.ProcessId = new Lazy<int>(() =>
{
var remoteEndPoint = (IPEndPoint) args.ProxyClient.TcpClient.Client.RemoteEndPoint;
var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
......@@ -388,6 +426,7 @@ namespace Titanium.Web.Proxy
//can't access process Id of remote request from remote machine
return -1;
});
try
{
//break up the line into three components (method, remote URL & Http Version)
......@@ -426,8 +465,7 @@ namespace Titanium.Web.Proxy
{
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
......@@ -450,9 +488,14 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
if (httpsHostName == null &&
(await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break;
}
......@@ -467,7 +510,7 @@ namespace Titanium.Web.Proxy
for (var i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(null, args);
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
......@@ -482,22 +525,41 @@ namespace Titanium.Web.Proxy
SelectClientCertificate,
clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break;
}
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false);
var result = await HandleHttpSessionRequestInternal(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false);
if (result == false)
{
//already disposed inside above method
break;
}
if (args.WebSession.Request.CancelRequest)
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break;
}
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break;
}
......@@ -507,13 +569,15 @@ namespace Titanium.Web.Proxy
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream,
clientStreamReader,
clientStreamWriter,
args.WebSession.ServerConnection);
break;
}
}
//dispose
connection?.Dispose();
}
/// <summary>
......
......@@ -9,6 +9,8 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers;
using System.Net.Sockets;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
......@@ -21,14 +23,14 @@ namespace Titanium.Web.Proxy
/// Called asynchronously when a request was successfully and we received the response
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task HandleHttpSessionResponse(SessionEventArgs args)
/// <returns>true if no errors</returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{
try
{
//read response & headers from server
await args.WebSession.ReceiveResponse();
try
{
if (!args.WebSession.Response.ResponseBodyRead)
{
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
......@@ -44,7 +46,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(this, args);
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
......@@ -53,7 +55,7 @@ namespace Titanium.Web.Proxy
if (args.ReRequest)
{
await HandleHttpSessionRequestInternal(null, args, null, null, true);
return;
return true;
}
args.WebSession.Response.ResponseLocked = true;
......@@ -125,14 +127,16 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args));
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args);
args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
return false;
}
finally
{
args.Dispose();
}
return true;
}
/// <summary>
......@@ -224,19 +228,19 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter, IDisposable args)
/// <param name="serverConnection"></param>
private void Dispose(Stream clientStream,
CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{
clientStream?.Close();
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Close();
clientStreamWriter?.Dispose();
args?.Dispose();
serverConnection?.Dispose();
}
}
}
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