Coder Social home page Coder Social logo

marcosbarbero / spring-cloud-zuul-ratelimit Goto Github PK

View Code? Open in Web Editor NEW
1.1K 87.0 387.0 1.34 MB

Rate limit auto-configure for Spring Cloud Netflix Zuul

Home Page: https://blog.marcosbarbero.com/spring-cloud-netflix-zuul-rate-limit/

License: Apache License 2.0

Java 99.50% Shell 0.43% Lua 0.06%
netflix-zuul rate-limit spring-cloud spring-cloud-netflix open-source throttling throttle spring-cloud-netflix-zuul loader load-shedding

spring-cloud-zuul-ratelimit's Introduction

Spring Cloud Zuul RateLimit Build Status Coverage Status Maven Central

Overview

Module to enable rate limit per service in Netflix Zuul.

There are five built-in rate limit approaches:

  • Authenticated User

    • Uses the authenticated username or 'anonymous'

  • Request Origin

    • Uses the user origin request

  • URL

    • Uses the request path of the downstream service

  • URL Pattern

    • Uses the request Ant path pattern to the downstream service

  • ROLE

    • Uses the authenticated user roles

  • Request method

    • Uses the HTTP request method

  • Request header

    • Uses the HTTP request header

  • Global configuration per service:

    • This one does not validate the request Origin, Authenticated User or URI

    • To use this approach just don’t set param 'type'

Note

It is possible to combine Authenticated User, Request Origin, URL, ROLE and Request Method just adding multiple values to the list

Usage

Note

Latest version: Maven Central

Note

If you are using Spring Boot version 1.5.x you MUST use Spring Cloud Zuul RateLimit version 1.7.x. Please take a look at the Maven Central and pick the latest artifact in this version line.

Add the dependency on pom.xml

<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>${latest-version}</version>
</dependency>

Add the following dependency accordingly to the chosen data storage:

  • Redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • Consul

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-consul</artifactId>
</dependency>
  • Spring Data JPA

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

This implementation also requires a database table, bellow here you can find a sample script:

CREATE TABLE rate (
  rate_key VARCHAR(255) NOT NULL,
  remaining BIGINT,
  remaining_quota BIGINT,
  reset BIGINT,
  expiration TIMESTAMP,
  PRIMARY KEY(rate_key)
);
  • Bucket4j JCache

<dependency>
     <groupId>com.github.vladimir-bukhtoyarov</groupId>
     <artifactId>bucket4j-core</artifactId>
</dependency>
<dependency>
     <groupId>com.github.vladimir-bukhtoyarov</groupId>
     <artifactId>bucket4j-jcache</artifactId>
</dependency>
<dependency>
     <groupId>javax.cache</groupId>
     <artifactId>cache-api</artifactId>
</dependency>
  • Bucket4j Hazelcast (depends on Bucket4j JCache)

<dependency>
     <groupId>com.github.vladimir-bukhtoyarov</groupId>
     <artifactId>bucket4j-hazelcast</artifactId>
</dependency>
<dependency>
     <groupId>com.hazelcast</groupId>
     <artifactId>hazelcast</artifactId>
</dependency>
  • Bucket4j Infinispan (depends on Bucket4j JCache)

<dependency>
     <groupId>com.github.vladimir-bukhtoyarov</groupId>
     <artifactId>bucket4j-infinispan</artifactId>
</dependency>
<dependency>
     <groupId>org.infinispan</groupId>
     <artifactId>infinispan-core</artifactId>
</dependency>
  • Bucket4j Ignite (depends on Bucket4j JCache)

<dependency>
     <groupId>com.github.vladimir-bukhtoyarov</groupId>
     <artifactId>bucket4j-ignite</artifactId>
</dependency>
<dependency>
     <groupId>org.apache.ignite</groupId>
     <artifactId>ignite-core</artifactId>
</dependency>

Sample YAML configuration

zuul:
  ratelimit:
    key-prefix: your-prefix
    enabled: true
    repository: REDIS
    behind-proxy: true
    add-response-headers: true
    deny-request:
      response-status-code: 404 #default value is 403 (FORBIDDEN)
      origins:
        - 200.187.10.25
        - somedomain.com
    default-policy-list: #optional - will apply unless specific policy exists
      - limit: 10 #optional - request number limit per refresh interval window
        quota: 1000 #optional - request time limit per refresh interval window (in seconds)
        refresh-interval: 60 #default value (in seconds)
        type: #optional
          - user
          - origin
          - url
          - http_method
    policy-list:
      myServiceId:
        - limit: 10 #optional - request number limit per refresh interval window
          quota: 1000 #optional - request time limit per refresh interval window (in seconds)
          refresh-interval: 60 #default value (in seconds)
          type: #optional
            - user
            - origin
            - url
        - type: #optional value for each type
            - user=anonymous
            - origin=somemachine.com
            - url=/api #url prefix
            - role=user
            - http_method=get #case insensitive
            - http_header=customHeader
        - type:
            - url_pattern=/api/*/payment

Sample Properties configuration

zuul.ratelimit.enabled=true
zuul.ratelimit.key-prefix=your-prefix
zuul.ratelimit.repository=REDIS
zuul.ratelimit.behind-proxy=true
zuul.ratelimit.add-response-headers=true

zuul.ratelimit.deny-request.response-status-code=404
zuul.ratelimit.deny-request.origins[0]=200.187.10.25
zuul.ratelimit.deny-request.origins[1]=somedomain.com

zuul.ratelimit.default-policy-list[0].limit=10
zuul.ratelimit.default-policy-list[0].quota=1000
zuul.ratelimit.default-policy-list[0].refresh-interval=60

# Adding multiple rate limit type
zuul.ratelimit.default-policy-list[0].type[0]=user
zuul.ratelimit.default-policy-list[0].type[1]=origin
zuul.ratelimit.default-policy-list[0].type[2]=url
zuul.ratelimit.default-policy-list[0].type[3]=http_method

# Adding the first rate limit policy to "myServiceId"
zuul.ratelimit.policy-list.myServiceId[0].limit=10
zuul.ratelimit.policy-list.myServiceId[0].quota=1000
zuul.ratelimit.policy-list.myServiceId[0].refresh-interval=60
zuul.ratelimit.policy-list.myServiceId[0].type[0]=user
zuul.ratelimit.policy-list.myServiceId[0].type[1]=origin
zuul.ratelimit.policy-list.myServiceId[0].type[2]=url

# Adding the second rate limit policy to "myServiceId"
zuul.ratelimit.policy-list.myServiceId[1].type[0]=user=anonymous
zuul.ratelimit.policy-list.myServiceId[1].type[1]=origin=somemachine.com
zuul.ratelimit.policy-list.myServiceId[1].type[2]=url_pattern=/api/*/payment
zuul.ratelimit.policy-list.myServiceId[1].type[3]=role=user
zuul.ratelimit.policy-list.myServiceId[1].type[4]=http_method=get
zuul.ratelimit.policy-list.myServiceId[1].type[5]=http_header=customHeader

Both 'quota' and 'refresh-interval', can be expressed with Spring Boot’s duration formats:

  • A regular long representation (using seconds as the default unit)

  • The standard ISO-8601 format used by java.time.Duration (e.g. PT30M means 30 minutes)

  • A more readable format where the value and the unit are coupled (e.g. 10s means 10 seconds)

Available implementations

There are eight implementations provided:

Implementation Data Storage

ConsulRateLimiter

Consul

RedisRateLimiter

Redis

SpringDataRateLimiter

Spring Data

Bucket4jJCacheRateLimiter

Bucket4j

Bucket4jHazelcastRateLimiter

Bucket4jIgniteRateLimiter

Bucket4jInfinispanRateLimiter

Bucket4j implementations require the relevant bean with @Qualifier("RateLimit"):

  • JCache - javax.cache.Cache

  • Hazelcast - com.hazelcast.map.IMap

  • Ignite - org.apache.ignite.IgniteCache

  • Infinispan - org.infinispan.functional.ReadWriteMap

Common application properties

Property namespace: zuul.ratelimit

Property name Values Default Value

enabled

true/false

false

behind-proxy

true/false

false

response-headers

NONE, STANDARD, VERBOSE

VERBOSE

key-prefix

String

${spring.application.name:rate-limit-application}

repository

CONSUL, REDIS, JPA, BUCKET4J_JCACHE, BUCKET4J_HAZELCAST, BUCKET4J_INFINISPAN, BUCKET4J_IGNITE

-

deny-request

DenyRequest

-

default-policy-list

List of Policy

-

policy-list

Map of Lists of Policy

-

postFilterOrder

int

FilterConstants.SEND_RESPONSE_FILTER_ORDER - 10

preFilterOrder

int

FilterConstants.FORM_BODY_WRAPPER_FILTER_ORDER

Deny Request properties

Property name Values Default Value

origins

list of origins to have the access denied

-

response-status-code

the http status code to be returned on a denied request

403 (FORBIDDEN)

Policy properties:

Property name Values Default Value

limit

number of requests

-

quota

time of requests

-

refresh-interval

seconds

60

type

[ORIGIN, USER, URL, URL_PATTERN, ROLE, HTTP_METHOD, HTTP_HEADER]

[]

breakOnMatch

true/false

false

Further Customization

This section details how to add custom implementations

Key Generator

If the application needs to control the key strategy beyond the options offered by the type property then it can be done just by creating a custom RateLimitKeyGenerator bean[1] implementation adding further qualifiers or something entirely different:

  @Bean
  public RateLimitKeyGenerator ratelimitKeyGenerator(RateLimitProperties properties, RateLimitUtils rateLimitUtils) {
      return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
          @Override
          public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
              return super.key(request, route, policy) + ":" + request.getMethod();
          }
      };
  }

Error Handling

This framework uses 3rd party applications to control the rate limit access and these libraries are out of control of this framework. If one of the 3rd party applications fails, the framework will handle this failure in the DefaultRateLimiterErrorHandler class which will log the error upon failure.

If there is a need to handle the errors differently, it can be achieved by defining a custom RateLimiterErrorHandler bean[2], e.g:

  @Bean
  public RateLimiterErrorHandler rateLimitErrorHandler() {
    return new DefaultRateLimiterErrorHandler() {
        @Override
        public void handleSaveError(String key, Exception e) {
            // custom code
        }

        @Override
        public void handleFetchError(String key, Exception e) {
            // custom code
        }

        @Override
        public void handleError(String msg, Exception e) {
            // custom code
        }
    }
  }

Event Handling

If the application needs to be notified when a rate limit access was exceeded then it can be done by listening to RateLimitExceededEvent event:

    @EventListener
    public void observe(RateLimitExceededEvent event) {
        // custom code
    }

Contributing

Spring Cloud Zuul Rate Limit is released under the non-restrictive Apache 2.0 license, and follows a very standard Github development process, using Github tracker for issues and merging pull requests into master. If you want to contribute even something trivial please do not hesitate, but follow the guidelines below.

Code of Conduct

This project adheres to the Contributor Covenant code of conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [email protected].

Acknowledgement

Jetbrains

Footnote

Any doubt open an issue. Any fix send me a Pull Request.


1. By declaring a new RateLimitKeyGenerator, you replace the DefaultRateLimitKeyGenerator.
2. By declaring a new RateLimitErrorHandler, you replace the DefaultRateLimitErrorHandler.

spring-cloud-zuul-ratelimit's People

Contributors

aloren avatar andrew-holman avatar dbelf avatar dependabot-preview[bot] avatar dependabot[bot] avatar devdengchao avatar fpnuseis avatar gitter-badger avatar lchayoun avatar marcosbarbero avatar mohamedfawzy1993 avatar panmax avatar phrinx avatar richardcsantana avatar roxspring avatar silho avatar vasilaio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-cloud-zuul-ratelimit's Issues

Move filter order to configuration

I was considering using your library for rate limiting and the only thing that is bothering me is the hardcoded order of the filters. We have a bunch of custom filters in Zuul that retrieve user data and based on this data we would like to do rate limiting. That means that RateLimitPreFilter should be placed after our custom filters. What do you think about moving filterOrder to configuration?
Thanks

get ip security bug

com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitUtils
at 40 line
"String xForwardedFor = request.getHeader(X_FORWARDED_FOR_HEADER);"
should be "X-REAL-IP"

Multiple policy for one service

Hi another question is it possible to have multiple policy for one service tried by seems cant do it correctly
Lets say we wanted to have within 1 minute max 10 request, but within 1 hour max 100 request

Failed to process import candidates for configuration class [com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.RateLimitAutoConfiguration

I tried run zuul-ratelimit, but i met some errors, i wish you can help me, thanks!

application.yml

server:
  port: 8080
spring:
  application:
    name: GETWAY

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

zuul:
  ratelimit:
    enabled: true
    behind-proxy: true
    policy-list:
      service:
        - limit: 10
          refresh-interval: 60
          type:
            - user
            - origin
            - url

pom.xml


<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/>
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>1.7.2.RELEASE</version>
        </dependency>
    </dependencies>

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
2018-06-12 20:16:12.847 ERROR 4108 --- [           main] o.s.boot.SpringApplication               : Application startup failed

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.RateLimitAutoConfiguration]; nested exception is java.lang.ClassCastException: java.lang.UnsupportedClassVersionError cannot be cast to [Ljava.lang.Object;
	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:616) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:299) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:606) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:548) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:185) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:308) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:228) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:270) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:93) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:686) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:524) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:134) [spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at com.bozhong.Application.main(Application.java:16) [classes/:na]
Caused by: java.lang.ClassCastException: java.lang.UnsupportedClassVersionError cannot be cast to [Ljava.lang.Object;
	at org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.selectImports(EnableConfigurationPropertiesImportSelector.java:54) ~[spring-boot-1.5.3.RELEASE.jar:1.5.3.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:586) ~[spring-context-4.3.8.RELEASE.jar:4.3.8.RELEASE]
	... 17 common frames omitted

Is that possible to control rate limit dynamically?

In my case, service#1 and service#2 are sharing a same request pool (to access a thirdparty service). The access rate of service#2 is very unstable, usually it's free, but sometimes many connections may come in suddenly and it will become very busy

What I want to implemented is: limit service#1 to 5 qps as a basic, limit service#2 to 10 qps as a basic, when service#1 reaches its limit, I want to check if service#2 is busy or not , if service#2 is not very busy, I will let service#1 steal 1 quota from service#2 for this perticular access.

Default policy configuration

Currently each service need to be configured in order to use rate limit
Since routes can be auto discovered in the gateway I think it will be a good idea to add a default policy configuration that can be overridden by each service

@marcosbarbero WDYT?

Validate maven central deployment

Add test projects using the latest release from maven central in order to validate the maven release:

  • create sample projects using latest version
  • clean .m2 repository
  • build the project using mvn clean package

Rate limit by request time

we have seen cases that rate limit by request number is not enough
sometimes a single request can be long and load the gateway

adding an option to measure the request time and add another limit option by time quetta might help in such cases

@marcosbarbero do you think it's a good idea?

ability to disable rate limiting basing on flag in request context

I would like to disable RateLimitPreFilter and RateLimitPostFilter on the fly.

It would be great to modify implementation of shouldFilter method for filters. For example it will stop liming when "rateLimitDisabled" flag in request context is present and set true.

about ZuulRuntimeException

hi, in the code of RateLimitPreFilter,we will throw a Exception:ZuulRuntimeException to show Too Many Requests.
But as a gateway, I must ensure no exception throw to clients.
so I create a Error Filter as a adapter for the exceptions. I think if throw a custom Exception,it is easy use instanceof to distinguish.
For example, when there is a Too Many Requests Exception, I want to return client a custom string message.

failed to run zuul-ratelimit

I add the depency

<dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>1.7.2.RELEASE</version>
        </dependency>

and the yml is :

zuul:
  routes:
    mercury-service:
      path: /ark/open_api/**
      url:  http://mercury-default-upd-product.dev.xiaohongshu.com/
      stripPrefix: false
  ratelimit:
    enable: true
    behind-proxy: true
#    repository: JPA
    policies:
      mercury-service:
        limit: 2
        quota: 1
        refresh-interval: 30
        type:
          - URL
          - ORIGIN
          - USER

then I run the springboot application, but when I call the API,the policies is not used ,should I over write a filter extends RateLimitPreFilter.

dynamic config params without restart

how can I use the project with dynamic params, just as if I want to limit some special users in a certain time interval, or config some high rate for my promotion after add more services. I saw that the project must config these things in yaml, and it may restart service after change the config. So dose it has some special treatment for this issue?

How to use this situation

An application with @EnableZuulProxy could act as a standalone server if you set a default route ("/"), for example zuul.route.home: / would route all traffic (i.e. "/**") to the "home" service.

InMemoryRateLimiter

  1. good job, but when i use it in my project (the version is Brixton.RELEASE) , can't find this class org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException

Policies required for every route on gateway

The method key make a request for policy.getTypes() after policy():

final Policy policy = this.policy();		
final Route route = route();
StringBuilder builder = new StringBuilder(route.getId());
if (policy.getType().contains(Policy.Type.ORIGIN)) {
...
if (policy.getType().contains(Policy.Type.USER)) {
...
private Policy policy() {
    return (route() != null) ? properties.getPolicies().get(route().getId()) : null;
}

So its implies that every route under the gateway should have a policy or it will throw a NullPointer.

Unable to find parent artefact 'spring-cloud-zuul-ratelimit-parent'

I am trying to add this dependency in my project.

On build, maven tries to fetch the url - https://jcenter.bintray.com/com/marcosbarbero/cloud/spring-cloud-zuul-ratelimit-parent/1.3.0.RELEASE/spring-cloud-zuul-ratelimit-parent-1.3.0.RELEASE.pom

And I get an error -
Failed to execute goal on project gateway-service: Could not resolve dependencies for project io.cimpress.abc.backbone.gateway:gateway-service:jar:1.0.0: Failed to collect dependencies at com.marcosbarbero.cloud:spring-cloud-zuul-ratelimit:jar:1.3.0.RELEASE: Failed to read artifact descriptor for com.marcosbarbero.cloud:spring-cloud-zuul-ratelimit:jar:1.3.0.RELEASE: Could not find artifact com.marcosbarbero.cloud:spring-cloud-zuul-ratelimit-parent:pom:1.3.0.RELEASE in central

On checking the file, spring-cloud-zuul-ratelimit/spring-cloud-zuul-ratelimit-core/pom.xml
I found that the parent artefact id is 'spring-cloud-zuul-ratelimit-parent' which I could not find in maven artefactory. I am not sure if this is the reason that I am not able to add dependency.

Can someone help me with this?
Thanks

A service with different interfaces has different policies

This is my application.yml

  ratelimit:
    key-prefix:
    enabled: true
    repository: IN_MEMORY
    behind-proxy: true
    default-policy-list:
      - limit: 10
        quota: 1000
        refresh-interval: 60
        type:
          - url=/api/get/4Kb
      - limit: 20
        quota: 1000
        refresh-interval: 60
        type:
          - url=/hello/hi

Then access /api/get/256Byte ,it will report the following error,how to configure multiple interfaces different policies?

this is ErrorFilter : Duplicate key RateLimitProperties.Policy(refreshInterval=60, limit=10, quota=1000, type=[RateLimitProperties.Policy.MatchType(type=URL, matcher=/api/get/4Kb)])

Correction for readme.md

@Bean
  public RateLimitKeyGenerator rateLimitKeyGenerator(final RateLimitProperties properties) {
      return new DefaultRateLimitKeyGenerator(properties) {
          @Override
          public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
              return super.key(request, route, policy) + ":" + request.getMethod();
          }
      };
  }

i think you need to pass RateLimitUtils also CMIIW

Add project on central maven

I didn't find any relative package on maven central. Can be a great addition to add it on maven central in order to easily use this starter

`policy(route, request)` called 4 times during handling one request

I was looking into filters and found that policy(route, request) is called 4 times during routing of the one request. Two times it is called in each shouldFilter and another 2 times in each run method of pre and post filters.
I think that this value could be computed on first shouldFilter and put into RequestContext.

The same applies to route(HttpServletRequest request) method.

WDYT?

NPE in DefaultRateLimitKeyGenerator

In the case that someone tries to access a url without a route, NPE is thrown.
This is because it tries to get the id and the path from the route

Caused by: java.lang.NullPointerException: null
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.DefaultRateLimitKeyGenerator.key(DefaultRateLimitKeyGenerator.java:46)
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.lambda$run$0(RateLimitPreFilter.java:76)
	at java.util.Optional.ifPresent(Optional.java:159)
	at com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.filters.RateLimitPreFilter.run(RateLimitPreFilter.java:75)
	at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112)
	at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193)

JPA uses MySQL reserved `KEY` keyword

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'varchar(255) not null, expiration datetime, remaining bigint, remaining_quota bi' at line 1
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_101]
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_101]
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_101]
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_101]

I know the reason, because of the use of the MySQL keyword key.
I revise as follows:

create table rate (key2 varchar(255) not null, expiration datetime, remaining bigint, remaining_quota bigint, reset bigint, primary key (key2))

It can run.
I use MySql version 5.7.20.
image

MySQL Keywords reference

Add spring-boot-configuration processor

Add the following dependency to the core module

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

Commands not supported in redis cluster

The methods multi and exec of class org.springframework.data.redis.core.RedisOperations are not supported in org.springframework.data.redis.connection.jedis.JedisClusterConnection, so it can`t work with redis cluster.

Storage service downgrade

If the storage service fails, let the user decide in the configuration file whether or not to pass the check.

such as :
my repo is redis, but my redis server is done, RateLimitFilter Should it pass?
@marcosbarbero

Registering multiple rate limiters

My use case: I would like to do rate limiting in context of multiple keys.

For example limit number of requests to 10 req/s for key = client+tenant+user but 1000 req/s for key = client+tenant.

Is it possible to accomplish this with current implementation?

Support multiple policies

Currently only supports a policy, but in many cases, we need a variety of strategies for different current limit, so I would like to modify the code to support multiple policies, you think of this idea? I now have a fork branch that does this 23mf:feat-support-policies

about use custom redis

hi,I have tow question, if you have time, please give me some opinions, thank you.
First,i want use my redis config, so i create this class:

@Component
@Configuration
public class RedisRateLimiterConfiguration {

    @Autowired
    RedisTemplate redisTemplate;

    @Bean
    public RateLimiter redisRateLimiter(RateLimiterErrorHandler rateLimiterErrorHandler) {
        return new RedisRateLimiter(rateLimiterErrorHandler, redisTemplate);
    }
}

Because there is no wiki to know how to do, so my question is this is right or perfect way?

Second,RateLimitPreFilterand RateLimitPostFilter have hard coding filterOrder, how can i change it.

Request.getRemoteUser () is always null.

I use eureka, zuul and oauth (spring-cloud-starter-oauth2), request.getRemoteUser () is always null.
In DefaultRateLimitKeyGenerator:

if (types.contains(Type.USER)) {
    joiner.add(request.getRemoteUser() != null ? request.getRemoteUser() : ANONYMOUS_USER);
}

I miss some something?

AbstractRateLimitFilter improvements

  1. I suppose it makes sense to extract applyPolicy to the separate class, that would contain a list of other filters, not knowing about concrete implementations. This change will give possibility to add custom policy filters.
    Something like that:
    private final List<PolicyFilter> filters;
    private final MatchersByTypeIndex matchersIndex;

    @Override
    public boolean filter(HttpServletRequest request, Route route, 
                                       RateLimitProperties.Policy policy) {
        return policy.getType().isEmpty() || allFiltersApply(request, route, policy);
    }

    private boolean allFiltersApply(HttpServletRequest request, Route route, 
                                                        RateLimitProperties.Policy policy) {
        return filters
                .stream()
                .allMatch(policyFilter -> {
                    Set<String> matchers = matchersIndex.get(policyFilter.getType());
                    return policyFilter.filter(matchers, request, route);
                });
    }
  1. Consider using Set instead of List for users and origins in userApply and originApply methods. Complexity of contains operation on set is O(1), on list is O(n). Usage of Set will not degrade app if big amount of matchers is added.
  2. getConfiguredType can be precomputed on startup and instead of list can be saved into map<Type, Set>. So for example instead of:
    private boolean urlApply(List<MatchType> types, Route route) {
        List<String> urls = getConfiguredType(types, Type.URL);

        return urls.isEmpty()
            || route == null
            || urls.stream().anyMatch(url -> route.getPath().startsWith(url));
    }

it can be done this way:

    private boolean urlApply(List<MatchType> types, Route route) {
        Set<String> urls = matchersIndex.getOrDefault(Type.URL, Collections.emptySet());

        return urls.isEmpty()
            || route == null
            || urls.stream().anyMatch(url -> route.getPath().startsWith(url));
    }

This would decrease complexity of the method.

If you are interested I would like to open PRs with described changes.

Get only origin IP instead of list from X-Forwarded-For when behind-proxy: true

In com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitUtils

    public String getRemoteAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader(X_FORWARDED_FOR_HEADER);
        if (properties.isBehindProxy() && xForwardedFor != null) {
            return xForwardedFor;
        }
        return request.getRemoteAddr();
    }

Since X-Forwarded-For is a list of IPs (X-Forwarded-For: client, proxy1, proxy2), I think it should return only the first IP (client IP) instead of all IP.

Further customization problem

Further customization mentioned a bean injected to override RateLimitKeyGenerator.
I override the RateLimitKeyGenerator in Spring boot Application but got build errors.

 @Bean
    public RateLimitKeyGenerator rateLimitKeyGenerator(final RateLimitProperties properties) {
        return new DefaultRateLimitKeyGenerator(properties) {
            @Override
            public String key(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
                String key = customkey(request, route, policy) + ":" + request.getMethod();
                return key;
            }

            public String customkey(final HttpServletRequest request, final Route route, final Policy policy) {
                final List<Type> types = policy.getType();
                final StringJoiner joiner = new StringJoiner(":");
                joiner.add(properties.getKeyPrefix());
                if (route != null) {
                    joiner.add(route.getId());
                }
                if (!types.isEmpty()) {
                    if (types.contains(Type.URL) && route != null) {
                        // joiner.add(route.getPath());
                        // updated for full path + domain
                        joiner.add(route.getFullPath());
                    }
                    if (types.contains(Type.ORIGIN)) {
                        joiner.add(this.getRemoteAddress(request));
                    }
                    if (types.contains(Type.USER)) {
                        // joiner.add(request.getRemoteUser() != null ? request.getRemoteUser() :
                        // "ANONYMOUS_USER");
                        // updated for sid and token
                        String value = null;
                        Cookie[] cookies = request.getCookies();
                        if ((null != cookies) || (cookies.length != 0)) {
                            for (Cookie cookie : cookies) {
                                if (cookie.getName().equalsIgnoreCase(ConstantsBean.SID)) {
                                    value = cookie.getValue();
                                }
                            }
                        }
                        joiner.add(value);
                    }
                }
                return joiner.toString();
            }

            private String getRemoteAddress(final HttpServletRequest request) {
                String xForwardedFor = request.getHeader(X_FORWARDED_FOR_HEADER);
                if (properties.isBehindProxy() && xForwardedFor != null) {
                    return xForwardedFor;
                }
                return request.getRemoteAddr();
            }
        };
    }

maven install got following:

Description:

Parameter 0 of method rateLimitKeyGenerator in com.wanda.cloud.bss.apigw.ApiGatewayApplication required a bean of type 'com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties' that could not be found.


Action:

Consider defining a bean of type 'com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties' in your configuration.

2017-12-06 18:35:09.185 ERROR 8364 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@2a32de6c] to prepare test instance [com.wanda.cloud.bss.apigw.ApiGatewayApplicationTests@4cc7d00d]

java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) ~[spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189) ~[spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131) ~[spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) ~[spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283) [surefire-junit4-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173) [surefire-junit4-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153) [surefire-junit4-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128) [surefire-junit4-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203) [surefire-booter-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155) [surefire-booter-2.18.1.jar:2.18.1]
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103) [surefire-booter-2.18.1.jar:2.18.1]

Any suggestions that if the injection has any problem

APPLICATION FAILED TO START

use version 1.3.4.RELEASE
add property zuul.ratelimit.repository
Parameter 0 of method rateLimiterPreFilter in com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.RateLimitAutoConfiguration required a bean of type 'com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimiter' that could not be found.
- Bean method 'consultRateLimiter' not loaded because @ConditionalOnProperty (zuul.ratelimit.repository=CONSUL) found different value in property 'repository'
- Bean method 'inMemoryRateLimiter' not loaded because @ConditionalOnProperty (zuul.ratelimit.repository=IN_MEMORY) found different value in property 'repository'
- Bean method 'redisRateLimiter' not loaded because @ConditionalOnProperty (zuul.ratelimit.repository=REDIS) found different value in property 'repository'
- Bean method 'springDataRateLimiter' not loaded because @ConditionalOnProperty (zuul.ratelimit.repository=JPA) found different value in property 'repository'

need working sample for redis, as well as other storage methods

hi,
I could work with this project using IN_MEMORY as storage, however, when i used REDIS, I just failed many times that it failed to start with this exception,

No qualifying bean of type [org.springframework.data.redis.core.RedisTemplate] found for dependency [org.springframework.data.redis.core.RedisTemplate<?, ?>]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=rateLimiterRedisTemplate)}

could you please update a working sample for users to use this project quickly enough

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.