protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
        {
            Log.Debug("ApplicationStartup");

            pipelines.EnableGzipCompression(new GzipCompressionSettings()
            {
                MinimumBytes = 1024
            });
            pipelines.AddHeadersAndLogging();
            pipelines.AddRequestStashing();

            // rate-limiting - uses remote address by default, but we can extend this to include user-agent and/or
            // authenticated user if necessary
            LeakyBucketRateLimiter.Enable(pipelines, new LeakyBucketRateLimiterConfiguration
            {
                // 100 requests per minute should be plenty
                MaxNumberOfRequests = 100, RefreshRate = TimeSpan.FromMinutes(1)
            });

            // setup auth last so that rate-limited responses include authenticated user
            // (uses AddItemToStartOfPipeline() to ensure it happens before rate-limiter)
            var auth_method = Env.GetEnvironmentString("COMPUTE_AUTH_METHOD", "");

            if (auth_method == "RHINO_ACCOUNT")
            {
                pipelines.AddAuthRhinoAccount();
            }
            else if (auth_method == "API_KEY")
            {
                pipelines.AddAuthApiKey();
            }

            base.ApplicationStartup(container, pipelines);
        }
Beispiel #2
0
        public ResponseDto.Root?Call(int num = 10)
        {
            var body = new RequestDto()
            {
                Num = num,
                R18 = 2
            };
            var jsonSerializeSetting = new JsonSerializerSettings
            {
                NullValueHandling    = NullValueHandling.Ignore,
                DefaultValueHandling = DefaultValueHandling.Ignore,
                ContractResolver     = new CamelCasePropertyNamesContractResolver()
            };

            string responseStr = null;

            using (var limiter = new LeakyBucketRateLimiter(nameof(SetuAPI), 1))
            {
                if (limiter.CanRequest())
                {
                    responseStr = WebHelper.HttpPost("https://api.lolicon.app/setu/v2", JsonConvert.SerializeObject(body, jsonSerializeSetting),
                                                     WebHelper.WebContentType.Json);
                }
            }

            return(responseStr == null ? null : JsonConvert.DeserializeObject <ResponseDto.Root>(responseStr, jsonSerializeSetting));
        }
Beispiel #3
0
        public void Should_throw_with_null_configuration()
        {
            var pipelines = A.Fake <IPipelines>();
            var module    = A.Fake <INancyModule>();

            Assert.Throws <ArgumentNullException>(() => LeakyBucketRateLimiter.Enable(pipelines, null));
            Assert.Throws <ArgumentNullException>(() => LeakyBucketRateLimiter.Enable(module, null));
        }
Beispiel #4
0
        public void Should_add_before_hook_in_module_when_enabled()
        {
            var module = A.Fake <INancyModule>();

            LeakyBucketRateLimiter.Enable(module, new LeakyBucketRateLimiterConfiguration());

            A.CallTo(() => module.Before.AddItemToStartOfPipeline(A <Func <NancyContext, Response> > .Ignored))
            .MustHaveHappened(Repeated.Exactly.Once);
        }
Beispiel #5
0
        public void Should_add_before_request_hook_in_application_when_enabled()
        {
            var pipelines = A.Fake <IPipelines>();

            LeakyBucketRateLimiter.Enable(pipelines, new LeakyBucketRateLimiterConfiguration());

            A.CallTo(() => pipelines.BeforeRequest.AddItemToStartOfPipeline(A <Func <NancyContext, Response> > .Ignored))
            .MustHaveHappened(Repeated.Exactly.Once);
        }
Beispiel #6
0
 private static INancyBootstrapper CreateBootstrapper(LeakyBucketRateLimiterConfiguration configuration)
 {
     return(new ConfigurableBootstrapper(config =>
     {
         config.ApplicationStartup((container, pipelines) =>
         {
             LeakyBucketRateLimiter.Enable(pipelines, configuration);
         });
     }));
 }
Beispiel #7
0
        /// <summary>
        /// 调用罗马音API
        /// </summary>
        /// <param name="japanese"></param>
        /// <returns></returns>
        public string CallAPI(string japanese)
        {
            if (japanese.IsNullOrWhiteSpace())
            {
                return(null);
            }
            string result;

            using (var limiter = new LeakyBucketRateLimiter(nameof(KouRomajiHelper), 1))
            {
                if (!limiter.CanRequest())
                {
                    this.InheritError(limiter, "发生在" + nameof(KouRomajiHelper) + "中的" + nameof(CallAPI));
                }
                string data = "mode=japanese&q=" + HttpUtility.UrlEncode(japanese);
                result = WebHelper.HttpPost("http://www.kawa.net/works/ajax/romanize/romanize.cgi ", data, WebHelper.WebContentType.General);
            }
            return(result);
        }
Beispiel #8
0
        public Root CallAPI(string str)
        {
            if (str.IsNullOrWhiteSpace())
            {
                return(null);
            }
            string result;

            using (var limiter = new LeakyBucketRateLimiter(nameof(KouNbnhhsh), 2))
            {
                if (!limiter.CanRequest())
                {
                    this.InheritError(limiter, "发生在" + nameof(KouNbnhhsh) + "中的" + nameof(CallAPI));
                    return(null);
                }
                result = WebHelper.HttpPost("https://lab.magiconch.com/api/nbnhhsh/guess/", "{\"text\":\"" + str + "\"}", WebHelper.WebContentType.Json);
            }

            result = "{\"result\":" + result + "}";
            Root root = JsonConvert.DeserializeObject <Root>(result);

            return(root);
        }
Beispiel #9
0
        public void Should_throw_with_null_module_with_configuration()
        {
            var config = new LeakyBucketRateLimiterConfiguration();

            Assert.Throws <ArgumentNullException>(() => LeakyBucketRateLimiter.Enable((INancyModule)null, config));
        }
Beispiel #10
0
        public static void ConfigureRateLimiting(HttpFilterCollection filters)
        {
            #region Setting up the Redis rate limiter
            var redisRateLimiterSettings = new RedisRateLimiterSettings();

            ConfigureRateLimitingSettings(redisRateLimiterSettings);

            var rateLimitCacheProvider = new LeakyBucketRateLimiter(
                redisRateLimiterSettings.RateLimitRedisCacheConnectionString,
                onException: (ex) =>
            {
                //_logger.LogError("Error connecting to Redis")
            },
                circuitBreaker: new DefaultCircuitBreaker(redisRateLimiterSettings.FaultThreshholdPerWindowDuration,
                                                          redisRateLimiterSettings.FaultWindowDurationInMilliseconds, redisRateLimiterSettings.CircuitOpenIntervalInSecs,
                                                          onCircuitException: (ex) =>
            {
                //_logger.LogError("Rate limiting circuit error")
            },
                                                          onCircuitOpened: () =>
            {
                //_logger.LogWarning("Rate limiting circuit opened")
            },
                                                          onCircuitClosed: () =>
            {
                //logger.LogWarning("Rate limiting circuit closed")
            }),

                countThrottledRequests: true
                );

            #endregion

            var configSettings = new DomainElasticSearchLoggingOptions()
            {
                Enabled     = true,
                Url         = "http://localhost:9200",
                Application = "Public Adapter",
                IndexFormat = "quotaaudits-{0:yyyy.MM.dd}"
            };

            var loggerConfig = new LoggerConfiguration()
                               .MinimumLevel.Information()
                               .Enrich.FromLogContext();

            // if enabled in appsettings.json, logs will be shipped to Elasticsearch in a standard json format
            if (configSettings.Enabled)
            {
                loggerConfig.WriteTo.DomainElasticsearch(configSettings);
            }

            var auditLogger = loggerConfig.CreateLogger();

            var policyProvider = new ClientQuotaPolicyProvider();

            filters.Add(new RateLimitingFilter(

                            new RateLimiter(rateLimitCacheProvider, new RateLimitingPolicyManager(policyProvider)),

                            filters,

                            onPostLimit: async(request, policy, result, actionContext) =>
            {
                var operationClass = CallClassification.RouteTemplateToClassMap[request.RouteTemplate];
                var cost           = CallClassification.CostPerClass[operationClass];

                var clientId = result.CacheKey.RequestId;
                // sns publish

                if (result.State == ResultState.Success)
                {
                    // sns publish
                    auditLogger.Information(
                        "Result {Result}: Request success for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost}",
                        "Success",
                        clientId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        cost);
                }
                else if (result.State == ResultState.Throttled)
                {
                    auditLogger.Information(
                        "Result {Result}: throttled for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost} by violating policy {ViolatedPolicy}",
                        "Throttled",
                        clientId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        cost,
                        $"{policy.Name}:{result.CacheKey.AllowedConsumptionRate}");
                }
                else if (result.State == ResultState.ThrottledButCompensationFailed)
                {
                    auditLogger.Information(
                        "Result {Result}: throttled but failed to compensate for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost} by violating policy {ViolatedPolicy}",
                        result.State,
                        clientId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        cost,
                        $"{policy.Name}:{result.CacheKey.AllowedConsumptionRate}");
                }
                else if (result.State == ResultState.LimitApplicationFailed)
                {
                    auditLogger.Information(
                        "Result {Result}: Free pass for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost}",
                        "FreePass",
                        clientId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        cost);
                }
                else if (result.State == ResultState.NotApplicable)
                {
                    auditLogger.Information(
                        "Result {Result}: Not Applicable for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost}",
                        ResultState.NotApplicable,
                        clientId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        cost);
                }


                return(Decision.OK);
            },

                            onPostLimitRevert: async(request, policy, result, actionContext) =>
            {
                var operationClass = CallClassification.RouteTemplateToClassMap[request.RouteTemplate];

                if (result.State == ResultState.Success || result.State == ResultState.Throttled)
                {
                    auditLogger.Information(
                        "Result {Result}: Limit Cost Reverted for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost}",
                        "SuccessCostReverted",
                        result.CacheKey.RequestId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        -policy.CostPerCall);
                }
                else if (result.State == ResultState.LimitApplicationFailed)
                {
                    auditLogger.Information(
                        "Result {Result}: Limit Cost Reverting failed for client {ClientId} and endpoint {Endpoint} with route {RouteTemplate} which is Class {Class} with Cost {Cost}",
                        "SuccessCostRevertingFailed",
                        result.CacheKey.RequestId,
                        request.Path,
                        request.RouteTemplate,
                        operationClass,
                        -policy.CostPerCall);
                }
            },

                            postOperationDecisionFuncAsync: async(request, policy, result, actionExecutedContext) =>
            {
                if (actionExecutedContext.Exception != null || (int)actionExecutedContext.Response.StatusCode >= 400)
                {
                    return(Decision.REVERTSUCCESSCOST);
                }

                return(Decision.OK);
            },

                            //getPolicyFuncAsync: policyProvider.GetPolicyAsync,
                            simulationMode: false));
        }