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

Merge pull request #216 from honfika/develop

Performance improvements:
parents 8de80542 5de1ef58
using System.Text;
using System;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
......
using System.Text;
using System;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
......
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Extensions
{
internal static class StringExtensions
{
internal static bool ContainsIgnoreCase(this string str, string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
}
}
}
......@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -17,13 +16,16 @@ namespace Titanium.Web.Proxy.Helpers
{
private readonly Stream stream;
private readonly Encoding encoding;
private readonly byte[] buffer;
internal CustomBinaryReader(Stream stream)
internal CustomBinaryReader(Stream stream, int bufferSize)
{
this.stream = stream;
//default to UTF-8
encoding = Encoding.UTF8;
buffer = new byte[bufferSize];
}
internal Stream BaseStream => stream;
......@@ -34,33 +36,40 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
internal async Task<string> ReadLineAsync()
{
using (var readBuffer = new MemoryStream())
{
var lastChar = default(char);
var buffer = new byte[1];
var lastChar = default(byte);
int bufferDataLength = 0;
while ((await stream.ReadAsync(buffer, 0, 1)) > 0)
// try to use the instance buffer, usually it is enough
var buffer = this.buffer;
while (await stream.ReadAsync(buffer, bufferDataLength, 1) > 0)
{
var newChar = buffer[bufferDataLength];
//if new line
if (lastChar == '\r' && buffer[0] == '\n')
if (lastChar == '\r' && newChar == '\n')
{
var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
//end of stream
if (buffer[0] == '\0')
if (newChar == '\0')
{
return encoding.GetString(readBuffer.ToArray());
return encoding.GetString(buffer, 0, bufferDataLength);
}
await readBuffer.WriteAsync(buffer,0,1);
bufferDataLength++;
//store last char for new line comparison
lastChar = (char)buffer[0];
}
lastChar = newChar;
return encoding.GetString(readBuffer.ToArray());
if (bufferDataLength == buffer.Length)
{
ResizeBuffer(ref buffer, bufferDataLength * 2);
}
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
/// <summary>
......@@ -78,6 +87,17 @@ namespace Titanium.Web.Proxy.Helpers
return requestLines;
}
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
internal async Task ReadAndIgnoreAllLinesAsync()
{
while (!string.IsNullOrEmpty(await ReadLineAsync()))
{
}
}
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// </summary>
......@@ -91,34 +111,47 @@ namespace Titanium.Web.Proxy.Helpers
if (totalBytesToRead < bufferSize)
bytesToRead = (int)totalBytesToRead;
var buffer = new byte[bufferSize];
var buffer = bytesToRead > this.buffer.Length ? new byte[bytesToRead] : this.buffer;
var bytesRead = 0;
int bytesRead;
var totalBytesRead = 0;
using (var outStream = new MemoryStream())
{
while ((bytesRead += await stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int)remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length)
{
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2));
}
}
return outStream.ToArray();
if (totalBytesRead != buffer.Length)
{
//Normally this should not happen. Resize the buffer anyway
var newBuffer = new byte[totalBytesRead];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, totalBytesRead);
buffer = newBuffer;
}
return buffer;
}
public void Dispose()
{
}
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
}
\ No newline at end of file
......@@ -181,7 +181,7 @@ namespace Titanium.Web.Proxy.Helpers
if (tmp.StartsWith("http=") || tmp.StartsWith("https="))
{
var endPoint = tmp.Substring(5);
return new HttpSystemProxyValue()
return new HttpSystemProxyValue
{
HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]),
......
......@@ -78,18 +78,19 @@ namespace Titanium.Web.Proxy.Http
//prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps))
{
requestLines.AppendLine(string.Join(" ", Request.Method, Request.RequestUri.AbsoluteUri, $"HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"));
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
else
{
requestLines.AppendLine(string.Join(" ", Request.Method, Request.RequestUri.PathAndQuery, $"HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"));
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
//Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
}
//write request headers
foreach (var headerItem in Request.RequestHeaders)
......@@ -97,7 +98,7 @@ namespace Titanium.Web.Proxy.Http
var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ": " + header.Value);
requestLines.AppendLine($"{header.Name}: {header.Value}");
}
}
......@@ -109,7 +110,7 @@ namespace Titanium.Web.Proxy.Http
{
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ": " + header.Value);
requestLines.AppendLine($"{header.Name}: {header.Value}");
}
}
}
......@@ -132,13 +133,13 @@ namespace Titanium.Web.Proxy.Http
//find if server is willing for expect continue
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
&& responseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{
Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
&& responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{
Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync();
......@@ -178,7 +179,7 @@ namespace Titanium.Web.Proxy.Http
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (Response.ResponseStatusCode.Equals("100")
&& Response.ResponseStatusDescription.ToLower().Equals("continue"))
&& Response.ResponseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{
//Read the next line after 100-continue
Response.Is100Continue = true;
......@@ -189,7 +190,7 @@ namespace Titanium.Web.Proxy.Http
return;
}
else if (Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{
//read next line after expectation failed response
Response.ExpectationFailed = true;
......
......@@ -172,7 +172,7 @@ namespace Titanium.Web.Proxy.Http
{
var header = RequestHeaders["transfer-encoding"];
return header.Value.ToLower().Contains("chunked");
return header.Value.ContainsIgnoreCase("chunked");
}
return false;
......@@ -266,7 +266,7 @@ namespace Titanium.Web.Proxy.Http
var header = RequestHeaders["upgrade"];
return header.Value.ToLower() == "websocket";
return header.Value.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
}
}
......
......@@ -54,7 +54,7 @@ namespace Titanium.Web.Proxy.Http
{
var header = ResponseHeaders["connection"];
if (header.Value.ToLower().Contains("close"))
if (header.Value.ContainsIgnoreCase("close"))
{
return false;
}
......@@ -153,7 +153,7 @@ namespace Titanium.Web.Proxy.Http
{
var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ToLower().Contains("chunked"))
if (header.Value.ContainsIgnoreCase("chunked"))
{
return true;
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models
{
......@@ -53,8 +55,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
private List<string> excludedHttpsHostNameRegex;
private List<string> includedHttpsHostNameRegex;
internal List<Regex> excludedHttpsHostNameRegex;
internal List<Regex> includedHttpsHostNameRegex;
internal bool IsSystemHttpProxy { get; set; }
......@@ -63,9 +65,9 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public List<string> ExcludedHttpsHostNameRegex
public IEnumerable<string> ExcludedHttpsHostNameRegex
{
get { return excludedHttpsHostNameRegex; }
get { return excludedHttpsHostNameRegex?.Select(x => x.ToString()).ToList(); }
set
{
if (IncludedHttpsHostNameRegex != null)
......@@ -73,16 +75,16 @@ namespace Titanium.Web.Proxy.Models
throw new ArgumentException("Cannot set excluded when included is set");
}
excludedHttpsHostNameRegex = value;
excludedHttpsHostNameRegex = value?.Select(x=>new Regex(x, RegexOptions.Compiled)).ToList();
}
}
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public List<string> IncludedHttpsHostNameRegex
public IEnumerable<string> IncludedHttpsHostNameRegex
{
get { return includedHttpsHostNameRegex; }
get { return includedHttpsHostNameRegex?.Select(x => x.ToString()).ToList(); }
set
{
if (ExcludedHttpsHostNameRegex != null)
......@@ -90,7 +92,7 @@ namespace Titanium.Web.Proxy.Models
throw new ArgumentException("Cannot set included when excluded is set");
}
includedHttpsHostNameRegex = value;
includedHttpsHostNameRegex = value?.Select(x => new Regex(x, RegexOptions.Compiled)).ToList();
}
}
......
......@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
......@@ -78,17 +79,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
writer.Close();
}
using (var reader = new CustomBinaryReader(stream))
using (var reader = new CustomBinaryReader(stream, bufferSize))
{
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAllLinesAsync();
await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
......@@ -137,7 +138,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection()
return new TcpConnection
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
......@@ -145,7 +146,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream),
StreamReader = new CustomBinaryReader(stream, bufferSize),
Stream = stream,
Version = httpVersion
};
......
......@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (null == header) throw new NullReferenceException();
var headerValue = header.Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
......
......@@ -616,8 +616,6 @@ namespace Titanium.Web.Proxy
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
}
......@@ -632,11 +630,11 @@ namespace Titanium.Web.Proxy
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Close();
tcpClient.Client.Dispose();
//the following 3 lines are unnecessary, tcpClient.Close() calls all of them internally
//tcpClient.Client.Shutdown(SocketShutdown.Both);
//tcpClient.Client.Close();
//tcpClient.Client.Dispose();
tcpClient.Close();
}
}
});
......
......@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
var clientStreamReader = new CustomBinaryReader(clientStream);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
Uri httpRemoteUri;
......@@ -76,12 +76,12 @@ namespace Titanium.Web.Proxy
if (endPoint.ExcludedHttpsHostNameRegex != null)
{
excluded = endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
excluded = endPoint.excludedHttpsHostNameRegex.Any(x => x.IsMatch(httpRemoteUri.Host));
}
if (endPoint.IncludedHttpsHostNameRegex != null)
{
excluded = !endPoint.IncludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
excluded = !endPoint.includedHttpsHostNameRegex.Any(x => x.IsMatch(httpRemoteUri.Host));
}
List<HttpHeader> connectRequestHeaders = null;
......@@ -123,7 +123,7 @@ namespace Titanium.Web.Proxy
//HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream;
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamReader = new CustomBinaryReader(sslStream, BufferSize);
clientStreamWriter = new StreamWriter(sslStream) {NewLine = ProxyConstants.NewLine };
}
......@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy
else if (httpVerb == "CONNECT")
{
//Cyphen out CONNECT request headers
await clientStreamReader.ReadAllLinesAsync();
await clientStreamReader.ReadAndIgnoreAllLinesAsync();
//write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version);
......@@ -193,7 +193,7 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false);
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamReader = new CustomBinaryReader(sslStream, BufferSize);
clientStreamWriter = new StreamWriter(sslStream) { NewLine = ProxyConstants.NewLine };
//HTTPS server created - we can now decrypt the client's traffic
......@@ -209,7 +209,7 @@ namespace Titanium.Web.Proxy
}
else
{
clientStreamReader = new CustomBinaryReader(clientStream);
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine };
}
......
......@@ -72,6 +72,7 @@
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\StringExtensions.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