diff --git a/src/Titanium.Web.Proxy/Certificates/CertificateManager.cs b/src/Titanium.Web.Proxy/Certificates/CertificateManager.cs index d4992c91a..083b1a3f3 100644 --- a/src/Titanium.Web.Proxy/Certificates/CertificateManager.cs +++ b/src/Titanium.Web.Proxy/Certificates/CertificateManager.cs @@ -120,7 +120,7 @@ private ICertificateMaker certEngine /// internal CertificateManager(string? rootCertificateName, string? rootCertificateIssuerName, bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { ExceptionFunc = exceptionFunc; @@ -167,7 +167,7 @@ internal CertificateManager(string? rootCertificateName, string? rootCertificate /// /// Exception handler /// - internal ExceptionHandler ExceptionFunc { get; set; } + internal ExceptionHandler? ExceptionFunc { get; set; } /// /// Select Certificate Engine. @@ -339,7 +339,7 @@ private void installCertificate(StoreName storeName, StoreLocation storeLocation } catch (Exception e) { - ExceptionFunc( + onException( new Exception("Failed to make system trust root certificate " + $" for {storeName}\\{storeLocation} store location. You may need admin rights.", e)); @@ -360,7 +360,7 @@ private void uninstallCertificate(StoreName storeName, StoreLocation storeLocati { if (certificate == null) { - ExceptionFunc(new Exception("Could not remove certificate as it is null or empty.")); + onException(new Exception("Could not remove certificate as it is null or empty.")); return; } @@ -374,9 +374,8 @@ private void uninstallCertificate(StoreName storeName, StoreLocation storeLocati } catch (Exception e) { - ExceptionFunc( - new Exception("Failed to remove root certificate trust " - + $" for {storeLocation} store location. You may need admin rights.", e)); + onException(new Exception("Failed to remove root certificate trust " + + $" for {storeLocation} store location. You may need admin rights.", e)); } finally { @@ -408,6 +407,11 @@ private X509Certificate2 makeCertificate(string certificateName, bool isRootCert return certificate; } + private void onException(Exception exception) + { + ExceptionFunc?.Invoke(exception); + } + private static ConcurrentDictionary saveCertificateLocks = new ConcurrentDictionary(); @@ -434,13 +438,13 @@ private static ConcurrentDictionary saveCertificateLocks if (certificate != null && certificate.NotAfter <= DateTime.Now) { - ExceptionFunc(new Exception($"Cached certificate for {subjectName} has expired.")); + onException(new Exception($"Cached certificate for {subjectName} has expired.")); certificate = null; } } catch (Exception e) { - ExceptionFunc(new Exception("Failed to load fake certificate.", e)); + onException(new Exception("Failed to load fake certificate.", e)); certificate = null; } @@ -472,7 +476,7 @@ private static ConcurrentDictionary saveCertificateLocks } catch (Exception e) { - ExceptionFunc(new Exception("Failed to save fake certificate.", e)); + onException(new Exception("Failed to save fake certificate.", e)); } }); } @@ -484,7 +488,7 @@ private static ConcurrentDictionary saveCertificateLocks } catch (Exception e) { - ExceptionFunc(e); + onException(e); certificate = null; } @@ -628,7 +632,7 @@ public bool CreateRootCertificate(bool persistToFile = true) if (rootCert != null && rootCert.NotAfter <= DateTime.Now) { - ExceptionFunc(new Exception("Loaded root certificate has expired.")); + onException(new Exception("Loaded root certificate has expired.")); return false; } @@ -641,7 +645,7 @@ public bool CreateRootCertificate(bool persistToFile = true) catch (Exception e) { // root cert cannot be loaded - ExceptionFunc(new Exception("Root cert cannot be loaded.", e)); + onException(new Exception("Root cert cannot be loaded.", e)); } } @@ -651,7 +655,7 @@ public bool CreateRootCertificate(bool persistToFile = true) } catch (Exception e) { - ExceptionFunc(e); + onException(e); } if (persistToFile && RootCertificate != null) @@ -664,14 +668,14 @@ public bool CreateRootCertificate(bool persistToFile = true) } catch (Exception e) { - ExceptionFunc(new Exception("An error happened when clearing certificate cache.", e)); + onException(new Exception("An error happened when clearing certificate cache.", e)); } certificateCache.SaveRootCertificate(PfxFilePath, PfxPassword, RootCertificate); } catch (Exception e) { - ExceptionFunc(e); + onException(e); } } @@ -691,7 +695,7 @@ public bool CreateRootCertificate(bool persistToFile = true) if (rootCert != null && rootCert.NotAfter <= DateTime.Now) { - ExceptionFunc(new ArgumentException("Loaded root certificate has expired.")); + onException(new ArgumentException("Loaded root certificate has expired.")); return null; } @@ -699,7 +703,7 @@ public bool CreateRootCertificate(bool persistToFile = true) } catch (Exception e) { - ExceptionFunc(e); + onException(e); return null; } } @@ -808,7 +812,7 @@ public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) } catch (Exception e) { - ExceptionFunc(e); + onException(e); return false; } @@ -1002,7 +1006,11 @@ void dispose(bool disposing) return; } - clearCertificatesTokenSource.Dispose(); + if (disposing) + { + clearCertificatesTokenSource.Dispose(); + } + disposed = true; } diff --git a/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMaker.cs b/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMaker.cs index c4ad9e65c..fc13eb430 100644 --- a/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMaker.cs +++ b/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMaker.cs @@ -34,9 +34,9 @@ internal class BCCertificateMaker : ICertificateMaker // Set this flag to true when exception detected to avoid further exceptions private static bool doNotSetFriendlyName; - private readonly ExceptionHandler exceptionFunc; + private readonly ExceptionHandler? exceptionFunc; - internal BCCertificateMaker(ExceptionHandler exceptionFunc, int certificateValidDays) + internal BCCertificateMaker(ExceptionHandler? exceptionFunc, int certificateValidDays) { this.certificateValidDays = certificateValidDays; this.exceptionFunc = exceptionFunc; diff --git a/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMakerFast.cs b/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMakerFast.cs index 3b3f9d330..e90e6a440 100644 --- a/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMakerFast.cs +++ b/src/Titanium.Web.Proxy/Certificates/Makers/BCCertificateMakerFast.cs @@ -34,11 +34,11 @@ internal class BCCertificateMakerFast : ICertificateMaker // Set this flag to true when exception detected to avoid further exceptions private static bool doNotSetFriendlyName; - private readonly ExceptionHandler exceptionFunc; + private readonly ExceptionHandler? exceptionFunc; public AsymmetricCipherKeyPair KeyPair { get; set; } - internal BCCertificateMakerFast(ExceptionHandler exceptionFunc, int certificateValidDays) + internal BCCertificateMakerFast(ExceptionHandler? exceptionFunc, int certificateValidDays) { this.certificateValidDays = certificateValidDays; this.exceptionFunc = exceptionFunc; diff --git a/src/Titanium.Web.Proxy/Certificates/Makers/WinCertificateMaker.cs b/src/Titanium.Web.Proxy/Certificates/Makers/WinCertificateMaker.cs index 01fe8ec39..dd845679b 100644 --- a/src/Titanium.Web.Proxy/Certificates/Makers/WinCertificateMaker.cs +++ b/src/Titanium.Web.Proxy/Certificates/Makers/WinCertificateMaker.cs @@ -17,7 +17,7 @@ internal class WinCertificateMaker : ICertificateMaker // Validity Days for Root Certificates Generated. private int certificateValidDays; - private readonly ExceptionHandler exceptionFunc; + private readonly ExceptionHandler? exceptionFunc; private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0"; @@ -53,7 +53,7 @@ internal class WinCertificateMaker : ICertificateMaker /// /// Constructor. /// - internal WinCertificateMaker(ExceptionHandler exceptionFunc, int certificateValidDays) + internal WinCertificateMaker(ExceptionHandler? exceptionFunc, int certificateValidDays) { this.certificateValidDays = certificateValidDays; this.exceptionFunc = exceptionFunc; diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index a36a36503..43644f1d1 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -144,7 +144,7 @@ internal void OnMultipartRequestPartSent(ReadOnlySpan boundary, HeaderColl } catch (Exception ex) { - ExceptionFunc(new Exception("Exception thrown in user event", ex)); + OnException(new Exception("Exception thrown in user event", ex)); } } @@ -684,7 +684,8 @@ public void TerminateServerConnection() HttpClient.CloseServerConnection = true; } - private bool disposed = false; + private bool disposed; + protected override void Dispose(bool disposing) { if (disposed) @@ -700,6 +701,11 @@ protected override void Dispose(bool disposing) ~SessionEventArgs() { +#if DEBUG + // Finalizer should not be called + System.Diagnostics.Debugger.Break(); +#endif + Dispose(false); } } diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs index 8828999c8..68493e5f1 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgsBase.cs @@ -37,7 +37,7 @@ public abstract class SessionEventArgsBase : ProxyEventArgsBase, IDisposable public Guid ServerConnectionId => HttpClient.HasConnection ? ServerConnection.Id : Guid.Empty; protected readonly IBufferPool BufferPool; - protected readonly ExceptionHandler ExceptionFunc; + protected readonly ExceptionHandler? ExceptionFunc; private bool enableWinAuth; /// @@ -150,6 +150,11 @@ public bool EnableWinAuth /// public Exception? Exception { get; internal set; } + protected void OnException(Exception exception) + { + ExceptionFunc?.Invoke(exception); + } + private bool disposed = false; protected virtual void Dispose(bool disposing) @@ -159,19 +164,18 @@ protected virtual void Dispose(bool disposing) return; } - disposed = true; - if (disposing) { CustomUpStreamProxyUsed = null; - DataSent = null; - DataReceived = null; - Exception = null; + HttpClient.FinishSession(); } - HttpClient.FinishSession(); + DataSent = null; + DataReceived = null; + Exception = null; + disposed = true; } public void Dispose() @@ -182,6 +186,11 @@ public void Dispose() ~SessionEventArgsBase() { +#if DEBUG + // Finalizer should not be called + System.Diagnostics.Debugger.Break(); +#endif + Dispose(false); } @@ -203,7 +212,7 @@ internal void OnDataSent(byte[] buffer, int offset, int count) } catch (Exception ex) { - ExceptionFunc(new Exception("Exception thrown in user event", ex)); + OnException(new Exception("Exception thrown in user event", ex)); } } @@ -215,7 +224,7 @@ internal void OnDataReceived(byte[] buffer, int offset, int count) } catch (Exception ex) { - ExceptionFunc(new Exception("Exception thrown in user event", ex)); + OnException(new Exception("Exception thrown in user event", ex)); } } diff --git a/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs index e41c1aa96..5eaf32d85 100644 --- a/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/TunnelConnectEventArgs.cs @@ -60,7 +60,7 @@ internal void OnDecryptedDataSent(byte[] buffer, int offset, int count) } catch (Exception ex) { - ExceptionFunc(new Exception("Exception thrown in user event", ex)); + OnException(new Exception("Exception thrown in user event", ex)); } } @@ -72,8 +72,18 @@ internal void OnDecryptedDataReceived(byte[] buffer, int offset, int count) } catch (Exception ex) { - ExceptionFunc(new Exception("Exception thrown in user event", ex)); + OnException(new Exception("Exception thrown in user event", ex)); } } + + ~TunnelConnectSessionEventArgs() + { +#if DEBUG + // Finalizer should not be called + System.Diagnostics.Debugger.Break(); +#endif + + Dispose(false); + } } } diff --git a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs index b40e80e2f..06163f5fe 100644 --- a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs +++ b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs @@ -38,10 +38,10 @@ private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnect Task? prefetchConnectionTask = null; bool closeServerConnection = false; + TunnelConnectSessionEventArgs? connectArgs = null; + try { - TunnelConnectSessionEventArgs? connectArgs = null; - var method = await HttpHelper.GetMethod(clientStream, BufferPool, cancellationToken); if (clientStream.IsClosed) { @@ -402,6 +402,7 @@ await Http2Helper.SendHttp2(clientStream, connection.Stream, await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection); clientStream.Dispose(); + connectArgs?.Dispose(); } } } diff --git a/src/Titanium.Web.Proxy/Extensions/FuncExtensions.cs b/src/Titanium.Web.Proxy/Extensions/FuncExtensions.cs index 9e7c018f3..2665e3751 100644 --- a/src/Titanium.Web.Proxy/Extensions/FuncExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/FuncExtensions.cs @@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Extensions internal static class FuncExtensions { internal static async Task InvokeAsync(this AsyncEventHandler callback, object sender, T args, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { var invocationList = callback.GetInvocationList(); @@ -18,7 +18,7 @@ internal static async Task InvokeAsync(this AsyncEventHandler callback, ob } private static async Task internalInvokeAsync(AsyncEventHandler callback, object sender, T args, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { try { @@ -26,7 +26,7 @@ private static async Task internalInvokeAsync(AsyncEventHandler callback, } catch (Exception e) { - exceptionFunc(new Exception("Exception thrown in user event", e)); + exceptionFunc?.Invoke(new Exception("Exception thrown in user event", e)); } } } diff --git a/src/Titanium.Web.Proxy/Handlers/ProxyAuthorizationHandler.cs b/src/Titanium.Web.Proxy/Handlers/ProxyAuthorizationHandler.cs index 4bbcbd751..67e88397a 100644 --- a/src/Titanium.Web.Proxy/Handlers/ProxyAuthorizationHandler.cs +++ b/src/Titanium.Web.Proxy/Handlers/ProxyAuthorizationHandler.cs @@ -72,7 +72,7 @@ private async Task checkAuthorization(SessionEventArgsBase session) } catch (Exception e) { - ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e, + onException(null, new ProxyAuthorizationException("Error whilst authorizing request", session, e, httpHeaders)); // Return not authorized diff --git a/src/Titanium.Web.Proxy/Helpers/TcpHelper.cs b/src/Titanium.Web.Proxy/Helpers/TcpHelper.cs index 97e80301f..fe4880069 100644 --- a/src/Titanium.Web.Proxy/Helpers/TcpHelper.cs +++ b/src/Titanium.Web.Proxy/Helpers/TcpHelper.cs @@ -133,7 +133,7 @@ private static async Task sendRawTap(Stream clientStream, Stream serverStream, I internal static Task SendRaw(Stream clientStream, Stream serverStream, IBufferPool bufferPool, Action? onDataSend, Action? onDataReceive, CancellationTokenSource cancellationTokenSource, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { // todo: fix APM mode return sendRawTap(clientStream, serverStream, bufferPool, onDataSend, onDataReceive, diff --git a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs index 56d21a355..e328cf87f 100644 --- a/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs +++ b/src/Titanium.Web.Proxy/Helpers/WinHttp/WinHttpWebProxyFinder.cs @@ -349,32 +349,21 @@ private enum AutoWebProxyState private bool disposed = false; - void dispose(bool disposing) + public void Dispose() { if (disposed) { return; } - disposed = true; - if (session == null || session.IsInvalid) { return; } session.Close(); - } - - public void Dispose() - { - dispose(true); - GC.SuppressFinalize(this); - } - ~WinHttpWebProxyFinder() - { - dispose(false); + disposed = true; } } } diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index 125e5185a..3705cd42f 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -34,7 +34,7 @@ internal static async Task SendHttp2(Stream clientStream, Stream serverStream, Func sessionFactory, Func onBeforeRequest, Func onBeforeResponse, CancellationTokenSource cancellationTokenSource, Guid connectionId, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { var clientSettings = new Http2Settings(); var serverSettings = new Http2Settings(); @@ -62,7 +62,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Func sessionFactory, ConcurrentDictionary sessions, Func onBeforeRequestResponse, Guid connectionId, bool isClient, CancellationToken cancellationToken, - ExceptionHandler exceptionFunc) + ExceptionHandler? exceptionFunc) { int headerTableSize = 0; Decoder? decoder = null; @@ -281,7 +281,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, } catch (Exception ex) { - exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args)); + exceptionFunc?.Invoke(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args)); } if (!endHeaders) @@ -351,7 +351,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, if (streamId == 0) { // connection error - exceptionFunc(new ProxyHttpException("HTTP/2 connection error. Error code: " + errorCode, null, args)); + exceptionFunc?.Invoke(new ProxyHttpException("HTTP/2 connection error. Error code: " + errorCode, null, args)); return; } else @@ -361,7 +361,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, if (errorCode != 8 /*cancel*/) { - exceptionFunc(new ProxyHttpException("HTTP/2 stream error. Error code: " + errorCode, null, args)); + exceptionFunc?.Invoke(new ProxyHttpException("HTTP/2 stream error. Error code: " + errorCode, null, args)); } } } diff --git a/src/Titanium.Web.Proxy/Models/ExplicitProxyEndPoint.cs b/src/Titanium.Web.Proxy/Models/ExplicitProxyEndPoint.cs index a364a9b2d..558c6dce3 100644 --- a/src/Titanium.Web.Proxy/Models/ExplicitProxyEndPoint.cs +++ b/src/Titanium.Web.Proxy/Models/ExplicitProxyEndPoint.cs @@ -43,7 +43,7 @@ public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = tr public event AsyncEventHandler? BeforeTunnelConnectResponse; internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, - TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc) + TunnelConnectSessionEventArgs connectArgs, ExceptionHandler? exceptionFunc) { if (BeforeTunnelConnectRequest != null) { @@ -52,7 +52,7 @@ internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, } internal async Task InvokeBeforeTunnelConnectResponse(ProxyServer proxyServer, - TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc, bool isClientHello = false) + TunnelConnectSessionEventArgs connectArgs, ExceptionHandler? exceptionFunc, bool isClientHello = false) { if (BeforeTunnelConnectResponse != null) { diff --git a/src/Titanium.Web.Proxy/Models/SocksProxyEndPoint.cs b/src/Titanium.Web.Proxy/Models/SocksProxyEndPoint.cs index 3cc428011..915f005f7 100644 --- a/src/Titanium.Web.Proxy/Models/SocksProxyEndPoint.cs +++ b/src/Titanium.Web.Proxy/Models/SocksProxyEndPoint.cs @@ -37,7 +37,7 @@ public SocksProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) public event AsyncEventHandler? BeforeSslAuthenticate; internal override async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, - BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc) + BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc) { if (BeforeSslAuthenticate != null) { diff --git a/src/Titanium.Web.Proxy/Models/TransparentBaseProxyEndPoint.cs b/src/Titanium.Web.Proxy/Models/TransparentBaseProxyEndPoint.cs index 9ebca387f..f4bf7a7fe 100644 --- a/src/Titanium.Web.Proxy/Models/TransparentBaseProxyEndPoint.cs +++ b/src/Titanium.Web.Proxy/Models/TransparentBaseProxyEndPoint.cs @@ -18,6 +18,6 @@ protected TransparentBaseProxyEndPoint(IPAddress ipAddress, int port, bool decry } internal abstract Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, - BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc); + BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc); } } diff --git a/src/Titanium.Web.Proxy/Models/TransparentProxyEndPoint.cs b/src/Titanium.Web.Proxy/Models/TransparentProxyEndPoint.cs index 6f58a4956..58fc755ac 100644 --- a/src/Titanium.Web.Proxy/Models/TransparentProxyEndPoint.cs +++ b/src/Titanium.Web.Proxy/Models/TransparentProxyEndPoint.cs @@ -37,7 +37,7 @@ public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = public event AsyncEventHandler? BeforeSslAuthenticate; internal override async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, - BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc) + BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler? exceptionFunc) { if (BeforeSslAuthenticate != null) { diff --git a/src/Titanium.Web.Proxy/Network/RetryPolicy.cs b/src/Titanium.Web.Proxy/Network/RetryPolicy.cs index 19863cba0..ab6048ff4 100644 --- a/src/Titanium.Web.Proxy/Network/RetryPolicy.cs +++ b/src/Titanium.Web.Proxy/Network/RetryPolicy.cs @@ -49,29 +49,19 @@ internal async Task ExecuteAsync(Func FillBufferAsync(CancellationToken cancellationToken return encoding.GetString(buffer, 0, bufferDataLength); } - /// - /// Read until the last new line, ignores the result - /// - /// - public async Task ReadAndIgnoreAllLinesAsync(CancellationToken cancellationToken = default) - { - while (!string.IsNullOrEmpty(await ReadLineAsync(cancellationToken))) - { - } - } - /// /// Base Stream.BeginRead will call this.Read and block thread (we don't want this, Network stream handles async) /// In order to really async Reading Launch this.ReadAsync as Task will fire NetworkStream.ReadAsync diff --git a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpClientConnection.cs b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpClientConnection.cs index a1745fb37..14aa5562c 100644 --- a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpClientConnection.cs +++ b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpClientConnection.cs @@ -90,13 +90,16 @@ protected virtual void Dispose(bool disposing) await Task.Delay(1000); proxyServer.UpdateClientConnectionCount(false); - try + if (disposing) { - tcpClientSocket.Close(); - } - catch - { - // ignore + try + { + tcpClientSocket.Close(); + } + catch + { + // ignore + } } }); @@ -111,6 +114,11 @@ public void Dispose() ~TcpClientConnection() { +#if DEBUG + // Finalizer should not be called + System.Diagnostics.Debugger.Break(); +#endif + Dispose(false); } } diff --git a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs index 23e86a54e..acd457469 100644 --- a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs +++ b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs @@ -543,14 +543,14 @@ internal Task GetServerConnection(ProxyServer proxyServer, await stream.WriteRequestAsync(connectRequest, cancellationToken); var httpStatus = await stream.ReadResponseStatus(cancellationToken); + var headers = new HeaderCollection(); + await HeaderParser.ReadHeaders(stream, headers, cancellationToken); if (httpStatus.StatusCode != 200 && !httpStatus.Description.EqualsIgnoreCase("OK") && !httpStatus.Description.EqualsIgnoreCase("Connection Established")) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } - - await stream.ReadAndIgnoreAllLinesAsync(cancellationToken); } if (isHttps) @@ -634,7 +634,7 @@ internal Task GetServerConnection(ProxyServer proxyServer, /// /// The Tcp server connection to return. /// Should we just close the connection instead of reusing? - internal async Task Release(TcpServerConnection connection, bool close = false) + internal async Task Release(TcpServerConnection? connection, bool close = false) { if (connection == null) { @@ -705,7 +705,10 @@ internal async Task Release(Task? connectionCreateTask, bo { connection = await connectionCreateTask; } - catch { } + catch + { + // ignore + } finally { if (connection != null) @@ -769,18 +772,17 @@ private async Task clearOutdatedConnections() } catch (Exception e) { - Server.ExceptionFunc(new Exception("An error occurred when disposing server connections.", e)); + Server.ExceptionFunc?.Invoke(new Exception("An error occurred when disposing server connections.", e)); } finally { // cleanup every 3 seconds by default await Task.Delay(1000 * 3); } - } } - private bool disposed = false; + private bool disposed; protected virtual void Dispose(bool disposing) { @@ -791,33 +793,36 @@ protected virtual void Dispose(bool disposing) runCleanUpTask = false; - try + if (disposing) { - @lock.Wait(); - - foreach (var queue in cache.Select(x => x.Value).ToList()) + try { - while (!queue.IsEmpty) + @lock.Wait(); + + foreach (var queue in cache.Select(x => x.Value).ToList()) { - if (queue.TryDequeue(out var connection)) + while (!queue.IsEmpty) { - disposalBag.Add(connection); + if (queue.TryDequeue(out var connection)) + { + disposalBag.Add(connection); + } } } - } - cache.Clear(); - } - finally - { - @lock.Release(); - } + cache.Clear(); + } + finally + { + @lock.Release(); + } - while (!disposalBag.IsEmpty) - { - if (disposalBag.TryTake(out var connection)) + while (!disposalBag.IsEmpty) { - connection?.Dispose(); + if (disposalBag.TryTake(out var connection)) + { + connection?.Dispose(); + } } } diff --git a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpServerConnection.cs b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpServerConnection.cs index b783b39bc..d7132c38a 100644 --- a/src/Titanium.Web.Proxy/Network/TcpConnection/TcpServerConnection.cs +++ b/src/Titanium.Web.Proxy/Network/TcpConnection/TcpServerConnection.cs @@ -99,16 +99,21 @@ protected virtual void Dispose(bool disposing) // so that server have enough time to call close first. // This way we can push tcp Time_Wait to server side when possible. await Task.Delay(1000); + proxyServer.UpdateServerConnectionCount(false); - Stream.Dispose(); - try - { - TcpSocket.Close(); - } - catch + if (disposing) { - // ignore + Stream.Dispose(); + + try + { + TcpSocket.Close(); + } + catch + { + // ignore + } } }); @@ -123,6 +128,11 @@ public void Dispose() ~TcpServerConnection() { +#if DEBUG + // Finalizer should not be called + System.Diagnostics.Debugger.Break(); +#endif + Dispose(false); } } diff --git a/src/Titanium.Web.Proxy/ProxyServer.cs b/src/Titanium.Web.Proxy/ProxyServer.cs index ca0220af6..232c192be 100644 --- a/src/Titanium.Web.Proxy/ProxyServer.cs +++ b/src/Titanium.Web.Proxy/ProxyServer.cs @@ -35,12 +35,6 @@ public partial class ProxyServer : IDisposable internal static ByteString UriSchemeHttp8 = (ByteString)UriSchemeHttp; internal static ByteString UriSchemeHttps8 = (ByteString)UriSchemeHttps; - - /// - /// A default exception log func. - /// - private readonly ExceptionHandler defaultExceptionFunc = e => { }; - /// /// Backing field for exposed public property. /// @@ -301,9 +295,9 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam /// /// Callback for error events in this proxy instance. /// - public ExceptionHandler ExceptionFunc + public ExceptionHandler? ExceptionFunc { - get => exceptionFunc ?? defaultExceptionFunc; + get => exceptionFunc; set { exceptionFunc = value; @@ -866,9 +860,9 @@ private async Task handleClient(Socket tcpClientSocket, ProxyEndPoint endPoint) /// /// The client stream. /// The exception. - private void onException(HttpClientStream clientStream, Exception exception) + private void onException(HttpClientStream? clientStream, Exception exception) { - ExceptionFunc(exception); + ExceptionFunc?.Invoke(exception); } /// @@ -895,7 +889,14 @@ internal void UpdateClientConnectionCount(bool increment) Interlocked.Decrement(ref clientConnectionCount); } - ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty); + try + { + ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty); + } + catch (Exception ex) + { + onException(null, ex); + } } /// @@ -913,7 +914,14 @@ internal void UpdateServerConnectionCount(bool increment) Interlocked.Decrement(ref serverConnectionCount); } - ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty); + try + { + ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty); + } + catch (Exception ex) + { + onException(null, ex); + } } /// @@ -965,11 +973,21 @@ protected virtual void Dispose(bool disposing) if (ProxyRunning) { - Stop(); + try + { + Stop(); + } + catch + { + // ignore + } } - CertificateManager?.Dispose(); - BufferPool?.Dispose(); + if (disposing) + { + CertificateManager?.Dispose(); + BufferPool?.Dispose(); + } } public void Dispose() diff --git a/src/Titanium.Web.Proxy/RequestHandler.cs b/src/Titanium.Web.Proxy/RequestHandler.cs index ba6b502d8..78c2055a5 100644 --- a/src/Titanium.Web.Proxy/RequestHandler.cs +++ b/src/Titanium.Web.Proxy/RequestHandler.cs @@ -190,8 +190,15 @@ await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, clientStream.Connection.NegotiatedApplicationProtocol, cancellationToken, cancellationTokenSource); + var newConnection = result.LatestConnection; + if (connection != newConnection && connection != null) + { + await tcpConnectionFactory.Release(connection); + } + // update connection to latest used connection = result.LatestConnection; + closeServerConnection = !result.Continue; // throw if exception happened diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs index b294244ba..72caadb4f 100644 --- a/src/Titanium.Web.Proxy/ResponseHandler.cs +++ b/src/Titanium.Web.Proxy/ResponseHandler.cs @@ -92,8 +92,13 @@ private async Task handleHttpSessionResponse(SessionEventArgs args) // clear current response await args.ClearResponse(cancellationToken); - await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedApplicationProtocol, + var result = await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedApplicationProtocol, cancellationToken, args.CancellationTokenSource); + if (result.LatestConnection != null) + { + args.HttpClient.SetConnection(result.LatestConnection); + } + return; }