/// <summary> /// Limits the bandwith used by the subsequent stages in the owin pipeline. /// </summary> /// <param name="getMaxBytesPerSecond"> /// A delegate to retrieve the maximum number of bytes per second to be transferred. /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth. /// </param> /// <returns>An OWIN middleware delegate.</returns> /// <exception cref="System.ArgumentNullException">getMaxBytesPerSecond</exception> public static MidFunc MaxBandwidthPerRequest(Func<RequestContext, int> getMaxBytesPerSecond) { getMaxBytesPerSecond.MustNotNull("getMaxBytesPerSecond"); return next => async env => { var context = new OwinContext(env); Stream requestBodyStream = context.Request.Body ?? Stream.Null; Stream responseBodyStream = context.Response.Body; var limitsRequestContext = new RequestContext(context.Request); var requestTokenBucket = new FixedTokenBucket( () => getMaxBytesPerSecond(limitsRequestContext)); var responseTokenBucket = new FixedTokenBucket( () => getMaxBytesPerSecond(limitsRequestContext)); using (requestTokenBucket.RegisterRequest()) using (responseTokenBucket.RegisterRequest()) { context.Request.Body = new ThrottledStream(requestBodyStream, requestTokenBucket); context.Response.Body = new ThrottledStream(responseBodyStream, responseTokenBucket); //TODO consider SendFile interception await next(env).ConfigureAwait(false); } }; }
/// <summary> /// Timeouts the connection if there hasn't been an read activity on the request body stream or any /// write activity on the response body stream. /// </summary> /// <param name="getTimeout"> /// A delegate to retrieve the timeout timespan. Allows you /// to supply different values at runtime. /// </param> /// <param name="loggerName">(Optional) The name of the logger log messages are written to.</param> /// <returns>An OWIN middleware delegate.</returns> /// <exception cref="System.ArgumentNullException">getTimeout</exception> public static MidFunc ConnectionTimeout( Func<RequestContext, TimeSpan> getTimeout, string loggerName = null) { getTimeout.MustNotNull("getTimeout"); loggerName = string.IsNullOrWhiteSpace(loggerName) ? "LimitsMiddleware.ConnectionTimeout" : loggerName; var logger = LogProvider.GetLogger(loggerName); return next => env => { var context = new OwinContext(env); var limitsRequestContext = new RequestContext(context.Request); var requestBodyStream = context.Request.Body ?? Stream.Null; var responseBodyStream = context.Response.Body; var connectionTimeout = getTimeout(limitsRequestContext); context.Request.Body = new TimeoutStream(requestBodyStream, connectionTimeout, logger); context.Response.Body = new TimeoutStream(responseBodyStream, connectionTimeout, logger); return next(env); }; }
/// <summary> /// Limits the number of concurrent requests that can be handled used by the subsequent stages in the owin pipeline. /// </summary> /// <param name="getMaxConcurrentRequests"> /// A delegate to retrieve the maximum number of concurrent requests. Allows you /// to supply different values at runtime. Use 0 or a negative number to specify unlimited number of concurrent /// requests. /// </param> /// <param name="loggerName">(Optional) The name of the logger log messages are written to.</param> /// <returns>An OWIN middleware delegate.</returns> /// <exception cref="System.ArgumentNullException">getMaxConcurrentRequests</exception> public static MidFunc MaxConcurrentRequests( Func<RequestContext, int> getMaxConcurrentRequests, string loggerName = null) { getMaxConcurrentRequests.MustNotNull("getMaxConcurrentRequests"); loggerName = string.IsNullOrWhiteSpace(loggerName) ? "LimitsMiddleware.MaxConcurrentRequests" : loggerName; var logger = LogProvider.GetLogger(loggerName); var concurrentRequestCounter = 0; return next => async env => { var owinRequest = new OwinRequest(env); var limitsRequestContext = new RequestContext(owinRequest); int maxConcurrentRequests = getMaxConcurrentRequests(limitsRequestContext); if (maxConcurrentRequests <= 0) { maxConcurrentRequests = int.MaxValue; } try { int concurrentRequests = Interlocked.Increment(ref concurrentRequestCounter); logger.Debug("Concurrent request {0}/{1}.".FormatWith(concurrentRequests, maxConcurrentRequests)); if (concurrentRequests > maxConcurrentRequests) { logger.Info("Limit ({0}). Request rejected." .FormatWith(maxConcurrentRequests, concurrentRequests)); var response = new OwinContext(env).Response; response.StatusCode = 503; response.ReasonPhrase = "Service Unavailable"; response.Write(response.ReasonPhrase); return; } await next(env); } finally { int concurrentRequests = Interlocked.Decrement(ref concurrentRequestCounter); logger.Debug("Concurrent request {0}/{1}.".FormatWith(concurrentRequests, maxConcurrentRequests)); } }; }
/// <summary> /// Timeouts the connection if there hasn't been an read activity on the request body stream or any /// write activity on the response body stream. /// </summary> /// <param name="getTimeout">A delegate to retrieve the timeout timespan. Allows you /// to supply different values at runtime.</param> /// <returns>An OWIN middleware delegate.</returns> /// <exception cref="System.ArgumentNullException">getTimeout</exception> public static MidFunc ConnectionTimeout(Func<RequestContext, TimeSpan> getTimeout) { getTimeout.MustNotNull("getTimeout"); var logger = LogProvider.GetLogger("LimitsMiddleware.ConnectionTimeout"); return next => env => { var context = new OwinContext(env); var limitsRequestContext = new RequestContext(context.Request); Stream requestBodyStream = context.Request.Body ?? Stream.Null; Stream responseBodyStream = context.Response.Body; TimeSpan connectionTimeout = getTimeout(limitsRequestContext); context.Request.Body = new TimeoutStream(requestBodyStream, connectionTimeout); context.Response.Body = new TimeoutStream(responseBodyStream, connectionTimeout); return next(env); }; }
/// <summary> /// Limits the length of the query string. /// </summary> /// <param name="getMaxQueryStringLength">A delegate to get the maximum query string length.</param> /// <returns>An OWIN middleware delegate.</returns> /// <exception cref="System.ArgumentNullException">getMaxQueryStringLength</exception> public static MidFunc MaxQueryStringLength(Func<RequestContext, int> getMaxQueryStringLength) { getMaxQueryStringLength.MustNotNull("getMaxQueryStringLength"); var logger = LogProvider.GetLogger("LimitsMiddleware.MaxQueryStringLength"); return next => async env => { var context = new OwinContext(env); var requestContext = new RequestContext(context.Request); QueryString queryString = context.Request.QueryString; if (queryString.HasValue) { int maxQueryStringLength = getMaxQueryStringLength(requestContext); string unescapedQueryString = Uri.UnescapeDataString(queryString.Value); logger.Debug("Querystring of request with an unescaped length of {0}".FormatWith(unescapedQueryString.Length)); if (unescapedQueryString.Length > maxQueryStringLength) { logger.Info("Querystring (Length {0}) too long (allowed {1}). Request rejected.".FormatWith( unescapedQueryString.Length, maxQueryStringLength)); context.Response.StatusCode = 414; context.Response.ReasonPhrase = "Request-URI Too Large"; context.Response.Write(context.Response.ReasonPhrase); return; } } else { logger.Debug("No querystring."); } await next(env); }; }
/// <summary> /// Adds a minimum delay before sending the response. /// </summary> /// <param name="getMinDelay">A delegate to return the min response delay.</param> /// <param name="loggerName">(Optional) The name of the logger log messages are written to.</param> /// <returns>The OWIN builder instance.</returns> /// <exception cref="System.ArgumentNullException">getMinDelay</exception> public static MidFunc MinResponseDelay(Func<RequestContext, TimeSpan> getMinDelay, string loggerName = null) { getMinDelay.MustNotNull("getMinDelay"); loggerName = string.IsNullOrWhiteSpace(loggerName) ? "LimitsMiddleware.MinResponseDelay" : loggerName; var logger = LogProvider.GetLogger(loggerName); return next => async env => { var context = new OwinContext(env); var limitsRequestContext = new RequestContext(context.Request); var delay = getMinDelay(limitsRequestContext); if (delay <= TimeSpan.Zero) { await next(env); return; } logger.Debug("Delaying response by {0}".FormatWith(delay)); await Task.Delay(delay, context.Request.CallCancelled); await next(env); }; }