You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2023/05/09 19:42:33 UTC
[tomcat] 02/03: Clean-up - formatting. No functional change.
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 7ba678c566dc82e4f169e5bc25313b310140df8f
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue May 9 20:38:49 2023 +0100
Clean-up - formatting. No functional change.
---
.../apache/catalina/filters/RateLimitFilter.java | 91 ++++++++++------------
.../apache/catalina/util/TimeBucketCounter.java | 60 ++++++--------
.../catalina/filters/TestRateLimitFilter.java | 14 ++--
3 files changed, 73 insertions(+), 92 deletions(-)
diff --git a/java/org/apache/catalina/filters/RateLimitFilter.java b/java/org/apache/catalina/filters/RateLimitFilter.java
index 94e615452a..097485aef3 100644
--- a/java/org/apache/catalina/filters/RateLimitFilter.java
+++ b/java/org/apache/catalina/filters/RateLimitFilter.java
@@ -33,45 +33,41 @@ import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
/**
- * <p>Servlet filter that can help mitigate Denial of Service
- * (DoS) and Brute Force attacks by limiting the number of a requests that are
- * allowed from a single IP address within a time window (also referred
- * to as a time bucket), e.g. 300 Requests per 60 seconds.</p>
- *
- * <p>The filter works by incrementing a counter in a time bucket for each IP
- * address, and if the counter exceeds the allowed limit then further requests
- * from that IP are dropped with a "429 Too many requests" response
- * until the bucket time ends and a new bucket starts.</p>
- *
- * <p>The filter is optimized for efficiency and low overhead, so it converts
- * some configured values to more efficient values. For example, a configuration
- * of a 60 seconds time bucket is converted to 65.536 seconds. That allows
- * for very fast bucket calculation using bit shift arithmetic. In order to remain
- * true to the user intent, the configured number of requests is then multiplied
- * by the same ratio, so a configuration of 100 Requests per 60 seconds, has the
- * real values of 109 Requests per 65 seconds.</p>
- *
- * <p>It is common to set up different restrictions for different URIs.
- * For example, a login page or authentication script is typically expected
- * to get far less requests than the rest of the application, so you can add
- * a filter definition that would allow only 5 requests per 15 seconds and map
- * those URIs to it.</p>
- *
- * <p>You can set <code>enforce</code> to <code>false</code>
- * to disable the termination of requests that exceed the allowed limit. Then
- * your application code can inspect the Request Attribute
- * <code>org.apache.catalina.filters.RateLimitFilter.Count</code> and decide
- * how to handle the request based on other information that it has, e.g. allow
- * more requests to certain users based on roles, etc.</p>
- *
- * <p><strong>WARNING:</strong> if Tomcat is behind a reverse proxy then you must
- * make sure that the Rate Limit Filter sees the client IP address, so if for
- * example you are using the <a href="#Remote_IP_Filter">Remote IP Filter</a>,
- * then the filter mapping for the Rate Limit Filter must come <em>after</em>
- * the mapping of the Remote IP Filter to ensure that each request has its IP
- * address resolved before the Rate Limit Filter is applied. Failure to do so
- * will count requests from different IPs in the same bucket and will result in
- * a self inflicted DoS attack.</p>
+ * <p>
+ * Servlet filter that can help mitigate Denial of Service (DoS) and Brute Force attacks by limiting the number of a
+ * requests that are allowed from a single IP address within a time window (also referred to as a time bucket), e.g. 300
+ * Requests per 60 seconds.
+ * </p>
+ * <p>
+ * The filter works by incrementing a counter in a time bucket for each IP address, and if the counter exceeds the
+ * allowed limit then further requests from that IP are dropped with a "429 Too many requests" response until
+ * the bucket time ends and a new bucket starts.
+ * </p>
+ * <p>
+ * The filter is optimized for efficiency and low overhead, so it converts some configured values to more efficient
+ * values. For example, a configuration of a 60 seconds time bucket is converted to 65.536 seconds. That allows for very
+ * fast bucket calculation using bit shift arithmetic. In order to remain true to the user intent, the configured number
+ * of requests is then multiplied by the same ratio, so a configuration of 100 Requests per 60 seconds, has the real
+ * values of 109 Requests per 65 seconds.
+ * </p>
+ * <p>
+ * It is common to set up different restrictions for different URIs. For example, a login page or authentication script
+ * is typically expected to get far less requests than the rest of the application, so you can add a filter definition
+ * that would allow only 5 requests per 15 seconds and map those URIs to it.
+ * </p>
+ * <p>
+ * You can set <code>enforce</code> to <code>false</code> to disable the termination of requests that exceed the allowed
+ * limit. Then your application code can inspect the Request Attribute
+ * <code>org.apache.catalina.filters.RateLimitFilter.Count</code> and decide how to handle the request based on other
+ * information that it has, e.g. allow more requests to certain users based on roles, etc.
+ * </p>
+ * <p>
+ * <strong>WARNING:</strong> if Tomcat is behind a reverse proxy then you must make sure that the Rate Limit Filter sees
+ * the client IP address, so if for example you are using the <a href="#Remote_IP_Filter">Remote IP Filter</a>, then the
+ * filter mapping for the Rate Limit Filter must come <em>after</em> the mapping of the Remote IP Filter to ensure that
+ * each request has its IP address resolved before the Rate Limit Filter is applied. Failure to do so will count
+ * requests from different IPs in the same bucket and will result in a self inflicted DoS attack.
+ * </p>
*/
public class RateLimitFilter extends GenericFilter {
@@ -199,16 +195,14 @@ public class RateLimitFilter extends GenericFilter {
actualRequests = (int) Math.round(bucketCounter.getRatio() * bucketRequests);
- log.info(sm.getString("rateLimitFilter.initialized",
- super.getFilterName(), Integer.valueOf(bucketRequests), Integer.valueOf(bucketDuration),
- Integer.valueOf(getActualRequests()), Integer.valueOf(getActualDurationInSeconds()),
- (!enforce ? "Not " : "") + "enforcing")
- );
+ log.info(sm.getString("rateLimitFilter.initialized", super.getFilterName(), Integer.valueOf(bucketRequests),
+ Integer.valueOf(bucketDuration), Integer.valueOf(getActualRequests()),
+ Integer.valueOf(getActualDurationInSeconds()), (!enforce ? "Not " : "") + "enforcing"));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
+ throws IOException, ServletException {
String ipAddr = request.getRemoteAddr();
int reqCount = bucketCounter.increment(ipAddr);
@@ -218,10 +212,9 @@ public class RateLimitFilter extends GenericFilter {
if (enforce && (reqCount > actualRequests)) {
((HttpServletResponse) response).sendError(statusCode, statusMessage);
- log.warn(sm.getString("rateLimitFilter.maxRequestsExceeded",
- super.getFilterName(), Integer.valueOf(reqCount), ipAddr, Integer.valueOf(getActualRequests()),
- Integer.valueOf(getActualDurationInSeconds()))
- );
+ log.warn(sm.getString("rateLimitFilter.maxRequestsExceeded", super.getFilterName(),
+ Integer.valueOf(reqCount), ipAddr, Integer.valueOf(getActualRequests()),
+ Integer.valueOf(getActualDurationInSeconds())));
return;
}
diff --git a/java/org/apache/catalina/util/TimeBucketCounter.java b/java/org/apache/catalina/util/TimeBucketCounter.java
index 4c1974242e..9a472523c7 100644
--- a/java/org/apache/catalina/util/TimeBucketCounter.java
+++ b/java/org/apache/catalina/util/TimeBucketCounter.java
@@ -21,34 +21,29 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * this class maintains a thread safe hash map that has timestamp-based buckets
- * followed by a string for a key, and a counter for a value. each time the
- * increment() method is called it adds the key if it does not exist, increments
- * its value and returns it.
- *
- * a maintenance thread cleans up keys that are prefixed by previous timestamp
- * buckets.
+ * This class maintains a thread safe hash map that has timestamp-based buckets followed by a string for a key, and a
+ * counter for a value. each time the increment() method is called it adds the key if it does not exist, increments its
+ * value and returns it. a maintenance thread cleans up keys that are prefixed by previous timestamp buckets.
*/
public class TimeBucketCounter {
/**
* Map to hold the buckets
*/
- private final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<String,AtomicInteger> map = new ConcurrentHashMap<>();
/**
- * Milliseconds bucket size as a Power of 2 for bit shift math, e.g.
- * 16 for 65_536ms which is about 1:05 minute
+ * Milliseconds bucket size as a Power of 2 for bit shift math, e.g. 16 for 65_536ms which is about 1:05 minute
*/
private final int numBits;
/**
- * ratio of actual duration to config duration
+ * Ratio of actual duration to config duration
*/
private final double ratio;
/**
- * flag for the maintenance thread
+ * Flag for the maintenance thread
*/
volatile boolean isRunning = false;
@@ -78,10 +73,10 @@ public class TimeBucketCounter {
}
/**
- * increments the counter for the passed identifier in the current time
- * bucket and returns the new value
+ * Increments the counter for the passed identifier in the current time bucket and returns the new value.
*
* @param identifier an identifier for which we want to maintain count, e.g. IP Address
+ *
* @return the count within the current time bucket
*/
public final int increment(String identifier) {
@@ -91,9 +86,8 @@ public class TimeBucketCounter {
}
/**
- * calculates the current time bucket prefix by shifting bits for fast
- * division, e.g. shift 16 bits is the same as dividing by 65,536 which is
- * about 1:05m
+ * Calculates the current time bucket prefix by shifting bits for fast division, e.g. shift 16 bits is the same as
+ * dividing by 65,536 which is about 1:05m.
*
* @return The current bucket prefix.
*/
@@ -106,9 +100,8 @@ public class TimeBucketCounter {
}
/**
- * the actual duration may differ from the configured duration because
- * it is set to the next power of 2 value in order to perform very fast
- * bit shift arithmetic
+ * The actual duration may differ from the configured duration because it is set to the next power of 2 value in
+ * order to perform very fast bit shift arithmetic.
*
* @return the actual bucket duration in milliseconds
*/
@@ -117,20 +110,18 @@ public class TimeBucketCounter {
}
/**
- * returns the ratio between the configured duration param and the
- * actual duration which will be set to the next power of 2. we then
- * multiply the configured requests param by the same ratio in order
- * to compensate for the added time, if any
+ * Returns the ratio between the configured duration param and the actual duration which will be set to the next
+ * power of 2. We then multiply the configured requests param by the same ratio in order to compensate for the added
+ * time, if any.
*
- * @return the ratio, e.g. 1.092 if the actual duration is 65_536 for
- * the configured duration of 60_000
+ * @return the ratio, e.g. 1.092 if the actual duration is 65_536 for the configured duration of 60_000
*/
public double getRatio() {
return ratio;
}
/**
- * returns the ratio to the next power of 2 so that we can adjust the value
+ * Returns the ratio to the next power of 2 so that we can adjust the value.
*/
static double ratioToPowerOf2(int value) {
double nextPO2 = nextPowerOf2(value);
@@ -138,8 +129,7 @@ public class TimeBucketCounter {
}
/**
- * returns the next power of 2 given a value, e.g. 256 for 250,
- * or 1024, for 1000
+ * Returns the next power of 2 given a value, e.g. 256 for 250, or 1024, for 1000.
*/
static int nextPowerOf2(int value) {
int valueOfHighestBit = Integer.highestOneBit(value);
@@ -151,8 +141,7 @@ public class TimeBucketCounter {
}
/**
- * when we want to test a full bucket duration we need to sleep until the
- * next bucket starts
+ * When we want to test a full bucket duration we need to sleep until the next bucket starts.
*
* @return the number of milliseconds until the next bucket
*/
@@ -164,14 +153,14 @@ public class TimeBucketCounter {
}
/**
- * sets isRunning to false to terminate the maintenance thread
+ * Sets isRunning to false to terminate the maintenance thread.
*/
public void destroy() {
this.isRunning = false;
}
/**
- * this class runs a background thread to clean up old keys from the map
+ * This class runs a background thread to clean up old keys from the map.
*/
class MaintenanceThread extends Thread {
@@ -194,14 +183,15 @@ public class TimeBucketCounter {
while (isRunning) {
String currentBucketPrefix = String.valueOf(getCurrentBucketPrefix());
- ConcurrentHashMap.KeySetView<String, AtomicInteger> keys = map.keySet();
+ ConcurrentHashMap.KeySetView<String,AtomicInteger> keys = map.keySet();
// remove obsolete keys
keys.removeIf(k -> !k.startsWith(currentBucketPrefix));
try {
Thread.sleep(sleeptime);
- } catch (InterruptedException e) {}
+ } catch (InterruptedException e) {
+ }
}
}
}
diff --git a/test/org/apache/catalina/filters/TestRateLimitFilter.java b/test/org/apache/catalina/filters/TestRateLimitFilter.java
index a1b0b6a0bc..bf6c397972 100644
--- a/test/org/apache/catalina/filters/TestRateLimitFilter.java
+++ b/test/org/apache/catalina/filters/TestRateLimitFilter.java
@@ -73,15 +73,15 @@ public class TestRateLimitFilter extends TomcatBaseTest {
Thread.sleep(5000);
- Assert.assertEquals(200, tc1.results[24]); // only 25 requests made, all allowed
+ Assert.assertEquals(200, tc1.results[24]); // only 25 requests made, all allowed
- Assert.assertEquals(200, tc2.results[49]); // only 25 requests made, all allowed
+ Assert.assertEquals(200, tc2.results[49]); // only 25 requests made, all allowed
Assert.assertEquals(200, tc3.results[allowedRequests - 1]); // first allowedRequests allowed
- Assert.assertEquals(429, tc3.results[allowedRequests]); // subsequent requests dropped
+ Assert.assertEquals(429, tc3.results[allowedRequests]); // subsequent requests dropped
Assert.assertEquals(200, tc4.results[allowedRequests - 1]); // first allowedRequests allowed
- Assert.assertEquals(429, tc4.results[allowedRequests]); // subsequent requests dropped
+ Assert.assertEquals(429, tc4.results[allowedRequests]); // subsequent requests dropped
}
private RateLimitFilter testRateLimitFilter(FilterDef filterDef, Context root) throws ServletException {
@@ -102,7 +102,6 @@ public class TestRateLimitFilter extends TomcatBaseTest {
rateLimitFilter.init(filterConfig);
return rateLimitFilter;
- //*/
}
static class TestClient extends Thread {
@@ -140,8 +139,7 @@ public class TestRateLimitFilter extends TomcatBaseTest {
Integer.valueOf(response.getStatus()));
Thread.sleep(sleep);
}
- }
- catch (Exception ex) {
+ } catch (Exception ex) {
ex.printStackTrace();
}
}
@@ -167,7 +165,7 @@ public class TestRateLimitFilter extends TomcatBaseTest {
private static FilterConfig generateFilterConfig(FilterDef filterDef) {
TesterServletContext mockServletContext = new TesterServletContext();
- Map<String, String> parameters = filterDef.getParameterMap();
+ Map<String,String> parameters = filterDef.getParameterMap();
FilterConfig filterConfig = new FilterConfig() {
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org