WebApiThrottle

repository·master·Indexed 22 days ago

https://github.com/stefanprodan/webapithrottle

A rate-limiting library for ASP.NET Web API that provides a throttling handler, OWIN middleware, and filter. It allows controlling request rates based on IP addresses, client API keys, and specific request routes. Features include support for whitelisting, custom rate limits via IpRules, ClientRules, and EndpointRules, and the ability to implement custom storage using the IThrottleRepository interface.

Tokens
4K
Snippets
12
Records
15
Agent score
29%

What's inside WebApiThrottle

  1. Include Rejected Requests in Quota Counters

    master

    By default, rejected (throttled) calls are not added to the throttle counter. If you want rejected requests to count towards the limits (e.g., to prevent a client from spamming even if they are already blocked), set StackBlockedRequests = true in the ThrottlePolicy.

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
    	{
    		IpThrottling = true,
    		ClientThrottling = true,
    		EndpointThrottling = true,
    		StackBlockedRequests = true
    	},
    	Repository = new CacheRepository()
    });
  2. Define Custom Rate Limits for IPs, Clients, or Endpoints

    master

    You can override the global ThrottlePolicy with specific limits for known entities using IpRules, ClientRules, or EndpointRules.

    • IpRules/ClientRules: Use a Dictionary<string, RateLimits> where the key is the IP or Client Key.
    • EndpointRules: Use a Dictionary<string, RateLimits> where the key is a relative route (e.g., api/search) or a URL segment (e.g., /entry/).

    Note: Custom limits only work if a global counterpart is defined. If multiple endpoint rules match a URI, the lower limit is applied.

    // Custom IP and Client Rules
    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500)
    	{
    		IpThrottling = true,
    		IpRules = new Dictionary<string, RateLimits>
    		{
    			"192.168.1.1", new RateLimits { PerSecond = 2 }
    		},
    		
    		ClientThrottling = true,
    		ClientRules = new Dictionary<string, RateLimits>
    		{
    			"api-client-key-1", new RateLimits { PerMinute = 40, PerHour = 400 }
    		}
    	},
    	Repository = new CacheRepository()
    });
    
    // Custom Endpoint Rules
    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200)
    	{
    		IpThrottling = true,
    		ClientThrottling = true,
    		EndpointThrottling = true,
    		EndpointRules = new Dictionary<string, RateLimits>
    		{
    			"api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 }
    		}
    	},
    	Repository = new CacheRepository()
    });
  3. Configure Endpoint Throttling based on IP and Client Key

    master

    To throttle based on a combination of IP and a unique API key, set IpThrottling, ClientThrottling, and EndpointThrottling to true. If you want to apply limits to clients regardless of their IP, set IpThrottling to false.

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
    	{
    		IpThrottling = true,
    		ClientThrottling = true,
    		EndpointThrottling = true
    	},
    	Repository = new CacheRepository()
    });
  4. Define Rate Limits in web.config or app.config

    master

    You can define the ThrottlePolicy using XML in your configuration files. This requires adding a throttlePolicy section to your <configSections> and using ThrottlePolicy.FromStore(new PolicyConfigurationProvider()) to load it.

    <configuration>
      <configSections>
        <section name="throttlePolicy" 
                 type="WebApiThrottle.ThrottlePolicyConfiguration, WebApiThrottle" />
      </configSections>
      
      <throttlePolicy limitPerSecond="1" 
                      limitPerMinute="10" 
                      limitPerHour="30" 
                      limitPerDay="300" 
                      limitPerWeek="1500" 
                      ipThrottling="true" 
                      clientThrottling="true" 
                      endpointThrottling="true">
        <rules>
          <!--Ip rules: policyType 1 is IP-->
          <add policyType="1" entry="::1/10" limitPerSecond="2" limitPerMinute="15"/>
          <!--Client rules: policyType 2 is ClientKey-->
          <add policyType="2" entry="api-client-key-1" limitPerHour="60" />
          <!--Endpoint rules: policyType 3 is Endpoint-->
          <add policyType="3" entry="api/values" limitPerDay="120" />
        </rules>
        <whitelists>
          <add policyType="1" entry="127.0.0.1" />
          <add policyType="2" entry="api-admin-key" />
        </whitelists>
      </throttlePolicy>
    </configuration>

    In C#:

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
        Policy = ThrottlePolicy.FromStore(new PolicyConfigurationProvider()),
        Repository = new CacheRepository()
    });
  5. Use ThrottlingMiddleware for OWIN

    master

    For OWIN-based applications (e.g., targeting endpoints outside the Web API area like SignalR or OAuth), use ThrottlingMiddleware. It functions similarly to the ThrottlingHandler.

    // IIS Hosted OWIN Example
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.Use(typeof(ThrottlingMiddleware),
                ThrottlePolicy.FromStore(new PolicyConfigurationProvider()),
                new PolicyCacheRepository(),
                new CacheRepository(),
                null,
                null);
        }
    }
  6. Configure Endpoint Throttling

    master

    To apply rate limits to specific routes rather than globally, set both IpThrottling and EndpointThrottling to true in the ThrottlePolicy. This ensures that a request to one route does not consume the quota for a different route.

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30)
    	{
    		IpThrottling = true,
    		EndpointThrottling = true
    	},
    	Repository = new CacheRepository()
    });
  7. Use Attribute-based Rate Limiting with ThrottlingFilter

    master

    If you prefer decorating controllers or actions with attributes, use ThrottlingFilter and EnableThrottlingAttribute. This allows you to specify custom limits directly on your API methods.

    Note: ThrottlingHandler executes earlier in the Web API request pipeline than ThrottlingFilter. Use the handler unless you specifically need the attribute-based features.

  8. Whitelist IPs and Client Keys

    master

    You can bypass throttling for specific IPs or Client Keys by using IpWhitelist and ClientWhitelist. The IpWhitelist supports IPv4 and IPv6 ranges (e.g., 192.168.0.0/24, fe80::/10) and ranges (e.g., 192.168.0.0-192.168.0.255). Whitelisted requests are not stored in the throttle counters.

    config.MessageHandlers.Add(new ThrottlingHandler()
    {
    	Policy = new ThrottlePolicy(perSecond: 2, perMinute: 60)
    	{
    		IpThrottling = true,
    		IpWhitelist = new List<string> { "::1", "192.168.0.0/24" },
    		
    		ClientThrottling = true,
    		ClientWhitelist = new List<string> { "admin-key" }
    	},
    	Repository = new CacheRepository()
    });
  9. Configure Global IP Throttling

    master

    To limit the number of requests originated from the same IP address across all endpoints, add a ThrottlingHandler to your HttpConfiguration.MessageHandlers. Use CacheRepository for IIS-hosted applications or MemoryCacheRepository for self-hosted OWIN applications.

    // IIS Hosted
    public static class WebApiConfig
    {
    	public static void Register(HttpConfiguration config)
    	{
    		config.MessageHandlers.Add(new ThrottlingHandler()
    		{
    			Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000)
    			{
    				IpThrottling = true
    			},
    			Repository = new CacheRepository()
    		});
    	}
    }
    
    // OWIN Self-Hosted
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            HttpConfiguration config = new HttpConfiguration();
    
            config.MessageHandlers.Add(new ThrottlingHandler()
            {
                Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000)
                {
                    IpThrottling = true
                },
                Repository = new MemoryCacheRepository()
            });
    
            appBuilder.UseWebApi(config);
        }
    }
  10. Update Rate Limits at Runtime

    master

    To update the ThrottlePolicy without restarting the application, you must use the ThrottlingHandler constructor that accepts an IPolicyRepository. You can then use the static ThrottleManager.UpdatePolicy method to apply changes.

    // 1. Register with PolicyRepository
    config.MessageHandlers.Add(new ThrottlingHandler(
        policy: new ThrottlePolicy(perMinute: 20) { IpThrottling = true },
        policyRepository: new PolicyCacheRepository(),
        repository: new CacheRepository()));
    
    // 2. Update anywhere in your code
    public void UpdateRateLimits()
    {
        var policyRepository = new PolicyCacheRepository();
        var policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey());
    
        policy.ClientRules["api-client-key-1"] = new RateLimits { PerMinute = 80, PerHour = 800 };
        
        ThrottleManager.UpdatePolicy(policy, policyRepository);
    }
  11. Override API Client Key Retrieval

    master

    By default, ThrottlingHandler looks for the client API key in the Authorization-Token request header. To use a different header or logic, inherit from ThrottlingHandler and override the SetIdentity method.

    public class CustomThrottlingHandler : ThrottlingHandler
    {
    	protected override RequestIdentity SetIdentity(HttpRequestMessage request)
    	{
    		return new RequestIdentity()
    		{
    			ClientKey = request.Headers.Contains("Authorization-Key") ? request.Headers.GetValues("Authorization-Key").First() : "anon",
    			ClientIp = base.GetClientIp(request).ToString(),
    			Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant()
    		};
    	}
    }