removed some of the debug logging and added author details
[httpd-crcsyncproxy.git] / modules / filters / mod_ratelimit.c
blob1b2e4ff5d62cf5ba1c5fd8fd85679736884fc439
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "httpd.h"
18 #include "http_config.h"
19 #include "http_log.h"
20 #include "util_filter.h"
22 #include "mod_ratelimit.h"
24 #define RATE_LIMIT_FILTER_NAME "RATE_LIMIT"
25 #define RATE_INTERVAL_MS (200)
27 typedef enum rl_state_e
29 RATE_ERROR,
30 RATE_LIMIT,
31 RATE_FULLSPEED
32 } rl_state_e;
34 typedef struct rl_ctx_t
36 int speed;
37 int chunk_size;
38 rl_state_e state;
39 apr_bucket_brigade *tmpbb;
40 apr_bucket_brigade *holdingbb;
41 } rl_ctx_t;
43 #if 0
44 static void brigade_dump(request_rec *r, apr_bucket_brigade *bb)
46 apr_bucket *e;
47 int i = 0;
49 for (e = APR_BRIGADE_FIRST(bb);
50 e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e), i++) {
51 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
52 "brigade: [%d] %s", i, e->type->name);
56 #endif
58 static apr_status_t
59 rate_limit_filter(ap_filter_t *f, apr_bucket_brigade *input_bb)
61 apr_status_t rv = APR_SUCCESS;
62 rl_ctx_t *ctx = f->ctx;
63 apr_bucket *fb;
64 int do_sleep = 0;
65 apr_bucket_alloc_t *ba = f->r->connection->bucket_alloc;
66 apr_bucket_brigade *bb = input_bb;
68 if (f->c->aborted) {
69 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "rl: conn aborted");
70 apr_brigade_cleanup(bb);
71 return APR_ECONNABORTED;
74 if (ctx == NULL) {
76 const char *rl = NULL;
78 /* no subrequests. */
79 if (f->r->main != NULL) {
80 ap_remove_output_filter(f);
81 return ap_pass_brigade(f->next, bb);
84 rl = apr_table_get(f->r->subprocess_env, "rate-limit");
86 if (rl == NULL) {
87 ap_remove_output_filter(f);
88 return ap_pass_brigade(f->next, bb);
91 /* first run, init stuff */
92 ctx = apr_palloc(f->r->pool, sizeof(rl_ctx_t));
93 f->ctx = ctx;
94 ctx->speed = 0;
95 ctx->state = RATE_LIMIT;
97 /* rl is in kilo bytes / second */
98 ctx->speed = atoi(rl) * 1024;
100 if (ctx->speed == 0) {
101 /* remove ourselves */
102 ap_remove_output_filter(f);
103 return ap_pass_brigade(f->next, bb);
106 /* calculate how many bytes / interval we want to send */
107 /* speed is bytes / second, so, how many (speed / 1000 % interval) */
108 ctx->chunk_size = (ctx->speed / (1000 / RATE_INTERVAL_MS));
109 ctx->tmpbb = apr_brigade_create(f->r->pool, ba);
110 ctx->holdingbb = apr_brigade_create(f->r->pool, ba);
113 while (ctx->state != RATE_ERROR &&
114 (!APR_BRIGADE_EMPTY(bb) || !APR_BRIGADE_EMPTY(ctx->holdingbb))) {
115 apr_bucket *e;
117 if (!APR_BRIGADE_EMPTY(ctx->holdingbb)) {
118 APR_BRIGADE_CONCAT(bb, ctx->holdingbb);
119 apr_brigade_cleanup(ctx->holdingbb);
122 while (ctx->state == RATE_FULLSPEED && !APR_BRIGADE_EMPTY(bb)) {
123 /* Find where we 'stop' going full speed. */
124 for (e = APR_BRIGADE_FIRST(bb);
125 e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
126 if (RL_BUCKET_IS_END(e)) {
127 apr_bucket *f;
128 f = APR_RING_LAST(&bb->list);
129 APR_RING_UNSPLICE(e, f, link);
130 APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f,
131 apr_bucket, link);
132 ctx->state = RATE_LIMIT;
133 break;
137 if (f->c->aborted) {
138 apr_brigade_cleanup(bb);
139 ctx->state = RATE_ERROR;
140 break;
143 fb = apr_bucket_flush_create(ba);
144 APR_BRIGADE_INSERT_TAIL(bb, fb);
145 rv = ap_pass_brigade(f->next, bb);
147 if (rv != APR_SUCCESS) {
148 ctx->state = RATE_ERROR;
149 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
150 "rl: full speed brigade pass failed.");
154 while (ctx->state == RATE_LIMIT && !APR_BRIGADE_EMPTY(bb)) {
155 for (e = APR_BRIGADE_FIRST(bb);
156 e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
157 if (RL_BUCKET_IS_START(e)) {
158 apr_bucket *f;
159 f = APR_RING_LAST(&bb->list);
160 APR_RING_UNSPLICE(e, f, link);
161 APR_RING_SPLICE_TAIL(&ctx->holdingbb->list, e, f,
162 apr_bucket, link);
163 ctx->state = RATE_FULLSPEED;
164 break;
168 while (!APR_BRIGADE_EMPTY(bb)) {
169 apr_bucket *stop_point;
170 apr_off_t len = 0;
172 if (f->c->aborted) {
173 apr_brigade_cleanup(bb);
174 ctx->state = RATE_ERROR;
175 break;
178 if (do_sleep) {
179 apr_sleep(RATE_INTERVAL_MS * 1000);
181 else {
182 do_sleep = 1;
185 apr_brigade_length(bb, 1, &len);
187 rv = apr_brigade_partition(bb, ctx->chunk_size, &stop_point);
188 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
189 ctx->state = RATE_ERROR;
190 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
191 "rl: partition failed.");
192 break;
195 if (stop_point != APR_BRIGADE_SENTINEL(bb)) {
196 apr_bucket *f;
197 apr_bucket *e = APR_BUCKET_PREV(stop_point);
198 f = APR_RING_FIRST(&bb->list);
199 APR_RING_UNSPLICE(f, e, link);
200 APR_RING_SPLICE_HEAD(&ctx->tmpbb->list, f, e, apr_bucket,
201 link);
203 else {
204 APR_BRIGADE_CONCAT(ctx->tmpbb, bb);
207 fb = apr_bucket_flush_create(ba);
209 APR_BRIGADE_INSERT_TAIL(ctx->tmpbb, fb);
211 #if 0
212 brigade_dump(f->r, ctx->tmpbb);
213 brigade_dump(f->r, bb);
214 #endif
216 rv = ap_pass_brigade(f->next, ctx->tmpbb);
217 apr_brigade_cleanup(ctx->tmpbb);
219 if (rv != APR_SUCCESS) {
220 ctx->state = RATE_ERROR;
221 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
222 "rl: brigade pass failed.");
223 break;
229 return rv;
233 static apr_status_t
234 rl_bucket_read(apr_bucket *b, const char **str,
235 apr_size_t *len, apr_read_type_e block)
237 *str = NULL;
238 *len = 0;
239 return APR_SUCCESS;
242 AP_DECLARE(apr_bucket *)
243 rl_end_create(apr_bucket_alloc_t *list)
245 apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
247 APR_BUCKET_INIT(b);
248 b->free = apr_bucket_free;
249 b->list = list;
250 b->length = 0;
251 b->start = 0;
252 b->data = NULL;
253 b->type = &rl_bucket_type_end;
255 return b;
258 AP_DECLARE(apr_bucket *)
259 rl_start_create(apr_bucket_alloc_t *list)
261 apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
263 APR_BUCKET_INIT(b);
264 b->free = apr_bucket_free;
265 b->list = list;
266 b->length = 0;
267 b->start = 0;
268 b->data = NULL;
269 b->type = &rl_bucket_type_start;
271 return b;
276 AP_DECLARE_DATA const apr_bucket_type_t rl_bucket_type_end = {
277 "RL_END", 5, APR_BUCKET_METADATA,
278 apr_bucket_destroy_noop,
279 rl_bucket_read,
280 apr_bucket_setaside_noop,
281 apr_bucket_split_notimpl,
282 apr_bucket_simple_copy
286 AP_DECLARE_DATA const apr_bucket_type_t rl_bucket_type_start = {
287 "RL_START", 5, APR_BUCKET_METADATA,
288 apr_bucket_destroy_noop,
289 rl_bucket_read,
290 apr_bucket_setaside_noop,
291 apr_bucket_split_notimpl,
292 apr_bucket_simple_copy
298 static void register_hooks(apr_pool_t *p)
300 /* run after mod_deflate etc etc, but not at connection level, ie, mod_ssl. */
301 ap_register_output_filter(RATE_LIMIT_FILTER_NAME, rate_limit_filter,
302 NULL, AP_FTYPE_PROTOCOL + 3);
305 module AP_MODULE_DECLARE_DATA ratelimit_module = {
306 STANDARD20_MODULE_STUFF,
307 NULL, /* create per-directory config structure */
308 NULL, /* merge per-directory config structures */
309 NULL, /* create per-server config structure */
310 NULL, /* merge per-server config structures */
311 NULL, /* command apr_table_t */
312 register_hooks