From 2edcd133417a432fb918a6688ec10d7093b97bec Mon Sep 17 00:00:00 2001 From: Luca Toscano Date: Sun, 1 Jul 2018 07:22:33 +0000 Subject: [PATCH] output-filters.xml: backport r1834466 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1834770 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/developer/output-filters.xml | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/manual/developer/output-filters.xml b/docs/manual/developer/output-filters.xml index 7aa43beea7..482712e186 100644 --- a/docs/manual/developer/output-filters.xml +++ b/docs/manual/developer/output-filters.xml @@ -494,4 +494,76 @@ while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) { +
+ Use case: buffering in mod_ratelimit +

The r1833875 change is a good + example to show what buffering and keeping state means in the context of an + output filter. In this use case, a user asked on the users' mailing list a + interesting question about why mod_ratelimit seemed not to + honor its setting with proxied content (either rate limiting at a different + speed or simply not doing it at all). Before diving deep into the solution, + it is better to explain on a high level how mod_ratelimit works. + The trick is really simple: take the rate limit settings and calculate a + chunk size of data to flush every 200ms to the client. For example, let's imagine + that to set rate-limit 60 in our config, these are the high level + steps to find the chunk size:

+ +/* milliseconds to wait between each flush of data */ +RATE_INTERVAL_MS = 200; +/* rate limit speed in b/s */ +speed = 60 * 1024; +/* final chunk size is 12228 bytes */ +chunk_size = (speed / (1000 / RATE_INTERVAL_MS)); + +

If we apply this calculation to a bucket brigade carrying 38400 bytes, it means + that the filter will try to do the following:

+
    +
  1. Split the 38400 bytes in chunks of maximum 12228 bytes each.
  2. +
  3. Flush the first 12228 chunk of bytes and sleep 200ms.
  4. +
  5. Flush the second 12228 chunk of bytes and sleep 200ms.
  6. +
  7. Flush the third 12228 chunk of bytes and sleep 200ms.
  8. +
  9. Flush the remaining 1716 bytes.
  10. +
+

The above pseudo code works fine if the output filter handles only one brigade + for each response, but it might happen that it needs to be called multiple times + with different brigade sizes as well. The former use case is for example when + httpd directly serves some content, like a static file: the bucket brigade + abstraction takes care of handling the whole content, and rate limiting + works nicely. But if the same static content is served via mod_proxy_http (for + example a backend is serving it rather than httpd) then the content generator + (in this case mod_proxy_http) may use a maximum buffer size and then send data + as bucket brigades to the output filters chain regularly, triggering of course + multiple calls to mod_ratelimit. If the reader tries to execute the pseudo code + assuming multiple calls to the output filter, each one requiring to process + a bucket brigade of 38400 bytes, then it is easy to spot some + anomalies:

+
    +
  1. Between the last flush of a brigade and the first one of the next, + there is no sleep.
  2. +
  3. Even if the sleep was forced after the last flush, then that chunk size + would not be the ideal size (1716 bytes instead of 12228) and the final client's speed + would quickly become different than what set in the httpd's config.
  4. +
+

In this case, two things might help:

+
    +
  1. Use the ctx internal data structure, initialized by mod_ratelimit + for each response handling cycle, to "remember" when the last sleep was + performed across multiple invocations, and act accordingly.
  2. +
  3. If a bucket brigade is not splittable into a finite number of chunk_size + blocks, store the remaining bytes (located in the tail of the bucket brigade) + in a temporary holding area (namely another bucket brigade) and then use + ap_save_brigade to set them aside. + These bytes will be preprended to the next bucket brigade that will be handled + in the subsequent invocation.
  4. +
  5. Avoid the previous logic if the bucket brigade that is currently being + processed contains the end of stream bucket (EOS). There is no need to sleep + or buffering data if the end of stream is reached.
  6. +
+

The commit linked in the beginning of the section contains also a bit of code + refactoring so it is not trivial to read during the first pass, but the overall + idea is basically what written up to now. The goal of this section is not to + cause an headache to the reader trying to read C code, but to put him/her into + the right mindset needed to use efficiently the tools offered by the httpd's + filter chain toolset.

+
-- 2.40.0