Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.

Commit 8b46f91

Browse files
Merge pull request #99 from justcoding121/release
Performance: Fix abrupt termination of SSL requests
2 parents 4456a22 + e1cf60d commit 8b46f91

File tree

6 files changed

+163
-146
lines changed

6 files changed

+163
-146
lines changed

Titanium.Web.Proxy/Helpers/Tcp.cs

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,37 @@
99
using System.Threading.Tasks;
1010
using Titanium.Web.Proxy.Extensions;
1111
using Titanium.Web.Proxy.Models;
12-
using Titanium.Web.Proxy.Shared;
12+
using Titanium.Web.Proxy.Network;
1313

1414
namespace Titanium.Web.Proxy.Helpers
1515
{
1616

1717
internal class TcpHelper
1818
{
19+
1920
/// <summary>
2021
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
2122
/// Usefull for websocket requests
2223
/// </summary>
23-
/// <param name="clientStream"></param>
24+
/// <param name="bufferSize"></param>
25+
/// <param name="connectionTimeOutSeconds"></param>
26+
/// <param name="remoteHostName"></param>
2427
/// <param name="httpCmd"></param>
28+
/// <param name="httpVersion"></param>
2529
/// <param name="requestHeaders"></param>
26-
/// <param name="hostName"></param>
27-
/// <param name="tunnelPort"></param>
2830
/// <param name="isHttps"></param>
31+
/// <param name="remotePort"></param>
32+
/// <param name="supportedProtocols"></param>
33+
/// <param name="remoteCertificateValidationCallback"></param>
34+
/// <param name="localCertificateSelectionCallback"></param>
35+
/// <param name="clientStream"></param>
36+
/// <param name="tcpConnectionFactory"></param>
2937
/// <returns></returns>
30-
internal static async Task SendRaw(Stream clientStream, string httpCmd, Dictionary<string, HttpHeader> requestHeaders, string hostName,
31-
int tunnelPort, bool isHttps, SslProtocols supportedProtocols, int connectionTimeOutSeconds)
38+
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
39+
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
40+
bool isHttps, SslProtocols supportedProtocols,
41+
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
42+
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
3243
{
3344
//prepare the prefix content
3445
StringBuilder sb = null;
@@ -54,40 +65,17 @@ internal static async Task SendRaw(Stream clientStream, string httpCmd, Dictiona
5465
sb.Append(Environment.NewLine);
5566
}
5667

57-
58-
TcpClient tunnelClient = null;
59-
Stream tunnelStream = null;
60-
//create the TcpClient to the server
68+
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
69+
remoteHostName, remotePort,
70+
httpVersion, isHttps,
71+
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
72+
null, null, clientStream);
73+
6174
try
6275
{
63-
tunnelClient = new TcpClient(hostName, tunnelPort);
64-
tunnelStream = tunnelClient.GetStream();
65-
66-
if (isHttps)
67-
{
68-
SslStream sslStream = null;
69-
try
70-
{
71-
sslStream = new SslStream(tunnelStream);
72-
await sslStream.AuthenticateAsClientAsync(hostName, null, supportedProtocols, false);
73-
tunnelStream = sslStream;
74-
}
75-
catch
76-
{
77-
if (sslStream != null)
78-
{
79-
sslStream.Dispose();
80-
}
81-
82-
throw;
83-
}
84-
}
85-
86-
tunnelClient.SendTimeout = connectionTimeOutSeconds * 1000;
87-
tunnelClient.ReceiveTimeout = connectionTimeOutSeconds * 1000;
76+
TcpClient tunnelClient = tcpConnection.TcpClient;
8877

89-
tunnelStream.ReadTimeout = connectionTimeOutSeconds * 1000;
90-
tunnelStream.WriteTimeout = connectionTimeOutSeconds * 1000;
78+
Stream tunnelStream = tcpConnection.Stream;
9179

9280
Task sendRelay;
9381

@@ -108,19 +96,13 @@ internal static async Task SendRaw(Stream clientStream, string httpCmd, Dictiona
10896
}
10997
catch
11098
{
111-
if (tunnelStream != null)
112-
{
113-
tunnelStream.Close();
114-
tunnelStream.Dispose();
115-
}
116-
117-
if (tunnelClient != null)
118-
{
119-
tunnelClient.Close();
120-
}
121-
12299
throw;
123100
}
101+
finally
102+
{
103+
tcpConnection.Dispose();
104+
}
124105
}
106+
125107
}
126108
}

Titanium.Web.Proxy/Network/TcpConnection.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Titanium.Web.Proxy.Network
88
/// <summary>
99
/// An object that holds TcpConnection to a particular server & port
1010
/// </summary>
11-
public class TcpConnection: IDisposable
11+
public class TcpConnection : IDisposable
1212
{
1313
internal string HostName { get; set; }
1414
internal int port { get; set; }
@@ -44,11 +44,15 @@ internal TcpConnection()
4444

4545
public void Dispose()
4646
{
47-
Stream.Close();
4847
Stream.Dispose();
48+
49+
TcpClient.LingerState = new LingerOption(true, 0);
50+
TcpClient.Client.Shutdown(SocketShutdown.Both);
4951
TcpClient.Client.Close();
50-
TcpClient.Close();
5152
TcpClient.Client.Dispose();
53+
54+
TcpClient.Close();
55+
5256
}
5357
}
5458
}

Titanium.Web.Proxy/Network/TcpConnectionFactory.cs

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
42
using System.Net.Sockets;
53
using System.Text;
64
using System.Threading.Tasks;
75
using System.IO;
86
using System.Net.Security;
97
using Titanium.Web.Proxy.Helpers;
10-
using System.Threading;
11-
using Titanium.Web.Proxy.Extensions;
128
using Titanium.Web.Proxy.Models;
139
using System.Security.Authentication;
1410

@@ -19,38 +15,30 @@ namespace Titanium.Web.Proxy.Network
1915
/// </summary>
2016
internal class TcpConnectionFactory
2117
{
22-
/// <summary>
23-
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
24-
/// </summary>
25-
/// <param name="hostname"></param>
26-
/// <param name="port"></param>
27-
/// <param name="isHttps"></param>
28-
/// <param name="version"></param>
29-
/// <returns></returns>
30-
internal async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version,
31-
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
32-
int connectionTimeOutSeconds,
33-
RemoteCertificateValidationCallback remoteCertificateValidationCallBack,
34-
LocalCertificateSelectionCallback localCertificateSelectionCallback)
35-
{
36-
//not in cache so create and return
37-
return await CreateClient(hostname, port, isHttps, version, connectionTimeOutSeconds, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
38-
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
39-
40-
}
41-
4218

4319
/// <summary>
44-
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version
20+
/// Creates a TCP connection to server
4521
/// </summary>
46-
/// <param name="hostname"></param>
47-
/// <param name="port"></param>
22+
/// <param name="bufferSize"></param>
23+
/// <param name="connectionTimeOutSeconds"></param>
24+
/// <param name="remoteHostName"></param>
25+
/// <param name="httpCmd"></param>
26+
/// <param name="httpVersion"></param>
4827
/// <param name="isHttps"></param>
49-
/// <param name="version"></param>
28+
/// <param name="remotePort"></param>
29+
/// <param name="supportedSslProtocols"></param>
30+
/// <param name="remoteCertificateValidationCallback"></param>
31+
/// <param name="localCertificateSelectionCallback"></param>
32+
/// <param name="externalHttpProxy"></param>
33+
/// <param name="externalHttpsProxy"></param>
34+
/// <param name="clientStream"></param>
5035
/// <returns></returns>
51-
private async Task<TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version, int connectionTimeOutSeconds,
52-
ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
53-
RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
36+
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
37+
string remoteHostName, int remotePort, Version httpVersion,
38+
bool isHttps, SslProtocols supportedSslProtocols,
39+
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
40+
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
41+
Stream clientStream)
5442
{
5543
TcpClient client;
5644
Stream stream;
@@ -60,15 +48,15 @@ private async Task<TcpConnection> CreateClient(string hostname, int port, bool i
6048
SslStream sslStream = null;
6149

6250
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
63-
if (upStreamHttpsProxy != null)
51+
if (externalHttpsProxy != null)
6452
{
65-
client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port);
53+
client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port);
6654
stream = client.GetStream();
6755

6856
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
6957
{
70-
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version));
71-
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port));
58+
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion));
59+
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort));
7260
await writer.WriteLineAsync("Connection: Keep-Alive");
7361
await writer.WriteLineAsync();
7462
await writer.FlushAsync();
@@ -89,16 +77,16 @@ private async Task<TcpConnection> CreateClient(string hostname, int port, bool i
8977
}
9078
else
9179
{
92-
client = new TcpClient(hostname, port);
80+
client = new TcpClient(remoteHostName, remotePort);
9381
stream = client.GetStream();
9482
}
9583

9684
try
9785
{
98-
sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
86+
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
9987
localCertificateSelectionCallback);
10088

101-
await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);
89+
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
10290

10391
stream = sslStream;
10492
}
@@ -114,14 +102,14 @@ private async Task<TcpConnection> CreateClient(string hostname, int port, bool i
114102
}
115103
else
116104
{
117-
if (upStreamHttpProxy != null)
105+
if (externalHttpProxy != null)
118106
{
119-
client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port);
107+
client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
120108
stream = client.GetStream();
121109
}
122110
else
123111
{
124-
client = new TcpClient(hostname, port);
112+
client = new TcpClient(remoteHostName, remotePort);
125113
stream = client.GetStream();
126114
}
127115
}
@@ -132,15 +120,17 @@ private async Task<TcpConnection> CreateClient(string hostname, int port, bool i
132120
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
133121
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
134122

123+
client.NoDelay = true;
124+
135125
return new TcpConnection()
136126
{
137-
HostName = hostname,
138-
port = port,
127+
HostName = remoteHostName,
128+
port = remotePort,
139129
IsHttps = isHttps,
140130
TcpClient = client,
141131
StreamReader = new CustomBinaryReader(stream),
142132
Stream = stream,
143-
Version = version
133+
Version = httpVersion
144134
};
145135
}
146136

Titanium.Web.Proxy/ProxyServer.cs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ namespace Titanium.Web.Proxy
1515
/// <summary>
1616
/// Proxy Server Main class
1717
/// </summary>
18-
public partial class ProxyServer: IDisposable
18+
public partial class ProxyServer : IDisposable
1919
{
20-
20+
2121
/// <summary>
2222
/// Manages certificates used by this proxy
2323
/// </summary>
@@ -197,7 +197,7 @@ public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
197197
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTP Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
198198

199199
}
200-
200+
201201

202202
/// <summary>
203203
/// Set the given explicit end point as the default proxy server for current machine
@@ -360,17 +360,12 @@ private void OnAcceptConnection(IAsyncResult asyn)
360360
{
361361
var endPoint = (ProxyEndPoint)asyn.AsyncState;
362362

363+
TcpClient tcpClient = null;
364+
363365
try
364366
{
365367
//based on end point type call appropriate request handlers
366-
var client = endPoint.listener.EndAcceptTcpClient(asyn);
367-
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
368-
HandleClient(endPoint as TransparentProxyEndPoint, client);
369-
else
370-
HandleClient(endPoint as ExplicitProxyEndPoint, client);
371-
372-
// Get the listener that handles the client request.
373-
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
368+
tcpClient = endPoint.listener.EndAcceptTcpClient(asyn);
374369
}
375370
catch (ObjectDisposedException)
376371
{
@@ -384,6 +379,43 @@ private void OnAcceptConnection(IAsyncResult asyn)
384379
//Other errors are discarded to keep proxy running
385380
}
386381

382+
if (tcpClient != null)
383+
{
384+
385+
Task.Run(async () =>
386+
{
387+
try
388+
{
389+
tcpClient.NoDelay = true;
390+
391+
392+
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
393+
{
394+
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
395+
}
396+
else
397+
{
398+
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
399+
}
400+
401+
}
402+
finally
403+
{
404+
if (tcpClient != null)
405+
{
406+
tcpClient.LingerState = new LingerOption(true, 0);
407+
tcpClient.Client.Shutdown(SocketShutdown.Both);
408+
tcpClient.Client.Close();
409+
tcpClient.Client.Dispose();
410+
411+
tcpClient.Close();
412+
}
413+
}
414+
});
415+
}
416+
417+
// Get the listener that handles the client request.
418+
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
387419
}
388420

389421
public void Dispose()

0 commit comments

Comments
 (0)