Commit 3ea9f89a authored by titanium007's avatar titanium007

perf: avoid TCP Time_Wait

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