Commit 65be519e authored by titanium007's avatar titanium007

Issue #27

Improve performance by avoiding new Tasks for each requests
parent ca13d730
...@@ -14,27 +14,28 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -14,27 +14,28 @@ namespace Titanium.Web.Proxy.Helpers
internal string ReadLine() internal string ReadLine()
{ {
var buf = new char[1];
var readBuffer = new StringBuilder(); var readBuffer = new StringBuilder();
try try
{ {
var lastChar = new char(); var lastChar = default(char);
while ((Read(buf, 0, 1)) > 0) while (true)
{ {
if (lastChar == '\r' && buf[0] == '\n') var buf = ReadChar();
if (lastChar == '\r' && buf == '\n')
{ {
return readBuffer.Remove(readBuffer.Length - 1, 1).ToString(); return readBuffer.Remove(readBuffer.Length - 1, 1).ToString();
} }
if (buf[0] == '\0') if (buf == '\0')
{ {
return readBuffer.ToString(); return readBuffer.ToString();
} }
readBuffer.Append(buf[0]); readBuffer.Append(buf);
lastChar = buf[0]; lastChar = buf;
} }
return readBuffer.ToString();
} }
catch (IOException) catch (IOException)
{ {
...@@ -42,7 +43,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -42,7 +43,6 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
internal List<string> ReadAllLines() internal List<string> ReadAllLines()
{ {
string tmpLine; string tmpLine;
......
...@@ -61,7 +61,7 @@ namespace Titanium.Web.Proxy ...@@ -61,7 +61,7 @@ namespace Titanium.Web.Proxy
{ {
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
WebRequest.DefaultWebProxy = null; WebRequest.DefaultWebProxy = null;
ServicePointManager.DefaultConnectionLimit = 10; ServicePointManager.DefaultConnectionLimit = int.MaxValue;
ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000; //3 minutes ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000; //3 minutes
ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000; ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000;
......
...@@ -97,10 +97,8 @@ namespace Titanium.Web.Proxy ...@@ -97,10 +97,8 @@ namespace Titanium.Web.Proxy
} }
//Now create the request //Now create the request
Task.Factory.StartNew( HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
() => httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.OriginalString : null);
HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.OriginalString : null));
} }
catch catch
{ {
...@@ -112,137 +110,144 @@ namespace Titanium.Web.Proxy ...@@ -112,137 +110,144 @@ namespace Titanium.Web.Proxy
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName)
{ {
if (string.IsNullOrEmpty(httpCmd)) while (true)
{ {
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); if (string.IsNullOrEmpty(httpCmd))
return; {
} Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return;
var args = new SessionEventArgs(BUFFER_SIZE); }
args.Client = client;
try var args = new SessionEventArgs(BUFFER_SIZE);
{ args.Client = client;
//break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3);
var httpMethod = httpCmdSplit[0];
var httpRemoteUri =
new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1]));
var httpVersion = httpCmdSplit[2];
Version version; try
if (httpVersion == "HTTP/1.1")
{
version = new Version(1, 1);
}
else
{ {
version = new Version(1, 0); //break up the line into three components (method, remote URL & Http Version)
} var httpCmdSplit = httpCmd.Split(SpaceSplit, 3);
if (httpRemoteUri.Scheme == Uri.UriSchemeHttps) var httpMethod = httpCmdSplit[0];
{ var httpRemoteUri =
args.IsHttps = true; new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1]));
} var httpVersion = httpCmdSplit[2];
args.RequestHeaders = new List<HttpHeader>(); Version version;
if (httpVersion == "HTTP/1.1")
{
version = new Version(1, 1);
}
else
{
version = new Version(1, 0);
}
string tmpLine; if (httpRemoteUri.Scheme == Uri.UriSchemeHttps)
{
args.IsHttps = true;
}
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) args.RequestHeaders = new List<HttpHeader>();
{
var header = tmpLine.Split(ColonSpaceSplit, 2, StringSplitOptions.None);
args.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
}
for (var i = 0; i < args.RequestHeaders.Count; i++) string tmpLine;
{
var rawHeader = args.RequestHeaders[i];
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
{
var header = tmpLine.Split(ColonSpaceSplit, 2, StringSplitOptions.None);
args.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
}
//if request was upgrade to web-socket protocol then relay the request without proxying for (var i = 0; i < args.RequestHeaders.Count; i++)
if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket"))
{ {
TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.RequestHeaders, var rawHeader = args.RequestHeaders[i];
httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; //if request was upgrade to web-socket protocol then relay the request without proxying
if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket"))
{
TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
} }
}
//construct the web request that we are going to issue on behalf of the client.
args.ProxyRequest = (HttpWebRequest)WebRequest.Create(httpRemoteUri);
args.ProxyRequest.Proxy = null;
args.ProxyRequest.UseDefaultCredentials = true;
args.ProxyRequest.Method = httpMethod;
args.ProxyRequest.ProtocolVersion = version;
args.ClientStream = clientStream;
args.ClientStreamReader = clientStreamReader;
args.ClientStreamWriter = clientStreamWriter;
args.ProxyRequest.AllowAutoRedirect = false;
args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.RequestHostname = args.ProxyRequest.RequestUri.Host;
args.RequestUrl = args.ProxyRequest.RequestUri.OriginalString;
args.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port;
args.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
args.RequestHttpVersion = version;
args.RequestIsAlive = args.ProxyRequest.KeepAlive;
args.ProxyRequest.AllowWriteStreamBuffering = true;
//If requested interception
if (BeforeRequest != null)
{
args.RequestEncoding = args.ProxyRequest.GetEncoding();
BeforeRequest(null, args);
}
//construct the web request that we are going to issue on behalf of the client. args.RequestLocked = true;
args.ProxyRequest = (HttpWebRequest) WebRequest.Create(httpRemoteUri);
args.ProxyRequest.Proxy = null;
args.ProxyRequest.UseDefaultCredentials = true;
args.ProxyRequest.Method = httpMethod;
args.ProxyRequest.ProtocolVersion = version;
args.ClientStream = clientStream;
args.ClientStreamReader = clientStreamReader;
args.ClientStreamWriter = clientStreamWriter;
args.ProxyRequest.AllowAutoRedirect = false;
args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.RequestHostname = args.ProxyRequest.RequestUri.Host;
args.RequestUrl = args.ProxyRequest.RequestUri.OriginalString;
args.ClientPort = ((IPEndPoint) client.Client.RemoteEndPoint).Port;
args.ClientIpAddress = ((IPEndPoint) client.Client.RemoteEndPoint).Address;
args.RequestHttpVersion = version;
args.RequestIsAlive = args.ProxyRequest.KeepAlive;
args.ProxyRequest.ConnectionGroupName = args.RequestHostname;
args.ProxyRequest.AllowWriteStreamBuffering = true;
if (args.CancelRequest)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
//If requested interception SetRequestHeaders(args.RequestHeaders, args.ProxyRequest);
if (BeforeRequest != null)
{
args.RequestEncoding = args.ProxyRequest.GetEncoding();
BeforeRequest(null, args);
}
args.RequestLocked = true; //If request was modified by user
if (args.RequestBodyRead)
{
args.ProxyRequest.ContentLength = args.RequestBody.Length;
var newStream = args.ProxyRequest.GetRequestStream();
newStream.Write(args.RequestBody, 0, args.RequestBody.Length);
}
else
{
//If its a post/put request, then read the client html body and send it to server
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{
SendClientRequestBody(args);
}
}
if (args.CancelRequest) HandleHttpSessionResponse(args);
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
SetRequestHeaders(args.RequestHeaders, args.ProxyRequest); if (args.ResponseHeaders.Any(x => x.Name.ToLower() == "proxy-connection" && x.Value.ToLower() == "close"))
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
//Now read the next request (if keep-Alive is enabled, otherwise exit this thread)
//If client is pipeling the request, this will be immediately hit before response for previous request was made
httpCmd = clientStreamReader.ReadLine();
//Http request body sent, now wait for next request
//If request was modified by user client = args.Client;
if (args.RequestBodyRead) clientStream = args.ClientStream;
{ clientStreamReader = args.ClientStreamReader;
args.ProxyRequest.ContentLength = args.RequestBody.Length; args.ClientStreamWriter = clientStreamWriter;
var newStream = args.ProxyRequest.GetRequestStream();
newStream.Write(args.RequestBody, 0, args.RequestBody.Length);
args.ProxyRequest.BeginGetResponse(HandleHttpSessionResponse, args);
} }
else catch
{ {
//If its a post/put request, then read the client html body and send it to server Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") throw;
{
SendClientRequestBody(args);
}
//Http request body sent, now wait asynchronously for response
args.ProxyRequest.BeginGetResponse(HandleHttpSessionResponse, args);
} }
//Now read the next request (if keep-Alive is enabled, otherwise exit this thread)
//If client is pipeling the request, this will be immediately hit before response for previous request was made
httpCmd = clientStreamReader.ReadLine();
//Http request body sent, now wait for next request
Task.Factory.StartNew(
() =>
HandleHttpSessionRequest(args.Client, httpCmd, args.ClientStream, args.ClientStreamReader,
args.ClientStreamWriter, secureTunnelHostName));
}
catch
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
} }
} }
...@@ -250,7 +255,7 @@ namespace Titanium.Web.Proxy ...@@ -250,7 +255,7 @@ namespace Titanium.Web.Proxy
{ {
clientStreamWriter.WriteLine(httpVersion + " 200 Connection established"); clientStreamWriter.WriteLine(httpVersion + " 200 Connection established");
clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now); clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now);
clientStreamWriter.WriteLine("connection:close"); //clientStreamWriter.WriteLine("connection:close");
clientStreamWriter.WriteLine(); clientStreamWriter.WriteLine();
clientStreamWriter.Flush(); clientStreamWriter.Flush();
} }
...@@ -302,6 +307,8 @@ namespace Titanium.Web.Proxy ...@@ -302,6 +307,8 @@ namespace Titanium.Web.Proxy
case "proxy-connection": case "proxy-connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive") if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true; webRequest.KeepAlive = true;
else if (requestHeaders[i].Value.ToLower() == "close")
webRequest.KeepAlive = false;
break; break;
case "range": case "range":
var startEnd = requestHeaders[i].Value.Replace(Environment.NewLine, "").Remove(0, 6).Split('-'); var startEnd = requestHeaders[i].Value.Replace(Environment.NewLine, "").Remove(0, 6).Split('-');
...@@ -359,18 +366,18 @@ namespace Titanium.Web.Proxy ...@@ -359,18 +366,18 @@ namespace Titanium.Web.Proxy
int bytesToRead; int bytesToRead;
if (args.ProxyRequest.ContentLength < BUFFER_SIZE) if (args.ProxyRequest.ContentLength < BUFFER_SIZE)
{ {
bytesToRead = (int) args.ProxyRequest.ContentLength; bytesToRead = (int)args.ProxyRequest.ContentLength;
} }
else else
bytesToRead = BUFFER_SIZE; bytesToRead = BUFFER_SIZE;
while (totalbytesRead < (int) args.ProxyRequest.ContentLength) while (totalbytesRead < (int)args.ProxyRequest.ContentLength)
{ {
var buffer = args.ClientStreamReader.ReadBytes(bytesToRead); var buffer = args.ClientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length; totalbytesRead += buffer.Length;
var remainingBytes = (int) args.ProxyRequest.ContentLength - totalbytesRead; var remainingBytes = (int)args.ProxyRequest.ContentLength - totalbytesRead;
if (remainingBytes < bytesToRead) if (remainingBytes < bytesToRead)
{ {
bytesToRead = remainingBytes; bytesToRead = remainingBytes;
...@@ -414,7 +421,6 @@ namespace Titanium.Web.Proxy ...@@ -414,7 +421,6 @@ namespace Titanium.Web.Proxy
} }
} }
postStream.Close(); postStream.Close();
} }
catch catch
......
...@@ -15,13 +15,12 @@ namespace Titanium.Web.Proxy ...@@ -15,13 +15,12 @@ namespace Titanium.Web.Proxy
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response //Called asynchronously when a request was successfully and we received the response
private static void HandleHttpSessionResponse(IAsyncResult asynchronousResult) private static void HandleHttpSessionResponse(SessionEventArgs args)
{ {
var args = (SessionEventArgs)asynchronousResult.AsyncState;
try try
{ {
args.ServerResponse = (HttpWebResponse)args.ProxyRequest.EndGetResponse(asynchronousResult); args.ServerResponse = (HttpWebResponse)args.ProxyRequest.GetResponse();
} }
catch (WebException webEx) catch (WebException webEx)
{ {
...@@ -132,6 +131,8 @@ namespace Titanium.Web.Proxy ...@@ -132,6 +131,8 @@ namespace Titanium.Web.Proxy
{ {
if (headers != null) if (headers != null)
{ {
FixProxyHeaders(headers);
foreach (var header in headers) foreach (var header in headers)
{ {
responseWriter.WriteLine(header.ToString()); responseWriter.WriteLine(header.ToString());
...@@ -141,10 +142,24 @@ namespace Titanium.Web.Proxy ...@@ -141,10 +142,24 @@ namespace Titanium.Web.Proxy
responseWriter.WriteLine(); responseWriter.WriteLine();
responseWriter.Flush(); responseWriter.Flush();
} }
private static void FixProxyHeaders(List<HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection
if (headers.Any(x => x.Name.ToLower() == "proxy-connection" && x.Value.ToLower() == "close"))
if (headers.Any(x => x.Name.ToLower() == "connection") == false)
{
headers.Add(new HttpHeader("connection", "close"));
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
else
headers.Find(x => x.Name.ToLower() == "connection").Value = "close";
}
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length, private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length,
bool isChunked) bool isChunked)
{ {
FixProxyHeaders(headers);
if (!isChunked) if (!isChunked)
{ {
if (headers.Any(x => x.Name.ToLower() == "content-length") == false) if (headers.Any(x => x.Name.ToLower() == "content-length") == false)
......
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