The connection speed to be simulated is specified, in KiB/s, using the environment
variable <code>rate-limit</code>.</p>
+<p>Optionally, an initial amount of burst data, in KiB, may be
+configured to be passed at full speed before throttling to the
+specified rate limit. This value is optional, and is set using
+the environment variable <code>rate-initial-burst</code>.</p>
+
<example><title>Example Configuration</title>
<highlight language="config">
<Location "/downloads">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
+ SetEnv rate-initial-burst 512
</Location>
</highlight>
</example>
{
int speed;
int chunk_size;
+ int burst;
rl_state_e state;
apr_bucket_brigade *tmpbb;
apr_bucket_brigade *holdingbb;
} rl_ctx_t;
-#if 0
+#if defined(RLFDEBUG)
static void brigade_dump(request_rec *r, apr_bucket_brigade *bb)
{
apr_bucket *e;
}
}
-#endif
+#endif /* RLFDEBUG */
static apr_status_t
rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *input_bb)
return APR_ECONNABORTED;
}
+ /* Set up our rl_ctx_t on first use */
if (ctx == NULL) {
const char *rl = NULL;
int ratelimit;
+ int burst = 0;
/* no subrequests. */
if (f->r->main != NULL) {
return ap_pass_brigade(f->next, bb);
}
+ /* Configuration: rate limit */
rl = apr_table_get(f->r->subprocess_env, "rate-limit");
if (rl == NULL) {
return ap_pass_brigade(f->next, bb);
}
- /* first run, init stuff */
+ /* Configuration: optional initial burst */
+ rl = apr_table_get(f->r->subprocess_env, "rate-initial-burst");
+ if (rl != NULL) {
+ burst = atoi(rl) * 1024;
+ if (burst <= 0) {
+ burst = 0;
+ }
+ }
+
+ /* Set up our context */
ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t));
f->ctx = ctx;
ctx->state = RATE_LIMIT;
ctx->speed = ratelimit;
+ ctx->burst = burst;
/* calculate how many bytes / interval we want to send */
/* speed is bytes / second, so, how many (speed / 1000 % interval) */
apr_brigade_length(bb, 1, &len);
- rv = apr_brigade_partition(bb, ctx->chunk_size, &stop_point);
+ /*
+ * Pull next chunk of data; the initial amount is our
+ * burst allotment (if any) plus a chunk. All subsequent
+ * iterations are just chunks with whatever remaining
+ * burst amounts we have left (in case not done in the
+ * first bucket).
+ */
+ rv = apr_brigade_partition(bb,
+ ctx->chunk_size + ctx->burst, &stop_point);
if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
ctx->state = RATE_ERROR;
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01456)
APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, fb);
-#if 0
+ /*
+ * Adjust the burst amount depending on how much
+ * we've done up to now.
+ */
+ if (ctx->burst) {
+ len = ctx->burst;
+ apr_brigade_length(ctx->tmpbb, 1, &len);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
+ APLOGNO(03485) "rl: burst %d; len %"APR_OFF_T_FMT, ctx->burst, len);
+ if (len < ctx->burst) {
+ ctx->burst -= len;
+ }
+ else {
+ ctx->burst = 0;
+ }
+ }
+
+#if defined(RLFDEBUG)
brigade_dump(f->r, ctx->tmpbb);
brigade_dump(f->r, bb);
-#endif
+#endif /* RLFDEBUG */
rv = ap_pass_brigade(f->next, ctx->tmpbb);
apr_brigade_cleanup(ctx->tmpbb);
if (rv != APR_SUCCESS) {
+ /* Most often, user disconnects from stream */
ctx->state = RATE_ERROR;
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, f->r, APLOGNO(01457)
"rl: brigade pass failed.");