@@ -138,14 +144,14 @@ public override int Read(byte[] buffer, int offset, int count)
///
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///
- /// An array of bytes. This method copies bytes from to the current stream.
- /// The zero-based byte offset in at which to begin copying bytes to the current stream.
+ /// An array of bytes. This method copies count bytes from buffer to the current stream.
+ /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream.
/// The number of bytes to be written to the current stream.
[DebuggerStepThrough]
public override void Write(byte[] buffer, int offset, int count)
{
OnDataWrite(buffer, offset, count);
- BaseStream.Write(buffer, offset, count);
+ baseStream.Write(buffer, offset, count);
}
///
@@ -178,7 +184,7 @@ public override async Task CopyToAsync(Stream destination, int bufferSize, Cance
///
public override Task FlushAsync(CancellationToken cancellationToken)
{
- return BaseStream.FlushAsync(cancellationToken);
+ return baseStream.FlushAsync(cancellationToken);
}
///
@@ -378,18 +384,14 @@ public byte ReadByteFromBuffer()
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
///
/// The buffer to write data from.
- /// The zero-based byte offset in from which to begin copying bytes to the stream.
+ /// The zero-based byte offset in buffer from which to begin copying bytes to the stream.
/// The maximum number of bytes to write.
- /// The token to monitor for cancellation requests. The default value is .
- ///
- /// A task that represents the asynchronous write operation.
- ///
+ /// The token to monitor for cancellation requests. The default value is .
[DebuggerStepThrough]
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
OnDataWrite(buffer, offset, count);
-
- await BaseStream.WriteAsync(buffer, offset, count, cancellationToken);
+ await baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
///
@@ -403,7 +405,7 @@ public override void WriteByte(byte value)
{
buffer[0] = value;
OnDataWrite(buffer, 0, 1);
- BaseStream.Write(buffer, 0, 1);
+ baseStream.Write(buffer, 0, 1);
}
finally
{
@@ -433,7 +435,7 @@ protected override void Dispose(bool disposing)
closed = true;
if (!leaveOpen)
{
- BaseStream.Dispose();
+ baseStream.Dispose();
}
bufferPool.ReturnBuffer(streamBuffer);
@@ -443,27 +445,27 @@ protected override void Dispose(bool disposing)
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
///
- public override bool CanRead => BaseStream.CanRead;
+ public override bool CanRead => baseStream.CanRead;
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
///
- public override bool CanSeek => BaseStream.CanSeek;
+ public override bool CanSeek => baseStream.CanSeek;
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
///
- public override bool CanWrite => BaseStream.CanWrite;
+ public override bool CanWrite => baseStream.CanWrite;
///
/// Gets a value that determines whether the current stream can time out.
///
- public override bool CanTimeout => BaseStream.CanTimeout;
+ public override bool CanTimeout => baseStream.CanTimeout;
///
/// When overridden in a derived class, gets the length in bytes of the stream.
///
- public override long Length => BaseStream.Length;
+ public override long Length => baseStream.Length;
///
/// Gets a value indicating whether data is available.
@@ -480,8 +482,8 @@ protected override void Dispose(bool disposing)
///
public override long Position
{
- get => BaseStream.Position;
- set => BaseStream.Position = value;
+ get => baseStream.Position;
+ set => baseStream.Position = value;
}
///
@@ -489,8 +491,8 @@ public override long Position
///
public override int ReadTimeout
{
- get => BaseStream.ReadTimeout;
- set => BaseStream.ReadTimeout = value;
+ get => baseStream.ReadTimeout;
+ set => baseStream.ReadTimeout = value;
}
///
@@ -498,8 +500,8 @@ public override int ReadTimeout
///
public override int WriteTimeout
{
- get => BaseStream.WriteTimeout;
- set => BaseStream.WriteTimeout = value;
+ get => baseStream.WriteTimeout;
+ set => baseStream.WriteTimeout = value;
}
///
@@ -524,7 +526,7 @@ public bool FillBuffer()
bool result = false;
try
{
- int readBytes = BaseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
+ int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
result = readBytes > 0;
if (result)
{
@@ -574,7 +576,7 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken
bool result = false;
try
{
- int readBytes = await BaseStream.ReadAsync(streamBuffer, bufferLength, bytesToRead, cancellationToken);
+ int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, bytesToRead, cancellationToken);
result = readBytes > 0;
if (result)
{
@@ -582,6 +584,11 @@ public async ValueTask FillBufferAsync(CancellationToken cancellationToken
bufferLength += readBytes;
}
}
+ catch
+ {
+ if (!swallowException)
+ throw;
+ }
finally
{
if (!result)
@@ -721,7 +728,7 @@ public override int EndRead(IAsyncResult asyncResult)
return ((TaskResult)asyncResult).Result;
}
-
+
///
/// Fix the .net bug with SslStream slow WriteAsync
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495
@@ -756,5 +763,292 @@ public override void EndWrite(IAsyncResult asyncResult)
((TaskResult)asyncResult).GetResult();
}
+
+ ///
+ /// Writes a line async
+ ///
+ /// Optional cancellation token for this async task.
+ ///
+ internal Task WriteLineAsync(CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(newLine, cancellationToken: cancellationToken);
+ }
+
+ internal Task WriteAsync(string value, CancellationToken cancellationToken = default)
+ {
+ return writeAsyncInternal(value, false, cancellationToken);
+ }
+
+ private async Task writeAsyncInternal(string value, bool addNewLine, CancellationToken cancellationToken)
+ {
+ int newLineChars = addNewLine ? newLine.Length : 0;
+ int charCount = value.Length;
+ if (charCount < bufferPool.BufferSize - newLineChars)
+ {
+ var buffer = bufferPool.GetBuffer();
+ try
+ {
+ int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
+ if (newLineChars > 0)
+ {
+ Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
+ idx += newLineChars;
+ }
+
+ await baseStream.WriteAsync(buffer, 0, idx, cancellationToken);
+ }
+ finally
+ {
+ bufferPool.ReturnBuffer(buffer);
+ }
+ }
+ else
+ {
+ var buffer = new byte[charCount + newLineChars + 1];
+ int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
+ if (newLineChars > 0)
+ {
+ Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
+ idx += newLineChars;
+ }
+
+ await baseStream.WriteAsync(buffer, 0, idx, cancellationToken);
+ }
+ }
+
+ internal Task WriteLineAsync(string value, CancellationToken cancellationToken = default)
+ {
+ return writeAsyncInternal(value, true, cancellationToken);
+ }
+
+ ///
+ /// Write the headers to client
+ ///
+ ///
+ ///
+ ///
+ internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
+ {
+ var buffer = headerBuilder.GetBuffer();
+ await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
+ }
+
+ ///
+ /// Writes the data to the stream.
+ ///
+ /// The data.
+ /// Should we flush after write?
+ /// The cancellation token.
+ internal async Task WriteAsync(byte[] data, bool flush = false, CancellationToken cancellationToken = default)
+ {
+ await baseStream.WriteAsync(data, 0, data.Length, cancellationToken);
+ if (flush)
+ {
+ await baseStream.FlushAsync(cancellationToken);
+ }
+ }
+
+ internal async Task WriteAsync(byte[] data, int offset, int count, bool flush,
+ CancellationToken cancellationToken = default)
+ {
+ await baseStream.WriteAsync(data, offset, count, cancellationToken);
+ if (flush)
+ {
+ await baseStream.FlushAsync(cancellationToken);
+ }
+ }
+
+ ///
+ /// Writes the byte array body to the stream; optionally chunked
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken cancellationToken)
+ {
+ if (isChunked)
+ {
+ return writeBodyChunkedAsync(data, cancellationToken);
+ }
+
+ return WriteAsync(data, cancellationToken: cancellationToken);
+ }
+
+ ///
+ /// Copies the specified content length number of bytes to the output stream from the given inputs stream
+ /// optionally chunked
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task CopyBodyAsync(IHttpStreamReader streamReader, bool isChunked, long contentLength,
+ Action? onCopy, CancellationToken cancellationToken)
+ {
+ // For chunked request we need to read data as they arrive, until we reach a chunk end symbol
+ if (isChunked)
+ {
+ return copyBodyChunkedAsync(streamReader, onCopy, cancellationToken);
+ }
+
+ // http 1.0 or the stream reader limits the stream
+ if (contentLength == -1)
+ {
+ contentLength = long.MaxValue;
+ }
+
+ // If not chunked then its easy just read the amount of bytes mentioned in content length header
+ return copyBytesFromStream(streamReader, contentLength, onCopy, cancellationToken);
+ }
+
+ ///
+ /// Copies the given input bytes to output stream chunked
+ ///
+ ///
+ ///
+ ///
+ private async Task writeBodyChunkedAsync(byte[] data, CancellationToken cancellationToken)
+ {
+ var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
+
+ await WriteAsync(chunkHead, cancellationToken: cancellationToken);
+ await WriteLineAsync(cancellationToken);
+ await WriteAsync(data, cancellationToken: cancellationToken);
+ await WriteLineAsync(cancellationToken);
+
+ await WriteLineAsync("0", cancellationToken);
+ await WriteLineAsync(cancellationToken);
+ }
+
+ ///
+ /// Copies the streams chunked
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task copyBodyChunkedAsync(IHttpStreamReader reader, Action? onCopy, CancellationToken cancellationToken)
+ {
+ while (true)
+ {
+ string chunkHead = (await reader.ReadLineAsync(cancellationToken))!;
+ int idx = chunkHead.IndexOf(";");
+ if (idx >= 0)
+ {
+ chunkHead = chunkHead.Substring(0, idx);
+ }
+
+ int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
+
+ await WriteLineAsync(chunkHead, cancellationToken);
+
+ if (chunkSize != 0)
+ {
+ await copyBytesFromStream(reader, chunkSize, onCopy, cancellationToken);
+ }
+
+ await WriteLineAsync(cancellationToken);
+
+ // chunk trail
+ await reader.ReadLineAsync(cancellationToken);
+
+ if (chunkSize == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Copies the specified bytes to the stream from the input stream
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task copyBytesFromStream(IHttpStreamReader reader, long count, Action? onCopy,
+ CancellationToken cancellationToken)
+ {
+ var buffer = bufferPool.GetBuffer();
+
+ try
+ {
+ long remainingBytes = count;
+
+ while (remainingBytes > 0)
+ {
+ int bytesToRead = buffer.Length;
+ if (remainingBytes < bytesToRead)
+ {
+ bytesToRead = (int)remainingBytes;
+ }
+
+ int bytesRead = await reader.ReadAsync(buffer, 0, bytesToRead, cancellationToken);
+ if (bytesRead == 0)
+ {
+ break;
+ }
+
+ remainingBytes -= bytesRead;
+
+ await baseStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
+
+ onCopy?.Invoke(buffer, 0, bytesRead);
+ }
+ }
+ finally
+ {
+ bufferPool.ReturnBuffer(buffer);
+ }
+ }
+
+ ///
+ /// Writes the request/response headers and body.
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
+ {
+ var body = requestResponse.CompressBodyAndUpdateContentLength();
+ headerBuilder.WriteHeaders(requestResponse.Headers);
+ await WriteHeadersAsync(headerBuilder, cancellationToken);
+
+ if (body != null)
+ {
+ await WriteBodyAsync(body, requestResponse.IsChunked, cancellationToken);
+ }
+ }
+
+#if NETSTANDARD2_1
+ ///
+ /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
+ ///
+ /// The buffer to write data from.
+ /// The token to monitor for cancellation requests. The default value is .
+ /// A task that represents the asynchronous write operation.
+ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
+ {
+ return baseStream.WriteAsync(buffer, cancellationToken);
+ }
+#else
+ ///
+ /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
+ ///
+ /// The buffer to write data from.
+ /// The token to monitor for cancellation requests. The default value is .
+ /// A task that represents the asynchronous write operation.
+ public Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken)
+ {
+ var buf = ArrayPool.Shared.Rent(buffer.Length);
+ buffer.CopyTo(buf);
+ return baseStream.WriteAsync(buf, 0, buf.Length, cancellationToken);
+ }
+#endif
}
}
diff --git a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs b/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs
deleted file mode 100644
index 62e9a8a60..000000000
--- a/src/Titanium.Web.Proxy/Helpers/HttpWriter.cs
+++ /dev/null
@@ -1,346 +0,0 @@
-using System;
-using System.Buffers;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Titanium.Web.Proxy.Http;
-using Titanium.Web.Proxy.Models;
-using Titanium.Web.Proxy.Shared;
-using Titanium.Web.Proxy.StreamExtended.BufferPool;
-using Titanium.Web.Proxy.StreamExtended.Network;
-
-namespace Titanium.Web.Proxy.Helpers
-{
- internal class HttpWriter : ICustomStreamWriter
- {
- private readonly Stream stream;
- private readonly IBufferPool bufferPool;
-
- private static readonly byte[] newLine = ProxyConstants.NewLineBytes;
-
- private static Encoding encoding => HttpHeader.Encoding;
-
- internal HttpWriter(Stream stream, IBufferPool bufferPool)
- {
- this.stream = stream;
- this.bufferPool = bufferPool;
- }
-
- ///
- /// Writes a line async
- ///
- /// Optional cancellation token for this async task.
- ///
- internal Task WriteLineAsync(CancellationToken cancellationToken = default)
- {
- return WriteAsync(newLine, cancellationToken: cancellationToken);
- }
-
- internal Task WriteAsync(string value, CancellationToken cancellationToken = default)
- {
- return writeAsyncInternal(value, false, cancellationToken);
- }
-
- private async Task writeAsyncInternal(string value, bool addNewLine, CancellationToken cancellationToken)
- {
- int newLineChars = addNewLine ? newLine.Length : 0;
- int charCount = value.Length;
- if (charCount < bufferPool.BufferSize - newLineChars)
- {
- var buffer = bufferPool.GetBuffer();
- try
- {
- int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
- if (newLineChars > 0)
- {
- Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
- idx += newLineChars;
- }
-
- await stream.WriteAsync(buffer, 0, idx, cancellationToken);
- }
- finally
- {
- bufferPool.ReturnBuffer(buffer);
- }
- }
- else
- {
- var buffer = new byte[charCount + newLineChars + 1];
- int idx = encoding.GetBytes(value, 0, charCount, buffer, 0);
- if (newLineChars > 0)
- {
- Buffer.BlockCopy(newLine, 0, buffer, idx, newLineChars);
- idx += newLineChars;
- }
-
- await stream.WriteAsync(buffer, 0, idx, cancellationToken);
- }
- }
-
- internal Task WriteLineAsync(string value, CancellationToken cancellationToken = default)
- {
- return writeAsyncInternal(value, true, cancellationToken);
- }
-
- ///
- /// Write the headers to client
- ///
- ///
- ///
- ///
- internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
- {
- var buffer = headerBuilder.GetBuffer();
- await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
- }
-
- ///
- /// Writes the data to the stream.
- ///
- /// The data.
- /// Should we flush after write?
- /// The cancellation token.
- internal async Task WriteAsync(byte[] data, bool flush = false, CancellationToken cancellationToken = default)
- {
- await stream.WriteAsync(data, 0, data.Length, cancellationToken);
- if (flush)
- {
- await stream.FlushAsync(cancellationToken);
- }
- }
-
- internal async Task WriteAsync(byte[] data, int offset, int count, bool flush,
- CancellationToken cancellationToken = default)
- {
- await stream.WriteAsync(data, offset, count, cancellationToken);
- if (flush)
- {
- await stream.FlushAsync(cancellationToken);
- }
- }
-
- ///
- /// Writes the byte array body to the stream; optionally chunked
- ///
- ///
- ///
- ///
- ///
- internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken cancellationToken)
- {
- if (isChunked)
- {
- return writeBodyChunkedAsync(data, cancellationToken);
- }
-
- return WriteAsync(data, cancellationToken: cancellationToken);
- }
-
- ///
- /// Copies the specified content length number of bytes to the output stream from the given inputs stream
- /// optionally chunked
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- internal Task CopyBodyAsync(CustomBufferedStream streamReader, bool isChunked, long contentLength,
- Action? onCopy, CancellationToken cancellationToken)
- {
- // For chunked request we need to read data as they arrive, until we reach a chunk end symbol
- if (isChunked)
- {
- return copyBodyChunkedAsync(streamReader, onCopy, cancellationToken);
- }
-
- // http 1.0 or the stream reader limits the stream
- if (contentLength == -1)
- {
- contentLength = long.MaxValue;
- }
-
- // If not chunked then its easy just read the amount of bytes mentioned in content length header
- return copyBytesFromStream(streamReader, contentLength, onCopy, cancellationToken);
- }
-
- ///
- /// Copies the given input bytes to output stream chunked
- ///
- ///
- ///
- ///
- private async Task writeBodyChunkedAsync(byte[] data, CancellationToken cancellationToken)
- {
- var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
-
- await WriteAsync(chunkHead, cancellationToken: cancellationToken);
- await WriteLineAsync(cancellationToken);
- await WriteAsync(data, cancellationToken: cancellationToken);
- await WriteLineAsync(cancellationToken);
-
- await WriteLineAsync("0", cancellationToken);
- await WriteLineAsync(cancellationToken);
- }
-
- ///
- /// Copies the streams chunked
- ///
- ///
- ///
- ///
- ///
- private async Task copyBodyChunkedAsync(CustomBufferedStream reader, Action? onCopy, CancellationToken cancellationToken)
- {
- while (true)
- {
- string chunkHead = (await reader.ReadLineAsync(cancellationToken))!;
- int idx = chunkHead.IndexOf(";");
- if (idx >= 0)
- {
- chunkHead = chunkHead.Substring(0, idx);
- }
-
- int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
-
- await WriteLineAsync(chunkHead, cancellationToken);
-
- if (chunkSize != 0)
- {
- await copyBytesFromStream(reader, chunkSize, onCopy, cancellationToken);
- }
-
- await WriteLineAsync(cancellationToken);
-
- // chunk trail
- await reader.ReadLineAsync(cancellationToken);
-
- if (chunkSize == 0)
- {
- break;
- }
- }
- }
-
- ///
- /// Copies the specified bytes to the stream from the input stream
- ///
- ///
- ///
- ///
- ///
- ///
- private async Task copyBytesFromStream(CustomBufferedStream reader, long count, Action? onCopy,
- CancellationToken cancellationToken)
- {
- var buffer = bufferPool.GetBuffer();
-
- try
- {
- long remainingBytes = count;
-
- while (remainingBytes > 0)
- {
- int bytesToRead = buffer.Length;
- if (remainingBytes < bytesToRead)
- {
- bytesToRead = (int)remainingBytes;
- }
-
- int bytesRead = await reader.ReadAsync(buffer, 0, bytesToRead, cancellationToken);
- if (bytesRead == 0)
- {
- break;
- }
-
- remainingBytes -= bytesRead;
-
- await stream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
-
- onCopy?.Invoke(buffer, 0, bytesRead);
- }
- }
- finally
- {
- bufferPool.ReturnBuffer(buffer);
- }
- }
-
- ///
- /// Writes the request/response headers and body.
- ///
- ///
- ///
- ///
- ///
- protected async Task WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
- {
- var body = requestResponse.CompressBodyAndUpdateContentLength();
- headerBuilder.WriteHeaders(requestResponse.Headers);
- await WriteHeadersAsync(headerBuilder, cancellationToken);
-
- if (body != null)
- {
- await WriteBodyAsync(body, requestResponse.IsChunked, cancellationToken);
- }
- }
-
- /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
- /// An array of bytes. This method copies count bytes from buffer to the current stream.
- /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream.
- /// The number of bytes to be written to the current stream.
- /// The sum of offset and count is greater than the buffer length.
- /// buffer is null.
- /// offset or count is negative.
- /// An I/O error occured, such as the specified file cannot be found.
- /// The stream does not support writing.
- /// was called after the stream was closed.
- public void Write(byte[] buffer, int offset, int count)
- {
- stream.Write(buffer, offset, count);
- }
-
- ///
- /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
- ///
- /// The buffer to write data from.
- /// The zero-based byte offset in from which to begin copying bytes to the stream.
- /// The maximum number of bytes to write.
- /// The token to monitor for cancellation requests. The default value is .
- /// A task that represents the asynchronous write operation.
- public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return stream.WriteAsync(buffer, offset, count, cancellationToken);
- }
-
-#if NETSTANDARD2_1
- ///
- /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
- ///
- /// The buffer to write data from.
- /// The token to monitor for cancellation requests. The default value is .
- /// A task that represents the asynchronous write operation.
- public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken)
- {
- return stream.WriteAsync(buffer, cancellationToken);
- }
-#else
- ///
- /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
- ///
- /// The buffer to write data from.
- /// The token to monitor for cancellation requests. The default value is .
- /// A task that represents the asynchronous write operation.
- public Task WriteAsync2(ReadOnlyMemory buffer, CancellationToken cancellationToken)
- {
- var buf = ArrayPool.Shared.Rent(buffer.Length);
- buffer.CopyTo(buf);
- return stream.WriteAsync(buf, 0, buf.Length, cancellationToken);
- }
-#endif
- }
-}
diff --git a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs
index 9ccbf31d9..8b8ec3295 100644
--- a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs
+++ b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs
@@ -95,7 +95,7 @@ public bool GetAutoProxies(Uri destination, out IList? proxyList)
return true;
}
- public ExternalProxy? GetProxy(Uri destination)
+ public IExternalProxy? GetProxy(Uri destination)
{
if (GetAutoProxies(destination, out var proxies))
{
diff --git a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs
index 1957aa1d9..b793a1525 100644
--- a/src/Titanium.Web.Proxy/Http/HttpWebClient.cs
+++ b/src/Titanium.Web.Proxy/Http/HttpWebClient.cs
@@ -111,7 +111,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTranspar
bool useUpstreamProxy = upstreamProxy != null && Connection.IsHttps == false;
- var writer = Connection.StreamWriter;
+ var serverStream = Connection.Stream;
string url;
if (useUpstreamProxy || isTransparent)
@@ -153,7 +153,7 @@ internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTranspar
headerBuilder.WriteLine();
- await writer.WriteHeadersAsync(headerBuilder, cancellationToken);
+ await serverStream.WriteHeadersAsync(headerBuilder, cancellationToken);
if (enable100ContinueBehaviour && Request.ExpectContinue)
{
diff --git a/src/Titanium.Web.Proxy/Models/ExternalProxy.cs b/src/Titanium.Web.Proxy/Models/ExternalProxy.cs
index 4c1deca60..931248233 100644
--- a/src/Titanium.Web.Proxy/Models/ExternalProxy.cs
+++ b/src/Titanium.Web.Proxy/Models/ExternalProxy.cs
@@ -6,7 +6,7 @@ namespace Titanium.Web.Proxy.Models
///
/// An upstream proxy this proxy uses if any.
///
- public class ExternalProxy
+ public class ExternalProxy : IExternalProxy
{
private static readonly Lazy defaultCredentials =
new Lazy(() => CredentialCache.DefaultNetworkCredentials);
diff --git a/src/Titanium.Web.Proxy/Models/IExternalProxy.cs b/src/Titanium.Web.Proxy/Models/IExternalProxy.cs
new file mode 100644
index 000000000..7eb877859
--- /dev/null
+++ b/src/Titanium.Web.Proxy/Models/IExternalProxy.cs
@@ -0,0 +1,37 @@
+namespace Titanium.Web.Proxy.Models
+{
+ public interface IExternalProxy
+ {
+ ///
+ /// Use default windows credentials?
+ ///
+ bool UseDefaultCredentials { get; set; }
+
+ ///
+ /// Bypass this proxy for connections to localhost?
+ ///
+ bool BypassLocalhost { get; set; }
+
+ ///
+ /// Username.
+ ///
+ string? UserName { get; set; }
+
+ ///
+ /// Password.
+ ///
+ string? Password { get; set; }
+
+ ///
+ /// Host name.
+ ///
+ string HostName { get; set; }
+
+ ///
+ /// Port.
+ ///
+ int Port { get; set; }
+
+ string ToString();
+ }
+}
diff --git a/src/Titanium.Web.Proxy/Network/DebugCustomBufferedStream.cs b/src/Titanium.Web.Proxy/Network/DebugCustomBufferedStream.cs
deleted file mode 100644
index a49894dc9..000000000
--- a/src/Titanium.Web.Proxy/Network/DebugCustomBufferedStream.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-
-using Titanium.Web.Proxy.StreamExtended.BufferPool;
-using Titanium.Web.Proxy.StreamExtended.Network;
-#if DEBUG
-using System;
-using System.IO;
-using System.Text;
-using System.Threading;
-
-namespace Titanium.Web.Proxy.Network
-{
- internal class DebugCustomBufferedStream : CustomBufferedStream
- {
- private const string basePath = @".";
-
- private static int counter;
-
- private readonly FileStream fileStreamReceived;
-
- private readonly FileStream fileStreamSent;
-
- public DebugCustomBufferedStream(Guid connectionId, string type, Stream baseStream, IBufferPool bufferPool, bool leaveOpen = false)
- : base(baseStream, bufferPool, leaveOpen)
- {
- Counter = Interlocked.Increment(ref counter);
- fileStreamSent = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_sent.dat"), FileMode.Create);
- fileStreamReceived = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_received.dat"), FileMode.Create);
- }
-
- public int Counter { get; }
-
- protected override void OnDataWrite(byte[] buffer, int offset, int count)
- {
- fileStreamSent.Write(buffer, offset, count);
- Flush();
- }
-
- protected override void OnDataRead(byte[] buffer, int offset, int count)
- {
- fileStreamReceived.Write(buffer, offset, count);
- Flush();
- }
-
- public void LogException(Exception ex)
- {
- var data = Encoding.UTF8.GetBytes("EXCEPTION: " + ex);
- fileStreamReceived.Write(data, 0, data.Length);
- fileStreamReceived.Flush();
- }
- public override void Flush()
- {
- fileStreamSent.Flush(true);
- fileStreamReceived.Flush(true);
-
- if (CanWrite)
- {
- base.Flush();
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- Flush();
- fileStreamSent.Dispose();
- fileStreamReceived.Dispose();
- }
-
- base.Dispose(disposing);
- }
- }
-}
-#endif
diff --git a/src/Titanium.Web.Proxy/Network/ProxyClient.cs b/src/Titanium.Web.Proxy/Network/ProxyClient.cs
deleted file mode 100644
index 1969cb1db..000000000
--- a/src/Titanium.Web.Proxy/Network/ProxyClient.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Titanium.Web.Proxy.Helpers;
-using Titanium.Web.Proxy.Network.Tcp;
-using Titanium.Web.Proxy.StreamExtended.Network;
-
-namespace Titanium.Web.Proxy.Network
-{
- ///
- /// This class wraps Tcp connection to client
- ///
- internal class ProxyClient
- {
- public ProxyClient(TcpClientConnection connection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter)
- {
- Connection = connection;
- ClientStream = clientStream;
- ClientStreamWriter = clientStreamWriter;
- }
-
- ///
- /// TcpClient connection used to communicate with client
- ///
- internal TcpClientConnection Connection { get; }
-
- ///
- /// Holds the stream to client
- ///
- internal CustomBufferedStream ClientStream { get; }
-
- ///
- /// Used to write line by line to client
- ///
- internal HttpResponseWriter ClientStreamWriter { get; }
- }
-}
diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpClientConnection.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpClientConnection.cs
index a8d4ec45c..22d09da03 100644
--- a/src/Titanium.Web.Proxy/Network/Tcp/TcpClientConnection.cs
+++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpClientConnection.cs
@@ -86,7 +86,6 @@ public void Dispose()
proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
});
-
}
}
}
diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
index ac11521b1..779950f6f 100644
--- a/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
+++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
@@ -49,7 +49,7 @@ internal TcpConnectionFactory(ProxyServer server)
internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
bool isHttps, List? applicationProtocols,
- IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy)
+ IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy)
{
// http version is ignored since its an application level decision b/w HTTP 1.0/1.1
// also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response.
@@ -115,7 +115,7 @@ internal async Task GetConnectionCacheKey(ProxyServer server, SessionEve
applicationProtocols = new List { applicationProtocol };
}
- ExternalProxy? customUpStreamProxy = null;
+ IExternalProxy? customUpStreamProxy = null;
bool isHttps = session.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
@@ -170,7 +170,7 @@ internal Task GetServerConnection(ProxyServer server, Sessi
internal async Task GetServerConnection(ProxyServer server, SessionEventArgsBase session, bool isConnect,
List? applicationProtocols, bool noCache, CancellationToken cancellationToken)
{
- ExternalProxy? customUpStreamProxy = null;
+ IExternalProxy? customUpStreamProxy = null;
bool isHttps = session.IsHttps;
if (server.GetCustomUpStreamProxyFunc != null)
@@ -208,10 +208,10 @@ internal async Task GetServerConnection(ProxyServer server,
///
internal async Task GetServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, List? applicationProtocols, bool isConnect,
- ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy,
+ ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy,
bool noCache, CancellationToken cancellationToken)
{
- var sslProtocol = session?.ProxyClient.Connection.SslProtocol ?? SslProtocols.None;
+ var sslProtocol = session?.ClientConnection.SslProtocol ?? SslProtocols.None;
var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
isHttps, applicationProtocols, upStreamEndPoint, externalProxy);
@@ -262,7 +262,7 @@ internal async Task GetServerConnection(string remoteHostNa
///
private async Task createServerConnection(string remoteHostName, int remotePort,
Version httpVersion, bool isHttps, SslProtocols sslProtocol, List? applicationProtocols, bool isConnect,
- ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, ExternalProxy? externalProxy, string cacheKey,
+ ProxyServer proxyServer, SessionEventArgsBase? session, IPEndPoint? upStreamEndPoint, IExternalProxy? externalProxy, string cacheKey,
CancellationToken cancellationToken)
{
// deny connection to proxy end points to avoid infinite connection loop.
@@ -297,14 +297,14 @@ private async Task createServerConnection(string remoteHost
}
TcpClient? tcpClient = null;
- CustomBufferedStream? stream = null;
+ HttpServerStream? stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default;
bool retry = true;
var enabledSslProtocols = sslProtocol;
- retry:
+retry:
try
{
string hostname = useUpstreamProxy ? externalProxy!.HostName : remoteHostName;
@@ -323,6 +323,7 @@ private async Task createServerConnection(string remoteHost
Array.Sort(ipAddresses, (x, y) => x.AddressFamily.CompareTo(y.AddressFamily));
+ Exception lastException = null;
for (int i = 0; i < ipAddresses.Length; i++)
{
try
@@ -352,28 +353,29 @@ private async Task createServerConnection(string remoteHost
}
catch (Exception e)
{
- if (i == ipAddresses.Length - 1)
- {
- throw new Exception($"Could not establish connection to {hostname}", e);
- }
-
// dispose the current TcpClient and try the next address
+ lastException = e;
tcpClient?.Dispose();
+ tcpClient = null;
}
}
+ if (tcpClient == null)
+ {
+ throw new Exception($"Could not establish connection to {hostname}", lastException);
+ }
+
if (session != null)
{
session.TimeLine["Connection Established"] = DateTime.Now;
}
- await proxyServer.InvokeConnectionCreateEvent(tcpClient!, false);
+ await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
- stream = new CustomBufferedStream(tcpClient!.GetStream(), proxyServer.BufferPool);
+ stream = new HttpServerStream(tcpClient.GetStream(), proxyServer.BufferPool);
if (useUpstreamProxy && (isConnect || isHttps))
{
- var writer = new HttpRequestWriter(stream, proxyServer.BufferPool);
string authority = $"{remoteHostName}:{remotePort}";
var connectRequest = new ConnectRequest(authority)
{
@@ -391,7 +393,7 @@ private async Task createServerConnection(string remoteHost
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
}
- await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
+ await stream.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken)
?? throw new ServerConnectionException("Server connection was closed.");
@@ -411,7 +413,7 @@ private async Task createServerConnection(string remoteHost
{
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
- stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool);
+ stream = new HttpServerStream(sslStream, proxyServer.BufferPool);
var options = new SslClientAuthenticationOptions
{
@@ -430,11 +432,13 @@ private async Task createServerConnection(string remoteHost
{
session.TimeLine["HTTPS Established"] = DateTime.Now;
}
-
}
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11)
{
+ stream?.Dispose();
+ tcpClient?.Close();
+
enabledSslProtocols = SslProtocols.Tls;
retry = false;
goto retry;
diff --git a/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs b/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs
index ef40391bb..6cc0bf431 100644
--- a/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs
+++ b/src/Titanium.Web.Proxy/Network/Tcp/TcpServerConnection.cs
@@ -15,15 +15,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
///
internal class TcpServerConnection : IDisposable
{
- internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, CustomBufferedStream stream,
+ internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, HttpServerStream stream,
string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol,
- Version version, bool useUpstreamProxy, ExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
+ Version version, bool useUpstreamProxy, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
{
- this.tcpClient = tcpClient;
+ TcpClient = tcpClient;
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
- StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool);
Stream = stream;
HostName = hostName;
Port = port;
@@ -41,7 +40,7 @@ internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, Custo
internal bool IsClosed => Stream.IsClosed;
- internal ExternalProxy? UpStreamProxy { get; set; }
+ internal IExternalProxy? UpStreamProxy { get; set; }
internal string HostName { get; set; }
@@ -63,22 +62,15 @@ internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, Custo
///
internal Version Version { get; set; } = HttpHeader.VersionUnknown;
- private readonly TcpClient tcpClient;
-
///
/// The TcpClient.
///
- internal TcpClient TcpClient => tcpClient;
+ internal TcpClient TcpClient { get; }
///
/// Used to write lines to server
///
- internal HttpRequestWriter StreamWriter { get; }
-
- ///
- /// Server stream
- ///
- internal CustomBufferedStream Stream { get; }
+ internal HttpServerStream Stream { get; }
///
/// Last time this connection was used
@@ -107,8 +99,8 @@ public void Dispose()
// This way we can push tcp Time_Wait to server side when possible.
await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false);
- Stream?.Dispose();
- tcpClient.CloseSocket();
+ Stream.Dispose();
+ TcpClient.CloseSocket();
});
}
diff --git a/src/Titanium.Web.Proxy/ProxyServer.cs b/src/Titanium.Web.Proxy/ProxyServer.cs
index 31e3ff34f..0bd684df2 100644
--- a/src/Titanium.Web.Proxy/ProxyServer.cs
+++ b/src/Titanium.Web.Proxy/ProxyServer.cs
@@ -61,7 +61,7 @@ public partial class ProxyServer : IDisposable
///
private WinHttpWebProxyFinder? systemProxyResolver;
-
+
///
///
/// Initializes a new instance of ProxyServer class with provided parameters.
@@ -145,7 +145,7 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam
/// Defaults to false.
///
public bool EnableWinAuth { get; set; }
-
+
///
/// Enable disable HTTP/2 support.
/// Warning: HTTP/2 support is very limited
@@ -253,12 +253,12 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam
///
/// External proxy used for Http requests.
///
- public ExternalProxy? UpStreamHttpProxy { get; set; }
+ public IExternalProxy? UpStreamHttpProxy { get; set; }
///
/// External proxy used for Https requests.
///
- public ExternalProxy? UpStreamHttpsProxy { get; set; }
+ public IExternalProxy? UpStreamHttpsProxy { get; set; }
///
/// Local adapter/NIC endpoint where proxy makes request via.
@@ -275,7 +275,7 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests.
/// User should return the ExternalProxy object with valid credentials.
///
- public Func>? GetCustomUpStreamProxyFunc { get; set; }
+ public Func>? GetCustomUpStreamProxyFunc { get; set; }
///
/// Callback for error events in this proxy instance.
@@ -709,7 +709,7 @@ private void validateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
///
/// The session.
/// The external proxy as task result.
- private Task getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
+ private Task getSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{
var proxy = systemProxyResolver!.GetProxy(sessionEventArgs.HttpClient.Request.RequestUri);
return Task.FromResult(proxy);
@@ -803,15 +803,8 @@ private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
///
/// The client stream.
/// The exception.
- private void onException(CustomBufferedStream clientStream, Exception exception)
+ private void onException(HttpClientStream clientStream, Exception exception)
{
-#if DEBUG
- if (clientStream is DebugCustomBufferedStream debugStream)
- {
- debugStream.LogException(exception);
- }
-#endif
-
ExceptionFunc(exception);
}
diff --git a/src/Titanium.Web.Proxy/RequestHandler.cs b/src/Titanium.Web.Proxy/RequestHandler.cs
index 14ecbc241..021af3e17 100644
--- a/src/Titanium.Web.Proxy/RequestHandler.cs
+++ b/src/Titanium.Web.Proxy/RequestHandler.cs
@@ -31,13 +31,11 @@ public partial class ProxyServer
/// The proxy endpoint.
/// The client connection.
/// The client stream.
- /// The client stream writer.
/// The cancellation token source for this async task.
/// The Connect request if this is a HTTPS request from explicit endpoint.
/// Prefetched server connection for current client using Connect/SNI headers.
private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
- CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
- CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
+ HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs? connectArgs = null,
Task? prefetchConnectionTask = null)
{
var connectRequest = connectArgs?.HttpClient.ConnectRequest;
@@ -66,7 +64,7 @@ private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientCon
return;
}
- var args = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), connectRequest, cancellationTokenSource)
+ var args = new SessionEventArgs(this, endPoint, clientConnection, clientStream, connectRequest, cancellationTokenSource)
{
UserData = connectArgs?.UserData
};
@@ -101,7 +99,7 @@ await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers,
await onBeforeResponse(args);
// send the response
- await clientStreamWriter.WriteResponseAsync(args.HttpClient.Response,
+ await clientStream.WriteResponseAsync(args.HttpClient.Response,
cancellationToken: cancellationToken);
return;
}
@@ -276,7 +274,7 @@ private async Task handleHttpSessionRequest(string requestHttpMetho
// if upgrading to websocket then relay the request without reading the contents
await handleWebSocketUpgrade(requestHttpMethod, requestHttpUrl, requestVersion, args, args.HttpClient.Request,
- args.HttpClient.Response, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamWriter,
+ args.HttpClient.Response, args.ClientStream,
connection, cancellationTokenSource, cancellationToken);
return false;
}
@@ -304,13 +302,13 @@ await args.HttpClient.SendRequest(Enable100ContinueBehaviour, args.IsTransparent
// If a successful 100 continue request was made, inform that to the client and reset response
if (request.ExpectationSucceeded)
{
- var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
+ var writer = args.ClientStream;
var response = args.HttpClient.Response;
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
headerBuilder.WriteHeaders(response.Headers);
- await clientStreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
+ await writer.WriteHeadersAsync(headerBuilder, cancellationToken);
await args.ClearResponse(cancellationToken);
}
@@ -320,14 +318,12 @@ await args.HttpClient.SendRequest(Enable100ContinueBehaviour, args.IsTransparent
{
if (request.IsBodyRead)
{
- var writer = args.HttpClient.Connection.StreamWriter;
- await writer.WriteBodyAsync(body!, request.IsChunked, cancellationToken);
+ await args.HttpClient.Connection.Stream.WriteBodyAsync(body!, request.IsChunked, cancellationToken);
}
else if (!request.ExpectationFailed)
{
// get the request body unless an unsuccessful 100 continue request was made
- HttpWriter writer = args.HttpClient.Connection.StreamWriter!;
- await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
+ await args.CopyRequestBodyAsync(args.HttpClient.Connection.Stream, TransformationMode.None, cancellationToken);
}
}
@@ -357,7 +353,7 @@ private void prepareRequestHeaders(HeaderCollection requestHeaders)
supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
- string.Join(",", supportedAcceptEncoding));
+ string.Join(", ", supportedAcceptEncoding));
}
requestHeaders.FixProxyHeaders();
diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs
index 18ff4aa97..6bf2ad7c4 100644
--- a/src/Titanium.Web.Proxy/ResponseHandler.cs
+++ b/src/Titanium.Web.Proxy/ResponseHandler.cs
@@ -64,13 +64,13 @@ private async Task handleHttpSessionResponse(SessionEventArgs args)
// it may changed in the user event
response = args.HttpClient.Response;
- var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
+ var clientStream = args.ClientStream;
// user set custom response by ignoring original response from server.
if (response.Locked)
{
// write custom user response with body and return.
- await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
+ await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
if (args.HttpClient.HasConnection && !args.HttpClient.CloseServerConnection)
{
@@ -108,7 +108,7 @@ await handleHttpSessionRequest(args.HttpClient.Request.Method, args.HttpClient.R
if (response.IsBodyRead)
{
- await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
+ await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
}
else
{
@@ -116,12 +116,12 @@ await handleHttpSessionRequest(args.HttpClient.Request.Method, args.HttpClient.R
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
headerBuilder.WriteHeaders(response.Headers);
- await clientStreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
+ await clientStream.WriteHeadersAsync(headerBuilder, cancellationToken);
// Write body if exists
if (response.HasBody)
{
- await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None,
+ await args.CopyResponseBodyAsync(clientStream, TransformationMode.None,
cancellationToken);
}
}
diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs
index 2e6b35ee8..886c05815 100644
--- a/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs
+++ b/src/Titanium.Web.Proxy/StreamExtended/Network/CopyStream.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
@@ -11,9 +12,9 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
///
internal class CopyStream : ILineStream, IDisposable
{
- private readonly CustomBufferedStream reader;
+ private readonly IHttpStreamReader reader;
- private readonly ICustomStreamWriter writer;
+ private readonly IHttpStreamWriter writer;
private readonly IBufferPool bufferPool;
@@ -27,7 +28,7 @@ internal class CopyStream : ILineStream, IDisposable
public long ReadBytes { get; private set; }
- public CopyStream(CustomBufferedStream reader, ICustomStreamWriter writer, IBufferPool bufferPool)
+ public CopyStream(IHttpStreamReader reader, IHttpStreamWriter writer, IBufferPool bufferPool)
{
this.reader = reader;
this.writer = writer;
@@ -61,7 +62,7 @@ public byte ReadByteFromBuffer()
public ValueTask ReadLineAsync(CancellationToken cancellationToken = default)
{
- return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
+ return HttpStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
public void Dispose()
diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamReader.cs
new file mode 100644
index 000000000..5d13c0d04
--- /dev/null
+++ b/src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamReader.cs
@@ -0,0 +1,12 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Titanium.Web.Proxy.StreamExtended.Network
+{
+ public interface IHttpStreamReader : ILineStream
+ {
+ int Read(byte[] buffer, int offset, int count);
+
+ Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamWriter.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamWriter.cs
similarity index 59%
rename from src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamWriter.cs
rename to src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamWriter.cs
index 919339f0b..a02143d2d 100644
--- a/src/Titanium.Web.Proxy/StreamExtended/Network/ICustomStreamWriter.cs
+++ b/src/Titanium.Web.Proxy/StreamExtended/Network/IHttpStreamWriter.cs
@@ -1,4 +1,5 @@
-using System.Threading;
+using System;
+using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
@@ -6,10 +7,13 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
///
/// A concrete implementation of this interface is required when calling CopyStream.
///
- public interface ICustomStreamWriter
+ internal interface IHttpStreamWriter
{
void Write(byte[] buffer, int i, int bufferLength);
Task WriteAsync(byte[] buffer, int i, int bufferLength, CancellationToken cancellationToken);
+
+ Task CopyBodyAsync(IHttpStreamReader streamReader, bool isChunked, long contentLength,
+ Action? onCopy, CancellationToken cancellationToken);
}
-}
\ No newline at end of file
+}
diff --git a/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs b/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs
index df5ba9a1b..a301f66e8 100644
--- a/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs
+++ b/src/Titanium.Web.Proxy/StreamExtended/Network/PeekStreamReader.cs
@@ -53,5 +53,5 @@ public byte[] ReadBytes(int length)
return buffer;
}
- }
+ }
}
diff --git a/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs b/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs
index 19a508dcb..3258be048 100644
--- a/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs
+++ b/src/Titanium.Web.Proxy/StreamExtended/SslTools.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
using Titanium.Web.Proxy.StreamExtended.Models;
using Titanium.Web.Proxy.StreamExtended.Network;
@@ -19,7 +20,7 @@ internal class SslTools
///
///
///
- public static async Task PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
+ public static async Task PeekClientHello(IPeekStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
@@ -127,11 +128,10 @@ internal class SslTools
return null;
}
- byte[] ciphersData = peekStream.ReadBytes(length);
- int[] ciphers = new int[ciphersData.Length / 2];
+ int[] ciphers = new int[length / 2];
for (int i = 0; i < ciphers.Length; i++)
{
- ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1];
+ ciphers[i] = peekStream.ReadInt16();
}
length = peekStream.ReadByte();
@@ -178,7 +178,7 @@ internal class SslTools
///
///
///
- public static async Task IsServerHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
+ public static async Task IsServerHello(IPeekStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
var serverHello = await PeekServerHello(stream, bufferPool, cancellationToken);
return serverHello != null;
@@ -190,7 +190,7 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe
///
///
///
- public static async Task PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
+ public static async Task PeekServerHello(IPeekStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
@@ -286,11 +286,11 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe
int cipherSuite = peekStream.ReadInt16();
byte compressionMethod = peekStream.ReadByte();
- int extenstionsStartPosition = peekStream.Position;
+ int extensionsStartPosition = peekStream.Position;
Dictionary? extensions = null;
- if (extenstionsStartPosition < recordLength + 5)
+ if (extensionsStartPosition < recordLength + 5)
{
extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken);
}
@@ -298,7 +298,7 @@ public static async Task IsServerHello(CustomBufferedStream stream, IBuffe
var serverHelloInfo = new ServerHelloInfo(3, majorVersion, minorVersion, random, sessionId, cipherSuite, peekStream.Position)
{
CompressionMethod = compressionMethod,
- EntensionsStartPosition = extenstionsStartPosition,
+ EntensionsStartPosition = extensionsStartPosition,
Extensions = extensions,
};
diff --git a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.Mono.csproj b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.Mono.csproj
index 3777fffbc..37cf96741 100644
--- a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.Mono.csproj
+++ b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.Mono.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.NetCore.csproj b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.NetCore.csproj
index ac8c9b734..de71aa587 100644
--- a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.NetCore.csproj
+++ b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.NetCore.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
index 0002e0bad..c41718e92 100644
--- a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+++ b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec
index ce61f46d4..a18c431b4 100644
--- a/src/Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec
+++ b/src/Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec
@@ -15,7 +15,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
diff --git a/src/Titanium.Web.Proxy/TransparentClientHandler.cs b/src/Titanium.Web.Proxy/TransparentClientHandler.cs
index ad0e106c9..6e0e8579c 100644
--- a/src/Titanium.Web.Proxy/TransparentClientHandler.cs
+++ b/src/Titanium.Web.Proxy/TransparentClientHandler.cs
@@ -33,8 +33,7 @@ private async Task handleClient(TransparentProxyEndPoint endPoint, TcpClientConn
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
- var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool);
- var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
+ var clientStream = new HttpClientStream(clientConnection.GetStream(), BufferPool);
SslStream? sslStream = null;
@@ -75,14 +74,12 @@ private async Task handleClient(TransparentProxyEndPoint endPoint, TcpClientConn
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
// HTTPS server created - we can now decrypt the client's traffic
- clientStream = new CustomBufferedStream(sslStream, BufferPool);
-
- clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
+ clientStream = new HttpClientStream(sslStream, BufferPool);
}
catch (Exception e)
{
var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false);
- var session = new SessionEventArgs(this, endPoint, new ProxyClient(clientConnection, clientStream, clientStreamWriter), null,
+ var session = new SessionEventArgs(this, endPoint, clientConnection, clientStream, null,
cancellationTokenSource);
throw new ProxyConnectException(
$"Couldn't authenticate host '{httpsHostName}' with certificate '{certname}'.", e, session);
@@ -108,7 +105,7 @@ private async Task handleClient(TransparentProxyEndPoint endPoint, TcpClientConn
{
// clientStream.Available should be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
- await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken);
+ await connection.Stream.WriteAsync(data, 0, available, true, cancellationToken);
}
finally
{
@@ -133,7 +130,7 @@ await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool,
// HTTPS server created - we can now decrypt the client's traffic
// Now create the request
- await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource);
+ await handleHttpSessionRequest(endPoint, clientConnection, clientStream, cancellationTokenSource);
}
catch (ProxyException e)
{
@@ -155,11 +152,6 @@ await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool,
{
sslStream?.Dispose();
clientStream.Dispose();
-
- if (!cancellationTokenSource.IsCancellationRequested)
- {
- cancellationTokenSource.Cancel();
- }
}
}
}
diff --git a/src/Titanium.Web.Proxy/WebSocketHandler.cs b/src/Titanium.Web.Proxy/WebSocketHandler.cs
index 10ebe2a7b..5003be5fb 100644
--- a/src/Titanium.Web.Proxy/WebSocketHandler.cs
+++ b/src/Titanium.Web.Proxy/WebSocketHandler.cs
@@ -18,15 +18,14 @@ public partial class ProxyServer
///
private async Task handleWebSocketUpgrade(string requestHttpMethod, string requestHttpUrl, Version requestVersion,
SessionEventArgs args, Request request, Response response,
- CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
- TcpServerConnection serverConnection,
+ HttpClientStream clientStream, TcpServerConnection serverConnection,
CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken)
{
// prepare the prefix content
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(requestHttpMethod, requestHttpUrl, requestVersion);
headerBuilder.WriteHeaders(request.Headers);
- await serverConnection.StreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken);
+ await serverConnection.Stream.WriteHeadersAsync(headerBuilder, cancellationToken);
string httpStatus;
try
@@ -51,7 +50,7 @@ await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers,
if (!args.IsTransparent)
{
- await clientStreamWriter.WriteResponseAsync(response,
+ await clientStream.WriteResponseAsync(response,
cancellationToken: cancellationToken);
}
diff --git a/src/Titanium.Web.Proxy/WinAuthHandler.cs b/src/Titanium.Web.Proxy/WinAuthHandler.cs
index 72f13a592..c72d333e1 100644
--- a/src/Titanium.Web.Proxy/WinAuthHandler.cs
+++ b/src/Titanium.Web.Proxy/WinAuthHandler.cs
@@ -175,7 +175,7 @@ private async Task rewriteUnauthorizedResponse(SessionEventArgs args)
// Add custom div to body to clarify that the proxy (not the client browser) failed authentication
string authErrorMessage =
"NTLM authentication through Titanium.Web.Proxy (" +
- args.ProxyClient.Connection.LocalEndPoint +
+ args.ClientConnection.LocalEndPoint +
") failed. Please check credentials.
";
string originalErrorMessage =
"Response from remote web server below.
";
diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs b/tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs
index 7980c6012..2ed0d10e9 100644
--- a/tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs
+++ b/tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs
@@ -69,7 +69,7 @@ public async Task Smoke_Test_Nested_Proxy_UserData()
};
var client = testSuite.GetClient(proxy1, true);
-
+
var response = await client.PostAsync(new Uri(server.ListeningHttpsUrl),
new StringContent("hello server. I am a client."));
diff --git a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj
index 279dcb57d..9764f7e8a 100644
--- a/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj
+++ b/tests/Titanium.Web.Proxy.IntegrationTests/Titanium.Web.Proxy.IntegrationTests.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj b/tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj
index fd26bfc64..c35f69cca 100644
--- a/tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj
+++ b/tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj
@@ -7,7 +7,7 @@
-
+