You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2022/07/02 09:11:32 UTC

svn commit: r1902408 - in /httpd/httpd/trunk/test/modules/http2: mod_h2test/mod_h2test.c test_105_timeout.py test_202_trailer.py

Author: icing
Date: Sat Jul  2 09:11:31 2022
New Revision: 1902408

URL: http://svn.apache.org/viewvc?rev=1902408&view=rev
Log:
  *) test/modules/http2: adding tests for response trailers with
     or without a body. This reproduces a bug reported in
     <https://github.com/icing/mod_h2/issues/233>
     where trailers are not sent on an empty response
     body. This is used in gRPC.


Modified:
    httpd/httpd/trunk/test/modules/http2/mod_h2test/mod_h2test.c
    httpd/httpd/trunk/test/modules/http2/test_105_timeout.py
    httpd/httpd/trunk/test/modules/http2/test_202_trailer.py

Modified: httpd/httpd/trunk/test/modules/http2/mod_h2test/mod_h2test.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/modules/http2/mod_h2test/mod_h2test.c?rev=1902408&r1=1902407&r2=1902408&view=diff
==============================================================================
--- httpd/httpd/trunk/test/modules/http2/mod_h2test/mod_h2test.c (original)
+++ httpd/httpd/trunk/test/modules/http2/mod_h2test/mod_h2test.c Sat Jul  2 09:11:31 2022
@@ -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_
     }
 
     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,71 @@ 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;
+
+    if (strcmp(r->handler, "h2test-trailer")) {
+        return DECLINED;
+    }
+    if (r->method_number != M_GET && r->method_number != M_POST) {
+        return DECLINED;
+    }
+
+    if (r->args) {
+        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");
+    apr_table_mergen(r->headers_out, "Trailer", "trailer-content-length");
+    apr_table_set(r->trailers_out, "trailer-content-length",
+                  apr_psprintf(r->pool, "%d", body_len));
+
+    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);
+    }
+    /* 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 +374,6 @@ static void h2test_hooks(apr_pool_t *poo
     /* 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);
 }
 

Modified: httpd/httpd/trunk/test/modules/http2/test_105_timeout.py
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/modules/http2/test_105_timeout.py?rev=1902408&r1=1902407&r2=1902408&view=diff
==============================================================================
--- httpd/httpd/trunk/test/modules/http2/test_105_timeout.py (original)
+++ httpd/httpd/trunk/test/modules/http2/test_105_timeout.py Sat Jul  2 09:11:31 2022
@@ -3,11 +3,10 @@ import time
 
 import pytest
 
-from .env import H2Conf, H2TestEnv
+from .env import H2Conf
 from pyhttpd.curl import CurlPiper
 
 
-@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
 class TestTimeout:
 
     # Check that base servers 'Timeout' setting is observed on SSL handshake
@@ -129,21 +128,22 @@ class TestTimeout:
     def test_h2_105_12(self, env):
         # long connection timeout, short stream timeout
         # sending a slow POST
-        conf = H2Conf(env)
-        conf.add_vhost_cgi()
-        conf.add("Timeout 10")
-        conf.add("H2StreamTimeout 1")
-        conf.install()
-        assert env.apache_restart() == 0
-        url = env.mkurl("https", "cgi", "/h2test/delay?5")
-        piper = CurlPiper(env=env, url=url)
-        piper.start()
-        for _ in range(3):
-            time.sleep(2)
-            try:
-                piper.send("0123456789\n")
-            except BrokenPipeError:
-                break
-        piper.close()
-        assert piper.response
-        assert piper.response['status'] == 408
+        if env.httpd_is_at_least("2.5.0"):
+            conf = H2Conf(env)
+            conf.add_vhost_cgi()
+            conf.add("Timeout 10")
+            conf.add("H2StreamTimeout 1")
+            conf.install()
+            assert env.apache_restart() == 0
+            url = env.mkurl("https", "cgi", "/h2test/delay?5")
+            piper = CurlPiper(env=env, url=url)
+            piper.start()
+            for _ in range(3):
+                time.sleep(2)
+                try:
+                    piper.send("0123456789\n")
+                except BrokenPipeError:
+                    break
+            piper.close()
+            assert piper.response
+            assert piper.response['status'] == 408

Modified: httpd/httpd/trunk/test/modules/http2/test_202_trailer.py
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/test/modules/http2/test_202_trailer.py?rev=1902408&r1=1902407&r2=1902408&view=diff
==============================================================================
--- httpd/httpd/trunk/test/modules/http2/test_202_trailer.py (original)
+++ httpd/httpd/trunk/test/modules/http2/test_202_trailer.py Sat Jul  2 09:11:31 2022
@@ -1,7 +1,7 @@
 import os
 import pytest
 
-from .env import H2Conf, H2TestEnv
+from .env import H2Conf
 
 
 def setup_data(env):
@@ -13,13 +13,20 @@ def setup_data(env):
 
 # The trailer tests depend on "nghttp" as no other client seems to be able to send those
 # rare things.
-@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
 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
@@ -64,3 +71,22 @@ class TestTrailers:
         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'