mirror of
https://github.com/curl/curl.git
synced 2026-06-21 18:25:37 +03:00
pytest: check 429 handling
Add a "limit" handler to httpd that responds 429 after 5 requests and then 429s all requests for 2 seconds. After which another 4 requests are served before 429 strikes again. Closes #21357
This commit is contained in:
parent
6daf4bc7e2
commit
e0b2ec2400
3 changed files with 142 additions and 3 deletions
|
|
@ -217,3 +217,15 @@ class TestErrors:
|
|||
# - CURLE_RECV_ERROR (56) - some TLS backends fail with that
|
||||
assert r.exit_code in [35, 56], \
|
||||
f'unexpected error {r.exit_code}\n{r.dump_logs()}'
|
||||
|
||||
# Get a resource many times with limited requests
|
||||
@pytest.mark.skipif(condition=not Env.have_h2_curl(), reason='curl without HTTP/2')
|
||||
def test_05_10_limits(self, env: Env, httpd):
|
||||
proto = 'h2'
|
||||
count = 6
|
||||
curl = CurlClient(env=env)
|
||||
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/limit?id=[0-{count-1}]'
|
||||
r = curl.http_download(urls=[url], alpn_proto=proto, extra_args=[
|
||||
'--parallel', '--retry', '5'
|
||||
])
|
||||
r.check_stats(count=count, http_status=200, exitcode=0)
|
||||
|
|
|
|||
|
|
@ -535,6 +535,9 @@ class Httpd:
|
|||
' <Location /curltest/echo>',
|
||||
' SetHandler curltest-echo',
|
||||
' </Location>',
|
||||
' <Location /curltest/limit>',
|
||||
' SetHandler curltest-limit',
|
||||
' </Location>',
|
||||
' <Location /curltest/put>',
|
||||
' SetHandler curltest-put',
|
||||
' </Location>',
|
||||
|
|
|
|||
|
|
@ -824,11 +824,136 @@ cleanup:
|
|||
return DECLINED;
|
||||
}
|
||||
|
||||
struct curltest_limit_rec {
|
||||
int rcount;
|
||||
int rlimit;
|
||||
apr_time_t end;
|
||||
apr_time_t duration_sec;
|
||||
struct apr_thread_mutex_t *lock;
|
||||
};
|
||||
|
||||
static struct curltest_limit_rec limitrec = {
|
||||
0, 5, 0, 2
|
||||
};
|
||||
|
||||
static int curltest_limit_handler(request_rec *r)
|
||||
{
|
||||
conn_rec *c = r->connection;
|
||||
apr_bucket_brigade *bb;
|
||||
apr_bucket *b;
|
||||
apr_status_t rv;
|
||||
const char *request_id = NULL;
|
||||
int i, denied;
|
||||
apr_time_t now;
|
||||
|
||||
if(strcmp(r->handler, "curltest-limit")) {
|
||||
return DECLINED;
|
||||
}
|
||||
if(r->method_number != M_GET) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
if(r->args) {
|
||||
apr_array_header_t *args = apr_cstr_split(r->args, "&", 1, r->pool);
|
||||
for(i = 0; i < args->nelts; ++i) {
|
||||
char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
|
||||
s = strchr(arg, '=');
|
||||
if(s) {
|
||||
*s = '\0';
|
||||
val = s + 1;
|
||||
if(!strcmp("id", arg)) {
|
||||
/* just an id for repeated requests with curl's URL globbing */
|
||||
request_id = val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
|
||||
"understood: '%s' in %s",
|
||||
arg, r->args);
|
||||
ap_die(HTTP_BAD_REQUEST, r);
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "limit: processing");
|
||||
|
||||
now = apr_time_now();
|
||||
apr_thread_mutex_lock(limitrec.lock);
|
||||
if(limitrec.end && (now > limitrec.end)) {
|
||||
/* reset limit */
|
||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "limit: reset");
|
||||
limitrec.rcount = 0;
|
||||
limitrec.end = 0;
|
||||
}
|
||||
limitrec.rcount += 1;
|
||||
denied = (limitrec.rcount > limitrec.rlimit);
|
||||
if(denied) {
|
||||
/* extend limit duration */
|
||||
limitrec.end = now + apr_time_from_sec(limitrec.duration_sec);
|
||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "limit: denied, %d request %s",
|
||||
limitrec.rcount, request_id);
|
||||
}
|
||||
else {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "limit: ok, %d request %s",
|
||||
limitrec.rcount, request_id);
|
||||
}
|
||||
apr_thread_mutex_unlock(limitrec.lock);
|
||||
|
||||
r->status = denied ? 429 : 200;
|
||||
r->clength = -1;
|
||||
r->chunked = 1;
|
||||
apr_table_unset(r->headers_out, "Content-Length");
|
||||
/* Discourage content-encodings */
|
||||
apr_table_unset(r->headers_out, "Content-Encoding");
|
||||
if(request_id)
|
||||
apr_table_setn(r->headers_out, "request-id", request_id);
|
||||
apr_table_setn(r->subprocess_env, "no-brotli", "1");
|
||||
apr_table_setn(r->subprocess_env, "no-gzip", "1");
|
||||
|
||||
if(denied) {
|
||||
char *v = apr_psprintf(r->pool, "%d", limitrec.duration_sec);
|
||||
apr_table_set(r->headers_out, "Retry-After", v);
|
||||
}
|
||||
|
||||
ap_set_content_type(r, "text/plain");
|
||||
|
||||
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
||||
|
||||
apr_brigade_puts(bb, NULL, NULL, "The resource served with limits.\n");
|
||||
|
||||
/* flush response */
|
||||
b = apr_bucket_flush_create(c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
rv = ap_pass_brigade(r->output_filters, bb);
|
||||
if(APR_SUCCESS != rv)
|
||||
goto cleanup;
|
||||
|
||||
/* we are done */
|
||||
b = apr_bucket_eos_create(c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
rv = ap_pass_brigade(r->output_filters, bb);
|
||||
|
||||
cleanup:
|
||||
if(rv == APR_SUCCESS ||
|
||||
r->status != HTTP_OK ||
|
||||
c->aborted) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "limit: done");
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
/* no way to know what type of error occurred */
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "limit failed");
|
||||
return AP_FILTER_ERROR;
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog,
|
||||
apr_pool_t *ptemp, server_rec *s)
|
||||
{
|
||||
void *data = NULL;
|
||||
const char *key = "mod_curltest_init_counter";
|
||||
apr_status_t rv;
|
||||
|
||||
(void)p;
|
||||
(void)plog;
|
||||
|
|
@ -842,9 +967,7 @@ static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog,
|
|||
return APR_SUCCESS;
|
||||
}
|
||||
|
||||
/* mess with the overall server here */
|
||||
|
||||
return APR_SUCCESS;
|
||||
return apr_thread_mutex_create(&limitrec.lock, APR_THREAD_MUTEX_DEFAULT, p);
|
||||
}
|
||||
|
||||
static void curltest_hooks(apr_pool_t *pool)
|
||||
|
|
@ -861,6 +984,7 @@ static void curltest_hooks(apr_pool_t *pool)
|
|||
ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_handler(curltest_limit_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
}
|
||||
|
||||
AP_DECLARE_MODULE(curltest) =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue