Commit e6872fde authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #218 from honfika/develop

Performance improvements: Buffered stream, avoid string concatenation…
parents 84b90bf4 b0607472
......@@ -14,18 +14,33 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
internal class CustomBinaryReader : IDisposable
{
private readonly Stream stream;
private readonly CustomBufferedStream stream;
private readonly int bufferSize;
private readonly Encoding encoding;
private readonly byte[] buffer;
internal CustomBinaryReader(Stream stream, int bufferSize)
[ThreadStatic]
private static byte[] staticBuffer;
private byte[] buffer
{
get
{
if (staticBuffer == null || staticBuffer.Length != bufferSize)
{
staticBuffer = new byte[bufferSize];
}
return staticBuffer;
}
}
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
this.bufferSize = bufferSize;
//default to UTF-8
encoding = Encoding.UTF8;
buffer = new byte[bufferSize];
}
internal Stream BaseStream => stream;
......@@ -40,12 +55,13 @@ namespace Titanium.Web.Proxy.Helpers
int bufferDataLength = 0;
// try to use the instance buffer, usually it is enough
// try to use the thread static buffer, usually it is enough
var buffer = this.buffer;
while (await stream.ReadAsync(buffer, bufferDataLength, 1) > 0)
while (stream.DataAvailable || await stream.FillBufferAsync())
{
var newChar = buffer[bufferDataLength];
var newChar = stream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
if (lastChar == '\r' && newChar == '\n')
......@@ -111,7 +127,11 @@ namespace Titanium.Web.Proxy.Helpers
if (totalBytesToRead < bufferSize)
bytesToRead = (int)totalBytesToRead;
var buffer = bytesToRead > this.buffer.Length ? new byte[bytesToRead] : this.buffer;
var buffer = this.buffer;
if (bytesToRead > buffer.Length)
{
buffer = new byte[bytesToRead];
}
int bytesRead;
var totalBytesRead = 0;
......
This diff is collapsed.
using System;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{
......@@ -28,6 +30,7 @@ namespace Titanium.Web.Proxy.Models
/// Header Name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Header Value.
/// </summary>
......@@ -41,5 +44,12 @@ namespace Titanium.Web.Proxy.Models
{
return $"{Name}: {Value}";
}
internal async Task WriteToStream(StreamWriter writer)
{
await writer.WriteAsync(Name);
await writer.WriteAsync(": ");
await writer.WriteLineAsync(Value);
}
}
}
\ No newline at end of file
......@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Stream clientStream, IPEndPoint upStreamEndPoint)
{
TcpClient client;
Stream stream;
CustomBufferedStream stream;
bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName);
......@@ -61,7 +61,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient(upStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = client.GetStream();
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine })
{
......@@ -83,7 +83,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
......@@ -96,7 +95,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient(upStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = client.GetStream();
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
}
try
......@@ -106,7 +105,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
stream = sslStream;
stream = new CustomBufferedStream(sslStream, bufferSize);
}
catch
{
......@@ -121,13 +120,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient(upStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream();
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
}
else
{
client = new TcpClient(upStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = client.GetStream();
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
}
}
......
......@@ -6,7 +6,6 @@ using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text.RegularExpressions;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
......@@ -30,7 +29,7 @@ namespace Titanium.Web.Proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
......@@ -112,7 +111,6 @@ namespace Titanium.Web.Proxy
try
{
sslStream = new SslStream(clientStream, true);
var certificate = endPoint.GenericCertificate ?? certificateManager.CreateCertificate(httpRemoteUri.Host, false);
......@@ -121,11 +119,10 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false,
SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream;
clientStreamReader = new CustomBinaryReader(sslStream, BufferSize);
clientStreamWriter = new StreamWriter(sslStream) {NewLine = ProxyConstants.NewLine };
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) {NewLine = ProxyConstants.NewLine };
}
catch
{
......@@ -171,8 +168,7 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
CustomBufferedStream clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
......@@ -193,8 +189,9 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false);
clientStreamReader = new CustomBinaryReader(sslStream, BufferSize);
clientStreamWriter = new StreamWriter(sslStream) { NewLine = ProxyConstants.NewLine };
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
//HTTPS server created - we can now decrypt the client's traffic
}
......@@ -205,7 +202,6 @@ namespace Titanium.Web.Proxy
Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return;
}
clientStream = sslStream;
}
else
{
......
......@@ -175,7 +175,7 @@ namespace Titanium.Web.Proxy
foreach (var header in response.ResponseHeaders)
{
await responseWriter.WriteLineAsync(header.Value.ToString());
await header.Value.WriteToStream(responseWriter);
}
//write non unique request headers
......@@ -184,11 +184,10 @@ namespace Titanium.Web.Proxy
var headers = headerItem.Value;
foreach (var header in headers)
{
await responseWriter.WriteLineAsync(header.ToString());
await header.WriteToStream(responseWriter);
}
}
await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync();
}
......
......@@ -26,6 +26,7 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Debug\Titanium.Web.Proxy.XML</DocumentationFile>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
......@@ -73,6 +74,7 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\Responses\GenericResponse.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