Commit 2c738ae1 authored by Honfika's avatar Honfika

Make request/response methods more similar

parent 6393b39a
...@@ -139,38 +139,42 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -139,38 +139,42 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
WebSession.Request.EnsureBodyAvailable(false); WebSession.Request.EnsureBodyAvailable(false);
//Caching check var request = WebSession.Request;
if (!WebSession.Request.IsBodyRead)
//If not already read (not cached yet)
if (!request.IsBodyRead)
{ {
//If chunked then its easy just read the whole body with the content length mentioned in the request header //If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
var streamReader = ProxyClient.ClientStreamReader;
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked) if (request.IsChunked)
{ {
await ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream); await streamReader.CopyBytesToStreamChunked(bodyStream);
} }
else else
{ {
//If not chunked then its easy just read the whole body with the content length mentioned in the request header //If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (WebSession.Request.ContentLength > 0) if (request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength); await streamReader.CopyBytesToStream(bodyStream, request.ContentLength);
} }
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if (request.HttpVersion.Major == 1 && request.HttpVersion.Minor == 0)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue); await streamReader.CopyBytesToStream(bodyStream, long.MaxValue);
} }
} }
WebSession.Request.Body = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()); request.Body = await GetDecompressedResponseBody(request.ContentEncoding, bodyStream.ToArray());
} }
//Now set the flag to true //Now set the flag to true
//So that next time we can deliver body from cache //So that next time we can deliver body from cache
WebSession.Request.IsBodyRead = true; request.IsBodyRead = true;
var body = WebSession.Request.Body; var body = request.Body;
OnDataSent(body, 0, body.Length); OnDataSent(body, 0, body.Length);
} }
} }
...@@ -210,43 +214,49 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -210,43 +214,49 @@ namespace Titanium.Web.Proxy.EventArguments
throw new Exception("You cannot read the response body before request is made to server."); throw new Exception("You cannot read the response body before request is made to server.");
} }
var response = WebSession.Response;
//If not already read (not cached yet) //If not already read (not cached yet)
if (!WebSession.Response.IsBodyRead) if (!response.IsBodyRead)
{ {
if (WebSession.Response.HasBody) if (response.HasBody)
{ {
using (var responseBodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
//If chuncked the read chunk by chunk until we hit chunk end symbol var streamReader = WebSession.ServerConnection.StreamReader;
if (WebSession.Response.IsChunked)
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (response.IsChunked)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream); await streamReader.CopyBytesToStreamChunked(bodyStream);
} }
else else
{ {
if (WebSession.Response.ContentLength > 0) //If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (response.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength); await streamReader.CopyBytesToStream(bodyStream, response.ContentLength);
} }
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || else if (response.HttpVersion.Major == 1 && response.HttpVersion.Minor == 0 ||
WebSession.Response.ContentLength == -1) response.ContentLength == -1)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue); await streamReader.CopyBytesToStream(bodyStream, long.MaxValue);
} }
} }
WebSession.Response.Body = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()); response.Body = await GetDecompressedResponseBody(response.ContentEncoding, bodyStream.ToArray());
} }
} }
else else
{ {
WebSession.Response.Body = new byte[0]; response.Body = new byte[0];
} }
//set this to true for caching //Now set the flag to true
WebSession.Response.IsBodyRead = true; //So that next time we can deliver body from cache
var body = WebSession.Response.Body; response.IsBodyRead = true;
var body = response.Body;
OnDataReceived(body, 0, body.Length); OnDataReceived(body, 0, body.Length);
} }
} }
......
...@@ -72,26 +72,26 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -72,26 +72,26 @@ namespace Titanium.Web.Proxy.Extensions
/// <summary> /// <summary>
/// Copies the stream chunked /// Copies the stream chunked
/// </summary> /// </summary>
/// <param name="clientStreamReader"></param> /// <param name="reader"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream) internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader reader, Stream stream)
{ {
while (true) while (true)
{ {
string chuchkHead = await clientStreamReader.ReadLineAsync(); string chunkHead = await reader.ReadLineAsync();
int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0) if (chunkSize != 0)
{ {
await CopyBytesToStream(clientStreamReader, stream, chunkSize); await CopyBytesToStream(reader, stream, chunkSize);
//chunk trail
await clientStreamReader.ReadLineAsync();
} }
else
//chunk trail
await reader.ReadLineAsync();
if (chunkSize == 0)
{ {
await clientStreamReader.ReadLineAsync();
break; break;
} }
} }
......
...@@ -40,96 +40,5 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -40,96 +40,5 @@ namespace Titanium.Web.Proxy.Helpers
{ {
return WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}"); return WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
} }
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(byte[] data, bool isChunked)
{
if (!isChunked)
{
await BaseStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunkedAsync(data);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task CopyBodyAsync(CustomBinaryReader inStreamReader, bool isChunked, long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await inStreamReader.CopyBytesToStream(BaseStream, contentLength);
}
else
{
await CopyBodyChunkedAsync(inStreamReader);
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private async Task WriteResponseBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead);
await WriteLineAsync();
await WriteAsync(data);
await WriteLineAsync();
await WriteLineAsync("0");
await WriteLineAsync();
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader inStreamReader)
{
while (true)
{
string chunkHead = await inStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead);
if (chunkSize != 0)
{
await inStreamReader.CopyBytesToStream(BaseStream, chunkSize);
}
await WriteLineAsync();
await inStreamReader.ReadLineAsync();
if (chunkSize == 0)
{
break;
}
}
}
} }
} }
using System.IO; using System.Globalization;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -64,5 +67,111 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,5 +67,111 @@ namespace Titanium.Web.Proxy.Helpers
await FlushAsync(); await FlushAsync();
} }
} }
public async Task WriteAsync(byte[] data, int offset, int count, bool flush = false)
{
await FlushAsync();
await BaseStream.WriteAsync(data, offset, count);
if (flush)
{
// flush the stream and the encoder, too
await FlushAsync();
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteBodyAsync(byte[] data, bool isChunked)
{
if (!isChunked)
{
await BaseStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteBodyChunkedAsync(data);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await streamReader.CopyBytesToStream(BaseStream, contentLength);
}
else
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
await CopyBodyChunkedAsync(streamReader);
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private async Task WriteBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead);
await WriteLineAsync();
await WriteAsync(data);
await WriteLineAsync();
await WriteLineAsync("0");
await WriteLineAsync();
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead);
if (chunkSize != 0)
{
await reader.CopyBytesToStream(BaseStream, chunkSize);
}
await WriteLineAsync();
//chunk trail
await reader.ReadLineAsync();
if (chunkSize == 0)
{
break;
}
}
}
} }
} }
...@@ -125,9 +125,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -125,9 +125,7 @@ namespace Titanium.Web.Proxy.Http
requestBytes = ms.ToArray(); requestBytes = ms.ToArray();
} }
var stream = ServerConnection.Stream; await ServerConnection.StreamWriter.WriteAsync(requestBytes, true);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (enable100ContinueBehaviour) if (enable100ContinueBehaviour)
{ {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
...@@ -38,6 +39,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -38,6 +39,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal CustomBinaryReader StreamReader { get; set; } internal CustomBinaryReader StreamReader { get; set; }
internal HttpRequestWriter StreamWriter { get; set; }
/// <summary> /// <summary>
/// Server stream /// Server stream
/// </summary> /// </summary>
...@@ -61,6 +64,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -61,6 +64,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Stream?.Dispose(); Stream?.Dispose();
StreamReader?.Dispose(); StreamReader?.Dispose();
StreamWriter?.Dispose();
try try
{ {
......
...@@ -130,6 +130,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -130,6 +130,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
UseUpstreamProxy = useUpstreamProxy, UseUpstreamProxy = useUpstreamProxy,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamReader = new CustomBinaryReader(stream, server.BufferSize),
StreamWriter = new HttpRequestWriter(stream, server.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
......
...@@ -171,8 +171,7 @@ namespace Titanium.Web.Proxy ...@@ -171,8 +171,7 @@ namespace Titanium.Web.Proxy
//send the buffered data //send the buffered data
var data = new byte[clientStream.Available]; var data = new byte[clientStream.Available];
await clientStream.ReadAsync(data, 0, data.Length); await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length); await connection.StreamWriter.WriteAsync(data, true);
await connection.Stream.FlushAsync();
} }
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream); var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);
...@@ -388,7 +387,7 @@ namespace Titanium.Web.Proxy ...@@ -388,7 +387,7 @@ namespace Titanium.Web.Proxy
requestBytes = ms.ToArray(); requestBytes = ms.ToArray();
} }
await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length); 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);
...@@ -478,15 +477,17 @@ namespace Titanium.Web.Proxy ...@@ -478,15 +477,17 @@ namespace Titanium.Web.Proxy
//If 100 continue was the response inform that to the client //If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour) if (Enable100ContinueBehaviour)
{ {
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (request.Is100Continue) if (request.Is100Continue)
{ {
await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
else if (request.ExpectationFailed) else if (request.ExpectationFailed)
{ {
await args.ProxyClient.ClientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
} }
...@@ -513,8 +514,7 @@ namespace Titanium.Web.Proxy ...@@ -513,8 +514,7 @@ namespace Titanium.Web.Proxy
//chunked send is not supported as of now //chunked send is not supported as of now
request.ContentLength = body.Length; request.ContentLength = body.Length;
var newStream = args.WebSession.ServerConnection.Stream; await args.WebSession.ServerConnection.StreamWriter.WriteAsync(body);
await newStream.WriteAsync(body, 0, body.Length);
} }
else else
{ {
...@@ -626,45 +626,37 @@ namespace Titanium.Web.Proxy ...@@ -626,45 +626,37 @@ namespace Titanium.Web.Proxy
private async Task SendClientRequestBody(SessionEventArgs args) private async Task SendClientRequestBody(SessionEventArgs args)
{ {
// End the operation // End the operation
var postStream = args.WebSession.ServerConnection.Stream; var request = args.WebSession.Request;
//send the request body bytes to server //send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0) if (request.ContentLength > 0 && args.HasMulipartEventSubscribers && request.IsMultipartFormData)
{ {
var request = args.WebSession.Request; string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
if (args.HasMulipartEventSubscribers && request.IsMultipartFormData)
{
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
long bytesToSend = args.WebSession.Request.ContentLength; long bytesToSend = request.ContentLength;
while (bytesToSend > 0) while (bytesToSend > 0)
{
var outputStream = args.WebSession.ServerConnection.StreamWriter;
long read = await SendUntilBoundaryAsync(args.ProxyClient.ClientStream, outputStream, bytesToSend, boundary);
if (read == 0)
{ {
long read = await SendUntilBoundaryAsync(args.ProxyClient.ClientStream, postStream, bytesToSend, boundary); break;
if (read == 0) }
{
break;
}
bytesToSend -= read; bytesToSend -= read;
if (bytesToSend > 0) if (bytesToSend > 0)
{ {
var headers = new HeaderCollection(); var headers = new HeaderCollection();
var stream = new CustomBufferedPeekStream(args.ProxyClient.ClientStream); var stream = new CustomBufferedPeekStream(args.ProxyClient.ClientStream);
await HeaderParser.ReadHeaders(new CustomBinaryReader(stream, BufferSize), headers); await HeaderParser.ReadHeaders(new CustomBinaryReader(stream, BufferSize), headers);
args.OnMultipartRequestPartSent(boundary, headers); args.OnMultipartRequestPartSent(boundary, headers);
}
} }
} }
else
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
}
} }
//Need to revist, find any potential bugs else
//send the request body bytes to server in chunks
else if (args.WebSession.Request.IsChunked)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream); await args.WebSession.ServerConnection.StreamWriter.CopyBodyAsync(args.ProxyClient.ClientStreamReader,
request.IsChunked, request.ContentLength);
} }
} }
...@@ -672,7 +664,7 @@ namespace Titanium.Web.Proxy ...@@ -672,7 +664,7 @@ namespace Titanium.Web.Proxy
/// Read a line from the byte stream /// Read a line from the byte stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<long> SendUntilBoundaryAsync(CustomBufferedStream inputStream, CustomBufferedStream outputStream, long totalBytesToRead, string boundary) private async Task<long> SendUntilBoundaryAsync(CustomBufferedStream inputStream, HttpRequestWriter outputStream, long totalBytesToRead, string boundary)
{ {
int bufferDataLength = 0; int bufferDataLength = 0;
......
...@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy ...@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
} }
await clientStreamWriter.WriteHeadersAsync(response.Headers); await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteResponseBodyAsync(response.Body, isChunked); await clientStreamWriter.WriteBodyAsync(response.Body, isChunked);
} }
else else
{ {
......
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