/** * Configures as a sliding window rate limit. Imagine the duration window divided into a number of smaller buckets, each with it's own count. * The number of smaller buckets is defined by the precision. * * @param precision Defines the number of buckets that will be used to approximate the sliding window. * @return a limit rule */ public RequestLimitRule withPrecision(int precision) { return new RequestLimitRule(this.durationSeconds, this.limit, precision, this.name, this.keys); }
@Override public void stop() throws Exception { requestRateLimiterFactory.close(); } });
public DefaultRequestLimitRulesSupplier(Set<RequestLimitRule> rules) { this.defaultRules = RequestLimitRulesSupplier.buildDefaultRuleSet(rules); this.ruleMap = RequestLimitRulesSupplier.buildRuleMap(rules); }
private JsonArray toJsonArray(RequestLimitRule rule) { return Json.array().asArray() .add(rule.getDurationSeconds()) .add(rule.getLimit()) .add(rule.getPrecision()); } }
DefaultRequestLimitRulesSupplierTest() { allRules.add(RequestLimitRule.of(Duration.ofSeconds(1), 10).withName("localhostPerSeconds") .matchingKeys("localhost", "127.0.0.1")); allRules.add(RequestLimitRule.of(Duration.ofHours(1), 2000).withName("localhostPerHours") .matchingKeys("localhost", "127.0.0.1")); allRules.add(RequestLimitRule.of(Duration.ofSeconds(1), 5).withName("perSeconds")); allRules.add(RequestLimitRule.of(Duration.ofHours(1), 1000).withName("perHours")); requestLimitRulesSupplier = new DefaultRequestLimitRulesSupplier(allRules); }
@Test void shouldHavePrecisionOf10() { RequestLimitRule requestLimitRule = RequestLimitRule.of(Duration.ofSeconds(1), 5).withPrecision(10); assertThat(requestLimitRule.getPrecision()).isEqualTo(10); }
@Test void shouldDefaultPrecisionToEqualDuration() { RequestLimitRule requestLimitRule = RequestLimitRule.of(Duration.ofMinutes(1), 5); assertThat(requestLimitRule.getPrecision()).isEqualTo(60); }
@Test void shouldHaveDuration60Seconds() { RequestLimitRule requestLimitRule = RequestLimitRule.of(Duration.ofMinutes(1), 5); assertThat(requestLimitRule.getDurationSeconds()).isEqualTo(60); }
@Test void shouldReloadMissingScript() { ImmutableSet<RequestLimitRule> rules = ImmutableSet.of(RequestLimitRule.of(Duration.ofSeconds(60), 1)); ReactiveRequestRateLimiter rateLimiter = getRateLimiter(rules, new SystemTimeSupplier()); rateLimiter.overLimitWhenIncrementedReactive(UUID.randomUUID().toString()).block(Duration.ofSeconds(5)); getRedisScriptingReactiveCommands().scriptFlush().block(); rateLimiter.overLimitWhenIncrementedReactive(UUID.randomUUID().toString()).block(Duration.ofSeconds(5)); } }
@Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { // This won't work if you are HTTP proxies fronting the servlet String clientIp = request.getRemoteAddr(); if (rateLimiter.overLimitWhenIncremented("remote-ip:" + clientIp)) { response.setStatus(429); } else { response.setStatus(200); response.setContentType("text/html;charset=UTF-8"); try (PrintWriter out = response.getWriter()) { out.println("hello RateLimiterJ"); } } }
private RequestLimitRule toLimitRule(Rate rate) { return RequestLimitRule.of(Duration.ofMillis(rate.timeUnit().toMillis(rate.duration())), rate.limit()); } }
InMemorySlidingWindowRequestRateLimiter(ExpiringMap<String, ConcurrentMap<String, Long>> expiringKeyMap, Set<RequestLimitRule> rules, TimeSupplier timeSupplier) { requireNonNull(rules, "rules can not be null"); requireNonNull(rules, "time supplier can not be null"); if (rules.isEmpty()) { throw new IllegalArgumentException("at least one rule must be provided"); } this.expiringKeyMap = expiringKeyMap; this.timeSupplier = timeSupplier; this.rulesSupplier = new DefaultRequestLimitRulesSupplier(rules); }
/** * Applies a key to the rate limit that defines to which keys, the rule applies, empty for any unmatched key. * * @param keys Defines a set of keys to which the rule applies. * @return a limit rule */ public RequestLimitRule matchingKeys(String... keys) { Set<String> keySet = keys.length > 0 ? new HashSet<>(Arrays.asList(keys)) : null; return matchingKeys(keySet); }
/** * Build the default rule set. * @param rules * The complete rule set. * @return * The rule set for any key that doesn't match a specific rule set. */ static Set<RequestLimitRule> buildDefaultRuleSet(Set<RequestLimitRule> rules) { return rules.stream() .filter( rule -> rule.getKeys() == null ) .collect(Collectors.toSet()); }
public HazelcastSlidingWindowRequestRateLimiter(HazelcastInstance hz, Set<RequestLimitRule> rules, TimeSupplier timeSupplier) { requireNonNull(hz, "hazelcast can not be null"); requireNonNull(rules, "rules can not be null"); if (rules.isEmpty()) { throw new IllegalArgumentException("at least one rule must be provided"); } requireNonNull(rules, "time supplier can not be null"); this.hz = hz; this.rulesSupplier = new DefaultRequestLimitRulesSupplier(rules); this.timeSupplier = timeSupplier; }
/** * Applies a name to the rate limit that is useful for metrics. * * @param name Defines a descriptive name for the rule limit. * @return a limit rule */ public RequestLimitRule withName(String name) { return new RequestLimitRule(this.durationSeconds, this.limit, this.precision, name, this.keys); }
/** * Applies a key to the rate limit that defines to which keys, the rule applies, null for any unmatched key. * * @param keys Defines a set of keys to which the rule applies. * @return a limit rule */ public RequestLimitRule matchingKeys(Set<String> keys) { return new RequestLimitRule(this.durationSeconds, this.limit, this.precision, this.name, keys); }
/** * Initialise a request rate limit. Imagine the whole duration window as being one large bucket with a single count. * * @param duration The time the limit will be applied over. * @param limit A number representing the maximum operations that can be performed in the given duration. * @return A limit rule. */ public static RequestLimitRule of(Duration duration, long limit) { requireNonNull(duration, "duration can not be null"); if (limit < 0) { throw new IllegalArgumentException("limit must be greater than zero."); } int durationSeconds = (int) duration.getSeconds(); return new RequestLimitRule(durationSeconds, limit, durationSeconds); }