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

Commit 6e995d6

Browse files
committed
Merge branch 'develop' into beta
2 parents 9cb2650 + 4d71be5 commit 6e995d6

File tree

8 files changed

+47
-39
lines changed

8 files changed

+47
-39
lines changed

src/Titanium.Web.Proxy/Exceptions/ServerConnectionException.cs renamed to src/Titanium.Web.Proxy/Exceptions/RetryableServerConnectionException.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
namespace Titanium.Web.Proxy.Exceptions
44
{
55
/// <summary>
6-
/// The server connection was closed upon first read with the new connection from pool.
6+
/// The server connection was closed upon first write with the new connection from pool.
77
/// Should retry the request with a new connection.
88
/// </summary>
9-
public class ServerConnectionException : ProxyException
9+
public class RetryableServerConnectionException : ProxyException
1010
{
11-
internal ServerConnectionException(string message) : base(message)
11+
internal RetryableServerConnectionException(string message) : base(message)
1212
{
1313
}
1414

@@ -17,9 +17,8 @@ internal ServerConnectionException(string message) : base(message)
1717
/// </summary>
1818
/// <param name="message"></param>
1919
/// <param name="e"></param>
20-
internal ServerConnectionException(string message, Exception e) : base(message, e)
20+
internal RetryableServerConnectionException(string message, Exception e) : base(message, e)
2121
{
2222
}
23-
2423
}
2524
}

src/Titanium.Web.Proxy/Network/RetryPolicy.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@ internal async Task<RetryResult> ExecuteAsync(Func<TcpServerConnection, Task<boo
3535

3636
while (true)
3737
{
38+
// setup connection
39+
currentConnection ??= await generator();
40+
3841
try
3942
{
40-
// setup connection
41-
currentConnection ??= await generator();
42-
43-
// try
4443
@continue = await action(currentConnection);
45-
4644
}
4745
catch (Exception ex)
4846
{

src/Titanium.Web.Proxy/Network/Streams/HttpServerStream.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,19 @@ internal async ValueTask WriteRequestAsync(Request request, CancellationToken ca
3030

3131
internal async ValueTask<ResponseStatusInfo> ReadResponseStatus(CancellationToken cancellationToken = default)
3232
{
33-
try
34-
{
35-
string httpStatus = await ReadLineAsync(cancellationToken) ??
36-
throw new ServerConnectionException("Server connection was closed.");
37-
38-
if (httpStatus == string.Empty)
39-
{
40-
// is this really possible?
41-
httpStatus = await ReadLineAsync(cancellationToken) ??
42-
throw new ServerConnectionException("Server connection was closed. Response status is empty.");
43-
}
33+
string httpStatus = await ReadLineAsync(cancellationToken) ??
34+
throw new IOException("Invalid http status code.");
4435

45-
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string description);
46-
return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description };
47-
}
48-
catch (Exception e) when (!(e is ServerConnectionException))
36+
if (httpStatus == string.Empty)
4937
{
50-
throw new ServerConnectionException("Server connection was closed. Exception while reading the response status.", e);
38+
// is this really possible?
39+
httpStatus = await ReadLineAsync(cancellationToken) ??
40+
throw new IOException("Response status is empty.");
5141
}
42+
43+
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string description);
44+
return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description };
45+
5246
}
5347
}
5448
}

src/Titanium.Web.Proxy/Network/Streams/HttpStream.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,21 @@ public ValueTask WriteLineAsync(string value, CancellationToken cancellationToke
932932
internal async Task WriteHeadersAsync(HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
933933
{
934934
var buffer = headerBuilder.GetBuffer();
935-
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
935+
936+
try
937+
{
938+
await WriteAsync(buffer.Array, buffer.Offset, buffer.Count, true, cancellationToken);
939+
}
940+
catch (IOException e)
941+
{
942+
//throw this as ServerConnectionException so that RetryPolicy can retry with a new server connection.
943+
if (this is HttpServerStream)
944+
{
945+
throw new RetryableServerConnectionException("Server connection was closed. Exception while sending request line and headers.", e);
946+
}
947+
948+
throw;
949+
}
936950
}
937951

938952
/// <summary>
@@ -1226,7 +1240,6 @@ private async Task copyBytesToStream(IHttpStreamWriter writer, long count, bool
12261240
protected async ValueTask WriteAsync(RequestResponseBase requestResponse, HeaderBuilder headerBuilder, CancellationToken cancellationToken = default)
12271241
{
12281242
var body = requestResponse.CompressBodyAndUpdateContentLength();
1229-
12301243
headerBuilder.WriteHeaders(requestResponse.Headers);
12311244
await WriteHeadersAsync(headerBuilder, cancellationToken);
12321245

src/Titanium.Web.Proxy/Network/TcpConnection/TcpConnectionFactory.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,15 +520,17 @@ internal Task<TcpServerConnection> GetServerConnection(ProxyServer proxyServer,
520520

521521
if (externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http && (isConnect || isHttps))
522522
{
523-
var authority = $"{remoteHostName}:{remotePort}".GetByteString();
524-
var connectRequest = new ConnectRequest(authority)
523+
var authority = $"{remoteHostName}:{remotePort}";
524+
var authorityBytes = authority.GetByteString();
525+
var connectRequest = new ConnectRequest(authorityBytes)
525526
{
526527
IsHttps = isHttps,
527-
RequestUriString8 = authority,
528+
RequestUriString8 = authorityBytes,
528529
HttpVersion = httpVersion
529530
};
530531

531532
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
533+
connectRequest.Headers.AddHeader(KnownHeaders.Host, authority);
532534

533535
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
534536
{

src/Titanium.Web.Proxy/ProxyServer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam
123123
/// <summary>
124124
/// Number of times to retry upon network failures when connection pool is enabled.
125125
/// </summary>
126-
public int NetworkFailureRetryAttempts { get; set; } = 0;
126+
public int NetworkFailureRetryAttempts { get; set; } = 1;
127127

128128
/// <summary>
129129
/// Is the proxy currently running?
@@ -208,9 +208,9 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam
208208
/// <summary>
209209
/// Maximum number of concurrent connections per remote host in cache.
210210
/// Only valid when connection pooling is enabled.
211-
/// Default value is 2.
211+
/// Default value is 4.
212212
/// </summary>
213-
public int MaxCachedConnections { get; set; } = 2;
213+
public int MaxCachedConnections { get; set; } = 4;
214214

215215
/// <summary>
216216
/// Number of seconds to linger when Tcp connection is in TIME_WAIT state.

src/Titanium.Web.Proxy/RequestHandler.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,13 @@ private async Task<RetryResult> handleHttpSessionRequest(SessionEventArgs args,
288288
noCache,
289289
cancellationToken);
290290

291-
// for connection pool, retry fails until cache is exhausted.
292-
return await retryPolicy<ServerConnectionException>().ExecuteAsync(async connection =>
291+
/// Retry with new connection if the initial stream.WriteAsync call to server fails.
292+
/// i.e if request line and headers failed to get send.
293+
/// Do not retry after reading data from client stream,
294+
/// because subsequent try will not have data to read from client
295+
/// and will hang at clientStream.ReadAsync call.
296+
/// So, throw RetryableServerConnectionException only when we are sure we can retry safely.
297+
return await retryPolicy<RetryableServerConnectionException>().ExecuteAsync(async connection =>
293298
{
294299
// set the connection and send request headers
295300
args.HttpClient.SetConnection(connection);

tests/Titanium.Web.Proxy.IntegrationTests/NestedProxyTests.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ public async Task Nested_Proxy_Farm_With_Connection_Cache_Should_Not_Hang()
202202
{
203203
return Task.FromResult(true);
204204
};
205-
206205
proxies2.Add(proxy2);
207206
}
208207

@@ -212,9 +211,7 @@ public async Task Nested_Proxy_Farm_With_Connection_Cache_Should_Not_Hang()
212211
for (int i = 0; i < 10; i++)
213212
{
214213
var proxy1 = testSuite.GetProxy();
215-
//proxy1.EnableConnectionPool = false;
216214
var proxy2 = proxies2[rnd.Next() % proxies2.Count];
217-
218215
var explicitEndpoint = proxy1.ProxyEndPoints.OfType<ExplicitProxyEndPoint>().First();
219216
explicitEndpoint.BeforeTunnelConnectRequest += (_, e) =>
220217
{

0 commit comments

Comments
 (0)