From: Stefan Eissing Date: Thu, 14 Jul 2022 09:37:43 +0000 (+0000) Subject: *) test: backporting improved nghttp output parsing. X-Git-Tag: 2.4.55-rc1-candidate~102 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=43adaacf52447e57f453c3945b56f751c2e40bf7;p=thirdparty%2Fapache%2Fhttpd.git *) test: backporting improved nghttp output parsing. test/modules/http2 adding trailer tests. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1902714 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/test/modules/http2/mod_h2test/mod_h2test.c b/test/modules/http2/mod_h2test/mod_h2test.c index 63dc28aac43..0d767ef75a0 100644 --- a/test/modules/http2/mod_h2test/mod_h2test.c +++ b/test/modules/http2/mod_h2test/mod_h2test.c @@ -43,6 +43,65 @@ AP_DECLARE_MODULE(h2test) = { #endif }; +#define SECS_PER_HOUR (60*60) +#define SECS_PER_DAY (24*SECS_PER_HOUR) + +static apr_status_t duration_parse(apr_interval_time_t *ptimeout, const char *value, + const char *def_unit) +{ + char *endp; + apr_int64_t n; + + n = apr_strtoi64(value, &endp, 10); + if (errno) { + return errno; + } + if (!endp || !*endp) { + if (!def_unit) def_unit = "s"; + } + else if (endp == value) { + return APR_EINVAL; + } + else { + def_unit = endp; + } + + switch (*def_unit) { + case 'D': + case 'd': + *ptimeout = apr_time_from_sec(n * SECS_PER_DAY); + break; + case 's': + case 'S': + *ptimeout = (apr_interval_time_t) apr_time_from_sec(n); + break; + case 'h': + case 'H': + /* Time is in hours */ + *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * SECS_PER_HOUR); + break; + case 'm': + case 'M': + switch (*(++def_unit)) { + /* Time is in milliseconds */ + case 's': + case 'S': + *ptimeout = (apr_interval_time_t) n * 1000; + break; + /* Time is in minutes */ + case 'i': + case 'I': + *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60); + break; + default: + return APR_EGENERAL; + } + break; + default: + return APR_EGENERAL; + } + return APR_SUCCESS; +} static int h2test_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) @@ -163,9 +222,10 @@ static int h2test_delay_handler(request_rec *r) } if (r->args) { - rv = apr_cstr_atoi(&i, r->args); - if (APR_SUCCESS == rv) { - delay = apr_time_from_sec(i); + rv = duration_parse(&delay, r->args, "s"); + if (APR_SUCCESS != rv) { + ap_die(HTTP_BAD_REQUEST, r); + return OK; } } @@ -230,6 +290,77 @@ cleanup: return AP_FILTER_ERROR; } +static int h2test_trailer_handler(request_rec *r) +{ + conn_rec *c = r->connection; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t rv; + char buffer[8192]; + int i, chunks = 3; + long l; + int body_len = 0, tclen; + + if (strcmp(r->handler, "h2test-trailer")) { + return DECLINED; + } + if (r->method_number != M_GET && r->method_number != M_POST) { + return DECLINED; + } + + if (r->args) { + tclen = body_len = (int)apr_atoi64(r->args); + if (body_len < 0) body_len = 0; + } + + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "trailer_handler: processing request, %d body length", + body_len); + r->status = 200; + r->clength = body_len; + ap_set_content_length(r, body_len); + + ap_set_content_type(r, "application/octet-stream"); + bb = apr_brigade_create(r->pool, c->bucket_alloc); + memset(buffer, 0, sizeof(buffer)); + while (body_len > 0) { + l = (sizeof(buffer) > body_len)? body_len : sizeof(buffer); + body_len -= l; + rv = apr_brigade_write(bb, NULL, NULL, buffer, l); + if (APR_SUCCESS != rv) goto cleanup; + rv = ap_pass_brigade(r->output_filters, bb); + if (APR_SUCCESS != rv) goto cleanup; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "trailer_handler: passed %ld bytes as response body", l); + } + /* flush it */ + b = apr_bucket_flush_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + rv = ap_pass_brigade(r->output_filters, bb); + apr_brigade_cleanup(bb); + apr_sleep(apr_time_from_msec(500)); + /* set trailers */ + apr_table_mergen(r->headers_out, "Trailer", "trailer-content-length"); + apr_table_set(r->trailers_out, "trailer-content-length", + apr_psprintf(r->pool, "%d", tclen)); + /* 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); + apr_brigade_cleanup(bb); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "trailer_handler: response passed"); + +cleanup: + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, + "trailer_handler: request cleanup, r->status=%d, aborted=%d", + r->status, c->aborted); + if (rv == APR_SUCCESS + || r->status != HTTP_OK + || c->aborted) { + return OK; + } + return AP_FILTER_ERROR; +} + /* Install this module into the apache2 infrastructure. */ static void h2test_hooks(apr_pool_t *pool) @@ -249,5 +380,6 @@ static void h2test_hooks(apr_pool_t *pool) /* test h2 handlers */ ap_hook_handler(h2test_echo_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(h2test_delay_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(h2test_trailer_handler, NULL, NULL, APR_HOOK_MIDDLE); } diff --git a/test/modules/http2/test_202_trailer.py b/test/modules/http2/test_202_trailer.py index 57bcff77f50..8571955dd7a 100644 --- a/test/modules/http2/test_202_trailer.py +++ b/test/modules/http2/test_202_trailer.py @@ -18,7 +18,15 @@ class TestTrailers: @pytest.fixture(autouse=True, scope='class') def _class_scope(self, env): setup_data(env) - H2Conf(env).add_vhost_cgi(h2proxy_self=True).install() + conf = H2Conf(env, extras={ + f"cgi.{env.http_tld}": [ + "", + " SetHandler h2test-trailer", + "" + ], + }) + conf.add_vhost_cgi(h2proxy_self=True) + conf.install() assert env.apache_restart() == 0 # check if the server survives a trailer or two @@ -26,40 +34,59 @@ class TestTrailers: url = env.mkurl("https", "cgi", "/echo.py") fpath = os.path.join(env.gen_dir, "data-1k") r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 1"]) - assert 300 > r.response["status"] - assert 1000 == len(r.response["body"]) + assert r.response["status"] < 300 + assert len(r.response["body"]) == 1000 r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 1b", "--trailer", "XXX: test"]) - assert 300 > r.response["status"] - assert 1000 == len(r.response["body"]) + assert r.response["status"] < 300 + assert len(r.response["body"]) == 1000 # check if the server survives a trailer without content-length def test_h2_202_02(self, env): url = env.mkurl("https", "cgi", "/echo.py") fpath = os.path.join(env.gen_dir, "data-1k") r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 2", "--no-content-length"]) - assert 300 > r.response["status"] - assert 1000 == len(r.response["body"]) + assert r.response["status"] < 300 + assert len(r.response["body"]) == 1000 # check if echoing request headers in response from GET works def test_h2_202_03(self, env): url = env.mkurl("https", "cgi", "/echohd.py?name=X") r = env.nghttp().get(url, options=["--header", "X: 3"]) - assert 300 > r.response["status"] - assert b"X: 3\n" == r.response["body"] + assert r.response["status"] < 300 + assert r.response["body"] == b"X: 3\n" # check if echoing request headers in response from POST works def test_h2_202_03b(self, env): url = env.mkurl("https", "cgi", "/echohd.py?name=X") r = env.nghttp().post_name(url, "Y", options=["--header", "X: 3b"]) - assert 300 > r.response["status"] - assert b"X: 3b\n" == r.response["body"] + assert r.response["status"] < 300 + assert r.response["body"] == b"X: 3b\n" # check if echoing request headers in response from POST works, but trailers are not seen # This is the way CGI invocation works. def test_h2_202_04(self, env): url = env.mkurl("https", "cgi", "/echohd.py?name=X") r = env.nghttp().post_name(url, "Y", options=["--header", "X: 4a", "--trailer", "X: 4b"]) - assert 300 > r.response["status"] - assert b"X: 4a\n" == r.response["body"] + assert r.response["status"] < 300 + assert r.response["body"] == b"X: 4a\n" + # check that our h2test-trailer handler works + def test_h2_202_10(self, env): + url = env.mkurl("https", "cgi", "/h2test/trailer?1024") + r = env.nghttp().get(url) + assert r.response["status"] == 200 + assert len(r.response["body"]) == 1024 + assert 'trailer' in r.response + assert 'trailer-content-length' in r.response['trailer'] + assert r.response['trailer']['trailer-content-length'] == '1024' + + # check that trailers also for with empty bodies + def test_h2_202_11(self, env): + url = env.mkurl("https", "cgi", "/h2test/trailer?0") + r = env.nghttp().get(url) + assert r.response["status"] == 200 + assert len(r.response["body"]) == 0 + assert 'trailer' in r.response + assert 'trailer-content-length' in r.response['trailer'] + assert r.response['trailer']['trailer-content-length'] == '0' diff --git a/test/pyhttpd/nghttp.py b/test/pyhttpd/nghttp.py index 3927bc34ff3..84b8f20c6f4 100644 --- a/test/pyhttpd/nghttp.py +++ b/test/pyhttpd/nghttp.py @@ -80,6 +80,7 @@ class Nghttp: # chunk output into lines. nghttp mixes text # meta output with bytes from the response body. lines = [l.decode() for l in btext.split(b'\n')] + for lidx, l in enumerate(lines): if len(l) == 0: body += '\n' @@ -96,7 +97,6 @@ class Nghttp: header[hname] += ", %s" % hval else: header[hname] = hval - body = '' continue m = re.match(r'(.*)\[.*] recv HEADERS frame <.* stream_id=(\d+)>', l) @@ -120,7 +120,6 @@ class Nghttp: response["previous"] = prev response[hkey] = s["header"] s["header"] = {} - body = '' continue m = re.match(r'(.*)\[.*] recv DATA frame ', l) @@ -179,8 +178,8 @@ class Nghttp: if '[' != l[0]: skip_indents = None - body += l + '\n' - + body += l + '\n' + # the main request is done on the lowest odd numbered id main_stream = 99999999999 for sid in streams: