Skip to content
This repository has been archived by the owner on Jul 9, 2023. It is now read-only.

Commit

Permalink
Merge branch 'develop' into beta
Browse files Browse the repository at this point in the history
  • Loading branch information
justcoding121 committed Aug 17, 2021
2 parents 9cb2650 + 4d71be5 commit 6e995d6
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// The server connection was closed upon first read with the new connection from pool.
/// The server connection was closed upon first write with the new connection from pool.
/// Should retry the request with a new connection.
/// </summary>
public class ServerConnectionException : ProxyException
public class RetryableServerConnectionException : ProxyException
{
internal ServerConnectionException(string message) : base(message)
internal RetryableServerConnectionException(string message) : base(message)
{
}

Expand All @@ -17,9 +17,8 @@ internal ServerConnectionException(string message) : base(message)
/// </summary>
/// <param name="message"></param>
/// <param name="e"></param>
internal ServerConnectionException(string message, Exception e) : base(message, e)
internal RetryableServerConnectionException(string message, Exception e) : base(message, e)
{
}

}
}
8 changes: 3 additions & 5 deletions src/Titanium.Web.Proxy/Network/RetryPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,12 @@ internal RetryPolicy(int retries, TcpConnectionFactory tcpConnectionFactory)

while (true)
{
// setup connection
currentConnection ??= await generator();

try
{
// setup connection
currentConnection ??= await generator();

// try
@continue = await action(currentConnection);

}
catch (Exception ex)
{
Expand Down
26 changes: 10 additions & 16 deletions src/Titanium.Web.Proxy/Network/Streams/HttpServerStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,19 @@ internal async ValueTask WriteRequestAsync(Request request, CancellationToken ca

internal async ValueTask<ResponseStatusInfo> ReadResponseStatus(CancellationToken cancellationToken = default)
{
try
{
string httpStatus = await ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed.");

if (httpStatus == string.Empty)
{
// is this really possible?
httpStatus = await ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed. Response status is empty.");
}
string httpStatus = await ReadLineAsync(cancellationToken) ??
throw new IOException("Invalid http status code.");

Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string description);
return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description };
}
catch (Exception e) when (!(e is ServerConnectionException))
if (httpStatus == string.Empty)
{
throw new ServerConnectionException("Server connection was closed. Exception while reading the response status.", e);
// is this really possible?
httpStatus = await ReadLineAsync(cancellationToken) ??
throw new IOException("Response status is empty.");
}

Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string description);
return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description };

}
}
}
17 changes: 15 additions & 2 deletions src/Titanium.Web.Proxy/Network/Streams/HttpStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,21 @@ public ValueTask WriteLineAsync(string value, CancellationToken cancellationToke
internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var buffer = headerBuilder.GetBuffer();
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);

try
{
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
}
catch (IOException e)
{
//throw this as ServerConnectionException so that RetryPolicy can retry with a new server connection.
if (this is HttpServerStream)
{
throw new RetryableServerConnectionException("Server connection was closed. Exception while sending request line and headers.", e);
}

throw;
}
}

/// <summary>
Expand Down Expand Up @@ -1226,7 +1240,6 @@ private async Task copyBodyChunkedAsync(IHttpStreamWriter writer, bool isRequest
protected async ValueTask WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
{
var body = requestResponse.CompressBodyAndUpdateContentLength();

headerBuilder.WriteHeaders(requestResponse.Headers);
await WriteHeadersAsync(headerBuilder, cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,15 +520,17 @@ internal TcpConnectionFactory(ProxyServer server)

if (externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http && (isConnect || isHttps))
{
var authority = $"{remoteHostName}:{remotePort}".GetByteString();
var connectRequest = new ConnectRequest(authority)
var authority = $"{remoteHostName}:{remotePort}";
var authorityBytes = authority.GetByteString();
var connectRequest = new ConnectRequest(authorityBytes)
{
IsHttps = isHttps,
RequestUriString8 = authority,
RequestUriString8 = authorityBytes,
HttpVersion = httpVersion
};

connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
connectRequest.Headers.AddHeader(KnownHeaders.Host, authority);

if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Titanium.Web.Proxy/ProxyServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public partial class ProxyServer : IDisposable
/// <summary>
/// Number of times to retry upon network failures when connection pool is enabled.
/// </summary>
public int NetworkFailureRetryAttempts { get; set; } = 0;
public int NetworkFailureRetryAttempts { get; set; } = 1;

/// <summary>
/// Is the proxy currently running?
Expand Down Expand Up @@ -208,9 +208,9 @@ public partial class ProxyServer : IDisposable
/// <summary>
/// Maximum number of concurrent connections per remote host in cache.
/// Only valid when connection pooling is enabled.
/// Default value is 2.
/// Default value is 4.
/// </summary>
public int MaxCachedConnections { get; set; } = 2;
public int MaxCachedConnections { get; set; } = 4;

/// <summary>
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.
Expand Down
9 changes: 7 additions & 2 deletions src/Titanium.Web.Proxy/RequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,13 @@ public partial class ProxyServer
noCache,
cancellationToken);

// for connection pool, retry fails until cache is exhausted.
return await retryPolicy<ServerConnectionException>().ExecuteAsync(async connection =>
/// Retry with new connection if the initial stream.WriteAsync call to server fails.
/// i.e if request line and headers failed to get send.
/// Do not retry after reading data from client stream,
/// because subsequent try will not have data to read from client
/// and will hang at clientStream.ReadAsync call.
/// So, throw RetryableServerConnectionException only when we are sure we can retry safely.
return await retryPolicy<RetryableServerConnectionException>().ExecuteAsync(async connection =>
{
// set the connection and send request headers
args.HttpClient.SetConnection(connection);
Expand Down
3 changes: 0 additions & 3 deletions tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ public async Task Nested_Proxy_Farm_With_Connection_Cache_Should_Not_Hang()
{
return Task.FromResult(true);
};

proxies2.Add(proxy2);
}

Expand All @@ -212,9 +211,7 @@ public async Task Nested_Proxy_Farm_With_Connection_Cache_Should_Not_Hang()
for (int i = 0; i < 10; i++)
{
var proxy1 = testSuite.GetProxy();
//proxy1.EnableConnectionPool = false;
var proxy2 = proxies2[rnd.Next() % proxies2.Count];

var explicitEndpoint = proxy1.ProxyEndPoints.OfType<ExplicitProxyEndPoint>().First();
explicitEndpoint.BeforeTunnelConnectRequest += (_, e) =>
{
Expand Down

0 comments on commit 6e995d6

Please sign in to comment.