You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by is...@apache.org on 2024/10/06 05:54:31 UTC
(tomcat) branch main updated: Allow different implementations for RateLimitFilter
This is an automated email from the ASF dual-hosted git repository.
isapir pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 9cc44dcca2 Allow different implementations for RateLimitFilter
9cc44dcca2 is described below
commit 9cc44dcca224ebc5fc4f8c835e3c64f42ab04fc1
Author: Igal Sapir <is...@apache.org>
AuthorDate: Sat Oct 5 22:54:21 2024 -0700
Allow different implementations for RateLimitFilter
---
.../apache/catalina/filters/RateLimitFilter.java | 82 ++++++++++----------
java/org/apache/catalina/util/FastRateLimiter.java | 88 ++++++++++++++++++++++
java/org/apache/catalina/util/RateLimiter.java | 67 ++++++++++++++++
.../catalina/filters/TestRateLimitFilter.java | 7 +-
webapps/docs/config/filter.xml | 4 +
5 files changed, 206 insertions(+), 42 deletions(-)
diff --git a/java/org/apache/catalina/filters/RateLimitFilter.java b/java/org/apache/catalina/filters/RateLimitFilter.java
index fe4674ccb7..a6cccbea30 100644
--- a/java/org/apache/catalina/filters/RateLimitFilter.java
+++ b/java/org/apache/catalina/filters/RateLimitFilter.java
@@ -18,7 +18,7 @@
package org.apache.catalina.filters;
import java.io.IOException;
-import java.util.concurrent.ScheduledExecutorService;
+import java.lang.reflect.InvocationTargetException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
@@ -28,11 +28,10 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
-import org.apache.catalina.util.TimeBucketCounter;
+import org.apache.catalina.util.RateLimiter;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
-import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor;
/**
* <p>
@@ -46,11 +45,13 @@ import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor;
* 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.
+ * The RateLimiter implementation can be set via the <code>className</code> init param. The default implementation,
+ * <code>org.apache.catalina.util.FastRateLimiter</code>, 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. You can specify
+ * a different class as long as it implements the <code>org.apache.catalina.util.RateLimiter</code> interface.
* </p>
* <p>
* It is common to set up different restrictions for different URIs. For example, a login page or authentication script
@@ -125,14 +126,19 @@ public class RateLimitFilter extends GenericFilter {
*/
public static final String PARAM_STATUS_CODE = "statusCode";
+ /**
+ * init-param to set a class name that implements RateLimiter
+ */
+ public static final String PARAM_CLASS_NAME = "className";
+
/**
* init-param to set a custom status message if requests per duration exceeded
*/
public static final String PARAM_STATUS_MESSAGE = "statusMessage";
- transient TimeBucketCounter bucketCounter;
+ transient RateLimiter rateLimiter;
- private int actualRequests;
+ private String rateLimitClassName = "org.apache.catalina.util.FastRateLimiter";
private int bucketRequests = DEFAULT_BUCKET_REQUESTS;
@@ -148,20 +154,6 @@ public class RateLimitFilter extends GenericFilter {
private static final StringManager sm = StringManager.getManager(RateLimitFilter.class);
- /**
- * @return the actual maximum allowed requests per time bucket
- */
- public int getActualRequests() {
- return actualRequests;
- }
-
- /**
- * @return the actual duration of a time bucket in milliseconds
- */
- public int getActualDurationInSeconds() {
- return bucketCounter.getActualDuration() / 1000;
- }
-
@Override
public void init() throws ServletException {
@@ -193,18 +185,26 @@ public class RateLimitFilter extends GenericFilter {
statusMessage = param;
}
- ScheduledExecutorService executorService = (ScheduledExecutorService) getServletContext()
- .getAttribute(ScheduledThreadPoolExecutor.class.getName());
- if (executorService == null) {
- executorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1);
+ param = config.getInitParameter(PARAM_CLASS_NAME);
+ if (param != null) {
+ rateLimitClassName = param;
+ }
+
+ try {
+ rateLimiter = (RateLimiter)Class.forName(rateLimitClassName).getConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
+ NoSuchMethodException | ClassNotFoundException e) {
+ throw new ServletException(e);
}
- bucketCounter = new TimeBucketCounter(bucketDuration, executorService);
- actualRequests = (int) Math.round(bucketCounter.getRatio() * bucketRequests);
+ rateLimiter.setDuration(bucketDuration);
+ rateLimiter.setRequests(bucketRequests);
+ rateLimiter.setFilterConfig(super.getFilterConfig());
- 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(rateLimiter.getRequests()), Integer.valueOf(rateLimiter.getDuration()),
+ (!enforce ? "Not " : "") + "enforcing"));
}
@Override
@@ -212,18 +212,20 @@ public class RateLimitFilter extends GenericFilter {
throws IOException, ServletException {
String ipAddr = request.getRemoteAddr();
- int reqCount = bucketCounter.increment(ipAddr);
+ int reqCount = rateLimiter.increment(ipAddr);
request.setAttribute(RATE_LIMIT_ATTRIBUTE_COUNT, Integer.valueOf(reqCount));
- if (enforce && (reqCount > actualRequests)) {
+ if (reqCount > rateLimiter.getRequests()) {
- ((HttpServletResponse) response).sendError(statusCode, statusMessage);
log.warn(sm.getString("rateLimitFilter.maxRequestsExceeded", super.getFilterName(),
- Integer.valueOf(reqCount), ipAddr, Integer.valueOf(getActualRequests()),
- Integer.valueOf(getActualDurationInSeconds())));
+ Integer.valueOf(reqCount), ipAddr, Integer.valueOf(rateLimiter.getRequests()),
+ Integer.valueOf(rateLimiter.getDuration())));
- return;
+ if (enforce) {
+ ((HttpServletResponse) response).sendError(statusCode, statusMessage);
+ return;
+ }
}
chain.doFilter(request, response);
@@ -231,7 +233,7 @@ public class RateLimitFilter extends GenericFilter {
@Override
public void destroy() {
- this.bucketCounter.destroy();
+ rateLimiter.destroy();
super.destroy();
}
}
diff --git a/java/org/apache/catalina/util/FastRateLimiter.java b/java/org/apache/catalina/util/FastRateLimiter.java
new file mode 100644
index 0000000000..bf2404ed1b
--- /dev/null
+++ b/java/org/apache/catalina/util/FastRateLimiter.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.util;
+
+import jakarta.servlet.FilterConfig;
+import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A RateLimiter that compromises accuracy for speed in order to provide maximum throughput
+ */
+public class FastRateLimiter implements RateLimiter {
+
+ TimeBucketCounter bucketCounter;
+
+ int duration;
+
+ int requests;
+
+ int actualRequests;
+
+ int actualDuration;
+
+ @Override
+ public int getDuration() {
+ return actualDuration;
+ }
+
+ @Override
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+
+ @Override
+ public int getRequests() {
+ return actualRequests;
+ }
+
+ @Override
+ public void setRequests(int requests) {
+ this.requests = requests;
+ }
+
+ @Override
+ public int increment(String ipAddress) {
+ return bucketCounter.increment(ipAddress);
+ }
+
+ @Override
+ public void destroy() {
+ bucketCounter.destroy();
+ }
+
+ @Override
+ public void setFilterConfig(FilterConfig filterConfig) {
+
+ ScheduledExecutorService executorService = (ScheduledExecutorService) filterConfig.getServletContext()
+ .getAttribute(ScheduledThreadPoolExecutor.class.getName());
+
+ if (executorService == null) {
+ executorService = new java.util.concurrent.ScheduledThreadPoolExecutor(1);
+ }
+
+ bucketCounter = new TimeBucketCounter(duration, executorService);
+ actualRequests = (int) Math.round(bucketCounter.getRatio() * requests);
+ actualDuration = bucketCounter.getActualDuration() / 1000;
+ }
+
+ public TimeBucketCounter getBucketCounter() {
+ return bucketCounter;
+ }
+}
diff --git a/java/org/apache/catalina/util/RateLimiter.java b/java/org/apache/catalina/util/RateLimiter.java
new file mode 100644
index 0000000000..fb3bb0d855
--- /dev/null
+++ b/java/org/apache/catalina/util/RateLimiter.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.util;
+
+import jakarta.servlet.FilterConfig;
+
+public interface RateLimiter {
+
+ /**
+ * @return the actual duration of a time window in seconds
+ */
+ int getDuration();
+
+ /**
+ * sets the configured duration value in seconds
+ *
+ * @param duration
+ */
+ void setDuration(int duration);
+
+ /**
+ * @return the maximum number of requests allowed per time window
+ */
+ int getRequests();
+
+ /**
+ * sets the configured number of requests allowed per time window
+ *
+ * @param requests
+ */
+ void setRequests(int requests);
+
+ /**
+ * increments the number of requests by the given ipAddress in the current time window
+ *
+ * @param ipAddress the ip address
+ * @return the new value after incrementing
+ */
+ int increment(String ipAddress);
+
+ /**
+ * cleanup no longer needed resources
+ */
+ void destroy();
+
+ /**
+ * pass the FilterConfig to configure the filter
+ *
+ * @param filterConfig
+ */
+ void setFilterConfig(FilterConfig filterConfig);
+}
diff --git a/test/org/apache/catalina/filters/TestRateLimitFilter.java b/test/org/apache/catalina/filters/TestRateLimitFilter.java
index 0d918285c7..ff9bdbf351 100644
--- a/test/org/apache/catalina/filters/TestRateLimitFilter.java
+++ b/test/org/apache/catalina/filters/TestRateLimitFilter.java
@@ -24,6 +24,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
+import org.apache.catalina.util.FastRateLimiter;
import org.junit.Assert;
import org.junit.Test;
@@ -55,9 +56,11 @@ public class TestRateLimitFilter extends TomcatBaseTest {
MockFilterChain filterChain = new MockFilterChain();
RateLimitFilter rateLimitFilter = testRateLimitFilter(filterDef, root);
- int allowedRequests = (int) Math.round(rateLimitFilter.bucketCounter.getRatio() * bucketRequests);
+ FastRateLimiter tbc = (FastRateLimiter) rateLimitFilter.rateLimiter;
- long sleepTime = rateLimitFilter.bucketCounter.getMillisUntilNextBucket();
+ int allowedRequests = (int) Math.round(tbc.getBucketCounter().getRatio() * bucketRequests);
+
+ long sleepTime = tbc.getBucketCounter().getMillisUntilNextBucket();
System.out.printf("Sleeping %d millis for the next time bucket to start\n", Long.valueOf(sleepTime));
Thread.sleep(sleepTime);
diff --git a/webapps/docs/config/filter.xml b/webapps/docs/config/filter.xml
index a3d1af57dc..3d236a77bf 100644
--- a/webapps/docs/config/filter.xml
+++ b/webapps/docs/config/filter.xml
@@ -1043,6 +1043,10 @@ FINE: Request "/docs/config/manager.html" with response status "200"
Default is "Too many requests".</p>
</attribute>
+ <attribute name="className" required="false">
+ <p>The full class name of an implementation of the RateLimiter interface.
+ Default is "org.apache.catalina.util.FastRateLimiter".</p>
+ </attribute>
</attributes>
</subsection>
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org
Re: (tomcat) branch main updated: Allow different implementations for RateLimitFilter
Posted by Mark Thomas <ma...@apache.org>.
On 06/10/2024 16:09, Igal Sapir wrote:
> On Sun, Oct 6, 2024, 06:43 Mark Thomas <ma...@apache.org> wrote:
<snip/>
>> I think this needs to be renamed for consistency. Generally, we use
>> className to define the implementing class of the component being
>> defined. For sub-components, we use something like rateLimiterClassName.
>> For an example, have a look at the Manager class where we have className
>> and secureRandomClass.
>>
>
> Sounds good. I was looking for example in the Connector configuration in
> server.xml and was unaware of the distinction between a component and a
> subcomponent.
>
> Are these rules/conventions documented anywhere? I'd be happy to start it
> if not but I'm not sure where would be a good place.
I don't think they are documented. The wiki is probably the place for that.
<snip/>
> Thank you. I will apply the changes above ASAP.
Tx.
FYI, I am currently looking at all of the Filters to see if any
can/should take advantage of the init-param setting provided by the base
class. This looks like a candidate for that but I should be able to take
care of it (just as soon as I figure out what I have done wrong that has
broken the test).
Mark
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org
Re: (tomcat) branch main updated: Allow different implementations for RateLimitFilter
Posted by Igal Sapir <is...@apache.org>.
Mark,
>
>> On Sun, Oct 6, 2024 at 8:48 AM Mark Thomas <ma...@apache.org> wrote:
>>
>>> On 06/10/2024 16:09, Igal Sapir wrote:
>>> > On Sun, Oct 6, 2024, 06:43 Mark Thomas <ma...@apache.org> wrote:
>>>
>>> <snip/>
>>>
>>> >> I think this needs to be renamed for consistency. Generally, we use
>>> >> className to define the implementing class of the component being
>>> >> defined. For sub-components, we use something like
>>> rateLimiterClassName.
>>> >> For an example, have a look at the Manager class where we have
>>> className
>>> >> and secureRandomClass.
>>> >>
>>> >
>>> > Sounds good. I was looking for example in the Connector configuration
>>> in
>>> > server.xml and was unaware of the distinction between a component and a
>>> > subcomponent.
>>> >
>>> > Are these rules/conventions documented anywhere? I'd be happy to
>>> start it
>>> > if not but I'm not sure where would be a good place.
>>>
>>> I don't think they are documented. The wiki is probably the place for
>>> that.
>>>
>>> <snip/>
>>>
>>> > Thank you. I will apply the changes above ASAP.
>>>
>>> Tx.
>>>
>>> FYI, I am currently looking at all of the Filters to see if any
>>> can/should take advantage of the init-param setting provided by the base
>>> class. This looks like a candidate for that but I should be able to take
>>
>> care of it (just as soon as I figure out what I have done wrong that has
>>> broken the test).
>>>
>>
I saw that you updated the init-param name.
I applied the two other changes that you pointed out.
Thank you,
Igal
>
>>> Mark
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
>>> For additional commands, e-mail: dev-help@tomcat.apache.org
>>>
>>>
Re: (tomcat) branch main updated: Allow different implementations for RateLimitFilter
Posted by Igal Sapir <is...@apache.org>.
Mark,
On Sun, Oct 6, 2024, 06:43 Mark Thomas <ma...@apache.org> wrote:
> On 06/10/2024 06:54, isapir@apache.org wrote:
> > This is an automated email from the ASF dual-hosted git repository.
> >
> > isapir pushed a commit to branch main
> > in repository https://gitbox.apache.org/repos/asf/tomcat.git
> >
> >
> > The following commit(s) were added to refs/heads/main by this push:
> > new 9cc44dcca2 Allow different implementations for RateLimitFilter
> > 9cc44dcca2 is described below
> >
> > commit 9cc44dcca224ebc5fc4f8c835e3c64f42ab04fc1
> > Author: Igal Sapir <is...@apache.org>
> > AuthorDate: Sat Oct 5 22:54:21 2024 -0700
> >
> > Allow different implementations for RateLimitFilter
>
> Overall, big +1 to this sort of extensibility.
>
> > + /**
> > + * init-param to set a class name that implements RateLimiter
> > + */
> > + public static final String PARAM_CLASS_NAME = "className";
>
> I think this needs to be renamed for consistency. Generally, we use
> className to define the implementing class of the component being
> defined. For sub-components, we use something like rateLimiterClassName.
> For an example, have a look at the Manager class where we have className
> and secureRandomClass.
>
Sounds good. I was looking for example in the Connector configuration in
server.xml and was unaware of the distinction between a component and a
subcomponent.
Are these rules/conventions documented anywhere? I'd be happy to start it
if not but I'm not sure where would be a good place.
Otherwise there is a risk that the next time I do something like that I
will forget and use the wrong convention again.
> > + try {
> > + rateLimiter =
> (RateLimiter)Class.forName(rateLimitClassName).getConstructor().newInstance();
> > + } catch (InstantiationException | IllegalAccessException |
> InvocationTargetException |
> > + NoSuchMethodException | ClassNotFoundException e) {
>
> You can catch ReflectiveOperationException here and simplify the above.
>
Ack
> > diff --git a/test/org/apache/catalina/filters/TestRateLimitFilter.java
> b/test/org/apache/catalina/filters/TestRateLimitFilter.java
> > index 0d918285c7..ff9bdbf351 100644
> > --- a/test/org/apache/catalina/filters/TestRateLimitFilter.java
> > +++ b/test/org/apache/catalina/filters/TestRateLimitFilter.java
> > @@ -24,6 +24,7 @@ import jakarta.servlet.FilterChain;
> > import jakarta.servlet.FilterConfig;
> > import jakarta.servlet.ServletException;
> >
> > +import org.apache.catalina.util.FastRateLimiter;
> > import org.junit.Assert;
> > import org.junit.Test;
> >
> > @@ -55,9 +56,11 @@ public class TestRateLimitFilter extends
> TomcatBaseTest {
> > MockFilterChain filterChain = new MockFilterChain();
> > RateLimitFilter rateLimitFilter =
> testRateLimitFilter(filterDef, root);
> >
> > - int allowedRequests = (int)
> Math.round(rateLimitFilter.bucketCounter.getRatio() * bucketRequests);
> > + FastRateLimiter tbc = (FastRateLimiter)
> rateLimitFilter.rateLimiter;
>
> tbc? Looks like "To Be Confirmed". Maybe expand this name.
>
Understood and agreed
> It is great you were able to address this so promptly.
>
Thank you. I will apply the changes above ASAP.
Igal
> Mark
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>
>
Re: (tomcat) branch main updated: Allow different implementations for RateLimitFilter
Posted by Mark Thomas <ma...@apache.org>.
On 06/10/2024 06:54, isapir@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
>
> isapir pushed a commit to branch main
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
>
>
> The following commit(s) were added to refs/heads/main by this push:
> new 9cc44dcca2 Allow different implementations for RateLimitFilter
> 9cc44dcca2 is described below
>
> commit 9cc44dcca224ebc5fc4f8c835e3c64f42ab04fc1
> Author: Igal Sapir <is...@apache.org>
> AuthorDate: Sat Oct 5 22:54:21 2024 -0700
>
> Allow different implementations for RateLimitFilter
Overall, big +1 to this sort of extensibility.
> + /**
> + * init-param to set a class name that implements RateLimiter
> + */
> + public static final String PARAM_CLASS_NAME = "className";
I think this needs to be renamed for consistency. Generally, we use
className to define the implementing class of the component being
defined. For sub-components, we use something like rateLimiterClassName.
For an example, have a look at the Manager class where we have className
and secureRandomClass.
> + try {
> + rateLimiter = (RateLimiter)Class.forName(rateLimitClassName).getConstructor().newInstance();
> + } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
> + NoSuchMethodException | ClassNotFoundException e) {
You can catch ReflectiveOperationException here and simplify the above.
> diff --git a/test/org/apache/catalina/filters/TestRateLimitFilter.java b/test/org/apache/catalina/filters/TestRateLimitFilter.java
> index 0d918285c7..ff9bdbf351 100644
> --- a/test/org/apache/catalina/filters/TestRateLimitFilter.java
> +++ b/test/org/apache/catalina/filters/TestRateLimitFilter.java
> @@ -24,6 +24,7 @@ import jakarta.servlet.FilterChain;
> import jakarta.servlet.FilterConfig;
> import jakarta.servlet.ServletException;
>
> +import org.apache.catalina.util.FastRateLimiter;
> import org.junit.Assert;
> import org.junit.Test;
>
> @@ -55,9 +56,11 @@ public class TestRateLimitFilter extends TomcatBaseTest {
> MockFilterChain filterChain = new MockFilterChain();
> RateLimitFilter rateLimitFilter = testRateLimitFilter(filterDef, root);
>
> - int allowedRequests = (int) Math.round(rateLimitFilter.bucketCounter.getRatio() * bucketRequests);
> + FastRateLimiter tbc = (FastRateLimiter) rateLimitFilter.rateLimiter;
tbc? Looks like "To Be Confirmed". Maybe expand this name.
It is great you were able to address this so promptly.
Mark
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org