Commit f69bed23 authored by titanium007's avatar titanium007

Fix connection cache

parent 8242536d
......@@ -93,8 +93,8 @@ namespace Titanium.Web.Proxy.EventArguments
//if (ProxyRequest != null)
// ProxyRequest.Abort();
if (ResponseStream != null)
ResponseStream.Dispose();
//if (ResponseStream != null)
// ResponseStream.Dispose();
//if (ServerResponse != null)
// ServerResponse.Close();
......
......@@ -81,36 +81,42 @@ namespace Titanium.Web.Proxy.Http
public Request Request { get; set; }
public Response Response { get; set; }
public TcpClient Client { get; set; }
public TcpConnection Client { get; set; }
public void SetConnection(TcpConnection Connection)
{
Client = Connection;
ServerStreamReader = Client.ServerStreamReader;
}
public HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
public CustomBinaryReader ServerStreamReader { get; set; }
public async Task SendRequest()
{
Stream stream = Client.GetStream();
Stream stream = Client.Stream;
StringBuilder requestLines = new StringBuilder();
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.AbsolutePath,
this.Request.RequestUri.PathAndQuery,
this.Request.Version
}));
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
}
requestLines.AppendLine();
requestLines.AppendLine();
......@@ -122,9 +128,7 @@ namespace Titanium.Web.Proxy.Http
public void ReceiveResponse()
{
Stream stream = Client.GetStream();
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII);
var httpResult = ServerStreamReader.ReadLine().Split(' ');
var httpResult = ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3);
var httpVersion = httpResult[0];
......@@ -141,17 +145,14 @@ namespace Titanium.Web.Proxy.Http
this.Response.ResponseProtocolVersion = version;
this.Response.ResponseStatusCode = httpResult[1];
string status = httpResult[2];
for (int i = 3; i < httpResult.Length; i++)
{
status = status + Space + httpResult[i];
}
this.Response.ResponseStatusDescription = status;
List<string> responseLines = ServerStreamReader.ReadAllLines();
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(':');
string[] strArray = responseLines[index].Split(new char[] { ':' }, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
}
}
......
......@@ -7,25 +7,32 @@ using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Http
{
public class TcpConnection
{
public TcpClient Client { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; }
public Stream Stream { get; set; }
}
internal class TcpConnectionManager
{
static ConcurrentDictionary<string, ConcurrentStack<TcpClient>> ConnectionCache = new ConcurrentDictionary<string, ConcurrentStack<TcpClient>>();
static ConcurrentDictionary<string, ConcurrentStack<TcpConnection>> ConnectionCache = new ConcurrentDictionary<string, ConcurrentStack<TcpConnection>>();
public static async Task<TcpClient> GetClient(string Hostname, int port, bool IsSecure)
public static async Task<TcpConnection> GetClient(string Hostname, int port, bool IsSecure)
{
var key = string.Concat(Hostname, ":", port, ":", IsSecure);
ConcurrentStack<TcpClient> connections;
ConcurrentStack<TcpConnection> connections;
if (!ConnectionCache.TryGetValue(key, out connections))
{
return await CreateClient(Hostname, port, IsSecure);
}
TcpClient client;
TcpConnection client;
if (!connections.TryPop(out client))
{
return await CreateClient(Hostname, port, IsSecure);
......@@ -33,7 +40,7 @@ namespace Titanium.Web.Proxy.Http
return client;
}
private static async Task<TcpClient> CreateClient(string Hostname, int port, bool IsSecure)
private static async Task<TcpConnection> CreateClient(string Hostname, int port, bool IsSecure)
{
var client = new TcpClient(Hostname, port);
var stream = (Stream)client.GetStream();
......@@ -54,17 +61,18 @@ namespace Titanium.Web.Proxy.Http
throw;
}
}
return client;
return new TcpConnection() { Client = client, ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII), Stream = stream };
}
public static void AddClient(string Hostname, int port, bool IsSecure, TcpClient Client)
public static void AddClient(string Hostname, int port, bool IsSecure, TcpConnection Client)
{
var key = string.Concat(Hostname, ":", port, ":", IsSecure);
ConcurrentStack<TcpClient> connections;
ConcurrentStack<TcpConnection> connections;
if (!ConnectionCache.TryGetValue(key, out connections))
{
connections = new ConcurrentStack<TcpClient>();
connections = new ConcurrentStack<TcpConnection>();
connections.Push(Client);
ConnectionCache.TryAdd(key, connections);
}
......
......@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy
private static readonly Regex CookieSplitRegEx = new Regex(@",(?! )");
private static readonly byte[] ChunkTrail = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
......
......@@ -30,6 +30,7 @@ namespace Titanium.Web.Proxy
try
{
//read the first line HTTP command
var httpCmd = clientStreamReader.ReadLine();
if (string.IsNullOrEmpty(httpCmd))
......@@ -156,7 +157,7 @@ namespace Titanium.Web.Proxy
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
{
var header = tmpLine.Split(ColonSpaceSplit, 2, StringSplitOptions.None);
var header = tmpLine.Split(new char[] { ':' }, 2);
args.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
}
......@@ -175,8 +176,9 @@ namespace Titanium.Web.Proxy
}
}
//construct the web request that we are going to issue on behalf of the client.
args.ProxySession = new Http.HttpWebSession();
args.ProxySession.Request.RequestUri = httpRemoteUri;
//args.ProxyRequest.Proxy = null;
......@@ -197,8 +199,8 @@ namespace Titanium.Web.Proxy
//args.RequestIsAlive = args.ProxyRequest.KeepAlive;
//args.ProxyRequest.AllowWriteStreamBuffering = true;
args.Client = await TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps);
args.ProxySession.Client = args.Client;
//If requested interception
if (BeforeRequest != null)
{
......@@ -215,7 +217,9 @@ namespace Titanium.Web.Proxy
}
SetRequestHeaders(args.RequestHeaders, args.ProxySession);
//construct the web request that we are going to issue on behalf of the client.
var connection = await TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps);
args.ProxySession.SetConnection(connection);
await args.ProxySession.SendRequest();
//If request was modified by user
......@@ -234,7 +238,7 @@ namespace Titanium.Web.Proxy
}
}
await HandleHttpSessionResponse(args);
HandleHttpSessionResponse(args);
//if connection is closing exit
if (args.ResponseHeaders.Any(x => x.Name.ToLower() == "connection" && x.Value.ToLower() == "close"))
......@@ -243,7 +247,7 @@ namespace Titanium.Web.Proxy
return;
}
//TcpConnectionManager.AddClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps, args.ProxySession.Client);
TcpConnectionManager.AddClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps, args.ProxySession.Client);
// read the next request
httpCmd = clientStreamReader.ReadLine();
......@@ -351,10 +355,27 @@ namespace Titanium.Web.Proxy
break;
}
}
FixRequestProxyHeaders(requestHeaders);
webRequest.Request.RequestHeaders = requestHeaders;
}
private static void FixRequestProxyHeaders(List<HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
var connectionheader = headers.FirstOrDefault(x => x.Name.ToLower() == "connection");
if (proxyHeader != null)
if (connectionheader == null)
{
headers.Add(new HttpHeader("connection", proxyHeader.Value));
}
else
{
connectionheader.Value = proxyHeader.Value;
}
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
//This is called when the request is PUT/POST to read the body
private static void SendClientRequestBody(SessionEventArgs args)
{
......
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
......@@ -17,7 +18,7 @@ namespace Titanium.Web.Proxy
partial class ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
private static async Task HandleHttpSessionResponse(SessionEventArgs args)
private static void HandleHttpSessionResponse(SessionEventArgs args)
{
args.ProxySession.ReceiveResponse();
......@@ -63,12 +64,14 @@ namespace Titanium.Web.Proxy
}
else
{
// var isChunked = args.ProxySession.ResponseHeaders.Any(x => x.Name.ToLower() == "transfer-encoding" && x.Value.ToLower().Contains("chunked"));
var isChunked = args.ProxySession.Response.ResponseHeaders.Any(x => x.Name.ToLower() == "transfer-encoding" && x.Value.ToLower().Contains("chunked"));
WriteResponseStatus(args.ProxySession.Response.ResponseProtocolVersion, args.ProxySession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, args.ResponseHeaders);
WriteResponseBody(args.ResponseStream, args.ClientStream, false, args.ProxySession.Response.ContentLength);
if (isChunked || args.ProxySession.Response.ContentLength > 0)
WriteResponseBody(args.ResponseStream, args.ClientStream, isChunked, args.ProxySession.Response.ContentLength);
}
args.ClientStream.Flush();
......@@ -90,6 +93,10 @@ namespace Titanium.Web.Proxy
{
switch (response.Response.ResponseHeaders[i].Name.ToLower())
{
case "content-length":
response.Response.ContentLength = int.Parse(response.Response.ResponseHeaders[i].Value);
break;
case "content-encoding":
response.Response.ResponseContentEncoding = response.Response.ResponseHeaders[i].Value;
break;
......@@ -105,14 +112,10 @@ namespace Titanium.Web.Proxy
break;
case "connection":
break;
default:
break;
}
}
// response.ResponseHeaders.RemoveAll(x => x.Name.ToLower() == "connection");
return response.Response.ResponseHeaders;
}
......@@ -128,7 +131,7 @@ namespace Titanium.Web.Proxy
{
if (headers != null)
{
//FixProxyHeaders(headers);
FixResponseProxyHeaders(headers);
foreach (var header in headers)
{
......@@ -139,23 +142,29 @@ namespace Titanium.Web.Proxy
responseWriter.WriteLine();
responseWriter.Flush();
}
private static void FixProxyHeaders(List<HttpHeader> headers)
private static void FixResponseProxyHeaders(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)
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
var connectionHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "connection");
if (proxyHeader != null)
if (connectionHeader == null)
{
headers.Add(new HttpHeader("connection", "close"));
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
headers.Add(new HttpHeader("connection", proxyHeader.Value));
}
else
headers.Find(x => x.Name.ToLower() == "connection").Value = "close";
{
connectionHeader.Value = "close";
}
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length,
bool isChunked)
{
FixProxyHeaders(headers);
FixResponseProxyHeaders(headers);
if (!isChunked)
{
......@@ -194,12 +203,27 @@ namespace Titanium.Web.Proxy
{
if (!isChunked)
{
int bytesToRead = BUFFER_SIZE;
if (BodyLength < BUFFER_SIZE)
bytesToRead = BodyLength;
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inStream.Read(buffer, 0, buffer.Length)) > 0)
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += inStream.Read(buffer, 0, bytesToRead)) > 0)
{
outStream.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == BodyLength)
break;
bytesRead = 0;
var remainingBytes = (BodyLength - totalBytesRead);
bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes;
}
}
else
......@@ -209,20 +233,35 @@ namespace Titanium.Web.Proxy
//Send chunked response
private static void WriteResponseBodyChunked(Stream inStream, Stream outStream)
{
var buffer = new byte[BUFFER_SIZE];
var inStreamReader = new CustomBinaryReader(inStream, Encoding.ASCII);
while (true)
{
var chuchkHead = inStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
int bytesRead;
while ((bytesRead = inStream.Read(buffer, 0, buffer.Length)) > 0)
if (chunkSize != 0)
{
var chunkHead = Encoding.ASCII.GetBytes(bytesRead.ToString("x2"));
var buffer = inStreamReader.ReadBytes(chunkSize);
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(buffer, 0, bytesRead);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
}
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(buffer, 0, chunkSize);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
inStreamReader.ReadLine();
}
else
{
inStreamReader.ReadLine();
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
break;
}
}
}
private static void WriteResponseBodyChunked(byte[] data, Stream outStream)
......@@ -230,9 +269,9 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(data, 0, data.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
}
......
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