Unverified Commit 1abc6e5f authored by honfika's avatar honfika Committed by GitHub

Merge pull request #627 from justcoding121/master

small fixes and improvements
parents 53ba585d 01f82f07
......@@ -99,9 +99,6 @@
</div>
<div class="inheritedMembers">
<h5>Inherited Members</h5>
<div>
<a class="xref" href="Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html#Titanium_Web_Proxy_EventArguments_SessionEventArgsBase_BufferSize">SessionEventArgsBase.BufferSize</a>
</div>
<div>
<a class="xref" href="Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html#Titanium_Web_Proxy_EventArguments_SessionEventArgsBase_BufferPool">SessionEventArgsBase.BufferPool</a>
</div>
......
......@@ -104,9 +104,6 @@ So client application know that it is communicating with a proxy server.</p>
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_DecryptSsl">ProxyEndPoint.DecryptSsl</a>
</div>
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled">ProxyEndPoint.IpV6Enabled</a>
</div>
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_GenericCertificate">ProxyEndPoint.GenericCertificate</a>
</div>
......
......@@ -206,7 +206,7 @@
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/new/master/apiSpec/new?filename=Titanium_Web_Proxy_Models_ProxyEndPoint_GenericCertificate.md&amp;value=---%0Auid%3A%20Titanium.Web.Proxy.Models.ProxyEndPoint.GenericCertificate%0Asummary%3A%20'*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax'%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">Improve this Doc</a>
</span>
<span class="small pull-right mobile-hide">
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/blob/master/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs/#L56">View Source</a>
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/blob/master/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs/#L48">View Source</a>
</span>
<a id="Titanium_Web_Proxy_Models_ProxyEndPoint_GenericCertificate_" data-uid="Titanium.Web.Proxy.Models.ProxyEndPoint.GenericCertificate*"></a>
<h4 id="Titanium_Web_Proxy_Models_ProxyEndPoint_GenericCertificate" data-uid="Titanium.Web.Proxy.Models.ProxyEndPoint.GenericCertificate">GenericCertificate</h4>
......@@ -263,37 +263,6 @@
</tr>
</tbody>
</table>
<span class="small pull-right mobile-hide">
<span class="divider">|</span>
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/new/master/apiSpec/new?filename=Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled.md&amp;value=---%0Auid%3A%20Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled%0Asummary%3A%20'*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax'%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">Improve this Doc</a>
</span>
<span class="small pull-right mobile-hide">
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/blob/master/src/Titanium.Web.Proxy/Models/ProxyEndPoint.cs/#L48">View Source</a>
</span>
<a id="Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled_" data-uid="Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled*"></a>
<h4 id="Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled" data-uid="Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled">IpV6Enabled</h4>
<div class="markdown level1 summary"><p>Is IPv6 enabled?</p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public bool IpV6Enabled { get; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.boolean">Boolean</a></td>
<td></td>
</tr>
</tbody>
</table>
<span class="small pull-right mobile-hide">
<span class="divider">|</span>
<a href="https://github.com/justcoding121/Titanium-Web-Proxy/new/master/apiSpec/new?filename=Titanium_Web_Proxy_Models_ProxyEndPoint_Port.md&amp;value=---%0Auid%3A%20Titanium.Web.Proxy.Models.ProxyEndPoint.Port%0Asummary%3A%20'*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax'%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">Improve this Doc</a>
......
......@@ -104,9 +104,6 @@ Useful when requests are redirected to this proxy end point through port forward
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_DecryptSsl">ProxyEndPoint.DecryptSsl</a>
</div>
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled">ProxyEndPoint.IpV6Enabled</a>
</div>
<div>
<a class="xref" href="Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_GenericCertificate">ProxyEndPoint.GenericCertificate</a>
</div>
......
This diff is collapsed.
......@@ -529,12 +529,6 @@ references:
commentId: F:Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.BufferPool
fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.BufferPool
nameWithType: SessionEventArgsBase.BufferPool
- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.BufferSize
name: BufferSize
href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html#Titanium_Web_Proxy_EventArguments_SessionEventArgsBase_BufferSize
commentId: F:Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.BufferSize
fullName: Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.BufferSize
nameWithType: SessionEventArgsBase.BufferSize
- uid: Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.ClientEndPoint
name: ClientEndPoint
href: api/Titanium.Web.Proxy.EventArguments.SessionEventArgsBase.html#Titanium_Web_Proxy_EventArguments_SessionEventArgsBase_ClientEndPoint
......@@ -2865,19 +2859,6 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.ProxyEndPoint.IpAddress
nameWithType: ProxyEndPoint.IpAddress
- uid: Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled
name: IpV6Enabled
href: api/Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled
commentId: P:Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled
fullName: Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled
nameWithType: ProxyEndPoint.IpV6Enabled
- uid: Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled*
name: IpV6Enabled
href: api/Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_IpV6Enabled_
commentId: Overload:Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.ProxyEndPoint.IpV6Enabled
nameWithType: ProxyEndPoint.IpV6Enabled
- uid: Titanium.Web.Proxy.Models.ProxyEndPoint.Port
name: Port
href: api/Titanium.Web.Proxy.Models.ProxyEndPoint.html#Titanium_Web_Proxy_Models_ProxyEndPoint_Port
......@@ -3369,19 +3350,6 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.ProxyServer.BufferPool
nameWithType: ProxyServer.BufferPool
- uid: Titanium.Web.Proxy.ProxyServer.BufferSize
name: BufferSize
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_BufferSize
commentId: P:Titanium.Web.Proxy.ProxyServer.BufferSize
fullName: Titanium.Web.Proxy.ProxyServer.BufferSize
nameWithType: ProxyServer.BufferSize
- uid: Titanium.Web.Proxy.ProxyServer.BufferSize*
name: BufferSize
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_BufferSize_
commentId: Overload:Titanium.Web.Proxy.ProxyServer.BufferSize
isSpec: "True"
fullName: Titanium.Web.Proxy.ProxyServer.BufferSize
nameWithType: ProxyServer.BufferSize
- uid: Titanium.Web.Proxy.ProxyServer.CertificateManager
name: CertificateManager
href: api/Titanium.Web.Proxy.ProxyServer.html#Titanium_Web_Proxy_ProxyServer_CertificateManager
......
......@@ -31,6 +31,7 @@
<GridViewColumn Header="Process" DisplayMemberBinding="{Binding Process}" />
<GridViewColumn Header="SentBytes" DisplayMemberBinding="{Binding SentDataCount}" />
<GridViewColumn Header="ReceivedBytes" DisplayMemberBinding="{Binding ReceivedDataCount}" />
<GridViewColumn Header="ClientEndPoint" DisplayMemberBinding="{Binding ClientEndPoint}" />
</GridView>
</ListView.View>
</ListView>
......
......@@ -226,10 +226,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
Number = lastSessionNumber,
HttpClient = e.HttpClient,
ClientEndPoint = e.ClientEndPoint,
IsTunnelConnect = isTunnelConnect
};
if (isTunnelConnect || e.HttpClient.Request.UpgradeToWebSocket)
//if (isTunnelConnect || e.HttpClient.Request.UpgradeToWebSocket)
{
e.DataReceived += (sender, args) =>
{
......
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.CompilerServices;
using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http;
......@@ -22,6 +23,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public HttpWebClient HttpClient { get; set; }
public IPEndPoint ClientEndPoint { get; set; }
public bool IsTunnelConnect { get; set; }
public string StatusCode
......
......@@ -132,7 +132,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
if (bytesRemaining != -1)
{
var buffer = bufferPool.GetBuffer(baseStream.BufferSize);
var buffer = bufferPool.GetBuffer();
try
{
int res = await ReadAsync(buffer, 0, buffer.Length);
......
......@@ -121,7 +121,6 @@ namespace Titanium.Web.Proxy.EventArguments
// Now set the flag to true
// So that next time we can deliver body from cache
request.IsBodyRead = true;
OnDataSent(body, 0, body.Length);
}
}
}
......@@ -194,7 +193,6 @@ namespace Titanium.Web.Proxy.EventArguments
// Now set the flag to true
// So that next time we can deliver body from cache
response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length);
}
}
}
......@@ -203,7 +201,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, BufferPool, BufferSize);
var writer = new HttpWriter(bodyStream, BufferPool);
if (isRequest)
{
......@@ -235,7 +233,7 @@ namespace Titanium.Web.Proxy.EventArguments
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, BufferPool, BufferSize);
var writer = new HttpWriter(bodyStream, BufferPool);
await copyBodyAsync(isRequest, true, writer, TransformationMode.None, null, cancellationToken);
}
}
......@@ -256,7 +254,7 @@ namespace Titanium.Web.Proxy.EventArguments
var reader = getStreamReader(true);
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, BufferPool, BufferSize))
using (var copyStream = new CopyStream(reader, writer, BufferPool))
{
while (contentLength > copyStream.ReadBytes)
{
......@@ -317,7 +315,7 @@ namespace Titanium.Web.Proxy.EventArguments
try
{
using (var bufStream = new CustomBufferedStream(s, BufferPool, BufferSize, true))
using (var bufStream = new CustomBufferedStream(s, BufferPool, true))
{
await writer.CopyBodyAsync(bufStream, false, -1, onCopy, cancellationToken);
}
......@@ -339,7 +337,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
int bufferDataLength = 0;
var buffer = BufferPool.GetBuffer(BufferSize);
var buffer = BufferPool.GetBuffer();
try
{
int boundaryLength = boundary.Length + 4;
......
......@@ -21,10 +21,11 @@ namespace Titanium.Web.Proxy.EventArguments
public abstract class SessionEventArgsBase : EventArgs, IDisposable
{
internal readonly CancellationTokenSource CancellationTokenSource;
internal TcpServerConnection ServerConnection => HttpClient.Connection;
internal TcpClientConnection ClientConnection => ProxyClient.Connection;
protected readonly int BufferSize;
protected readonly IBufferPool BufferPool;
protected readonly ExceptionHandler ExceptionFunc;
......@@ -36,10 +37,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Initializes a new instance of the <see cref="SessionEventArgsBase" /> class.
/// </summary>
private SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource)
private SessionEventArgsBase(ProxyServer server)
{
BufferSize = server.BufferSize;
BufferPool = server.BufferPool;
ExceptionFunc = server.ExceptionFunc;
TimeLine["Session Created"] = DateTime.Now;
......@@ -47,7 +46,7 @@ namespace Titanium.Web.Proxy.EventArguments
protected SessionEventArgsBase(ProxyServer server, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource,
Request request) : this(server, endPoint, cancellationTokenSource)
Request request) : this(server)
{
CancellationTokenSource = cancellationTokenSource;
......
......@@ -35,8 +35,8 @@ namespace Titanium.Web.Proxy
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
Task<TcpServerConnection> prefetchConnectionTask = null;
bool closeServerConnection = false;
......@@ -50,7 +50,7 @@ namespace Titanium.Web.Proxy
TunnelConnectSessionEventArgs connectArgs = null;
// Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1)
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, cancellationToken) == 1)
{
// read the first line HTTP command
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
......@@ -75,6 +75,8 @@ namespace Titanium.Web.Proxy
connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest,
cancellationTokenSource);
clientStream.DataRead += (o, args) => connectArgs.OnDataSent(args.Buffer, args.Offset, args.Count);
clientStream.DataWrite += (o, args) => connectArgs.OnDataReceived(args.Buffer, args.Offset, args.Count);
connectArgs.ProxyClient.Connection = clientConnection;
connectArgs.ProxyClient.ClientStream = clientStream;
......@@ -213,8 +215,8 @@ namespace Titanium.Web.Proxy
#endif
// HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferPool, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);
clientStream = new CustomBufferedStream(sslStream, BufferPool);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
}
catch (Exception e)
{
......@@ -223,7 +225,7 @@ namespace Titanium.Web.Proxy
$"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs);
}
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == -1)
if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, cancellationToken) == -1)
{
decryptSsl = false;
}
......@@ -263,7 +265,7 @@ namespace Titanium.Web.Proxy
if (available > 0)
{
// send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
var data = BufferPool.GetBuffer();
try
{
......@@ -283,10 +285,8 @@ namespace Titanium.Web.Proxy
if (!clientStream.IsClosed && !connection.Stream.IsClosed)
{
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, ExceptionFunc);
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool,
null, null, connectArgs.CancellationTokenSource, ExceptionFunc);
}
}
finally
......@@ -298,7 +298,7 @@ namespace Titanium.Web.Proxy
}
}
if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1)
if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, cancellationToken) == 1)
{
// todo
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
......@@ -335,7 +335,7 @@ namespace Titanium.Web.Proxy
await connection.StreamWriter.WriteLineAsync("SM", cancellationToken);
await connection.StreamWriter.WriteLineAsync(cancellationToken);
#if NETCOREAPP2_1
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferPool.BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
() => new SessionEventArgs(this, endPoint, cancellationTokenSource)
......@@ -357,6 +357,7 @@ namespace Titanium.Web.Proxy
}
calledRequestHandler = true;
// Now create the request
await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
cancellationTokenSource, connectHostname, connectArgs, prefetchConnectionTask);
......
......@@ -18,11 +18,10 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferPool"></param>
/// <param name="bufferSize"></param>
internal static Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy,
IBufferPool bufferPool, int bufferSize)
IBufferPool bufferPool)
{
return CopyToAsync(input, output, onCopy, bufferPool, bufferSize, CancellationToken.None);
return CopyToAsync(input, output, onCopy, bufferPool, CancellationToken.None);
}
/// <summary>
......@@ -32,12 +31,11 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferPool"></param>
/// <param name="bufferSize"></param>
/// <param name="cancellationToken"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy,
IBufferPool bufferPool, int bufferSize, CancellationToken cancellationToken)
IBufferPool bufferPool, CancellationToken cancellationToken)
{
var buffer = bufferPool.GetBuffer(bufferSize);
var buffer = bufferPool.GetBuffer();
try
{
while (!cancellationToken.IsCancellationRequested)
......
......@@ -12,5 +12,5 @@ namespace Titanium.Web.Proxy.Extensions
return uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
}
}
}
}
......@@ -128,39 +128,35 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Determines whether is connect method.
/// </summary>
/// <param name="clientStreamReader">The client stream reader.</param>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsConnectMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, int bufferSize, CancellationToken cancellationToken = default)
internal static Task<int> IsConnectMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, bufferSize, "CONNECT", cancellationToken);
return startsWith(clientStreamReader, bufferPool, "CONNECT", cancellationToken);
}
/// <summary>
/// Determines whether is pri method (HTTP/2).
/// </summary>
/// <param name="clientStreamReader">The client stream reader.</param>
/// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsPriMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, int bufferSize, CancellationToken cancellationToken = default)
internal static Task<int> IsPriMethod(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(clientStreamReader, bufferPool, bufferSize, "PRI", cancellationToken);
return startsWith(clientStreamReader, bufferPool, "PRI", cancellationToken);
}
/// <summary>
/// Determines whether the stream starts with the given string.
/// </summary>
/// <param name="clientStreamReader">The client stream reader.</param>
/// <param name="expectedStart">The expected start.</param>
/// <returns>
/// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
/// </returns>
private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, int bufferSize, string expectedStart, CancellationToken cancellationToken = default)
private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
{
int iRet = -1;
const int lengthToCheck = 10;
byte[] buffer = null;
try
{
buffer = bufferPool.GetBuffer(Math.Max(bufferSize, lengthToCheck));
buffer = bufferPool.GetBuffer(Math.Max(bufferPool.BufferSize, lengthToCheck));
int peeked = await clientStreamReader.PeekBytesAsync(buffer, 0, 0, lengthToCheck, cancellationToken);
......
......@@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Helpers
{
internal sealed class HttpRequestWriter : HttpWriter
{
internal HttpRequestWriter(Stream stream, IBufferPool bufferPool, int bufferSize)
: base(stream, bufferPool, bufferSize)
internal HttpRequestWriter(Stream stream, IBufferPool bufferPool)
: base(stream, bufferPool)
{
}
......
......@@ -9,8 +9,8 @@ namespace Titanium.Web.Proxy.Helpers
{
internal sealed class HttpResponseWriter : HttpWriter
{
internal HttpResponseWriter(Stream stream, IBufferPool bufferPool, int bufferSize)
: base(stream, bufferPool, bufferSize)
internal HttpResponseWriter(Stream stream, IBufferPool bufferPool)
: base(stream, bufferPool)
{
}
......
......@@ -20,15 +20,12 @@ namespace Titanium.Web.Proxy.Helpers
private static readonly Encoding encoder = Encoding.ASCII;
internal HttpWriter(Stream stream, IBufferPool bufferPool, int bufferSize)
internal HttpWriter(Stream stream, IBufferPool bufferPool)
{
BufferSize = bufferSize;
this.stream = stream;
this.bufferPool = bufferPool;
}
internal int BufferSize { get; }
/// <summary>
/// Writes a line async
/// </summary>
......@@ -48,9 +45,9 @@ namespace Titanium.Web.Proxy.Helpers
{
int newLineChars = addNewLine ? newLine.Length : 0;
int charCount = value.Length;
if (charCount < BufferSize - newLineChars)
if (charCount < bufferPool.BufferSize - newLineChars)
{
var buffer = bufferPool.GetBuffer(BufferSize);
var buffer = bufferPool.GetBuffer();
try
{
int idx = encoder.GetBytes(value, 0, charCount, buffer, 0);
......@@ -246,7 +243,7 @@ namespace Titanium.Web.Proxy.Helpers
private async Task copyBytesFromStream(ICustomStreamReader reader, long count, Action<byte[], int, int> onCopy,
CancellationToken cancellationToken)
{
var buffer = bufferPool.GetBuffer(BufferSize);
var buffer = bufferPool.GetBuffer();
try
{
......
using System;
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
......@@ -8,38 +9,32 @@ using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.Helpers
{
internal enum IpVersion
{
Ipv4 = 1,
Ipv6 = 2
}
internal class TcpHelper
{
/// <summary>
/// Gets the process id by local port number.
/// </summary>
/// <returns>Process id.</returns>
internal static unsafe int GetProcessIdByLocalPort(IpVersion ipVersion, int localPort)
internal static unsafe int GetProcessIdByLocalPort(AddressFamily addressFamily, int localPort)
{
var tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
int addressFamilyValue = addressFamily == AddressFamily.InterNetwork ? NativeMethods.AfInet : NativeMethods.AfInet6;
const int allPid = (int)NativeMethods.TcpTableType.OwnerPidAll;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, allPid, 0) != 0)
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, addressFamilyValue, allPid, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, allPid,
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, addressFamilyValue, allPid,
0) == 0)
{
int rowCount = *(int*)tcpTable;
uint portInNetworkByteOrder = toNetworkByteOrder((uint)localPort);
if (ipVersion == IpVersion.Ipv4)
if (addressFamily == AddressFamily.InterNetwork)
{
var rowPtr = (NativeMethods.TcpRow*)(tcpTable + 4);
......@@ -102,23 +97,21 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferPool"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
private static async Task sendRawTap(Stream clientStream, Stream serverStream,
IBufferPool bufferPool, int bufferSize,
private static async Task sendRawTap(Stream clientStream, Stream serverStream, IBufferPool bufferPool,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{
// Now async relay all server=>client & client=>server data
var sendRelay =
clientStream.CopyToAsync(serverStream, onDataSend, bufferPool, bufferSize, cancellationTokenSource.Token);
clientStream.CopyToAsync(serverStream, onDataSend, bufferPool, cancellationTokenSource.Token);
var receiveRelay =
serverStream.CopyToAsync(clientStream, onDataReceive, bufferPool, bufferSize, cancellationTokenSource.Token);
serverStream.CopyToAsync(clientStream, onDataReceive, bufferPool, cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
......@@ -134,20 +127,18 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferPool"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static Task SendRaw(Stream clientStream, Stream serverStream,
IBufferPool bufferPool, int bufferSize,
internal static Task SendRaw(Stream clientStream, Stream serverStream, IBufferPool bufferPool,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{
// todo: fix APM mode
return sendRawTap(clientStream, serverStream, bufferPool, bufferSize, onDataSend, onDataReceive,
return sendRawTap(clientStream, serverStream, bufferPool, onDataSend, onDataReceive,
cancellationTokenSource,
exceptionFunc);
}
......
......@@ -42,14 +42,6 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public bool DecryptSsl { get; }
/// <summary>
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
......
......@@ -19,8 +19,8 @@ namespace Titanium.Web.Proxy.Network
private readonly FileStream fileStreamSent;
public DebugCustomBufferedStream(Guid connectionId, string type, Stream baseStream, IBufferPool bufferPool, int bufferSize, bool leaveOpen = false)
: base(baseStream, bufferPool, bufferSize, leaveOpen)
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);
......
......@@ -60,8 +60,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
// If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
var ipVersion = endPoint.IpV6Enabled ? IpVersion.Ipv6 : IpVersion.Ipv4;
processId = TcpHelper.GetProcessIdByLocalPort(ipVersion, remoteEndPoint.Port);
processId = TcpHelper.GetProcessIdByLocalPort(endPoint.IpAddress.AddressFamily, remoteEndPoint.Port);
}
else
{
......
......@@ -76,8 +76,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Gets the connection cache key.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="session">The session event arguments.</param>
/// <param name="applicationProtocol"></param>
/// <param name="applicationProtocol">The application protocol.</param>
/// <returns></returns>
internal async Task<string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase session,
SslApplicationProtocol applicationProtocol)
......@@ -340,11 +341,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool);
if (useUpstreamProxy && (isConnect || isHttps))
{
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
var writer = new HttpRequestWriter(stream, proxyServer.BufferPool);
var connectRequest = new ConnectRequest
{
OriginalUrl = $"{remoteHostName}:{remotePort}",
......@@ -379,7 +380,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool);
var options = new SslClientAuthenticationOptions
{
......@@ -423,7 +424,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
IsHttps = isHttps,
NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
UseUpstreamProxy = useUpstreamProxy,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool),
Stream = stream,
Version = httpVersion
};
......
......@@ -111,6 +111,7 @@ namespace Titanium.Web.Proxy
/// Create an authentication required response.
/// </summary>
/// <param name="description">Response description.</param>
/// <param name="continuation">The continuation.</param>
/// <returns></returns>
private Response createAuthentication407Response(string description, string continuation = null)
{
......
......@@ -95,12 +95,7 @@ namespace Titanium.Web.Proxy
bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false)
{
if (BufferPool == null)
{
BufferPool = new DefaultBufferPool();
}
BufferPool = new DefaultBufferPool();
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(this);
if (RunTime.IsWindows && !RunTime.IsUwpOnWindows)
......@@ -190,12 +185,6 @@ namespace Titanium.Web.Proxy
/// </summary>
public bool NoDelay { get; set; } = true;
/// <summary>
/// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes.
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete.
/// This will also determine the pool eviction time when connection pool is enabled.
......
......@@ -9,8 +9,23 @@ namespace Titanium.Web.Proxy.StreamExtended.BufferPool
/// Works well when all consumers ask for buffers with the same size.
/// If your application would use variable size buffers consider implementing IBufferPool using System.Buffers library from Microsoft.
/// </summary>
public class DefaultBufferPool : IBufferPool
internal class DefaultBufferPool : IBufferPool
{
/// <summary>
/// Buffer size in bytes used throughout this proxy.
/// Default value is 8192 bytes.
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// Gets a buffer with a default size.
/// </summary>
/// <returns></returns>
public byte[] GetBuffer()
{
return ArrayPool<byte>.Shared.Rent(BufferSize);
}
/// <summary>
/// Gets a buffer.
/// </summary>
......
......@@ -8,6 +8,10 @@ namespace Titanium.Web.Proxy.StreamExtended.BufferPool
/// </summary>
public interface IBufferPool : IDisposable
{
int BufferSize { get; }
byte[] GetBuffer();
byte[] GetBuffer(int bufferSize);
void ReturnBuffer(byte[] buffer);
......
......@@ -17,8 +17,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
private readonly IBufferPool bufferPool;
public int BufferSize { get; }
private int bufferLength;
private byte[] buffer;
......@@ -31,12 +29,11 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public long ReadBytes { get; private set; }
public CopyStream(ICustomStreamReader reader, ICustomStreamWriter writer, IBufferPool bufferPool, int bufferSize)
public CopyStream(ICustomStreamReader reader, ICustomStreamWriter writer, IBufferPool bufferPool)
{
this.reader = reader;
this.writer = writer;
BufferSize = bufferSize;
buffer = bufferPool.GetBuffer(bufferSize);
buffer = bufferPool.GetBuffer();
this.bufferPool = bufferPool;
}
......@@ -94,7 +91,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
int result = reader.Read(buffer, offset, count);
if (result > 0)
{
if (bufferLength + result > BufferSize)
if (bufferLength + result > bufferPool.BufferSize)
{
Flush();
}
......@@ -113,7 +110,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
int result = await reader.ReadAsync(buffer, offset, count, cancellationToken);
if (result > 0)
{
if (bufferLength + result > BufferSize)
if (bufferLength + result > bufferPool.BufferSize)
{
await FlushAsync(cancellationToken);
}
......
......@@ -18,8 +18,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
Position = startPosition;
}
int ICustomStreamReader.BufferSize => baseStream.BufferSize;
/// <summary>
/// Gets a value indicating whether data is available.
/// </summary>
......@@ -92,11 +90,12 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken)
Task<int> ICustomStreamReader.PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken)
{
return baseStream.PeekBytesAsync(buffer, offset, index, size, cancellationToken);
return baseStream.PeekBytesAsync(buffer, offset, index, count, cancellationToken);
}
/// <summary>
......@@ -114,7 +113,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// Reads a byte from buffer.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception">Buffer is empty</exception>
byte ICustomStreamReader.ReadByteFromBuffer()
{
return ReadByte();
......
......@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// of UTF-8 encoded string or raw bytes asynchronously from last read position.
/// </summary>
/// <seealso cref="System.IO.Stream" />
public class CustomBufferedStream : Stream, ICustomStreamReader
internal class CustomBufferedStream : Stream, ICustomStreamReader
{
private readonly bool leaveOpen;
private byte[] streamBuffer;
......@@ -35,8 +35,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
private readonly IBufferPool bufferPool;
public int BufferSize { get; }
public event EventHandler<DataEventArgs> DataRead;
public event EventHandler<DataEventArgs> DataWrite;
......@@ -68,14 +66,12 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bufferPool">Bufferpool.</param>
/// <param name="bufferSize">Size of the buffer.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream open after disposing the <see cref="T:CustomBufferedStream" /> object; otherwise, <see langword="false" />.</param>
public CustomBufferedStream(Stream baseStream, IBufferPool bufferPool, int bufferSize, bool leaveOpen = false)
public CustomBufferedStream(Stream baseStream, IBufferPool bufferPool, bool leaveOpen = false)
{
BaseStream = baseStream;
BufferSize = bufferSize;
this.leaveOpen = leaveOpen;
streamBuffer = bufferPool.GetBuffer(bufferSize);
streamBuffer = bufferPool.GetBuffer();
this.bufferPool = bufferPool;
}
......@@ -278,9 +274,10 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken = default)
public async Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default)
{
if (Available <= index)
{
......@@ -288,19 +285,19 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
}
// When index is greater than the buffer size
if (streamBuffer.Length <= (index + size))
if (streamBuffer.Length <= (index + count))
{
throw new Exception("Requested Peek index and size exceeds the buffer size. Consider increasing the buffer size.");
}
if (Available <= (index + size))
if (Available <= (index + count))
{
return -1;
}
Buffer.BlockCopy(streamBuffer, index, buffer, offset, size);
Buffer.BlockCopy(streamBuffer, index, buffer, offset, count);
return size;
return count;
}
/// <summary>
......@@ -359,7 +356,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value)
{
var buffer = bufferPool.GetBuffer(BufferSize);
var buffer = bufferPool.GetBuffer();
try
{
buffer[0] = value;
......@@ -576,7 +573,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
int bufferDataLength = 0;
// try to use buffer from the buffer pool, usually it is enough
var bufferPoolBuffer = bufferPool.GetBuffer(reader.BufferSize);
var bufferPoolBuffer = bufferPool.GetBuffer();
var buffer = bufferPoolBuffer;
try
......
......@@ -9,8 +9,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// </summary>
public interface ICustomStreamReader
{
int BufferSize { get; }
int Available { get; }
bool DataAvailable { get; }
......@@ -43,9 +41,10 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <param name="buffer">The buffer to copy.</param>
/// <param name="offset">The offset where copying.</param>
/// <param name="index">The index.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int size, CancellationToken cancellationToken = default);
Task<int> PeekBytesAsync(byte[] buffer, int offset, int index, int count, CancellationToken cancellationToken = default);
byte ReadByteFromBuffer();
......
......@@ -7,7 +7,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <summary>
/// Mimic a Task but you can set AsyncState
/// </summary>
/// <typeparam name="T"></typeparam>
public class TaskResult : IAsyncResult
{
Task Task;
......@@ -31,7 +30,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
}
/// <summary>
/// Mimic a Task<T> but you can set AsyncState
/// Mimic a Task&lt;T&gt; but you can set AsyncState
/// </summary>
/// <typeparam name="T"></typeparam>
public class TaskResult<T> : IAsyncResult
......
......@@ -10,7 +10,7 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <summary>
/// Use this class to peek SSL client/server hello information.
/// </summary>
public class SslTools
internal class SslTools
{
/// <summary>
/// Is the given stream starts with an SSL client hello?
......
......@@ -32,8 +32,8 @@ namespace Titanium.Web.Proxy
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
SslStream sslStream = null;
......@@ -78,9 +78,9 @@ namespace Titanium.Web.Proxy
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
// HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferPool, BufferSize);
clientStream = new CustomBufferedStream(sslStream, BufferPool);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool);
}
catch (Exception e)
{
......@@ -109,7 +109,7 @@ namespace Titanium.Web.Proxy
if (available > 0)
{
// send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
var data = BufferPool.GetBuffer();
try
{
// clientStream.Available should be at most BufferSize because it is using the same buffer size
......@@ -124,7 +124,7 @@ namespace Titanium.Web.Proxy
if (!clientStream.IsClosed && !connection.Stream.IsClosed)
{
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, BufferSize,
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool,
null, null, cancellationTokenSource, ExceptionFunc);
}
}
......
......@@ -63,10 +63,8 @@ namespace Titanium.Web.Proxy
await invokeBeforeResponse(args);
}
await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferPool, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
cancellationTokenSource, ExceptionFunc);
await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferPool,
args.OnDataSent, args.OnDataReceived, cancellationTokenSource, ExceptionFunc);
}
}
}
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