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 &quot;Too many requests&quot;.</p>
       </attribute>
 
+      <attribute name="className" required="false">
+        <p>The full class name of an implementation of the RateLimiter interface.
+        Default is &quot;org.apache.catalina.util.FastRateLimiter&quot;.</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