Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rate limit handler fix #1201

Merged
merged 3 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public class LimitHandler implements MiddlewareHandler {
private static final ObjectMapper mapper = Config.getInstance().getMapper();


public LimitHandler() {
public LimitHandler() throws Exception{
config = LimitConfig.load();
logger.info("RateLimit started with key type:" + config.getKey().name());
rateLimiter = new RateLimiter(config);
}

@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
rateLimiter = new RateLimiter(config);
RateLimitResponse rateLimitResponse = rateLimiter.handleRequest(exchange, config.getKey());


Expand Down
7 changes: 5 additions & 2 deletions rate-limit/src/main/java/com/networknt/limit/RateLimiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,18 @@ protected RateLimitResponse isAllowDirect(String directKey, String path, String
*/
public RateLimitResponse isAllowByServer(String path) {
long currentTimeWindow = Instant.now().getEpochSecond();
LimitQuota limitQuota;
if (!serverTimeMap.containsKey(path)) {
synchronized(this) {
serverTimeMap.put(path, new ConcurrentHashMap<>());
}
}
LimitQuota limitQuota;
if (!config.getServer().containsKey(path)) {
limitQuota = this.config.getRateLimit().get(0);
} else {
limitQuota = config.getServer().get(path);
limitQuota = this.config.getServer().get(path);
}

Map<Long, AtomicLong> timeMap = serverTimeMap.get(path);
synchronized(this) {
if (timeMap.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;


/**
Expand All @@ -54,7 +55,7 @@ public class LimitHandlerTest {
static Undertow server = null;

@BeforeClass
public static void setUp() {
public static void setUp() throws Exception{
if(server == null) {
logger.info("starting serverconfig");
HttpHandler handler = getTestHandler();
Expand Down Expand Up @@ -155,9 +156,9 @@ public String callApi() throws Exception {
// You can run it within the IDE or remove the @Ignore and run it locally with mvn clean install.
public void testMoreRequests() throws Exception {
Callable<String> task = this::callApi;
List<Callable<String>> tasks = Collections.nCopies(10, task);
List<Callable<String>> tasks = Collections.nCopies(12, task);
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(10);
ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Future<String>> futures = executorService.invokeAll(tasks);
List<String> resultList = new ArrayList<>(futures.size());
// Check for exceptions
Expand All @@ -169,6 +170,7 @@ public void testMoreRequests() throws Exception {
}
long last = (System.currentTimeMillis() - start);
// make sure that there are at least one element in resultList is :503 or :429
Assert.assertTrue(resultList.contains(":" + config.getErrorCode()));
List<String> errorList = resultList.stream().filter(r->r.contains(":" + config.getErrorCode())).collect(Collectors.toList());
Assert.assertTrue(errorList.size()>0);
}
}
45 changes: 45 additions & 0 deletions rate-limit/src/test/resources/config/limit-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This is a serer wide rate limit configuration. It is usually used by the embedded gateway.

# If this handler is enabled or not. It is disabled by default as this handle might be in
# most http-sidecar, light-proxy and light-router instances. However, it should only be used
# internally to throttle request for a slow backend service or externally for DDoS attacks.
enabled: ${limit.enabled:true}
# To make the old config file still work, we are using this concurrentRequest to set as 1
# request per second. If you are using the new handler, please don't use this property.
# Same as rateLimit: 1 1s
concurrentRequest: ${limit.concurrentRequest:1}
# request rate limit: 10 1s 1000 1h
# 10 requests per second limit and 1000 requests per day quota.
rateLimit: 10/1s 10000/1h
# Overflow request queue size. -1 means there is no limit on the queue size and this should
# only be used in the corporate network for throttling. For Internet facing service, set it
# to a small value to prevent DDoS attacks. New requests will be dropped with 503 response
# code returned if the queue is full.
queueSize: ${limit.queueSize:1}
# If the rate limit is exposed to the Internet to prevent DDoS attacks, it will return 503
# error code to trick the DDoS client/tool to stop the attacks as it considers the server
# is down. However, if the rate limit is used internally to throttle the client requests to
# protect a slow backend API, it will return 429 error code to indicate too many requests
# for the client to wait a grace period to resent the request. By default, 503 is returned.
errorCode: ${limit.errorCode:429}
# Key of the rate limit: server, address, client
# server: The entire server has one rate limit key, and it means all users share the same.
# address: The IP address is the key and each IP will have its rate limit configuration.
# client: The client id in the JWT token so that we can give rate limit per client.
key: ${limit.key:server}
# Address specific rate limit if they don't follow the default rateLimit definition.
server:
/v1/address: 10/1s
/v2/address: 1000/1s
address:
192.168.1.100: 10/1h 1000/1d
192.168.1.101: 1000/1s 1000000/1d
192.168.1.102:
/v1/address: 10/1s
/v2/address: 100/1s
client:
f7d42348-c647-4efb-a52d-4c5787421e73: 100/1m 10000/1d
f7d42348-c647-4efb-a52d-4c5787421e74: 10/1m 1000/1d
f7d42348-c647-4efb-a52d-4c5787421e75:
/v1/address: 10/1s
/v2/address: 100/1s
2 changes: 1 addition & 1 deletion rate-limit/src/test/resources/config/limit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ queueSize: ${limit.queueSize:1}
errorCode: ${limit.errorCode:429}
# request rate limit: 10 1s 1000 1h
# 10 requests per second limit and 1000 requests per day quota.
rateLimit: 10/s 1000/d
rateLimit: 10/m 1000/d
# Key of the rate limit: server, address, client
# server: The entire server has one rate limit key, and it means all users share the same.
# address: The IP address is the key and each IP will have its rate limit configuration.
Expand Down