]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) test: backporting improved nghttp output parsing.
authorStefan Eissing <icing@apache.org>
Thu, 14 Jul 2022 09:37:43 +0000 (09:37 +0000)
committerStefan Eissing <icing@apache.org>
Thu, 14 Jul 2022 09:37:43 +0000 (09:37 +0000)
     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

test/modules/http2/mod_h2test/mod_h2test.c
test/modules/http2/test_202_trailer.py
test/pyhttpd/nghttp.py

index 63dc28aac4301b0aae36e46c1b1c3d9203ea2b86..0d767ef75a0b5f93201183fc55aa59419b3bb2df 100644 (file)
@@ -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);
 }
 
index 57bcff77f506c8653afeb4b10dc362cbb01bec6c..8571955dd7ae89ac1f676ec027e31815701999b7 100644 (file)
@@ -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}": [
+                "<Location \"/h2test/trailer\">",
+                "  SetHandler h2test-trailer",
+                "</Location>"
+            ],
+        })
+        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'
index 3927bc34ff3c56397a418a593284619d5128f00a..84b8f20c6f4a4467885a6e56c53e479083e0fcf5 100644 (file)
@@ -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 <length=(\d+), .*stream_id=(\d+)>', 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: