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

perf: avoid TCP Time_Wait

parent 369611a9
......@@ -9,7 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -27,8 +27,10 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="tunnelPort"></param>
/// <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 connectionTimeOutSeconds)
internal static async Task SendRaw(TcpConnectionFactory tcpConnectionFactory, Stream clientStream, string httpCmd, Version version,
Dictionary<string, HttpHeader> requestHeaders, string hostName, int bufferSize,
int tunnelPort, bool isHttps, SslProtocols supportedProtocols, int connectionTimeOutSeconds,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -54,41 +56,14 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(Environment.NewLine);
}
var tcpConnection = await tcpConnectionFactory.GetClient(hostName, tunnelPort, isHttps, version, null, null, bufferSize,
supportedProtocols, connectionTimeOutSeconds, remoteCertificateValidationCallback, localCertificateSelectionCallback);
TcpClient tunnelClient = tcpConnection.TcpClient;
Stream tunnelStream = tcpConnection.Stream;
TcpClient tunnelClient = null;
Stream tunnelStream = null;
//create the TcpClient to the server
try
{
tunnelClient = new TcpClient(hostName, tunnelPort);
tunnelStream = tunnelClient.GetStream();
if (isHttps)
{
SslStream sslStream = null;
try
{
sslStream = new SslStream(tunnelStream);
await sslStream.AuthenticateAsClientAsync(hostName, null, supportedProtocols, false);
tunnelStream = sslStream;
}
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
throw;
}
}
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
......@@ -108,19 +83,13 @@ namespace Titanium.Web.Proxy.Helpers
}
catch
{
if (tunnelStream != null)
{
tunnelStream.Close();
tunnelStream.Dispose();
}
if (tunnelClient != null)
{
tunnelClient.Close();
}
throw;
}
finally
{
tunnelStream.Dispose();
}
}
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public class TcpConnection: IDisposable
public class TcpConnection : IDisposable
{
internal string HostName { get; set; }
internal int port { get; set; }
......@@ -44,11 +44,11 @@ namespace Titanium.Web.Proxy.Network
public void Dispose()
{
Stream.Close();
Stream.Dispose();
TcpClient.Client.Close();
TcpClient.Close();
TcpClient.Client.Shutdown(SocketShutdown.Both);
TcpClient.Client.Disconnect(true);
TcpClient.Client.Dispose();
Stream.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
......@@ -19,6 +15,7 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
/// </summary>
......@@ -39,7 +36,6 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version
/// </summary>
......
......@@ -15,9 +15,9 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Proxy Server Main class
/// </summary>
public partial class ProxyServer: IDisposable
public partial class ProxyServer : IDisposable
{
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
......@@ -197,7 +197,7 @@ namespace Titanium.Web.Proxy
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTP Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
......@@ -360,17 +360,12 @@ namespace Titanium.Web.Proxy
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
try
{
//based on end point type call appropriate request handlers
var client = endPoint.listener.EndAcceptTcpClient(asyn);
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
HandleClient(endPoint as TransparentProxyEndPoint, client);
else
HandleClient(endPoint as ExplicitProxyEndPoint, client);
// Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
tcpClient = endPoint.listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
......@@ -384,6 +379,37 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () =>
{
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
{
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
}
}
finally
{
if (tcpClient != null)
{
tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Disconnect(true);
tcpClient.Client.Dispose();
}
}
});
}
// Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
public void Dispose()
......
......@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy
{
//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)
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{
Stream clientStream = client.GetStream();
......@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy
if (string.IsNullOrEmpty(httpCmd))
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -109,7 +109,7 @@ namespace Titanium.Web.Proxy
sslStream.Dispose();
}
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -126,10 +126,12 @@ namespace Titanium.Web.Proxy
await WriteConnectResponse(clientStreamWriter, version);
//Just relay the request/response without decrypting it
await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false, SupportedSslProtocols, ConnectionTimeOutSeconds);
await TcpHelper.SendRaw(tcpConnectionFactory, clientStream, null, version, null, httpRemoteUri.Host, BUFFER_SIZE,
httpRemoteUri.Port, false, SupportedSslProtocols, ConnectionTimeOutSeconds,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
......@@ -139,13 +141,13 @@ namespace Titanium.Web.Proxy
}
catch
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
}
//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)
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
......@@ -181,7 +183,7 @@ namespace Titanium.Web.Proxy
sslStream.Dispose();
}
Dispose(tcpClient, sslStream, clientStreamReader, clientStreamWriter, null);
Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return;
}
clientStream = sslStream;
......@@ -212,14 +214,14 @@ namespace Titanium.Web.Proxy
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName)
{
TcpConnection connection = null;
string lastConnectionKey = 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(client, clientStream, clientStreamReader, clientStreamWriter, null);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
break;
}
......@@ -310,24 +312,31 @@ namespace Titanium.Web.Proxy
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
{
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols, ConnectionTimeOutSeconds);
await TcpHelper.SendRaw(tcpConnectionFactory, clientStream, httpCmd, version, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, BUFFER_SIZE, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols, ConnectionTimeOutSeconds,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate));
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
//construct the web request that we are going to issue on behalf of the client.
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));
if (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;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
......@@ -397,7 +406,7 @@ namespace Titanium.Web.Proxy
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
......@@ -407,7 +416,7 @@ namespace Titanium.Web.Proxy
}
catch
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
......
......@@ -114,7 +114,7 @@ namespace Titanium.Web.Proxy
}
catch
{
Dispose(args.ProxyClient.TcpClient, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args);
}
finally
......@@ -216,32 +216,30 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
private void Dispose(TcpClient tcpClient, IDisposable clientStream, IDisposable clientStreamReader,
private void Dispose(Stream clientStream, IDisposable clientStreamReader,
IDisposable clientStreamWriter, IDisposable args)
{
if (clientStream != null)
{
(clientStream as Stream).Close();
(clientStream as Stream).Dispose();
}
if (tcpClient != null)
{
tcpClient.Client.Close();
tcpClient.Close();
tcpClient.Client.Dispose();
clientStream.Close();
clientStream.Dispose();
}
if (args != null)
{
args.Dispose();
}
if (clientStreamReader != null)
{
clientStreamReader.Dispose();
}
if (clientStreamWriter != null)
{
clientStreamWriter.Dispose();
}
}
}
}
\ No newline at end of file
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