├── NEWS
├── README
├── test
├── unit
│ ├── .dirstamp
│ ├── main.c
│ ├── test_common.h
│ └── test_h2_util.c
├── pyhttpd
│ ├── __init__.py
│ ├── htdocs
│ │ ├── test2
│ │ │ ├── x%2f.test
│ │ │ ├── 10%abnormal.txt
│ │ │ ├── alive.json
│ │ │ └── 006
│ │ │ │ └── 006.css
│ │ ├── cgi
│ │ │ ├── files
│ │ │ │ └── empty.txt
│ │ │ ├── echo.py
│ │ │ ├── mnot164.py
│ │ │ ├── echohd.py
│ │ │ ├── hello.py
│ │ │ ├── env.py
│ │ │ ├── hecho.py
│ │ │ ├── necho.py
│ │ │ └── upload.py
│ │ ├── test1
│ │ │ ├── apache.org-files
│ │ │ │ ├── cse.js
│ │ │ │ ├── css.css
│ │ │ │ ├── jsapi.js
│ │ │ │ ├── ant.jpg
│ │ │ │ ├── asf_logo.png
│ │ │ │ ├── min.css.br
│ │ │ │ ├── mrunit.jpg
│ │ │ │ ├── synapse.jpg
│ │ │ │ ├── small-logo.png
│ │ │ │ ├── async-ads.js.br
│ │ │ │ ├── search_box_icon.png
│ │ │ │ └── styles.css
│ │ │ ├── 006
│ │ │ │ ├── header.html
│ │ │ │ ├── 006.css
│ │ │ │ └── 006.js
│ │ │ ├── alive.json
│ │ │ ├── 002.jpg
│ │ │ ├── 003
│ │ │ │ └── 003_img.jpg
│ │ │ ├── 004
│ │ │ │ └── gophertiles.jpg
│ │ │ ├── 001.html
│ │ │ ├── 003.html
│ │ │ ├── 009.py
│ │ │ ├── 006.html
│ │ │ ├── 007
│ │ │ │ └── 007.py
│ │ │ ├── 007.html
│ │ │ └── index.html
│ │ ├── alive.json
│ │ ├── noh2
│ │ │ ├── alive.json
│ │ │ └── index.html
│ │ ├── index.html
│ │ └── forbidden.html
│ ├── conf
│ │ ├── test.conf
│ │ ├── stop.conf.template
│ │ └── httpd.conf.template
│ ├── config.ini.in
│ ├── result.py
│ ├── curl.py
│ └── log.py
├── modules
│ └── http2
│ │ ├── __init__.py
│ │ ├── test_001_httpd_alive.py
│ │ ├── mod_h2test
│ │ └── mod_h2test.h
│ │ ├── test_600_h2proxy.py
│ │ ├── test_501_proxy_serverheader.py
│ │ ├── test_102_require.py
│ │ ├── test_300_interim.py
│ │ ├── test_005_files.py
│ │ ├── conftest.py
│ │ ├── test_401_early_hints.py
│ │ ├── test_700_load_get.py
│ │ ├── test_106_shutdown.py
│ │ ├── test_710_load_post_static.py
│ │ ├── test_711_load_post_cgi.py
│ │ ├── test_712_buffering.py
│ │ ├── test_100_conn_reuse.py
│ │ ├── test_202_trailer.py
│ │ ├── test_002_curl_basics.py
│ │ ├── test_006_assets.py
│ │ ├── test_201_header_conditional.py
│ │ ├── data
│ │ └── nghttp-output-1k-1.txt
│ │ ├── test_104_padding.py
│ │ ├── test_103_upgrade.py
│ │ ├── test_105_timeout.py
│ │ ├── test_500_proxy.py
│ │ ├── env.py
│ │ └── test_101_ssl_reneg.py
├── Makefile.am
└── scoreboard.py
├── m4
├── h2.m4
└── ax_check_compile_flag.m4
├── AUTHORS
├── mod_http2
├── m4
│ └── h2.m4
├── .gitignore
├── mod_proxy_http2.h
├── h2_private.h
├── h2_switch.h
├── h2_bucket_eos.h
├── h2_version.h
├── h2_version.h.in
├── h2_c2.h
├── h2_c2_filter.h
├── h2_protocol.h
├── h2_request.h
├── Makefile.am
├── h2_workers.h
├── h2_c1.h
├── h2_c1_io.h
├── h2_config.h
├── h2_bucket_eos.c
├── h2_headers.h
├── mod_http2.h
├── h2_conn_ctx.h
├── h2_conn_ctx.c
├── h2_proxy_session.h
└── h2_push.h
├── .env
├── COPYING
├── .gitignore
├── .travis.yml
├── docker
├── archlinux
│ ├── Dockerfile
│ └── bin
│ │ └── mod_h2_test.sh
├── debian-sid
│ ├── Dockerfile
│ └── bin
│ │ └── mod_h2_test.sh
└── ubuntu-focal
│ ├── Dockerfile
│ └── bin
│ └── mod_h2_test.sh
├── http2_test.sh
├── Makefile.am
├── docker-compose.yml
└── README.md
/NEWS:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | See README.md
--------------------------------------------------------------------------------
/test/unit/.dirstamp:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/pyhttpd/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/modules/http2/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/m4/h2.m4:
--------------------------------------------------------------------------------
1 | # just so it is not empty
2 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test2/x%2f.test:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/files/empty.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test2/10%abnormal.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Stefan Eissing
--------------------------------------------------------------------------------
/mod_http2/m4/h2.m4:
--------------------------------------------------------------------------------
1 | # just so it is not empty
2 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/cse.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/css.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/jsapi.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | DOCKER_REGISTRY=docker-registry.greenbytes.de
2 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 |
2 | Please see the file called LICENSE.
3 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/006/header.html:
--------------------------------------------------------------------------------
1 | My Header Title
2 |
--------------------------------------------------------------------------------
/test/pyhttpd/conf/test.conf:
--------------------------------------------------------------------------------
1 | # empty placeholder for test specific configurations
2 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/alive.json:
--------------------------------------------------------------------------------
1 | {
2 | "host" : "generic",
3 | "alive" : true
4 | }
5 |
--------------------------------------------------------------------------------
/mod_http2/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | *.slo
3 | *.lo
4 | *.la
5 | .libs
6 | Makefile.in
7 | Makefile
8 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test2/alive.json:
--------------------------------------------------------------------------------
1 | {
2 | "host" : "test2",
3 | "alive" : true
4 | }
5 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/noh2/alive.json:
--------------------------------------------------------------------------------
1 | {
2 | "host" : "noh2",
3 | "alive" : true
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/alive.json:
--------------------------------------------------------------------------------
1 | {
2 | "host" : "test1",
3 | "alive" : true
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/002.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/002.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/003/003_img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/003/003_img.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/004/gophertiles.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/004/gophertiles.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/ant.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/ant.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/asf_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/asf_logo.png
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/min.css.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/min.css.br
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/mrunit.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/mrunit.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/synapse.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/synapse.jpg
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/small-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/small-logo.png
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/async-ads.js.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/async-ads.js.br
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/search_box_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h0nuss/vuejs/HEAD/test/pyhttpd/htdocs/test1/apache.org-files/search_box_icon.png
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | mod_h2 test site generic
4 |
5 |
6 | mod_h2 test site generic
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/noh2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | mod_h2 test site noh2
4 |
5 |
6 | mod_h2 test site noh2
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/forbidden.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 403 - Forbidden
4 |
5 |
6 | 403 - Forbidden
7 |
8 | An example of an error document.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/001.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML/2.0 Test File: 001
5 |
6 |
7 |
HTML/2.0 Test File: 001
8 | This file only contains a simple HTML structure with plain text.
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/006/006.css:
--------------------------------------------------------------------------------
1 | @CHARSET "ISO-8859-1";
2 | body{
3 | background:HoneyDew;
4 | }
5 | p{
6 | color:#0000FF;
7 | text-align:left;
8 | }
9 |
10 | h1{
11 | color:#FF0000;
12 | text-align:center;
13 | }
14 |
15 | .listTitle{
16 | font-size:large;
17 | }
18 |
19 | .listElements{
20 | color:#3366FF
21 | }
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test2/006/006.css:
--------------------------------------------------------------------------------
1 | @CHARSET "ISO-8859-1";
2 | body{
3 | background:HoneyDew;
4 | }
5 | p{
6 | color:#0000FF;
7 | text-align:left;
8 | }
9 |
10 | h1{
11 | color:#FF0000;
12 | text-align:center;
13 | }
14 |
15 | .listTitle{
16 | font-size:large;
17 | }
18 |
19 | .listElements{
20 | color:#3366FF
21 | }
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/echo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys, cgi, os
3 |
4 | status = '200 Ok'
5 |
6 | content = ''
7 | for line in sys.stdin:
8 | content += line
9 |
10 | # Just echo what we get
11 | print("Status: 200")
12 | print(f"Request-Length: {len(content)}")
13 | print("Content-Type: application/data\n")
14 | sys.stdout.write(content)
15 |
16 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/003.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML/2.0 Test File: 003
5 |
6 |
7 |
HTML/2.0 Test File: 003
8 | This is a text HTML file with a big image:
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/mnot164.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import cgi
4 | import cgitb; cgitb.enable()
5 | import os
6 | import sys
7 |
8 | try:
9 | form = cgi.FieldStorage()
10 | count = form['count'].value
11 | text = form['text'].value
12 | except KeyError:
13 | text="a"
14 | count=77784
15 |
16 |
17 | print("Status: 200 OK")
18 | print("Content-Type: text/html")
19 | print()
20 | sys.stdout.write(text*int(count))
21 |
22 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/echohd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import cgi, os
3 | import cgitb; cgitb.enable()
4 |
5 | status = '200 Ok'
6 |
7 | form = cgi.FieldStorage()
8 | name = form.getvalue('name')
9 |
10 | if name:
11 | print("Status: 200")
12 | print("""\
13 | Content-Type: text/plain\n""")
14 | print("""%s: %s""" % (name, os.environ['HTTP_'+name]))
15 | else:
16 | print("Status: 400 Parameter Missing")
17 | print("""\
18 | Content-Type: text/html\n
19 |
20 | No name was specified
21 | """)
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/hello.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | print("Content-Type: application/json")
6 | print()
7 | print("{")
8 | print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', '')))
9 | print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', '')))
10 | print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', '')))
11 | print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', '')))
12 | print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', '')))
13 | print(" \"h2push\" : \"%s\"" % (os.getenv('H2PUSH', '')))
14 | print("}")
15 |
16 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/009.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import cgi, sys, time
4 | import cgitb; cgitb.enable()
5 |
6 | print "Content-Type: text/html;charset=UTF-8"
7 | print
8 |
9 | print """\
10 |
11 | HTML/2.0 Test File: 009 (server time)
12 | HTML/2.0 Test File: 009
13 | 60 seconds of server time, one by one.
"""
14 |
15 | for i in range(60):
16 | s = time.strftime("%Y-%m-%d %H:%M:%S")
17 | print "", s, "
"
18 | sys.stdout.flush()
19 | time.sleep(1)
20 |
21 | print "done.
"
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/006.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML/2.0 Test File: 006
5 |
6 |
7 |
8 |
9 | HTML/2.0 Test File: 006
10 | This page contains:
11 |
12 | HTML
13 | CSS
14 | JavaScript
15 |
16 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | *.slo
3 | *.lo
4 | *.la
5 | *.pcap
6 | .libs
7 | .configured
8 | .deps
9 | compile
10 | aclocal.m4
11 | autom4te.cache
12 | autoscan.log
13 | config.guess
14 | config.log
15 | config.status
16 | config.sub
17 | config.h
18 | config.h.in
19 | config.h.in~
20 | configure
21 | configure.scan
22 | depcomp
23 | install-sh
24 | libtool
25 | ltmain.sh
26 | missing
27 | stamp-h1
28 | Makefile.in
29 | Makefile
30 | mod_h2-*.tar.gz
31 | mod_http2-*.tar.gz
32 | m4
33 | test-driver
34 | test/.cache/
35 | test/__pycache__
36 | test/pyhttpd/__pycache__
37 | test/modules/http2/__pycache__
38 | test/*.pyc
39 | test/gen/
40 | test/pyhttpd/config.ini
41 | test/.pytest_cache
42 | .idea
43 |
--------------------------------------------------------------------------------
/test/pyhttpd/config.ini.in:
--------------------------------------------------------------------------------
1 | [global]
2 | curl_bin = curl
3 | nghttp = nghttp
4 | h2load = h2load
5 |
6 | prefix = @prefix@
7 | exec_prefix = @exec_prefix@
8 | bindir = @bindir@
9 | sbindir = @sbindir@
10 | libdir = @libdir@
11 | libexecdir = @libexecdir@
12 |
13 | apr_bindir = @APR_BINDIR@
14 | apxs = @bindir@/apxs
15 | apachectl = @sbindir@/apachectl
16 |
17 | [httpd]
18 | version = @HTTPD_VERSION@
19 | name = @progname@
20 | dso_modules = @DSO_MODULES@
21 | static_modules = @STATIC_MODULES@
22 |
23 | [test]
24 | gen_dir = @abs_srcdir@/../gen
25 | http_port = 5002
26 | https_port = 5001
27 | proxy_port = 5003
28 | http_tld = tests.httpd.apache.org
29 | test_dir = @abs_srcdir@
30 | test_src_dir = @abs_srcdir@
31 |
--------------------------------------------------------------------------------
/test/modules/http2/test_001_httpd_alive.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestBasicAlive:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_test1().install()
11 | assert env.apache_restart() == 0
12 |
13 | # we expect to see the document from the generic server
14 | def test_h2_001_01(self, env):
15 | url = env.mkurl("https", "test1", "/alive.json")
16 | r = env.curl_get(url, 5)
17 | assert r.exit_code == 0, r.stderr + r.stdout
18 | assert r.response["json"]
19 | assert r.response["json"]["alive"] is True
20 | assert r.response["json"]["host"] == "test1"
21 |
22 |
--------------------------------------------------------------------------------
/test/Makefile.am:
--------------------------------------------------------------------------------
1 | # Licensed under the Apache License, Version 2.0 (the "License");
2 | # you may not use this file except in compliance with the License.
3 | # You may obtain a copy of the License at
4 | #
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | #
7 | # Unless required by applicable law or agreed to in writing, software
8 | # distributed under the License is distributed on an "AS IS" BASIS,
9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | # See the License for the specific language governing permissions and
11 | # limitations under the License.
12 | #
13 |
14 | .PHONY: test loadtest
15 |
16 | test:
17 | pytest
18 |
19 | loadtest:
20 | python3 load_test.py
21 |
22 | clean-local:
23 | rm -rf *.pyc __pycache__
24 | rm -rf $(GEN)
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: c
2 | sudo: required
3 |
4 | os:
5 | - linux
6 |
7 | addons:
8 | apt:
9 | update: true
10 | config:
11 | retries: true
12 | sources:
13 | - ubuntu-toolchain-r-test
14 | packages:
15 | - apache2
16 | - apache2-dev
17 | - libnghttp2-14
18 | - libnghttp2-dev
19 |
20 | # lets see if bionic's apache2 is recent enough
21 | matrix:
22 | include:
23 | - os: linux
24 | dist: bionic
25 | compiler: gcc
26 | env:
27 | - MY_ENV="BASE_OS=xenial-gcc"
28 |
29 |
30 | before_install:
31 | - eval "${MY_ENV}"
32 |
33 | before_script:
34 | - ./buildconf
35 |
36 | branches:
37 | only:
38 | - master
39 |
40 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/007/007.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import cgi, sys
4 | import cgitb; cgitb.enable()
5 |
6 | print "Content-Type: text/html;charset=UTF-8"
7 | print
8 |
9 | print """\
10 |
11 | HTML/2.0 Test File: 007 (received data)
12 | HTML/2.0 Test File: 007 """
13 |
14 | # alternative output: parsed form params <-> plain POST body
15 | parseContent = True # <-> False
16 |
17 | if parseContent:
18 | print 'Data processed: '
19 | form = cgi.FieldStorage()
20 | for name in form:
21 | print '', name, ': ', form[name].value, ' '
22 | print ' '
23 | else:
24 | print 'POST data output: '
25 | data = sys.stdin.read()
26 | print data
27 | print ' '
28 |
29 | print ''
--------------------------------------------------------------------------------
/docker/archlinux/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM archlinux
2 |
3 | RUN pacman -Sy --noconfirm --needed \
4 | git base-devel gcc python python-pip make openssl iputils
5 | RUN pacman -Sy --noconfirm --needed \
6 | curl libcurl-compat apr apr-util nghttp2 pcre jansson rsync
7 |
8 | RUN pacman -Sy --noconfirm --needed apache
9 |
10 | RUN pip install pytest tqdm pycurl cryptography requests
11 |
12 | COPY docker/archlinux/bin/* /apache-httpd/bin/
13 | COPY configure.ac Makefile.am NEWS README* AUTHORS ChangeLog COPYING LICENSE /apache-httpd/mod_h2/
14 | COPY m4 /apache-httpd/mod_h2/m4
15 | COPY mod_http2 /apache-httpd/mod_h2/mod_http2
16 | COPY test test/Makefile.am /apache-httpd/mod_h2/test/
17 | COPY test/pyhttpd /apache-httpd/mod_h2/test/pyhttpd
18 | COPY test/modules /apache-httpd/mod_h2/test/modules
19 | COPY test/unit /apache-httpd/mod_h2/test/unit
20 |
21 | CMD ["/bin/bash", "-c", "/apache-httpd/bin/mod_h2_test.sh"]
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/007.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML/2.0 Test File: 007
6 |
7 |
8 | HTML/2.0 Test File: 007
9 | This page is used to send data from the client to the server:
10 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/mod_http2/mod_proxy_http2.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __MOD_PROXY_HTTP2_H__
18 | #define __MOD_PROXY_HTTP2_H__
19 |
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/test/modules/http2/mod_h2test/mod_h2test.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __MOD_H2TEST_H__
18 | #define __MOD_H2TEST_H__
19 |
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/006/006.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JavaScript Functions File
3 | */
4 | function returnDate()
5 | {
6 | var currentDate;
7 | currentDate=new Date();
8 | var dateString=(currentDate.getMonth()+1)+'/'+currentDate.getDate()+'/'+currentDate.getFullYear();
9 | return dateString;
10 | }
11 |
12 | function returnHour()
13 | {
14 | var currentDate;
15 | currentDate=new Date();
16 | var hourString=currentDate.getHours()+':'+currentDate.getMinutes()+':'+currentDate.getSeconds();
17 | return hourString;
18 | }
19 |
20 | function javaScriptMessage(){
21 | return 'This section is generated under JavaScript: ';
22 | }
23 |
24 | function mainJavascript(){
25 | document.write(javaScriptMessage())
26 | document.write('');
27 | document.write('Current date (dd/mm/yyyy): ' + returnDate());
28 | document.write(' ');
29 | document.write(' Current time (hh:mm:ss): '+returnHour());
30 | document.write(' ');
31 | }
--------------------------------------------------------------------------------
/test/modules/http2/test_600_h2proxy.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestH2Proxy:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | conf = H2Conf(env)
11 | conf.add_vhost_cgi(h2proxy_self=True)
12 | if env.verbosity > 1:
13 | conf.add("LogLevel proxy:trace2 proxy_http2:trace2")
14 | conf.install()
15 | assert env.apache_restart() == 0
16 |
17 | def test_h2_600_01(self, env):
18 | url = env.mkurl("https", "cgi", "/h2proxy/hello.py")
19 | r = env.curl_get(url, 5)
20 | assert r.response["status"] == 200
21 | assert r.response["json"]["protocol"] == "HTTP/2.0"
22 | assert r.response["json"]["https"] == "on"
23 | assert r.response["json"]["ssl_protocol"] != ""
24 | assert r.response["json"]["h2"] == "on"
25 | assert r.response["json"]["h2push"] == "off"
26 | assert r.response["json"]["host"] == f"cgi.{env.http_tld}"
27 |
--------------------------------------------------------------------------------
/docker/debian-sid/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:sid
2 |
3 | RUN apt-get update; apt-get upgrade -y
4 | RUN apt-get install -y apt-listchanges \
5 | make openssl libssl-dev libcurl4 libcurl4-openssl-dev \
6 | gcc subversion git cargo python3 iputils-ping \
7 | libapr1-dev libaprutil1-dev libnghttp2-dev pip \
8 | autoconf libtool libtool-bin libpcre3-dev libjansson-dev curl rsync nghttp2-client
9 |
10 | RUN pip install pytest tqdm pycurl cryptography requests
11 |
12 | RUN apt-get install -y apache2 apache2-dev libapache2-mod-md
13 |
14 | COPY docker/debian-sid/bin/* /apache-httpd/bin/
15 | COPY configure.ac Makefile.am NEWS README* AUTHORS ChangeLog COPYING LICENSE /apache-httpd/mod_h2/
16 | COPY m4 /apache-httpd/mod_h2/m4
17 | COPY mod_http2 /apache-httpd/mod_h2/mod_http2
18 | COPY test test/Makefile.am /apache-httpd/mod_h2/test/
19 | COPY test/pyhttpd /apache-httpd/mod_h2/test/pyhttpd
20 | COPY test/modules /apache-httpd/mod_h2/test/modules
21 | COPY test/unit /apache-httpd/mod_h2/test/unit
22 |
23 | CMD ["/bin/bash", "-c", "/apache-httpd/bin/mod_h2_test.sh"]
--------------------------------------------------------------------------------
/docker/ubuntu-focal/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:focal
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 | RUN apt update; apt upgrade -y
5 | RUN apt install -yy apt-listchanges \
6 | make openssl libssl-dev libcurl4 libcurl4-openssl-dev \
7 | gcc subversion git cargo python3 iputils-ping \
8 | libapr1-dev libaprutil1-dev libnghttp2-dev pip \
9 | autoconf libtool libtool-bin libpcre3-dev libjansson-dev curl rsync nghttp2-client
10 |
11 | RUN pip install pytest tqdm pycurl cryptography requests
12 |
13 | RUN apt install -y apache2 apache2-dev libapache2-mod-md
14 |
15 | COPY docker/debian-sid/bin/* /apache-httpd/bin/
16 | COPY configure.ac Makefile.am NEWS README* AUTHORS ChangeLog COPYING LICENSE /apache-httpd/mod_h2/
17 | COPY m4 /apache-httpd/mod_h2/m4
18 | COPY mod_http2 /apache-httpd/mod_h2/mod_http2
19 | COPY test test/Makefile.am /apache-httpd/mod_h2/test/
20 | COPY test/pyhttpd /apache-httpd/mod_h2/test/pyhttpd
21 | COPY test/modules /apache-httpd/mod_h2/test/modules
22 | COPY test/unit /apache-httpd/mod_h2/test/unit
23 |
24 | CMD ["/bin/bash", "-c", "/apache-httpd/bin/mod_h2_test.sh"]
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/env.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import cgi, os
3 | import cgitb; cgitb.enable()
4 |
5 | status = '200 Ok'
6 |
7 | try:
8 | form = cgi.FieldStorage()
9 | input = form['name']
10 |
11 | # Test if the file was uploaded
12 | if input.value is not None:
13 | val = os.environ[input.value] if input.value in os.environ else ""
14 | print("Status: 200")
15 | print("""\
16 | Content-Type: text/plain\n""")
17 | print("{0}={1}".format(input.value, val))
18 |
19 | else:
20 | print("Status: 400 Parameter Missing")
21 | print("""\
22 | Content-Type: text/html\n
23 |
24 | No name was specified: %s
25 | """ % (count.value))
26 |
27 | except KeyError:
28 | print("Status: 200 Ok")
29 | print("""\
30 | Content-Type: text/html\n
31 |
32 | Echo
35 | """)
36 | pass
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/mod_http2/h2_private.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef mod_h2_h2_private_h
18 | #define mod_h2_h2_private_h
19 |
20 | #include
21 |
22 | #include
23 |
24 | extern module AP_MODULE_DECLARE_DATA http2_module;
25 |
26 | APLOG_USE_MODULE(http2);
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/docker/archlinux/bin/mod_h2_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TOP=/apache-httpd
4 | DATADIR=$TOP/data
5 |
6 | fail() {
7 | echo "$@"
8 | exit 1
9 | }
10 |
11 | needs_update() {
12 | local ref_file="$1"
13 | local check_dir="$2"
14 | if test ! -f "$ref_file"; then
15 | return 0
16 | fi
17 | find "$check_dir" -type f -a -newer "$ref_file" -o -type d -name .git -prune -a -false |
18 | while read fname; do
19 | return 0
20 | done
21 | return 1
22 | }
23 |
24 | PREFIX=$(apxs -q exec_prefix)
25 | if test ! -d $PREFIX; then
26 | fail "apache install prefix not found: $PREFIX"
27 | fi
28 |
29 | # remove some stuff that accumulates
30 | LOG_DIR=$(apxs -q logfiledir)
31 | rm -f $LOG_DIR/*
32 |
33 | cd "$TOP/mod_h2" ||fail
34 | if needs_update .installed .; then
35 | rm -f .installed
36 | if test ! -f configure -o configure.ac -nt configure; then
37 | autoreconf -i ||fail
38 | fi
39 | if test ! -d Makefile -o ./configure -nt Makefile; then
40 | ./configure || fail
41 | touch ./configure
42 | fi
43 | make clean||fail
44 | make ||fail
45 | touch .installed
46 | fi
47 | make install ||fail
48 | make test
49 | #make loadtest
50 |
--------------------------------------------------------------------------------
/docker/debian-sid/bin/mod_h2_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TOP=/apache-httpd
4 | DATADIR=$TOP/data
5 |
6 | fail() {
7 | echo "$@"
8 | exit 1
9 | }
10 |
11 | needs_update() {
12 | local ref_file="$1"
13 | local check_dir="$2"
14 | if test ! -f "$ref_file"; then
15 | return 0
16 | fi
17 | find "$check_dir" -type f -a -newer "$ref_file" -o -type d -name .git -prune -a -false |
18 | while read fname; do
19 | return 0
20 | done
21 | return 1
22 | }
23 |
24 | PREFIX=$(apxs -q exec_prefix)
25 | if test ! -d $PREFIX; then
26 | fail "apache install prefix not found: $PREFIX"
27 | fi
28 |
29 | # remove some stuff that accumulates
30 | LOG_DIR=$(apxs -q logfiledir)
31 | rm -f $LOG_DIR/*
32 |
33 | cd "$TOP/mod_h2" ||fail
34 | if needs_update .installed .; then
35 | rm -f .installed
36 | if test ! -f configure -o configure.ac -nt configure; then
37 | autoreconf -i ||fail
38 | fi
39 | if test ! -d Makefile -o ./configure -nt Makefile; then
40 | ./configure || fail
41 | touch ./configure
42 | fi
43 | make clean||fail
44 | make ||fail
45 | find .
46 | touch .installed
47 | fi
48 | make install ||fail
49 | make test
50 | #make loadtest
51 |
--------------------------------------------------------------------------------
/docker/ubuntu-focal/bin/mod_h2_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TOP=/apache-httpd
4 | DATADIR=$TOP/data
5 |
6 | fail() {
7 | echo "$@"
8 | exit 1
9 | }
10 |
11 | needs_update() {
12 | local ref_file="$1"
13 | local check_dir="$2"
14 | if test ! -f "$ref_file"; then
15 | return 0
16 | fi
17 | find "$check_dir" -type f -a -newer "$ref_file" -o -type d -name .git -prune -a -false |
18 | while read fname; do
19 | return 0
20 | done
21 | return 1
22 | }
23 |
24 | PREFIX=$(apxs -q exec_prefix)
25 | if test ! -d $PREFIX; then
26 | fail "apache install prefix not found: $PREFIX"
27 | fi
28 |
29 | # remove some stuff that accumulates
30 | LOG_DIR=$(apxs -q logfiledir)
31 | rm -f $LOG_DIR/*
32 |
33 | cd "$TOP/mod_h2" ||fail
34 | if needs_update .installed .; then
35 | rm -f .installed
36 | if test ! -f configure -o configure.ac -nt configure; then
37 | autoreconf -i ||fail
38 | fi
39 | if test ! -d Makefile -o ./configure -nt Makefile; then
40 | ./configure || fail
41 | touch ./configure
42 | fi
43 | make clean||fail
44 | make ||fail
45 | find .
46 | touch .installed
47 | fi
48 | make install ||fail
49 | make test
50 | #make loadtest
51 |
--------------------------------------------------------------------------------
/mod_http2/h2_switch.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_switch__
18 | #define __mod_h2__h2_switch__
19 |
20 | /*
21 | * One time, post config initialization.
22 | */
23 | apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s);
24 |
25 | /* Register apache hooks for protocol switching
26 | */
27 | void h2_switch_register_hooks(void);
28 |
29 |
30 | #endif /* defined(__mod_h2__h2_switch__) */
31 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/hecho.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import cgi, os
3 | import cgitb; cgitb.enable()
4 |
5 | status = '200 Ok'
6 |
7 | try:
8 | form = cgi.FieldStorage()
9 |
10 | # A nested FieldStorage instance holds the file
11 | name = form['name'].value
12 | value = ''
13 |
14 | try:
15 | value = form['value'].value
16 | except KeyError:
17 | value = os.environ.get("HTTP_"+name, "unset")
18 |
19 | # Test if a value was given
20 | if name:
21 | print("Status: 200")
22 | print("%s: %s" % (name, value,))
23 | print ("""\
24 | Content-Type: text/plain\n""")
25 |
26 | else:
27 | print("Status: 400 Parameter Missing")
28 | print("""\
29 | Content-Type: text/html\n
30 |
31 | No name and value was specified: %s %s
32 | """ % (name, value))
33 |
34 | except KeyError:
35 | print("Status: 200 Ok")
36 | print("""\
37 | Content-Type: text/html\n
38 |
39 | Echo
43 | """)
44 | pass
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/modules/http2/test_501_proxy_serverheader.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestProxyServerHeader:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | conf = H2Conf(env, extras={
11 | f'cgi.{env.http_tld}': [
12 | "Header unset Server",
13 | "Header always set Server cgi",
14 | ]
15 | })
16 | conf.add_vhost_cgi(proxy_self=True, h2proxy_self=False)
17 | conf.install()
18 | assert env.apache_restart() == 0
19 |
20 | def setup_method(self, method):
21 | print("setup_method: %s" % method.__name__)
22 |
23 | def teardown_method(self, method):
24 | print("teardown_method: %s" % method.__name__)
25 |
26 | def test_h2_501_01(self, env):
27 | url = env.mkurl("https", "cgi", "/proxy/hello.py")
28 | r = env.curl_get(url, 5)
29 | assert r.response["status"] == 200
30 | assert "HTTP/1.1" == r.response["json"]["protocol"]
31 | assert "" == r.response["json"]["https"]
32 | assert "" == r.response["json"]["ssl_protocol"]
33 | assert "" == r.response["json"]["h2"]
34 | assert "" == r.response["json"]["h2push"]
35 | assert "cgi" == r.response["header"]["server"]
36 |
--------------------------------------------------------------------------------
/http2_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 |
4 | function curl() {
5 | command curl "$@" --limit-rate 5M \
6 | -o /dev/null -w "HTTP/%{http_version}" -m 5 \
7 | 2>&1 | grep -v 'Operation timed out after'
8 | echo; echo
9 | }
10 |
11 | [ "$#" -eq 1 ] || {
12 | echo "Usage: $0 URL" >&2
13 | exit 1
14 | }
15 |
16 | SRV="$1"; shift
17 |
18 | ###
19 |
20 | UNIQ_ID="test_id=$(date +%s)"
21 | URI="$SRV?$UNIQ_ID"
22 |
23 | echo "### Performing test with ID $UNIQ_ID"
24 | echo "### grep $UNIQ_ID /var/log/access.mod_h2_issue_203"
25 | echo
26 |
27 | echo "# http2_whole_ssl"
28 | curl --http2 "https://$URI&_http2_whole_ssl___"
29 |
30 | echo "# http11_whole_ssl"
31 | curl --http1.1 "https://$URI&http11_whole_ssl___"
32 |
33 | echo "# http10_whole_ssl"
34 | curl --http1.0 "https://$URI&http10_whole_ssl___"
35 |
36 | ###
37 |
38 | echo "# http2_whole_nonssl"
39 | curl --http2 "http://$URI&_http2_whole_nonssl"
40 |
41 | echo "# http11_whole_nonssl"
42 | curl --http1.1 "http://$URI&http11_whole_nonssl"
43 |
44 | echo "# http10_whole_nonssl"
45 | curl --http1.0 "http://$URI&http10_whole_nonssl"
46 |
47 | ###
48 |
49 | echo "# http2_half_ssl"
50 | curl --http2 -H "Range: bytes=0-52428800" "https://$URI&_http2_half_ssl____"
51 |
52 | echo "# http11_half_ssl"
53 | curl --http1.1 -H "Range: bytes=0-52428800" "https://$URI&http11_half_ssl____"
54 |
--------------------------------------------------------------------------------
/test/pyhttpd/conf/stop.conf.template:
--------------------------------------------------------------------------------
1 | # a config safe to use for stopping the server
2 | # this allows us to stop the server even when+
3 | # the config in the file is borked (as test cases may try to do that)
4 | #
5 | ServerName localhost
6 | ServerRoot "${server_dir}"
7 |
8 | DefaultRuntimeDir logs
9 | PidFile httpd.pid
10 |
11 | Include "conf/modules.conf"
12 |
13 | DocumentRoot "${server_dir}/htdocs"
14 |
15 |
16 | LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %k" combined
17 | LogFormat "%h %l %u %t \"%r\" %>s %b" common
18 | CustomLog "logs/access_log" combined
19 |
20 |
21 |
22 | TypesConfig "${gen_dir}/apache/conf/mime.types"
23 |
24 | Listen ${http_port}
25 | Listen ${https_port}
26 |
27 |
28 | # provide some default
29 | SSLSessionCache "shmcb:ssl_gcache_data(32000)"
30 |
31 |
32 |
33 | ServerName ${http_tld}
34 | ServerAlias www.${http_tld}
35 |
36 | SSLEngine off
37 |
38 | DocumentRoot "${server_dir}/htdocs"
39 |
40 |
41 |
42 | Options Indexes FollowSymLinks
43 | AllowOverride None
44 | Require all granted
45 |
46 | AddHandler cgi-script .py
47 | AddHandler cgi-script .cgi
48 | Options +ExecCGI
49 |
50 |
--------------------------------------------------------------------------------
/mod_http2/h2_bucket_eos.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef mod_http2_h2_bucket_stream_eos_h
18 | #define mod_http2_h2_bucket_stream_eos_h
19 |
20 | struct h2_stream;
21 |
22 | /** End Of HTTP/2 STREAM (H2EOS) bucket */
23 | extern const apr_bucket_type_t h2_bucket_type_eos;
24 |
25 | #define H2_BUCKET_IS_H2EOS(e) (e->type == &h2_bucket_type_eos)
26 |
27 | apr_bucket *h2_bucket_eos_make(apr_bucket *b, struct h2_stream *stream);
28 |
29 | apr_bucket *h2_bucket_eos_create(apr_bucket_alloc_t *list,
30 | struct h2_stream *stream);
31 |
32 | #endif /* mod_http2_h2_bucket_stream_eos_h */
33 |
--------------------------------------------------------------------------------
/test/modules/http2/test_102_require.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestRequire:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | domain = f"ssl.{env.http_tld}"
11 | conf = H2Conf(env)
12 | conf.start_vhost(domains=[domain], port=env.https_port)
13 | conf.add("""
14 | Protocols h2 http/1.1
15 | SSLOptions +StdEnvVars
16 |
17 | Require expr \"%{HTTP2} == 'on'\"
18 |
19 |
20 | Require expr \"%{HTTP2} == 'off'\"
21 | """)
22 | conf.end_vhost()
23 | conf.install()
24 | # the dir needs to exists for the configuration to have effect
25 | env.mkpath(f"{env.server_dir}/htdocs/ssl-client-verify")
26 | assert env.apache_restart() == 0
27 |
28 | def test_h2_102_01(self, env):
29 | url = env.mkurl("https", "ssl", "/h2only.html")
30 | r = env.curl_get(url)
31 | assert 0 == r.exit_code
32 | assert r.response
33 | assert 404 == r.response["status"]
34 |
35 | def test_h2_102_02(self, env):
36 | url = env.mkurl("https", "ssl", "/noh2.html")
37 | r = env.curl_get(url)
38 | assert 0 == r.exit_code
39 | assert r.response
40 | assert 403 == r.response["status"]
41 |
--------------------------------------------------------------------------------
/test/unit/main.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 |
15 | #include "test_common.h"
16 |
17 | #include
18 |
19 | static Suite *main_test_suite(void)
20 | {
21 | Suite *suite = suite_create("main");
22 |
23 | suite_add_tcase(suite, h2_util_test_case());
24 |
25 | return suite;
26 | }
27 |
28 | int main(int argc, const char * const argv[])
29 | {
30 | SRunner *runner;
31 | int failed;
32 |
33 | /* Initialize APR and create our test runner. */
34 | apr_app_initialize(&argc, &argv, NULL);
35 | runner = srunner_create(main_test_suite());
36 |
37 | /* Log TAP to stdout. */
38 | srunner_set_tap(runner, "-");
39 |
40 | /* Run the tests and collect failures. */
41 | srunner_run_all(runner, CK_SILENT /* output only TAP */);
42 | failed = srunner_ntests_failed(runner);
43 |
44 | /* Clean up. */
45 | srunner_free(runner);
46 | apr_terminate();
47 |
48 | return failed ? 1 : 0;
49 | }
50 |
--------------------------------------------------------------------------------
/mod_http2/h2_version.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef mod_h2_h2_version_h
18 | #define mod_h2_h2_version_h
19 |
20 | #undef PACKAGE_VERSION
21 | #undef PACKAGE_TARNAME
22 | #undef PACKAGE_STRING
23 | #undef PACKAGE_NAME
24 | #undef PACKAGE_BUGREPORT
25 |
26 | /**
27 | * @macro
28 | * Version number of the http2 module as c string
29 | */
30 | #define MOD_HTTP2_VERSION "2.0.2-git"
31 |
32 | /**
33 | * @macro
34 | * Numerical representation of the version number of the http2 module
35 | * release. This is a 24 bit number with 8 bits for major number, 8 bits
36 | * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
37 | */
38 | #define MOD_HTTP2_VERSION_NUM 0x020002
39 |
40 |
41 | #endif /* mod_h2_h2_version_h */
42 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/necho.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import cgi, os
3 | import time
4 | import cgitb; cgitb.enable()
5 |
6 | status = '200 Ok'
7 |
8 | try:
9 | form = cgi.FieldStorage()
10 | count = form['count']
11 | text = form['text']
12 |
13 | waitsec = float(form['wait1'].value) if 'wait1' in form else 0.0
14 | if waitsec > 0:
15 | time.sleep(waitsec)
16 |
17 | if int(count.value):
18 | print("Status: 200")
19 | print("""\
20 | Content-Type: text/plain\n""")
21 |
22 | waitsec = float(form['wait2'].value) if 'wait2' in form else 0.0
23 | if waitsec > 0:
24 | time.sleep(waitsec)
25 |
26 | i = 0;
27 | for i in range(0, int(count.value)):
28 | print("%s" % (text.value))
29 |
30 | waitsec = float(form['wait3'].value) if 'wait3' in form else 0.0
31 | if waitsec > 0:
32 | time.sleep(waitsec)
33 |
34 | else:
35 | print("Status: 400 Parameter Missing")
36 | print("""\
37 | Content-Type: text/html\n
38 |
39 | No count was specified: %s
40 | """ % (count.value))
41 |
42 | except KeyError:
43 | print("Status: 200 Ok")
44 | print("""\
45 | Content-Type: text/html\n
46 |
47 | Echo
51 | """)
52 | pass
53 |
54 |
55 |
--------------------------------------------------------------------------------
/test/modules/http2/test_300_interim.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestInterimResponses:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_test1().add_vhost_cgi().install()
11 | assert env.apache_restart() == 0
12 |
13 | def setup_method(self, method):
14 | print("setup_method: %s" % method.__name__)
15 |
16 | def teardown_method(self, method):
17 | print("teardown_method: %s" % method.__name__)
18 |
19 | # check that we normally do not see an interim response
20 | def test_h2_300_01(self, env):
21 | url = env.mkurl("https", "test1", "/index.html")
22 | r = env.curl_post_data(url, 'XYZ')
23 | assert r.response["status"] == 200
24 | assert "previous" not in r.response
25 |
26 | # check that we see an interim response when we ask for it
27 | def test_h2_300_02(self, env):
28 | url = env.mkurl("https", "cgi", "/echo.py")
29 | r = env.curl_post_data(url, 'XYZ', options=["-H", "expect: 100-continue"])
30 | assert r.response["status"] == 200
31 | assert "previous" in r.response
32 | assert 100 == r.response["previous"]["status"]
33 |
34 | # check proper answer on unexpected
35 | def test_h2_300_03(self, env):
36 | url = env.mkurl("https", "cgi", "/echo.py")
37 | r = env.curl_post_data(url, 'XYZ', options=["-H", "expect: the-unexpected"])
38 | assert 417 == r.response["status"]
39 | assert "previous" not in r.response
40 |
--------------------------------------------------------------------------------
/mod_http2/h2_version.h.in:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef mod_h2_h2_version_h
18 | #define mod_h2_h2_version_h
19 |
20 | #undef PACKAGE_VERSION
21 | #undef PACKAGE_TARNAME
22 | #undef PACKAGE_STRING
23 | #undef PACKAGE_NAME
24 | #undef PACKAGE_BUGREPORT
25 |
26 | /**
27 | * @macro
28 | * Version number of the http2 module as c string
29 | */
30 | #define MOD_HTTP2_VERSION "@PACKAGE_VERSION@-git"
31 |
32 | /**
33 | * @macro
34 | * Numerical representation of the version number of the http2 module
35 | * release. This is a 24 bit number with 8 bits for major number, 8 bits
36 | * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
37 | */
38 | #define MOD_HTTP2_VERSION_NUM @PACKAGE_VERSION_NUM@
39 |
40 |
41 | #endif /* mod_h2_h2_version_h */
42 |
--------------------------------------------------------------------------------
/test/unit/test_common.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 |
15 | /*
16 | * Common headers and declarations needed by most/all test source files.
17 | */
18 |
19 | #include /* for pid_t on Windows, needed by Check */
20 | #include
21 |
22 | /*
23 | * Compatibility ck_assert macros for Check < 0.11.
24 | */
25 | #if (CHECK_MAJOR_VERSION == 0) && (CHECK_MINOR_VERSION < 11)
26 | # include /* fabs() */
27 | # include /* memcmp() */
28 |
29 | # define ck_assert_double_eq_tol(a, b, tol) \
30 | ck_assert(fabs((a) - (b)) <= (tol))
31 | # define ck_assert_mem_eq(a, b, len) \
32 | ck_assert(!memcmp((a), (b), (len)))
33 | # define ck_assert_ptr_nonnull(p) \
34 | ck_assert((p) != NULL)
35 | #endif
36 |
37 | /*
38 | * A list of Check test case declarations, usually one per source file. Add your
39 | * test case here when adding a new source file, then add it to the
40 | * main_test_suite() in main.c.
41 | */
42 |
43 | TCase *h2_util_test_case(void);
44 |
--------------------------------------------------------------------------------
/test/modules/http2/test_005_files.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pytest
3 |
4 | from .env import H2Conf
5 |
6 |
7 | def mk_text_file(fpath: str, lines: int):
8 | t110 = ""
9 | for _ in range(11):
10 | t110 += "0123456789"
11 | with open(fpath, "w") as fd:
12 | for i in range(lines):
13 | fd.write("{0:015d}: ".format(i)) # total 128 bytes per line
14 | fd.write(t110)
15 | fd.write("\n")
16 |
17 |
18 | class TestFiles:
19 |
20 | URI_PATHS = []
21 |
22 | @pytest.fixture(autouse=True, scope='class')
23 | def _class_scope(self, env):
24 | docs_a = os.path.join(env.server_docs_dir, "cgi/files")
25 | uris = []
26 | file_count = 32
27 | file_sizes = [1, 10, 100, 10000]
28 | for i in range(file_count):
29 | fsize = file_sizes[i % len(file_sizes)]
30 | if fsize is None:
31 | raise Exception("file sizes?: {0} {1}".format(i, fsize))
32 | fname = "{0}-{1}k.txt".format(i, fsize)
33 | mk_text_file(os.path.join(docs_a, fname), 8 * fsize)
34 | self.URI_PATHS.append(f"/files/{fname}")
35 |
36 | H2Conf(env).add_vhost_cgi(
37 | proxy_self=True, h2proxy_self=True
38 | ).add_vhost_test1(
39 | proxy_self=True, h2proxy_self=True
40 | ).install()
41 | assert env.apache_restart() == 0
42 |
43 | def test_h2_005_01(self, env):
44 | url = env.mkurl("https", "cgi", self.URI_PATHS[2])
45 | r = env.curl_get(url)
46 | assert r.response, r.stderr + r.stdout
47 | assert r.response["status"] == 200
48 |
--------------------------------------------------------------------------------
/Makefile.am:
--------------------------------------------------------------------------------
1 | # Licensed under the Apache License, Version 2.0 (the "License");
2 | # you may not use this file except in compliance with the License.
3 | # You may obtain a copy of the License at
4 | #
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | #
7 | # Unless required by applicable law or agreed to in writing, software
8 | # distributed under the License is distributed on an "AS IS" BASIS,
9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | # See the License for the specific language governing permissions and
11 | # limitations under the License.
12 | #
13 | SUBDIRS = @BUILD_SUBDIRS@
14 | DIST_SUBDIRS = mod_http2
15 |
16 | ACLOCAL_AMFLAGS = -I m4
17 |
18 | EXTRA_DIST = \
19 | test/unit \
20 | test/Makefile.in \
21 | test/Makefile.am \
22 | test/modules \
23 | test/pyhttpd
24 |
25 |
26 | dist_doc_DATA = README README.md LICENSE
27 |
28 | dist-hook:
29 | @rm -rf $(distdir)/test/unit/.deps \
30 | $(distdir)/test/unit/.py* \
31 | `find $(distdir) -name __pycache__` \
32 | `find $(distdir) -name .deps` \
33 | `find $(distdir) -name .libs` \
34 | `find $(distdir) -name .dirstamp` \
35 | `find $(distdir) -name .pytest_cache` \
36 | `find $(distdir) -name "*.o"`
37 |
38 | .PHONY: test
39 |
40 | test:
41 | $(MAKE) -C test/ test
42 |
43 | loadtest:
44 | $(MAKE) -C test/ loadtest
45 |
46 | clean-local:
47 | $(MAKE) -C test/ clean
48 |
49 | docker-test:
50 | docker-compose build debian-sid
51 | docker-compose build archlinux
52 | docker-compose run debian-sid
53 | docker-compose run archlinux
54 |
--------------------------------------------------------------------------------
/test/pyhttpd/conf/httpd.conf.template:
--------------------------------------------------------------------------------
1 | ServerName localhost
2 | ServerRoot "${server_dir}"
3 |
4 | DefaultRuntimeDir logs
5 | PidFile httpd.pid
6 |
7 | Include "conf/modules.conf"
8 |
9 | DocumentRoot "${server_dir}/htdocs"
10 |
11 |
12 | LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %k" combined
13 | LogFormat "%h %l %u %t \"%r\" %>s %b" common
14 | CustomLog "logs/access_log" combined
15 |
16 |
17 |
18 | TypesConfig "${gen_dir}/apache/conf/mime.types"
19 |
20 | Listen ${http_port}
21 | Listen ${https_port}
22 |
23 |
24 | # provide some default
25 | SSLSessionCache "shmcb:ssl_gcache_data(32000)"
26 |
27 |
28 | # Insert our test specific configuration before the first vhost,
29 | # so that its vhosts can be the default one. This is relevant in
30 | # certain behaviours, such as protocol selection during SSL ALPN
31 | # negotiation.
32 | #
33 | Include "conf/test.conf"
34 |
35 | RequestReadTimeout header=10 body=10
36 |
37 |
38 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
39 |
40 |
41 | AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css
42 |
43 |
44 |
45 | ServerName ${http_tld}
46 | ServerAlias www.${http_tld}
47 |
48 | SSLEngine off
49 |
50 | DocumentRoot "${server_dir}/htdocs"
51 |
52 |
53 |
54 | Options Indexes FollowSymLinks
55 | AllowOverride None
56 | Require all granted
57 |
58 | AddHandler cgi-script .py
59 | AddHandler cgi-script .cgi
60 | Options +ExecCGI
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/test/scoreboard.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import logging
3 | import sys
4 |
5 | from modules.http2.env import H2TestEnv, H2TestSetup, H2Conf
6 |
7 | log = logging.getLogger(__name__)
8 |
9 |
10 | class ScoreboardTest:
11 |
12 | def __init__(self):
13 | pass
14 |
15 | @classmethod
16 | def main(cls):
17 | parser = argparse.ArgumentParser(prog='load_h1', description="""
18 | Spin up a server with server-status configured.
19 | """)
20 | parser.add_argument("-v", "--verbose", action='count', default=0,
21 | help="log more output on stderr")
22 | args = parser.parse_args()
23 |
24 | if args.verbose > 0:
25 | console = logging.StreamHandler()
26 | console.setLevel(logging.INFO)
27 | console.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
28 | logging.getLogger('').addHandler(console)
29 |
30 | rv = 0
31 | try:
32 | env = H2TestEnv()
33 | setup = H2TestSetup(env=env)
34 | env.setup_httpd(setup=setup)
35 |
36 | conf = H2Conf(env=env, extras={
37 | 'base': [
38 | "ServerLimit 1",
39 | "LogLevel http2:debug",
40 | "",
41 | " SetHandler server-status",
42 | " ",
43 | ]
44 | })
45 | conf.add_vhost_test1()
46 | conf.add_vhost_cgi()
47 | conf.install()
48 | env.apache_restart()
49 | sys.stdin.read()
50 |
51 | except KeyboardInterrupt:
52 | log.warning("aborted")
53 | rv = 1
54 |
55 | env.apache_stop()
56 | sys.exit(rv)
57 |
58 | if __name__ == "__main__":
59 | ScoreboardTest.main()
60 |
--------------------------------------------------------------------------------
/test/modules/http2/conftest.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 |
4 | import pytest
5 | import sys
6 |
7 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
8 |
9 | from .env import H2TestEnv
10 |
11 |
12 | def pytest_report_header(config, startdir):
13 | env = H2TestEnv()
14 | return f"mod_h2 [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
15 |
16 |
17 | def pytest_addoption(parser):
18 | parser.addoption("--repeat", action="store", type=int, default=1,
19 | help='Number of times to repeat each test')
20 | parser.addoption("--all", action="store_true")
21 |
22 |
23 | def pytest_generate_tests(metafunc):
24 | if "repeat" in metafunc.fixturenames:
25 | count = int(metafunc.config.getoption("repeat"))
26 | metafunc.fixturenames.append('tmp_ct')
27 | metafunc.parametrize('repeat', range(count))
28 |
29 |
30 | @pytest.fixture(scope="package")
31 | def env(pytestconfig) -> H2TestEnv:
32 | level = logging.INFO
33 | console = logging.StreamHandler()
34 | console.setLevel(level)
35 | console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
36 | logging.getLogger('').addHandler(console)
37 | logging.getLogger('').setLevel(level=level)
38 | env = H2TestEnv(pytestconfig=pytestconfig)
39 | env.setup_httpd()
40 | env.apache_access_log_clear()
41 | env.httpd_error_log.clear_log()
42 | return env
43 |
44 |
45 | @pytest.fixture(autouse=True, scope="package")
46 | def _session_scope(env):
47 | yield
48 | assert env.apache_stop() == 0
49 | errors, warnings = env.httpd_error_log.get_missed()
50 | assert (len(errors), len(warnings)) == (0, 0),\
51 | f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
52 | "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
53 |
54 |
--------------------------------------------------------------------------------
/test/modules/http2/test_401_early_hints.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | # The push tests depend on "nghttp"
7 | class TestEarlyHints:
8 |
9 | @pytest.fixture(autouse=True, scope='class')
10 | def _class_scope(self, env):
11 | H2Conf(env).start_vhost(domains=[f"hints.{env.http_tld}"],
12 | port=env.https_port, doc_root="htdocs/test1"
13 | ).add("""
14 | H2EarlyHints on
15 | RewriteEngine on
16 | RewriteRule ^/006-(.*)?\\.html$ /006.html
17 |
18 | H2PushResource "/006/006.css" critical
19 |
20 |
21 | Header add Link "006/006.css>;rel=preload"
22 |
23 | """).end_vhost(
24 | ).install()
25 | assert env.apache_restart() == 0
26 |
27 | # H2EarlyHints enabled in general, check that it works for H2PushResource
28 | def test_h2_401_31(self, env):
29 | url = env.mkurl("https", "hints", "/006-hints.html")
30 | r = env.nghttp().get(url)
31 | assert r.response["status"] == 200
32 | promises = r.results["streams"][r.response["id"]]["promises"]
33 | assert 1 == len(promises)
34 | early = r.response["previous"]
35 | assert early
36 | assert 103 == int(early["header"][":status"])
37 | assert early["header"]["link"]
38 |
39 | # H2EarlyHints enabled in general, but does not trigger on added response headers
40 | def test_h2_401_32(self, env):
41 | url = env.mkurl("https", "hints", "/006-nohints.html")
42 | r = env.nghttp().get(url)
43 | assert r.response["status"] == 200
44 | promises = r.results["streams"][r.response["id"]]["promises"]
45 | assert 1 == len(promises)
46 | assert "previous" not in r.response
47 |
--------------------------------------------------------------------------------
/mod_http2/h2_c2.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_c2__
18 | #define __mod_h2__h2_c2__
19 |
20 | #include
21 |
22 | typedef enum {
23 | H2_MPM_UNKNOWN,
24 | H2_MPM_WORKER,
25 | H2_MPM_EVENT,
26 | H2_MPM_PREFORK,
27 | H2_MPM_MOTORZ,
28 | H2_MPM_SIMPLE,
29 | H2_MPM_NETWARE,
30 | H2_MPM_WINNT,
31 | } h2_mpm_type_t;
32 |
33 | /* Returns the type of MPM module detected */
34 | h2_mpm_type_t h2_conn_mpm_type(void);
35 | const char *h2_conn_mpm_name(void);
36 | int h2_mpm_supported(void);
37 |
38 | /* Initialize this child process for h2 secondary connection work,
39 | * to be called once during child init before multi processing
40 | * starts.
41 | */
42 | apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s);
43 |
44 | conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent);
45 | void h2_c2_destroy(conn_rec *c2);
46 |
47 | /**
48 | * Process a secondary connection for a HTTP/2 stream request.
49 | */
50 | apr_status_t h2_c2_process(conn_rec *c, apr_thread_t *thread, int worker_id);
51 |
52 | void h2_c2_register_hooks(void);
53 |
54 | #endif /* defined(__mod_h2__h2_c2__) */
55 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/cgi/upload.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import cgi, os
3 | import cgitb
4 | cgitb.enable()
5 |
6 | status = '200 Ok'
7 |
8 | try: # Windows needs stdio set for binary mode.
9 | import msvcrt
10 | msvcrt.setmode (0, os.O_BINARY) # stdin = 0
11 | msvcrt.setmode (1, os.O_BINARY) # stdout = 1
12 | except ImportError:
13 | pass
14 |
15 | form = cgi.FieldStorage()
16 |
17 | # Test if the file was uploaded
18 | if 'file' in form:
19 | fileitem = form['file']
20 | # strip leading path from file name to avoid directory traversal attacks
21 | fn = os.path.basename(fileitem.filename)
22 | f = open(('%s/files/%s' % (os.environ["DOCUMENT_ROOT"], fn)), 'wb');
23 | f.write(fileitem.file.read())
24 | f.close()
25 | message = "The file %s was uploaded successfully" % (fn)
26 | print("Status: 201 Created")
27 | print("Content-Type: text/html")
28 | print("Location: %s://%s/files/%s" % (os.environ["REQUEST_SCHEME"], os.environ["HTTP_HOST"], fn))
29 | print("")
30 | print("%s
" % (message))
31 |
32 | elif 'remove' in form:
33 | remove = form['remove'].value
34 | try:
35 | fn = os.path.basename(remove)
36 | os.remove('./files/' + fn)
37 | message = 'The file "' + fn + '" was removed successfully'
38 | except OSError as e:
39 | message = 'Error removing ' + fn + ': ' + e.strerror
40 | status = '404 File Not Found'
41 | print("Status: %s" % (status))
42 | print("""
43 | Content-Type: text/html
44 |
45 |
46 | %s
47 | """ % (message))
48 |
49 | else:
50 | message = '''\
51 | Upload File
54 | '''
55 | print("Status: %s" % (status))
56 | print("""\
57 | Content-Type: text/html
58 |
59 |
60 | %s
61 | """ % (message))
62 |
63 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | mod_h2 test site
4 |
5 |
6 | mod_h2 test site
7 |
8 | served directly
9 |
20 | mod_proxyied
21 |
32 | mod_rewritten
33 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/mod_http2/h2_c2_filter.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_c2_filter__
18 | #define __mod_h2__h2_c2_filter__
19 |
20 | /**
21 | * h2_from_h1 parses a HTTP/1.1 response into
22 | * - response status
23 | * - a list of header values
24 | * - a series of bytes that represent the response body alone, without
25 | * any meta data, such as inserted by chunked transfer encoding.
26 | *
27 | * All data is allocated from the stream memory pool.
28 | *
29 | * Again, see comments in h2_request: ideally we would take the headers
30 | * and status from the httpd structures instead of parsing them here, but
31 | * we need to have all handlers and filters involved in request/response
32 | * processing, so this seems to be the way for now.
33 | */
34 | struct h2_headers;
35 | struct h2_response_parser;
36 |
37 | apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb);
38 |
39 | apr_status_t h2_c2_filter_response_out(ap_filter_t *f, apr_bucket_brigade *bb);
40 |
41 | apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
42 | apr_bucket_brigade* brigade,
43 | ap_input_mode_t mode,
44 | apr_read_type_e block,
45 | apr_off_t readbytes);
46 |
47 | apr_status_t h2_c2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
48 |
49 | #endif /* defined(__mod_h2__h2_c2_filter__) */
50 |
--------------------------------------------------------------------------------
/mod_http2/h2_protocol.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_protocol__
18 | #define __mod_h2__h2_protocol__
19 |
20 | /**
21 | * List of protocol identifiers that we support in cleartext
22 | * negotiations. NULL terminated.
23 | */
24 | extern const char *h2_protocol_ids_clear[];
25 |
26 | /**
27 | * List of protocol identifiers that we support in TLS encrypted
28 | * negotiations (ALPN). NULL terminated.
29 | */
30 | extern const char *h2_protocol_ids_tls[];
31 |
32 | /**
33 | * Provide a user readable description of the HTTP/2 error code-
34 | * @param h2_error http/2 error code, as in rfc 7540, ch. 7
35 | * @return textual description of code or that it is unknown.
36 | */
37 | const char *h2_protocol_err_description(unsigned int h2_error);
38 |
39 | /*
40 | * One time, post config initialization.
41 | */
42 | apr_status_t h2_protocol_init(apr_pool_t *pool, server_rec *s);
43 |
44 | /**
45 | * Check if the given primary connection fulfills the protocol
46 | * requirements for HTTP/2.
47 | * @param c the connection
48 | * @param require_all != 0 iff any missing connection properties make
49 | * the test fail. For example, a cipher might not have been selected while
50 | * the handshake is still ongoing.
51 | * @return != 0 iff protocol requirements are met
52 | */
53 | int h2_protocol_is_acceptable_c1(conn_rec *c, request_rec *r, int require_all);
54 |
55 |
56 | #endif /* defined(__mod_h2__h2_protocol__) */
57 |
--------------------------------------------------------------------------------
/test/modules/http2/test_700_load_get.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestLoadGet:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_cgi().add_vhost_test1().install()
11 | assert env.apache_restart() == 0
12 |
13 | def check_h2load_ok(self, env, r, n):
14 | assert 0 == r.exit_code
15 | r = env.h2load_status(r)
16 | assert n == r.results["h2load"]["requests"]["total"]
17 | assert n == r.results["h2load"]["requests"]["started"]
18 | assert n == r.results["h2load"]["requests"]["done"]
19 | assert n == r.results["h2load"]["requests"]["succeeded"]
20 | assert n == r.results["h2load"]["status"]["2xx"]
21 | assert 0 == r.results["h2load"]["status"]["3xx"]
22 | assert 0 == r.results["h2load"]["status"]["4xx"]
23 | assert 0 == r.results["h2load"]["status"]["5xx"]
24 |
25 | # test load on cgi script, single connection, different sizes
26 | @pytest.mark.parametrize("start", [
27 | 1000, 80000
28 | ])
29 | def test_h2_700_10(self, env, start):
30 | text = "X"
31 | chunk = 32
32 | for n in range(0, 5):
33 | args = [env.h2load, "-n", "%d" % chunk, "-c", "1", "-m", "10",
34 | f"--base-uri={env.https_base_url}"]
35 | for i in range(0, chunk):
36 | args.append(env.mkurl("https", "cgi", ("/mnot164.py?count=%d&text=%s" % (start+(n*chunk)+i, text))))
37 | r = env.run(args)
38 | self.check_h2load_ok(env, r, chunk)
39 |
40 | # test load on cgi script, single connection
41 | @pytest.mark.parametrize("conns", [
42 | 1, 2, 16, 32
43 | ])
44 | def test_h2_700_11(self, env, conns):
45 | text = "X"
46 | start = 1200
47 | chunk = 64
48 | for n in range(0, 5):
49 | args = [env.h2load, "-n", "%d" % chunk, "-c", "%d" % conns, "-m", "10",
50 | f"--base-uri={env.https_base_url}"]
51 | for i in range(0, chunk):
52 | args.append(env.mkurl("https", "cgi", ("/mnot164.py?count=%d&text=%s" % (start+(n*chunk)+i, text))))
53 | r = env.run(args)
54 | self.check_h2load_ok(env, r, chunk)
55 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 | services:
3 | debian-sid:
4 | # build and run tests in a debian sid container
5 | image: ${DOCKER_REGISTRY}/mod-h2-debian-sid:0.0.1
6 | container_name: mod-h2-debian-sid
7 | build:
8 | context: .
9 | dockerfile: docker/debian-sid/Dockerfile
10 | labels:
11 | - "description=mod_h2 debian sid server"
12 | - "maintainer=stefan@eissing.org"
13 | expose:
14 | - "42001"
15 | - "42002"
16 | volumes:
17 | - mod-h2-debian-sid-data:/apache-httpd/data
18 | ports:
19 | - "42001"
20 | - "42002"
21 |
22 | archlinux:
23 | # build and run tests in a debian sid container
24 | image: ${DOCKER_REGISTRY}/mod-h2-archlinux:0.0.1
25 | container_name: mod-h2-archlinux
26 | build:
27 | context: .
28 | dockerfile: docker/archlinux/Dockerfile
29 | labels:
30 | - "description=mod_h2 archlinux server"
31 | - "maintainer=stefan@eissing.org"
32 | expose:
33 | - "42001"
34 | - "42002"
35 | volumes:
36 | - mod-h2-archlinux-data:/apache-httpd/data
37 | ports:
38 | - "42001"
39 | - "42002"
40 |
41 | ubuntu-focal:
42 | # build and run tests in a ubuntu-focal container
43 | image: ${DOCKER_REGISTRY}/mod-h2-ubuntu-focal:0.0.1
44 | container_name: mod-h2-ubuntu-focal
45 | build:
46 | context: .
47 | dockerfile: docker/ubuntu-focal/Dockerfile
48 | labels:
49 | - "description=mod_h2 ubuntu-focal server"
50 | - "maintainer=stefan@eissing.org"
51 | expose:
52 | - "42001"
53 | - "42002"
54 | volumes:
55 | - mod-h2-ubuntu-focal-data:/apache-httpd/data
56 | ports:
57 | - "42001"
58 | - "42002"
59 |
60 | volumes:
61 | mod-h2-debian-sid-data:
62 | name: mod-h2-debian-sid-data
63 | labels:
64 | - "description=debian sid data for mod_h2"
65 | - "maintainer=stefan@eissing.org"
66 |
67 | mod-h2-archlinux-data:
68 | name: mod-h2-archlinux-data
69 | labels:
70 | - "description=archlinux data for mod_h2"
71 | - "maintainer=stefan@eissing.org"
72 |
73 | mod-h2-ubuntu-focal-data:
74 | name: mod-h2-ubuntu-focal-data
75 | labels:
76 | - "description=ubuntu-focal data for mod_h2"
77 | - "maintainer=stefan@eissing.org"
78 |
79 |
80 |
--------------------------------------------------------------------------------
/test/modules/http2/test_106_shutdown.py:
--------------------------------------------------------------------------------
1 | #
2 | # mod-h2 test suite
3 | # check HTTP/2 timeout behaviour
4 | #
5 | import time
6 | from threading import Thread
7 |
8 | import pytest
9 |
10 | from .env import H2Conf
11 | from pyhttpd.result import ExecResult
12 |
13 |
14 | class TestShutdown:
15 |
16 | @pytest.fixture(autouse=True, scope='class')
17 | def _class_scope(self, env):
18 | conf = H2Conf(env)
19 | conf.add_vhost_cgi()
20 | conf.install()
21 | assert env.apache_restart() == 0
22 |
23 | def test_h2_106_01(self, env):
24 | url = env.mkurl("https", "cgi", "/necho.py")
25 | lines = 100000
26 | text = "123456789"
27 | wait2 = 1.0
28 | self.r = None
29 | def long_request():
30 | args = ["-vvv",
31 | "-F", f"count={lines}",
32 | "-F", f"text={text}",
33 | "-F", f"wait2={wait2}",
34 | ]
35 | self.r = env.curl_get(url, 5, options=args)
36 |
37 | t = Thread(target=long_request)
38 | t.start()
39 | time.sleep(0.5)
40 | assert env.apache_reload() == 0
41 | t.join()
42 | # noinspection PyTypeChecker
43 | time.sleep(1)
44 | r: ExecResult = self.r
45 | assert r.exit_code == 0
46 | assert r.response, f"no response via {r.args} in {r.stderr}\nstdout: {len(r.stdout)} bytes"
47 | assert r.response["status"] == 200, f"{r}"
48 | assert len(r.response["body"]) == (lines * (len(text)+1)), f"{r}"
49 |
50 | def test_h2_106_02(self, env):
51 | # PR65731: invalid GOAWAY frame at session start when
52 | # MaxRequestsPerChild is reached
53 | # Create a low limit and only 2 children, so we'll encounter this easily
54 | conf = H2Conf(env, extras={
55 | 'base': [
56 | "ServerLimit 2",
57 | "MaxRequestsPerChild 3"
58 | ]
59 | })
60 | conf.add_vhost_test1()
61 | conf.install()
62 | assert env.apache_restart() == 0
63 | url = env.mkurl("https", "test1", "/index.html")
64 | for i in range(7):
65 | r = env.curl_get(url, options=['-vvv'])
66 | assert r.exit_code == 0, f"failed on {i}. request: {r.stdout} {r.stderr}"
67 | assert r.response["status"] == 200
68 | assert "HTTP/2" == r.response["protocol"]
69 |
--------------------------------------------------------------------------------
/test/modules/http2/test_710_load_post_static.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import os
3 |
4 | from .env import H2Conf
5 |
6 |
7 | class TestLoadPostStatic:
8 |
9 | @pytest.fixture(autouse=True, scope='class')
10 | def _class_scope(self, env):
11 | H2Conf(env).add_vhost_test1().install()
12 | assert env.apache_restart() == 0
13 |
14 | def check_h2load_ok(self, env, r, n):
15 | assert 0 == r.exit_code
16 | r = env.h2load_status(r)
17 | assert n == r.results["h2load"]["requests"]["total"]
18 | assert n == r.results["h2load"]["requests"]["started"]
19 | assert n == r.results["h2load"]["requests"]["done"]
20 | assert n == r.results["h2load"]["requests"]["succeeded"]
21 | assert n == r.results["h2load"]["status"]["2xx"]
22 | assert 0 == r.results["h2load"]["status"]["3xx"]
23 | assert 0 == r.results["h2load"]["status"]["4xx"]
24 | assert 0 == r.results["h2load"]["status"]["5xx"]
25 |
26 | # test POST on static file, slurped in by server
27 | def test_h2_710_00(self, env):
28 | url = env.mkurl("https", "test1", "/index.html")
29 | n = 10
30 | m = 1
31 | conn = 1
32 | fname = "data-10k"
33 | args = [env.h2load, "-n", f"{n}", "-c", f"{conn}", "-m", f"{m}",
34 | f"--base-uri={env.https_base_url}",
35 | "-d", os.path.join(env.gen_dir, fname), url]
36 | r = env.run(args)
37 | self.check_h2load_ok(env, r, n)
38 |
39 | def test_h2_710_01(self, env):
40 | url = env.mkurl("https", "test1", "/index.html")
41 | n = 1000
42 | m = 100
43 | conn = 1
44 | fname = "data-1k"
45 | args = [env.h2load, "-n", f"{n}", "-c", f"{conn}", "-m", f"{m}",
46 | f"--base-uri={env.https_base_url}",
47 | "-d", os.path.join(env.gen_dir, fname), url]
48 | r = env.run(args)
49 | self.check_h2load_ok(env, r, n)
50 |
51 | def test_h2_710_02(self, env):
52 | url = env.mkurl("https", "test1", "/index.html")
53 | n = 100
54 | m = 50
55 | conn = 1
56 | fname = "data-100k"
57 | args = [env.h2load, "-n", f"{n}", "-c", f"{conn}", "-m", f"{m}",
58 | f"--base-uri={env.https_base_url}",
59 | "-d", os.path.join(env.gen_dir, fname), url]
60 | r = env.run(args)
61 | self.check_h2load_ok(env, r, n)
62 |
--------------------------------------------------------------------------------
/mod_http2/h2_request.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_request__
18 | #define __mod_h2__h2_request__
19 |
20 | #include "h2.h"
21 |
22 | h2_request *h2_request_create(int id, apr_pool_t *pool, const char *method,
23 | const char *scheme, const char *authority,
24 | const char *path, apr_table_t *header);
25 |
26 | apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
27 | request_rec *r);
28 |
29 | apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
30 | const char *name, size_t nlen,
31 | const char *value, size_t vlen,
32 | size_t max_field_len, int *pwas_added);
33 |
34 | apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
35 | const char *name, size_t nlen,
36 | const char *value, size_t vlen);
37 |
38 | apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos, size_t raw_bytes);
39 |
40 | h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
41 |
42 | /**
43 | * Create a request_rec representing the h2_request to be
44 | * processed on the given connection.
45 | *
46 | * @param req the h2 request to process
47 | * @param conn the connection to process the request on
48 | * @return the request_rec representing the request
49 | */
50 | request_rec *h2_create_request_rec(const h2_request *req, conn_rec *conn);
51 |
52 |
53 | #endif /* defined(__mod_h2__h2_request__) */
54 |
--------------------------------------------------------------------------------
/test/pyhttpd/result.py:
--------------------------------------------------------------------------------
1 | import json
2 | from datetime import timedelta
3 | from typing import Optional, Dict, List
4 |
5 |
6 | class ExecResult:
7 |
8 | def __init__(self, args: List[str], exit_code: int,
9 | stdout: bytes, stderr: bytes = None, duration: timedelta = None):
10 | self._args = args
11 | self._exit_code = exit_code
12 | self._raw = stdout if stdout else b''
13 | self._stdout = stdout.decode() if stdout is not None else ""
14 | self._stderr = stderr.decode() if stderr is not None else ""
15 | self._duration = duration if duration is not None else timedelta()
16 | self._response = None
17 | self._results = {}
18 | self._assets = []
19 | # noinspection PyBroadException
20 | try:
21 | self._json_out = json.loads(self._stdout)
22 | except:
23 | self._json_out = None
24 |
25 | def __repr__(self):
26 | return f"ExecResult[code={self.exit_code}, args={self._args}, stdout={self.stdout}, stderr={self.stderr}]"
27 |
28 | @property
29 | def exit_code(self) -> int:
30 | return self._exit_code
31 |
32 | @property
33 | def args(self) -> List[str]:
34 | return self._args
35 |
36 | @property
37 | def outraw(self) -> bytes:
38 | return self._raw
39 |
40 | @property
41 | def stdout(self) -> str:
42 | return self._stdout
43 |
44 | @property
45 | def json(self) -> Optional[Dict]:
46 | """Output as JSON dictionary or None if not parseable."""
47 | return self._json_out
48 |
49 | @property
50 | def stderr(self) -> str:
51 | return self._stderr
52 |
53 | @property
54 | def duration(self) -> timedelta:
55 | return self._duration
56 |
57 | @property
58 | def response(self) -> Optional[Dict]:
59 | return self._response
60 |
61 | @property
62 | def results(self) -> Dict:
63 | return self._results
64 |
65 | @property
66 | def assets(self) -> List:
67 | return self._assets
68 |
69 | def add_response(self, resp: Dict):
70 | if self._response:
71 | resp['previous'] = self._response
72 | self._response = resp
73 |
74 | def add_results(self, results: Dict):
75 | self._results.update(results)
76 | if 'response' in results:
77 | self.add_response(results['response'])
78 |
79 | def add_assets(self, assets: List):
80 | self._assets.extend(assets)
81 |
--------------------------------------------------------------------------------
/test/modules/http2/test_711_load_post_cgi.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import os
3 |
4 | from .env import H2Conf
5 |
6 |
7 | class TestLoadCgi:
8 |
9 | @pytest.fixture(autouse=True, scope='class')
10 | def _class_scope(self, env):
11 | H2Conf(env).add_vhost_cgi(proxy_self=True, h2proxy_self=True).install()
12 | assert env.apache_restart() == 0
13 |
14 | def check_h2load_ok(self, env, r, n):
15 | assert 0 == r.exit_code
16 | r = env.h2load_status(r)
17 | assert n == r.results["h2load"]["requests"]["total"]
18 | assert n == r.results["h2load"]["requests"]["started"]
19 | assert n == r.results["h2load"]["requests"]["done"]
20 | assert n == r.results["h2load"]["requests"]["succeeded"]
21 | assert n == r.results["h2load"]["status"]["2xx"]
22 | assert 0 == r.results["h2load"]["status"]["3xx"]
23 | assert 0 == r.results["h2load"]["status"]["4xx"]
24 | assert 0 == r.results["h2load"]["status"]["5xx"]
25 |
26 | # test POST on cgi, where input is read
27 | def test_h2_711_10(self, env):
28 | url = env.mkurl("https", "test1", "/echo.py")
29 | n = 100
30 | m = 5
31 | conn = 1
32 | fname = "data-100k"
33 | args = [
34 | env.h2load, "-n", str(n), "-c", str(conn), "-m", str(m),
35 | f"--base-uri={env.https_base_url}",
36 | "-d", os.path.join(env.gen_dir, fname), url
37 | ]
38 | r = env.run(args)
39 | self.check_h2load_ok(env, r, n)
40 |
41 | # test POST on cgi via http/1.1 proxy, where input is read
42 | def test_h2_711_11(self, env):
43 | url = env.mkurl("https", "test1", "/proxy/echo.py")
44 | n = 100
45 | m = 5
46 | conn = 1
47 | fname = "data-100k"
48 | args = [
49 | env.h2load, "-n", str(n), "-c", str(conn), "-m", str(m),
50 | f"--base-uri={env.https_base_url}",
51 | "-d", os.path.join(env.gen_dir, fname), url
52 | ]
53 | r = env.run(args)
54 | self.check_h2load_ok(env, r, n)
55 |
56 | # test POST on cgi via h2proxy, where input is read
57 | def test_h2_711_12(self, env):
58 | url = env.mkurl("https", "test1", "/h2proxy/echo.py")
59 | n = 100
60 | m = 5
61 | conn = 1
62 | fname = "data-100k"
63 | args = [
64 | env.h2load, "-n", str(n), "-c", str(conn), "-m", str(m),
65 | f"--base-uri={env.https_base_url}",
66 | "-d", os.path.join(env.gen_dir, fname), url
67 | ]
68 | r = env.run(args)
69 | self.check_h2load_ok(env, r, n)
70 |
--------------------------------------------------------------------------------
/test/modules/http2/test_712_buffering.py:
--------------------------------------------------------------------------------
1 | from datetime import timedelta
2 |
3 | import pytest
4 |
5 | from .env import H2Conf
6 | from pyhttpd.curl import CurlPiper
7 |
8 |
9 | class TestBuffering:
10 |
11 | @pytest.fixture(autouse=True, scope='class')
12 | def _class_scope(self, env):
13 | conf = H2Conf(env)
14 | conf.add_vhost_cgi(h2proxy_self=True).install()
15 | assert env.apache_restart() == 0
16 |
17 | @pytest.mark.skip(reason="this test shows unreliable jitter")
18 | def test_h2_712_01(self, env):
19 | # test gRPC like requests that do not end, but give answers, see #207
20 | #
21 | # this test works like this:
22 | # - use curl to POST data to the server /h2test/echo
23 | # - feed curl the data in chunks, wait a bit between chunks
24 | # - since some buffering on curl's stdout to Python is involved,
25 | # we will see the response data only at the end.
26 | # - therefore, we enable tracing with timestamps in curl on stderr
27 | # and see when the response chunks arrive
28 | # - if the server sends the incoming data chunks back right away,
29 | # as it should, we see receiving timestamps separated roughly by the
30 | # wait time between sends.
31 | #
32 | url = env.mkurl("https", "cgi", "/h2test/echo")
33 | base_chunk = "0123456789"
34 | chunks = ["chunk-{0:03d}-{1}\n".format(i, base_chunk) for i in range(5)]
35 | stutter = timedelta(seconds=0.2) # this is short, but works on my machine (tm)
36 | piper = CurlPiper(env=env, url=url)
37 | piper.stutter_check(chunks, stutter)
38 |
39 | def test_h2_712_02(self, env):
40 | # same as 712_01 but via mod_proxy_http2
41 | #
42 | url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo")
43 | base_chunk = "0123456789"
44 | chunks = ["chunk-{0:03d}-{1}\n".format(i, base_chunk) for i in range(3)]
45 | stutter = timedelta(seconds=0.4) # need a bit more delay since we have the extra connection
46 | piper = CurlPiper(env=env, url=url)
47 | piper.stutter_check(chunks, stutter)
48 |
49 | def test_h2_712_03(self, env):
50 | # same as 712_02 but with smaller chunks
51 | #
52 | url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo")
53 | base_chunk = "0"
54 | chunks = ["ck{0}-{1}\n".format(i, base_chunk) for i in range(3)]
55 | stutter = timedelta(seconds=0.4) # need a bit more delay since we have the extra connection
56 | piper = CurlPiper(env=env, url=url)
57 | piper.stutter_check(chunks, stutter)
58 |
--------------------------------------------------------------------------------
/test/modules/http2/test_100_conn_reuse.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestConnReuse:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_noh2().add_vhost_test1().add_vhost_cgi().install()
11 | assert env.apache_restart() == 0
12 |
13 | # make sure the protocol selection on the different hosts work as expected
14 | def test_h2_100_01(self, env):
15 | # this host defaults to h2, but we can request h1
16 | url = env.mkurl("https", "cgi", "/hello.py")
17 | assert "2" == env.curl_protocol_version( url )
18 | assert "1.1" == env.curl_protocol_version( url, options=[ "--http1.1" ] )
19 |
20 | # this host does not enable h2, it always falls back to h1
21 | url = env.mkurl("https", "noh2", "/hello.py")
22 | assert "1.1" == env.curl_protocol_version( url )
23 | assert "1.1" == env.curl_protocol_version( url, options=[ "--http2" ] )
24 |
25 | # access a ServerAlias, after using ServerName in SNI
26 | def test_h2_100_02(self, env):
27 | url = env.mkurl("https", "cgi", "/hello.py")
28 | hostname = ("cgi-alias.%s" % env.http_tld)
29 | r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
30 | assert r.response["status"] == 200
31 | assert "HTTP/2" == r.response["protocol"]
32 | assert hostname == r.response["json"]["host"]
33 |
34 | # access another vhost, after using ServerName in SNI, that uses same SSL setup
35 | def test_h2_100_03(self, env):
36 | url = env.mkurl("https", "cgi", "/")
37 | hostname = ("test1.%s" % env.http_tld)
38 | r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
39 | assert r.response["status"] == 200
40 | assert "HTTP/2" == r.response["protocol"]
41 | assert "text/html" == r.response["header"]["content-type"]
42 |
43 | # access another vhost, after using ServerName in SNI,
44 | # that has different SSL certificate. This triggers a 421 (misdirected request) response.
45 | def test_h2_100_04(self, env):
46 | url = env.mkurl("https", "cgi", "/hello.py")
47 | hostname = ("noh2.%s" % env.http_tld)
48 | r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
49 | assert 421 == r.response["status"]
50 |
51 | # access an unknown vhost, after using ServerName in SNI
52 | def test_h2_100_05(self, env):
53 | url = env.mkurl("https", "cgi", "/hello.py")
54 | hostname = ("unknown.%s" % env.http_tld)
55 | r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
56 | assert 421 == r.response["status"]
57 |
--------------------------------------------------------------------------------
/test/pyhttpd/htdocs/test1/apache.org-files/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top:60px;
3 | background-color: #fff;
4 | }
5 |
6 | h1, h2, h3, h4, h5, h6 {
7 | text-transform: uppercase;
8 | }
9 |
10 | .navbar-default .navbar-toggle {
11 | border: none;
12 | }
13 |
14 | .navbar-toggle {
15 | margin-top: 0px;
16 | margin-bottom: 0px;
17 | }
18 |
19 | section {
20 | padding-bottom: 25px;
21 | }
22 |
23 | ul.white > li > a,
24 | a.white {
25 | color: #ddd;
26 | }
27 |
28 | ul.white > li > a:hover,
29 | a.white:hover {
30 | color: #fff;
31 | }
32 |
33 | .white {
34 | color: #fff;
35 | }
36 |
37 | .bg-gray {
38 | background: #eee;
39 | }
40 |
41 | .letter-header {
42 | display: block;
43 | background-color: #f3f3f3;
44 | color: #303284;
45 | text-align: center;
46 | }
47 |
48 | .list-unstyled {
49 | list-style: none;
50 | }
51 |
52 | .no-top-margin{
53 | margin-top: 0px;
54 | }
55 |
56 | .no-btm-margin {
57 | margin-bottom: 0px;
58 | }
59 |
60 |
61 | /* Custom Left Tabs */
62 |
63 | .tabs-left > .nav-tabs {
64 | border-bottom: 0;
65 | }
66 |
67 | .tabs-left > .nav-tabs > li,
68 | .tabs-right > .nav-tabs > li {
69 | float: none;
70 | }
71 |
72 | .tabs-left > .nav-tabs > li > a,
73 | .tabs-right > .nav-tabs > li > a {
74 | min-width: 74px;
75 | margin-right: 0;
76 | margin-bottom: 3px;
77 | border: none;
78 | }
79 |
80 | .tabs-left > .nav-tabs > li > a:hover:after {
81 | content: "";
82 | position: absolute;
83 | right: -24px;
84 | top: 50%;
85 | width: 0;
86 | height: 0;
87 | border-top: 25px solid transparent;
88 | border-bottom: 25px solid transparent;
89 | border-left: 25px solid #eee;
90 | margin-top: -25px;
91 | }
92 |
93 | .tabs-left > .nav-tabs > .active > a:after,
94 | .tabs-left > .nav-tabs > .active > a:hover:after {
95 | content: "";
96 | position: absolute;
97 | right: -24px;
98 | top: 50%;
99 | width: 0;
100 | height: 0;
101 | border-top: 25px solid transparent;
102 | border-bottom: 25px solid transparent;
103 | border-left: 25px solid #303284;
104 | margin-top: -25px;
105 | }
106 |
107 |
108 | .tabs-left > .nav-tabs > li > a {
109 | margin-right: -1px;
110 | width: 90%;
111 | }
112 |
113 | .tabs-left > .nav-tabs > li > a:hover,
114 | .tabs-left > .nav-tabs > li > a:focus {
115 | border: none;
116 | }
117 |
118 | /* Media Adjustments */
119 | @media (max-width: 992px){
120 | .navbar-nav > li > a {
121 | padding-left: 5px;
122 | }
123 | }
124 |
125 | /* Prevent anchor links from displaying under the navbar */
126 | :target:before {
127 | content: "";
128 | display: block;
129 | height: 30px;
130 | margin-top: -30px;
131 | }
132 |
--------------------------------------------------------------------------------
/mod_http2/Makefile.am:
--------------------------------------------------------------------------------
1 | # Licensed under the Apache License, Version 2.0 (the "License");
2 | # you may not use this file except in compliance with the License.
3 | # You may obtain a copy of the License at
4 | #
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | #
7 | # Unless required by applicable law or agreed to in writing, software
8 | # distributed under the License is distributed on an "AS IS" BASIS,
9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | # See the License for the specific language governing permissions and
11 | # limitations under the License.
12 | #
13 | SUBDIRS =
14 |
15 | ACLOCAL_AMFLAGS = -I m4
16 | AUTOMAKE_OPTIONS = foreign
17 |
18 | lib_LTLIBRARIES = mod_http2.la mod_proxy_http2.la
19 |
20 | mod_http2_la_CPPFLAGS = -std=c99 -D_GNU_SOURCE -Werror @WERROR_CFLAGS@
21 | mod_http2_la_LDFLAGS = -module -avoid-version -export-symbols-regex http2_module
22 |
23 | mod_proxy_http2_la_CPPFLAGS = -std=c99 -D_GNU_SOURCE -Werror @WERROR_CFLAGS@
24 | mod_proxy_http2_la_LDFLAGS = -module -avoid-version -export-symbols-regex proxy_http2_module
25 |
26 | OBJECTS = \
27 | h2_bucket_beam.c \
28 | h2_bucket_eos.c \
29 | h2_c1.c \
30 | h2_c1_io.c \
31 | h2_c2.c \
32 | h2_c2_filter.c \
33 | h2_config.c \
34 | h2_conn_ctx.c \
35 | h2_headers.c \
36 | h2_mplx.c \
37 | h2_protocol.c \
38 | h2_push.c \
39 | h2_request.c \
40 | h2_session.c \
41 | h2_stream.c \
42 | h2_switch.c \
43 | h2_util.c \
44 | h2_workers.c \
45 | mod_http2.c
46 |
47 | HFILES = \
48 | h2.h \
49 | h2_bucket_beam.h \
50 | h2_bucket_eos.h \
51 | h2_c1.h \
52 | h2_c1_io.h \
53 | h2_c2.h \
54 | h2_c2_filter.h \
55 | h2_config.h \
56 | h2_conn_ctx.h \
57 | h2_headers.h \
58 | h2_mplx.h \
59 | h2_private.h \
60 | h2_protocol.h \
61 | h2_push.h \
62 | h2_request.h \
63 | h2_session.h \
64 | h2_stream.h \
65 | h2_switch.h \
66 | h2_util.h \
67 | h2_version.h \
68 | h2_workers.h \
69 | mod_http2.h
70 |
71 | PROXY_HFILES = \
72 | h2.h \
73 | h2_proxy_session.h \
74 | h2_proxy_util.h \
75 | mod_proxy_http2.h
76 |
77 | PROXY_OBJECTS = \
78 | h2_proxy_session.c \
79 | h2_proxy_util.c \
80 | mod_proxy_http2.c
81 |
82 | mod_http2_la_SOURCES = $(HFILES) $(OBJECTS)
83 |
84 | mod_proxy_http2_la_SOURCES = $(PROXY_HFILES) $(PROXY_OBJECTS)
85 |
86 | all: mod_http2.la \
87 | mod_proxy_http2.la
88 |
89 | install-libLTLIBRARIES:
90 | @: # override
91 |
92 | install-exec-local: mod_http2.la mod_proxy_http2.la
93 | $(MKDIR_P) $(DESTDIR)/@LIBEXEC_DIR@
94 | $(APXS) -i -S LIBEXECDIR=$(DESTDIR)/@LIBEXEC_DIR@ -n h2 mod_http2.la
95 | $(APXS) -i -S LIBEXECDIR=$(DESTDIR)/@LIBEXEC_DIR@ -n h2 mod_proxy_http2.la
96 |
97 |
98 |
--------------------------------------------------------------------------------
/test/modules/http2/test_202_trailer.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pytest
3 |
4 | from .env import H2Conf
5 |
6 |
7 | def setup_data(env):
8 | s100 = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
9 | with open(os.path.join(env.gen_dir, "data-1k"), 'w') as f:
10 | for i in range(10):
11 | f.write(s100)
12 |
13 |
14 | # The trailer tests depend on "nghttp" as no other client seems to be able to send those
15 | # rare things.
16 | class TestTrailers:
17 |
18 | @pytest.fixture(autouse=True, scope='class')
19 | def _class_scope(self, env):
20 | setup_data(env)
21 | H2Conf(env).add_vhost_cgi(h2proxy_self=True).install()
22 | assert env.apache_restart() == 0
23 |
24 | # check if the server survives a trailer or two
25 | def test_h2_202_01(self, env):
26 | url = env.mkurl("https", "cgi", "/echo.py")
27 | fpath = os.path.join(env.gen_dir, "data-1k")
28 | r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 1"])
29 | assert 300 > r.response["status"]
30 | assert 1000 == len(r.response["body"])
31 |
32 | r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 1b", "--trailer", "XXX: test"])
33 | assert 300 > r.response["status"]
34 | assert 1000 == len(r.response["body"])
35 |
36 | # check if the server survives a trailer without content-length
37 | def test_h2_202_02(self, env):
38 | url = env.mkurl("https", "cgi", "/echo.py")
39 | fpath = os.path.join(env.gen_dir, "data-1k")
40 | r = env.nghttp().upload(url, fpath, options=["--trailer", "test: 2", "--no-content-length"])
41 | assert 300 > r.response["status"]
42 | assert 1000 == len(r.response["body"])
43 |
44 | # check if echoing request headers in response from GET works
45 | def test_h2_202_03(self, env):
46 | url = env.mkurl("https", "cgi", "/echohd.py?name=X")
47 | r = env.nghttp().get(url, options=["--header", "X: 3"])
48 | assert 300 > r.response["status"]
49 | assert b"X: 3\n" == r.response["body"]
50 |
51 | # check if echoing request headers in response from POST works
52 | def test_h2_202_03b(self, env):
53 | url = env.mkurl("https", "cgi", "/echohd.py?name=X")
54 | r = env.nghttp().post_name(url, "Y", options=["--header", "X: 3b"])
55 | assert 300 > r.response["status"]
56 | assert b"X: 3b\n" == r.response["body"]
57 |
58 | # check if echoing request headers in response from POST works, but trailers are not seen
59 | # This is the way CGI invocation works.
60 | def test_h2_202_04(self, env):
61 | url = env.mkurl("https", "cgi", "/echohd.py?name=X")
62 | r = env.nghttp().post_name(url, "Y", options=["--header", "X: 4a", "--trailer", "X: 4b"])
63 | assert 300 > r.response["status"]
64 | assert b"X: 4a\n" == r.response["body"]
65 |
66 |
--------------------------------------------------------------------------------
/test/modules/http2/test_002_curl_basics.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestCurlBasics:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | conf = H2Conf(env)
11 | conf.add_vhost_test1()
12 | conf.add_vhost_test2()
13 | conf.install()
14 | assert env.apache_restart() == 0
15 |
16 | # check that we see the correct documents when using the test1 server name over http:
17 | def test_h2_002_01(self, env):
18 | url = env.mkurl("http", "test1", "/alive.json")
19 | r = env.curl_get(url, 5)
20 | assert r.response["status"] == 200
21 | assert "HTTP/1.1" == r.response["protocol"]
22 | assert r.response["json"]["alive"] is True
23 | assert r.response["json"]["host"] == "test1"
24 |
25 | # check that we see the correct documents when using the test1 server name over https:
26 | def test_h2_002_02(self, env):
27 | url = env.mkurl("https", "test1", "/alive.json")
28 | r = env.curl_get(url, 5)
29 | assert r.response["status"] == 200
30 | assert r.response["json"]["alive"] is True
31 | assert "test1" == r.response["json"]["host"]
32 | assert r.response["header"]["content-type"] == "application/json"
33 |
34 | # enforce HTTP/1.1
35 | def test_h2_002_03(self, env):
36 | url = env.mkurl("https", "test1", "/alive.json")
37 | r = env.curl_get(url, 5, options=["--http1.1"])
38 | assert r.response["status"] == 200
39 | assert r.response["protocol"] == "HTTP/1.1"
40 |
41 | # enforce HTTP/2
42 | def test_h2_002_04(self, env):
43 | url = env.mkurl("https", "test1", "/alive.json")
44 | r = env.curl_get(url, 5, options=["--http2"])
45 | assert r.response["status"] == 200
46 | assert r.response["protocol"] == "HTTP/2"
47 |
48 | # default is HTTP/2 on this host
49 | def test_h2_002_04b(self, env):
50 | url = env.mkurl("https", "test1", "/alive.json")
51 | r = env.curl_get(url, 5)
52 | assert r.response["status"] == 200
53 | assert r.response["protocol"] == "HTTP/2"
54 | assert r.response["json"]["host"] == "test1"
55 |
56 | # although, without ALPN, we cannot select it
57 | def test_h2_002_05(self, env):
58 | url = env.mkurl("https", "test1", "/alive.json")
59 | r = env.curl_get(url, 5, options=["--no-alpn"])
60 | assert r.response["status"] == 200
61 | assert r.response["protocol"] == "HTTP/1.1"
62 | assert r.response["json"]["host"] == "test1"
63 |
64 | # default is HTTP/1.1 on the other
65 | def test_h2_002_06(self, env):
66 | url = env.mkurl("https", "test2", "/alive.json")
67 | r = env.curl_get(url, 5)
68 | assert r.response["status"] == 200
69 | assert r.response["protocol"] == "HTTP/1.1"
70 | assert r.response["json"]["host"] == "test2"
71 |
--------------------------------------------------------------------------------
/mod_http2/h2_workers.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_workers__
18 | #define __mod_h2__h2_workers__
19 |
20 | /* Thread pool specific to executing secondary connections.
21 | * Has a minimum and maximum number of workers it creates.
22 | * Starts with minimum workers and adds some on load,
23 | * reduces the number again when idle.
24 | */
25 | struct apr_thread_mutex_t;
26 | struct apr_thread_cond_t;
27 | struct h2_mplx;
28 | struct h2_request;
29 | struct h2_fifo;
30 |
31 | struct h2_slot;
32 |
33 | typedef struct h2_workers h2_workers;
34 |
35 | struct h2_workers {
36 | server_rec *s;
37 | apr_pool_t *pool;
38 |
39 | int next_worker_id;
40 | apr_uint32_t max_workers;
41 | volatile apr_uint32_t min_workers; /* is changed during graceful shutdown */
42 | volatile apr_interval_time_t max_idle_duration; /* is changed during graceful shutdown */
43 |
44 | volatile int aborted;
45 | volatile int shutdown;
46 | int dynamic;
47 |
48 | apr_threadattr_t *thread_attr;
49 | int nslots;
50 | struct h2_slot *slots;
51 |
52 | volatile apr_uint32_t worker_count;
53 |
54 | struct h2_slot *free;
55 | struct h2_slot *idle;
56 | struct h2_slot *zombies;
57 |
58 | struct h2_fifo *mplxs;
59 |
60 | struct apr_thread_mutex_t *lock;
61 | struct apr_thread_cond_t *all_done;
62 | };
63 |
64 |
65 | /* Create a worker pool with the given minimum and maximum number of
66 | * threads.
67 | */
68 | h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
69 | int min_size, int max_size, int idle_secs);
70 |
71 | /**
72 | * Registers a h2_mplx for scheduling. If this h2_mplx runs
73 | * out of work, it will be automatically be unregistered. Should
74 | * new work arrive, it needs to be registered again.
75 | */
76 | apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m);
77 |
78 | /**
79 | * Remove a h2_mplx from the worker registry.
80 | */
81 | apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
82 |
83 | /**
84 | * Shut down processing gracefully by terminating all idle workers.
85 | */
86 | void h2_workers_graceful_shutdown(h2_workers *workers);
87 |
88 | #endif /* defined(__mod_h2__h2_workers__) */
89 |
--------------------------------------------------------------------------------
/test/modules/http2/test_006_assets.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestAssets:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_test1().install()
11 | assert env.apache_restart() == 0
12 |
13 | # single page without any assets
14 | def test_h2_006_01(self, env):
15 | url = env.mkurl("https", "test1", "/001.html")
16 | r = env.nghttp().assets(url, options=["-Haccept-encoding: none"])
17 | assert 0 == r.exit_code
18 | assert 1 == len(r.assets)
19 | assert r.assets == [
20 | {"status": 200, "size": "251", "path": "/001.html"}
21 | ]
22 |
23 | # single image without any assets
24 | def test_h2_006_02(self, env):
25 | url = env.mkurl("https", "test1", "/002.jpg")
26 | r = env.nghttp().assets(url, options=["-Haccept-encoding: none"])
27 | assert 0 == r.exit_code
28 | assert 1 == len(r.assets)
29 | assert r.assets == [
30 | {"status": 200, "size": "88K", "path": "/002.jpg"}
31 | ]
32 |
33 | # gophertiles, yea!
34 | def test_h2_006_03(self, env):
35 | # create the tiles files we originally had checked in
36 | exp_assets = [
37 | {"status": 200, "size": "10K", "path": "/004.html"},
38 | {"status": 200, "size": "742", "path": "/004/gophertiles.jpg"},
39 | ]
40 | for i in range(2, 181):
41 | with open(f"{env.server_docs_dir}/test1/004/gophertiles_{i:03d}.jpg", "w") as fd:
42 | fd.write("0123456789\n")
43 | exp_assets.append(
44 | {"status": 200, "size": "11", "path": f"/004/gophertiles_{i:03d}.jpg"},
45 | )
46 |
47 | url = env.mkurl("https", "test1", "/004.html")
48 | r = env.nghttp().assets(url, options=["-Haccept-encoding: none"])
49 | assert 0 == r.exit_code
50 | assert 181 == len(r.assets)
51 | assert r.assets == exp_assets
52 |
53 | # page with js and css
54 | def test_h2_006_04(self, env):
55 | url = env.mkurl("https", "test1", "/006.html")
56 | r = env.nghttp().assets(url, options=["-Haccept-encoding: none"])
57 | assert 0 == r.exit_code
58 | assert 3 == len(r.assets)
59 | assert r.assets == [
60 | {"status": 200, "size": "543", "path": "/006.html"},
61 | {"status": 200, "size": "216", "path": "/006/006.css"},
62 | {"status": 200, "size": "839", "path": "/006/006.js"}
63 | ]
64 |
65 | # page with image, try different window size
66 | def test_h2_006_05(self, env):
67 | url = env.mkurl("https", "test1", "/003.html")
68 | r = env.nghttp().assets(url, options=["--window-bits=24", "-Haccept-encoding: none"])
69 | assert 0 == r.exit_code
70 | assert 2 == len(r.assets)
71 | assert r.assets == [
72 | {"status": 200, "size": "316", "path": "/003.html"},
73 | {"status": 200, "size": "88K", "path": "/003/003_img.jpg"}
74 | ]
75 |
--------------------------------------------------------------------------------
/test/unit/test_h2_util.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include "test_common.h"
22 | #include "h2_util.h"
23 |
24 | /*
25 | * Helpers
26 | */
27 |
28 | /*
29 | * Test Fixture -- runs once per test
30 | */
31 |
32 | static apr_pool_t *g_pool;
33 |
34 | static void h2_util_setup(void)
35 | {
36 | if (apr_pool_create(&g_pool, NULL) != APR_SUCCESS) {
37 | exit(1);
38 | }
39 | }
40 |
41 | static void h2_util_teardown(void)
42 | {
43 | apr_pool_destroy(g_pool);
44 | }
45 |
46 | static void base64_roundtrip(const char *buf_in, size_t buf_len)
47 | {
48 | const char *buf_out, *buf64;
49 | apr_size_t out_len;
50 |
51 | buf64 = h2_util_base64url_encode(buf_in, buf_len, g_pool);
52 | ck_assert(buf64);
53 |
54 | out_len = h2_util_base64url_decode(&buf_out, buf64, g_pool);
55 |
56 | ck_assert_int_eq(buf_len, out_len);
57 | ck_assert_mem_eq(buf_in, buf_out, buf_len);
58 | }
59 |
60 | /*
61 | * Tests
62 | */
63 | START_TEST(base64_h2_util_roundtrip)
64 | {
65 | base64_roundtrip("1", 1);
66 | base64_roundtrip("12", 2);
67 | base64_roundtrip("123", 3);
68 | base64_roundtrip("1234", 4);
69 | base64_roundtrip("12345", 5);
70 | base64_roundtrip("123456", 6);
71 | base64_roundtrip("1234567", 7);
72 | base64_roundtrip("12345678", 8);
73 | base64_roundtrip("123456789", 9);
74 | }
75 | END_TEST
76 |
77 | static void largetrip(int step)
78 | {
79 | char buffer[256];
80 | int i, start;
81 |
82 | for (start = 0; start < 256; ++start) {
83 | for (i = 0; i < 256; ++i) {
84 | buffer[(start+(i*step)) % 256] = (char)i;
85 | }
86 | base64_roundtrip(buffer, 256);
87 | }
88 | }
89 |
90 | START_TEST(base64_h2_util_largetrip)
91 | {
92 | largetrip(1);
93 | largetrip(3);
94 | largetrip(5);
95 | largetrip(17);
96 | largetrip(31);
97 | largetrip(53);
98 | largetrip(101);
99 | largetrip(167);
100 | largetrip(223);
101 | }
102 | END_TEST
103 |
104 | TCase *h2_util_test_case(void)
105 | {
106 | TCase *testcase = tcase_create("h2_util");
107 |
108 | tcase_add_checked_fixture(testcase, h2_util_setup, h2_util_teardown);
109 |
110 | tcase_add_test(testcase, base64_h2_util_roundtrip);
111 | tcase_add_test(testcase, base64_h2_util_largetrip);
112 |
113 | return testcase;
114 | }
115 |
--------------------------------------------------------------------------------
/mod_http2/h2_c1.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_c1__
18 | #define __mod_h2__h2_c1__
19 |
20 | #include
21 |
22 | struct h2_conn_ctx_t;
23 |
24 | extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_in) *h2_c_logio_add_bytes_in;
25 | extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *h2_c_logio_add_bytes_out;
26 |
27 | /* Initialize this child process for h2 primary connection work,
28 | * to be called once during child init before multi processing
29 | * starts.
30 | */
31 | apr_status_t h2_c1_child_init(apr_pool_t *pool, server_rec *s);
32 |
33 | /**
34 | * Setup the primary connection and our context for HTTP/2 processing
35 | *
36 | * @param c the connection HTTP/2 is starting on
37 | * @param r the upgrade request that still awaits an answer, optional
38 | * @param s the server selected for this connection (can be != c->base_server)
39 | */
40 | apr_status_t h2_c1_setup(conn_rec *c, request_rec *r, server_rec *s);
41 |
42 | /**
43 | * Run the HTTP/2 primary connection in synchronous fashion.
44 | * Return when the HTTP/2 session is done
45 | * and the connection will close or a fatal error occurred.
46 | *
47 | * @param c the http2 connection to run
48 | * @return APR_SUCCESS when session is done.
49 | */
50 | apr_status_t h2_c1_run(conn_rec *c);
51 |
52 | /**
53 | * The primary connection is about to close. If we have not send a GOAWAY
54 | * yet, this is the last chance.
55 | */
56 | apr_status_t h2_c1_pre_close(struct h2_conn_ctx_t *ctx, conn_rec *c);
57 |
58 | /**
59 | * Check if the connection allows a direct detection of HTTPP/2,
60 | * as configurable by the H2Direct directive.
61 | * @param c the connection to check on
62 | * @return != 0 if direct detection is enabled
63 | */
64 | int h2_c1_allows_direct(conn_rec *c);
65 |
66 | /**
67 | * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
68 | * for the given request.
69 | * @param r the request to check
70 | * @return != 0 iff Upgrade switching is enabled
71 | */
72 | int h2_c1_can_upgrade(request_rec *r);
73 |
74 | /* Register hooks for h2 handling on primary connections.
75 | */
76 | void h2_c1_register_hooks(void);
77 |
78 | /**
79 | * Child is about to be stopped, release unused resources
80 | */
81 | void h2_c1_child_stopping(apr_pool_t *pool, int graceful);
82 |
83 | #endif /* defined(__mod_h2__h2_c1__) */
84 |
--------------------------------------------------------------------------------
/test/modules/http2/test_201_header_conditional.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestConditionalHeaders:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add(
11 | """
12 | KeepAlive on
13 | MaxKeepAliveRequests 30
14 | KeepAliveTimeout 30"""
15 | ).add_vhost_test1().install()
16 | assert env.apache_restart() == 0
17 |
18 | # check handling of 'if-modified-since' header
19 | def test_h2_201_01(self, env):
20 | url = env.mkurl("https", "test1", "/006/006.css")
21 | r = env.curl_get(url)
22 | assert r.response["status"] == 200
23 | lm = r.response["header"]["last-modified"]
24 | assert lm
25 | r = env.curl_get(url, options=["-H", "if-modified-since: %s" % lm])
26 | assert 304 == r.response["status"]
27 | r = env.curl_get(url, options=["-H", "if-modified-since: Tue, 04 Sep 2010 11:51:59 GMT"])
28 | assert r.response["status"] == 200
29 |
30 | # check handling of 'if-none-match' header
31 | def test_h2_201_02(self, env):
32 | url = env.mkurl("https", "test1", "/006/006.css")
33 | r = env.curl_get(url)
34 | assert r.response["status"] == 200
35 | etag = r.response["header"]["etag"]
36 | assert etag
37 | r = env.curl_get(url, options=["-H", "if-none-match: %s" % etag])
38 | assert 304 == r.response["status"]
39 | r = env.curl_get(url, options=["-H", "if-none-match: dummy"])
40 | assert r.response["status"] == 200
41 |
42 | @pytest.mark.skipif(True, reason="304 misses the Vary header in trunk and 2.4.x")
43 | def test_h2_201_03(self, env):
44 | url = env.mkurl("https", "test1", "/006.html")
45 | r = env.curl_get(url, options=["-H", "Accept-Encoding: gzip"])
46 | assert r.response["status"] == 200
47 | for h in r.response["header"]:
48 | print("%s: %s" % (h, r.response["header"][h]))
49 | lm = r.response["header"]["last-modified"]
50 | assert lm
51 | assert "gzip" == r.response["header"]["content-encoding"]
52 | assert "Accept-Encoding" in r.response["header"]["vary"]
53 |
54 | r = env.curl_get(url, options=["-H", "if-modified-since: %s" % lm,
55 | "-H", "Accept-Encoding: gzip"])
56 | assert 304 == r.response["status"]
57 | for h in r.response["header"]:
58 | print("%s: %s" % (h, r.response["header"][h]))
59 | assert "vary" in r.response["header"]
60 |
61 | # Check if "Keep-Alive" response header is removed in HTTP/2.
62 | def test_h2_201_04(self, env):
63 | url = env.mkurl("https", "test1", "/006.html")
64 | r = env.curl_get(url, options=["--http1.1", "-H", "Connection: keep-alive"])
65 | assert r.response["status"] == 200
66 | assert "timeout=30, max=30" == r.response["header"]["keep-alive"]
67 | r = env.curl_get(url, options=["-H", "Connection: keep-alive"])
68 | assert r.response["status"] == 200
69 | assert "keep-alive" not in r.response["header"]
70 |
--------------------------------------------------------------------------------
/mod_http2/h2_c1_io.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_c1_io__
18 | #define __mod_h2__h2_c1_io__
19 |
20 | struct h2_config;
21 | struct h2_session;
22 |
23 | /* h2_io is the basic handler of a httpd connection. It keeps two brigades,
24 | * one for input, one for output and works with the installed connection
25 | * filters.
26 | * The read is done via a callback function, so that input can be processed
27 | * directly without copying.
28 | */
29 | typedef struct {
30 | struct h2_session *session;
31 | apr_bucket_brigade *output;
32 |
33 | int is_tls;
34 | int unflushed;
35 | apr_time_t cooldown_usecs;
36 | apr_int64_t warmup_size;
37 |
38 | apr_size_t write_size;
39 | apr_time_t last_write;
40 | apr_int64_t bytes_read;
41 | apr_int64_t bytes_written;
42 |
43 | int buffer_output;
44 | apr_off_t buffered_len;
45 | apr_off_t flush_threshold;
46 | unsigned int is_flushed : 1;
47 |
48 | char *scratch;
49 | apr_size_t ssize;
50 | apr_size_t slen;
51 | } h2_c1_io;
52 |
53 | apr_status_t h2_c1_io_init(h2_c1_io *io, struct h2_session *session);
54 |
55 | /**
56 | * Append data to the buffered output.
57 | * @param buf the data to append
58 | * @param length the length of the data to append
59 | */
60 | apr_status_t h2_c1_io_add_data(h2_c1_io *io,
61 | const char *buf,
62 | size_t length);
63 |
64 | apr_status_t h2_c1_io_add(h2_c1_io *io, apr_bucket *b);
65 |
66 | apr_status_t h2_c1_io_append(h2_c1_io *io, apr_bucket_brigade *bb);
67 |
68 | /**
69 | * Pass any buffered data on to the connection output filters.
70 | * @param io the connection io
71 | */
72 | apr_status_t h2_c1_io_pass(h2_c1_io *io);
73 |
74 | /**
75 | * if there is any data pendiong or was any data send
76 | * since the last FLUSH, send out a FLUSH now.
77 | */
78 | apr_status_t h2_c1_io_assure_flushed(h2_c1_io *io);
79 |
80 | /**
81 | * Check if the buffered amount of data needs flushing.
82 | */
83 | int h2_c1_io_needs_flush(h2_c1_io *io);
84 |
85 | /**
86 | * Check if we have output pending.
87 | */
88 | int h2_c1_io_pending(h2_c1_io *io);
89 |
90 | struct h2_session;
91 |
92 | /**
93 | * Read c1 input and pass it on to nghttp2.
94 | * @param session the session
95 | * @param when_pending != 0 if only pending input (sitting in filters)
96 | * needs to be read
97 | */
98 | apr_status_t h2_c1_read(struct h2_session *session);
99 |
100 | #endif /* defined(__mod_h2__h2_c1_io__) */
101 |
--------------------------------------------------------------------------------
/mod_http2/h2_config.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_config_h__
18 | #define __mod_h2__h2_config_h__
19 |
20 | #undef PACKAGE_VERSION
21 | #undef PACKAGE_TARNAME
22 | #undef PACKAGE_STRING
23 | #undef PACKAGE_NAME
24 | #undef PACKAGE_BUGREPORT
25 |
26 | typedef enum {
27 | H2_CONF_MAX_STREAMS,
28 | H2_CONF_WIN_SIZE,
29 | H2_CONF_MIN_WORKERS,
30 | H2_CONF_MAX_WORKERS,
31 | H2_CONF_MAX_WORKER_IDLE_SECS,
32 | H2_CONF_STREAM_MAX_MEM,
33 | H2_CONF_DIRECT,
34 | H2_CONF_MODERN_TLS_ONLY,
35 | H2_CONF_UPGRADE,
36 | H2_CONF_TLS_WARMUP_SIZE,
37 | H2_CONF_TLS_COOLDOWN_SECS,
38 | H2_CONF_PUSH,
39 | H2_CONF_PUSH_DIARY_SIZE,
40 | H2_CONF_COPY_FILES,
41 | H2_CONF_EARLY_HINTS,
42 | H2_CONF_PADDING_BITS,
43 | H2_CONF_PADDING_ALWAYS,
44 | H2_CONF_OUTPUT_BUFFER,
45 | H2_CONF_STREAM_TIMEOUT,
46 | } h2_config_var_t;
47 |
48 | struct apr_hash_t;
49 | struct h2_priority;
50 | struct h2_push_res;
51 |
52 | typedef struct h2_push_res {
53 | const char *uri_ref;
54 | int critical;
55 | } h2_push_res;
56 |
57 |
58 | void *h2_config_create_dir(apr_pool_t *pool, char *x);
59 | void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
60 | void *h2_config_create_svr(apr_pool_t *pool, server_rec *s);
61 | void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
62 |
63 | extern const command_rec h2_cmds[];
64 |
65 | int h2_config_geti(request_rec *r, server_rec *s, h2_config_var_t var);
66 | apr_int64_t h2_config_geti64(request_rec *r, server_rec *s, h2_config_var_t var);
67 |
68 | /**
69 | * Get the configured value for variable at the given connection.
70 | */
71 | int h2_config_cgeti(conn_rec *c, h2_config_var_t var);
72 | apr_int64_t h2_config_cgeti64(conn_rec *c, h2_config_var_t var);
73 |
74 | /**
75 | * Get the configured value for variable at the given server.
76 | */
77 | int h2_config_sgeti(server_rec *s, h2_config_var_t var);
78 | apr_int64_t h2_config_sgeti64(server_rec *s, h2_config_var_t var);
79 |
80 | /**
81 | * Get the configured value for variable at the given request,
82 | * if configured for the request location.
83 | * Fallback to request server config otherwise.
84 | */
85 | int h2_config_rgeti(request_rec *r, h2_config_var_t var);
86 | apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
87 |
88 | apr_array_header_t *h2_config_push_list(request_rec *r);
89 |
90 |
91 | void h2_get_num_workers(server_rec *s, int *minw, int *maxw);
92 | void h2_config_init(apr_pool_t *pool);
93 |
94 | const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type);
95 |
96 | #endif /* __mod_h2__h2_config_h__ */
97 |
98 |
--------------------------------------------------------------------------------
/mod_http2/h2_bucket_eos.c:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #include
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "h2_private.h"
26 | #include "h2.h"
27 | #include "h2_mplx.h"
28 | #include "h2_stream.h"
29 | #include "h2_bucket_eos.h"
30 |
31 | typedef struct {
32 | apr_bucket_refcount refcount;
33 | h2_stream *stream;
34 | } h2_bucket_eos;
35 |
36 | static apr_status_t bucket_cleanup(void *data)
37 | {
38 | h2_stream **pstream = data;
39 |
40 | if (*pstream) {
41 | /* If bucket_destroy is called after us, this prevents
42 | * bucket_destroy from trying to destroy the stream again. */
43 | *pstream = NULL;
44 | }
45 | return APR_SUCCESS;
46 | }
47 |
48 | static apr_status_t bucket_read(apr_bucket *b, const char **str,
49 | apr_size_t *len, apr_read_type_e block)
50 | {
51 | (void)b;
52 | (void)block;
53 | *str = NULL;
54 | *len = 0;
55 | return APR_SUCCESS;
56 | }
57 |
58 | apr_bucket *h2_bucket_eos_make(apr_bucket *b, h2_stream *stream)
59 | {
60 | h2_bucket_eos *h;
61 |
62 | h = apr_bucket_alloc(sizeof(*h), b->list);
63 | h->stream = stream;
64 |
65 | b = apr_bucket_shared_make(b, h, 0, 0);
66 | b->type = &h2_bucket_type_eos;
67 |
68 | return b;
69 | }
70 |
71 | apr_bucket *h2_bucket_eos_create(apr_bucket_alloc_t *list,
72 | h2_stream *stream)
73 | {
74 | apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
75 |
76 | APR_BUCKET_INIT(b);
77 | b->free = apr_bucket_free;
78 | b->list = list;
79 | b = h2_bucket_eos_make(b, stream);
80 | if (stream) {
81 | h2_bucket_eos *h = b->data;
82 | apr_pool_pre_cleanup_register(stream->pool, &h->stream, bucket_cleanup);
83 | }
84 | return b;
85 | }
86 |
87 | static void bucket_destroy(void *data)
88 | {
89 | h2_bucket_eos *h = data;
90 |
91 | if (apr_bucket_shared_destroy(h)) {
92 | h2_stream *stream = h->stream;
93 | if (stream && stream->pool) {
94 | apr_pool_cleanup_kill(stream->pool, &h->stream, bucket_cleanup);
95 | }
96 | apr_bucket_free(h);
97 | if (stream) {
98 | h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
99 | }
100 | }
101 | }
102 |
103 | const apr_bucket_type_t h2_bucket_type_eos = {
104 | "H2EOS", 5, APR_BUCKET_METADATA,
105 | bucket_destroy,
106 | bucket_read,
107 | apr_bucket_setaside_noop,
108 | apr_bucket_split_notimpl,
109 | apr_bucket_shared_copy
110 | };
111 |
112 |
--------------------------------------------------------------------------------
/m4/ax_check_compile_flag.m4:
--------------------------------------------------------------------------------
1 | # ===========================================================================
2 | # http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
3 | # ===========================================================================
4 | #
5 | # SYNOPSIS
6 | #
7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
8 | #
9 | # DESCRIPTION
10 | #
11 | # Check whether the given FLAG works with the current language's compiler
12 | # or gives an error. (Warnings, however, are ignored)
13 | #
14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
15 | # success/failure.
16 | #
17 | # If EXTRA-FLAGS is defined, it is added to the current language's default
18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with
19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
20 | # force the compiler to issue an error when a bad flag is given.
21 | #
22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE.
23 | #
24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
26 | #
27 | # LICENSE
28 | #
29 | # Copyright (c) 2008 Guido U. Draheim
30 | # Copyright (c) 2011 Maarten Bosmans
31 | #
32 | # This program is free software: you can redistribute it and/or modify it
33 | # under the terms of the GNU General Public License as published by the
34 | # Free Software Foundation, either version 3 of the License, or (at your
35 | # option) any later version.
36 | #
37 | # This program is distributed in the hope that it will be useful, but
38 | # WITHOUT ANY WARRANTY; without even the implied warranty of
39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
40 | # Public License for more details.
41 | #
42 | # You should have received a copy of the GNU General Public License along
43 | # with this program. If not, see .
44 | #
45 | # As a special exception, the respective Autoconf Macro's copyright owner
46 | # gives unlimited permission to copy, distribute and modify the configure
47 | # scripts that are the output of Autoconf when processing the Macro. You
48 | # need not follow the terms of the GNU General Public License when using
49 | # or distributing such scripts, even though portions of the text of the
50 | # Macro appear in them. The GNU General Public License (GPL) does govern
51 | # all other use of the material that constitutes the Autoconf Macro.
52 | #
53 | # This special exception to the GPL applies to versions of the Autoconf
54 | # Macro released by the Autoconf Archive. When you make and distribute a
55 | # modified version of the Autoconf Macro, you may extend this special
56 | # exception to the GPL to apply to your modified version as well.
57 |
58 | #serial 3
59 |
60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG],
61 | [AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
63 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
64 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
65 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
66 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
67 | [AS_VAR_SET(CACHEVAR,[yes])],
68 | [AS_VAR_SET(CACHEVAR,[no])])
69 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
70 | AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
71 | [m4_default([$2], :)],
72 | [m4_default([$3], :)])
73 | AS_VAR_POPDEF([CACHEVAR])dnl
74 | ])dnl AX_CHECK_COMPILE_FLAGS
75 |
--------------------------------------------------------------------------------
/mod_http2/h2_headers.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_headers__
18 | #define __mod_h2__h2_headers__
19 |
20 | #include "h2.h"
21 |
22 | struct h2_bucket_beam;
23 |
24 | extern const apr_bucket_type_t h2_bucket_type_headers;
25 |
26 | #define H2_BUCKET_IS_HEADERS(e) (e->type == &h2_bucket_type_headers)
27 |
28 | apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r);
29 |
30 | apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list,
31 | h2_headers *r);
32 |
33 | h2_headers *h2_bucket_headers_get(apr_bucket *b);
34 |
35 | apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
36 | apr_bucket_brigade *dest,
37 | const apr_bucket *src);
38 |
39 | /**
40 | * Create the headers from the given status and headers
41 | * @param status the headers status
42 | * @param header the headers of the headers
43 | * @param notes the notes carried by the headers
44 | * @param raw_bytes the raw network bytes (if known) used to transmit these
45 | * @param pool the memory pool to use
46 | */
47 | h2_headers *h2_headers_create(int status, const apr_table_t *header,
48 | const apr_table_t *notes, apr_off_t raw_bytes,
49 | apr_pool_t *pool);
50 |
51 | /**
52 | * Create the headers from the given request_rec.
53 | * @param r the request record which was processed
54 | * @param status the headers status
55 | * @param header the headers of the headers
56 | * @param pool the memory pool to use
57 | */
58 | h2_headers *h2_headers_rcreate(request_rec *r, int status,
59 | const apr_table_t *header, apr_pool_t *pool);
60 |
61 | /**
62 | * Copy the headers into another pool. This will not copy any
63 | * header strings.
64 | */
65 | h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h);
66 |
67 | /**
68 | * Clone the headers into another pool. This will also clone any
69 | * header strings.
70 | */
71 | h2_headers *h2_headers_clone(apr_pool_t *pool, h2_headers *h);
72 |
73 | /**
74 | * Create the headers for the given error.
75 | * @param type the error code
76 | * @param req the original h2_request
77 | * @param pool the memory pool to use
78 | */
79 | h2_headers *h2_headers_die(apr_status_t type,
80 | const struct h2_request *req, apr_pool_t *pool);
81 |
82 | int h2_headers_are_final_response(h2_headers *headers);
83 |
84 | /**
85 | * Give the number of bytes of all contained header strings.
86 | */
87 | apr_size_t h2_headers_length(h2_headers *headers);
88 |
89 | /**
90 | * For H2HEADER buckets, return the length of all contained header strings.
91 | * For all other buckets, return 0.
92 | */
93 | apr_size_t h2_bucket_headers_headers_length(apr_bucket *b);
94 |
95 | #endif /* defined(__mod_h2__h2_headers__) */
96 |
--------------------------------------------------------------------------------
/test/modules/http2/data/nghttp-output-1k-1.txt:
--------------------------------------------------------------------------------
1 | stderr: [WARNING] Certificate verification failed: unable to verify the first certificate
2 |
3 | stdout: [ 0.002] Connected
4 | The negotiated protocol: h2
5 | [ 0.004] send SETTINGS frame
6 | (niv=2)
7 | [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
8 | [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
9 | [ 0.004] send PRIORITY frame
10 | (dep_stream_id=0, weight=201, exclusive=0)
11 | [ 0.004] send PRIORITY frame
12 | (dep_stream_id=0, weight=101, exclusive=0)
13 | [ 0.004] send PRIORITY frame
14 | (dep_stream_id=0, weight=1, exclusive=0)
15 | [ 0.004] send PRIORITY frame
16 | (dep_stream_id=7, weight=1, exclusive=0)
17 | [ 0.004] send PRIORITY frame
18 | (dep_stream_id=3, weight=1, exclusive=0)
19 | [ 0.004] send HEADERS frame
20 | ; END_HEADERS | PRIORITY
21 | (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
22 | ; Open new stream
23 | :method: POST
24 | :path: //echo.py
25 | :scheme: https
26 | :authority: 127.0.0.1:42002
27 | accept: */*
28 | accept-encoding: gzip, deflate
29 | user-agent: nghttp2/1.33.0
30 | content-length: 1000
31 | host: cgi.tests.httpd.apache.org:42002
32 | [ 0.004] send DATA frame
33 | ; END_STREAM
34 | [ 0.005] recv SETTINGS frame
35 | (niv=1)
36 | [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
37 | [ 0.005] recv SETTINGS frame
38 | ; ACK
39 | (niv=0)
40 | [ 0.005] recv WINDOW_UPDATE frame
41 | (window_size_increment=2147418112)
42 | [ 0.005] send SETTINGS frame
43 | ; ACK
44 | (niv=0)
45 | [ 0.048] recv (stream_id=13) :status: 200
46 | [ 0.048] recv (stream_id=13) date: Thu, 14 Feb 2019 11:48:18 GMT
47 | [ 0.048] recv (stream_id=13) server: Apache/2.4.39-dev (Unix) OpenSSL/1.1.1
48 | [ 0.048] recv (stream_id=13) content-type: application/data
49 | [ 0.048] recv HEADERS frame
50 | ; END_HEADERS
51 | (padlen=0)
52 | ; First response header
53 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
54 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
55 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
56 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
57 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
58 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
59 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
60 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
61 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
62 | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
63 | [ 0.048] recv DATA frame
64 | [ 0.048] recv DATA frame
65 | ; END_STREAM
66 | [ 0.048] send GOAWAY frame
67 | (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
68 |
69 |
--------------------------------------------------------------------------------
/mod_http2/mod_http2.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __MOD_HTTP2_H__
18 | #define __MOD_HTTP2_H__
19 |
20 | /** The http2_var_lookup() optional function retrieves HTTP2 environment
21 | * variables. */
22 | APR_DECLARE_OPTIONAL_FN(char *,
23 | http2_var_lookup, (apr_pool_t *, server_rec *,
24 | conn_rec *, request_rec *, char *));
25 |
26 | /** An optional function which returns non-zero if the given connection
27 | * or its master connection is using HTTP/2. */
28 | APR_DECLARE_OPTIONAL_FN(int,
29 | http2_is_h2, (conn_rec *));
30 |
31 |
32 | /*******************************************************************************
33 | * START HTTP/2 request engines (DEPRECATED)
34 | ******************************************************************************/
35 |
36 | /* The following functions were introduced for the experimental mod_proxy_http2
37 | * support, but have been abandoned since.
38 | * They are still declared here for backward compatibility, in case someone
39 | * tries to build an old mod_proxy_http2 against it, but will disappear
40 | * completely sometime in the future.
41 | */
42 |
43 | struct apr_thread_cond_t;
44 | typedef struct h2_req_engine h2_req_engine;
45 | typedef void http2_output_consumed(void *ctx, conn_rec *c, apr_off_t consumed);
46 |
47 | typedef apr_status_t http2_req_engine_init(h2_req_engine *engine,
48 | const char *id,
49 | const char *type,
50 | apr_pool_t *pool,
51 | apr_size_t req_buffer_size,
52 | request_rec *r,
53 | http2_output_consumed **pconsumed,
54 | void **pbaton);
55 |
56 | APR_DECLARE_OPTIONAL_FN(apr_status_t,
57 | http2_req_engine_push, (const char *engine_type,
58 | request_rec *r,
59 | http2_req_engine_init *einit));
60 |
61 | APR_DECLARE_OPTIONAL_FN(apr_status_t,
62 | http2_req_engine_pull, (h2_req_engine *engine,
63 | apr_read_type_e block,
64 | int capacity,
65 | request_rec **pr));
66 | APR_DECLARE_OPTIONAL_FN(void,
67 | http2_req_engine_done, (h2_req_engine *engine,
68 | conn_rec *rconn,
69 | apr_status_t status));
70 |
71 | APR_DECLARE_OPTIONAL_FN(void,
72 | http2_get_num_workers, (server_rec *s,
73 | int *minw, int *max));
74 |
75 | /*******************************************************************************
76 | * END HTTP/2 request engines (DEPRECATED)
77 | ******************************************************************************/
78 |
79 | #endif
80 |
--------------------------------------------------------------------------------
/test/modules/http2/test_104_padding.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | def frame_padding(payload, padbits):
7 | mask = (1 << padbits) - 1
8 | return ((payload + 9 + mask) & ~mask) - (payload + 9)
9 |
10 |
11 | class TestPadding:
12 |
13 | @pytest.fixture(autouse=True, scope='class')
14 | def _class_scope(self, env):
15 | conf = H2Conf(env)
16 | conf.start_vhost(domains=[f"ssl.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
17 | conf.add("AddHandler cgi-script .py")
18 | conf.end_vhost()
19 | conf.start_vhost(domains=[f"pad0.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
20 | conf.add("H2Padding 0")
21 | conf.add("AddHandler cgi-script .py")
22 | conf.end_vhost()
23 | conf.start_vhost(domains=[f"pad1.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
24 | conf.add("H2Padding 1")
25 | conf.add("AddHandler cgi-script .py")
26 | conf.end_vhost()
27 | conf.start_vhost(domains=[f"pad2.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
28 | conf.add("H2Padding 2")
29 | conf.add("AddHandler cgi-script .py")
30 | conf.end_vhost()
31 | conf.start_vhost(domains=[f"pad3.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
32 | conf.add("H2Padding 3")
33 | conf.add("AddHandler cgi-script .py")
34 | conf.end_vhost()
35 | conf.start_vhost(domains=[f"pad8.{env.http_tld}"], port=env.https_port, doc_root="htdocs/cgi")
36 | conf.add("H2Padding 8")
37 | conf.add("AddHandler cgi-script .py")
38 | conf.end_vhost()
39 | conf.install()
40 | assert env.apache_restart() == 0
41 |
42 | # default paddings settings: 0 bits
43 | def test_h2_104_01(self, env):
44 | url = env.mkurl("https", "ssl", "/echo.py")
45 | # we get 2 frames back: one with data and an empty one with EOF
46 | # check the number of padding bytes is as expected
47 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
48 | r = env.nghttp().post_data(url, data, 5)
49 | assert r.response["status"] == 200
50 | assert r.results["paddings"] == [
51 | frame_padding(len(data)+1, 0),
52 | frame_padding(0, 0)
53 | ]
54 |
55 | # 0 bits of padding
56 | def test_h2_104_02(self, env):
57 | url = env.mkurl("https", "pad0", "/echo.py")
58 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
59 | r = env.nghttp().post_data(url, data, 5)
60 | assert r.response["status"] == 200
61 | assert r.results["paddings"] == [0, 0]
62 |
63 | # 1 bit of padding
64 | def test_h2_104_03(self, env):
65 | url = env.mkurl("https", "pad1", "/echo.py")
66 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
67 | r = env.nghttp().post_data(url, data, 5)
68 | assert r.response["status"] == 200
69 | for i in r.results["paddings"]:
70 | assert i in range(0, 2)
71 |
72 | # 2 bits of padding
73 | def test_h2_104_04(self, env):
74 | url = env.mkurl("https", "pad2", "/echo.py")
75 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
76 | r = env.nghttp().post_data(url, data, 5)
77 | assert r.response["status"] == 200
78 | for i in r.results["paddings"]:
79 | assert i in range(0, 4)
80 |
81 | # 3 bits of padding
82 | def test_h2_104_05(self, env):
83 | url = env.mkurl("https", "pad3", "/echo.py")
84 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
85 | r = env.nghttp().post_data(url, data, 5)
86 | assert r.response["status"] == 200
87 | for i in r.results["paddings"]:
88 | assert i in range(0, 8)
89 |
90 | # 8 bits of padding
91 | def test_h2_104_06(self, env):
92 | url = env.mkurl("https", "pad8", "/echo.py")
93 | for data in ["x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", "xxxxxxx", "xxxxxxxx"]:
94 | r = env.nghttp().post_data(url, data, 5)
95 | assert r.response["status"] == 200
96 | for i in r.results["paddings"]:
97 | assert i in range(0, 256)
98 |
--------------------------------------------------------------------------------
/mod_http2/h2_conn_ctx.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_conn_ctx__
18 | #define __mod_h2__h2_conn_ctx__
19 |
20 | struct h2_session;
21 | struct h2_stream;
22 | struct h2_mplx;
23 | struct h2_bucket_beam;
24 | struct h2_response_parser;
25 |
26 | #define H2_PIPE_OUT 0
27 | #define H2_PIPE_IN 1
28 |
29 | /**
30 | * The h2 module context associated with a connection.
31 | *
32 | * It keeps track of the different types of connections:
33 | * - those from clients that use HTTP/2 protocol
34 | * - those from clients that do not use HTTP/2
35 | * - those created by ourself to perform work on HTTP/2 streams
36 | */
37 | struct h2_conn_ctx_t {
38 | const char *id; /* c*: our identifier of this connection */
39 | server_rec *server; /* c*: httpd server selected. */
40 | const char *protocol; /* c1: the protocol negotiated */
41 | struct h2_session *session; /* c1: the h2 session established */
42 | struct h2_mplx *mplx; /* c2: the multiplexer */
43 |
44 | int pre_conn_done; /* has pre_connection setup run? */
45 | int stream_id; /* c1: 0, c2: stream id processed */
46 | apr_pool_t *req_pool; /* c2: a c2 child pool for a request */
47 | const struct h2_request *request; /* c2: the request to process */
48 | struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
49 | struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
50 |
51 | apr_pool_t *mplx_pool; /* c2: an mplx child pool for safe use inside mplx lock */
52 | apr_file_t *pipe_in_prod[2]; /* c2: input produced notification pipe */
53 | apr_file_t *pipe_in_drain[2]; /* c2: input drained notification pipe */
54 | apr_file_t *pipe_out_prod[2]; /* c2: output produced notification pipe */
55 |
56 | apr_pollfd_t pfd_in_drain; /* c2: poll pipe_in_drain output */
57 | apr_pollfd_t pfd_out_prod; /* c2: poll pipe_out_prod output */
58 |
59 | int has_final_response; /* final HTTP response passed on out */
60 | apr_status_t last_err; /* APR_SUCCES or last error encountered in filters */
61 | struct h2_response_parser *parser; /* optional parser to catch H1 responses */
62 |
63 | volatile int done; /* c2: processing has finished */
64 | apr_time_t started_at; /* c2: when processing started */
65 | apr_time_t done_at; /* c2: when processing was done */
66 | };
67 | typedef struct h2_conn_ctx_t h2_conn_ctx_t;
68 |
69 | /**
70 | * Get the h2 connection context.
71 | * @param c the connection to look at
72 | * @return h2 context of this connection
73 | */
74 | #define h2_conn_ctx_get(c) \
75 | ((c)? (h2_conn_ctx_t*)ap_get_module_config((c)->conn_config, &http2_module) : NULL)
76 |
77 | /**
78 | * Create the h2 connection context.
79 | * @param c the connection to create it at
80 | * @param s the server in use
81 | * @param protocol the procotol selected
82 | * @return created h2 context of this connection
83 | */
84 | h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c, server_rec *s, const char *protocol);
85 |
86 | apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c,
87 | struct h2_mplx *mplx, struct h2_stream *stream);
88 |
89 | void h2_conn_ctx_clear_for_c2(conn_rec *c2);
90 |
91 | void h2_conn_ctx_detach(conn_rec *c);
92 |
93 | void h2_conn_ctx_destroy(conn_rec *c);
94 |
95 | void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout);
96 |
97 | #endif /* defined(__mod_h2__h2_conn_ctx__) */
98 |
--------------------------------------------------------------------------------
/test/modules/http2/test_103_upgrade.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from .env import H2Conf
4 |
5 |
6 | class TestUpgrade:
7 |
8 | @pytest.fixture(autouse=True, scope='class')
9 | def _class_scope(self, env):
10 | H2Conf(env).add_vhost_test1().add_vhost_test2().add_vhost_noh2(
11 | ).start_vhost(domains=[f"test3.{env.http_tld}"], port=env.https_port, doc_root="htdocs/test1"
12 | ).add(
13 | """
14 | Protocols h2 http/1.1
15 | Header unset Upgrade"""
16 | ).end_vhost(
17 | ).start_vhost(domains=[f"test1b.{env.http_tld}"], port=env.http_port, doc_root="htdocs/test1"
18 | ).add(
19 | """
20 | Protocols h2c http/1.1
21 | H2Upgrade off
22 |
23 | H2Upgrade on
24 | """
25 | ).end_vhost(
26 | ).install()
27 | assert env.apache_restart() == 0
28 |
29 | # accessing http://test1, will not try h2 and advertise h2 in the response
30 | def test_h2_103_01(self, env):
31 | url = env.mkurl("http", "test1", "/index.html")
32 | r = env.curl_get(url)
33 | assert 0 == r.exit_code
34 | assert r.response
35 | assert "upgrade" in r.response["header"]
36 | assert "h2c" == r.response["header"]["upgrade"]
37 |
38 | # accessing http://noh2, will not advertise, because noh2 host does not have it enabled
39 | def test_h2_103_02(self, env):
40 | url = env.mkurl("http", "noh2", "/index.html")
41 | r = env.curl_get(url)
42 | assert 0 == r.exit_code
43 | assert r.response
44 | assert "upgrade" not in r.response["header"]
45 |
46 | # accessing http://test2, will not advertise, because h2 has less preference than http/1.1
47 | def test_h2_103_03(self, env):
48 | url = env.mkurl("http", "test2", "/index.html")
49 | r = env.curl_get(url)
50 | assert 0 == r.exit_code
51 | assert r.response
52 | assert "upgrade" not in r.response["header"]
53 |
54 | # accessing https://noh2, will not advertise, because noh2 host does not have it enabled
55 | def test_h2_103_04(self, env):
56 | url = env.mkurl("https", "noh2", "/index.html")
57 | r = env.curl_get(url)
58 | assert 0 == r.exit_code
59 | assert r.response
60 | assert "upgrade" not in r.response["header"]
61 |
62 | # accessing https://test2, will not advertise, because h2 has less preference than http/1.1
63 | def test_h2_103_05(self, env):
64 | url = env.mkurl("https", "test2", "/index.html")
65 | r = env.curl_get(url)
66 | assert 0 == r.exit_code
67 | assert r.response
68 | assert "upgrade" not in r.response["header"]
69 |
70 | # accessing https://test1, will advertise h2 in the response
71 | def test_h2_103_06(self, env):
72 | url = env.mkurl("https", "test1", "/index.html")
73 | r = env.curl_get(url, options=["--http1.1"])
74 | assert 0 == r.exit_code
75 | assert r.response
76 | assert "upgrade" in r.response["header"]
77 | assert "h2" == r.response["header"]["upgrade"]
78 |
79 | # accessing https://test3, will not send Upgrade since it is suppressed
80 | def test_h2_103_07(self, env):
81 | url = env.mkurl("https", "test3", "/index.html")
82 | r = env.curl_get(url, options=["--http1.1"])
83 | assert 0 == r.exit_code
84 | assert r.response
85 | assert "upgrade" not in r.response["header"]
86 |
87 | # upgrade to h2c for a request, where h2c is preferred
88 | def test_h2_103_20(self, env):
89 | url = env.mkurl("http", "test1", "/index.html")
90 | r = env.nghttp().get(url, options=["-u"])
91 | assert r.response["status"] == 200
92 |
93 | # upgrade to h2c for a request where http/1.1 is preferred, but the clients upgrade
94 | # wish is honored nevertheless
95 | def test_h2_103_21(self, env):
96 | url = env.mkurl("http", "test2", "/index.html")
97 | r = env.nghttp().get(url, options=["-u"])
98 | assert 404 == r.response["status"]
99 |
100 | # ugrade to h2c on a host where h2c is not enabled will fail
101 | def test_h2_103_22(self, env):
102 | url = env.mkurl("http", "noh2", "/index.html")
103 | r = env.nghttp().get(url, options=["-u"])
104 | assert not r.response
105 |
106 | # ugrade to h2c on a host where h2c is preferred, but Upgrade is disabled
107 | def test_h2_103_23(self, env):
108 | url = env.mkurl("http", "test1b", "/index.html")
109 | r = env.nghttp().get(url, options=["-u"])
110 | assert not r.response
111 |
112 | # ugrade to h2c on a host where h2c is preferred, but Upgrade is disabled on the server,
113 | # but allowed for a specific location
114 | def test_h2_103_24(self, env):
115 | url = env.mkurl("http", "test1b", "/006.html")
116 | r = env.nghttp().get(url, options=["-u"])
117 | assert r.response["status"] == 200
118 |
--------------------------------------------------------------------------------
/mod_http2/h2_conn_ctx.c:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #include
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "h2_private.h"
26 | #include "h2_session.h"
27 | #include "h2_bucket_beam.h"
28 | #include "h2_c2.h"
29 | #include "h2_mplx.h"
30 | #include "h2_stream.h"
31 | #include "h2_util.h"
32 | #include "h2_conn_ctx.h"
33 |
34 |
35 | void h2_conn_ctx_detach(conn_rec *c)
36 | {
37 | ap_set_module_config(c->conn_config, &http2_module, NULL);
38 | }
39 |
40 | static h2_conn_ctx_t *ctx_create(conn_rec *c, const char *id)
41 | {
42 | h2_conn_ctx_t *conn_ctx = apr_pcalloc(c->pool, sizeof(*conn_ctx));
43 | conn_ctx->id = id;
44 | conn_ctx->server = c->base_server;
45 | conn_ctx->started_at = apr_time_now();
46 |
47 | ap_set_module_config(c->conn_config, &http2_module, conn_ctx);
48 | return conn_ctx;
49 | }
50 |
51 | h2_conn_ctx_t *h2_conn_ctx_create_for_c1(conn_rec *c1, server_rec *s, const char *protocol)
52 | {
53 | h2_conn_ctx_t *ctx;
54 |
55 | ctx = ctx_create(c1, apr_psprintf(c1->pool, "%ld", c1->id));
56 | ctx->server = s;
57 | ctx->protocol = apr_pstrdup(c1->pool, protocol);
58 |
59 | ctx->pfd_out_prod.desc_type = APR_POLL_SOCKET;
60 | ctx->pfd_out_prod.desc.s = ap_get_conn_socket(c1);
61 | apr_socket_opt_set(ctx->pfd_out_prod.desc.s, APR_SO_NONBLOCK, 1);
62 | ctx->pfd_out_prod.reqevents = APR_POLLIN | APR_POLLERR | APR_POLLHUP;
63 | ctx->pfd_out_prod.client_data = ctx;
64 |
65 | return ctx;
66 | }
67 |
68 | apr_status_t h2_conn_ctx_init_for_c2(h2_conn_ctx_t **pctx, conn_rec *c2,
69 | struct h2_mplx *mplx, struct h2_stream *stream)
70 | {
71 | h2_conn_ctx_t *conn_ctx;
72 | apr_status_t rv = APR_SUCCESS;
73 |
74 | ap_assert(c2->master);
75 | conn_ctx = h2_conn_ctx_get(c2);
76 | if (!conn_ctx) {
77 | h2_conn_ctx_t *c1_ctx;
78 |
79 | c1_ctx = h2_conn_ctx_get(c2->master);
80 | ap_assert(c1_ctx);
81 | ap_assert(c1_ctx->session);
82 |
83 | conn_ctx = ctx_create(c2, c1_ctx->id);
84 | conn_ctx->server = c2->master->base_server;
85 | }
86 |
87 | conn_ctx->mplx = mplx;
88 | conn_ctx->stream_id = stream->id;
89 | apr_pool_create(&conn_ctx->req_pool, c2->pool);
90 | apr_pool_tag(conn_ctx->req_pool, "H2_C2_REQ");
91 | conn_ctx->request = stream->request;
92 | conn_ctx->started_at = apr_time_now();
93 | conn_ctx->done = 0;
94 | conn_ctx->done_at = 0;
95 |
96 | *pctx = conn_ctx;
97 | return rv;
98 | }
99 |
100 | void h2_conn_ctx_clear_for_c2(conn_rec *c2)
101 | {
102 | h2_conn_ctx_t *conn_ctx;
103 |
104 | ap_assert(c2->master);
105 | conn_ctx = h2_conn_ctx_get(c2);
106 | conn_ctx->stream_id = -1;
107 | conn_ctx->request = NULL;
108 |
109 | if (conn_ctx->req_pool) {
110 | apr_pool_destroy(conn_ctx->req_pool);
111 | conn_ctx->req_pool = NULL;
112 | conn_ctx->beam_out = NULL;
113 | }
114 | memset(&conn_ctx->pfd_in_drain, 0, sizeof(conn_ctx->pfd_in_drain));
115 | memset(&conn_ctx->pfd_out_prod, 0, sizeof(conn_ctx->pfd_out_prod));
116 | conn_ctx->beam_in = NULL;
117 | }
118 |
119 | void h2_conn_ctx_destroy(conn_rec *c)
120 | {
121 | h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
122 |
123 | if (conn_ctx) {
124 | if (conn_ctx->mplx_pool) {
125 | apr_pool_destroy(conn_ctx->mplx_pool);
126 | conn_ctx->mplx_pool = NULL;
127 | }
128 | ap_set_module_config(c->conn_config, &http2_module, NULL);
129 | }
130 | }
131 |
132 | void h2_conn_ctx_set_timeout(h2_conn_ctx_t *conn_ctx, apr_interval_time_t timeout)
133 | {
134 | if (conn_ctx->beam_out) {
135 | h2_beam_timeout_set(conn_ctx->beam_out, timeout);
136 | }
137 | if (conn_ctx->pipe_out_prod[H2_PIPE_OUT]) {
138 | apr_file_pipe_timeout_set(conn_ctx->pipe_out_prod[H2_PIPE_OUT], timeout);
139 | }
140 |
141 | if (conn_ctx->beam_in) {
142 | h2_beam_timeout_set(conn_ctx->beam_in, timeout);
143 | }
144 | if (conn_ctx->pipe_in_prod[H2_PIPE_OUT]) {
145 | apr_file_pipe_timeout_set(conn_ctx->pipe_in_prod[H2_PIPE_OUT], timeout);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # mod_h[ttp]2 - http/2 for Apache httpd
3 |
4 | This repository contains `mod_h[ttp]2` and `mod_proxy_h[ttp]2` from Apache httpd as a standalone build. It servers as early access to features and fixes before being shipped in the next Apache release. Both modules can be considered **production ready** and stable as shipped by the Apache project.
5 |
6 | ## Status
7 |
8 | **`mod_h[ttp]2` is an official Apache httpd module** since release 2.4.17. `mod_proxy_h[ttp]2` has been added in Apache in 2.4.23. The versions here at github are for more frequent releases than the Apache schedule provides for.
9 |
10 | ## Thanks
11 |
12 | The following beautiful people have directly contributed to this project via commits over the years:
13 | Julian Reschke, Lubos Uhliarik, Luca Toscano, MATSUMOTO Ryosuke,
14 | Michael Kaufmann, Michael Köller, Mike Frysinger, Nicholas Race,
15 | Nicolas Noble, Petri Koistinen, Sam Hurst, Tatsuhiro Tsujikawa.
16 |
17 | ## Install
18 |
19 | You need a built Apache httpd 2.4.34 or newer, including apxs and headers to compile and
20 | run this module. Additionally, you need an installed libnghttp2, at least in version
21 | 1.7.0. And additionally, you want an installed OpenSSL 1.0.2 or later.
22 |
23 | tl;dr
24 |
25 | **You need an installed recent Apache 2.4.x**
26 |
27 | ## Apache 2.4.x Packages
28 |
29 | * **Ubuntu**: [ppa by ondrej](https://launchpad.net/~ondrej/+archive/ubuntu/apache2) for Ubuntu 14.04 and others
30 | * **Fedora**: [shipped in Fedora 23 and later](https://bodhi.fedoraproject.org/updates/?packages=httpd)
31 | * **Debian** sid (unstable) [how to install debian sid](https://wiki.debian.org/InstallFAQ#Q._How_do_I_install_.22unstable.22_.28.22sid.22.29.3F)
32 | * **Gentoo**: [latest stable](https://packages.gentoo.org/packages/www-servers/apache)
33 | * **FreeBSD**: [Apache 2.4 port includes mod_http2](http://www.freshports.org/www/apache24/) / [mod_http2 port](http://www.freshports.org/www/mod_http2/)
34 |
35 | ## Changes
36 |
37 | See ```ChangeLog``` for details.
38 |
39 | ## Tests
40 |
41 | I decided to make the test suite part of this repository again. The existing suite resides
42 | in test Apache httpd test repository and is a set of shell scripts. It works, but I miss
43 | features that professional test frameworks bring. The tests included here use ```python3``` and ```pytest``` which I think is an excellent way to do tests. I use it also in my Let's Encrypt module ```mod_md```.
44 |
45 | You can build the module without having the test prerequisites. If you want to run them, however, you need ```pytest```, ```python3``` and a ```curl``` with http2 support. Then you can
46 |
47 | ```
48 | > make
49 | > make test
50 | ```
51 |
52 |
53 | ## `mod_proxy_http2`
54 |
55 | This module is part of the Apache httpd proxy architecture and functions similar to `mod_proxy_http`
56 | and friends. To configure it, you need to use ```h2:``` or ```h2c:``` in the proxy URL. Example:
57 |
58 | ```
59 |
60 | BalancerMember "h2://test.example.org:SUBST_PORT_HTTPS_SUBST"
61 |
62 |
63 | BalancerMember "h2c://test.example.org:SUBST_PORT_HTTP_SUBST"
64 |
65 |
66 |
67 | ProxyPass "/h2proxy" "balancer://h2-local"
68 | ProxyPassReverse "/h2proxy" "balancer://h2-local"
69 | ProxyPass "/h2cproxy" "balancer://h2c-local"
70 | ProxyPassReverse "/h2cproxy" "balancer://h2c-local"
71 |
72 | ```
73 |
74 | This will only work under the following conditions:
75 | * the backend speaks HTTP/2, the module will not fallback to HTTP/1.1
76 | * the backend supports HTTP/2 direct mode (see also ```H2Direct``` directive of ```mod_http2```)
77 |
78 | All other common httpd ```proxy``` directives also apply.
79 |
80 |
81 | ## Documentation
82 |
83 | The official [Apache documentation of the module](https://httpd.apache.org/docs/2.4/en/mod/mod_http2.html).
84 |
85 | I also compiled a [how to h2 in apache](https://icing.github.io/mod_h2/howto.html) document with advice on how to deploy, configure and verify your ```mod_h[ttp]2``` installation.
86 |
87 | ## Build from git
88 |
89 | Still not dissuaded? Ok, here are some hints to get you started.
90 | Building from git is easy, but please be sure that at least autoconf 2.68 is
91 | used:
92 |
93 | ```
94 | > autoreconf -i
95 | > automake
96 | > autoconf
97 | > ./configure --with-apxs=
98 | > make
99 | ```
100 |
101 | ## Licensing
102 |
103 | Please see the file called LICENSE.
104 |
105 | ## Credits
106 |
107 | This work has been funded by the GSM Association (http://gsma.com). The module
108 | itself was heavily influenced by mod_spdy, the Google implementation of their
109 | SPDY protocol. And without Tatsuhiro Tsujikawa excellent nghttp2 work, this
110 | would not have been possible.
111 |
112 |
113 | Münster, 04.11.2019,
114 |
115 | Stefan Eissing, greenbytes GmbH
116 |
117 | Copying and distribution of this file, with or without modification,
118 | are permitted in any medium without royalty provided the copyright
119 | notice and this notice are preserved. This file is offered as-is,
120 | without warranty of any kind. See LICENSE for details.
121 |
122 |
123 |
--------------------------------------------------------------------------------
/test/pyhttpd/curl.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import re
3 | import subprocess
4 | import sys
5 | import time
6 | from threading import Thread
7 |
8 | from .env import HttpdTestEnv
9 |
10 |
11 | class CurlPiper:
12 |
13 | def __init__(self, env: HttpdTestEnv, url: str):
14 | self.env = env
15 | self.url = url
16 | self.proc = None
17 | self.args = None
18 | self.headerfile = None
19 | self._stderr = []
20 | self._stdout = []
21 | self.stdout_thread = None
22 | self.stderr_thread = None
23 | self._exitcode = -1
24 | self._r = None
25 |
26 | @property
27 | def exitcode(self):
28 | return self._exitcode
29 |
30 | @property
31 | def response(self):
32 | return self._r.response if self._r else None
33 |
34 | def start(self):
35 | self.args, self.headerfile = self.env.curl_complete_args(self.url, timeout=5, options=[
36 | "-T", "-", "-X", "POST", "--trace-ascii", "%", "--trace-time"])
37 | sys.stderr.write("starting: {0}\n".format(self.args))
38 | self.proc = subprocess.Popen(self.args, stdin=subprocess.PIPE,
39 | stdout=subprocess.PIPE,
40 | stderr=subprocess.PIPE,
41 | bufsize=0)
42 |
43 | def read_output(fh, buffer):
44 | while True:
45 | chunk = fh.read()
46 | if not chunk:
47 | break
48 | buffer.append(chunk.decode())
49 |
50 | # collect all stdout and stderr until we are done
51 | # use separate threads to not block ourself
52 | self._stderr = []
53 | self._stdout = []
54 | if self.proc.stderr:
55 | self.stderr_thread = Thread(target=read_output, args=(self.proc.stderr, self._stderr))
56 | self.stderr_thread.start()
57 | if self.proc.stdout:
58 | self.stdout_thread = Thread(target=read_output, args=(self.proc.stdout, self._stdout))
59 | self.stdout_thread.start()
60 | return self.proc
61 |
62 | def send(self, data: str):
63 | self.proc.stdin.write(data.encode())
64 | self.proc.stdin.flush()
65 |
66 | def close(self) -> ([str], [str]):
67 | self.proc.stdin.close()
68 | self.stdout_thread.join()
69 | self.stderr_thread.join()
70 | self._end()
71 | return self._stdout, self._stderr
72 |
73 | def _end(self):
74 | if self.proc:
75 | # noinspection PyBroadException
76 | try:
77 | if self.proc.stdin:
78 | # noinspection PyBroadException
79 | try:
80 | self.proc.stdin.close()
81 | except Exception:
82 | pass
83 | if self.proc.stdout:
84 | self.proc.stdout.close()
85 | if self.proc.stderr:
86 | self.proc.stderr.close()
87 | except Exception:
88 | self.proc.terminate()
89 | finally:
90 | self.proc.wait()
91 | self.stdout_thread = None
92 | self.stderr_thread = None
93 | self._exitcode = self.proc.returncode
94 | self.proc = None
95 | self._r = self.env.curl_parse_headerfile(self.headerfile)
96 |
97 | def stutter_check(self, chunks: [str], stutter: datetime.timedelta):
98 | if not self.proc:
99 | self.start()
100 | for chunk in chunks:
101 | self.send(chunk)
102 | time.sleep(stutter.total_seconds())
103 | recv_out, recv_err = self.close()
104 | # assert we got everything back
105 | assert "".join(chunks) == "".join(recv_out)
106 | # now the tricky part: check *when* we got everything back
107 | recv_times = []
108 | for line in "".join(recv_err).split('\n'):
109 | m = re.match(r'^\s*(\d+:\d+:\d+(\.\d+)?) <= Recv data, (\d+) bytes.*', line)
110 | if m:
111 | recv_times.append(datetime.time.fromisoformat(m.group(1)))
112 | # received as many chunks as we sent
113 | assert len(chunks) == len(recv_times), "received response not in {0} chunks, but {1}".format(
114 | len(chunks), len(recv_times))
115 |
116 | def microsecs(tdelta):
117 | return ((tdelta.hour * 60 + tdelta.minute) * 60 + tdelta.second) * 1000000 + tdelta.microsecond
118 |
119 | recv_deltas = []
120 | last_mics = microsecs(recv_times[0])
121 | for ts in recv_times[1:]:
122 | mics = microsecs(ts)
123 | delta_mics = mics - last_mics
124 | if delta_mics < 0:
125 | delta_mics += datetime.time(23, 59, 59, 999999)
126 | recv_deltas.append(datetime.timedelta(microseconds=delta_mics))
127 | last_mics = mics
128 | stutter_td = datetime.timedelta(seconds=stutter.total_seconds() * 0.9) # 10% leeway
129 | # TODO: the first two chunks are often close together, it seems
130 | # there still is a little buffering delay going on
131 | for idx, td in enumerate(recv_deltas[1:]):
132 | assert stutter_td < td, \
133 | f"chunk {idx} arrived too early \n{recv_deltas}\nafter {td}\n{recv_err}"
134 |
--------------------------------------------------------------------------------
/test/modules/http2/test_105_timeout.py:
--------------------------------------------------------------------------------
1 | import socket
2 | import time
3 |
4 | import pytest
5 |
6 | from .env import H2Conf
7 | from pyhttpd.curl import CurlPiper
8 |
9 |
10 | class TestTimeout:
11 |
12 | # Check that base servers 'Timeout' setting is observed on SSL handshake
13 | def test_h2_105_01(self, env):
14 | conf = H2Conf(env)
15 | conf.add("""
16 | AcceptFilter http none
17 | Timeout 1.5
18 | """)
19 | conf.add_vhost_cgi()
20 | conf.install()
21 | assert env.apache_restart() == 0
22 | host = 'localhost'
23 | # read with a longer timeout than the server
24 | sock = socket.create_connection((host, int(env.https_port)))
25 | try:
26 | # on some OS, the server does not see our connection until there is
27 | # something incoming
28 | sock.send(b'0')
29 | sock.settimeout(4)
30 | buff = sock.recv(1024)
31 | assert buff == b''
32 | except Exception as ex:
33 | print(f"server did not close in time: {ex}")
34 | assert False
35 | sock.close()
36 | # read with a shorter timeout than the server
37 | sock = socket.create_connection((host, int(env.https_port)))
38 | try:
39 | sock.settimeout(0.5)
40 | sock.recv(1024)
41 | assert False
42 | except Exception as ex:
43 | print(f"as expected: {ex}")
44 | sock.close()
45 |
46 | # Check that mod_reqtimeout handshake setting takes effect
47 | def test_h2_105_02(self, env):
48 | conf = H2Conf(env)
49 | conf.add("""
50 | AcceptFilter http none
51 | Timeout 10
52 | RequestReadTimeout handshake=1 header=5 body=10
53 | """)
54 | conf.add_vhost_cgi()
55 | conf.install()
56 | assert env.apache_restart() == 0
57 | host = 'localhost'
58 | # read with a longer timeout than the server
59 | sock = socket.create_connection((host, int(env.https_port)))
60 | try:
61 | # on some OS, the server does not see our connection until there is
62 | # something incoming
63 | sock.send(b'0')
64 | sock.settimeout(4)
65 | buff = sock.recv(1024)
66 | assert buff == b''
67 | except Exception as ex:
68 | print(f"server did not close in time: {ex}")
69 | assert False
70 | sock.close()
71 | # read with a shorter timeout than the server
72 | sock = socket.create_connection((host, int(env.https_port)))
73 | try:
74 | sock.settimeout(0.5)
75 | sock.recv(1024)
76 | assert False
77 | except Exception as ex:
78 | print(f"as expected: {ex}")
79 | sock.close()
80 |
81 | # Check that mod_reqtimeout handshake setting do no longer apply to handshaked
82 | # connections. See .
83 | def test_h2_105_03(self, env):
84 | conf = H2Conf(env)
85 | conf.add("""
86 | Timeout 10
87 | RequestReadTimeout handshake=1 header=5 body=10
88 | """)
89 | conf.add_vhost_cgi()
90 | conf.install()
91 | assert env.apache_restart() == 0
92 | url = env.mkurl("https", "cgi", "/necho.py")
93 | r = env.curl_get(url, 5, options=[
94 | "-vvv",
95 | "-F", ("count=%d" % 100),
96 | "-F", ("text=%s" % "abcdefghijklmnopqrstuvwxyz"),
97 | "-F", ("wait1=%f" % 1.5),
98 | ])
99 | assert r.response["status"] == 200
100 |
101 | def test_h2_105_10(self, env):
102 | # just a check without delays if all is fine
103 | conf = H2Conf(env)
104 | conf.add_vhost_cgi()
105 | conf.install()
106 | assert env.apache_restart() == 0
107 | url = env.mkurl("https", "cgi", "/h2test/delay")
108 | piper = CurlPiper(env=env, url=url)
109 | piper.start()
110 | stdout, stderr = piper.close()
111 | assert piper.exitcode == 0
112 | assert len("".join(stdout)) == 3 * 8192
113 |
114 | def test_h2_105_11(self, env):
115 | # short connection timeout, longer stream delay
116 | # receiving the first response chunk, then timeout
117 | conf = H2Conf(env)
118 | conf.add_vhost_cgi()
119 | conf.add("Timeout 1")
120 | conf.install()
121 | assert env.apache_restart() == 0
122 | url = env.mkurl("https", "cgi", "/h2test/delay?5")
123 | piper = CurlPiper(env=env, url=url)
124 | piper.start()
125 | stdout, stderr = piper.close()
126 | assert len("".join(stdout)) == 8192
127 |
128 | def test_h2_105_12(self, env):
129 | # long connection timeout, short stream timeout
130 | # sending a slow POST
131 | conf = H2Conf(env)
132 | conf.add_vhost_cgi()
133 | conf.add("Timeout 10")
134 | conf.add("H2StreamTimeout 1")
135 | conf.install()
136 | assert env.apache_restart() == 0
137 | url = env.mkurl("https", "cgi", "/h2test/delay?5")
138 | piper = CurlPiper(env=env, url=url)
139 | piper.start()
140 | for _ in range(3):
141 | time.sleep(2)
142 | try:
143 | piper.send("0123456789\n")
144 | except BrokenPipeError:
145 | break
146 | piper.close()
147 | assert piper.response
148 | assert piper.response['status'] == 408
149 |
--------------------------------------------------------------------------------
/test/modules/http2/test_500_proxy.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import os
3 | import re
4 | import pytest
5 |
6 | from .env import H2Conf
7 |
8 |
9 | class TestProxy:
10 |
11 | @pytest.fixture(autouse=True, scope='class')
12 | def _class_scope(self, env):
13 | TestProxy._local_dir = os.path.dirname(inspect.getfile(TestProxy))
14 | H2Conf(env).add_vhost_cgi(proxy_self=True).install()
15 | assert env.apache_restart() == 0
16 |
17 | def local_src(self, fname):
18 | return os.path.join(TestProxy._local_dir, fname)
19 |
20 | def setup_method(self, method):
21 | print("setup_method: %s" % method.__name__)
22 |
23 | def teardown_method(self, method):
24 | print("teardown_method: %s" % method.__name__)
25 |
26 | def test_h2_500_01(self, env):
27 | url = env.mkurl("https", "cgi", "/proxy/hello.py")
28 | r = env.curl_get(url, 5)
29 | assert r.response["status"] == 200
30 | assert "HTTP/1.1" == r.response["json"]["protocol"]
31 | assert "" == r.response["json"]["https"]
32 | assert "" == r.response["json"]["ssl_protocol"]
33 | assert "" == r.response["json"]["h2"]
34 | assert "" == r.response["json"]["h2push"]
35 |
36 | # upload and GET again using curl, compare to original content
37 | def curl_upload_and_verify(self, env, fname, options=None):
38 | url = env.mkurl("https", "cgi", "/proxy/upload.py")
39 | fpath = os.path.join(env.gen_dir, fname)
40 | r = env.curl_upload(url, fpath, options=options)
41 | assert r.exit_code == 0
42 | assert 200 <= r.response["status"] < 300
43 |
44 | # why is the scheme wrong?
45 | r2 = env.curl_get(re.sub(r'http:', 'https:', r.response["header"]["location"]))
46 | assert r2.exit_code == 0
47 | assert r2.response["status"] == 200
48 | with open(self.local_src(fpath), mode='rb') as file:
49 | src = file.read()
50 | assert src == r2.response["body"]
51 |
52 | def test_h2_500_10(self, env):
53 | self.curl_upload_and_verify(env, "data-1k", ["--http2"])
54 | self.curl_upload_and_verify(env, "data-10k", ["--http2"])
55 | self.curl_upload_and_verify(env, "data-100k", ["--http2"])
56 | self.curl_upload_and_verify(env, "data-1m", ["--http2"])
57 |
58 | # POST some data using nghttp and see it echo'ed properly back
59 | def nghttp_post_and_verify(self, env, fname, options=None):
60 | url = env.mkurl("https", "cgi", "/proxy/echo.py")
61 | fpath = os.path.join(env.gen_dir, fname)
62 | r = env.nghttp().upload(url, fpath, options=options)
63 | assert r.exit_code == 0
64 | assert 200 <= r.response["status"] < 300
65 | with open(self.local_src(fpath), mode='rb') as file:
66 | src = file.read()
67 | assert src == r.response["body"]
68 |
69 | def test_h2_500_20(self, env):
70 | self.nghttp_post_and_verify(env, "data-1k", [])
71 | self.nghttp_post_and_verify(env, "data-10k", [])
72 | self.nghttp_post_and_verify(env, "data-100k", [])
73 | self.nghttp_post_and_verify(env, "data-1m", [])
74 |
75 | def test_h2_500_21(self, env):
76 | self.nghttp_post_and_verify(env, "data-1k", ["--no-content-length"])
77 | self.nghttp_post_and_verify(env, "data-10k", ["--no-content-length"])
78 | self.nghttp_post_and_verify(env, "data-100k", ["--no-content-length"])
79 | self.nghttp_post_and_verify(env, "data-1m", ["--no-content-length"])
80 |
81 | # upload and GET again using nghttp, compare to original content
82 | def nghttp_upload_and_verify(self, env, fname, options=None):
83 | url = env.mkurl("https", "cgi", "/proxy/upload.py")
84 | fpath = os.path.join(env.gen_dir, fname)
85 |
86 | r = env.nghttp().upload_file(url, fpath, options=options)
87 | assert r.exit_code == 0
88 | assert 200 <= r.response["status"] < 300
89 | assert r.response["header"]["location"]
90 |
91 | # why is the scheme wrong?
92 | r2 = env.nghttp().get(re.sub(r'http:', 'https:', r.response["header"]["location"]))
93 | assert r2.exit_code == 0
94 | assert r2.response["status"] == 200
95 | with open(self.local_src(fpath), mode='rb') as file:
96 | src = file.read()
97 | assert src == r2.response["body"]
98 |
99 | def test_h2_500_22(self, env):
100 | self.nghttp_upload_and_verify(env, "data-1k", [])
101 | self.nghttp_upload_and_verify(env, "data-10k", [])
102 | self.nghttp_upload_and_verify(env, "data-100k", [])
103 | self.nghttp_upload_and_verify(env, "data-1m", [])
104 |
105 | def test_h2_500_23(self, env):
106 | self.nghttp_upload_and_verify(env, "data-1k", ["--no-content-length"])
107 | self.nghttp_upload_and_verify(env, "data-10k", ["--no-content-length"])
108 | self.nghttp_upload_and_verify(env, "data-100k", ["--no-content-length"])
109 | self.nghttp_upload_and_verify(env, "data-1m", ["--no-content-length"])
110 |
111 | # upload using nghttp and check returned status
112 | def nghttp_upload_stat(self, env, fname, options=None):
113 | url = env.mkurl("https", "cgi", "/proxy/upload.py")
114 | fpath = os.path.join(env.gen_dir, fname)
115 |
116 | r = env.nghttp().upload_file(url, fpath, options=options)
117 | assert r.exit_code == 0
118 | assert 200 <= r.response["status"] < 300
119 | assert r.response["header"]["location"]
120 |
121 | def test_h2_500_24(self, env):
122 | for i in range(100):
123 | self.nghttp_upload_stat(env, "data-1k", ["--no-content-length"])
124 |
--------------------------------------------------------------------------------
/mod_http2/h2_proxy_session.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef h2_proxy_session_h
18 | #define h2_proxy_session_h
19 |
20 | #define H2_ALEN(a) (sizeof(a)/sizeof((a)[0]))
21 |
22 | #include
23 |
24 | struct h2_proxy_iqueue;
25 | struct h2_proxy_ihash_t;
26 |
27 | typedef enum {
28 | H2_STREAM_ST_IDLE,
29 | H2_STREAM_ST_OPEN,
30 | H2_STREAM_ST_RESV_LOCAL,
31 | H2_STREAM_ST_RESV_REMOTE,
32 | H2_STREAM_ST_CLOSED_INPUT,
33 | H2_STREAM_ST_CLOSED_OUTPUT,
34 | H2_STREAM_ST_CLOSED,
35 | } h2_proxy_stream_state_t;
36 |
37 | typedef enum {
38 | H2_PROXYS_ST_INIT, /* send initial SETTINGS, etc. */
39 | H2_PROXYS_ST_DONE, /* finished, connection close */
40 | H2_PROXYS_ST_IDLE, /* no streams to process */
41 | H2_PROXYS_ST_BUSY, /* read/write without stop */
42 | H2_PROXYS_ST_WAIT, /* waiting for tasks reporting back */
43 | H2_PROXYS_ST_LOCAL_SHUTDOWN, /* we announced GOAWAY */
44 | H2_PROXYS_ST_REMOTE_SHUTDOWN, /* client announced GOAWAY */
45 | } h2_proxys_state;
46 |
47 | typedef enum {
48 | H2_PROXYS_EV_INIT, /* session was initialized */
49 | H2_PROXYS_EV_LOCAL_GOAWAY, /* we send a GOAWAY */
50 | H2_PROXYS_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */
51 | H2_PROXYS_EV_CONN_ERROR, /* connection error */
52 | H2_PROXYS_EV_PROTO_ERROR, /* protocol error */
53 | H2_PROXYS_EV_CONN_TIMEOUT, /* connection timeout */
54 | H2_PROXYS_EV_NO_IO, /* nothing has been read or written */
55 | H2_PROXYS_EV_STREAM_SUBMITTED, /* stream has been submitted */
56 | H2_PROXYS_EV_STREAM_DONE, /* stream has been finished */
57 | H2_PROXYS_EV_STREAM_RESUMED, /* stream signalled availability of headers/data */
58 | H2_PROXYS_EV_DATA_READ, /* connection data has been read */
59 | H2_PROXYS_EV_NGH2_DONE, /* nghttp2 wants neither read nor write anything */
60 | H2_PROXYS_EV_PRE_CLOSE, /* connection will close after this */
61 | } h2_proxys_event_t;
62 |
63 | typedef enum {
64 | H2_PING_ST_NONE, /* normal connection mode, ProxyTimeout rules */
65 | H2_PING_ST_AWAIT_ANY, /* waiting for any frame from backend */
66 | H2_PING_ST_AWAIT_PING, /* waiting for PING frame from backend */
67 | } h2_ping_state_t;
68 |
69 | typedef struct h2_proxy_session h2_proxy_session;
70 | typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
71 | apr_status_t status, int touched);
72 |
73 | struct h2_proxy_session {
74 | const char *id;
75 | conn_rec *c;
76 | proxy_conn_rec *p_conn;
77 | proxy_server_conf *conf;
78 | apr_pool_t *pool;
79 | nghttp2_session *ngh2; /* the nghttp2 session itself */
80 |
81 | unsigned int aborted : 1;
82 | unsigned int h2_front : 1; /* if front-end connection is HTTP/2 */
83 |
84 | h2_proxy_request_done *done;
85 | void *user_data;
86 |
87 | unsigned char window_bits_stream;
88 | unsigned char window_bits_connection;
89 |
90 | h2_proxys_state state;
91 | apr_interval_time_t wait_timeout;
92 |
93 | struct h2_proxy_ihash_t *streams;
94 | struct h2_proxy_iqueue *suspended;
95 | apr_size_t remote_max_concurrent;
96 | int last_stream_id; /* last stream id processed by backend, or 0 */
97 | apr_time_t last_frame_received;
98 |
99 | apr_bucket_brigade *input;
100 | apr_bucket_brigade *output;
101 |
102 | h2_ping_state_t ping_state;
103 | apr_time_t ping_timeout;
104 | apr_time_t save_timeout;
105 | };
106 |
107 | h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
108 | proxy_server_conf *conf,
109 | int h2_front,
110 | unsigned char window_bits_connection,
111 | unsigned char window_bits_stream,
112 | h2_proxy_request_done *done);
113 |
114 | apr_status_t h2_proxy_session_submit(h2_proxy_session *s, const char *url,
115 | request_rec *r, int standalone);
116 |
117 | /**
118 | * Perform a step in processing the proxy session. Will return aftert
119 | * one read/write cycle and indicate session status by status code.
120 | * @param s the session to process
121 | * @return APR_EAGAIN when processing needs to be invoked again
122 | * APR_SUCCESS when all streams have been processed, session still live
123 | * APR_EOF when the session has been terminated
124 | */
125 | apr_status_t h2_proxy_session_process(h2_proxy_session *s);
126 |
127 | void h2_proxy_session_cancel_all(h2_proxy_session *s);
128 |
129 | void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);
130 |
131 | #define H2_PROXY_REQ_URL_NOTE "h2-proxy-req-url"
132 |
133 | #endif /* h2_proxy_session_h */
134 |
--------------------------------------------------------------------------------
/test/modules/http2/env.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import logging
3 | import os
4 | import re
5 | import subprocess
6 | from typing import Dict, Any
7 |
8 | from pyhttpd.certs import CertificateSpec
9 | from pyhttpd.conf import HttpdConf
10 | from pyhttpd.env import HttpdTestEnv, HttpdTestSetup
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | class H2TestSetup(HttpdTestSetup):
16 |
17 | def __init__(self, env: 'HttpdTestEnv'):
18 | super().__init__(env=env)
19 | self.add_source_dir(os.path.dirname(inspect.getfile(H2TestSetup)))
20 | self.add_modules(["http2", "proxy_http2", "cgid", "autoindex"])
21 |
22 | def make(self):
23 | super().make()
24 | self._add_h2test()
25 | self._setup_data_1k_1m()
26 |
27 | def _add_h2test(self):
28 | local_dir = os.path.dirname(inspect.getfile(H2TestSetup))
29 | p = subprocess.run([self.env.apxs, '-c', 'mod_h2test.c'],
30 | capture_output=True,
31 | cwd=os.path.join(local_dir, 'mod_h2test'))
32 | rv = p.returncode
33 | if rv != 0:
34 | log.error(f"compiling md_h2test failed: {p.stderr}")
35 | raise Exception(f"compiling md_h2test failed: {p.stderr}")
36 |
37 | modules_conf = os.path.join(self.env.server_dir, 'conf/modules.conf')
38 | with open(modules_conf, 'a') as fd:
39 | # load our test module which is not installed
40 | fd.write(f"LoadModule h2test_module \"{local_dir}/mod_h2test/.libs/mod_h2test.so\"\n")
41 |
42 | def _setup_data_1k_1m(self):
43 | s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
44 | with open(os.path.join(self.env.gen_dir, "data-1k"), 'w') as f:
45 | for i in range(10):
46 | f.write(f"{i:09d}-{s90}")
47 | with open(os.path.join(self.env.gen_dir, "data-10k"), 'w') as f:
48 | for i in range(100):
49 | f.write(f"{i:09d}-{s90}")
50 | with open(os.path.join(self.env.gen_dir, "data-100k"), 'w') as f:
51 | for i in range(1000):
52 | f.write(f"{i:09d}-{s90}")
53 | with open(os.path.join(self.env.gen_dir, "data-1m"), 'w') as f:
54 | for i in range(10000):
55 | f.write(f"{i:09d}-{s90}")
56 |
57 |
58 | class H2TestEnv(HttpdTestEnv):
59 |
60 | def __init__(self, pytestconfig=None):
61 | super().__init__(pytestconfig=pytestconfig)
62 | self.add_httpd_conf([
63 | "H2MinWorkers 1",
64 | "H2MaxWorkers 64",
65 | "Protocols h2 http/1.1 h2c",
66 | ])
67 | self.add_httpd_log_modules(["http2", "proxy_http2", "h2test"])
68 | self.add_cert_specs([
69 | CertificateSpec(domains=[
70 | f"push.{self._http_tld}",
71 | f"hints.{self._http_tld}",
72 | f"ssl.{self._http_tld}",
73 | f"pad0.{self._http_tld}",
74 | f"pad1.{self._http_tld}",
75 | f"pad2.{self._http_tld}",
76 | f"pad3.{self._http_tld}",
77 | f"pad8.{self._http_tld}",
78 | ]),
79 | CertificateSpec(domains=[f"noh2.{self.http_tld}"], key_type='rsa2048'),
80 | ])
81 |
82 | self.httpd_error_log.set_ignored_lognos([
83 | 'AH02032',
84 | 'AH01276',
85 | 'AH01630',
86 | 'AH00135',
87 | 'AH02261', # Re-negotiation handshake failed (our test_101)
88 | 'AH03490', # scoreboard full, happens on limit tests
89 | ])
90 | self.httpd_error_log.add_ignored_patterns([
91 | re.compile(r'.*malformed header from script \'hecho.py\': Bad header: x.*'),
92 | re.compile(r'.*:tls_post_process_client_hello:.*'),
93 | re.compile(r'.*:tls_process_client_certificate:.*'),
94 | ])
95 |
96 | def setup_httpd(self, setup: HttpdTestSetup = None):
97 | super().setup_httpd(setup=H2TestSetup(env=self))
98 |
99 |
100 | class H2Conf(HttpdConf):
101 |
102 | def __init__(self, env: HttpdTestEnv, extras: Dict[str, Any] = None):
103 | super().__init__(env=env, extras=HttpdConf.merge_extras(extras, {
104 | f"cgi.{env.http_tld}": [
105 | "SSLOptions +StdEnvVars",
106 | "AddHandler cgi-script .py",
107 | ]
108 | }))
109 |
110 | def start_vhost(self, domains, port=None, doc_root="htdocs", with_ssl=None):
111 | super().start_vhost(domains=domains, port=port, doc_root=doc_root, with_ssl=with_ssl)
112 | if f"noh2.{self.env.http_tld}" in domains:
113 | protos = ["http/1.1"]
114 | elif port == self.env.https_port or with_ssl is True:
115 | protos = ["h2", "http/1.1"]
116 | else:
117 | protos = ["h2c", "http/1.1"]
118 | if f"test2.{self.env.http_tld}" in domains:
119 | protos = reversed(protos)
120 | self.add(f"Protocols {' '.join(protos)}")
121 | return self
122 |
123 | def add_vhost_noh2(self):
124 | domains = [f"noh2.{self.env.http_tld}", f"noh2-alias.{self.env.http_tld}"]
125 | self.start_vhost(domains=domains, port=self.env.https_port, doc_root="htdocs/noh2")
126 | self.add(["Protocols http/1.1", "SSLOptions +StdEnvVars"])
127 | self.end_vhost()
128 | self.start_vhost(domains=domains, port=self.env.http_port, doc_root="htdocs/noh2")
129 | self.add(["Protocols http/1.1", "SSLOptions +StdEnvVars"])
130 | self.end_vhost()
131 | return self
132 |
133 | def add_vhost_test1(self, proxy_self=False, h2proxy_self=False):
134 | return super().add_vhost_test1(proxy_self=proxy_self, h2proxy_self=h2proxy_self)
135 |
136 | def add_vhost_test2(self):
137 | return super().add_vhost_test2()
138 |
--------------------------------------------------------------------------------
/test/modules/http2/test_101_ssl_reneg.py:
--------------------------------------------------------------------------------
1 | import re
2 | import pytest
3 |
4 | from .env import H2Conf
5 |
6 |
7 | class TestSslRenegotiation:
8 |
9 | @pytest.fixture(autouse=True, scope='class')
10 | def _class_scope(self, env):
11 | domain = f"ssl.{env.http_tld}"
12 | conf = H2Conf(env, extras={
13 | 'base': [
14 | "SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384",
15 | f"",
16 | " Require all granted",
17 | " SSLVerifyClient require",
18 | " SSLVerifyDepth 0",
19 | " "
20 | ],
21 | domain: [
22 | "Protocols h2 http/1.1",
23 | "",
24 | " SSLCipherSuite ECDHE-RSA-CHACHA20-POLY1305",
25 | " ",
26 | "",
27 | " SSLCipherSuite ECDHE-RSA-CHACHA20-POLY1305",
28 | " ErrorDocument 403 /forbidden.html",
29 | " ",
30 | "",
31 | " SSLVerifyClient require",
32 | " ",
33 | f"",
34 | " SSLRequireSSL",
35 | " ",
36 | f"",
37 | " Require ssl",
38 | " ",
39 | ]})
40 | conf.add_vhost(domains=[domain], port=env.https_port,
41 | doc_root=f"{env.server_dir}/htdocs")
42 | conf.install()
43 | # the dir needs to exists for the configuration to have effect
44 | env.mkpath("%s/htdocs/ssl-client-verify" % env.server_dir)
45 | env.mkpath("%s/htdocs/renegotiate/cipher" % env.server_dir)
46 | env.mkpath("%s/htdocs/sslrequire" % env.server_dir)
47 | env.mkpath("%s/htdocs/requiressl" % env.server_dir)
48 | assert env.apache_restart() == 0
49 |
50 | # access a resource with SSL renegotiation, using HTTP/1.1
51 | def test_h2_101_01(self, env):
52 | url = env.mkurl("https", "ssl", "/renegotiate/cipher/")
53 | r = env.curl_get(url, options=["-v", "--http1.1", "--tlsv1.2", "--tls-max", "1.2"])
54 | assert 0 == r.exit_code, f"{r}"
55 | assert r.response
56 | assert 403 == r.response["status"]
57 |
58 | # try to renegotiate the cipher, should fail with correct code
59 | def test_h2_101_02(self, env):
60 | url = env.mkurl("https", "ssl", "/renegotiate/cipher/")
61 | r = env.curl_get(url, options=[
62 | "-vvv", "--tlsv1.2", "--tls-max", "1.2", "--ciphers", "ECDHE-RSA-AES256-GCM-SHA384"
63 | ])
64 | assert 0 != r.exit_code
65 | assert not r.response
66 | assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
67 |
68 | # try to renegotiate a client certificate from Location
69 | # needs to fail with correct code
70 | def test_h2_101_03(self, env):
71 | url = env.mkurl("https", "ssl", "/renegotiate/verify/")
72 | r = env.curl_get(url, options=["-vvv", "--tlsv1.2", "--tls-max", "1.2"])
73 | assert 0 != r.exit_code
74 | assert not r.response
75 | assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
76 |
77 | # try to renegotiate a client certificate from Directory
78 | # needs to fail with correct code
79 | def test_h2_101_04(self, env):
80 | url = env.mkurl("https", "ssl", "/ssl-client-verify/index.html")
81 | r = env.curl_get(url, options=["-vvv", "--tlsv1.2", "--tls-max", "1.2"])
82 | assert 0 != r.exit_code, f"{r}"
83 | assert not r.response
84 | assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
85 |
86 | # make 10 requests on the same connection, none should produce a status code
87 | # reported by erki@example.ee
88 | def test_h2_101_05(self, env):
89 | r = env.run([env.h2load, "-n", "10", "-c", "1", "-m", "1", "-vvvv",
90 | f"{env.https_base_url}/ssl-client-verify/index.html"])
91 | assert 0 == r.exit_code
92 | r = env.h2load_status(r)
93 | assert 10 == r.results["h2load"]["requests"]["total"]
94 | assert 10 == r.results["h2load"]["requests"]["started"]
95 | assert 10 == r.results["h2load"]["requests"]["done"]
96 | assert 0 == r.results["h2load"]["requests"]["succeeded"]
97 | assert 0 == r.results["h2load"]["status"]["2xx"]
98 | assert 0 == r.results["h2load"]["status"]["3xx"]
99 | assert 0 == r.results["h2load"]["status"]["4xx"]
100 | assert 0 == r.results["h2load"]["status"]["5xx"]
101 |
102 | # Check that "SSLRequireSSL" works on h2 connections
103 | # See
104 | def test_h2_101_10a(self, env):
105 | url = env.mkurl("https", "ssl", "/sslrequire/index.html")
106 | r = env.curl_get(url)
107 | assert 0 == r.exit_code
108 | assert r.response
109 | assert 404 == r.response["status"]
110 |
111 | # Check that "require ssl" works on h2 connections
112 | # See
113 | def test_h2_101_10b(self, env):
114 | url = env.mkurl("https", "ssl", "/requiressl/index.html")
115 | r = env.curl_get(url)
116 | assert 0 == r.exit_code
117 | assert r.response
118 | assert 404 == r.response["status"]
119 |
120 | # Check that status works with ErrorDoc, see pull #174, fixes #172
121 | def test_h2_101_11(self, env):
122 | url = env.mkurl("https", "ssl", "/renegotiate/err-doc-cipher")
123 | r = env.curl_get(url, options=[
124 | "-vvv", "--tlsv1.2", "--tls-max", "1.2", "--ciphers", "ECDHE-RSA-AES256-GCM-SHA384"
125 | ])
126 | assert 0 != r.exit_code
127 | assert not r.response
128 | assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
129 |
--------------------------------------------------------------------------------
/mod_http2/h2_push.h:
--------------------------------------------------------------------------------
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
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 |
17 | #ifndef __mod_h2__h2_push__
18 | #define __mod_h2__h2_push__
19 |
20 | #include "h2.h"
21 |
22 | struct h2_request;
23 | struct h2_headers;
24 | struct h2_ngheader;
25 | struct h2_session;
26 | struct h2_stream;
27 |
28 | typedef struct h2_push {
29 | const struct h2_request *req;
30 | h2_priority *priority;
31 | } h2_push;
32 |
33 | typedef enum {
34 | H2_PUSH_DIGEST_APR_HASH,
35 | H2_PUSH_DIGEST_SHA256
36 | } h2_push_digest_type;
37 |
38 | /*******************************************************************************
39 | * push diary
40 | *
41 | * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
42 | * connection. It records a hash value from the absolute URL of the resource
43 | * pushed.
44 | * - Lacking openssl,
45 | * - with openssl, it uses SHA256 to calculate the hash value, otherwise it
46 | * falls back to apr_hashfunc_default()
47 | * - whatever the method to generate the hash, the diary keeps a maximum of 64
48 | * bits per hash, limiting the memory consumption to about
49 | * H2PushDiarySize * 8
50 | * bytes. Entries are sorted by most recently used and oldest entries are
51 | * forgotten first.
52 | * - While useful by itself to avoid duplicated PUSHes on the same connection,
53 | * the original idea was that clients provided a 'Cache-Digest' header with
54 | * the values of *their own* cached resources. This was described in
55 | *
56 | * and some subsequent revisions that tweaked values but kept the overall idea.
57 | * - The draft was abandoned by the IETF http-wg, as support from major clients,
58 | * e.g. browsers, was lacking for various reasons.
59 | * - For these reasons, mod_h2 abandoned its support for client supplied values
60 | * but keeps the diary. It seems to provide value for applications using PUSH,
61 | * is configurable in size and defaults to a very moderate amount of memory
62 | * used.
63 | * - The cache digest header is a Golomb Coded Set of hash values, but it may
64 | * limit the amount of bits per hash value even further. For a good description
65 | * of GCS, read here:
66 | *
67 | ******************************************************************************/
68 |
69 |
70 | /*
71 | * The push diary is based on the abandoned draft
72 | *
73 | * that describes how to use golomb filters.
74 | */
75 |
76 | typedef struct h2_push_diary h2_push_diary;
77 |
78 | typedef void h2_push_digest_calc(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push);
79 |
80 | struct h2_push_diary {
81 | apr_array_header_t *entries;
82 | int NMax; /* Maximum for N, should size change be necessary */
83 | int N; /* Current maximum number of entries, power of 2 */
84 | apr_uint64_t mask; /* mask for relevant bits */
85 | unsigned int mask_bits; /* number of relevant bits */
86 | const char *authority;
87 | h2_push_digest_type dtype;
88 | h2_push_digest_calc *dcalc;
89 | };
90 |
91 | /**
92 | * Determine the list of h2_push'es to send to the client on behalf of
93 | * the given request/response pair.
94 | *
95 | * @param p the pool to use
96 | * @param req the requst from the client
97 | * @param res the response from the server
98 | * @return array of h2_push addresses or NULL
99 | */
100 | apr_array_header_t *h2_push_collect(apr_pool_t *p,
101 | const struct h2_request *req,
102 | apr_uint32_t push_policy,
103 | const struct h2_headers *res);
104 |
105 | /**
106 | * Create a new push diary for the given maximum number of entries.
107 | *
108 | * @param p the pool to use
109 | * @param N the max number of entries, rounded up to 2^x
110 | * @return the created diary, might be NULL of max_entries is 0
111 | */
112 | h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N);
113 |
114 | /**
115 | * Filters the given pushes against the diary and returns only those pushes
116 | * that were newly entered in the diary.
117 | */
118 | apr_array_header_t *h2_push_diary_update(struct h2_session *session, apr_array_header_t *pushes);
119 |
120 | /**
121 | * Collect pushes for the given request/response pair, enter them into the
122 | * diary and return those pushes newly entered.
123 | */
124 | apr_array_header_t *h2_push_collect_update(struct h2_stream *stream,
125 | const struct h2_request *req,
126 | const struct h2_headers *res);
127 | /**
128 | * Get a cache digest as described in
129 | * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
130 | * from the contents of the push diary.
131 | *
132 | * @param diary the diary to calculdate the digest from
133 | * @param p the pool to use
134 | * @param authority the authority to get the data for, use NULL/"*" for all
135 | * @param pdata on successful return, the binary cache digest
136 | * @param plen on successful return, the length of the binary data
137 | */
138 | apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *p,
139 | int maxP, const char *authority,
140 | const char **pdata, apr_size_t *plen);
141 |
142 | #endif /* defined(__mod_h2__h2_push__) */
143 |
--------------------------------------------------------------------------------
/test/pyhttpd/log.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import time
4 | from datetime import datetime, timedelta
5 | from io import SEEK_END
6 | from typing import List, Tuple, Any
7 |
8 |
9 | class HttpdErrorLog:
10 | """Checking the httpd error log for errors and warnings, including
11 | limiting checks from a last known position forward.
12 | """
13 |
14 | RE_ERRLOG_ERROR = re.compile(r'.*\[(?P[^:]+):error].*')
15 | RE_ERRLOG_WARN = re.compile(r'.*\[(?P[^:]+):warn].*')
16 | RE_APLOGNO = re.compile(r'.*\[(?P[^:]+):(error|warn)].* (?PAH\d+): .+')
17 | RE_SSL_LIB_ERR = re.compile(r'.*\[ssl:error].* SSL Library Error: error:(?P\S+):.+')
18 |
19 | def __init__(self, path: str):
20 | self._path = path
21 | self._ignored_modules = []
22 | self._ignored_lognos = set()
23 | self._ignored_patterns = []
24 | # remember the file position we started with
25 | self._start_pos = 0
26 | if os.path.isfile(self._path):
27 | with open(self._path) as fd:
28 | self._start_pos = fd.seek(0, SEEK_END)
29 | self._last_pos = self._start_pos
30 | self._last_errors = []
31 | self._last_warnings = []
32 | self._observed_erros = set()
33 | self._observed_warnings = set()
34 |
35 | def __repr__(self):
36 | return f"HttpdErrorLog[{self._path}, errors: {' '.join(self._last_errors)}, " \
37 | f"warnings: {' '.join(self._last_warnings)}]"
38 |
39 | @property
40 | def path(self) -> str:
41 | return self._path
42 |
43 | def clear_log(self):
44 | if os.path.isfile(self.path):
45 | os.remove(self.path)
46 | self._start_pos = 0
47 | self._last_pos = self._start_pos
48 | self._last_errors = []
49 | self._last_warnings = []
50 | self._observed_erros = set()
51 | self._observed_warnings = set()
52 |
53 | def set_ignored_modules(self, modules: List[str]):
54 | self._ignored_modules = modules.copy() if modules else []
55 |
56 | def set_ignored_lognos(self, lognos: List[str]):
57 | if lognos:
58 | for l in lognos:
59 | self._ignored_lognos.add(l)
60 |
61 | def add_ignored_patterns(self, patterns: List[Any]):
62 | self._ignored_patterns.extend(patterns)
63 |
64 | def _is_ignored(self, line: str) -> bool:
65 | for p in self._ignored_patterns:
66 | if p.match(line):
67 | return True
68 | m = self.RE_APLOGNO.match(line)
69 | if m and m.group('aplogno') in self._ignored_lognos:
70 | return True
71 | return False
72 |
73 | def get_recent(self, advance=True) -> Tuple[List[str], List[str]]:
74 | """Collect error and warning from the log since the last remembered position
75 | :param advance: advance the position to the end of the log afterwards
76 | :return: list of error and list of warnings as tuple
77 | """
78 | self._last_errors = []
79 | self._last_warnings = []
80 | if os.path.isfile(self._path):
81 | with open(self._path) as fd:
82 | fd.seek(self._last_pos, os.SEEK_SET)
83 | for line in fd:
84 | if self._is_ignored(line):
85 | continue
86 | m = self.RE_ERRLOG_ERROR.match(line)
87 | if m and m.group('module') not in self._ignored_modules:
88 | self._last_errors.append(line)
89 | continue
90 | m = self.RE_ERRLOG_WARN.match(line)
91 | if m:
92 | if m and m.group('module') not in self._ignored_modules:
93 | self._last_warnings.append(line)
94 | continue
95 | if advance:
96 | self._last_pos = fd.tell()
97 | self._observed_erros.update(set(self._last_errors))
98 | self._observed_warnings.update(set(self._last_warnings))
99 | return self._last_errors, self._last_warnings
100 |
101 | def get_recent_count(self, advance=True):
102 | errors, warnings = self.get_recent(advance=advance)
103 | return len(errors), len(warnings)
104 |
105 | def ignore_recent(self):
106 | """After a test case triggered errors/warnings on purpose, add
107 | those to our 'observed' list so the do not get reported as 'missed'.
108 | """
109 | self._last_errors = []
110 | self._last_warnings = []
111 | if os.path.isfile(self._path):
112 | with open(self._path) as fd:
113 | fd.seek(self._last_pos, os.SEEK_SET)
114 | for line in fd:
115 | if self._is_ignored(line):
116 | continue
117 | m = self.RE_ERRLOG_ERROR.match(line)
118 | if m and m.group('module') not in self._ignored_modules:
119 | self._observed_erros.add(line)
120 | continue
121 | m = self.RE_ERRLOG_WARN.match(line)
122 | if m:
123 | if m and m.group('module') not in self._ignored_modules:
124 | self._observed_warnings.add(line)
125 | continue
126 | self._last_pos = fd.tell()
127 |
128 | def get_missed(self) -> Tuple[List[str], List[str]]:
129 | errors = []
130 | warnings = []
131 | if os.path.isfile(self._path):
132 | with open(self._path) as fd:
133 | fd.seek(self._start_pos, os.SEEK_SET)
134 | for line in fd:
135 | if self._is_ignored(line):
136 | continue
137 | m = self.RE_ERRLOG_ERROR.match(line)
138 | if m and m.group('module') not in self._ignored_modules \
139 | and line not in self._observed_erros:
140 | errors.append(line)
141 | continue
142 | m = self.RE_ERRLOG_WARN.match(line)
143 | if m:
144 | if m and m.group('module') not in self._ignored_modules \
145 | and line not in self._observed_warnings:
146 | warnings.append(line)
147 | continue
148 | return errors, warnings
149 |
150 | def scan_recent(self, pattern: re, timeout=10):
151 | if not os.path.isfile(self.path):
152 | return False
153 | with open(self.path) as fd:
154 | end = datetime.now() + timedelta(seconds=timeout)
155 | while True:
156 | fd.seek(self._last_pos, os.SEEK_SET)
157 | for line in fd:
158 | if pattern.match(line):
159 | return True
160 | if datetime.now() > end:
161 | raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
162 | time.sleep(.1)
163 | return False
164 |
--------------------------------------------------------------------------------