Make sure long polling transport can survive http client timeout
This commit is contained in:
parent
4ac8e786cf
commit
62bbe943e8
|
|
@ -22,6 +22,8 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
public class HttpConnection : IConnection
|
public class HttpConnection : IConnection
|
||||||
{
|
{
|
||||||
|
private static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(120);
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
|
@ -77,6 +79,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
_logger = _loggerFactory.CreateLogger<HttpConnection>();
|
_logger = _loggerFactory.CreateLogger<HttpConnection>();
|
||||||
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler);
|
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler);
|
||||||
|
_httpClient.Timeout = HttpClientTimeout;
|
||||||
_transportFactory = new DefaultTransportFactory(transportType, _loggerFactory, _httpClient);
|
_transportFactory = new DefaultTransportFactory(transportType, _loggerFactory, _httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +89,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
_logger = _loggerFactory.CreateLogger<HttpConnection>();
|
_logger = _loggerFactory.CreateLogger<HttpConnection>();
|
||||||
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler);
|
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler);
|
||||||
|
_httpClient.Timeout = HttpClientTimeout;
|
||||||
_transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory));
|
_transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,20 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, pollUrl);
|
var request = new HttpRequestMessage(HttpMethod.Get, pollUrl);
|
||||||
request.Headers.UserAgent.Add(Constants.UserAgentHeader);
|
request.Headers.UserAgent.Add(Constants.UserAgentHeader);
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
HttpResponseMessage response;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = await _httpClient.SendAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// SendAsync will throw the OperationCanceledException if the passed cancellationToken is canceled
|
||||||
|
// or if the http request times out due to HttpClient.Timeout expiring. In the latter case we
|
||||||
|
// just want to start a new poll.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.NoContent || cancellationToken.IsCancellationRequested)
|
if (response.StatusCode == HttpStatusCode.NoContent || cancellationToken.IsCancellationRequested)
|
||||||
|
|
|
||||||
|
|
@ -437,5 +437,48 @@ namespace Microsoft.AspNetCore.Client.Tests
|
||||||
Assert.Equal("requestedTransferMode", exception.ParamName);
|
Assert.Equal("requestedTransferMode", exception.ParamName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LongPollingTransportRePollsIfRequestCancelled()
|
||||||
|
{
|
||||||
|
var numPolls = 0;
|
||||||
|
var completionTcs = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
if (Interlocked.Increment(ref numPolls) < 3)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
|
||||||
|
completionTcs.SetResult(null);
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
{
|
||||||
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var connectionToTransport = Channel.CreateUnbounded<SendMessage>();
|
||||||
|
var transportToConnection = Channel.CreateUnbounded<byte[]>();
|
||||||
|
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
|
||||||
|
await longPollingTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection, TransferMode.Binary, connectionId: string.Empty);
|
||||||
|
|
||||||
|
var completedTask = await Task.WhenAny(completionTcs.Task, longPollingTransport.Running).OrTimeout();
|
||||||
|
Assert.Equal(completionTcs.Task, completedTask);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await longPollingTransport.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue