public async Task Invoke_PropagatesExceptions() { var ex = new Exception("Kaboom!"); bool nextInvoked = false; RequestDelegate next = (ctxt) => { nextInvoked = true; throw ex; }; var options = new HttpOptions { MaxOutstandingRequests = 10, MaxConcurrentRequests = 5 }; var middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromSeconds(1)); var resultEx = await Assert.ThrowsAsync <Exception>(async() => { var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object); }); Assert.True(nextInvoked); Assert.Same(ex, resultEx); }
public HttpThrottleMiddlewareTests() { _functionDescriptor = new FunctionDescriptor("Test", null, null, new Collection <ParameterDescriptor>(), null, null, null); _scriptHost = new Mock <IScriptJobHost>(MockBehavior.Strict); _metricsLogger = new Mock <IMetricsLogger>(MockBehavior.Strict); _metricsLogger.Setup(p => p.LogEvent(MetricEventNames.FunctionInvokeThrottled, null, null)).Callback(() => { Interlocked.Increment(ref _throttleMetricCount); }); var environment = SystemEnvironment.Instance; var mockServiceProvider = new Mock <IServiceProvider>(MockBehavior.Strict); var healthMonitorOptions = new HostHealthMonitorOptions(); _performanceManager = new Mock <HostPerformanceManager>(MockBehavior.Strict, environment, new OptionsWrapper <HostHealthMonitorOptions>(healthMonitorOptions), mockServiceProvider.Object); _httpOptions = new HttpOptions(); _loggerFactory = new LoggerFactory(); _loggerProvider = new TestLoggerProvider(); _loggerFactory.AddProvider(_loggerProvider); RequestDelegate next = (ctxt) => { ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; return(Task.CompletedTask); }; _middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromSeconds(1)); _requestQueue = new HttpRequestQueue(new OptionsWrapper <HttpOptions>(_httpOptions)); }
public void ShouldEnable_ReturnsExpectedValue() { IOptions <HttpOptions> optionsWrapper = null; var scriptHostManagerMock = new Mock <IScriptHostManager>(MockBehavior.Strict); var hostServiceProviderMock = scriptHostManagerMock.As <IServiceProvider>(); hostServiceProviderMock.Setup(p => p.GetService(typeof(IOptions <HttpOptions>))).Returns(() => optionsWrapper); var rootServiceProvider = new Mock <IServiceProvider>(MockBehavior.Strict); rootServiceProvider.Setup(p => p.GetService(typeof(IScriptHostManager))).Returns(scriptHostManagerMock.Object); Assert.False(HttpThrottleMiddleware.ShouldEnable(null)); Assert.False(HttpThrottleMiddleware.ShouldEnable(rootServiceProvider.Object)); var httpOptions = new HttpOptions(); optionsWrapper = new OptionsWrapper <HttpOptions>(httpOptions); Assert.False(HttpThrottleMiddleware.ShouldEnable(rootServiceProvider.Object)); httpOptions.MaxConcurrentRequests = 5; Assert.True(HttpThrottleMiddleware.ShouldEnable(rootServiceProvider.Object)); httpOptions.MaxConcurrentRequests = -1; httpOptions.DynamicThrottlesEnabled = true; Assert.True(HttpThrottleMiddleware.ShouldEnable(rootServiceProvider.Object)); }
public async Task Invoke_MaxOutstandingRequestsExceeded_RequestsAreRejected() { int maxParallelism = 1; int maxQueueLength = 10; _httpOptions = new HttpOptions { MaxOutstandingRequests = maxQueueLength, MaxConcurrentRequests = maxParallelism }; _requestQueue = new HttpRequestQueue(new OptionsWrapper <HttpOptions>(_httpOptions)); RequestDelegate next = async(ctxt) => { await Task.Delay(100); ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; }; var middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromSeconds(1)); // expect requests past the threshold to be rejected var tasks = new List <Task>(); var httpContexts = new List <HttpContext>(); for (int i = 0; i < 25; i++) { var httpContext = new DefaultHttpContext(); httpContexts.Add(httpContext); tasks.Add(middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object)); } await Task.WhenAll(tasks); int countSuccess = httpContexts.Count(p => (HttpStatusCode)p.Response.StatusCode == HttpStatusCode.Accepted); Assert.Equal(maxQueueLength, countSuccess); int rejectCount = 25 - countSuccess; Assert.Equal(rejectCount, httpContexts.Count(p => p.Response.StatusCode == 429)); IEnumerable <LogMessage> logMessages = _loggerProvider.GetAllLogMessages(); Assert.Equal(rejectCount, logMessages.Count()); Assert.True(logMessages.All(p => string.Compare("Http request queue limit of 10 has been exceeded.", p.FormattedMessage) == 0)); // send a number of requests not exceeding the limit // expect all to succeed tasks = new List <Task>(); httpContexts = new List <HttpContext>(); for (int i = 0; i < maxQueueLength; i++) { var httpContext = new DefaultHttpContext(); httpContexts.Add(httpContext); tasks.Add(middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object)); } await Task.WhenAll(tasks); Assert.True(httpContexts.All(p => (HttpStatusCode)p.Response.StatusCode == HttpStatusCode.Accepted)); }
public async Task Invoke_NoThrottle_DispatchesDirectly() { bool nextInvoked = false; RequestDelegate next = (ctxt) => { nextInvoked = true; ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; return(Task.CompletedTask); }; var options = new HttpOptions(); var middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromSeconds(1)); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object); Assert.True(nextInvoked); Assert.Equal(HttpStatusCode.Accepted, (HttpStatusCode)httpContext.Response.StatusCode); }
public async Task Invoke_MaxParallelism_RequestsAreThrottled() { int maxParallelism = 3; _httpOptions = new HttpOptions { MaxConcurrentRequests = maxParallelism }; _requestQueue = new HttpRequestQueue(new OptionsWrapper <HttpOptions>(_httpOptions)); int count = 0; RequestDelegate next = async(ctxt) => { if (Interlocked.Increment(ref count) > maxParallelism) { throw new Exception($"Max parallelism of {maxParallelism} exceeded. Current parallelism: {count}"); } await Task.Delay(100); Interlocked.Decrement(ref count); ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; }; var middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromSeconds(1)); // expect all requests to succeed var tasks = new List <Task>(); var httpContexts = new List <HttpContext>(); for (int i = 0; i < 20; i++) { var httpContext = new DefaultHttpContext(); httpContexts.Add(httpContext); tasks.Add(middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object)); } await Task.WhenAll(tasks); Assert.True(httpContexts.All(p => (HttpStatusCode)p.Response.StatusCode == HttpStatusCode.Accepted)); }
public async Task Invoke_HostIsOverloaded_RequestsAreRejected() { _httpOptions = new HttpOptions { DynamicThrottlesEnabled = true }; _requestQueue = new HttpRequestQueue(new OptionsWrapper <HttpOptions>(_httpOptions)); bool isOverloaded = false; _performanceManager.Setup(p => p.PerformanceCountersExceeded(It.IsAny <Collection <string> >(), It.IsAny <ILogger>())).Returns(() => isOverloaded); RequestDelegate next = async(ctxt) => { await Task.Delay(100); ctxt.Response.StatusCode = (int)HttpStatusCode.Accepted; }; var middleware = new HttpThrottleMiddleware(next, _loggerFactory, TimeSpan.FromMilliseconds(50)); var tasks = new List <Task>(); var httpContexts = new List <HttpContext>(); for (int i = 0; i < 10; i++) { if (i == 7) { isOverloaded = true; } var httpContext = new DefaultHttpContext(); httpContexts.Add(httpContext); await middleware.Invoke(httpContext, new OptionsWrapper <HttpOptions>(_httpOptions), _requestQueue, _performanceManager.Object, _metricsLogger.Object); } await Task.WhenAll(tasks); Assert.Equal(7, httpContexts.Count(p => (HttpStatusCode)p.Response.StatusCode == HttpStatusCode.Accepted)); Assert.Equal(3, httpContexts.Count(p => (HttpStatusCode)p.Response.StatusCode == HttpStatusCode.TooManyRequests)); }
public static IApplicationBuilder UseWebJobsScriptHost(this IApplicationBuilder builder, IApplicationLifetime applicationLifetime, Action <WebJobsRouteBuilder> routes) { IEnvironment environment = builder.ApplicationServices.GetService <IEnvironment>() ?? SystemEnvironment.Instance; IOptionsMonitor <StandbyOptions> standbyOptions = builder.ApplicationServices.GetService <IOptionsMonitor <StandbyOptions> >(); IOptionsMonitor <HttpBodyControlOptions> httpBodyControlOptions = builder.ApplicationServices.GetService <IOptionsMonitor <HttpBodyControlOptions> >(); IServiceProvider serviceProvider = builder.ApplicationServices; builder.UseMiddleware <HttpRequestBodySizeMiddleware>(); builder.UseMiddleware <SystemTraceMiddleware>(); builder.UseMiddleware <HostnameFixupMiddleware>(); if (environment.IsLinuxConsumption()) { builder.UseMiddleware <EnvironmentReadyCheckMiddleware>(); } if (standbyOptions.CurrentValue.InStandbyMode) { builder.UseMiddleware <PlaceholderSpecializationMiddleware>(); } // Specialization can change the CompatMode setting, so this must run later than // the PlaceholderSpecializationMiddleware builder.UseWhen(context => httpBodyControlOptions.CurrentValue.AllowSynchronousIO || context.Request.IsAdminDownloadRequest(), config => { config.UseMiddleware <AllowSynchronousIOMiddleware>(); }); // This middleware must be registered before we establish the request service provider. builder.UseWhen(context => !context.Request.IsAdminRequest(), config => { config.UseMiddleware <HostAvailabilityCheckMiddleware>(); }); builder.UseWhen(context => HostWarmupMiddleware.IsWarmUpRequest(context.Request, standbyOptions.CurrentValue.InStandbyMode, environment), config => { config.UseMiddleware <HostWarmupMiddleware>(); }); // This middleware must be registered before any other middleware depending on // JobHost/ScriptHost scoped services. builder.UseMiddleware <ScriptHostRequestServiceProviderMiddleware>(); if (environment.IsLinuxConsumption()) { builder.UseMiddleware <AppServiceHeaderFixupMiddleware>(); } builder.UseMiddleware <ExceptionMiddleware>(); builder.UseWhen(HomepageMiddleware.IsHomepageRequest, config => { config.UseMiddleware <HomepageMiddleware>(); }); builder.UseWhen(context => !context.Request.IsAdminRequest() && HttpThrottleMiddleware.ShouldEnable(serviceProvider), config => { config.UseMiddleware <HttpThrottleMiddleware>(); }); builder.UseMiddleware <JobHostPipelineMiddleware>(); builder.UseMiddleware <FunctionInvocationMiddleware>(); // Register /admin/vfs, and /admin/zip to the VirtualFileSystem middleware. builder.UseWhen(VirtualFileSystemMiddleware.IsVirtualFileSystemRequest, config => config.UseMiddleware <VirtualFileSystemMiddleware>()); // MVC routes (routes defined by Controllers like HostController, FunctionsController, ... must be added before functions/proxy routes so they are matched first and can not be overridden by functions or proxy routes) // source here: https://github.com/aspnet/Mvc/blob/master/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs builder.UseMvc(); // Ensure the HTTP binding routing is registered after all middleware builder.UseHttpBindingRouting(applicationLifetime, routes); return(builder); }
public static IApplicationBuilder UseWebJobsScriptHost(this IApplicationBuilder builder, IApplicationLifetime applicationLifetime, Action <WebJobsRouteBuilder> routes) { IEnvironment environment = builder.ApplicationServices.GetService <IEnvironment>() ?? SystemEnvironment.Instance; IOptionsMonitor <StandbyOptions> standbyOptions = builder.ApplicationServices.GetService <IOptionsMonitor <StandbyOptions> >(); IOptionsMonitor <HttpBodyControlOptions> httpBodyControlOptions = builder.ApplicationServices.GetService <IOptionsMonitor <HttpBodyControlOptions> >(); IOptionsMonitor <HttpOptions> httpOptions = builder.ApplicationServices.GetService <IOptionsMonitor <HttpOptions> >(); builder.UseMiddleware <SystemTraceMiddleware>(); builder.UseMiddleware <HostnameFixupMiddleware>(); if (environment.IsLinuxConsumption()) { builder.UseMiddleware <EnvironmentReadyCheckMiddleware>(); } if (standbyOptions.CurrentValue.InStandbyMode) { builder.UseMiddleware <PlaceholderSpecializationMiddleware>(); } // Specialization can change the CompatMode setting, so this must run later than // the PlaceholderSpecializationMiddleware builder.UseWhen(context => httpBodyControlOptions.CurrentValue.AllowSynchronousIO || context.Request.IsAdminDownloadRequest(), config => { config.UseMiddleware <AllowSynchronousIOMiddleware>(); }); // This middleware must be registered before we establish the request service provider. builder.UseWhen(context => !context.Request.IsAdminRequest(), config => { config.UseMiddleware <HostAvailabilityCheckMiddleware>(); }); builder.UseWhen(context => HostWarmupMiddleware.IsWarmUpRequest(context.Request, standbyOptions.CurrentValue.InStandbyMode, environment), config => { config.UseMiddleware <HostWarmupMiddleware>(); }); // This middleware must be registered before any other middleware depending on // JobHost/ScriptHost scoped services. builder.UseMiddleware <ScriptHostRequestServiceProviderMiddleware>(); if (environment.IsLinuxConsumption()) { builder.UseMiddleware <AppServiceHeaderFixupMiddleware>(); } builder.UseMiddleware <ExceptionMiddleware>(); builder.UseWhen(HomepageMiddleware.IsHomepageRequest, config => { config.UseMiddleware <HomepageMiddleware>(); }); builder.UseWhen(context => HttpThrottleMiddleware.ShouldEnable(httpOptions.CurrentValue) && !context.Request.IsAdminRequest(), config => { config.UseMiddleware <HttpThrottleMiddleware>(); }); builder.UseMiddleware <ResponseContextItemsCheckMiddleware>(); builder.UseMiddleware <JobHostPipelineMiddleware>(); builder.UseMiddleware <FunctionInvocationMiddleware>(); // Register /admin/vfs, and /admin/zip to the VirtualFileSystem middleware. builder.UseWhen(VirtualFileSystemMiddleware.IsVirtualFileSystemRequest, config => config.UseMiddleware <VirtualFileSystemMiddleware>()); // Ensure the HTTP binding routing is registered after all middleware builder.UseHttpBindingRouting(applicationLifetime, routes); builder.UseMvc(); return(builder); }