|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
4 |
| -using System; |
5 | 4 | using System.Buffers;
|
6 |
| -using System.Collections.Generic; |
7 | 5 | using System.Diagnostics;
|
8 | 6 | using System.IdentityModel.Tokens.Jwt;
|
9 |
| -using System.IO; |
10 | 7 | using System.IO.Pipelines;
|
11 |
| -using System.Linq; |
12 | 8 | using System.Net;
|
13 | 9 | using System.Net.Http;
|
14 | 10 | using System.Net.WebSockets;
|
15 | 11 | using System.Security.Claims;
|
16 | 12 | using System.Security.Principal;
|
17 | 13 | using System.Text;
|
18 |
| -using System.Threading; |
19 |
| -using System.Threading.Tasks; |
20 | 14 | using Microsoft.AspNetCore.Authentication;
|
21 | 15 | using Microsoft.AspNetCore.Authentication.Cookies;
|
22 | 16 | using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
29 | 23 | using Microsoft.AspNetCore.Hosting.Server;
|
30 | 24 | using Microsoft.AspNetCore.Hosting.Server.Features;
|
31 | 25 | using Microsoft.AspNetCore.Http.Connections.Client;
|
| 26 | +using Microsoft.AspNetCore.Http.Connections.Features; |
32 | 27 | using Microsoft.AspNetCore.Http.Connections.Internal;
|
33 | 28 | using Microsoft.AspNetCore.Http.Features;
|
| 29 | +using Microsoft.AspNetCore.Http.Timeouts; |
34 | 30 | using Microsoft.AspNetCore.Internal;
|
35 | 31 | using Microsoft.AspNetCore.Routing;
|
36 | 32 | using Microsoft.AspNetCore.SignalR.Tests;
|
|
45 | 41 | using Moq;
|
46 | 42 | using Newtonsoft.Json;
|
47 | 43 | using Newtonsoft.Json.Linq;
|
48 |
| -using Xunit; |
49 | 44 |
|
50 | 45 | namespace Microsoft.AspNetCore.Http.Connections.Tests;
|
51 | 46 |
|
@@ -3112,6 +3107,109 @@ public async Task AuthenticationExpirationSetToMaxValueByDefault()
|
3112 | 3107 | await connection.DisposeAsync();
|
3113 | 3108 | }
|
3114 | 3109 |
|
| 3110 | + [Theory] |
| 3111 | + [InlineData(HttpTransportType.ServerSentEvents)] |
| 3112 | + [InlineData(HttpTransportType.WebSockets)] |
| 3113 | + public async Task RequestTimeoutDisabledWhenConnected(HttpTransportType transportType) |
| 3114 | + { |
| 3115 | + using (StartVerifiableLog()) |
| 3116 | + { |
| 3117 | + using var host = new HostBuilder() |
| 3118 | + .ConfigureWebHost(webHostBuilder => |
| 3119 | + { |
| 3120 | + webHostBuilder |
| 3121 | + .UseKestrel() |
| 3122 | + .ConfigureLogging(o => |
| 3123 | + { |
| 3124 | + o.AddProvider(new ForwardingLoggerProvider(LoggerFactory)); |
| 3125 | + }) |
| 3126 | + .ConfigureServices(services => |
| 3127 | + { |
| 3128 | + services.AddConnections(); |
| 3129 | + |
| 3130 | + // Since tests run in parallel, it's possible multiple servers will startup, |
| 3131 | + // we use an ephemeral key provider to avoid filesystem contention issues |
| 3132 | + services.AddSingleton<IDataProtectionProvider, EphemeralDataProtectionProvider>(); |
| 3133 | + }) |
| 3134 | + .Configure(app => |
| 3135 | + { |
| 3136 | + app.Use((c, n) => |
| 3137 | + { |
| 3138 | + c.Features.Set<IHttpRequestTimeoutFeature>(new HttpRequestTimeoutFeature()); |
| 3139 | + Assert.True(((HttpRequestTimeoutFeature)c.Features.Get<IHttpRequestTimeoutFeature>()).Enabled); |
| 3140 | + return n(c); |
| 3141 | + }); |
| 3142 | + app.UseRouting(); |
| 3143 | + app.UseEndpoints(endpoints => |
| 3144 | + { |
| 3145 | + endpoints.MapConnectionHandler<TestConnectionHandler>("/foo"); |
| 3146 | + }); |
| 3147 | + }) |
| 3148 | + .UseUrls("http://127.0.0.1:0"); |
| 3149 | + }) |
| 3150 | + .Build(); |
| 3151 | + |
| 3152 | + host.Start(); |
| 3153 | + |
| 3154 | + var manager = host.Services.GetRequiredService<HttpConnectionManager>(); |
| 3155 | + var url = host.Services.GetService<IServer>().Features.Get<IServerAddressesFeature>().Addresses.Single(); |
| 3156 | + |
| 3157 | + var stream = new MemoryStream(); |
| 3158 | + var connection = new HttpConnection( |
| 3159 | + new HttpConnectionOptions() |
| 3160 | + { |
| 3161 | + Url = new Uri(url + "/foo"), |
| 3162 | + Transports = transportType, |
| 3163 | + DefaultTransferFormat = TransferFormat.Text, |
| 3164 | + HttpMessageHandlerFactory = handler => new GetNegotiateHttpHandler(handler, stream) |
| 3165 | + }, |
| 3166 | + LoggerFactory); |
| 3167 | + |
| 3168 | + await connection.StartAsync(); |
| 3169 | + |
| 3170 | + var negotiateResponse = NegotiateProtocol.ParseResponse(stream.ToArray()); |
| 3171 | + |
| 3172 | + Assert.True(manager.TryGetConnection(negotiateResponse.ConnectionToken, out var context)); |
| 3173 | + var feature = Assert.IsType<HttpRequestTimeoutFeature>(context.Features.Get<IHttpContextFeature>()?.HttpContext.Features.Get<IHttpRequestTimeoutFeature>()); |
| 3174 | + Assert.False(feature.Enabled); |
| 3175 | + |
| 3176 | + await connection.DisposeAsync(); |
| 3177 | + } |
| 3178 | + } |
| 3179 | + |
| 3180 | + [Fact] |
| 3181 | + public async Task DisableRequestTimeoutInLongPolling() |
| 3182 | + { |
| 3183 | + using (StartVerifiableLog()) |
| 3184 | + { |
| 3185 | + var manager = CreateConnectionManager(LoggerFactory, TimeSpan.FromSeconds(5)); |
| 3186 | + var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); |
| 3187 | + var options = new HttpConnectionDispatcherOptions(); |
| 3188 | + var connection = manager.CreateConnection(options); |
| 3189 | + connection.TransportType = HttpTransportType.LongPolling; |
| 3190 | + |
| 3191 | + var services = new ServiceCollection(); |
| 3192 | + var builder = new ConnectionBuilder(services.BuildServiceProvider()); |
| 3193 | + builder.UseConnectionHandler<HttpContextConnectionHandler>(); |
| 3194 | + var app = builder.Build(); |
| 3195 | + var context = MakeRequest("/foo", connection, services); |
| 3196 | + context.Features.Set<IHttpRequestTimeoutFeature>(new HttpRequestTimeoutFeature()); |
| 3197 | + Assert.True(((HttpRequestTimeoutFeature)context.Features.Get<IHttpRequestTimeoutFeature>()).Enabled); |
| 3198 | + |
| 3199 | + // Initial poll will complete immediately |
| 3200 | + await dispatcher.ExecuteAsync(context, options, app).DefaultTimeout(); |
| 3201 | + Assert.False(((HttpRequestTimeoutFeature)context.Features.Get<IHttpRequestTimeoutFeature>()).Enabled); |
| 3202 | + |
| 3203 | + context.Features.Set<IHttpRequestTimeoutFeature>(new HttpRequestTimeoutFeature()); |
| 3204 | + Assert.True(((HttpRequestTimeoutFeature)context.Features.Get<IHttpRequestTimeoutFeature>()).Enabled); |
| 3205 | + var pollTask = dispatcher.ExecuteAsync(context, options, app); |
| 3206 | + // disables on every poll |
| 3207 | + Assert.False(((HttpRequestTimeoutFeature)context.Features.Get<IHttpRequestTimeoutFeature>()).Enabled); |
| 3208 | + |
| 3209 | + await connection.DisposeAsync().DefaultTimeout(); |
| 3210 | + } |
| 3211 | + } |
| 3212 | + |
3115 | 3213 | private class GetNegotiateHttpHandler : DelegatingHandler
|
3116 | 3214 | {
|
3117 | 3215 | private readonly MemoryStream _stream;
|
@@ -3502,3 +3600,15 @@ public class MessageWrapper
|
3502 | 3600 | {
|
3503 | 3601 | public ReadOnlySequence<byte> Buffer { get; set; }
|
3504 | 3602 | }
|
| 3603 | + |
| 3604 | +internal sealed class HttpRequestTimeoutFeature : IHttpRequestTimeoutFeature |
| 3605 | +{ |
| 3606 | + public bool Enabled { get; private set; } = true; |
| 3607 | + |
| 3608 | + public CancellationToken RequestTimeoutToken => new CancellationToken(); |
| 3609 | + |
| 3610 | + public void DisableTimeout() |
| 3611 | + { |
| 3612 | + Enabled = false; |
| 3613 | + } |
| 3614 | +} |
0 commit comments