Commit 2661ae02 authored by Honfika's avatar Honfika

Http writer refactored

parent a6956ee1
......@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpRequestWriter : HttpWriter
{
public HttpRequestWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
}
}
......
......@@ -7,8 +7,7 @@ namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpResponseWriter : HttpWriter
{
public HttpResponseWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
}
......
using System.Globalization;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
......@@ -9,31 +11,64 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
abstract class HttpWriter : StreamWriter
abstract class HttpWriter
{
protected HttpWriter(Stream stream, int bufferSize, bool leaveOpen)
: base(stream, Encoding.ASCII, bufferSize, leaveOpen)
public int BufferSize { get; }
private readonly Stream stream;
private readonly char[] charBuffer;
private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
protected HttpWriter(Stream stream, int bufferSize)
{
NewLine = ProxyConstants.NewLine;
BufferSize = bufferSize;
// ASCII encoder max byte count is char count + 1
charBuffer = new char[BufferSize - 1];
this.stream = stream;
}
public void WriteHeaders(HeaderCollection headers, bool flush = true)
public Task WriteLineAsync()
{
if (headers != null)
return WriteAsync(newLine);
}
public async Task WriteAsync(string value)
{
int charCount = value.Length;
value.CopyTo(0, charBuffer, 0, charCount);
if (charCount < BufferSize)
{
foreach (var header in headers)
var buffer = BufferPool.GetBuffer(BufferSize);
try
{
header.WriteToStream(this);
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
WriteLine();
if (flush)
else
{
Flush();
var buffer = new byte[charCount + 1];
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
}
}
public async Task WriteLineAsync(string value)
{
await WriteAsync(value);
await WriteLineAsync();
}
/// <summary>
/// Write the headers to client
/// </summary>
......@@ -53,29 +88,28 @@ namespace Titanium.Web.Proxy.Helpers
await WriteLineAsync();
if (flush)
{
await FlushAsync();
// flush the stream
await stream.FlushAsync();
}
}
public async Task WriteAsync(byte[] data, bool flush = false)
{
await FlushAsync();
await BaseStream.WriteAsync(data, 0, data.Length);
await stream.WriteAsync(data, 0, data.Length);
if (flush)
{
// flush the stream and the encoder, too
await FlushAsync();
// flush the stream
await stream.FlushAsync();
}
}
public async Task WriteAsync(byte[] data, int offset, int count, bool flush = false)
{
await FlushAsync();
await BaseStream.WriteAsync(data, offset, count);
await stream.WriteAsync(data, offset, count);
if (flush)
{
// flush the stream and the encoder, too
await FlushAsync();
// flush the stream
await stream.FlushAsync();
}
}
......@@ -85,16 +119,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteBodyAsync(byte[] data, bool isChunked)
internal Task WriteBodyAsync(byte[] data, bool isChunked)
{
if (isChunked)
{
await WriteBodyChunkedAsync(data);
}
else
{
await WriteAsync(data);
return WriteBodyChunkedAsync(data);
}
return WriteAsync(data);
}
/// <summary>
......@@ -105,24 +137,22 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength)
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength)
{
if (isChunked)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
await CopyBodyChunkedAsync(streamReader);
return CopyBodyChunkedAsync(streamReader);
}
else
//http 1.0
if (contentLength == -1)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await CopyBytesFromStream(streamReader, contentLength);
contentLength = long.MaxValue;
}
return CopyBytesFromStream(streamReader, contentLength);
}
/// <summary>
......@@ -175,10 +205,9 @@ namespace Titanium.Web.Proxy.Helpers
}
}
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count)
private Task CopyBytesFromStream(CustomBinaryReader reader, long count)
{
await FlushAsync();
await reader.CopyBytesToStream(BaseStream, count);
return reader.CopyBytesToStream(stream, count);
}
}
}
......@@ -74,53 +74,45 @@ namespace Titanium.Web.Proxy.Http
connection.LastAccess = DateTime.Now;
ServerConnection = connection;
}
/// <summary>
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms, bufferSize))
{
var upstreamProxy = ServerConnection.UpStreamProxy;
var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
//prepare the request & headers
writer.WriteLine(Request.CreateRequestLine(Request.Method,
useUpstreamProxy ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion));
var writer = ServerConnection.StreamWriter;
//prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion));
//Send Authentication to Upstream proxy if needed
if (upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer);
HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer);
}
//write request headers
foreach (var header in Request.Headers)
//Send Authentication to Upstream proxy if needed
if (upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStreamAsync(writer);
}
//write request headers
foreach (var header in Request.Headers)
{
if (header.Name != "Proxy-Authorization")
{
if (header.Name != "Proxy-Authorization")
{
header.WriteToStream(writer);
}
await header.WriteToStreamAsync(writer);
}
writer.WriteLine();
writer.Flush();
requestBytes = ms.ToArray();
}
await ServerConnection.StreamWriter.WriteAsync(requestBytes, true);
await writer.WriteLineAsync();
if (enable100ContinueBehaviour)
{
......
......@@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Models
{
......@@ -61,14 +62,7 @@ namespace Titanium.Web.Proxy.Models
return result;
}
internal void WriteToStream(StreamWriter writer)
{
writer.Write(Name);
writer.Write(": ");
writer.WriteLine(Value);
}
internal async Task WriteToStreamAsync(StreamWriter writer)
internal async Task WriteToStreamAsync(HttpWriter writer)
{
await writer.WriteAsync(Name);
await writer.WriteAsync(": ");
......
......@@ -65,7 +65,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
public void Dispose()
{
StreamReader?.Dispose();
StreamWriter?.Dispose();
Stream?.Dispose();
......
......@@ -68,24 +68,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps))
{
using (var writer = new HttpRequestWriter(stream, server.BufferSize))
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
var writer = new HttpRequestWriter(stream, server.BufferSize);
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
await writer.WriteLineAsync();
await writer.FlushAsync();
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync();
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
string result = await reader.ReadLineAsync();
......
......@@ -613,7 +613,6 @@ namespace Titanium.Web.Proxy
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
clientStream?.Dispose();
......
......@@ -378,16 +378,8 @@ namespace Titanium.Web.Proxy
{
//prepare the prefix content
var requestHeaders = args.WebSession.Request.Headers;
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms, BufferSize))
{
writer.WriteLine(httpCmd);
writer.WriteHeaders(requestHeaders);
requestBytes = ms.ToArray();
}
await connection.StreamWriter.WriteAsync(requestBytes);
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
......@@ -670,57 +662,64 @@ namespace Titanium.Web.Proxy
// try to use the thread static buffer
var buffer = BufferPool.GetBuffer(BufferSize);
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (inputStream.DataAvailable || await inputStream.FillBufferAsync()))
try
{
byte newChar = inputStream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
if (bufferDataLength >= boundaryLength)
while (bytesRead < totalBytesToRead && (inputStream.DataAvailable || await inputStream.FillBufferAsync()))
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-'
&& buffer[bufferDataLength - 2] == '\r' && buffer[bufferDataLength - 1] == '\n')
byte newChar = inputStream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-'
&& buffer[bufferDataLength - 2] == '\r' && buffer[bufferDataLength - 1] == '\n')
{
if (buffer[startIdx + i] != boundary[i])
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
ok = false;
break;
}
}
}
if (ok)
{
break;
}
if (bufferDataLength == buffer.Length)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
await outputStream.WriteAsync(buffer, 0, buffer.Length - bytesToKeep);
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
if (bufferDataLength == buffer.Length)
if (bytesRead > 0)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
await outputStream.WriteAsync(buffer, 0, buffer.Length - bytesToKeep);
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
await outputStream.WriteAsync(buffer, 0, bufferDataLength);
}
}
if (bytesRead > 0)
return bytesRead;
}
finally
{
await outputStream.WriteAsync(buffer, 0, bufferDataLength);
BufferPool.ReturnBuffer(buffer);
}
return bytesRead;
}
}
}
......@@ -109,8 +109,6 @@ namespace Titanium.Web.Proxy
response.IsChunked, response.ContentLength);
}
}
await clientStreamWriter.FlushAsync();
}
catch (Exception e)
{
......
......@@ -14,6 +14,6 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal const string NewLine = "\r\n";
internal static readonly byte[] NewLine = {(byte)'\r', (byte)'\n' };
}
}
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