/// <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);
                };
        }