Commit 2661ae02 authored by Honfika's avatar Honfika

Http writer refactored

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