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.
18 #include "http_config.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
34 typedef struct rl_ctx_t
39 apr_bucket_brigade
*tmpbb
;
40 apr_bucket_brigade
*holdingbb
;
44 static void brigade_dump(request_rec
*r
, apr_bucket_brigade
*bb
)
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
);
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
;
65 apr_bucket_alloc_t
*ba
= f
->r
->connection
->bucket_alloc
;
66 apr_bucket_brigade
*bb
= input_bb
;
69 ap_log_rerror(APLOG_MARK
, APLOG_ERR
, 0, f
->r
, "rl: conn aborted");
70 apr_brigade_cleanup(bb
);
71 return APR_ECONNABORTED
;
76 const char *rl
= NULL
;
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");
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
));
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
))) {
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
)) {
128 f
= APR_RING_LAST(&bb
->list
);
129 APR_RING_UNSPLICE(e
, f
, link
);
130 APR_RING_SPLICE_TAIL(&ctx
->holdingbb
->list
, e
, f
,
132 ctx
->state
= RATE_LIMIT
;
138 apr_brigade_cleanup(bb
);
139 ctx
->state
= RATE_ERROR
;
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
)) {
159 f
= APR_RING_LAST(&bb
->list
);
160 APR_RING_UNSPLICE(e
, f
, link
);
161 APR_RING_SPLICE_TAIL(&ctx
->holdingbb
->list
, e
, f
,
163 ctx
->state
= RATE_FULLSPEED
;
168 while (!APR_BRIGADE_EMPTY(bb
)) {
169 apr_bucket
*stop_point
;
173 apr_brigade_cleanup(bb
);
174 ctx
->state
= RATE_ERROR
;
179 apr_sleep(RATE_INTERVAL_MS
* 1000);
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.");
195 if (stop_point
!= APR_BRIGADE_SENTINEL(bb
)) {
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
,
204 APR_BRIGADE_CONCAT(ctx
->tmpbb
, bb
);
207 fb
= apr_bucket_flush_create(ba
);
209 APR_BRIGADE_INSERT_TAIL(ctx
->tmpbb
, fb
);
212 brigade_dump(f
->r
, ctx
->tmpbb
);
213 brigade_dump(f
->r
, bb
);
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.");
234 rl_bucket_read(apr_bucket
*b
, const char **str
,
235 apr_size_t
*len
, apr_read_type_e block
)
242 AP_DECLARE(apr_bucket
*)
243 rl_end_create(apr_bucket_alloc_t
*list
)
245 apr_bucket
*b
= apr_bucket_alloc(sizeof(*b
), list
);
248 b
->free
= apr_bucket_free
;
253 b
->type
= &rl_bucket_type_end
;
258 AP_DECLARE(apr_bucket
*)
259 rl_start_create(apr_bucket_alloc_t
*list
)
261 apr_bucket
*b
= apr_bucket_alloc(sizeof(*b
), list
);
264 b
->free
= apr_bucket_free
;
269 b
->type
= &rl_bucket_type_start
;
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
,
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
,
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 */