diff --git a/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs b/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs index b34fa12b8..92089f2fd 100644 --- a/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs +++ b/Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs @@ -14,27 +14,28 @@ internal CustomBinaryReader(Stream stream, Encoding encoding) internal string ReadLine() { - var buf = new char[1]; var readBuffer = new StringBuilder(); + 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(); } - if (buf[0] == '\0') + if (buf == '\0') { return readBuffer.ToString(); } - readBuffer.Append(buf[0]); + readBuffer.Append(buf); - lastChar = buf[0]; + lastChar = buf; } - return readBuffer.ToString(); + } catch (IOException) { @@ -42,7 +43,6 @@ internal string ReadLine() } } - internal List ReadAllLines() { string tmpLine; diff --git a/Titanium.Web.Proxy/ProxyServer.cs b/Titanium.Web.Proxy/ProxyServer.cs index b648cac85..cd20a539c 100644 --- a/Titanium.Web.Proxy/ProxyServer.cs +++ b/Titanium.Web.Proxy/ProxyServer.cs @@ -61,7 +61,7 @@ public static void Initialize() { ServicePointManager.Expect100Continue = false; WebRequest.DefaultWebProxy = null; - ServicePointManager.DefaultConnectionLimit = 10; + ServicePointManager.DefaultConnectionLimit = int.MaxValue; ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000; //3 minutes ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000; diff --git a/Titanium.Web.Proxy/RequestHandler.cs b/Titanium.Web.Proxy/RequestHandler.cs index 61b552f51..3fb0da6fd 100644 --- a/Titanium.Web.Proxy/RequestHandler.cs +++ b/Titanium.Web.Proxy/RequestHandler.cs @@ -97,10 +97,8 @@ private static void HandleClient(TcpClient client) } //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 { @@ -112,137 +110,144 @@ private static void HandleClient(TcpClient client) private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName) { - if (string.IsNullOrEmpty(httpCmd)) + while (true) { - Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); - return; - } - - var args = new SessionEventArgs(BUFFER_SIZE); - args.Client = client; - + if (string.IsNullOrEmpty(httpCmd)) + { + Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); + return; + } - try - { - //break up the line into three components (method, remote URL & Http Version) - var httpCmdSplit = httpCmd.Split(SpaceSplit, 3); + var args = new SessionEventArgs(BUFFER_SIZE); + args.Client = client; - var httpMethod = httpCmdSplit[0]; - var httpRemoteUri = - new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1])); - var httpVersion = httpCmdSplit[2]; - Version version; - if (httpVersion == "HTTP/1.1") - { - version = new Version(1, 1); - } - else + try { - 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) - { - args.IsHttps = true; - } + var httpMethod = httpCmdSplit[0]; + var httpRemoteUri = + new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1])); + var httpVersion = httpCmdSplit[2]; - args.RequestHeaders = new List(); + 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())) - { - var header = tmpLine.Split(ColonSpaceSplit, 2, StringSplitOptions.None); - args.RequestHeaders.Add(new HttpHeader(header[0], header[1])); - } + args.RequestHeaders = new List(); - for (var i = 0; i < args.RequestHeaders.Count; i++) - { - var rawHeader = args.RequestHeaders[i]; + string tmpLine; + 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 - if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket")) + for (var i = 0; i < args.RequestHeaders.Count; i++) { - TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.RequestHeaders, - httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps); - Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); - return; + var rawHeader = args.RequestHeaders[i]; + + + //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.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; + args.RequestLocked = true; + if (args.CancelRequest) + { + Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); + return; + } - //If requested interception - if (BeforeRequest != null) - { - args.RequestEncoding = args.ProxyRequest.GetEncoding(); - BeforeRequest(null, args); - } + SetRequestHeaders(args.RequestHeaders, args.ProxyRequest); - 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) - { - Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); - return; - } + HandleHttpSessionResponse(args); - 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 - if (args.RequestBodyRead) - { - args.ProxyRequest.ContentLength = args.RequestBody.Length; - var newStream = args.ProxyRequest.GetRequestStream(); - newStream.Write(args.RequestBody, 0, args.RequestBody.Length); + client = args.Client; + clientStream = args.ClientStream; + clientStreamReader = args.ClientStreamReader; + args.ClientStreamWriter = clientStreamWriter; - args.ProxyRequest.BeginGetResponse(HandleHttpSessionResponse, args); } - else + catch { - //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); - } - //Http request body sent, now wait asynchronously for response - args.ProxyRequest.BeginGetResponse(HandleHttpSessionResponse, args); + Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); + throw; } - - //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 @@ private static void WriteConnectResponse(StreamWriter clientStreamWriter, string { clientStreamWriter.WriteLine(httpVersion + " 200 Connection established"); clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now); - clientStreamWriter.WriteLine("connection:close"); + //clientStreamWriter.WriteLine("connection:close"); clientStreamWriter.WriteLine(); clientStreamWriter.Flush(); } @@ -302,6 +307,8 @@ private static void SetRequestHeaders(List requestHeaders, HttpWebRe case "proxy-connection": if (requestHeaders[i].Value.ToLower() == "keep-alive") webRequest.KeepAlive = true; + else if (requestHeaders[i].Value.ToLower() == "close") + webRequest.KeepAlive = false; break; case "range": var startEnd = requestHeaders[i].Value.Replace(Environment.NewLine, "").Remove(0, 6).Split('-'); @@ -359,18 +366,18 @@ private static void SendClientRequestBody(SessionEventArgs args) int bytesToRead; if (args.ProxyRequest.ContentLength < BUFFER_SIZE) { - bytesToRead = (int) args.ProxyRequest.ContentLength; + bytesToRead = (int)args.ProxyRequest.ContentLength; } else bytesToRead = BUFFER_SIZE; - while (totalbytesRead < (int) args.ProxyRequest.ContentLength) + while (totalbytesRead < (int)args.ProxyRequest.ContentLength) { var buffer = args.ClientStreamReader.ReadBytes(bytesToRead); totalbytesRead += buffer.Length; - var remainingBytes = (int) args.ProxyRequest.ContentLength - totalbytesRead; + var remainingBytes = (int)args.ProxyRequest.ContentLength - totalbytesRead; if (remainingBytes < bytesToRead) { bytesToRead = remainingBytes; @@ -414,7 +421,6 @@ private static void SendClientRequestBody(SessionEventArgs args) } } - postStream.Close(); } catch diff --git a/Titanium.Web.Proxy/ResponseHandler.cs b/Titanium.Web.Proxy/ResponseHandler.cs index d41ccdea7..946cbfc7a 100644 --- a/Titanium.Web.Proxy/ResponseHandler.cs +++ b/Titanium.Web.Proxy/ResponseHandler.cs @@ -15,13 +15,12 @@ namespace Titanium.Web.Proxy partial class ProxyServer { //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 { - args.ServerResponse = (HttpWebResponse)args.ProxyRequest.EndGetResponse(asynchronousResult); + args.ServerResponse = (HttpWebResponse)args.ProxyRequest.GetResponse(); } catch (WebException webEx) { @@ -132,6 +131,8 @@ private static void WriteResponseHeaders(StreamWriter responseWriter, List 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 headers, int length, bool isChunked) { + FixProxyHeaders(headers); + if (!isChunked) { if (headers.Any(x => x.Name.ToLower() == "content-length") == false)