From 1ef72a79fe73acdbef53e3a0bd0946713495028c Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 26 Mar 2021 11:29:41 +0300 Subject: Version bump. --- docs/changes.xml | 29 +++++++++++++++++++++++++++++ version | 4 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 7656c15a..c1fcc466 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,35 @@ + + + + +NGINX Unit updated to 1.24.0. + + + + + + + + + + + Date: Fri, 26 Mar 2021 21:06:23 +0000 Subject: Tests: SNI. --- test/test_tls_sni.py | 286 ++++++++++++++++++++++++++++++++++++++++++ test/unit/applications/tls.py | 21 +++- test/unit/http.py | 3 +- 3 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 test/test_tls_sni.py diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py new file mode 100644 index 00000000..7da05e6e --- /dev/null +++ b/test/test_tls_sni.py @@ -0,0 +1,286 @@ +import subprocess +import ssl + +import pytest +from unit.applications.tls import TestApplicationTLS +from unit.option import option + + +class TestTLSSNI(TestApplicationTLS): + prerequisites = {'modules': {'openssl': 'any'}} + + def setup_method(self): + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ) + + def openssl_date_to_sec_epoch(self, date): + return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z') + + def add_tls(self, cert='default'): + assert 'success' in self.conf( + { + "pass": "routes", + "tls": {"certificate": cert} + }, + 'listeners/*:7080', + ) + + def remove_tls(self): + assert 'success' in self.conf({"pass": "routes"}, 'listeners/*:7080') + + def generate_ca_conf(self): + with open(option.temp_dir + '/ca.conf', 'w') as f: + f.write( + """[ ca ] +default_ca = myca + +[ myca ] +new_certs_dir = %(dir)s +database = %(database)s +default_md = sha256 +policy = myca_policy +serial = %(certserial)s +default_days = 1 +x509_extensions = myca_extensions +copy_extensions = copy + +[ myca_policy ] +commonName = optional + +[ myca_extensions ] +basicConstraints = critical,CA:TRUE""" + % { + 'dir': option.temp_dir, + 'database': option.temp_dir + '/certindex', + 'certserial': option.temp_dir + '/certserial', + } + ) + + with open(option.temp_dir + '/certserial', 'w') as f: + f.write('1000') + + with open(option.temp_dir + '/certindex', 'w') as f: + f.write('') + + def config_bundles(self, bundles): + self.certificate('root', False) + + for b in bundles: + self.openssl_conf(rewrite=True, alt_names=bundles[b]['alt_names']) + subj = ( + '/CN={}/'.format(bundles[b]['subj']) + if 'subj' in bundles[b] + else '/' + ) + + subprocess.call( + [ + 'openssl', + 'req', + '-new', + '-subj', + subj, + '-config', + option.temp_dir + '/openssl.conf', + '-out', + option.temp_dir + '/{}.csr'.format(b), + '-keyout', + option.temp_dir + '/{}.key'.format(b), + ], + stderr=subprocess.STDOUT, + ) + + self.generate_ca_conf() + + for b in bundles: + subj = ( + '/CN={}/'.format(bundles[b]['subj']) + if 'subj' in bundles[b] + else '/' + ) + + subprocess.call( + [ + 'openssl', + 'ca', + '-batch', + '-subj', + subj, + '-config', + option.temp_dir + '/ca.conf', + '-keyfile', + option.temp_dir + '/root.key', + '-cert', + option.temp_dir + '/root.crt', + '-in', + option.temp_dir + '/{}.csr'.format(b), + '-out', + option.temp_dir + '/{}.crt'.format(b), + ], + stderr=subprocess.STDOUT, + ) + + self.context = ssl.create_default_context() + self.context.check_hostname = False + self.context.verify_mode = ssl.CERT_REQUIRED + self.context.load_verify_locations(option.temp_dir + '/root.crt') + + self.load_certs(bundles) + + def load_certs(self, bundles): + for bname, bvalue in bundles.items(): + assert 'success' in self.certificate_load( + bname, bname + ), 'certificate {} upload'.format(bvalue['subj']) + + def check_cert(self, host, expect): + resp, sock = self.get_ssl( + headers={ + 'Host': host, + 'Content-Length': '0', + 'Connection': 'close', + }, + start=True, + ) + + assert resp['status'] == 200 + assert sock.getpeercert()['subject'][0][0][1] == expect + + def test_tls_sni(self): + bundles = { + "default": { + "subj": "default", + "alt_names": ["default"], + }, + "localhost.com": { + "subj": "localhost.com", + "alt_names": ["alt1.localhost.com"], + }, + "example.com": { + "subj": "example.com", + "alt_names": ["alt1.example.com", "alt2.example.com"], + }, + } + self.config_bundles(bundles) + self.add_tls(["default", "localhost.com", "example.com"]) + + self.check_cert('alt1.localhost.com', bundles['localhost.com']['subj']) + self.check_cert('alt2.example.com', bundles['example.com']['subj']) + self.check_cert('blah', bundles['default']['subj']) + + def test_tls_sni_upper_case(self): + bundles = { + "localhost.com": {"subj": "LOCALHOST.COM", "alt_names": []}, + "example.com": { + "subj": "example.com", + "alt_names": ["ALT1.EXAMPLE.COM", "*.ALT2.EXAMPLE.COM"], + }, + } + self.config_bundles(bundles) + self.add_tls(["localhost.com", "example.com"]) + + self.check_cert('localhost.com', bundles['localhost.com']['subj']) + self.check_cert('LOCALHOST.COM', bundles['localhost.com']['subj']) + self.check_cert('EXAMPLE.COM', bundles['localhost.com']['subj']) + self.check_cert('ALT1.EXAMPLE.COM', bundles['example.com']['subj']) + self.check_cert('WWW.ALT2.EXAMPLE.COM', bundles['example.com']['subj']) + + def test_tls_sni_only_bundle(self): + bundles = { + "localhost.com": { + "subj": "localhost.com", + "alt_names": ["alt1.localhost.com", "alt2.localhost.com"], + } + } + self.config_bundles(bundles) + self.add_tls(["localhost.com"]) + + self.check_cert('domain.com', bundles['localhost.com']['subj']) + self.check_cert('alt1.domain.com', bundles['localhost.com']['subj']) + + def test_tls_sni_wildcard(self): + bundles = { + "localhost.com": { + "subj": "localhost.com", + "alt_names": [], + }, + "example.com": { + "subj": "example.com", + "alt_names": ["*.example.com", "*.alt.example.com"], + }, + } + self.config_bundles(bundles) + self.add_tls(["localhost.com", "example.com"]) + + self.check_cert('example.com', bundles['localhost.com']['subj']) + self.check_cert('www.example.com', bundles['example.com']['subj']) + self.check_cert('alt.example.com', bundles['example.com']['subj']) + self.check_cert('www.alt.example.com', bundles['example.com']['subj']) + self.check_cert('www.alt.example.ru', bundles['localhost.com']['subj']) + + def test_tls_sni_duplicated_bundle(self): + bundles = { + "localhost.com": { + "subj": "localhost.com", + "alt_names": ["localhost.com", "alt2.localhost.com"], + } + } + self.config_bundles(bundles) + self.add_tls(["localhost.com", "localhost.com"]) + + self.check_cert('localhost.com', bundles['localhost.com']['subj']) + self.check_cert('alt2.localhost.com', bundles['localhost.com']['subj']) + + def test_tls_sni_same_alt(self): + bundles = { + "localhost": {"subj": "subj1", "alt_names": "same.altname.com"}, + "example": {"subj": "subj2", "alt_names": "same.altname.com"}, + } + self.config_bundles(bundles) + self.add_tls(["localhost", "example"]) + + self.check_cert('localhost', bundles['localhost']['subj']) + self.check_cert('example', bundles['localhost']['subj']) + + def test_tls_sni_empty_cn(self): + bundles = { + "localhost": { + "alt_names": ["alt.localhost.com"], + } + } + self.config_bundles(bundles) + self.add_tls(["localhost"]) + + resp, sock = self.get_ssl( + headers={ + 'Host': 'domain.com', + 'Content-Length': '0', + 'Connection': 'close', + }, + start=True, + ) + + assert resp['status'] == 200 + assert sock.getpeercert()['subjectAltName'][0][1] == 'alt.localhost.com' + + def test_tls_sni_invalid(self): + self.config_bundles({"localhost": {"subj": "subj1", "alt_names": ''}}) + self.add_tls(["localhost"]) + + def check_certificate(cert): + assert 'error' in self.conf( + {"pass": "routes", "tls": {"certificate": cert}}, + 'listeners/*:7080', + ) + + check_certificate('') + check_certificate('blah') + check_certificate([]) + check_certificate(['blah']) + check_certificate(['localhost', 'blah']) + check_certificate(['localhost', []]) diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index b0cd5abb..490ae916 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -63,19 +63,34 @@ class TestApplicationTLS(TestApplicationProto): return ssl.get_server_certificate(addr, ssl_version=ssl_version) - def openssl_conf(self): + def openssl_conf(self, rewrite=False, alt_names=[]): conf_path = option.temp_dir + '/openssl.conf' - if os.path.exists(conf_path): + if not rewrite and os.path.exists(conf_path): return + # Generates alt_names section with dns names + a_names = "[alt_names]\n" + for i, k in enumerate(alt_names, 1): + a_names += "DNS.%d = %s\n" % (i, k) + + # Generates section for sign request extension + a_sec = """req_extensions = myca_req_extensions + +[ myca_req_extensions ] +subjectAltName = @alt_names + +{a_names}""".format(a_names=a_names) + with open(conf_path, 'w') as f: f.write( """[ req ] default_bits = 2048 encrypt_key = no distinguished_name = req_distinguished_name -[ req_distinguished_name ]""" + +{a_sec} +[ req_distinguished_name ]""".format(a_sec=a_sec if alt_names else "") ) def load(self, script, name=None): diff --git a/test/unit/http.py b/test/unit/http.py index 57e6ed3a..7706fe05 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -44,7 +44,8 @@ class TestHTTP(): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if 'wrapper' in kwargs: - sock = kwargs['wrapper'](sock) + server_hostname = headers.get('Host', 'localhost') + sock = kwargs['wrapper'](sock, server_hostname=server_hostname) connect_args = addr if sock_type == 'unix' else (addr, port) try: -- cgit From 0ae75733f7e63c7f2c190edb1425c0031262dc71 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 31 Mar 2021 03:24:01 +0100 Subject: Tests: added file descriptor leak detection. --- test/conftest.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_respawn.py | 6 ++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 20ac6e81..7b3314e2 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -56,6 +56,12 @@ def pytest_addoption(parser): type=str, help="Default user for non-privileged processes of unitd", ) + parser.addoption( + "--fds-threshold", + type=int, + default=0, + help="File descriptors threshold", + ) parser.addoption( "--restart", default=False, @@ -67,12 +73,23 @@ def pytest_addoption(parser): unit_instance = {} unit_log_copy = "unit.log.copy" _processes = [] +_fds_check = { + 'main': {'fds': 0, 'skip': False}, + 'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False}, + 'controller': { + 'name': 'unit: controller', + 'pid': -1, + 'fds': 0, + 'skip': False, + }, +} http = TestHTTP() def pytest_configure(config): option.config = config.option option.detailed = config.option.detailed + option.fds_threshold = config.option.fds_threshold option.print_log = config.option.print_log option.save_log = config.option.save_log option.unsafe = config.option.unsafe @@ -257,6 +274,10 @@ def run(request): ] option.skip_sanitizer = False + _fds_check['main']['skip'] = False + _fds_check['router']['skip'] = False + _fds_check['router']['skip'] = False + yield # stop unit @@ -304,6 +325,50 @@ def run(request): else: shutil.rmtree(path) + # check descriptors (wait for some time before check) + + def waitforfds(diff): + for i in range(600): + fds_diff = diff() + + if fds_diff <= option.fds_threshold: + break + + time.sleep(0.1) + + return fds_diff + + ps = _fds_check['main'] + if not ps['skip']: + fds_diff = waitforfds( + lambda: _count_fds(unit_instance['pid']) - ps['fds'] + ) + ps['fds'] += fds_diff + + assert ( + fds_diff <= option.fds_threshold + ), 'descriptors leak main process' + + else: + ps['fds'] = _count_fds(unit_instance['pid']) + + for name in ['controller', 'router']: + ps = _fds_check[name] + ps_pid = ps['pid'] + ps['pid'] = pid_by_name(ps['name']) + + if not ps['skip']: + fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) + ps['fds'] += fds_diff + + assert ps['pid'] == ps_pid, 'same pid %s' % name + assert fds_diff <= option.fds_threshold, ( + 'descriptors leak %s' % name + ) + + else: + ps['fds'] = _count_fds(ps['pid']) + # print unit.log in case of error if hasattr(request.node, 'rep_call') and request.node.rep_call.failed: @@ -371,6 +436,21 @@ def unit_run(): unit_instance['control_sock'] = temp_dir + '/control.unit.sock' unit_instance['unitd'] = unitd + with open(temp_dir + '/unit.pid', 'r') as f: + unit_instance['pid'] = f.read().rstrip() + + _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock') + + _fds_check['main']['fds'] = _count_fds(unit_instance['pid']) + + router = _fds_check['router'] + router['pid'] = pid_by_name(router['name']) + router['fds'] = _count_fds(router['pid']) + + controller = _fds_check['controller'] + controller['pid'] = pid_by_name(controller['name']) + controller['fds'] = _count_fds(controller['pid']) + return unit_instance @@ -492,6 +572,32 @@ def _clear_conf(sock, log=None): check_success(resp) +def _count_fds(pid): + procfile = '/proc/%s/fd' % pid + if os.path.isdir(procfile): + return len(os.listdir(procfile)) + + try: + out = subprocess.check_output( + ['procstat', '-f', pid], stderr=subprocess.STDOUT, + ).decode() + return len(out.splitlines()) + + except (FileNotFoundError, subprocess.CalledProcessError): + pass + + try: + out = subprocess.check_output( + ['lsof', '-n', '-p', pid], stderr=subprocess.STDOUT, + ).decode() + return len(out.splitlines()) + + except (FileNotFoundError, subprocess.CalledProcessError): + pass + + return 0 + + def run_process(target, *args): global _processes @@ -517,6 +623,18 @@ def stop_processes(): return 'Fail to stop process(es)' +def pid_by_name(name): + output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode() + m = re.search( + r'\s*(\d+)\s*' + str(unit_instance['pid']) + r'.*' + name, output + ) + return None if m is None else m.group(1) + + +def find_proc(name, ps_output): + return re.findall(str(unit_instance['pid']) + r'.*' + name, ps_output) + + @pytest.fixture() def skip_alert(): def _skip(*alerts): @@ -525,6 +643,16 @@ def skip_alert(): return _skip +@pytest.fixture() +def skip_fds_check(): + def _skip(main=False, router=False, controller=False): + _fds_check['main']['skip'] = main + _fds_check['router']['skip'] = router + _fds_check['controller']['skip'] = controller + + return _skip + + @pytest.fixture def temp_dir(request): return unit_instance['temp_dir'] diff --git a/test/test_respawn.py b/test/test_respawn.py index ed85ee95..50c19186 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -58,7 +58,8 @@ class TestRespawn(TestApplicationPython): assert len(self.find_proc(self.PATTERN_CONTROLLER, unit_pid, out)) == 1 assert len(self.find_proc(self.app_name, unit_pid, out)) == 1 - def test_respawn_router(self, skip_alert, unit_pid): + def test_respawn_router(self, skip_alert, unit_pid, skip_fds_check): + skip_fds_check(router=True) pid = self.pid_by_name(self.PATTERN_ROUTER, unit_pid) self.kill_pids(pid) @@ -68,7 +69,8 @@ class TestRespawn(TestApplicationPython): self.smoke_test(unit_pid) - def test_respawn_controller(self, skip_alert, unit_pid): + def test_respawn_controller(self, skip_alert, unit_pid, skip_fds_check): + skip_fds_check(controller=True) pid = self.pid_by_name(self.PATTERN_CONTROLLER, unit_pid) self.kill_pids(pid) -- cgit From a5eca0b83fec1ec26f8f4a1e471be3b78772cef9 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 31 Mar 2021 18:39:42 +0300 Subject: Packages: fixed "dist" target to include man page in the archive. --- pkg/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/Makefile b/pkg/Makefile index 15ff075d..4cf9ff80 100644 --- a/pkg/Makefile +++ b/pkg/Makefile @@ -14,7 +14,7 @@ dist: hg archive unit-$(VERSION).tar.gz \ -r $(VERSION) \ -p unit-$(VERSION) \ - -X "../.hg*" -X "../pkg/" -X "../docs/" + -X "../.hg*" -X "../pkg/" -X "../docs/*.*" -X "../docs/Makefile" $(SHA512SUM) unit-$(VERSION).tar.gz > unit-$(VERSION).tar.gz.sha512 rpm: -- cgit From f43265ba2c0c4a08a3c513fb11dca9835388a001 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 31 Mar 2021 23:42:00 +0100 Subject: Tests: removed skip_alert(). --- test/test_routing.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test_routing.py b/test/test_routing.py index be9a1faf..cb9c3fd2 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1362,10 +1362,7 @@ class TestRouting(TestApplicationProto): assert self.get(url='/?var2=val2')['status'] == 404, 'arr 7' assert self.get(url='/?var3=foo')['status'] == 200, 'arr 8' - def test_routes_match_arguments_invalid(self, skip_alert): - # TODO remove it after controller fixed - skip_alert(r'failed to apply new conf') - + def test_routes_match_arguments_invalid(self): self.route_match_invalid({"arguments": ["var"]}) self.route_match_invalid({"arguments": [{"var1": {}}]}) self.route_match_invalid({"arguments": {"": "bar"}}) -- cgit From 46d8567dd7c2a9af025f1de13084a9efd7118e20 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Thu, 1 Apr 2021 00:05:44 +0100 Subject: Tests: unset LC_ALL variable in Ruby encoding test. This change is necessary to set Encoding.default_external value correctly. --- test/test_ruby_application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 12a078b7..17491619 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -250,7 +250,8 @@ class TestRubyApplication(TestApplicationRuby): def check_locale(enc): assert 'success' in self.conf( - {"LC_CTYPE": enc}, '/config/applications/encoding/environment', + {"LC_CTYPE": enc, "LC_ALL": ""}, + '/config/applications/encoding/environment', ) resp = self.get() -- cgit From 6c97a1a069f0ae8cf683c8364fb7f9dabc5e89cb Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 5 Apr 2021 14:03:05 +0100 Subject: Tests: style. --- test/conftest.py | 38 +++--- test/python/atexit/wsgi.py | 1 + test/python/body_io/wsgi.py | 1 + test/python/callable/wsgi.py | 1 + test/python/ctx_iter_atexit/wsgi.py | 16 ++- test/python/custom_header/wsgi.py | 11 +- test/python/delayed/asgi.py | 27 ++-- test/python/empty/asgi.py | 14 +- test/python/environment/wsgi.py | 1 + test/python/header_fields/wsgi.py | 7 +- test/python/host/wsgi.py | 13 +- test/python/iter_exception/wsgi.py | 4 +- test/python/legacy/asgi.py | 15 ++- test/python/legacy_force/asgi.py | 15 ++- test/python/mirror/asgi.py | 19 ++- test/python/mirror/wsgi.py | 4 +- test/python/path/wsgi.py | 1 + test/python/query_string/asgi.py | 18 +-- test/python/query_string/wsgi.py | 11 +- test/python/server_port/asgi.py | 18 +-- test/python/server_port/wsgi.py | 8 +- test/python/threading/asgi.py | 14 +- test/python/threads/asgi.py | 19 +-- test/python/threads/wsgi.py | 14 +- test/python/upload/wsgi.py | 10 +- test/python/user_group/wsgi.py | 17 +-- test/python/variables/asgi.py | 39 +++--- test/python/variables/wsgi.py | 33 ++--- test/python/websockets/mirror/asgi.py | 18 +-- test/python/websockets/subprotocol/asgi.py | 30 +++-- test/test_access_log.py | 1 + test/test_asgi_application.py | 13 +- test/test_asgi_lifespan.py | 1 + test/test_asgi_websockets.py | 208 +++++++++++++++-------------- test/test_configuration.py | 1 + test/test_go_application.py | 8 +- test/test_go_isolation.py | 14 +- test/test_go_isolation_rootfs.py | 1 + test/test_http_header.py | 1 + test/test_java_application.py | 11 +- test/test_java_isolation_rootfs.py | 1 + test/test_java_websockets.py | 197 ++++++++++++++------------- test/test_node_application.py | 5 +- test/test_node_websockets.py | 197 ++++++++++++++------------- test/test_perl_application.py | 1 + test/test_php_application.py | 14 +- test/test_php_isolation.py | 5 +- test/test_proxy.py | 10 +- test/test_python_application.py | 10 +- test/test_python_isolation.py | 18 +-- test/test_python_isolation_chroot.py | 4 +- test/test_python_procman.py | 1 + test/test_respawn.py | 7 +- test/test_routing.py | 11 +- test/test_ruby_application.py | 12 +- test/test_ruby_isolation.py | 1 + test/test_settings.py | 1 + test/test_share_fallback.py | 6 +- test/test_static.py | 11 +- test/test_tls.py | 3 +- test/test_tls_sni.py | 28 ++-- test/test_variables.py | 12 +- test/unit/applications/tls.py | 22 ++- test/unit/applications/websockets.py | 18 +-- test/unit/check/isolation.py | 4 +- test/unit/http.py | 23 ++-- test/unit/option.py | 3 +- 67 files changed, 698 insertions(+), 623 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 7b3314e2..38e1138e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -6,7 +6,6 @@ import platform import re import shutil import signal -import socket import stat import subprocess import sys @@ -15,11 +14,12 @@ import time from multiprocessing import Process import pytest + from unit.check.go import check_go from unit.check.isolation import check_isolation from unit.check.node import check_node -from unit.check.tls import check_openssl from unit.check.regex import check_regex +from unit.check.tls import check_openssl from unit.http import TestHTTP from unit.option import option from unit.utils import public_dir @@ -85,6 +85,7 @@ _fds_check = { } http = TestHTTP() + def pytest_configure(config): option.config = config.option @@ -115,9 +116,11 @@ def pytest_configure(config): def pytest_generate_tests(metafunc): cls = metafunc.cls - if (not hasattr(cls, 'application_type') - or cls.application_type == None - or cls.application_type == 'external'): + if ( + not hasattr(cls, 'application_type') + or cls.application_type == None + or cls.application_type == 'external' + ): return type = cls.application_type @@ -216,6 +219,7 @@ def pytest_sessionstart(session): elif option.save_log: open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'w').close() + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): # execute all other hooks to obtain the report object @@ -320,7 +324,9 @@ def run(request): public_dir(path) - if os.path.isfile(path) or stat.S_ISSOCK(os.stat(path).st_mode): + if os.path.isfile(path) or stat.S_ISSOCK( + os.stat(path).st_mode + ): os.remove(path) else: shutil.rmtree(path) @@ -384,6 +390,7 @@ def run(request): _check_alerts(log=log) + def unit_run(): global unit_instance @@ -482,7 +489,6 @@ def unit_stop(): return 'Could not terminate unit' - def _check_alerts(path=None, log=None): if path is None: path = unit_instance['log'] @@ -554,24 +560,21 @@ def _clear_conf(sock, log=None): return try: - certs = json.loads(http.get( - url='/certificates', - sock_type='unix', - addr=sock, - )['body']).keys() + certs = json.loads( + http.get(url='/certificates', sock_type='unix', addr=sock,)['body'] + ).keys() except json.JSONDecodeError: pytest.fail('Can\'t parse certificates list.') for cert in certs: resp = http.delete( - url='/certificates/' + cert, - sock_type='unix', - addr=sock, + url='/certificates/' + cert, sock_type='unix', addr=sock, )['body'] check_success(resp) + def _count_fds(pid): procfile = '/proc/%s/fd' % pid if os.path.isdir(procfile): @@ -606,6 +609,7 @@ def run_process(target, *args): _processes.append(process) + def stop_processes(): if not _processes: return @@ -657,18 +661,22 @@ def skip_fds_check(): def temp_dir(request): return unit_instance['temp_dir'] + @pytest.fixture def is_unsafe(request): return request.config.getoption("--unsafe") + @pytest.fixture def is_su(request): return os.geteuid() == 0 + @pytest.fixture def unit_pid(request): return unit_instance['process'].pid + def pytest_sessionfinish(session): if not option.restart and option.save_log: print('Path to unit.log:\n' + unit_instance['log'] + '\n') diff --git a/test/python/atexit/wsgi.py b/test/python/atexit/wsgi.py index a5a9918d..b64b8477 100644 --- a/test/python/atexit/wsgi.py +++ b/test/python/atexit/wsgi.py @@ -1,5 +1,6 @@ import atexit + def application(environ, start_response): def at_exit(): environ['wsgi.errors'].write('At exit called.\n') diff --git a/test/python/body_io/wsgi.py b/test/python/body_io/wsgi.py index 14303b5f..58ce76a4 100644 --- a/test/python/body_io/wsgi.py +++ b/test/python/body_io/wsgi.py @@ -1,5 +1,6 @@ import io + def application(env, start_response): start_response('200', [('Content-Length', '10')]) f = io.BytesIO(b'0123456789') diff --git a/test/python/callable/wsgi.py b/test/python/callable/wsgi.py index 365f82fa..374ecb28 100644 --- a/test/python/callable/wsgi.py +++ b/test/python/callable/wsgi.py @@ -2,6 +2,7 @@ def application(env, start_response): start_response('204', [('Content-Length', '0')]) return [] + def app(env, start_response): start_response('200', [('Content-Length', '0')]) return [] diff --git a/test/python/ctx_iter_atexit/wsgi.py b/test/python/ctx_iter_atexit/wsgi.py index d0b33daa..b2f12c35 100644 --- a/test/python/ctx_iter_atexit/wsgi.py +++ b/test/python/ctx_iter_atexit/wsgi.py @@ -1,5 +1,6 @@ import atexit + class application: def __init__(self, environ, start_response): self.environ = environ @@ -11,13 +12,14 @@ class application: content_length = int(self.environ.get('CONTENT_LENGTH', 0)) body = bytes(self.environ['wsgi.input'].read(content_length)) - self.start('200', [ - ('Content-Type', self.environ.get('CONTENT_TYPE')), - ('Content-Length', str(len(body))) - ]) + self.start( + '200', + [ + ('Content-Type', self.environ.get('CONTENT_TYPE')), + ('Content-Length', str(len(body))), + ], + ) yield body def _atexit(self): - self.start('200', [ - ('Content-Length', '0') - ]) + self.start('200', [('Content-Length', '0')]) diff --git a/test/python/custom_header/wsgi.py b/test/python/custom_header/wsgi.py index 44f145d1..44fc2af5 100644 --- a/test/python/custom_header/wsgi.py +++ b/test/python/custom_header/wsgi.py @@ -1,7 +1,10 @@ def application(environ, start_response): - start_response('200', [ - ('Content-Length', '0'), - ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')) - ]) + start_response( + '200', + [ + ('Content-Length', '0'), + ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')), + ], + ) return [] diff --git a/test/python/delayed/asgi.py b/test/python/delayed/asgi.py index d5cad929..1cb15a92 100644 --- a/test/python/delayed/asgi.py +++ b/test/python/delayed/asgi.py @@ -1,5 +1,6 @@ import asyncio + async def application(scope, receive, send): assert scope['type'] == 'http' @@ -28,13 +29,13 @@ async def application(scope, receive, send): loop.call_later(n, future.set_result, None) await future - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', str(len(body)).encode()), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', str(len(body)).encode()),], + } + ) if not body: await sleep(delay) @@ -42,10 +43,12 @@ async def application(scope, receive, send): step = int(len(body) / parts) for i in range(0, len(body), step): - await send({ - 'type': 'http.response.body', - 'body': body[i : i + step], - 'more_body': True, - }) + await send( + { + 'type': 'http.response.body', + 'body': body[i : i + step], + 'more_body': True, + } + ) await sleep(delay) diff --git a/test/python/empty/asgi.py b/test/python/empty/asgi.py index 58b7c1f2..14a40629 100644 --- a/test/python/empty/asgi.py +++ b/test/python/empty/asgi.py @@ -1,10 +1,10 @@ async def application(scope, receive, send): assert scope['type'] == 'http' - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0')], + } + ) diff --git a/test/python/environment/wsgi.py b/test/python/environment/wsgi.py index fa3a1d2b..d1564f29 100644 --- a/test/python/environment/wsgi.py +++ b/test/python/environment/wsgi.py @@ -1,5 +1,6 @@ import os + def application(env, start_response): body = '' vars = env.get('HTTP_X_VARIABLES').split(',') diff --git a/test/python/header_fields/wsgi.py b/test/python/header_fields/wsgi.py index bd1ba0e2..41144528 100644 --- a/test/python/header_fields/wsgi.py +++ b/test/python/header_fields/wsgi.py @@ -2,8 +2,7 @@ def application(environ, start_response): h = (k for k, v in environ.items() if k.startswith('HTTP_')) - start_response('200', [ - ('Content-Length', '0'), - ('All-Headers', ','.join(h)) - ]) + start_response( + '200', [('Content-Length', '0'), ('All-Headers', ','.join(h))] + ) return [] diff --git a/test/python/host/wsgi.py b/test/python/host/wsgi.py index db7de306..0a08bc36 100644 --- a/test/python/host/wsgi.py +++ b/test/python/host/wsgi.py @@ -1,7 +1,10 @@ def application(env, start_response): - start_response('200', [ - ('Content-Length', '0'), - ('X-Server-Name', env.get('SERVER_NAME')), - ('X-Http-Host', str(env.get('HTTP_HOST'))) - ]) + start_response( + '200', + [ + ('Content-Length', '0'), + ('X-Server-Name', env.get('SERVER_NAME')), + ('X-Http-Host', str(env.get('HTTP_HOST'))), + ], + ) return [] diff --git a/test/python/iter_exception/wsgi.py b/test/python/iter_exception/wsgi.py index 66a09af7..2779a845 100644 --- a/test/python/iter_exception/wsgi.py +++ b/test/python/iter_exception/wsgi.py @@ -8,7 +8,9 @@ class application: def __iter__(self): self.__i = 0 self._skip_level = int(self.environ.get('HTTP_X_SKIP', 0)) - self._not_skip_close = int(self.environ.get('HTTP_X_NOT_SKIP_CLOSE', 0)) + self._not_skip_close = int( + self.environ.get('HTTP_X_NOT_SKIP_CLOSE', 0) + ) self._is_chunked = self.environ.get('HTTP_X_CHUNKED') headers = [(('Content-Length', '10'))] diff --git a/test/python/legacy/asgi.py b/test/python/legacy/asgi.py index f065d026..1d45cc4f 100644 --- a/test/python/legacy/asgi.py +++ b/test/python/legacy/asgi.py @@ -3,11 +3,12 @@ def application(scope): return app_http + async def app_http(receive, send): - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0'),], + } + ) diff --git a/test/python/legacy_force/asgi.py b/test/python/legacy_force/asgi.py index 2e5859f2..ad2785f2 100644 --- a/test/python/legacy_force/asgi.py +++ b/test/python/legacy_force/asgi.py @@ -7,11 +7,12 @@ def application(scope, receive=None, send=None): else: return app_http(receive, send) + async def app_http(receive, send): - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0'),], + } + ) diff --git a/test/python/mirror/asgi.py b/test/python/mirror/asgi.py index 7088e893..18a66a4c 100644 --- a/test/python/mirror/asgi.py +++ b/test/python/mirror/asgi.py @@ -8,15 +8,12 @@ async def application(scope, receive, send): if not m.get('more_body', False): break - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', str(len(body)).encode()), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', str(len(body)).encode())], + } + ) - await send({ - 'type': 'http.response.body', - 'body': body, - }) + await send({'type': 'http.response.body', 'body': body}) diff --git a/test/python/mirror/wsgi.py b/test/python/mirror/wsgi.py index eb1fb922..3cd95437 100644 --- a/test/python/mirror/wsgi.py +++ b/test/python/mirror/wsgi.py @@ -3,7 +3,5 @@ def application(environ, start_response): content_length = int(environ.get('CONTENT_LENGTH', 0)) body = bytes(environ['wsgi.input'].read(content_length)) - start_response('200', [ - ('Content-Length', str(len(body))) - ]) + start_response('200', [('Content-Length', str(len(body)))]) return [body] diff --git a/test/python/path/wsgi.py b/test/python/path/wsgi.py index 2807f6ef..da7e1ff1 100644 --- a/test/python/path/wsgi.py +++ b/test/python/path/wsgi.py @@ -1,6 +1,7 @@ import os import sys + def application(environ, start_response): body = os.pathsep.join(sys.path).encode() diff --git a/test/python/query_string/asgi.py b/test/python/query_string/asgi.py index 28f4d107..5b659f9c 100644 --- a/test/python/query_string/asgi.py +++ b/test/python/query_string/asgi.py @@ -1,11 +1,13 @@ async def application(scope, receive, send): assert scope['type'] == 'http' - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - (b'query-string', scope['query_string']), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-length', b'0'), + (b'query-string', scope['query_string']), + ], + } + ) diff --git a/test/python/query_string/wsgi.py b/test/python/query_string/wsgi.py index 90f1c7ec..54a67b03 100644 --- a/test/python/query_string/wsgi.py +++ b/test/python/query_string/wsgi.py @@ -1,7 +1,10 @@ def application(environ, start_response): - start_response('200', [ - ('Content-Length', '0'), - ('Query-String', environ.get('QUERY_STRING')) - ]) + start_response( + '200', + [ + ('Content-Length', '0'), + ('Query-String', environ.get('QUERY_STRING')), + ], + ) return [] diff --git a/test/python/server_port/asgi.py b/test/python/server_port/asgi.py index e79ced00..810a182c 100644 --- a/test/python/server_port/asgi.py +++ b/test/python/server_port/asgi.py @@ -1,11 +1,13 @@ async def application(scope, receive, send): assert scope['type'] == 'http' - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - (b'server-port', str(scope['server'][1]).encode()), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-length', b'0'), + (b'server-port', str(scope['server'][1]).encode()), + ], + } + ) diff --git a/test/python/server_port/wsgi.py b/test/python/server_port/wsgi.py index 89cd82b3..c796da95 100644 --- a/test/python/server_port/wsgi.py +++ b/test/python/server_port/wsgi.py @@ -1,7 +1,7 @@ def application(environ, start_response): - start_response('200', [ - ('Content-Length', '0'), - ('Server-Port', environ.get('SERVER_PORT')) - ]) + start_response( + '200', + [('Content-Length', '0'), ('Server-Port', environ.get('SERVER_PORT'))], + ) return [] diff --git a/test/python/threading/asgi.py b/test/python/threading/asgi.py index 3c978e50..c4169a24 100644 --- a/test/python/threading/asgi.py +++ b/test/python/threading/asgi.py @@ -33,10 +33,10 @@ async def application(scope, receive, send): Foo(Foo.num).start() Foo.num += 10 - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0')], + } + ) diff --git a/test/python/threads/asgi.py b/test/python/threads/asgi.py index d51ae431..ff4e52ad 100644 --- a/test/python/threads/asgi.py +++ b/test/python/threads/asgi.py @@ -2,6 +2,7 @@ import asyncio import time import threading + async def application(scope, receive, send): assert scope['type'] == 'http' @@ -17,11 +18,13 @@ async def application(scope, receive, send): time.sleep(delay) - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-length', b'0'), - (b'x-thread', str(threading.currentThread().ident).encode()), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-length', b'0'), + (b'x-thread', str(threading.currentThread().ident).encode()), + ], + } + ) diff --git a/test/python/threads/wsgi.py b/test/python/threads/wsgi.py index 1cc8ffe2..cc283cfe 100644 --- a/test/python/threads/wsgi.py +++ b/test/python/threads/wsgi.py @@ -1,15 +1,19 @@ import time import threading + def application(environ, start_response): delay = float(environ.get('HTTP_X_DELAY', 0)) time.sleep(delay) - start_response('200', [ - ('Content-Length', '0'), - ('Wsgi-Multithread', str(environ['wsgi.multithread'])), - ('X-Thread', str(threading.currentThread().ident)) - ]) + start_response( + '200', + [ + ('Content-Length', '0'), + ('Wsgi-Multithread', str(environ['wsgi.multithread'])), + ('X-Thread', str(threading.currentThread().ident)), + ], + ) return [] diff --git a/test/python/upload/wsgi.py b/test/python/upload/wsgi.py index 37ee89eb..953c5ecc 100644 --- a/test/python/upload/wsgi.py +++ b/test/python/upload/wsgi.py @@ -1,6 +1,7 @@ from tempfile import TemporaryFile import os, cgi + def read(environ): length = int(environ.get('CONTENT_LENGTH', 0)) @@ -11,6 +12,7 @@ def read(environ): environ['wsgi.input'] = body return body + def application(environ, start_response): file = read(environ) @@ -19,9 +21,9 @@ def application(environ, start_response): filename = form['file'].filename data = filename.encode() + form['file'].file.read() - start_response('200 OK', [ - ('Content-Type', 'text/plain'), - ('Content-Length', str(len(data))), - ]) + start_response( + '200 OK', + [('Content-Type', 'text/plain'), ('Content-Length', str(len(data)))], + ) return data diff --git a/test/python/user_group/wsgi.py b/test/python/user_group/wsgi.py index f5deb87d..4003c064 100644 --- a/test/python/user_group/wsgi.py +++ b/test/python/user_group/wsgi.py @@ -1,18 +1,19 @@ import json import os + def application(environ, start_response): uid = os.geteuid() gid = os.getegid() - out = json.dumps({ - 'UID': uid, - 'GID': gid, - }).encode('utf-8') + out = json.dumps({'UID': uid, 'GID': gid,}).encode('utf-8') - start_response('200 OK', [ - ('Content-Length', str(len(out))), - ('Content-Type', 'application/json') - ]) + start_response( + '200 OK', + [ + ('Content-Length', str(len(out))), + ('Content-Type', 'application/json'), + ], + ) return [out] diff --git a/test/python/variables/asgi.py b/test/python/variables/asgi.py index dd1cca72..5a4f55e8 100644 --- a/test/python/variables/asgi.py +++ b/test/python/variables/asgi.py @@ -17,24 +17,23 @@ async def application(scope, receive, send): res.append(h[1]) return b', '.join(res) - await send({ - 'type': 'http.response.start', - 'status': 200, - 'headers': [ - (b'content-type', get_header(b'content-type')), - (b'content-length', str(len(body)).encode()), - (b'request-method', scope['method'].encode()), - (b'request-uri', scope['path'].encode()), - (b'http-host', get_header(b'host')), - (b'http-version', scope['http_version'].encode()), - (b'asgi-version', scope['asgi']['version'].encode()), - (b'asgi-spec-version', scope['asgi']['spec_version'].encode()), - (b'scheme', scope['scheme'].encode()), - (b'custom-header', get_header(b'custom-header')), - ] - }) + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [ + (b'content-type', get_header(b'content-type')), + (b'content-length', str(len(body)).encode()), + (b'request-method', scope['method'].encode()), + (b'request-uri', scope['path'].encode()), + (b'http-host', get_header(b'host')), + (b'http-version', scope['http_version'].encode()), + (b'asgi-version', scope['asgi']['version'].encode()), + (b'asgi-spec-version', scope['asgi']['spec_version'].encode()), + (b'scheme', scope['scheme'].encode()), + (b'custom-header', get_header(b'custom-header')), + ], + } + ) - await send({ - 'type': 'http.response.body', - 'body': body, - }) + await send({'type': 'http.response.body', 'body': body}) diff --git a/test/python/variables/wsgi.py b/test/python/variables/wsgi.py index 53991e5e..5d77902d 100644 --- a/test/python/variables/wsgi.py +++ b/test/python/variables/wsgi.py @@ -3,19 +3,22 @@ def application(environ, start_response): content_length = int(environ.get('CONTENT_LENGTH', 0)) body = bytes(environ['wsgi.input'].read(content_length)) - start_response('200', [ - ('Content-Type', environ.get('CONTENT_TYPE')), - ('Content-Length', str(len(body))), - ('Request-Method', environ.get('REQUEST_METHOD')), - ('Request-Uri', environ.get('REQUEST_URI')), - ('Http-Host', environ.get('HTTP_HOST')), - ('Server-Protocol', environ.get('SERVER_PROTOCOL')), - ('Server-Software', environ.get('SERVER_SOFTWARE')), - ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')), - ('Wsgi-Version', str(environ['wsgi.version'])), - ('Wsgi-Url-Scheme', environ['wsgi.url_scheme']), - ('Wsgi-Multithread', str(environ['wsgi.multithread'])), - ('Wsgi-Multiprocess', str(environ['wsgi.multiprocess'])), - ('Wsgi-Run-Once', str(environ['wsgi.run_once'])) - ]) + start_response( + '200', + [ + ('Content-Type', environ.get('CONTENT_TYPE')), + ('Content-Length', str(len(body))), + ('Request-Method', environ.get('REQUEST_METHOD')), + ('Request-Uri', environ.get('REQUEST_URI')), + ('Http-Host', environ.get('HTTP_HOST')), + ('Server-Protocol', environ.get('SERVER_PROTOCOL')), + ('Server-Software', environ.get('SERVER_SOFTWARE')), + ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')), + ('Wsgi-Version', str(environ['wsgi.version'])), + ('Wsgi-Url-Scheme', environ['wsgi.url_scheme']), + ('Wsgi-Multithread', str(environ['wsgi.multithread'])), + ('Wsgi-Multiprocess', str(environ['wsgi.multiprocess'])), + ('Wsgi-Run-Once', str(environ['wsgi.run_once'])), + ], + ) return [body] diff --git a/test/python/websockets/mirror/asgi.py b/test/python/websockets/mirror/asgi.py index 0f1d9953..72a32d67 100644 --- a/test/python/websockets/mirror/asgi.py +++ b/test/python/websockets/mirror/asgi.py @@ -3,16 +3,16 @@ async def application(scope, receive, send): while True: m = await receive() if m['type'] == 'websocket.connect': - await send({ - 'type': 'websocket.accept', - }) + await send({'type': 'websocket.accept'}) if m['type'] == 'websocket.receive': - await send({ - 'type': 'websocket.send', - 'bytes': m.get('bytes', None), - 'text': m.get('text', None), - }) + await send( + { + 'type': 'websocket.send', + 'bytes': m.get('bytes', None), + 'text': m.get('text', None), + } + ) if m['type'] == 'websocket.disconnect': - break; + break diff --git a/test/python/websockets/subprotocol/asgi.py b/test/python/websockets/subprotocol/asgi.py index 92263dd7..0385bb9d 100644 --- a/test/python/websockets/subprotocol/asgi.py +++ b/test/python/websockets/subprotocol/asgi.py @@ -6,20 +6,24 @@ async def application(scope, receive, send): if m['type'] == 'websocket.connect': subprotocols = scope['subprotocols'] - await send({ - 'type': 'websocket.accept', - 'headers': [ - (b'x-subprotocols', str(subprotocols).encode()), - ], - 'subprotocol': subprotocols[0], - }) + await send( + { + 'type': 'websocket.accept', + 'headers': [ + (b'x-subprotocols', str(subprotocols).encode()), + ], + 'subprotocol': subprotocols[0], + } + ) if m['type'] == 'websocket.receive': - await send({ - 'type': 'websocket.send', - 'bytes': m.get('bytes', None), - 'text': m.get('text', None), - }) + await send( + { + 'type': 'websocket.send', + 'bytes': m.get('bytes', None), + 'text': m.get('text', None), + } + ) if m['type'] == 'websocket.disconnect': - break; + break diff --git a/test/test_access_log.py b/test/test_access_log.py index 65d5e50a..72a78c33 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,6 +1,7 @@ import time import pytest + from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py index 886a160b..70b2e4c3 100644 --- a/test/test_asgi_application.py +++ b/test/test_asgi_application.py @@ -3,13 +3,15 @@ import time from distutils.version import LooseVersion import pytest + from unit.applications.lang.python import TestApplicationPython from unit.option import option class TestASGIApplication(TestApplicationPython): - prerequisites = {'modules': {'python': - lambda v: LooseVersion(v) >= LooseVersion('3.5')}} + prerequisites = { + 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')} + } load_module = 'asgi' def findall(self, pattern): @@ -31,7 +33,8 @@ Content-Type: text/html Connection: close custom-header: BLAH -%s""" % (len(body), body.encode()), +%s""" + % (len(body), body.encode()), raw=True, ) @@ -145,7 +148,7 @@ custom-header: BLAH assert 'success' in self.conf( '{"http":{"max_body_size": ' + str(max_body_size) + ' }}', - 'settings' + 'settings', ) assert self.get()['status'] == 200, 'init' @@ -398,7 +401,7 @@ Connection: close socks.append(sock) - time.sleep(1.0) # required to avoid greedy request reading + time.sleep(1.0) # required to avoid greedy request reading threads = set() diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py index 3ecece43..43286e22 100644 --- a/test/test_asgi_lifespan.py +++ b/test/test_asgi_lifespan.py @@ -2,6 +2,7 @@ import os from distutils.version import LooseVersion import pytest + from conftest import unit_stop from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py index 7218d526..140bcb9a 100644 --- a/test/test_asgi_websockets.py +++ b/test/test_asgi_websockets.py @@ -3,14 +3,16 @@ import time from distutils.version import LooseVersion import pytest + from unit.applications.lang.python import TestApplicationPython from unit.applications.websockets import TestApplicationWebsocket from unit.option import option class TestASGIWebsockets(TestApplicationPython): - prerequisites = {'modules': {'python': - lambda v: LooseVersion(v) >= LooseVersion('3.5')}} + prerequisites = { + 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')} + } load_module = 'asgi' ws = TestApplicationWebsocket() @@ -74,7 +76,9 @@ class TestASGIWebsockets(TestApplicationPython): sock.close() assert resp['status'] == 101, 'status' - assert resp['headers']['x-subprotocols'] == "('chat', 'phone', 'video')", 'subprotocols' + assert ( + resp['headers']['x-subprotocols'] == "('chat', 'phone', 'video')" + ), 'subprotocols' assert resp['headers']['sec-websocket-protocol'] == 'chat', 'key' def test_asgi_websockets_mirror(self): @@ -159,7 +163,7 @@ class TestASGIWebsockets(TestApplicationPython): self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False) self.ws.frame_write( - sock, self.ws.OP_CONT, 'fragment2', length=2**64 - 1 + sock, self.ws.OP_CONT, 'fragment2', length=2 ** 64 - 1 ) self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE @@ -231,7 +235,7 @@ class TestASGIWebsockets(TestApplicationPython): @pytest.mark.skip('not yet') def test_asgi_websockets_handshake_upgrade_absent( - self + self, ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets/mirror') @@ -324,7 +328,9 @@ class TestASGIWebsockets(TestApplicationPython): }, ) - assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + assert ( + resp['status'] == 400 + ), 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 def test_asgi_websockets_handshake_method_invalid(self): self.load('websockets/mirror') @@ -419,14 +425,14 @@ class TestASGIWebsockets(TestApplicationPython): frame = self.ws.frame_read(sock) self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_1_1 - check_length(125) # 1_1_2 - check_length(126) # 1_1_3 - check_length(127) # 1_1_4 - check_length(128) # 1_1_5 - check_length(65535) # 1_1_6 - check_length(65536) # 1_1_7 - check_length(65536, chopsize = 997) # 1_1_8 + check_length(0) # 1_1_1 + check_length(125) # 1_1_2 + check_length(126) # 1_1_3 + check_length(127) # 1_1_4 + check_length(128) # 1_1_5 + check_length(65535) # 1_1_6 + check_length(65536) # 1_1_7 + check_length(65536, chopsize=997) # 1_1_8 self.close_connection(sock) @@ -445,14 +451,14 @@ class TestASGIWebsockets(TestApplicationPython): self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_2_1 - check_length(125) # 1_2_2 - check_length(126) # 1_2_3 - check_length(127) # 1_2_4 - check_length(128) # 1_2_5 - check_length(65535) # 1_2_6 - check_length(65536) # 1_2_7 - check_length(65536, chopsize = 997) # 1_2_8 + check_length(0) # 1_2_1 + check_length(125) # 1_2_2 + check_length(126) # 1_2_3 + check_length(127) # 1_2_4 + check_length(128) # 1_2_5 + check_length(65535) # 1_2_6 + check_length(65536) # 1_2_7 + check_length(65536, chopsize=997) # 1_2_8 self.close_connection(sock) @@ -470,11 +476,11 @@ class TestASGIWebsockets(TestApplicationPython): self.check_frame(frame, True, op_pong, payload, decode=decode) - check_ping('') # 2_1 - check_ping('Hello, world!') # 2_2 + check_ping('') # 2_1 + check_ping('Hello, world!') # 2_2 check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3 - check_ping(b'\xfe' * 125, decode=False) # 2_4 - check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 + check_ping(b'\xfe' * 125, decode=False) # 2_4 + check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 self.close_connection(sock) @@ -935,7 +941,9 @@ class TestASGIWebsockets(TestApplicationPython): frame = self.ws.frame_read(sock) if frame['opcode'] == self.ws.OP_TEXT: - self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + self.check_frame( + frame, True, self.ws.OP_TEXT, 'fragment1fragment2' + ) frame = None self.check_close(sock, 1002, frame=frame) @@ -1092,27 +1100,27 @@ class TestASGIWebsockets(TestApplicationPython): self.close_connection(sock) -# Unit does not support UTF-8 validation -# -# # 6_3_1 FAIL -# -# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' -# payload_2 = '\xed\xa0\x80' -# payload_3 = '\x65\x64\x69\x74\x65\x64' -# -# payload = payload_1 + payload_2 + payload_3 -# -# self.ws.message(sock, self.ws.OP_TEXT, payload) -# self.check_close(sock, 1007) -# -# # 6_3_2 FAIL -# -# _, sock, _ = self.ws.upgrade() -# -# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) -# self.check_close(sock, 1007) -# -# # 6_4_1 ... 6_4_4 FAIL + # Unit does not support UTF-8 validation + # + # # 6_3_1 FAIL + # + # payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' + # payload_2 = '\xed\xa0\x80' + # payload_3 = '\x65\x64\x69\x74\x65\x64' + # + # payload = payload_1 + payload_2 + payload_3 + # + # self.ws.message(sock, self.ws.OP_TEXT, payload) + # self.check_close(sock, 1007) + # + # # 6_3_2 FAIL + # + # _, sock, _ = self.ws.upgrade() + # + # self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + # self.check_close(sock, 1007) + # + # # 6_4_1 ... 6_4_4 FAIL def test_asgi_websockets_7_1_1__7_5_1(self): self.load('websockets/mirror') @@ -1239,15 +1247,15 @@ class TestASGIWebsockets(TestApplicationPython): self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) -# # 7_5_1 FAIL Unit does not support UTF-8 validation -# -# _, sock, _ = self.ws.upgrade() -# -# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ -# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') -# -# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) -# self.check_close(sock, 1007) + # # 7_5_1 FAIL Unit does not support UTF-8 validation + # + # _, sock, _ = self.ws.upgrade() + # + # payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ + # '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') + # + # self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + # self.check_close(sock, 1007) def test_asgi_websockets_7_7_X__7_9_X(self): self.load('websockets/mirror') @@ -1350,52 +1358,52 @@ class TestASGIWebsockets(TestApplicationPython): frame = self.ws.frame_read(sock, read_timeout=5) self.check_frame(frame, True, opcode, payload) - check_payload(op_text, 64 * 2 ** 10) # 9_1_1 - check_payload(op_text, 256 * 2 ** 10) # 9_1_2 - check_payload(op_text, 2 ** 20) # 9_1_3 - check_payload(op_text, 4 * 2 ** 20) # 9_1_4 - check_payload(op_text, 8 * 2 ** 20) # 9_1_5 - check_payload(op_text, 16 * 2 ** 20) # 9_1_6 + check_payload(op_text, 64 * 2 ** 10) # 9_1_1 + check_payload(op_text, 256 * 2 ** 10) # 9_1_2 + check_payload(op_text, 2 ** 20) # 9_1_3 + check_payload(op_text, 4 * 2 ** 20) # 9_1_4 + check_payload(op_text, 8 * 2 ** 20) # 9_1_5 + check_payload(op_text, 16 * 2 ** 20) # 9_1_6 - check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 - check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 - check_payload(op_binary, 2 ** 20) # 9_2_3 - check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 - check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 - check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 + check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 + check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 + check_payload(op_binary, 2 ** 20) # 9_2_3 + check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 + check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 + check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 if option.system != 'Darwin' and option.system != 'FreeBSD': - check_message(op_text, 64) # 9_3_1 - check_message(op_text, 256) # 9_3_2 - check_message(op_text, 2 ** 10) # 9_3_3 - check_message(op_text, 4 * 2 ** 10) # 9_3_4 - check_message(op_text, 16 * 2 ** 10) # 9_3_5 - check_message(op_text, 64 * 2 ** 10) # 9_3_6 - check_message(op_text, 256 * 2 ** 10) # 9_3_7 - check_message(op_text, 2 ** 20) # 9_3_8 - check_message(op_text, 4 * 2 ** 20) # 9_3_9 - - check_message(op_binary, 64) # 9_4_1 - check_message(op_binary, 256) # 9_4_2 - check_message(op_binary, 2 ** 10) # 9_4_3 - check_message(op_binary, 4 * 2 ** 10) # 9_4_4 - check_message(op_binary, 16 * 2 ** 10) # 9_4_5 - check_message(op_binary, 64 * 2 ** 10) # 9_4_6 - check_message(op_binary, 256 * 2 ** 10) # 9_4_7 - check_message(op_binary, 2 ** 20) # 9_4_8 - check_message(op_binary, 4 * 2 ** 20) # 9_4_9 - - check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 - check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 - check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 - check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 - check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 - check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 - - check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 - check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 - check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 - check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2 ** 10) # 9_3_3 + check_message(op_text, 4 * 2 ** 10) # 9_3_4 + check_message(op_text, 16 * 2 ** 10) # 9_3_5 + check_message(op_text, 64 * 2 ** 10) # 9_3_6 + check_message(op_text, 256 * 2 ** 10) # 9_3_7 + check_message(op_text, 2 ** 20) # 9_3_8 + check_message(op_text, 4 * 2 ** 20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2 ** 10) # 9_4_3 + check_message(op_binary, 4 * 2 ** 10) # 9_4_4 + check_message(op_binary, 16 * 2 ** 10) # 9_4_5 + check_message(op_binary, 64 * 2 ** 10) # 9_4_6 + check_message(op_binary, 256 * 2 ** 10) # 9_4_7 + check_message(op_binary, 2 ** 20) # 9_4_8 + check_message(op_binary, 4 * 2 ** 20) # 9_4_9 + + check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 + check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 + check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 + check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 + check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 check_payload(op_binary, 2 ** 20, chopsize=1024) # 9_6_5 check_payload(op_binary, 2 ** 20, chopsize=2048) # 9_6_6 diff --git a/test/test_configuration.py b/test/test_configuration.py index b7417264..7feb3adb 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,4 +1,5 @@ import pytest + from unit.control import TestControl diff --git a/test/test_go_application.py b/test/test_go_application.py index e833d190..438ce2e0 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -129,11 +129,9 @@ class TestGoApplication(TestApplicationGo): def test_go_application_command_line_arguments_type(self): self.load('command_line_arguments') - assert 'error' in \ - self.conf( - '' "a b c", 'applications/command_line_arguments/arguments' - ), \ - 'arguments type' + assert 'error' in self.conf( + '' "a b c", 'applications/command_line_arguments/arguments' + ), 'arguments type' def test_go_application_command_line_arguments_0(self): self.load('command_line_arguments') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index e8fa26c6..e02ef1cf 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -3,10 +3,12 @@ import os import pwd import pytest + from unit.applications.lang.go import TestApplicationGo from unit.option import option from unit.utils import getns + class TestGoIsolation(TestApplicationGo): prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} @@ -279,7 +281,7 @@ class TestGoIsolation(TestApplicationGo): isolation['namespaces'] = { 'mount': True, 'credential': True, - 'pid': True + 'pid': True, } self.load('ns_inspect', isolation=isolation) @@ -337,12 +339,10 @@ class TestGoIsolation(TestApplicationGo): isolation['namespaces'] = { 'mount': True, 'credential': True, - 'pid': True + 'pid': True, } - isolation['automount'] = { - 'tmpfs': False - } + isolation['automount'] = {'tmpfs': False} self.load('ns_inspect', isolation=isolation) @@ -352,9 +352,7 @@ class TestGoIsolation(TestApplicationGo): "/ /tmp" not in obj['Mounts'] and "tmpfs" not in obj['Mounts'] ), 'app has no /tmp mounted' - isolation['automount'] = { - 'tmpfs': True - } + isolation['automount'] = {'tmpfs': True} self.load('ns_inspect', isolation=isolation) diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py index 2bded5ec..1cc59c67 100644 --- a/test/test_go_isolation_rootfs.py +++ b/test/test_go_isolation_rootfs.py @@ -1,6 +1,7 @@ import os import pytest + from unit.applications.lang.go import TestApplicationGo diff --git a/test/test_http_header.py b/test/test_http_header.py index ca355eb7..fdb557cf 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,4 +1,5 @@ import pytest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_java_application.py b/test/test_java_application.py index 4a67f291..3fd5c26e 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -7,6 +7,7 @@ from unit.applications.lang.java import TestApplicationJava from unit.option import option from unit.utils import public_dir + class TestJavaApplication(TestApplicationJava): prerequisites = {'modules': {'java': 'all'}} @@ -712,9 +713,9 @@ class TestJavaApplication(TestApplicationJava): assert ( 'javax.servlet.include.request_uri: /data/test' in body ) == True, 'include request uri' - #assert ( + # assert ( # 'javax.servlet.include.context_path: ' in body - #) == True, 'include request context path' + # ) == True, 'include request context path' assert ( 'javax.servlet.include.servlet_path: /data' in body ) == True, 'include request servlet path' @@ -754,9 +755,9 @@ class TestJavaApplication(TestApplicationJava): assert ( 'javax.servlet.include.request_uri: null' in body ) == True, 'include request uri' - #assert ( + # assert ( # 'javax.servlet.include.context_path: null' in body - #) == True, 'include request context path' + # ) == True, 'include request context path' assert ( 'javax.servlet.include.servlet_path: null' in body ) == True, 'include request servlet path' @@ -1034,7 +1035,7 @@ class TestJavaApplication(TestApplicationJava): socks.append(sock) - time.sleep(0.25) # required to avoid greedy request reading + time.sleep(0.25) # required to avoid greedy request reading threads = set() diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py index 91773981..a401e23b 100644 --- a/test/test_java_isolation_rootfs.py +++ b/test/test_java_isolation_rootfs.py @@ -2,6 +2,7 @@ import os import subprocess import pytest + from unit.applications.lang.java import TestApplicationJava from unit.option import option diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index df9e0885..df0f76e8 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -2,6 +2,7 @@ import struct import time import pytest + from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket from unit.option import option @@ -163,7 +164,7 @@ class TestJavaWebsockets(TestApplicationJava): @pytest.mark.skip('not yet') def test_java_websockets_handshake_upgrade_absent( - self + self, ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets_mirror') @@ -256,7 +257,9 @@ class TestJavaWebsockets(TestApplicationJava): }, ) - assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + assert ( + resp['status'] == 400 + ), 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 def test_java_websockets_handshake_method_invalid(self): self.load('websockets_mirror') @@ -351,14 +354,14 @@ class TestJavaWebsockets(TestApplicationJava): frame = self.ws.message_read(sock) self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_1_1 - check_length(125) # 1_1_2 - check_length(126) # 1_1_3 - check_length(127) # 1_1_4 - check_length(128) # 1_1_5 - check_length(65535) # 1_1_6 - check_length(65536) # 1_1_7 - check_length(65536, chopsize = 997) # 1_1_8 + check_length(0) # 1_1_1 + check_length(125) # 1_1_2 + check_length(126) # 1_1_3 + check_length(127) # 1_1_4 + check_length(128) # 1_1_5 + check_length(65535) # 1_1_6 + check_length(65536) # 1_1_7 + check_length(65536, chopsize=997) # 1_1_8 self.close_connection(sock) @@ -377,14 +380,14 @@ class TestJavaWebsockets(TestApplicationJava): frame = self.ws.message_read(sock) self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_2_1 - check_length(125) # 1_2_2 - check_length(126) # 1_2_3 - check_length(127) # 1_2_4 - check_length(128) # 1_2_5 - check_length(65535) # 1_2_6 - check_length(65536) # 1_2_7 - check_length(65536, chopsize = 997) # 1_2_8 + check_length(0) # 1_2_1 + check_length(125) # 1_2_2 + check_length(126) # 1_2_3 + check_length(127) # 1_2_4 + check_length(128) # 1_2_5 + check_length(65535) # 1_2_6 + check_length(65536) # 1_2_7 + check_length(65536, chopsize=997) # 1_2_8 self.close_connection(sock) @@ -402,11 +405,11 @@ class TestJavaWebsockets(TestApplicationJava): self.check_frame(frame, True, op_pong, payload, decode=decode) - check_ping('') # 2_1 - check_ping('Hello, world!') # 2_2 + check_ping('') # 2_1 + check_ping('Hello, world!') # 2_2 check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3 - check_ping(b'\xfe' * 125, decode=False) # 2_4 - check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 + check_ping(b'\xfe' * 125, decode=False) # 2_4 + check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 self.close_connection(sock) @@ -867,7 +870,9 @@ class TestJavaWebsockets(TestApplicationJava): frame = self.ws.frame_read(sock) if frame['opcode'] == self.ws.OP_TEXT: - self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + self.check_frame( + frame, True, self.ws.OP_TEXT, 'fragment1fragment2' + ) frame = None self.check_close(sock, 1002, frame=frame) @@ -1024,27 +1029,27 @@ class TestJavaWebsockets(TestApplicationJava): self.close_connection(sock) -# Unit does not support UTF-8 validation -# -# # 6_3_1 FAIL -# -# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' -# payload_2 = '\xed\xa0\x80' -# payload_3 = '\x65\x64\x69\x74\x65\x64' -# -# payload = payload_1 + payload_2 + payload_3 -# -# self.ws.message(sock, self.ws.OP_TEXT, payload) -# self.check_close(sock, 1007) -# -# # 6_3_2 FAIL -# -# _, sock, _ = self.ws.upgrade() -# -# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) -# self.check_close(sock, 1007) -# -# # 6_4_1 ... 6_4_4 FAIL + # Unit does not support UTF-8 validation + # + # # 6_3_1 FAIL + # + # payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' + # payload_2 = '\xed\xa0\x80' + # payload_3 = '\x65\x64\x69\x74\x65\x64' + # + # payload = payload_1 + payload_2 + payload_3 + # + # self.ws.message(sock, self.ws.OP_TEXT, payload) + # self.check_close(sock, 1007) + # + # # 6_3_2 FAIL + # + # _, sock, _ = self.ws.upgrade() + # + # self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + # self.check_close(sock, 1007) + # + # # 6_4_1 ... 6_4_4 FAIL def test_java_websockets_7_1_1__7_5_1(self): self.load('websockets_mirror') @@ -1171,15 +1176,15 @@ class TestJavaWebsockets(TestApplicationJava): self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) -# # 7_5_1 FAIL Unit does not support UTF-8 validation -# -# _, sock, _ = self.ws.upgrade() -# -# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ -# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') -# -# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) -# self.check_close(sock, 1007) + # # 7_5_1 FAIL Unit does not support UTF-8 validation + # + # _, sock, _ = self.ws.upgrade() + # + # payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ + # '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') + # + # self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + # self.check_close(sock, 1007) def test_java_websockets_7_7_X__7_9_X(self): self.load('websockets_mirror') @@ -1282,52 +1287,52 @@ class TestJavaWebsockets(TestApplicationJava): frame = self.ws.frame_read(sock, read_timeout=5) self.check_frame(frame, True, opcode, payload) - check_payload(op_text, 64 * 2 ** 10) # 9_1_1 - check_payload(op_text, 256 * 2 ** 10) # 9_1_2 - check_payload(op_text, 2 ** 20) # 9_1_3 - check_payload(op_text, 4 * 2 ** 20) # 9_1_4 - check_payload(op_text, 8 * 2 ** 20) # 9_1_5 - check_payload(op_text, 16 * 2 ** 20) # 9_1_6 + check_payload(op_text, 64 * 2 ** 10) # 9_1_1 + check_payload(op_text, 256 * 2 ** 10) # 9_1_2 + check_payload(op_text, 2 ** 20) # 9_1_3 + check_payload(op_text, 4 * 2 ** 20) # 9_1_4 + check_payload(op_text, 8 * 2 ** 20) # 9_1_5 + check_payload(op_text, 16 * 2 ** 20) # 9_1_6 - check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 - check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 - check_payload(op_binary, 2 ** 20) # 9_2_3 - check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 - check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 - check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 + check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 + check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 + check_payload(op_binary, 2 ** 20) # 9_2_3 + check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 + check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 + check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 if option.system != 'Darwin' and option.system != 'FreeBSD': - check_message(op_text, 64) # 9_3_1 - check_message(op_text, 256) # 9_3_2 - check_message(op_text, 2 ** 10) # 9_3_3 - check_message(op_text, 4 * 2 ** 10) # 9_3_4 - check_message(op_text, 16 * 2 ** 10) # 9_3_5 - check_message(op_text, 64 * 2 ** 10) # 9_3_6 - check_message(op_text, 256 * 2 ** 10) # 9_3_7 - check_message(op_text, 2 ** 20) # 9_3_8 - check_message(op_text, 4 * 2 ** 20) # 9_3_9 - - check_message(op_binary, 64) # 9_4_1 - check_message(op_binary, 256) # 9_4_2 - check_message(op_binary, 2 ** 10) # 9_4_3 - check_message(op_binary, 4 * 2 ** 10) # 9_4_4 - check_message(op_binary, 16 * 2 ** 10) # 9_4_5 - check_message(op_binary, 64 * 2 ** 10) # 9_4_6 - check_message(op_binary, 256 * 2 ** 10) # 9_4_7 - check_message(op_binary, 2 ** 20) # 9_4_8 - check_message(op_binary, 4 * 2 ** 20) # 9_4_9 - - check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 - check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 - check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 - check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 - check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 - check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 - - check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 - check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 - check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 - check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2 ** 10) # 9_3_3 + check_message(op_text, 4 * 2 ** 10) # 9_3_4 + check_message(op_text, 16 * 2 ** 10) # 9_3_5 + check_message(op_text, 64 * 2 ** 10) # 9_3_6 + check_message(op_text, 256 * 2 ** 10) # 9_3_7 + check_message(op_text, 2 ** 20) # 9_3_8 + check_message(op_text, 4 * 2 ** 20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2 ** 10) # 9_4_3 + check_message(op_binary, 4 * 2 ** 10) # 9_4_4 + check_message(op_binary, 16 * 2 ** 10) # 9_4_5 + check_message(op_binary, 64 * 2 ** 10) # 9_4_6 + check_message(op_binary, 256 * 2 ** 10) # 9_4_7 + check_message(op_binary, 2 ** 20) # 9_4_8 + check_message(op_binary, 4 * 2 ** 20) # 9_4_9 + + check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 + check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 + check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 + check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 + check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 check_payload(op_binary, 2 ** 20, chopsize=1024) # 9_6_5 check_payload(op_binary, 2 ** 20, chopsize=2048) # 9_6_6 diff --git a/test/test_node_application.py b/test/test_node_application.py index b277dc3a..52ad72ee 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,6 +1,7 @@ import re import pytest + from unit.applications.lang.node import TestApplicationNode from unit.utils import waitforfiles @@ -205,7 +206,9 @@ class TestNodeApplication(TestApplicationNode): def test_node_application_status_message(self): self.load('status_message') - assert re.search(r'200 blah', self.get(raw_resp=True)), 'status message' + assert re.search( + r'200 blah', self.get(raw_resp=True) + ), 'status message' def test_node_application_get_header_type(self): self.load('get_header_type') diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index d5f79811..51515f4e 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -2,6 +2,7 @@ import struct import time import pytest + from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket from unit.option import option @@ -182,7 +183,7 @@ class TestNodeWebsockets(TestApplicationNode): @pytest.mark.skip('not yet') def test_node_websockets_handshake_upgrade_absent( - self + self, ): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1 self.load('websockets/mirror') @@ -275,7 +276,9 @@ class TestNodeWebsockets(TestApplicationNode): }, ) - assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 + assert ( + resp['status'] == 400 + ), 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1 def test_node_websockets_handshake_method_invalid(self): self.load('websockets/mirror') @@ -370,14 +373,14 @@ class TestNodeWebsockets(TestApplicationNode): frame = self.ws.frame_read(sock) self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_1_1 - check_length(125) # 1_1_2 - check_length(126) # 1_1_3 - check_length(127) # 1_1_4 - check_length(128) # 1_1_5 - check_length(65535) # 1_1_6 - check_length(65536) # 1_1_7 - check_length(65536, chopsize = 997) # 1_1_8 + check_length(0) # 1_1_1 + check_length(125) # 1_1_2 + check_length(126) # 1_1_3 + check_length(127) # 1_1_4 + check_length(128) # 1_1_5 + check_length(65535) # 1_1_6 + check_length(65536) # 1_1_7 + check_length(65536, chopsize=997) # 1_1_8 self.close_connection(sock) @@ -396,14 +399,14 @@ class TestNodeWebsockets(TestApplicationNode): self.check_frame(frame, True, opcode, payload) - check_length(0) # 1_2_1 - check_length(125) # 1_2_2 - check_length(126) # 1_2_3 - check_length(127) # 1_2_4 - check_length(128) # 1_2_5 - check_length(65535) # 1_2_6 - check_length(65536) # 1_2_7 - check_length(65536, chopsize = 997) # 1_2_8 + check_length(0) # 1_2_1 + check_length(125) # 1_2_2 + check_length(126) # 1_2_3 + check_length(127) # 1_2_4 + check_length(128) # 1_2_5 + check_length(65535) # 1_2_6 + check_length(65536) # 1_2_7 + check_length(65536, chopsize=997) # 1_2_8 self.close_connection(sock) @@ -421,11 +424,11 @@ class TestNodeWebsockets(TestApplicationNode): self.check_frame(frame, True, op_pong, payload, decode=decode) - check_ping('') # 2_1 - check_ping('Hello, world!') # 2_2 + check_ping('') # 2_1 + check_ping('Hello, world!') # 2_2 check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3 - check_ping(b'\xfe' * 125, decode=False) # 2_4 - check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 + check_ping(b'\xfe' * 125, decode=False) # 2_4 + check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6 self.close_connection(sock) @@ -886,7 +889,9 @@ class TestNodeWebsockets(TestApplicationNode): frame = self.ws.frame_read(sock) if frame['opcode'] == self.ws.OP_TEXT: - self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2') + self.check_frame( + frame, True, self.ws.OP_TEXT, 'fragment1fragment2' + ) frame = None self.check_close(sock, 1002, frame=frame) @@ -1043,27 +1048,27 @@ class TestNodeWebsockets(TestApplicationNode): self.close_connection(sock) -# Unit does not support UTF-8 validation -# -# # 6_3_1 FAIL -# -# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' -# payload_2 = '\xed\xa0\x80' -# payload_3 = '\x65\x64\x69\x74\x65\x64' -# -# payload = payload_1 + payload_2 + payload_3 -# -# self.ws.message(sock, self.ws.OP_TEXT, payload) -# self.check_close(sock, 1007) -# -# # 6_3_2 FAIL -# -# _, sock, _ = self.ws.upgrade() -# -# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) -# self.check_close(sock, 1007) -# -# # 6_4_1 ... 6_4_4 FAIL + # Unit does not support UTF-8 validation + # + # # 6_3_1 FAIL + # + # payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5' + # payload_2 = '\xed\xa0\x80' + # payload_3 = '\x65\x64\x69\x74\x65\x64' + # + # payload = payload_1 + payload_2 + payload_3 + # + # self.ws.message(sock, self.ws.OP_TEXT, payload) + # self.check_close(sock, 1007) + # + # # 6_3_2 FAIL + # + # _, sock, _ = self.ws.upgrade() + # + # self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1) + # self.check_close(sock, 1007) + # + # # 6_4_1 ... 6_4_4 FAIL def test_node_websockets_7_1_1__7_5_1(self): self.load('websockets/mirror') @@ -1190,15 +1195,15 @@ class TestNodeWebsockets(TestApplicationNode): self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) self.check_close(sock, 1002) -# # 7_5_1 FAIL Unit does not support UTF-8 validation -# -# _, sock, _ = self.ws.upgrade() -# -# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ -# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') -# -# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) -# self.check_close(sock, 1007) + # # 7_5_1 FAIL Unit does not support UTF-8 validation + # + # _, sock, _ = self.ws.upgrade() + # + # payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \ + # '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64') + # + # self.ws.frame_write(sock, self.ws.OP_CLOSE, payload) + # self.check_close(sock, 1007) def test_node_websockets_7_7_X__7_9_X(self): self.load('websockets/mirror') @@ -1301,52 +1306,52 @@ class TestNodeWebsockets(TestApplicationNode): frame = self.ws.frame_read(sock, read_timeout=5) self.check_frame(frame, True, opcode, payload) - check_payload(op_text, 64 * 2 ** 10) # 9_1_1 - check_payload(op_text, 256 * 2 ** 10) # 9_1_2 - check_payload(op_text, 2 ** 20) # 9_1_3 - check_payload(op_text, 4 * 2 ** 20) # 9_1_4 - check_payload(op_text, 8 * 2 ** 20) # 9_1_5 - check_payload(op_text, 16 * 2 ** 20) # 9_1_6 + check_payload(op_text, 64 * 2 ** 10) # 9_1_1 + check_payload(op_text, 256 * 2 ** 10) # 9_1_2 + check_payload(op_text, 2 ** 20) # 9_1_3 + check_payload(op_text, 4 * 2 ** 20) # 9_1_4 + check_payload(op_text, 8 * 2 ** 20) # 9_1_5 + check_payload(op_text, 16 * 2 ** 20) # 9_1_6 - check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 - check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 - check_payload(op_binary, 2 ** 20) # 9_2_3 - check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 - check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 - check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 + check_payload(op_binary, 64 * 2 ** 10) # 9_2_1 + check_payload(op_binary, 256 * 2 ** 10) # 9_2_2 + check_payload(op_binary, 2 ** 20) # 9_2_3 + check_payload(op_binary, 4 * 2 ** 20) # 9_2_4 + check_payload(op_binary, 8 * 2 ** 20) # 9_2_5 + check_payload(op_binary, 16 * 2 ** 20) # 9_2_6 if option.system != 'Darwin' and option.system != 'FreeBSD': - check_message(op_text, 64) # 9_3_1 - check_message(op_text, 256) # 9_3_2 - check_message(op_text, 2 ** 10) # 9_3_3 - check_message(op_text, 4 * 2 ** 10) # 9_3_4 - check_message(op_text, 16 * 2 ** 10) # 9_3_5 - check_message(op_text, 64 * 2 ** 10) # 9_3_6 - check_message(op_text, 256 * 2 ** 10) # 9_3_7 - check_message(op_text, 2 ** 20) # 9_3_8 - check_message(op_text, 4 * 2 ** 20) # 9_3_9 - - check_message(op_binary, 64) # 9_4_1 - check_message(op_binary, 256) # 9_4_2 - check_message(op_binary, 2 ** 10) # 9_4_3 - check_message(op_binary, 4 * 2 ** 10) # 9_4_4 - check_message(op_binary, 16 * 2 ** 10) # 9_4_5 - check_message(op_binary, 64 * 2 ** 10) # 9_4_6 - check_message(op_binary, 256 * 2 ** 10) # 9_4_7 - check_message(op_binary, 2 ** 20) # 9_4_8 - check_message(op_binary, 4 * 2 ** 20) # 9_4_9 - - check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 - check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 - check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 - check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 - check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 - check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 - - check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 - check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 - check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 - check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 + check_message(op_text, 64) # 9_3_1 + check_message(op_text, 256) # 9_3_2 + check_message(op_text, 2 ** 10) # 9_3_3 + check_message(op_text, 4 * 2 ** 10) # 9_3_4 + check_message(op_text, 16 * 2 ** 10) # 9_3_5 + check_message(op_text, 64 * 2 ** 10) # 9_3_6 + check_message(op_text, 256 * 2 ** 10) # 9_3_7 + check_message(op_text, 2 ** 20) # 9_3_8 + check_message(op_text, 4 * 2 ** 20) # 9_3_9 + + check_message(op_binary, 64) # 9_4_1 + check_message(op_binary, 256) # 9_4_2 + check_message(op_binary, 2 ** 10) # 9_4_3 + check_message(op_binary, 4 * 2 ** 10) # 9_4_4 + check_message(op_binary, 16 * 2 ** 10) # 9_4_5 + check_message(op_binary, 64 * 2 ** 10) # 9_4_6 + check_message(op_binary, 256 * 2 ** 10) # 9_4_7 + check_message(op_binary, 2 ** 20) # 9_4_8 + check_message(op_binary, 4 * 2 ** 20) # 9_4_9 + + check_payload(op_text, 2 ** 20, chopsize=64) # 9_5_1 + check_payload(op_text, 2 ** 20, chopsize=128) # 9_5_2 + check_payload(op_text, 2 ** 20, chopsize=256) # 9_5_3 + check_payload(op_text, 2 ** 20, chopsize=512) # 9_5_4 + check_payload(op_text, 2 ** 20, chopsize=1024) # 9_5_5 + check_payload(op_text, 2 ** 20, chopsize=2048) # 9_5_6 + + check_payload(op_binary, 2 ** 20, chopsize=64) # 9_6_1 + check_payload(op_binary, 2 ** 20, chopsize=128) # 9_6_2 + check_payload(op_binary, 2 ** 20, chopsize=256) # 9_6_3 + check_payload(op_binary, 2 ** 20, chopsize=512) # 9_6_4 check_payload(op_binary, 2 ** 20, chopsize=1024) # 9_6_5 check_payload(op_binary, 2 ** 20, chopsize=2048) # 9_6_6 diff --git a/test/test_perl_application.py b/test/test_perl_application.py index dfd8be6c..e906aaca 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,6 +1,7 @@ import re import pytest + from unit.applications.lang.perl import TestApplicationPerl diff --git a/test/test_php_application.py b/test/test_php_application.py index e73c67ba..de3da939 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -5,9 +5,11 @@ import time from subprocess import call import pytest + from unit.applications.lang.php import TestApplicationPHP from unit.option import option + class TestPHPApplication(TestApplicationPHP): prerequisites = {'modules': {'php': 'all'}} @@ -428,11 +430,13 @@ class TestPHPApplication(TestApplicationPHP): self.load('auth') def check_auth(auth): - resp = self.get(headers={ - 'Host': 'localhost', - 'Authorization': auth, - 'Connection': 'close', - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Authorization': auth, + 'Connection': 'close', + } + ) assert resp['status'] == 200, 'status' assert resp['headers']['X-Digest'] == 'not set', 'Digest' diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py index 2e458023..8db6b590 100644 --- a/test/test_php_isolation.py +++ b/test/test_php_isolation.py @@ -1,4 +1,5 @@ import pytest + from unit.applications.lang.php import TestApplicationPHP from unit.option import option @@ -28,7 +29,7 @@ class TestPHPIsolation(TestApplicationPHP): isolation['namespaces'] = { 'mount': True, 'credential': True, - 'pid': True + 'pid': True, } self.load('phpinfo', isolation=isolation) @@ -64,7 +65,7 @@ class TestPHPIsolation(TestApplicationPHP): isolation['namespaces'] = { 'mount': True, 'credential': True, - 'pid': True + 'pid': True, } self.load('list-extensions', isolation=isolation) diff --git a/test/test_proxy.py b/test/test_proxy.py index 7e7c7246..3d59cf24 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -3,6 +3,7 @@ import socket import time import pytest + from conftest import run_process from unit.applications.lang.python import TestApplicationPython from unit.option import option @@ -464,9 +465,9 @@ Content-Length: 10 def test_proxy_invalid(self): def check_proxy(proxy): - assert 'error' in \ - self.conf([{"action": {"proxy": proxy}}], 'routes'), \ - 'proxy invalid' + assert 'error' in self.conf( + [{"action": {"proxy": proxy}}], 'routes' + ), 'proxy invalid' check_proxy('blah') check_proxy('/blah') @@ -502,7 +503,8 @@ Content-Length: 10 "type": "python", "processes": {"spare": 0}, "path": option.test_dir + "/python/mirror", - "working_directory": option.test_dir + "/python/mirror", + "working_directory": option.test_dir + + "/python/mirror", "module": "wsgi", }, }, diff --git a/test/test_python_application.py b/test/test_python_application.py index 41f6f538..fd99a3af 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -5,6 +5,7 @@ import re import time import pytest + from unit.applications.lang.python import TestApplicationPython from unit.option import option @@ -31,7 +32,8 @@ Content-Type: text/html Connection: close custom-header: BLAH -%s""" % (len(body), body.encode()), +%s""" + % (len(body), body.encode()), raw=True, ) @@ -816,7 +818,11 @@ last line: 987654321 assert ['/new', *sys_path] == get_path(), 'check path update' set_path('["/blah1", "/blah2"]') - assert ['/blah1', '/blah2', *sys_path] == get_path(), 'check path array' + assert [ + '/blah1', + '/blah2', + *sys_path, + ] == get_path(), 'check path array' def test_python_application_path_invalid(self): self.load('path') diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py index 680f2c03..93f85264 100644 --- a/test/test_python_isolation.py +++ b/test/test_python_isolation.py @@ -1,5 +1,5 @@ - import pytest + from unit.applications.lang.python import TestApplicationPython from unit.option import option from unit.utils import findmnt @@ -32,7 +32,7 @@ class TestPythonIsolation(TestApplicationPython): isolation['namespaces'] = { 'mount': True, 'credential': True, - 'pid': True + 'pid': True, } self.load('ns_inspect', isolation=isolation) @@ -43,8 +43,7 @@ class TestPythonIsolation(TestApplicationPython): ), 'temp_dir does not exists in rootfs' assert ( - self.getjson(url='/?path=/proc/self')['body']['FileExists'] - == True + self.getjson(url='/?path=/proc/self')['body']['FileExists'] == True ), 'no /proc/self' assert ( @@ -66,15 +65,12 @@ class TestPythonIsolation(TestApplicationPython): if not is_su: pytest.skip('requires root') - isolation = { - 'rootfs': temp_dir, - 'automount': {'language_deps': False} - } + isolation = {'rootfs': temp_dir, 'automount': {'language_deps': False}} self.load('empty', isolation=isolation) assert findmnt().find(temp_dir) == -1 - assert (self.get()['status'] != 200), 'disabled language_deps' + assert self.get()['status'] != 200, 'disabled language_deps' assert findmnt().find(temp_dir) == -1 isolation['automount']['language_deps'] = True @@ -82,7 +78,7 @@ class TestPythonIsolation(TestApplicationPython): self.load('empty', isolation=isolation) assert findmnt().find(temp_dir) == -1 - assert (self.get()['status'] == 200), 'enabled language_deps' + assert self.get()['status'] == 200, 'enabled language_deps' assert waitformount(temp_dir), 'language_deps mount' self.conf({"listeners": {}, "applications": {}}) @@ -90,8 +86,6 @@ class TestPythonIsolation(TestApplicationPython): assert waitforunmount(temp_dir), 'language_deps unmount' def test_python_isolation_procfs(self, is_su, temp_dir): - isolation_features = option.available['features']['isolation'].keys() - if not is_su: pytest.skip('requires root') diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py index 7281f5f6..54fbad12 100644 --- a/test/test_python_isolation_chroot.py +++ b/test/test_python_isolation_chroot.py @@ -1,4 +1,5 @@ import pytest + from unit.applications.lang.python import TestApplicationPython @@ -21,8 +22,7 @@ class TestPythonIsolation(TestApplicationPython): ), 'temp_dir does not exists in rootfs' assert ( - self.getjson(url='/?path=/proc/self')['body']['FileExists'] - == True + self.getjson(url='/?path=/proc/self')['body']['FileExists'] == True ), 'no /proc/self' assert ( diff --git a/test/test_python_procman.py b/test/test_python_procman.py index ac403ce4..b0d0f5af 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -3,6 +3,7 @@ import subprocess import time import pytest + from unit.applications.lang.python import TestApplicationPython from unit.option import option diff --git a/test/test_respawn.py b/test/test_respawn.py index 50c19186..edbfa2a8 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -76,9 +76,10 @@ class TestRespawn(TestApplicationPython): self.kill_pids(pid) skip_alert(r'process %s exited on signal 9' % pid) - assert self.wait_for_process( - self.PATTERN_CONTROLLER, unit_pid - ) is not None + assert ( + self.wait_for_process(self.PATTERN_CONTROLLER, unit_pid) + is not None + ) assert self.get()['status'] == 200 diff --git a/test/test_routing.py b/test/test_routing.py index cb9c3fd2..7392c1ab 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import pytest + from unit.applications.proto import TestApplicationProto from unit.option import option @@ -232,11 +233,11 @@ class TestRouting(TestApplicationProto): if not option.available['modules']['regex']: pytest.skip('requires regex') - self.route_match({"uri":"~"}) + self.route_match({"uri": "~"}) assert self.get(url='/')['status'] == 200, 'empty regexp' assert self.get(url='/anything')['status'] == 200, '/anything' - self.route_match({"uri":"!~"}) + self.route_match({"uri": "!~"}) assert self.get(url='/')['status'] == 404, 'empty regexp 2' assert self.get(url='/nothing')['status'] == 404, '/nothing' @@ -336,8 +337,7 @@ class TestRouting(TestApplicationProto): "type": "python", "processes": {"spare": 0}, "path": option.test_dir + '/python/empty', - "working_directory": option.test_dir - + '/python/empty', + "working_directory": option.test_dir + '/python/empty', "module": "wsgi", } }, @@ -495,8 +495,7 @@ class TestRouting(TestApplicationProto): 'routes/0/action', ), 'proxy pass' assert 'error' in self.conf( - {"share": temp_dir, "pass": "applications/app"}, - 'routes/0/action', + {"share": temp_dir, "pass": "applications/app"}, 'routes/0/action', ), 'share pass' def test_routes_rules_two(self): diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 17491619..ddd31f59 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -2,6 +2,7 @@ import re import subprocess import pytest + from unit.applications.lang.ruby import TestApplicationRuby @@ -208,7 +209,6 @@ class TestRubyApplication(TestApplicationRuby): self.get() - assert ( self.wait_for_record(r'\[error\].+1234567890') is not None ), 'errors write int' @@ -228,9 +228,13 @@ class TestRubyApplication(TestApplicationRuby): self.load('encoding') try: - locales = subprocess.check_output( - ['locale', '-a'], stderr=subprocess.STDOUT, - ).decode().split('\n') + locales = ( + subprocess.check_output( + ['locale', '-a'], stderr=subprocess.STDOUT, + ) + .decode() + .split('\n') + ) except (FileNotFoundError, subprocess.CalledProcessError): pytest.skip('require locale') diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index c49b6732..8443d857 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -2,6 +2,7 @@ import os import shutil import pytest + from unit.applications.lang.ruby import TestApplicationRuby from unit.option import option diff --git a/test/test_settings.py b/test/test_settings.py index c59ca8b9..d129dec9 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -3,6 +3,7 @@ import socket import time import pytest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index 46464670..0b1c270e 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -1,6 +1,7 @@ import os import pytest + from unit.applications.proto import TestApplicationProto from unit.option import option @@ -81,10 +82,7 @@ class TestStatic(TestApplicationProto): def test_fallback_share(self, temp_dir): self.action_update( - { - "share": "/blah", - "fallback": {"share": temp_dir + "/assets"}, - } + {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} ) resp = self.get() diff --git a/test/test_static.py b/test/test_static.py index 3e85b435..d8319292 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -2,6 +2,7 @@ import os import socket import pytest + from unit.applications.proto import TestApplicationProto from unit.option import option from unit.utils import waitforfiles @@ -85,8 +86,7 @@ class TestStatic(TestApplicationProto): def test_static_space_in_name(self, temp_dir): os.rename( - temp_dir + '/assets/dir/file', - temp_dir + '/assets/dir/fi le', + temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/fi le', ) assert waitforfiles(temp_dir + '/assets/dir/fi le') assert self.get(url='/dir/fi le')['body'] == 'blah', 'file name' @@ -95,9 +95,7 @@ class TestStatic(TestApplicationProto): assert waitforfiles(temp_dir + '/assets/di r/fi le') assert self.get(url='/di r/fi le')['body'] == 'blah', 'dir name' - os.rename( - temp_dir + '/assets/di r', temp_dir + '/assets/ di r ' - ) + os.rename(temp_dir + '/assets/di r', temp_dir + '/assets/ di r ') assert waitforfiles(temp_dir + '/assets/ di r /fi le') assert ( self.get(url='/ di r /fi le')['body'] == 'blah' @@ -150,8 +148,7 @@ class TestStatic(TestApplicationProto): ), 'file name 2' os.rename( - temp_dir + '/assets/ di r ', - temp_dir + '/assets/ди ректория', + temp_dir + '/assets/ di r ', temp_dir + '/assets/ди ректория', ) assert waitforfiles(temp_dir + '/assets/ди ректория/фа йл') assert ( diff --git a/test/test_tls.py b/test/test_tls.py index 206ea828..63422dfb 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -4,6 +4,7 @@ import ssl import subprocess import pytest + from unit.applications.tls import TestApplicationTLS from unit.option import option @@ -22,7 +23,7 @@ class TestTLS(TestApplicationTLS): assert 'success' in self.conf( { "pass": "applications/" + application, - "tls": {"certificate": cert} + "tls": {"certificate": cert}, }, 'listeners/*:' + str(port), ) diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py index 7da05e6e..2e5424e2 100644 --- a/test/test_tls_sni.py +++ b/test/test_tls_sni.py @@ -1,7 +1,8 @@ -import subprocess import ssl +import subprocess import pytest + from unit.applications.tls import TestApplicationTLS from unit.option import option @@ -23,10 +24,7 @@ class TestTLSSNI(TestApplicationTLS): def add_tls(self, cert='default'): assert 'success' in self.conf( - { - "pass": "routes", - "tls": {"certificate": cert} - }, + {"pass": "routes", "tls": {"certificate": cert}}, 'listeners/*:7080', ) @@ -153,10 +151,7 @@ basicConstraints = critical,CA:TRUE""" def test_tls_sni(self): bundles = { - "default": { - "subj": "default", - "alt_names": ["default"], - }, + "default": {"subj": "default", "alt_names": ["default"]}, "localhost.com": { "subj": "localhost.com", "alt_names": ["alt1.localhost.com"], @@ -205,10 +200,7 @@ basicConstraints = critical,CA:TRUE""" def test_tls_sni_wildcard(self): bundles = { - "localhost.com": { - "subj": "localhost.com", - "alt_names": [], - }, + "localhost.com": {"subj": "localhost.com", "alt_names": []}, "example.com": { "subj": "example.com", "alt_names": ["*.example.com", "*.alt.example.com"], @@ -248,11 +240,7 @@ basicConstraints = critical,CA:TRUE""" self.check_cert('example', bundles['localhost']['subj']) def test_tls_sni_empty_cn(self): - bundles = { - "localhost": { - "alt_names": ["alt.localhost.com"], - } - } + bundles = {"localhost": {"alt_names": ["alt.localhost.com"]}} self.config_bundles(bundles) self.add_tls(["localhost"]) @@ -266,7 +254,9 @@ basicConstraints = critical,CA:TRUE""" ) assert resp['status'] == 200 - assert sock.getpeercert()['subjectAltName'][0][1] == 'alt.localhost.com' + assert ( + sock.getpeercert()['subjectAltName'][0][1] == 'alt.localhost.com' + ) def test_tls_sni_invalid(self): self.config_bundles({"localhost": {"subj": "subj1", "alt_names": ''}}) diff --git a/test/test_variables.py b/test/test_variables.py index bbb8f769..139d867e 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -92,16 +92,8 @@ class TestVariables(TestApplicationProto): "*:7080": {"pass": "upstreams$uri"}, "*:7081": {"pass": "routes/one"}, }, - "upstreams": { - "1": { - "servers": { - "127.0.0.1:7081": {}, - }, - }, - }, - "routes": { - "one": [{"action": {"return": 200}}], - }, + "upstreams": {"1": {"servers": {"127.0.0.1:7081": {}}}}, + "routes": {"one": [{"action": {"return": 200}}]}, }, ), 'upstreams initial configuration' diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 490ae916..95eeac55 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -21,10 +21,14 @@ class TestApplicationTLS(TestApplicationProto): 'req', '-x509', '-new', - '-subj', '/CN=' + name + '/', - '-config', option.temp_dir + '/openssl.conf', - '-out', option.temp_dir + '/' + name + '.crt', - '-keyout', option.temp_dir + '/' + name + '.key', + '-subj', + '/CN=' + name + '/', + '-config', + option.temp_dir + '/openssl.conf', + '-out', + option.temp_dir + '/' + name + '.crt', + '-keyout', + option.temp_dir + '/' + name + '.key', ], stderr=subprocess.STDOUT, ) @@ -75,12 +79,14 @@ class TestApplicationTLS(TestApplicationProto): a_names += "DNS.%d = %s\n" % (i, k) # Generates section for sign request extension - a_sec = """req_extensions = myca_req_extensions + a_sec = """req_extensions = myca_req_extensions [ myca_req_extensions ] subjectAltName = @alt_names -{a_names}""".format(a_names=a_names) +{a_names}""".format( + a_names=a_names + ) with open(conf_path, 'w') as f: f.write( @@ -90,7 +96,9 @@ encrypt_key = no distinguished_name = req_distinguished_name {a_sec} -[ req_distinguished_name ]""".format(a_sec=a_sec if alt_names else "") +[ req_distinguished_name ]""".format( + a_sec=a_sec if alt_names else "" + ) ) def load(self, script, name=None): diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index cc720a98..aa83339c 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -43,11 +43,7 @@ class TestApplicationWebsocket(TestApplicationProto): 'Sec-WebSocket-Version': 13, } - _, sock = self.get( - headers=headers, - no_recv=True, - start=True, - ) + _, sock = self.get(headers=headers, no_recv=True, start=True,) resp = '' while True: @@ -57,7 +53,7 @@ class TestApplicationWebsocket(TestApplicationProto): resp += sock.recv(4096).decode() - if (resp.startswith('HTTP/') and '\r\n\r\n' in resp): + if resp.startswith('HTTP/') and '\r\n\r\n' in resp: resp = self._resp_to_dict(resp) break @@ -90,8 +86,8 @@ class TestApplicationWebsocket(TestApplicationProto): frame = {} - head1, = struct.unpack('!B', recv_bytes(sock, 1)) - head2, = struct.unpack('!B', recv_bytes(sock, 1)) + (head1,) = struct.unpack('!B', recv_bytes(sock, 1)) + (head2,) = struct.unpack('!B', recv_bytes(sock, 1)) frame['fin'] = bool(head1 & 0b10000000) frame['rsv1'] = bool(head1 & 0b01000000) @@ -103,10 +99,10 @@ class TestApplicationWebsocket(TestApplicationProto): length = head2 & 0b01111111 if length == 126: data = recv_bytes(sock, 2) - length, = struct.unpack('!H', data) + (length,) = struct.unpack('!H', data) elif length == 127: data = recv_bytes(sock, 8) - length, = struct.unpack('!Q', data) + (length,) = struct.unpack('!Q', data) if frame['mask']: mask_bits = recv_bytes(sock, 4) @@ -121,7 +117,7 @@ class TestApplicationWebsocket(TestApplicationProto): if frame['opcode'] == self.OP_CLOSE: if length >= 2: - code, = struct.unpack('!H', data[:2]) + (code,) = struct.unpack('!H', data[:2]) reason = data[2:].decode('utf-8') if not (code in self.CLOSE_CODES or 3000 <= code < 5000): pytest.fail('Invalid status code') diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py index fe5a41f8..7c83ae35 100644 --- a/test/unit/check/isolation.py +++ b/test/unit/check/isolation.py @@ -12,6 +12,7 @@ from unit.utils import getns allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] http = TestHTTP() + def check_isolation(): test_conf = {"namespaces": {"credential": True}} available = option.available @@ -117,8 +118,7 @@ def check_isolation(): "body_empty": { "type": "perl", "processes": {"spare": 0}, - "working_directory": option.test_dir - + "/perl/body_empty", + "working_directory": option.test_dir + "/perl/body_empty", "script": option.test_dir + "/perl/body_empty/psgi.pl", "isolation": {"namespaces": {"credential": True}}, } diff --git a/test/unit/http.py b/test/unit/http.py index 7706fe05..797b7681 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -10,15 +10,16 @@ import pytest from unit.option import option -class TestHTTP(): +class TestHTTP: def http(self, start_str, **kwargs): sock_type = kwargs.get('sock_type', 'ipv4') port = kwargs.get('port', 7080) url = kwargs.get('url', '/') http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' - headers = kwargs.get('headers', - {'Host': 'localhost', 'Connection': 'close'}) + headers = kwargs.get( + 'headers', {'Host': 'localhost', 'Connection': 'close'} + ) body = kwargs.get('body', b'') crlf = '\r\n' @@ -305,8 +306,9 @@ class TestHTTP(): return body, content_type def form_url_encode(self, fields): - data = "&".join("%s=%s" % (name, value) - for name, value in fields.items()).encode() + data = "&".join( + "%s=%s" % (name, value) for name, value in fields.items() + ).encode() return data, 'application/x-www-form-urlencoded' def multipart_encode(self, fields): @@ -326,7 +328,9 @@ class TestHTTP(): datatype = value['type'] if not isinstance(value['data'], io.IOBase): - pytest.fail('multipart encoding of file requires a stream.') + pytest.fail( + 'multipart encoding of file requires a stream.' + ) data = value['data'].read() @@ -336,9 +340,10 @@ class TestHTTP(): else: pytest.fail('multipart requires a string or stream data') - body += ( - "--%s\r\nContent-Disposition: form-data; name=\"%s\"" - ) % (boundary, field) + body += ("--%s\r\nContent-Disposition: form-data; name=\"%s\"") % ( + boundary, + field, + ) if filename != '': body += "; filename=\"%s\"" % filename diff --git a/test/unit/option.py b/test/unit/option.py index 677d806e..cb3803dc 100644 --- a/test/unit/option.py +++ b/test/unit/option.py @@ -1,4 +1,4 @@ -class Options(): +class Options: _options = { 'skip_alerts': [], 'skip_sanitizer': False, @@ -13,4 +13,5 @@ class Options(): raise AttributeError + option = Options() -- cgit From 30922c5741af0c712f465d0e98b71f8848c0db91 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov Date: Thu, 8 Apr 2021 10:55:30 +0300 Subject: Packages: moved Amazon Linux 2 packages to use openssl 1.1 --- pkg/rpm/Makefile | 4 ++++ pkg/rpm/unit.spec.in | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index 75327c49..f9c57b5b 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -41,8 +41,12 @@ endif ifneq (,$(findstring $(OSVER),opensuse-leap opensuse-tumbleweed sles)) BUILD_DEPENDS_unit += libxml2-tools libxslt1 libopenssl-devel else +ifneq (,$(findstring $(OSVER),amazonlinux2)) +BUILD_DEPENDS_unit += libxml2 libxslt openssl11-devel +else BUILD_DEPENDS_unit += libxml2 libxslt openssl-devel endif +endif BUILD_DEPENDS = $(BUILD_DEPENDS_unit) diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 9d16265d..07ca9e6f 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -4,8 +4,12 @@ %if 0%{?rhel}%{?fedora} BuildRequires: gcc +%if 0%{?amzn2} +BuildRequires: openssl11-devel +%else BuildRequires: openssl-devel %endif +%endif %if 0%{?rhel} %if 0%{?amzn} == 0 -- cgit From 74b1b1fc17726d805b00dee6b5547254f5cf230c Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 8 Apr 2021 19:11:11 +0300 Subject: Tests: preserving unit.log when run without restart. Introducing "unit.log.Log" class for "unit.log" file management. Moving "findall()" function into TestApplicationProto. Using "os.kill()" to send signals. --- test/conftest.py | 39 +++++++++-------------------- test/test_access_log.py | 2 +- test/test_asgi_application.py | 4 --- test/test_php_application.py | 39 +++++++++++------------------ test/test_python_application.py | 5 ---- test/test_tls.py | 5 ---- test/test_usr1.py | 55 +++++++++++++++++++++++------------------ test/unit/applications/proto.py | 18 +++++++++----- test/unit/log.py | 23 +++++++++++++++++ 9 files changed, 94 insertions(+), 96 deletions(-) create mode 100644 test/unit/log.py diff --git a/test/conftest.py b/test/conftest.py index 38e1138e..b9f5c60b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -22,6 +22,7 @@ from unit.check.regex import check_regex from unit.check.tls import check_openssl from unit.http import TestHTTP from unit.option import option +from unit.log import Log from unit.utils import public_dir from unit.utils import waitforfiles @@ -71,7 +72,6 @@ def pytest_addoption(parser): unit_instance = {} -unit_log_copy = "unit.log.copy" _processes = [] _fds_check = { 'main': {'fds': 0, 'skip': False}, @@ -165,12 +165,11 @@ def pytest_sessionstart(session): option.available = {'modules': {}, 'features': {}} unit = unit_run() - option.temp_dir = unit['temp_dir'] # read unit.log for i in range(50): - with open(unit['temp_dir'] + '/unit.log', 'r') as f: + with open(Log.get_path(), 'r') as f: log = f.read() m = re.search('controller started', log) @@ -216,9 +215,6 @@ def pytest_sessionstart(session): if option.restart: shutil.rmtree(unit_instance['temp_dir']) - elif option.save_log: - open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'w').close() - @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): @@ -269,7 +265,6 @@ def check_prerequisites(request): @pytest.fixture(autouse=True) def run(request): unit = unit_run() - option.temp_dir = unit['temp_dir'] option.skip_alerts = [ r'read signalfd\(4\) failed', @@ -291,34 +286,25 @@ def run(request): # prepare log - with open( - unit_instance['log'], 'r', encoding='utf-8', errors='ignore' - ) as f: + with Log.open(encoding='utf-8') as f: log = f.read() - - if not option.restart and option.save_log: - with open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'a') as f: - f.write(log) - - # remove unit.log + Log.set_pos(f.tell()) if not option.save_log and option.restart: shutil.rmtree(unit['temp_dir']) + Log.set_pos(0) # clean temp_dir before the next test if not option.restart: _clear_conf(unit['temp_dir'] + '/control.unit.sock', log) - open(unit['log'], 'w').close() - for item in os.listdir(unit['temp_dir']): if item not in [ 'control.unit.sock', 'state', 'unit.pid', 'unit.log', - unit_log_copy, ]: path = os.path.join(unit['temp_dir'], item) @@ -439,10 +425,12 @@ def unit_run(): exit('Could not start unit') unit_instance['temp_dir'] = temp_dir - unit_instance['log'] = temp_dir + '/unit.log' unit_instance['control_sock'] = temp_dir + '/control.unit.sock' unit_instance['unitd'] = unitd + option.temp_dir = temp_dir + Log.temp_dir = temp_dir + with open(temp_dir + '/unit.pid', 'r') as f: unit_instance['pid'] = f.read().rstrip() @@ -489,12 +477,9 @@ def unit_stop(): return 'Could not terminate unit' -def _check_alerts(path=None, log=None): - if path is None: - path = unit_instance['log'] - +def _check_alerts(log=None): if log is None: - with open(path, 'r', encoding='utf-8', errors='ignore') as f: + with Log.open(encoding='utf-8') as f: log = f.read() found = False @@ -526,7 +511,7 @@ def _check_alerts(path=None, log=None): def _print_log(data=None): - path = unit_instance['log'] + path = Log.get_path() print('Path to unit.log:\n' + path + '\n') @@ -679,7 +664,7 @@ def unit_pid(request): def pytest_sessionfinish(session): if not option.restart and option.save_log: - print('Path to unit.log:\n' + unit_instance['log'] + '\n') + print('Path to unit.log:\n' + Log.get_path() + '\n') option.restart = True diff --git a/test/test_access_log.py b/test/test_access_log.py index 72a78c33..ba254c5e 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -249,7 +249,7 @@ Connection: close assert self.search_in_log(r'/delete', 'access.log') is None, 'delete' - def test_access_log_change(self, temp_dir): + def test_access_log_change(self): self.load('empty') self.get() diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py index 70b2e4c3..f503fa82 100644 --- a/test/test_asgi_application.py +++ b/test/test_asgi_application.py @@ -14,10 +14,6 @@ class TestASGIApplication(TestApplicationPython): } load_module = 'asgi' - def findall(self, pattern): - with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f: - return re.findall(pattern, f.read()) - def test_asgi_application_variables(self): self.load('variables') diff --git a/test/test_php_application.py b/test/test_php_application.py index de3da939..350ac0d0 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,8 +1,8 @@ import os import re import shutil +import signal import time -from subprocess import call import pytest @@ -95,37 +95,29 @@ class TestPHPApplication(TestApplicationPHP): assert resp['status'] == 200, 'query string empty status' assert resp['headers']['Query-String'] == '', 'query string empty' - def test_php_application_fastcgi_finish_request(self, temp_dir): + def test_php_application_fastcgi_finish_request(self, unit_pid): self.load('fastcgi_finish_request') assert self.get()['body'] == '0123' - with open(temp_dir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() + os.kill(unit_pid, signal.SIGUSR1); - call(['kill', '-s', 'USR1', pid]) + errs = self.findall(r'Error in fastcgi_finish_request') - with open(temp_dir + '/unit.log', 'r', errors='ignore') as f: - errs = re.findall(r'Error in fastcgi_finish_request', f.read()) + assert len(errs) == 0, 'no error' - assert len(errs) == 0, 'no error' - - def test_php_application_fastcgi_finish_request_2(self, temp_dir): + def test_php_application_fastcgi_finish_request_2(self, unit_pid): self.load('fastcgi_finish_request') resp = self.get(url='/?skip') assert resp['status'] == 200 assert resp['body'] == '' - with open(temp_dir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() - - call(['kill', '-s', 'USR1', pid]) + os.kill(unit_pid, signal.SIGUSR1); - with open(temp_dir + '/unit.log', 'r', errors='ignore') as f: - errs = re.findall(r'Error in fastcgi_finish_request', f.read()) + errs = self.findall(r'Error in fastcgi_finish_request') - assert len(errs) == 0, 'no error' + assert len(errs) == 0, 'no error' def test_php_application_query_string_absent(self): self.load('query_string') @@ -538,7 +530,7 @@ class TestPHPApplication(TestApplicationPHP): r'012345', self.get()['body'] ), 'disable_classes before' - def test_php_application_error_log(self, temp_dir): + def test_php_application_error_log(self): self.load('error_log') assert self.get()['status'] == 200, 'status' @@ -551,14 +543,13 @@ class TestPHPApplication(TestApplicationPHP): assert self.wait_for_record(pattern) is not None, 'errors print' - with open(temp_dir + '/unit.log', 'r', errors='ignore') as f: - errs = re.findall(pattern, f.read()) + errs = self.findall(pattern) - assert len(errs) == 2, 'error_log count' + assert len(errs) == 2, 'error_log count' - date = errs[0].split('[')[0] - date2 = errs[1].split('[')[0] - assert date != date2, 'date diff' + date = errs[0].split('[')[0] + date2 = errs[1].split('[')[0] + assert date != date2, 'date diff' def test_php_application_script(self): assert 'success' in self.conf( diff --git a/test/test_python_application.py b/test/test_python_application.py index fd99a3af..48c3d603 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -7,16 +7,11 @@ import time import pytest from unit.applications.lang.python import TestApplicationPython -from unit.option import option class TestPythonApplication(TestApplicationPython): prerequisites = {'modules': {'python': 'all'}} - def findall(self, pattern): - with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f: - return re.findall(pattern, f.read()) - def test_python_application_variables(self): self.load('variables') diff --git a/test/test_tls.py b/test/test_tls.py index 63422dfb..abdca167 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -6,16 +6,11 @@ import subprocess import pytest from unit.applications.tls import TestApplicationTLS -from unit.option import option class TestTLS(TestApplicationTLS): prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}} - def findall(self, pattern): - with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f: - return re.findall(pattern, f.read()) - def openssl_date_to_sec_epoch(self, date): return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z') diff --git a/test/test_usr1.py b/test/test_usr1.py index dbb5265c..9a12b747 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -1,14 +1,15 @@ import os -from subprocess import call +import signal from unit.applications.lang.python import TestApplicationPython +from unit.log import Log from unit.utils import waitforfiles class TestUSR1(TestApplicationPython): prerequisites = {'modules': {'python': 'any'}} - def test_usr1_access_log(self, temp_dir): + def test_usr1_access_log(self, temp_dir, unit_pid): self.load('empty') log = 'access.log' @@ -31,10 +32,7 @@ class TestUSR1(TestApplicationPython): ), 'rename new' assert not os.path.isfile(log_path), 'rename old' - with open(temp_dir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() - - call(['kill', '-s', 'USR1', pid]) + os.kill(unit_pid, signal.SIGUSR1) assert waitforfiles(log_path), 'reopen' @@ -46,7 +44,7 @@ class TestUSR1(TestApplicationPython): ), 'reopen 2' assert self.search_in_log(r'/usr1', log_new) is None, 'rename new 2' - def test_usr1_unit_log(self, temp_dir): + def test_usr1_unit_log(self, temp_dir, unit_pid): self.load('log_body') log_new = 'new.log' @@ -55,28 +53,37 @@ class TestUSR1(TestApplicationPython): os.rename(log_path, log_path_new) - body = 'body_for_a_log_new' - assert self.post(body=body)['status'] == 200 + Log.swap(log_new) - assert self.wait_for_record(body, log_new) is not None, 'rename new' - assert not os.path.isfile(log_path), 'rename old' + try: + body = 'body_for_a_log_new\n' + assert self.post(body=body)['status'] == 200 - with open(temp_dir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() + assert ( + self.wait_for_record(body, log_new) is not None + ), 'rename new' + assert not os.path.isfile(log_path), 'rename old' - call(['kill', '-s', 'USR1', pid]) + os.kill(unit_pid, signal.SIGUSR1) - assert waitforfiles(log_path), 'reopen' + assert waitforfiles(log_path), 'reopen' + + body = 'body_for_a_log_unit\n' + assert self.post(body=body)['status'] == 200 + + assert self.wait_for_record(body) is not None, 'rename new' + assert self.search_in_log(body, log_new) is None, 'rename new 2' - body = 'body_for_a_log_unit' - assert self.post(body=body)['status'] == 200 + finally: + # merge two log files into unit.log to check alerts - assert self.wait_for_record(body) is not None, 'rename new' - assert self.search_in_log(body, log_new) is None, 'rename new 2' + with open(log_path, 'r', errors='ignore') as unit_log: + log = unit_log.read() - # merge two log files into unit.log to check alerts + with open(log_path, 'w') as unit_log, open( + log_path_new, 'r', errors='ignore' + ) as unit_log_new: + unit_log.write(unit_log_new.read()) + unit_log.write(log) - with open(log_path, 'w') as unit_log, open( - log_path_new, 'r' - ) as unit_log_new: - unit_log.write(unit_log_new.read()) + Log.swap(log_new) diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index 5c400621..92754c03 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -4,6 +4,7 @@ import time from unit.control import TestControl from unit.option import option +from unit.log import Log class TestApplicationProto(TestControl): @@ -15,18 +16,23 @@ class TestApplicationProto(TestControl): def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'): return time.mktime(time.strptime(date, template)) + def findall(self, pattern, name='unit.log'): + with Log.open(name) as f: + return re.findall(pattern, f.read()) + def search_in_log(self, pattern, name='unit.log'): - with open(option.temp_dir + '/' + name, 'r', errors='ignore') as f: + with Log.open(name) as f: return re.search(pattern, f.read()) def wait_for_record(self, pattern, name='unit.log', wait=150): - for i in range(wait): - found = self.search_in_log(pattern, name) + with Log.open(name) as f: + for i in range(wait): + found = re.search(pattern, f.read()) - if found is not None: - break + if found is not None: + break - time.sleep(0.1) + time.sleep(0.1) return found diff --git a/test/unit/log.py b/test/unit/log.py new file mode 100644 index 00000000..7263443d --- /dev/null +++ b/test/unit/log.py @@ -0,0 +1,23 @@ +UNIT_LOG = 'unit.log' + + +class Log: + temp_dir = None + pos = {} + + def open(name=UNIT_LOG, encoding=None): + f = open(Log.get_path(name), 'r', encoding=encoding, errors='ignore') + f.seek(Log.pos.get(name, 0)) + + return f + + def set_pos(pos, name=UNIT_LOG): + Log.pos[name] = pos + + def swap(name): + pos = Log.pos.get(UNIT_LOG, 0) + Log.pos[UNIT_LOG] = Log.pos.get(name, 0) + Log.pos[name] = pos + + def get_path(name=UNIT_LOG): + return Log.temp_dir + '/' + name -- cgit From d5889d7daa1c404af72bba830c5337ad6502d850 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov Date: Mon, 12 Apr 2021 18:39:45 +0300 Subject: Packages: fixed Amazon Linux 2 module packages to use openssl 1.1 --- pkg/rpm/unit.module.spec.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index 39083e66..101b4991 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -9,8 +9,12 @@ %if 0%{?rhel}%{?fedora} BuildRequires: gcc +%if 0%{?amzn2} +BuildRequires: openssl11-devel +%else BuildRequires: openssl-devel %endif +%endif %if 0%{?suse_version} >= 1315 BuildRequires: libopenssl-devel -- cgit From 5b332cae832c9f8245280fa60a6edc2ce48202c6 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 14 Apr 2021 15:56:03 +0100 Subject: Tests: fixed "skip" descriptors check flag for controller. --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index b9f5c60b..0eaf54be 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -275,7 +275,7 @@ def run(request): _fds_check['main']['skip'] = False _fds_check['router']['skip'] = False - _fds_check['router']['skip'] = False + _fds_check['controller']['skip'] = False yield -- cgit From f90754f84a375d5183ed6883862d19dfd417225a Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Wed, 21 Apr 2021 16:07:26 +0300 Subject: Packages: switched to common address for package maintainers. --- pkg/deb/Makefile | 6 +++++ pkg/deb/debian.module/control-noarch.in | 2 +- pkg/deb/debian.module/control.in | 2 +- pkg/deb/debian/control | 41 --------------------------------- pkg/deb/debian/control.in | 41 +++++++++++++++++++++++++++++++++ pkg/rpm/Makefile | 4 ++++ pkg/rpm/unit.module.spec.in | 3 +-- pkg/rpm/unit.spec.in | 3 +-- 8 files changed, 55 insertions(+), 47 deletions(-) delete mode 100644 pkg/deb/debian/control create mode 100644 pkg/deb/debian/control.in diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 8c33fc53..d28351d0 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -8,6 +8,8 @@ DEFAULT_RELEASE := 1 VERSION ?= $(DEFAULT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) +PACKAGE_VENDOR = NGINX Packaging + SRCDIR= unit-$(VERSION) CODENAME = $(shell lsb_release -cs) @@ -205,6 +207,9 @@ debuild/$(SRCDIR)/debian: echo '9' > debuild/$(SRCDIR)/debian/compat ; \ mkdir -p debuild/$(SRCDIR)/debian/source ; \ echo '3.0 (quilt)' > debuild/$(SRCDIR)/debian/source/format ; \ + cat debian/control.in | sed \ + -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \ + > debuild/$(SRCDIR)/debian/control ; \ cat debian/rules.in | sed \ -e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \ > debuild/$(SRCDIR)/debian/rules ; \ @@ -280,6 +285,7 @@ endif -e "s#%%UNIT_RELEASE%%#$(RELEASE)#g" \ -e "s#%%VERSION%%#$(MODULE_VERSION_$*)#g" \ -e "s#%%RELEASE%%#$(MODULE_RELEASE_$*)#g" \ + -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \ -e "s#%%MODULE_BUILD_DEPENDS%%#$(MODULE_BUILD_DEPENDS_$*)#g" \ -e "s#%%MODULE_DEPENDS%%#$(MODULE_DEPENDS_$*)#g" \ > $@/$(SRCDIR)/debian/control ; \ diff --git a/pkg/deb/debian.module/control-noarch.in b/pkg/deb/debian.module/control-noarch.in index e22bb49a..d9d9e5e1 100644 --- a/pkg/deb/debian.module/control-noarch.in +++ b/pkg/deb/debian.module/control-noarch.in @@ -1,7 +1,7 @@ Source: %%NAME%% Section: admin Priority: extra -Maintainer: Andrei Belov +Maintainer: %%PACKAGE_VENDOR%% Build-Depends: debhelper (>= 9), linux-libc-dev%%MODULE_BUILD_DEPENDS%% Standards-Version: 3.9.5 diff --git a/pkg/deb/debian.module/control.in b/pkg/deb/debian.module/control.in index 7e28f5e9..9a6fa797 100644 --- a/pkg/deb/debian.module/control.in +++ b/pkg/deb/debian.module/control.in @@ -1,7 +1,7 @@ Source: %%NAME%% Section: admin Priority: extra -Maintainer: Andrei Belov +Maintainer: %%PACKAGE_VENDOR%% Build-Depends: debhelper (>= 9), linux-libc-dev, libssl-dev, diff --git a/pkg/deb/debian/control b/pkg/deb/debian/control deleted file mode 100644 index a8e8cdc4..00000000 --- a/pkg/deb/debian/control +++ /dev/null @@ -1,41 +0,0 @@ -Source: unit -Section: admin -Priority: extra -Maintainer: Andrei Belov -Build-Depends: debhelper (>= 9), - linux-libc-dev, - libssl-dev, - libpcre2-dev -Standards-Version: 3.9.5 -Homepage: https://unit.nginx.org - -Package: unit -Section: admin -Architecture: any -Depends: lsb-base, - ${misc:Depends}, ${shlibs:Depends} -Description: NGINX Unit - NGINX Unit is a runtime and delivery environment for modern distributed - applications. It runs the application code in multiple languages - (PHP, Python, Go, etc.), and tightly couples it with traffic delivery - in and out of the application. Take this application server and proxy - directly in the cloud / container environments and fully control your app - dynamically via an API. - -Package: unit-dbg -Section: debug -Architecture: any -Priority: extra -Depends: unit (= ${binary:Version}), - ${misc:Depends} -Description: NGINX Unit (debug symbols) - This package contains the debugging symbols for NGINX Unit. - -Package: unit-dev -Section: libdevel -Priority: optional -Architecture: any -Depends: unit (= ${binary:Version}), - ${misc:Depends} -Description: NGINX Unit (development files) - Library and include files required for NGINX Unit modules development. diff --git a/pkg/deb/debian/control.in b/pkg/deb/debian/control.in new file mode 100644 index 00000000..4d59520e --- /dev/null +++ b/pkg/deb/debian/control.in @@ -0,0 +1,41 @@ +Source: unit +Section: admin +Priority: extra +Maintainer: %%PACKAGE_VENDOR%% +Build-Depends: debhelper (>= 9), + linux-libc-dev, + libssl-dev, + libpcre2-dev +Standards-Version: 3.9.5 +Homepage: https://unit.nginx.org + +Package: unit +Section: admin +Architecture: any +Depends: lsb-base, + ${misc:Depends}, ${shlibs:Depends} +Description: NGINX Unit + NGINX Unit is a runtime and delivery environment for modern distributed + applications. It runs the application code in multiple languages + (PHP, Python, Go, etc.), and tightly couples it with traffic delivery + in and out of the application. Take this application server and proxy + directly in the cloud / container environments and fully control your app + dynamically via an API. + +Package: unit-dbg +Section: debug +Architecture: any +Priority: extra +Depends: unit (= ${binary:Version}), + ${misc:Depends} +Description: NGINX Unit (debug symbols) + This package contains the debugging symbols for NGINX Unit. + +Package: unit-dev +Section: libdevel +Priority: optional +Architecture: any +Depends: unit (= ${binary:Version}), + ${misc:Depends} +Description: NGINX Unit (development files) + Library and include files required for NGINX Unit modules development. diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index f9c57b5b..e67846cf 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -8,6 +8,8 @@ DEFAULT_RELEASE := 1 VERSION ?= $(DEFAULT_VERSION) RELEASE ?= $(DEFAULT_RELEASE) +PACKAGE_VENDOR = NGINX Packaging + ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 6 -a 0%{?amzn} -eq 0'`; echo $$?), 0) OSVER = centos6 else ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 7 -a 0%{?amzn} -eq 0'`; echo $$?), 0) @@ -205,6 +207,7 @@ rpmbuild/SPECS/unit.spec: unit.spec.in ../../docs/changes.xml | rpmbuild/SPECS sed -e "s#%%VERSION%%#$(VERSION)#g" \ -e "s#%%RELEASE%%#$(RELEASE)#g" \ -e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \ + -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \ > rpmbuild/SPECS/unit.spec cd ../../docs && make ../build/unit.rpm-changelog ifneq ($(DEFAULT_VERSION)$(DEFAULT_RELEASE), $(VERSION)$(RELEASE)) @@ -252,6 +255,7 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil -e "s#%%RELEASE%%#$(MODULE_RELEASE_$*)#g" \ -e "s#%%UNIT_VERSION%%#$(VERSION)#g" \ -e "s#%%UNIT_RELEASE%%#$(RELEASE)#g" \ + -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \ -e "s#%%MODULE_SOURCES%%#$${sources}#g" \ -e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \ -e "s#%%MODULE_CONFARGS%%#$(MODULE_CONFARGS_$*)#g" \ diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index 101b4991..4f096c73 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -32,9 +32,8 @@ Summary: %%SUMMARY%% Version: %%VERSION%% Release: %%RELEASE%%%{?dist}.ngx License: ASL 2.0 -Vendor: Nginx Software, Inc. +Vendor: %%PACKAGE_VENDOR%% URL: https://unit.nginx.org/ -Packager: Nginx Software, Inc. Group: System Environment/Daemons Source0: unit-%{version}.tar.gz diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 07ca9e6f..b35b8998 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -33,9 +33,8 @@ Summary: NGINX Unit Version: %%VERSION%% Release: %%RELEASE%%%{?dist}.ngx License: ASL 2.0 -Vendor: Nginx Software, Inc. +Vendor: %%PACKAGE_VENDOR%% URL: https://unit.nginx.org/ -Packager: Nginx Software, Inc. Group: System Environment/Daemons Source0: unit-%{version}.tar.gz -- cgit From 113afb09ea7ddeebf2376cf6df3af212705e6128 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Thu, 22 Apr 2021 13:13:06 +0800 Subject: Router: grouped app and share fields in nxt_http_action_t. This is a prerequisite for further introduction of openat2() features. No functional changes. --- src/nxt_http.h | 12 +++++++++--- src/nxt_http_request.c | 4 ++-- src/nxt_http_route.c | 46 +++++++++++++++++++++++++++------------------- src/nxt_http_static.c | 12 ++++++------ src/nxt_router.c | 2 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/nxt_http.h b/src/nxt_http.h index e30bfeb4..2aa108ec 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -206,16 +206,22 @@ struct nxt_http_action_s { nxt_http_action_t *action); union { nxt_http_route_t *route; - nxt_app_t *application; - nxt_http_action_t *fallback; nxt_upstream_t *upstream; uint32_t upstream_number; nxt_http_status_t return_code; nxt_var_t *var; + + struct { + nxt_app_t *application; + nxt_int_t target; + } app; + + struct { + nxt_http_action_t *fallback; + } share; } u; nxt_str_t name; - nxt_int_t target; }; diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 650c1a89..779cfcf8 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -348,9 +348,9 @@ nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&r->server_name, "localhost"); } - r->app_target = action->target; + r->app_target = action->u.app.target; - nxt_router_process_http_request(task, r, action->u.application); + nxt_router_process_http_request(task, r, action->u.app.application); return NULL; } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 28545fc9..dfdb07df 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -627,14 +627,14 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { offsetof(nxt_http_route_action_conf_t, location) }, { - nxt_string("share"), + nxt_string("proxy"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, share) + offsetof(nxt_http_route_action_conf_t, proxy) }, { - nxt_string("proxy"), + nxt_string("share"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, proxy) + offsetof(nxt_http_route_action_conf_t, share) }, { nxt_string("fallback"), @@ -700,14 +700,14 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, return NXT_OK; } - conf = accf.pass; - if (accf.share != NULL) { conf = accf.share; - action->handler = nxt_http_static_handler; } else if (accf.proxy != NULL) { conf = accf.proxy; + + } else { + conf = accf.pass; } nxt_conf_get_string(conf, &name); @@ -717,14 +717,21 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, return NXT_ERROR; } - if (accf.fallback != NULL) { - action->u.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); - if (nxt_slow_path(action->u.fallback == NULL)) { - return NXT_ERROR; + if (accf.share != NULL) { + action->handler = nxt_http_static_handler; + + if (accf.fallback != NULL) { + action->u.share.fallback = nxt_mp_alloc(mp, + sizeof(nxt_http_action_t)); + if (nxt_slow_path(action->u.share.fallback == NULL)) { + return NXT_ERROR; + } + + return nxt_http_route_action_create(tmcf, accf.fallback, + action->u.share.fallback); } - return nxt_http_route_action_create(tmcf, accf.fallback, - action->u.fallback); + return NXT_OK; } if (accf.proxy != NULL) { @@ -1411,9 +1418,10 @@ nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (action->handler != NULL) { if (action->handler == nxt_http_static_handler - && action->u.fallback != NULL) + && action->u.share.fallback != NULL) { - return nxt_http_action_resolve(task, tmcf, action->u.fallback); + return nxt_http_action_resolve(task, tmcf, + action->u.share.fallback); } return NXT_OK; @@ -1533,14 +1541,14 @@ nxt_http_pass_find(nxt_task_t *task, nxt_mp_t *mp, nxt_router_conf_t *rtcf, } if (segments[2].length != 0) { - targets = action->u.application->targets; + targets = action->u.app.application->targets; for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++); - action->target = i; + action->u.app.target = i; } else { - action->target = 0; + action->u.app.target = 0; } return NXT_OK; @@ -1678,7 +1686,7 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, (void) nxt_router_listener_application(rtcf, name, action); - action->target = 0; + action->u.app.target = 0; return action; } diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index df2655fc..c0b48586 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -49,8 +49,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) { if (!nxt_str_eq(r->method, "HEAD", 4)) { - if (action->u.fallback != NULL) { - return action->u.fallback; + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; } nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED); @@ -127,8 +127,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, break; } - if (level == NXT_LOG_ERR && action->u.fallback != NULL) { - return action->u.fallback; + if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) { + return action->u.share.fallback; } if (status != NXT_HTTP_NOT_FOUND) { @@ -230,8 +230,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_file_close(task, f); if (nxt_slow_path(!nxt_is_dir(&fi))) { - if (action->u.fallback != NULL) { - return action->u.fallback; + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; } nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file", diff --git a/src/nxt_router.c b/src/nxt_router.c index 8524b358..a20e4ede 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -2144,7 +2144,7 @@ nxt_router_listener_application(nxt_router_conf_t *rtcf, nxt_str_t *name, return NXT_DECLINED; } - action->u.application = app; + action->u.app.application = app; action->handler = nxt_http_application_handler; return NXT_OK; -- cgit From 53279af5d44dce2b679399d6a36eb46292928175 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Thu, 29 Apr 2021 22:04:34 +0800 Subject: Static: support for openat2() features. Support for chrooting, rejecting symlinks, and rejecting crossing mounting points on a per-request basis during static file serving. --- auto/files | 32 ++++++++++++ docs/changes.xml | 7 +++ src/nxt_conf_validation.c | 32 ++++++++++++ src/nxt_errno.h | 1 + src/nxt_file.c | 44 +++++++++++++++++ src/nxt_file.h | 32 ++++++++++++ src/nxt_http.h | 2 + src/nxt_http_route.c | 63 +++++++++++++++++++++++- src/nxt_http_static.c | 123 ++++++++++++++++++++++++++++++++++++---------- src/nxt_unix.h | 4 ++ 10 files changed, 313 insertions(+), 27 deletions(-) diff --git a/auto/files b/auto/files index d99e93d7..591c5ee1 100644 --- a/auto/files +++ b/auto/files @@ -49,3 +49,35 @@ nxt_feature_test="#include return 0; }" . auto/feature + + +nxt_feature="openat2()" +nxt_feature_name=NXT_HAVE_OPENAT2 +nxt_feature_run= +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include + #include + #include + #include + #include + + int main() { + struct open_how how; + + memset(&how, 0, sizeof(how)); + + how.flags = O_RDONLY; + how.mode = O_NONBLOCK; + how.resolve = RESOLVE_IN_ROOT + | RESOLVE_NO_SYMLINKS + | RESOLVE_NO_XDEV; + + int fd = syscall(SYS_openat2, AT_FDCWD, \".\", + &how, sizeof(how)); + if (fd == -1) + return 1; + + return 0; + }" +. auto/feature diff --git a/docs/changes.xml b/docs/changes.xml index c1fcc466..0858c2da 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,13 @@ NGINX Unit updated to 1.24.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +support for chrooting, rejecting symlinks, and rejecting crossing mounting +points on a per-request basis during static file serving. + + + diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 8c5d1ec7..ac1a81d8 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -75,6 +75,8 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...); static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_str_t *value); +nxt_inline nxt_int_t nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); @@ -458,6 +460,27 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = { .name = nxt_string("fallback"), .type = NXT_CONF_VLDT_OBJECT, .validator = nxt_conf_vldt_action, + }, { + .name = nxt_string("chroot"), + .type = NXT_CONF_VLDT_STRING, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "chroot", +#endif + }, { + .name = nxt_string("follow_symlinks"), + .type = NXT_CONF_VLDT_BOOLEAN, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "follow_symlinks", +#endif + }, { + .name = nxt_string("traverse_mounts"), + .type = NXT_CONF_VLDT_BOOLEAN, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "traverse_mounts", +#endif }, NXT_CONF_VLDT_END @@ -1032,6 +1055,15 @@ nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...) } +nxt_inline nxt_int_t +nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + return nxt_conf_vldt_error(vldt, "Unit is built without the \"%s\" " + "option support.", data); +} + + static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_str_t *value) diff --git a/src/nxt_errno.h b/src/nxt_errno.h index 40bcfa3f..ec700537 100644 --- a/src/nxt_errno.h +++ b/src/nxt_errno.h @@ -22,6 +22,7 @@ typedef int nxt_err_t; #define NXT_EACCES EACCES #define NXT_EBUSY EBUSY #define NXT_EEXIST EEXIST +#define NXT_ELOOP ELOOP #define NXT_EXDEV EXDEV #define NXT_ENOTDIR ENOTDIR #define NXT_EISDIR EISDIR diff --git a/src/nxt_file.c b/src/nxt_file.c index a9595dd9..5d38d57e 100644 --- a/src/nxt_file.c +++ b/src/nxt_file.c @@ -42,6 +42,50 @@ nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, } +#if (NXT_HAVE_OPENAT2) + +nxt_int_t +nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, + nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd, + nxt_uint_t resolve) +{ + struct open_how how; + + nxt_memzero(&how, sizeof(how)); + + /* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */ + mode |= (O_NONBLOCK | create); + + how.flags = mode; + how.mode = access; + how.resolve = resolve; + + file->fd = syscall(SYS_openat2, dfd, file->name, &how, sizeof(how)); + + file->error = (file->fd == -1) ? nxt_errno : 0; + +#if (NXT_DEBUG) + nxt_thread_time_update(task->thread); +#endif + + nxt_debug(task, "openat2(%FD, \"%FN\"): %FD err:%d", dfd, file->name, + file->fd, file->error); + + if (file->fd != -1) { + return NXT_OK; + } + + if (file->log_level != 0) { + nxt_log(task, file->log_level, "openat2(%FD, \"%FN\") failed %E", dfd, + file->name, file->error); + } + + return NXT_ERROR; +} + +#endif + + void nxt_file_close(nxt_task_t *task, nxt_file_t *file) { diff --git a/src/nxt_file.h b/src/nxt_file.h index 4f56e746..4846305b 100644 --- a/src/nxt_file.h +++ b/src/nxt_file.h @@ -109,6 +109,12 @@ typedef struct { NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access); +#if (NXT_HAVE_OPENAT2) +NXT_EXPORT nxt_int_t nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, + nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd, + nxt_uint_t resolve); +#endif + /* The file open access modes. */ #define NXT_FILE_RDONLY O_RDONLY @@ -116,6 +122,32 @@ NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file, #define NXT_FILE_RDWR O_RDWR #define NXT_FILE_APPEND (O_WRONLY | O_APPEND) +#if (NXT_HAVE_OPENAT2) + +#if defined(O_DIRECTORY) +#define NXT_FILE_DIRECTORY O_DIRECTORY +#else +#define NXT_FILE_DIRECTORY 0 +#endif + +#if defined(O_SEARCH) +#define NXT_FILE_SEARCH (O_SEARCH|NXT_FILE_DIRECTORY) + +#elif defined(O_EXEC) +#define NXT_FILE_SEARCH (O_EXEC|NXT_FILE_DIRECTORY) + +#else +/* + * O_PATH is used in combination with O_RDONLY. The last one is ignored + * if O_PATH is used, but it allows Unit to not fail when it was built on + * modern system (i.e. glibc 2.14+) and run with a kernel older than 2.6.39. + * Then O_PATH is unknown to the kernel and ignored, while O_RDONLY is used. + */ +#define NXT_FILE_SEARCH (O_PATH|O_RDONLY|NXT_FILE_DIRECTORY) +#endif + +#endif /* NXT_HAVE_OPENAT2 */ + /* The file creation modes. */ #define NXT_FILE_CREATE_OR_OPEN O_CREAT #define NXT_FILE_OPEN 0 diff --git a/src/nxt_http.h b/src/nxt_http.h index 2aa108ec..da1124a8 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -217,6 +217,8 @@ struct nxt_http_action_s { } app; struct { + nxt_str_t chroot; + nxt_uint_t resolve; nxt_http_action_t *fallback; } share; } u; diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index dfdb07df..bfe5ce5f 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -50,8 +50,11 @@ typedef struct { nxt_conf_value_t *pass; nxt_conf_value_t *ret; nxt_str_t location; - nxt_conf_value_t *share; nxt_conf_value_t *proxy; + nxt_conf_value_t *share; + nxt_str_t chroot; + nxt_conf_value_t *follow_symlinks; + nxt_conf_value_t *traverse_mounts; nxt_conf_value_t *fallback; } nxt_http_route_action_conf_t; @@ -636,6 +639,21 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { NXT_CONF_MAP_PTR, offsetof(nxt_http_route_action_conf_t, share) }, + { + nxt_string("chroot"), + NXT_CONF_MAP_STR, + offsetof(nxt_http_route_action_conf_t, chroot) + }, + { + nxt_string("follow_symlinks"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, follow_symlinks) + }, + { + nxt_string("traverse_mounts"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, traverse_mounts) + }, { nxt_string("fallback"), NXT_CONF_MAP_PTR, @@ -648,6 +666,11 @@ static nxt_int_t nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_action_t *action) { +#if (NXT_HAVE_OPENAT2) + u_char *p; + uint8_t slash; + nxt_str_t *chroot; +#endif nxt_mp_t *mp; nxt_int_t ret; nxt_str_t name, *string; @@ -720,6 +743,44 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, if (accf.share != NULL) { action->handler = nxt_http_static_handler; +#if (NXT_HAVE_OPENAT2) + string = &accf.chroot; + chroot = &action->u.share.chroot; + + if (string->length > 0) { + action->u.share.resolve |= RESOLVE_IN_ROOT; + + slash = (string->start[string->length - 1] != '/'); + + chroot->length = string->length + (slash ? 1 : 0); + + chroot->start = nxt_mp_alloc(mp, chroot->length + 1); + if (nxt_slow_path(chroot->start == NULL)) { + return NXT_ERROR; + } + + p = nxt_cpymem(chroot->start, string->start, string->length); + + if (slash) { + *p++ = '/'; + } + + *p = '\0'; + } + + if (accf.follow_symlinks != NULL + && !nxt_conf_get_boolean(accf.follow_symlinks)) + { + action->u.share.resolve |= RESOLVE_NO_SYMLINKS; + } + + if (accf.traverse_mounts != NULL + && !nxt_conf_get_boolean(accf.traverse_mounts)) + { + action->u.share.resolve |= RESOLVE_NO_XDEV; + } +#endif + if (accf.fallback != NULL) { action->u.share.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index c0b48586..98d70739 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -31,15 +31,15 @@ nxt_http_action_t * nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - size_t alloc, encode; - u_char *p; + size_t length, encode; + u_char *p, *fname; struct tm tm; nxt_buf_t *fb; nxt_int_t ret; - nxt_str_t index, extension, *mtype; + nxt_str_t index, extension, *mtype, *chroot; nxt_uint_t level; nxt_bool_t need_body; - nxt_file_t *f; + nxt_file_t *f, af, file; nxt_file_info_t fi; nxt_http_field_t *field; nxt_http_status_t status; @@ -63,13 +63,6 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, need_body = 1; } - f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t)); - if (nxt_slow_path(f == NULL)) { - goto fail; - } - - f->fd = NXT_FILE_INVALID; - if (r->path->start[r->path->length - 1] == '/') { /* TODO: dynamic index setting. */ nxt_str_set(&index, "index.html"); @@ -80,23 +73,83 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_null(&extension); } - alloc = action->name.length + r->path->length + index.length + 1; + f = NULL; - f->name = nxt_mp_nget(r->mem_pool, alloc); - if (nxt_slow_path(f->name == NULL)) { + length = action->name.length + r->path->length + index.length; + + fname = nxt_mp_nget(r->mem_pool, length + 1); + if (nxt_slow_path(fname == NULL)) { goto fail; } - p = f->name; + p = fname; p = nxt_cpymem(p, action->name.start, action->name.length); p = nxt_cpymem(p, r->path->start, r->path->length); p = nxt_cpymem(p, index.start, index.length); *p = '\0'; - ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + nxt_memzero(&file, sizeof(nxt_file_t)); + + file.name = fname; + + chroot = &action->u.share.chroot; + +#if (NXT_HAVE_OPENAT2) + + if (action->u.share.resolve != 0) { + + if (chroot->length > 0) { + file.name = chroot->start; + + if (length > chroot->length + && nxt_memcmp(fname, chroot->start, chroot->length) == 0) + { + fname += chroot->length; + ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, + 0); + + } else { + file.error = NXT_EACCES; + ret = NXT_ERROR; + } + + } else if (fname[0] == '/') { + file.name = (u_char *) "/"; + ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0); + + } else { + file.name = (u_char *) "."; + file.fd = AT_FDCWD; + ret = NXT_OK; + } + + if (nxt_fast_path(ret == NXT_OK)) { + af = file; + nxt_memzero(&file, sizeof(nxt_file_t)); + file.name = fname; + + ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY, + NXT_FILE_OPEN, 0, af.fd, + action->u.share.resolve); + + if (af.fd != AT_FDCWD) { + nxt_file_close(task, &af); + } + } + + } else { + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + } + +#else + + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + +#endif if (nxt_slow_path(ret != NXT_OK)) { - switch (f->error) { + + switch (file.error) { /* * For Unix domain sockets "errno" is set to: @@ -117,6 +170,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, break; case NXT_EACCES: +#if (NXT_HAVE_OPENAT2) + case NXT_ELOOP: + case NXT_EXDEV: +#endif level = NXT_LOG_ERR; status = NXT_HTTP_FORBIDDEN; break; @@ -132,13 +189,27 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } if (status != NXT_HTTP_NOT_FOUND) { - nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error); + if (chroot->length > 0) { + nxt_log(task, level, "opening \"%FN\" at \"%FN\" failed %E", + fname, chroot, file.error); + + } else { + nxt_log(task, level, "opening \"%FN\" failed %E", + fname, file.error); + } } nxt_http_request_error(task, r, status); return NULL; } + f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t)); + if (nxt_slow_path(f == NULL)) { + goto fail; + } + + *f = file; + ret = nxt_file_info(f, &fi); if (nxt_slow_path(ret != NXT_OK)) { goto fail; @@ -172,15 +243,15 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_field_name_set(field, "ETag"); - alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; + length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; - p = nxt_mp_nget(r->mem_pool, alloc); + p = nxt_mp_nget(r->mem_pool, length); if (nxt_slow_path(p == NULL)) { goto fail; } field->value = p; - field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"", + field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"", nxt_file_mtime(&fi), nxt_file_size(&fi)) - p; @@ -254,19 +325,19 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_field_name_set(field, "Location"); encode = nxt_encode_uri(NULL, r->path->start, r->path->length); - alloc = r->path->length + encode * 2 + 1; + length = r->path->length + encode * 2 + 1; if (r->args->length > 0) { - alloc += 1 + r->args->length; + length += 1 + r->args->length; } - p = nxt_mp_nget(r->mem_pool, alloc); + p = nxt_mp_nget(r->mem_pool, length); if (nxt_slow_path(p == NULL)) { goto fail; } field->value = p; - field->value_length = alloc; + field->value_length = length; if (encode > 0) { p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length); @@ -294,7 +365,7 @@ fail: nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - if (f != NULL && f->fd != NXT_FILE_INVALID) { + if (f != NULL) { nxt_file_close(task, f); } diff --git a/src/nxt_unix.h b/src/nxt_unix.h index 609f7e95..393f61d9 100644 --- a/src/nxt_unix.h +++ b/src/nxt_unix.h @@ -242,6 +242,10 @@ #include #endif +#if (NXT_HAVE_OPENAT2) +#include +#endif + #if (NXT_TEST_BUILD) #include #endif -- cgit From 8bea2977bc292a9631512734ff6c46b62b24cf26 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 5 May 2021 16:30:26 +0800 Subject: Fixed building without openat2(). --- src/nxt_http_static.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index 98d70739..fe3e19cc 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -39,7 +39,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_t index, extension, *mtype, *chroot; nxt_uint_t level; nxt_bool_t need_body; - nxt_file_t *f, af, file; + nxt_file_t *f, file; nxt_file_info_t fi; nxt_http_field_t *field; nxt_http_status_t status; @@ -124,6 +124,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, } if (nxt_fast_path(ret == NXT_OK)) { + nxt_file_t af; + af = file; nxt_memzero(&file, sizeof(nxt_file_t)); file.name = fname; -- cgit From de631d8c36cebdd69018dfa3ff145ba8b701a2d1 Mon Sep 17 00:00:00 2001 From: Zhidao HONG Date: Wed, 5 May 2021 17:23:33 +0800 Subject: Fixed format and arguments mismatches in error log messages. --- src/nxt_http_static.c | 4 ++-- src/nxt_pcre2.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index fe3e19cc..e603819b 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -192,11 +192,11 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, if (status != NXT_HTTP_NOT_FOUND) { if (chroot->length > 0) { - nxt_log(task, level, "opening \"%FN\" at \"%FN\" failed %E", + nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E", fname, chroot, file.error); } else { - nxt_log(task, level, "opening \"%FN\" failed %E", + nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error); } } diff --git a/src/nxt_pcre2.c b/src/nxt_pcre2.c index 22c4d2d4..cb51062c 100644 --- a/src/nxt_pcre2.c +++ b/src/nxt_pcre2.c @@ -144,7 +144,7 @@ nxt_regex_match(nxt_regex_t *re, u_char *subject, size_t length, if (pcre2_get_error_message(ret, errptr, ERR_BUF_SIZE) < 0) { nxt_thread_log_error(NXT_LOG_ERR, "pcre2_match() failed: %d on \"%*s\" " - "using \"%V\"", ret, subject, length, subject, + "using \"%V\"", ret, length, subject, &re->pattern); } else { -- cgit From e0a061955bba69aaf28022d405362c8a5e444ff6 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 5 May 2021 12:36:57 +0100 Subject: Tests: added tests for openat2() features. --- test/conftest.py | 2 + test/test_share_chroot.py | 108 ++++++++++++++++++++++++++++++++++ test/test_share_mount.py | 142 +++++++++++++++++++++++++++++++++++++++++++++ test/test_share_symlink.py | 96 ++++++++++++++++++++++++++++++ test/test_static.py | 8 --- test/unit/check/chroot.py | 32 ++++++++++ 6 files changed, 380 insertions(+), 8 deletions(-) create mode 100644 test/test_share_chroot.py create mode 100644 test/test_share_mount.py create mode 100644 test/test_share_symlink.py create mode 100644 test/unit/check/chroot.py diff --git a/test/conftest.py b/test/conftest.py index 0eaf54be..a084953a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -15,6 +15,7 @@ from multiprocessing import Process import pytest +from unit.check.chroot import check_chroot from unit.check.go import check_go from unit.check.isolation import check_isolation from unit.check.node import check_node @@ -204,6 +205,7 @@ def pytest_sessionstart(session): k: v for k, v in option.available['modules'].items() if v is not None } + check_chroot() check_isolation() _clear_conf(unit['temp_dir'] + '/control.unit.sock') diff --git a/test/test_share_chroot.py b/test/test_share_chroot.py new file mode 100644 index 00000000..7e53d3f7 --- /dev/null +++ b/test/test_share_chroot.py @@ -0,0 +1,108 @@ +import os +from pathlib import Path + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestShareChroot(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir') + with open(temp_dir + '/assets/index.html', 'w') as index, open( + temp_dir + '/assets/dir/file', 'w' + ) as file: + index.write('0123456789') + file.write('blah') + + test = Path(__file__) + self.test_path = '/' + test.parent.name + '/' + test.name + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + } + ) + + def test_share_chroot(self, temp_dir): + assert self.get(url='/dir/file')['status'] == 200, 'default chroot' + assert self.get(url='/index.html')['status'] == 200, 'default chroot 2' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/file')['status'] == 200, 'chroot' + assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2' + assert self.get(url='/file')['status'] == 403, 'chroot 403' + + def test_share_chroot_permission(self, temp_dir): + os.chmod(temp_dir + '/assets/dir', 0o100) + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/file')['status'] == 200, 'chroot' + + def test_share_chroot_empty(self, temp_dir): + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', + ), 'configure chroot empty absolute' + + assert ( + self.get(url='/dir/file')['status'] == 200 + ), 'chroot empty absolute' + + assert 'success' in self.conf( + {"share": ".", "chroot": ""}, 'routes/0/action', + ), 'configure chroot empty relative' + + assert ( + self.get(url=self.test_path)['status'] == 200 + ), 'chroot empty relative' + + def test_share_chroot_relative(self, is_su, temp_dir): + if is_su: + pytest.skip('does\'t work under root') + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action', + ), 'configure relative chroot' + + assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' + + assert 'success' in self.conf( + {"share": "."}, 'routes/0/action', + ), 'configure relative share' + + assert self.get(url=self.test_path)['status'] == 200, 'relative share' + + assert 'success' in self.conf( + {"share": ".", "chroot": "."}, 'routes/0/action', + ), 'configure relative' + + assert self.get(url=self.test_path)['status'] == 200, 'relative' + + def test_share_chroot_invalid(self, temp_dir): + assert 'error' in self.conf( + {"share": temp_dir, "chroot": True}, 'routes/0/action', + ), 'configure chroot error' + assert 'error' in self.conf( + {"share": temp_dir, "symlinks": "True"}, 'routes/0/action', + ), 'configure symlink error' + assert 'error' in self.conf( + {"share": temp_dir, "mount": "True"}, 'routes/0/action', + ), 'configure mount error' diff --git a/test/test_share_mount.py b/test/test_share_mount.py new file mode 100644 index 00000000..f46e1279 --- /dev/null +++ b/test/test_share_mount.py @@ -0,0 +1,142 @@ +import os +import subprocess + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestShareMount(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, is_su, temp_dir): + if not is_su: + pytest.skip('requires root') + + os.makedirs(temp_dir + '/assets/dir/mount') + os.makedirs(temp_dir + '/assets/dir/dir') + os.makedirs(temp_dir + '/assets/mount') + with open(temp_dir + '/assets/index.html', 'w') as index, open( + temp_dir + '/assets/dir/dir/file', 'w' + ) as file, open(temp_dir + '/assets/mount/index.html', 'w') as mount: + index.write('index') + file.write('file') + mount.write('mount') + + try: + process = subprocess.Popen( + [ + "mount", + "--bind", + temp_dir + "/assets/mount", + temp_dir + "/assets/dir/mount", + ], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except KeyboardInterrupt: + raise + + except: + pytest.fail('Can\'t run mount process.') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets/dir"}}], + } + ) + + yield + + try: + process = subprocess.Popen( + ["umount", "--lazy", temp_dir + "/assets/dir/mount"], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except KeyboardInterrupt: + raise + + except: + pytest.fail('Can\'t run umount process.') + + def test_share_mount(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + resp = self.get(url='/mount/') + resp['status'] == 200 + resp['body'] == 'mount' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, + 'routes/0/action', + ), 'configure mount disable' + + assert self.get(url='/mount/')['status'] == 403 + + assert 'success' in self.conf( + {"share": temp_dir + "/assets/dir", "traverse_mounts": True}, + 'routes/0/action', + ), 'configure mount enable' + + resp = self.get(url='/mount/') + resp['status'] == 200 + resp['body'] == 'mount' + + def test_share_mount_two_blocks(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert 'success' in self.conf( + [ + { + "match": {"method": "HEAD"}, + "action": { + "share": temp_dir + "/assets/dir", + "traverse_mounts": False, + }, + }, + { + "match": {"method": "GET"}, + "action": { + "share": temp_dir + "/assets/dir", + "traverse_mounts": True, + }, + }, + ], + 'routes', + ), 'configure two options' + + assert self.get(url='/mount/')['status'] == 200, 'block enabled' + assert self.head(url='/mount/')['status'] == 403, 'block disabled' + + def test_share_mount_chroot(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets/dir", + "chroot": temp_dir + "/assets", + }, + 'routes/0/action', + ), 'configure chroot mount default' + + self.get(url='/mount/')['status'] == 200, 'chroot' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets/dir", + "chroot": temp_dir + "/assets", + "traverse_mounts": False, + }, + 'routes/0/action', + ), 'configure chroot mount disable' + + self.get(url='/mount/')['status'] == 403, 'chroot mount' diff --git a/test/test_share_symlink.py b/test/test_share_symlink.py new file mode 100644 index 00000000..3970b605 --- /dev/null +++ b/test/test_share_symlink.py @@ -0,0 +1,96 @@ +import os + +import pytest + +from unit.applications.proto import TestApplicationProto + + +class TestShareSymlink(TestApplicationProto): + prerequisites = {'features': ['chroot']} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + os.makedirs(temp_dir + '/assets/dir/dir') + with open(temp_dir + '/assets/index.html', 'w') as index, open( + temp_dir + '/assets/dir/file', 'w' + ) as file: + index.write('0123456789') + file.write('blah') + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + } + ) + + def test_share_symlink(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert self.get(url='/dir')['status'] == 301, 'dir' + assert self.get(url='/dir/file')['status'] == 200, 'file' + assert self.get(url='/link')['status'] == 301, 'symlink dir' + assert self.get(url='/link/file')['status'] == 200, 'symlink file' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "follow_symlinks": False}, + 'routes/0/action', + ), 'configure symlink disable' + + assert self.get(url='/link/file')['status'] == 403, 'symlink disabled' + + assert 'success' in self.conf( + {"share": temp_dir + "/assets", "follow_symlinks": True}, + 'routes/0/action', + ), 'configure symlink enable' + + assert self.get(url='/link/file')['status'] == 200, 'symlink enabled' + + def test_share_symlink_two_blocks(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') + + assert 'success' in self.conf( + [ + { + "match": {"method": "HEAD"}, + "action": { + "share": temp_dir + "/assets", + "follow_symlinks": False, + }, + }, + { + "match": {"method": "GET"}, + "action": { + "share": temp_dir + "/assets", + "follow_symlinks": True, + }, + }, + ], + 'routes', + ), 'configure two options' + + assert self.get(url='/link/file')['status'] == 200, 'block enabled' + assert self.head(url='/link/file')['status'] == 403, 'block disabled' + + def test_share_symlink_chroot(self, temp_dir, skip_alert): + skip_alert(r'opening.*failed') + + os.symlink( + temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/dir/link' + ) + + assert self.get(url='/dir/dir/link')['status'] == 200, 'default chroot' + + assert 'success' in self.conf( + { + "share": temp_dir + "/assets", + "chroot": temp_dir + "/assets/dir/dir", + }, + 'routes/0/action', + ), 'configure chroot' + + assert self.get(url='/dir/dir/link')['status'] == 404, 'chroot' diff --git a/test/test_static.py b/test/test_static.py index d8319292..669e265d 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -168,14 +168,6 @@ class TestStatic(TestApplicationProto): assert self.get(url='/fifo')['status'] == 404, 'fifo' - def test_static_symlink(self, temp_dir): - os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link') - - assert self.get(url='/dir')['status'] == 301, 'dir' - assert self.get(url='/dir/file')['status'] == 200, 'file' - assert self.get(url='/link')['status'] == 301, 'symlink dir' - assert self.get(url='/link/file')['status'] == 200, 'symlink file' - def test_static_method(self): resp = self.head() assert resp['status'] == 200, 'HEAD status' diff --git a/test/unit/check/chroot.py b/test/unit/check/chroot.py new file mode 100644 index 00000000..40b75058 --- /dev/null +++ b/test/unit/check/chroot.py @@ -0,0 +1,32 @@ +import json + +from unit.http import TestHTTP +from unit.option import option + +http = TestHTTP() + + +def check_chroot(): + available = option.available + + resp = http.put( + url='/config', + sock_type='unix', + addr=option.temp_dir + '/control.unit.sock', + body=json.dumps( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "action": { + "share": option.temp_dir, + "chroot": option.temp_dir, + } + } + ], + } + ), + ) + + if 'success' in resp['body']: + available['features']['chroot'] = True -- cgit From b9d5eb285a2fca8b9b60d948acc679a5e16b3f94 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 6 May 2021 14:22:21 +0000 Subject: Static: implemented MIME filtering --- docs/changes.xml | 6 ++++++ src/nxt_conf_validation.c | 4 ++++ src/nxt_http.h | 6 +++++- src/nxt_http_route.c | 41 +++++++++++++++++++++++++++++------------ src/nxt_http_static.c | 39 +++++++++++++++++++++++++++++++++++---- 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 0858c2da..3d4fc698 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.24.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +ability to limit serving of static files by MIME types. + + + support for chrooting, rejecting symlinks, and rejecting crossing mounting diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index ac1a81d8..1f654e53 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -456,6 +456,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = { { .name = nxt_string("share"), .type = NXT_CONF_VLDT_STRING, + }, { + .name = nxt_string("types"), + .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + .validator = nxt_conf_vldt_match_patterns, }, { .name = nxt_string("fallback"), .type = NXT_CONF_VLDT_OBJECT, diff --git a/src/nxt_http.h b/src/nxt_http.h index da1124a8..f82d837e 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -197,7 +197,8 @@ struct nxt_http_request_s { }; -typedef struct nxt_http_route_s nxt_http_route_t; +typedef struct nxt_http_route_s nxt_http_route_t; +typedef struct nxt_http_route_rule_s nxt_http_route_rule_t; struct nxt_http_action_s { @@ -219,6 +220,7 @@ struct nxt_http_action_s { struct { nxt_str_t chroot; nxt_uint_t resolve; + nxt_http_route_rule_t *types; nxt_http_action_t *fallback; } share; } u; @@ -306,6 +308,8 @@ nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, nxt_str_t *segments, nxt_uint_t n); nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_str_t *name); +nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, + nxt_http_route_rule_t *rule, u_char *start, size_t length); nxt_int_t nxt_upstreams_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf); diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index bfe5ce5f..15b85544 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -55,6 +55,7 @@ typedef struct { nxt_str_t chroot; nxt_conf_value_t *follow_symlinks; nxt_conf_value_t *traverse_mounts; + nxt_conf_value_t *types; nxt_conf_value_t *fallback; } nxt_http_route_action_conf_t; @@ -115,7 +116,7 @@ typedef struct { } nxt_http_cookie_t; -typedef struct { +struct nxt_http_route_rule_s { /* The object must be the first field. */ nxt_http_route_object_t object:8; uint32_t items; @@ -131,7 +132,7 @@ typedef struct { } u; nxt_http_route_pattern_t pattern[0]; -} nxt_http_route_rule_t; +}; typedef struct { @@ -198,8 +199,9 @@ static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); -static nxt_int_t nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *cv, nxt_http_action_t *action); +static nxt_int_t nxt_http_route_action_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, + nxt_http_action_t *action); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); @@ -277,8 +279,6 @@ static nxt_http_name_value_t *nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length, u_char *start, u_char *end); static nxt_int_t nxt_http_route_test_cookie(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array); -static nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, - nxt_http_route_rule_t *rule, u_char *start, size_t length); static nxt_int_t nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, u_char *start, size_t length); static nxt_int_t nxt_http_route_memcmp(u_char *start, u_char *test, @@ -460,7 +460,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, match_conf = nxt_conf_get_path(cv, &match_path); n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; - size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *); + size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_test_t *); mp = tmcf->router_conf->mem_pool; @@ -476,7 +476,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NULL; } - ret = nxt_http_route_action_create(tmcf, action_conf, &match->action); + ret = nxt_http_route_action_create(task, tmcf, action_conf, &match->action); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -654,6 +654,11 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { NXT_CONF_MAP_PTR, offsetof(nxt_http_route_action_conf_t, traverse_mounts) }, + { + nxt_string("types"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, types) + }, { nxt_string("fallback"), NXT_CONF_MAP_PTR, @@ -663,8 +668,8 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { static nxt_int_t -nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, - nxt_http_action_t *action) +nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv, nxt_http_action_t *action) { #if (NXT_HAVE_OPENAT2) u_char *p; @@ -676,6 +681,7 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_str_t name, *string; nxt_uint_t encode; nxt_conf_value_t *conf; + nxt_http_route_rule_t *rule; nxt_http_route_action_conf_t accf; nxt_memzero(&accf, sizeof(accf)); @@ -781,6 +787,17 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, } #endif + if (accf.types != NULL) { + rule = nxt_http_route_rule_create(task, mp, accf.types, 0, + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); + if (nxt_slow_path(rule == NULL)) { + return NXT_ERROR; + } + + action->u.share.types = rule; + } + if (accf.fallback != NULL) { action->u.share.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); @@ -788,7 +805,7 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, return NXT_ERROR; } - return nxt_http_route_action_create(tmcf, accf.fallback, + return nxt_http_route_action_create(task, tmcf, accf.fallback, action->u.share.fallback); } @@ -2495,7 +2512,7 @@ nxt_http_route_test_cookie(nxt_http_request_t *r, } -static nxt_int_t +nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule, u_char *start, size_t length) { diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index e603819b..c961bb97 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -75,6 +75,37 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, f = NULL; + rtcf = r->conf->socket_conf->router_conf; + + mtype = NULL; + + if (action->u.share.types != NULL && extension.start == NULL) { + nxt_http_static_extract_extension(r->path, &extension); + mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, + &extension); + + if (mtype != NULL) { + ret = nxt_http_route_test_rule(r, action->u.share.types, + mtype->start, mtype->length); + if (ret == 1) { + goto mime_ok; + } + + if (nxt_slow_path(ret == NXT_ERROR)) { + goto fail; + } + } + + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; + } + + nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN); + return NULL; + } + +mime_ok: + length = action->name.length + r->path->length + index.length; fname = nxt_mp_nget(r->mem_pool, length + 1); @@ -262,10 +293,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_static_extract_extension(r->path, &extension); } - rtcf = r->conf->socket_conf->router_conf; - - mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, - &extension); + if (mtype == NULL) { + mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, + &extension); + } if (mtype != NULL) { field = nxt_list_zero_add(r->resp.fields); -- cgit From 6703b68ed00e7cc8132c0aaf3049ed30889de8a8 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 6 May 2021 14:22:36 +0000 Subject: Tests: MIME filtering --- test/test_share_types.py | 170 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 test/test_share_types.py diff --git a/test/test_share_types.py b/test/test_share_types.py new file mode 100644 index 00000000..98ad106b --- /dev/null +++ b/test/test_share_types.py @@ -0,0 +1,170 @@ +import os +from pathlib import Path + +import pytest +from unit.applications.proto import TestApplicationProto +from unit.option import option + + +class TestShareTypes(TestApplicationProto): + prerequisites = {} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, temp_dir): + Path(temp_dir + '/assets').mkdir() + for ext in ['.xml', '.mp4', '.php', '', '.txt', '.html', '.png']: + Path(temp_dir + '/assets/file' + ext).write_text(ext) + + Path(temp_dir + '/assets/index.html').write_text('index') + + self._load_conf( + { + "listeners": { + "*:7080": {"pass": "routes"}, + "*:7081": {"pass": "routes"}, + }, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + "applications": {}, + } + ) + + def action_update(self, conf): + assert 'success' in self.conf(conf, 'routes/0/action') + + def check_body(self, http_url, body): + resp = self.get(url=http_url) + assert resp['status'] == 200, 'status' + assert resp['body'] == body, 'body' + + def test_share_types_basic(self, temp_dir): + self.action_update({"share": temp_dir + "/assets"}) + self.check_body('/index.html', 'index') + self.check_body('/file.xml', '.xml') + + self.action_update( + {"share": temp_dir + "/assets", "types": "application/xml"} + ) + self.check_body('/file.xml', '.xml') + + self.action_update( + {"share": temp_dir + "/assets", "types": ["application/xml"]} + ) + self.check_body('/file.xml', '.xml') + + self.action_update({"share": temp_dir + "/assets", "types": [""]}) + assert self.get(url='/file.xml')['status'] == 403, 'no mtype' + + def test_share_types_wildcard(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["application/*"]} + ) + self.check_body('/file.xml', '.xml') + assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["video/*"]} + ) + assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' + self.check_body('/file.mp4', '.mp4') + + def test_share_types_negation(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["!application/xml"]} + ) + assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation' + self.check_body('/file.mp4', '.mp4') + + # sorting negation + self.action_update( + { + "share": temp_dir + "/assets", + "types": ["!video/*", "image/png", "!image/jpg"], + } + ) + assert self.get(url='/file.mp4')['status'] == 403, 'negation sort mp4' + self.check_body('/file.png', '.png') + assert self.get(url='/file.jpg')['status'] == 403, 'negation sort jpg' + + def test_share_types_regex(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} + ) + assert self.get(url='/file.php')['status'] == 403, 'regex fail' + self.check_body('/file.html', '.html') + self.check_body('/file.txt', '.txt') + + def test_share_types_case(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} + ) + self.check_body('/file.mp4', '.mp4') + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case xml negation' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]} + ) + assert self.get(url='/file.mp4')['status'] == 200, 'mixed case' + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case video negation' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["vIdEo/*"]} + ) + self.check_body('/file.mp4', '.mp4') + assert ( + self.get(url='/file.xml')['status'] == 403 + ), 'mixed case video * negation' + + def test_share_types_fallback(self, temp_dir): + assert 'success' in self.conf( + [ + { + "match": {"destination": "*:7081"}, + "action": {"return": 200}, + }, + { + "action": { + "share": temp_dir + "/assets", + "types": ["!application/php"], + "fallback": {"proxy": "http://127.0.0.1:7081"}, + } + }, + ], + 'routes', + ), 'configure fallback proxy route' + + self.check_body('/file.php', '') + self.check_body('/file.mp4', '.mp4') + + def test_share_types_index(self, temp_dir): + self.action_update( + {"share": temp_dir + "/assets", "types": "application/xml"} + ) + self.check_body('/', 'index') + self.check_body('/file.xml', '.xml') + assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' + + def test_share_types_custom_mime(self, temp_dir): + self._load_conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [{"action": {"share": temp_dir + "/assets"}}], + "applications": {}, + "settings": { + "http": { + "static": {"mime_types": {"test/mime-type": ["file"]}} + } + }, + } + ) + + self.action_update({"share": temp_dir + "/assets", "types": [""]}) + assert self.get(url='/file')['status'] == 403, 'forbidden custom mime' + + self.action_update( + {"share": temp_dir + "/assets", "types": ["test/mime-type"]} + ) + self.check_body('/file', '') -- cgit From b0e32bc015f8eb146e745b7184549e15c6657f85 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 7 May 2021 07:46:25 +0300 Subject: PHP: forced initialization of $_SERVER in fastcgi_finish_request(). The "auto_globals_jit" PHP option postponed the initialization of the $_SERVER global variable until the script using it had been loaded (e. g. via the "include" expression). As a result, nxt_php_register_variables() could be called after fastcgi_finish_request() had finished the request and nulled ctx->req, which thus caused a segmentation fault. --- docs/changes.xml | 7 +++++++ src/nxt_php_sapi.c | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/changes.xml b/docs/changes.xml index 3d4fc698..e2cb4f1d 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -44,6 +44,13 @@ points on a per-request basis during static file serving. + + +a segmentation fault might have occurred in the PHP module if +fastcgi_finish_request() was used with the "auto_globals_jit" option enabled. + + + diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 8fbe7f65..23c148c8 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -163,7 +163,8 @@ static const zend_function_entry nxt_php_ext_functions[] = { }; -zif_handler nxt_php_chdir_handler; +zif_handler nxt_php_chdir_handler; +zend_auto_global *nxt_php_server_ag; static zend_module_entry nxt_php_unit_module = { @@ -211,6 +212,7 @@ ZEND_NAMED_FUNCTION(nxt_php_chdir) PHP_FUNCTION(fastcgi_finish_request) { + zend_auto_global *ag; nxt_php_run_ctx_t *ctx; if (nxt_slow_path(zend_parse_parameters_none() == FAILURE)) { @@ -240,6 +242,16 @@ PHP_FUNCTION(fastcgi_finish_request) php_header(TSRMLS_C); #endif + ag = nxt_php_server_ag; + + if (ag->armed) { +#ifdef NXT_PHP7 + ag->armed = ag->auto_global_callback(ag->name); +#else + ag->armed = ag->auto_global_callback(ag->name, ag->name_len TSRMLS_CC); +#endif + } + nxt_unit_request_done(ctx->req, NXT_UNIT_OK); ctx->req = NULL; @@ -411,6 +423,19 @@ nxt_php_setup(nxt_task_t *task, nxt_process_t *process, nxt_php_set_options(task, value, ZEND_INI_USER); } +#ifdef NXT_PHP7 + nxt_php_server_ag = zend_hash_str_find_ptr(CG(auto_globals), "_SERVER", + nxt_length("_SERVER")); +#else + zend_hash_quick_find(CG(auto_globals), "_SERVER", sizeof("_SERVER"), + zend_hash_func("_SERVER", sizeof("_SERVER")), + (void **) &nxt_php_server_ag); +#endif + if (nxt_slow_path(nxt_php_server_ag == NULL)) { + nxt_alert(task, "failed to find $_SERVER auto global"); + return NXT_ERROR; + } + return NXT_OK; } -- cgit From b9e8d8073ce9da9c1019ca885d99bca7ff727ba1 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 7 May 2021 16:55:42 +0100 Subject: Tests: PHP test with getting variable before the script is loaded. --- test/php/fastcgi_finish_request/index.php | 2 ++ test/php/fastcgi_finish_request/server.php | 4 ++++ test/test_php_application.py | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 test/php/fastcgi_finish_request/server.php diff --git a/test/php/fastcgi_finish_request/index.php b/test/php/fastcgi_finish_request/index.php index a6211303..fbfae71b 100644 --- a/test/php/fastcgi_finish_request/index.php +++ b/test/php/fastcgi_finish_request/index.php @@ -8,4 +8,6 @@ if (!fastcgi_finish_request()) { } echo "4567"; + +include 'server.php'; ?> diff --git a/test/php/fastcgi_finish_request/server.php b/test/php/fastcgi_finish_request/server.php new file mode 100644 index 00000000..af71c5f2 --- /dev/null +++ b/test/php/fastcgi_finish_request/server.php @@ -0,0 +1,4 @@ + diff --git a/test/test_php_application.py b/test/test_php_application.py index 350ac0d0..66e2ef7d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -98,9 +98,14 @@ class TestPHPApplication(TestApplicationPHP): def test_php_application_fastcgi_finish_request(self, unit_pid): self.load('fastcgi_finish_request') + assert 'success' in self.conf( + {"admin": {"auto_globals_jit": "1"}}, + 'applications/fastcgi_finish_request/options', + ) + assert self.get()['body'] == '0123' - os.kill(unit_pid, signal.SIGUSR1); + os.kill(unit_pid, signal.SIGUSR1) errs = self.findall(r'Error in fastcgi_finish_request') @@ -109,11 +114,16 @@ class TestPHPApplication(TestApplicationPHP): def test_php_application_fastcgi_finish_request_2(self, unit_pid): self.load('fastcgi_finish_request') + assert 'success' in self.conf( + {"admin": {"auto_globals_jit": "1"}}, + 'applications/fastcgi_finish_request/options', + ) + resp = self.get(url='/?skip') assert resp['status'] == 200 assert resp['body'] == '' - os.kill(unit_pid, signal.SIGUSR1); + os.kill(unit_pid, signal.SIGUSR1) errs = self.findall(r'Error in fastcgi_finish_request') -- cgit From a17f7e03d4c34f3dc7cf71ee25c6efc337949d99 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 7 May 2021 17:42:48 +0100 Subject: Tests: added test for TLS with empty Subject field. --- test/test_tls.py | 239 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 130 insertions(+), 109 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index abdca167..d4d1900c 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -6,6 +6,7 @@ import subprocess import pytest from unit.applications.tls import TestApplicationTLS +from unit.option import option class TestTLS(TestApplicationTLS): @@ -28,6 +29,91 @@ class TestTLS(TestApplicationTLS): {"pass": "applications/" + application}, 'listeners/*:' + str(port) ) + def req(self, name='localhost', subject=None, x509=False): + subj = subject if subject is not None else '/CN=' + name + '/' + + subprocess.call( + [ + 'openssl', + 'req', + '-new', + '-subj', + subj, + '-config', + option.temp_dir + '/openssl.conf', + '-out', + option.temp_dir + '/' + name + '.csr', + '-keyout', + option.temp_dir + '/' + name + '.key', + ], + stderr=subprocess.STDOUT, + ) + + def generate_ca_conf(self): + with open(option.temp_dir + '/ca.conf', 'w') as f: + f.write( + """[ ca ] +default_ca = myca + +[ myca ] +new_certs_dir = %(dir)s +database = %(database)s +default_md = sha256 +policy = myca_policy +serial = %(certserial)s +default_days = 1 +x509_extensions = myca_extensions +copy_extensions = copy + +[ myca_policy ] +commonName = optional + +[ myca_extensions ] +basicConstraints = critical,CA:TRUE""" + % { + 'dir': option.temp_dir, + 'database': option.temp_dir + '/certindex', + 'certserial': option.temp_dir + '/certserial', + } + ) + + with open(option.temp_dir + '/certserial', 'w') as f: + f.write('1000') + + with open(option.temp_dir + '/certindex', 'w') as f: + f.write('') + + with open(option.temp_dir + '/certindex.attr', 'w') as f: + f.write('') + + def ca(self, cert='root', out='localhost'): + subprocess.call( + [ + 'openssl', + 'ca', + '-batch', + '-config', + option.temp_dir + '/ca.conf', + '-keyfile', + option.temp_dir + '/' + cert + '.key', + '-cert', + option.temp_dir + '/' + cert + '.crt', + '-in', + option.temp_dir + '/' + out + '.csr', + '-out', + option.temp_dir + '/' + out + '.crt', + ], + stderr=subprocess.STDOUT, + ) + + def set_certificate_req_context(self, cert='root'): + self.context = ssl.create_default_context() + self.context.check_hostname = False + self.context.verify_mode = ssl.CERT_REQUIRED + self.context.load_verify_locations( + option.temp_dir + '/' + cert + '.crt' + ) + def test_tls_listener_option_add(self): self.load('empty') @@ -208,113 +294,13 @@ class TestTLS(TestApplicationTLS): self.certificate('root', False) - subprocess.call( - [ - 'openssl', - 'req', - '-new', - '-subj', - '/CN=int/', - '-config', - temp_dir + '/openssl.conf', - '-out', - temp_dir + '/int.csr', - '-keyout', - temp_dir + '/int.key', - ], - stderr=subprocess.STDOUT, - ) + self.req('int') + self.req('end') - subprocess.call( - [ - 'openssl', - 'req', - '-new', - '-subj', - '/CN=end/', - '-config', - temp_dir + '/openssl.conf', - '-out', - temp_dir + '/end.csr', - '-keyout', - temp_dir + '/end.key', - ], - stderr=subprocess.STDOUT, - ) - - with open(temp_dir + '/ca.conf', 'w') as f: - f.write( - """[ ca ] -default_ca = myca - -[ myca ] -new_certs_dir = %(dir)s -database = %(database)s -default_md = sha256 -policy = myca_policy -serial = %(certserial)s -default_days = 1 -x509_extensions = myca_extensions - -[ myca_policy ] -commonName = supplied - -[ myca_extensions ] -basicConstraints = critical,CA:TRUE""" - % { - 'dir': temp_dir, - 'database': temp_dir + '/certindex', - 'certserial': temp_dir + '/certserial', - } - ) + self.generate_ca_conf() - with open(temp_dir + '/certserial', 'w') as f: - f.write('1000') - - with open(temp_dir + '/certindex', 'w') as f: - f.write('') - - subprocess.call( - [ - 'openssl', - 'ca', - '-batch', - '-subj', - '/CN=int/', - '-config', - temp_dir + '/ca.conf', - '-keyfile', - temp_dir + '/root.key', - '-cert', - temp_dir + '/root.crt', - '-in', - temp_dir + '/int.csr', - '-out', - temp_dir + '/int.crt', - ], - stderr=subprocess.STDOUT, - ) - - subprocess.call( - [ - 'openssl', - 'ca', - '-batch', - '-subj', - '/CN=end/', - '-config', - temp_dir + '/ca.conf', - '-keyfile', - temp_dir + '/int.key', - '-cert', - temp_dir + '/int.crt', - '-in', - temp_dir + '/end.csr', - '-out', - temp_dir + '/end.crt', - ], - stderr=subprocess.STDOUT, - ) + self.ca(cert='root', out='int') + self.ca(cert='int', out='end') crt_path = temp_dir + '/end-int.crt' end_path = temp_dir + '/end.crt' @@ -325,10 +311,7 @@ basicConstraints = critical,CA:TRUE""" ) as int: crt.write(end.read() + int.read()) - self.context = ssl.create_default_context() - self.context.check_hostname = False - self.context.verify_mode = ssl.CERT_REQUIRED - self.context.load_verify_locations(temp_dir + '/root.crt') + self.set_certificate_req_context() # incomplete chain @@ -402,6 +385,44 @@ basicConstraints = critical,CA:TRUE""" self.get_ssl()['status'] == 200 ), 'certificate chain intermediate server' + def test_tls_certificate_empty_cn(self, temp_dir): + self.certificate('root', False) + + self.req(subject='/') + + self.generate_ca_conf() + self.ca() + + self.set_certificate_req_context() + + assert 'success' in self.certificate_load('localhost', 'localhost') + + cert = self.conf_get('/certificates/localhost') + assert cert['chain'][0]['subject'] == {}, 'empty subject' + assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer' + + def test_tls_certificate_empty_cn_san(self, temp_dir): + self.certificate('root', False) + + self.openssl_conf( + rewrite=True, alt_names=["example.com", "www.example.net"] + ) + + self.req(subject='/') + + self.generate_ca_conf() + self.ca() + + self.set_certificate_req_context() + + assert 'success' in self.certificate_load('localhost', 'localhost') + + cert = self.conf_get('/certificates/localhost') + assert cert['chain'][0]['subject'] == { + 'alt_names': ['example.com', 'www.example.net'] + }, 'subject alt_names' + assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer' + @pytest.mark.skip('not yet') def test_tls_reconfigure(self): self.load('empty') -- cgit From 07c6bf165d0e414da3827c7b2aebf5044a7e6093 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 11 May 2021 15:30:12 +0100 Subject: Tests: temporary dir removed after tests execution. --- test/conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index a084953a..c2781571 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -671,4 +671,10 @@ def pytest_sessionfinish(session): option.restart = True unit_stop() + + public_dir(option.cache_dir) shutil.rmtree(option.cache_dir) + + if not option.save_log: + public_dir(option.temp_dir) + shutil.rmtree(option.temp_dir) -- cgit From a0c083af208cd9f676bb56762b4e27a3174a773d Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Wed, 12 May 2021 09:26:55 +0000 Subject: Node.js: a shim for overriding "http" and "websocket" modules. Also added stubs for Server.address() This was done to prevent crashes in some popular frameworks like express Supports both CommonJS and the new ES Modules system syntax e.g: app.js: const http = require('http') app.mjs: import http from "http" Usage on Node 14.16.x and higher: { "type": "external", "processes": {"spare": 0}, "working_directory": '/project', "executable": "/usr/bin/env", "arguments": [ "node", "--loader", "unit-http/require_shim.mjs" "--require", "unit-http/require_shim", "app.js" ] } Usage on Node 14.15.x and lower: { "type": "external", "processes": {"spare": 0}, "working_directory": '/project', "executable": "/usr/bin/env", "arguments": [ "node", "--require", "unit-http/require_shim", "app.js" ] } --- docs/changes.xml | 6 +++ src/nodejs/unit-http/http.js | 15 ++++--- src/nodejs/unit-http/http_server.js | 24 ++++++++--- src/nodejs/unit-http/require_shim.js | 27 ++++++++++++ src/nodejs/unit-http/require_shim.mjs | 18 ++++++++ test/node/404/app.js | 3 +- test/node/basic/app.js | 3 +- test/node/double_end/app.js | 3 +- test/node/get_header_names/app.js | 3 +- test/node/get_header_type/app.js | 3 +- test/node/get_variables/app.js | 3 +- test/node/has_header/app.js | 3 +- test/node/header_name_case/app.js | 3 +- test/node/header_name_valid/app.js | 3 +- test/node/header_value_object/app.js | 3 +- test/node/mirror/app.js | 3 +- test/node/post_variables/app.js | 3 +- test/node/promise_end/app.js | 3 +- test/node/promise_handler/app.js | 3 +- test/node/remove_header/app.js | 3 +- test/node/require_shim/es_modules_http/app.mjs | 6 +++ .../require_shim/es_modules_http_indirect/app.js | 1 + .../es_modules_http_indirect/module.mjs | 6 +++ .../node/require_shim/es_modules_websocket/app.mjs | 30 +++++++++++++ .../es_modules_websocket_indirect/app.js | 1 + .../es_modules_websocket_indirect/module.mjs | 30 +++++++++++++ .../node/require_shim/transitive_dependency/app.js | 1 + .../transitive_dependency/transitive_http.js | 8 ++++ test/node/require_shim/unit_http/app.js | 4 ++ test/node/set_header_array/app.js | 3 +- test/node/status_message/app.js | 3 +- test/node/update_header/app.js | 3 +- test/node/variables/app.js | 3 +- test/node/websockets/mirror/app.js | 7 +-- test/node/websockets/mirror_fragmentation/app.js | 7 +-- test/node/write_before_write_head/app.js | 3 +- test/node/write_buffer/app.js | 3 +- test/node/write_callback/app.js | 3 +- test/node/write_multiple/app.js | 3 +- test/node/write_return/app.js | 3 +- test/test_node_application.py | 19 ++++++-- test/test_node_es_modules.py | 50 ++++++++++++++++++++++ test/unit/applications/lang/node.py | 21 +++++++-- test/unit/check/node.py | 13 +++++- 44 files changed, 288 insertions(+), 78 deletions(-) create mode 100644 src/nodejs/unit-http/require_shim.js create mode 100644 src/nodejs/unit-http/require_shim.mjs mode change 100755 => 100644 test/node/404/app.js mode change 100755 => 100644 test/node/basic/app.js mode change 100755 => 100644 test/node/double_end/app.js mode change 100755 => 100644 test/node/get_header_names/app.js mode change 100755 => 100644 test/node/get_header_type/app.js mode change 100755 => 100644 test/node/get_variables/app.js mode change 100755 => 100644 test/node/has_header/app.js mode change 100755 => 100644 test/node/header_name_case/app.js mode change 100755 => 100644 test/node/header_name_valid/app.js mode change 100755 => 100644 test/node/header_value_object/app.js mode change 100755 => 100644 test/node/mirror/app.js mode change 100755 => 100644 test/node/post_variables/app.js mode change 100755 => 100644 test/node/promise_end/app.js mode change 100755 => 100644 test/node/promise_handler/app.js mode change 100755 => 100644 test/node/remove_header/app.js create mode 100644 test/node/require_shim/es_modules_http/app.mjs create mode 100644 test/node/require_shim/es_modules_http_indirect/app.js create mode 100644 test/node/require_shim/es_modules_http_indirect/module.mjs create mode 100644 test/node/require_shim/es_modules_websocket/app.mjs create mode 100644 test/node/require_shim/es_modules_websocket_indirect/app.js create mode 100644 test/node/require_shim/es_modules_websocket_indirect/module.mjs create mode 100644 test/node/require_shim/transitive_dependency/app.js create mode 100644 test/node/require_shim/transitive_dependency/transitive_http.js create mode 100644 test/node/require_shim/unit_http/app.js mode change 100755 => 100644 test/node/set_header_array/app.js mode change 100755 => 100644 test/node/status_message/app.js mode change 100755 => 100644 test/node/update_header/app.js mode change 100755 => 100644 test/node/variables/app.js mode change 100755 => 100644 test/node/websockets/mirror/app.js mode change 100755 => 100644 test/node/websockets/mirror_fragmentation/app.js mode change 100755 => 100644 test/node/write_before_write_head/app.js mode change 100755 => 100644 test/node/write_buffer/app.js mode change 100755 => 100644 test/node/write_callback/app.js mode change 100755 => 100644 test/node/write_multiple/app.js mode change 100755 => 100644 test/node/write_return/app.js create mode 100644 test/test_node_es_modules.py diff --git a/docs/changes.xml b/docs/changes.xml index e2cb4f1d..c69523f9 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.24.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +a shim for automatic overriding "http" and "websocket" modules in Node.js. + + + ability to limit serving of static files by MIME types. diff --git a/src/nodejs/unit-http/http.js b/src/nodejs/unit-http/http.js index 3a25fa2f..d298a35f 100644 --- a/src/nodejs/unit-http/http.js +++ b/src/nodejs/unit-http/http.js @@ -5,19 +5,22 @@ 'use strict'; -const server = require('unit-http/http_server'); - -const { Server } = server; +const { + Server, + ServerRequest, + ServerResponse, +} = require('./http_server'); function createServer (requestHandler) { return new Server(requestHandler); } +const http = require("http") module.exports = { + ...http, Server, - STATUS_CODES: server.STATUS_CODES, createServer, - IncomingMessage: server.ServerRequest, - ServerResponse: server.ServerResponse + IncomingMessage: ServerRequest, + ServerResponse, }; diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index e59296ae..89964ec3 100644 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -444,17 +444,30 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) { Server.prototype.listen = function (...args) { this.unit.listen(); - const cb = args.pop(); - - if (typeof cb === 'function') { - this.once('listening', cb); + if (typeof args[args.length - 1] === 'function') { + this.once('listening', args[args.length - 1]); } - this.emit('listening'); + /* + * Some express.js apps use the returned server object inside the listening + * callback, so we timeout the listening event to occur after this function + * returns. + */ + setImmediate(function() { + this.emit('listening') + }.bind(this)) return this; }; +Server.prototype.address = function () { + return { + family: "IPv4", + address: "127.0.0.1", + port: 80 + } +} + Server.prototype.emit_request = function (req, res) { if (req._websocket_handshake && this._upgradeListenerCount > 0) { this.emit('upgrade', req, req.socket); @@ -530,7 +543,6 @@ function connectionListener(socket) { } module.exports = { - STATUS_CODES: http.STATUS_CODES, Server, ServerResponse, ServerRequest, diff --git a/src/nodejs/unit-http/require_shim.js b/src/nodejs/unit-http/require_shim.js new file mode 100644 index 00000000..2b307629 --- /dev/null +++ b/src/nodejs/unit-http/require_shim.js @@ -0,0 +1,27 @@ +// can only be ran as part of a --require param on the node process +if (module.parent && module.parent.id === "internal/preload") { + const { Module } = require("module") + + if (!Module.prototype.require.__unit_shim) { + const http = require("./http") + const websocket = require("./websocket") + + const original = Module.prototype.require; + + Module.prototype.require = function (id) { + switch(id) { + case "http": + case "unit-http": + return http + + case "websocket": + case "unit-http/websocket": + return websocket + } + + return original.apply(this, arguments); + } + + Module.prototype.require.__unit_shim = true; + } +} diff --git a/src/nodejs/unit-http/require_shim.mjs b/src/nodejs/unit-http/require_shim.mjs new file mode 100644 index 00000000..067d63d4 --- /dev/null +++ b/src/nodejs/unit-http/require_shim.mjs @@ -0,0 +1,18 @@ +// must be ran as part of a --loader or --experimental-loader param +export async function resolve(specifier, context, defaultResolver) { + switch (specifier) { + case "websocket": + return { + url: new URL("./websocket.js", import.meta.url).href, + format: "cjs" + } + + case "http": + return { + url: new URL("./http.js", import.meta.url).href, + format: "cjs" + } + } + + return defaultResolver(specifier, context, defaultResolver) +} diff --git a/test/node/404/app.js b/test/node/404/app.js old mode 100755 new mode 100644 index 587c432d..ba15c104 --- a/test/node/404/app.js +++ b/test/node/404/app.js @@ -1,7 +1,6 @@ -#!/usr/bin/env node var fs = require('fs'); -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(404, {}).end(fs.readFileSync('404.html')); }).listen(7080); diff --git a/test/node/basic/app.js b/test/node/basic/app.js old mode 100755 new mode 100644 index 7820c474..9092022c --- a/test/node/basic/app.js +++ b/test/node/basic/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) .end('Hello World\n'); }).listen(7080); diff --git a/test/node/double_end/app.js b/test/node/double_end/app.js old mode 100755 new mode 100644 index 63912097..653e33b1 --- a/test/node/double_end/app.js +++ b/test/node/double_end/app.js @@ -1,5 +1,4 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.end().end(); }).listen(7080); diff --git a/test/node/get_header_names/app.js b/test/node/get_header_names/app.js old mode 100755 new mode 100644 index 4cbccc16..a938b762 --- a/test/node/get_header_names/app.js +++ b/test/node/get_header_names/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('DATE', ['date1', 'date2']); res.setHeader('X-Header', 'blah'); res.setHeader('X-Names', res.getHeaderNames()); diff --git a/test/node/get_header_type/app.js b/test/node/get_header_type/app.js old mode 100755 new mode 100644 index b606f142..6e45b71f --- a/test/node/get_header_type/app.js +++ b/test/node/get_header_type/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Number', 100); res.setHeader('X-Type', typeof(res.getHeader('X-Number'))); res.end(); diff --git a/test/node/get_variables/app.js b/test/node/get_variables/app.js old mode 100755 new mode 100644 index 5c1faf41..cded43d2 --- a/test/node/get_variables/app.js +++ b/test/node/get_variables/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { let query = require('url').parse(req.url, true).query; res.setHeader('X-Var-1', query.var1); res.setHeader('X-Var-2', query.var2); diff --git a/test/node/has_header/app.js b/test/node/has_header/app.js old mode 100755 new mode 100644 index eff7f4ff..04b13916 --- a/test/node/has_header/app.js +++ b/test/node/has_header/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Has-Header', res.hasHeader(req.headers['x-header']) + ''); res.end(); }).listen(7080); diff --git a/test/node/header_name_case/app.js b/test/node/header_name_case/app.js old mode 100755 new mode 100644 index 490bd4d5..af157547 --- a/test/node/header_name_case/app.js +++ b/test/node/header_name_case/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Header', '1'); res.setHeader('X-header', '2'); res.setHeader('X-HEADER', '3'); diff --git a/test/node/header_name_valid/app.js b/test/node/header_name_valid/app.js old mode 100755 new mode 100644 index 425f026f..c0c36098 --- a/test/node/header_name_valid/app.js +++ b/test/node/header_name_valid/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {}); res.setHeader('@$', 'test'); res.end(); diff --git a/test/node/header_value_object/app.js b/test/node/header_value_object/app.js old mode 100755 new mode 100644 index ff4e2bb0..bacdc7d5 --- a/test/node/header_value_object/app.js +++ b/test/node/header_value_object/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Header', {}); res.end(); }).listen(7080); diff --git a/test/node/mirror/app.js b/test/node/mirror/app.js old mode 100755 new mode 100644 index 1488917e..bdefe1cd --- a/test/node/mirror/app.js +++ b/test/node/mirror/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { let body = ''; req.on('data', chunk => { body += chunk.toString(); diff --git a/test/node/post_variables/app.js b/test/node/post_variables/app.js old mode 100755 new mode 100644 index 928a38cf..12b867cb --- a/test/node/post_variables/app.js +++ b/test/node/post_variables/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { let body = ''; req.on('data', chunk => { body += chunk.toString(); diff --git a/test/node/promise_end/app.js b/test/node/promise_end/app.js old mode 100755 new mode 100644 index ed22464c..373c3bc6 --- a/test/node/promise_end/app.js +++ b/test/node/promise_end/app.js @@ -1,8 +1,7 @@ -#!/usr/bin/env node var fs = require('fs'); -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.write('blah'); Promise.resolve().then(() => { diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js old mode 100755 new mode 100644 index 51c3666b..32d7d7b9 --- a/test/node/promise_handler/app.js +++ b/test/node/promise_handler/app.js @@ -1,8 +1,7 @@ -#!/usr/bin/env node var fs = require('fs'); -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.end(); if (req.headers['x-write-call']) { diff --git a/test/node/remove_header/app.js b/test/node/remove_header/app.js old mode 100755 new mode 100644 index cd7b80c3..2a591235 --- a/test/node/remove_header/app.js +++ b/test/node/remove_header/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Header', 'test'); res.setHeader('Was-Header', res.hasHeader('X-Header').toString()); diff --git a/test/node/require_shim/es_modules_http/app.mjs b/test/node/require_shim/es_modules_http/app.mjs new file mode 100644 index 00000000..c7bcfe49 --- /dev/null +++ b/test/node/require_shim/es_modules_http/app.mjs @@ -0,0 +1,6 @@ +import http from "http" + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/require_shim/es_modules_http_indirect/app.js b/test/node/require_shim/es_modules_http_indirect/app.js new file mode 100644 index 00000000..535befba --- /dev/null +++ b/test/node/require_shim/es_modules_http_indirect/app.js @@ -0,0 +1 @@ +import("./module.mjs") diff --git a/test/node/require_shim/es_modules_http_indirect/module.mjs b/test/node/require_shim/es_modules_http_indirect/module.mjs new file mode 100644 index 00000000..c7bcfe49 --- /dev/null +++ b/test/node/require_shim/es_modules_http_indirect/module.mjs @@ -0,0 +1,6 @@ +import http from "http" + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/require_shim/es_modules_websocket/app.mjs b/test/node/require_shim/es_modules_websocket/app.mjs new file mode 100644 index 00000000..a71ffa9d --- /dev/null +++ b/test/node/require_shim/es_modules_websocket/app.mjs @@ -0,0 +1,30 @@ +import http from "http" +import websocket from "websocket" + +let server = http.createServer(function() {}); +let webSocketServer = websocket.server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/require_shim/es_modules_websocket_indirect/app.js b/test/node/require_shim/es_modules_websocket_indirect/app.js new file mode 100644 index 00000000..535befba --- /dev/null +++ b/test/node/require_shim/es_modules_websocket_indirect/app.js @@ -0,0 +1 @@ +import("./module.mjs") diff --git a/test/node/require_shim/es_modules_websocket_indirect/module.mjs b/test/node/require_shim/es_modules_websocket_indirect/module.mjs new file mode 100644 index 00000000..a71ffa9d --- /dev/null +++ b/test/node/require_shim/es_modules_websocket_indirect/module.mjs @@ -0,0 +1,30 @@ +import http from "http" +import websocket from "websocket" + +let server = http.createServer(function() {}); +let webSocketServer = websocket.server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/require_shim/transitive_dependency/app.js b/test/node/require_shim/transitive_dependency/app.js new file mode 100644 index 00000000..aaca5216 --- /dev/null +++ b/test/node/require_shim/transitive_dependency/app.js @@ -0,0 +1 @@ +require("./transitive_http") diff --git a/test/node/require_shim/transitive_dependency/transitive_http.js b/test/node/require_shim/transitive_dependency/transitive_http.js new file mode 100644 index 00000000..f1eb98e5 --- /dev/null +++ b/test/node/require_shim/transitive_dependency/transitive_http.js @@ -0,0 +1,8 @@ +const http = require("http"); + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); + +module.exports = http; diff --git a/test/node/require_shim/unit_http/app.js b/test/node/require_shim/unit_http/app.js new file mode 100644 index 00000000..9172e44f --- /dev/null +++ b/test/node/require_shim/unit_http/app.js @@ -0,0 +1,4 @@ +require("unit-http").createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/set_header_array/app.js b/test/node/set_header_array/app.js old mode 100755 new mode 100644 index faac45c7..965330e2 --- a/test/node/set_header_array/app.js +++ b/test/node/set_header_array/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('Set-Cookie', ['tc=one,two,three', 'tc=four,five,six']); res.end(); }).listen(7080); diff --git a/test/node/status_message/app.js b/test/node/status_message/app.js old mode 100755 new mode 100644 index e8a798dd..ba51d35b --- a/test/node/status_message/app.js +++ b/test/node/status_message/app.js @@ -1,5 +1,4 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/update_header/app.js b/test/node/update_header/app.js old mode 100755 new mode 100644 index 0c5cd237..905ac294 --- a/test/node/update_header/app.js +++ b/test/node/update_header/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.setHeader('X-Header', 'test'); res.setHeader('X-Header', 'new'); res.end(); diff --git a/test/node/variables/app.js b/test/node/variables/app.js old mode 100755 new mode 100644 index d8cdc20c..a569dddd --- a/test/node/variables/app.js +++ b/test/node/variables/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { let body = ''; req.on('data', chunk => { body += chunk.toString(); diff --git a/test/node/websockets/mirror/app.js b/test/node/websockets/mirror/app.js old mode 100755 new mode 100644 index 23746465..0443adb2 --- a/test/node/websockets/mirror/app.js +++ b/test/node/websockets/mirror/app.js @@ -1,9 +1,6 @@ -#!/usr/bin/env node -server = require('unit-http').createServer(function() {}); -webSocketServer = require('unit-http/websocket').server; -//server = require('http').createServer(function() {}); -//webSocketServer = require('websocket').server; +server = require('http').createServer(function() {}); +webSocketServer = require('websocket').server; server.listen(7080, function() {}); diff --git a/test/node/websockets/mirror_fragmentation/app.js b/test/node/websockets/mirror_fragmentation/app.js old mode 100755 new mode 100644 index 7024252a..ea580ac2 --- a/test/node/websockets/mirror_fragmentation/app.js +++ b/test/node/websockets/mirror_fragmentation/app.js @@ -1,9 +1,6 @@ -#!/usr/bin/env node -server = require('unit-http').createServer(function() {}); -webSocketServer = require('unit-http/websocket').server; -//server = require('http').createServer(function() {}); -//webSocketServer = require('websocket').server; +server = require('http').createServer(function() {}); +webSocketServer = require('websocket').server; server.listen(7080, function() {}); diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js old mode 100755 new mode 100644 index 724b0efb..2293111a --- a/test/node/write_before_write_head/app.js +++ b/test/node/write_before_write_head/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.write('blah'); res.writeHead(200, {'Content-Type': 'text/plain'}).end(); }).listen(7080); diff --git a/test/node/write_buffer/app.js b/test/node/write_buffer/app.js old mode 100755 new mode 100644 index a7623523..506e8613 --- a/test/node/write_buffer/app.js +++ b/test/node/write_buffer/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}) .end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72])); }).listen(7080); diff --git a/test/node/write_callback/app.js b/test/node/write_callback/app.js old mode 100755 new mode 100644 index 3a9e51e8..71eb4116 --- a/test/node/write_callback/app.js +++ b/test/node/write_callback/app.js @@ -1,8 +1,7 @@ -#!/usr/bin/env node var fs = require('fs'); -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); var a = 'world'; res.write('hello', 'utf8', function() { diff --git a/test/node/write_multiple/app.js b/test/node/write_multiple/app.js old mode 100755 new mode 100644 index 3cbb3b86..e9c51ae0 --- a/test/node/write_multiple/app.js +++ b/test/node/write_multiple/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': 14}); res.write('write'); res.write('write2'); diff --git a/test/node/write_return/app.js b/test/node/write_return/app.js old mode 100755 new mode 100644 index 82dfbc6e..345b6c4b --- a/test/node/write_return/app.js +++ b/test/node/write_return/app.js @@ -1,6 +1,5 @@ -#!/usr/bin/env node -require('unit-http').createServer(function (req, res) { +require('http').createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}) .end(res.write('body').toString()); }).listen(7080); diff --git a/test/test_node_application.py b/test/test_node_application.py index 52ad72ee..59601ff0 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -9,13 +9,26 @@ from unit.utils import waitforfiles class TestNodeApplication(TestApplicationNode): prerequisites = {'modules': {'node': 'all'}} - def test_node_application_basic(self): - self.load('basic') - + def assert_basic_application(self): resp = self.get() assert resp['headers']['Content-Type'] == 'text/plain', 'basic header' assert resp['body'] == 'Hello World\n', 'basic body' + def test_node_application_basic(self): + self.load('basic') + + self.assert_basic_application() + + def test_node_application_require_shim_unit_http(self): + self.load('require_shim/unit_http') + + self.assert_basic_application() + + def test_node_application_require_shim_transitive_dependency(self): + self.load('require_shim/transitive_dependency') + + self.assert_basic_application() + def test_node_application_seq(self): self.load('basic') diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py new file mode 100644 index 00000000..ce27e474 --- /dev/null +++ b/test/test_node_es_modules.py @@ -0,0 +1,50 @@ +import pytest + +from distutils.version import LooseVersion +from unit.applications.lang.node import TestApplicationNode +from unit.applications.websockets import TestApplicationWebsocket + + +class TestNodeESModules(TestApplicationNode): + prerequisites = { + 'modules': { + 'node': lambda v: LooseVersion(v) >= LooseVersion("14.16.0") + } + } + + es_modules = True + ws = TestApplicationWebsocket() + + def assert_basic_application(self): + resp = self.get() + assert resp['headers']['Content-Type'] == 'text/plain', 'basic header' + assert resp['body'] == 'Hello World\n', 'basic body' + + def test_node_es_modules_require_shim_http(self): + self.load('require_shim/es_modules_http', name="app.mjs") + + self.assert_basic_application() + + def test_node_es_modules_require_shim_http_indirect(self): + self.load('require_shim/es_modules_http_indirect', name="app.js") + + self.assert_basic_application() + + def test_node_es_modules_require_shim_websockets(self): + self.load('require_shim/es_modules_websocket', name="app.mjs") + + message = 'blah' + + _, sock, _ = self.ws.upgrade() + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + assert message == frame['data'].decode('utf-8'), 'mirror' + + self.ws.frame_write(sock, self.ws.OP_TEXT, message) + frame = self.ws.frame_read(sock) + + assert message == frame['data'].decode('utf-8'), 'mirror 2' + + sock.close() diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index cc6d06ef..3254f3d4 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -7,15 +7,16 @@ from unit.utils import public_dir class TestApplicationNode(TestApplicationProto): + application_type = "node" + es_modules = False + def prepare_env(self, script): # copy application - shutil.copytree( option.test_dir + '/node/' + script, option.temp_dir + '/node' ) # copy modules - shutil.copytree( option.current_dir + '/node/node_modules', option.temp_dir + '/node/node_modules', @@ -26,6 +27,19 @@ class TestApplicationNode(TestApplicationProto): def load(self, script, name='app.js', **kwargs): self.prepare_env(script) + if self.es_modules: + arguments = [ + "node", + "--loader", + "unit-http/require_shim.mjs", + "--require", + "unit-http/require_shim", + name, + ] + + else: + arguments = ["node", "--require", "unit-http/require_shim", name] + self._load_conf( { "listeners": { @@ -36,7 +50,8 @@ class TestApplicationNode(TestApplicationProto): "type": "external", "processes": {"spare": 0}, "working_directory": option.temp_dir + '/node', - "executable": name, + "executable": '/usr/bin/env', + "arguments": arguments, } }, }, diff --git a/test/unit/check/node.py b/test/unit/check/node.py index 236ba7b5..e053a749 100644 --- a/test/unit/check/node.py +++ b/test/unit/check/node.py @@ -1,6 +1,15 @@ import os +import subprocess def check_node(current_dir): - if os.path.exists(current_dir + '/node/node_modules'): - return True + if not os.path.exists(current_dir + '/node/node_modules'): + return None + + try: + v_bytes = subprocess.check_output(['/usr/bin/env', 'node', '-v']) + + return [str(v_bytes, 'utf-8').lstrip('v').rstrip()] + + except subprocess.CalledProcessError: + return None -- cgit From 25603eae9f8d3c2a6af3c5efb12b4a826776e300 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 12 May 2021 14:37:25 +0100 Subject: Tests: added test for TLS with IP in SAN. --- test/test_tls.py | 23 +++++++++++++++++++++++ test/unit/applications/tls.py | 9 +++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test/test_tls.py b/test/test_tls.py index d4d1900c..3ab6f7d7 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -423,6 +423,29 @@ basicConstraints = critical,CA:TRUE""" }, 'subject alt_names' assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer' + def test_tls_certificate_empty_cn_san_ip(self): + self.certificate('root', False) + + self.openssl_conf( + rewrite=True, + alt_names=['example.com', 'www.example.net', 'IP|10.0.0.1'], + ) + + self.req(subject='/') + + self.generate_ca_conf() + self.ca() + + self.set_certificate_req_context() + + assert 'success' in self.certificate_load('localhost', 'localhost') + + cert = self.conf_get('/certificates/localhost') + assert cert['chain'][0]['subject'] == { + 'alt_names': ['example.com', 'www.example.net'] + }, 'subject alt_names' + assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer' + @pytest.mark.skip('not yet') def test_tls_reconfigure(self): self.load('empty') diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 95eeac55..583b618f 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -76,9 +76,14 @@ class TestApplicationTLS(TestApplicationProto): # Generates alt_names section with dns names a_names = "[alt_names]\n" for i, k in enumerate(alt_names, 1): - a_names += "DNS.%d = %s\n" % (i, k) + k = k.split('|') - # Generates section for sign request extension + if k[0] == 'IP': + a_names += "IP.%d = %s\n" % (i, k[1]) + else: + a_names += "DNS.%d = %s\n" % (i, k[0]) + + # Generates section for sign request extension a_sec = """req_extensions = myca_req_extensions [ myca_req_extensions ] -- cgit From c216f26d3040beef46ca25f73e580153c4c59d02 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 17 May 2021 17:34:15 +0300 Subject: Fixing racing condition on listen socket close in router. Listen socket is actually closed in the instant timer handler. This patch moves the "configuration has been applied" notification to the timer handler to avoid a situation when the user gets the response from the controller, but the listen socket is still open in the router. --- src/nxt_router.c | 24 ++++++++++++++---------- src/nxt_router.h | 2 ++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index a20e4ede..da38aac0 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3223,12 +3223,11 @@ nxt_router_listen_socket_update(nxt_task_t *task, void *obj, void *data) static void nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data) { - nxt_joint_job_t *job; - nxt_socket_conf_t *skcf; - nxt_listen_event_t *lev; - nxt_event_engine_t *engine; + nxt_socket_conf_t *skcf; + nxt_listen_event_t *lev; + nxt_event_engine_t *engine; + nxt_socket_conf_joint_t *joint; - job = obj; skcf = data; engine = task->thread->engine; @@ -3240,15 +3239,13 @@ nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "engine %p: listen socket delete: %d", engine, lev->socket.fd); + joint = lev->socket.data; + joint->close_job = obj; + lev->timer.handler = nxt_router_listen_socket_close; lev->timer.work_queue = &engine->fast_work_queue; nxt_timer_add(engine, &lev->timer, 0); - - job->work.next = NULL; - job->work.handler = nxt_router_conf_wait; - - nxt_event_engine_post(job->tmcf->engine, &job->work); } @@ -3273,6 +3270,7 @@ static void nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data) { nxt_timer_t *timer; + nxt_joint_job_t *job; nxt_listen_event_t *lev; nxt_socket_conf_joint_t *joint; @@ -3287,6 +3285,12 @@ nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data) joint = lev->socket.data; lev->socket.data = NULL; + job = joint->close_job; + job->work.next = NULL; + job->work.handler = nxt_router_conf_wait; + + nxt_event_engine_post(job->tmcf->engine, &job->work); + /* 'task' refers to lev->task and we cannot use after nxt_free() */ task = &task->thread->engine->task; diff --git a/src/nxt_router.h b/src/nxt_router.h index 5804840f..b1ccdf51 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -205,6 +205,8 @@ typedef struct { nxt_event_engine_t *engine; nxt_socket_conf_t *socket_conf; + nxt_joint_job_t *close_job; + nxt_upstream_t **upstreams; /* Modules configuraitons. */ -- cgit From 1198118b3b987930c508d78d90af909eec1835db Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 17 May 2021 15:39:15 +0100 Subject: Tests: fixed incorrect "--restart" mode performing. --- test/conftest.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index c2781571..5ea4e49d 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -355,7 +355,9 @@ def run(request): fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds']) ps['fds'] += fds_diff - assert ps['pid'] == ps_pid, 'same pid %s' % name + if not option.restart: + assert ps['pid'] == ps_pid, 'same pid %s' % name + assert fds_diff <= option.fds_threshold, ( 'descriptors leak %s' % name ) @@ -573,7 +575,7 @@ def _count_fds(pid): ).decode() return len(out.splitlines()) - except (FileNotFoundError, subprocess.CalledProcessError): + except (FileNotFoundError, TypeError, subprocess.CalledProcessError): pass try: @@ -582,7 +584,7 @@ def _count_fds(pid): ).decode() return len(out.splitlines()) - except (FileNotFoundError, subprocess.CalledProcessError): + except (FileNotFoundError, TypeError, subprocess.CalledProcessError): pass return 0 @@ -675,6 +677,6 @@ def pytest_sessionfinish(session): public_dir(option.cache_dir) shutil.rmtree(option.cache_dir) - if not option.save_log: + if not option.save_log and os.path.isdir(option.temp_dir): public_dir(option.temp_dir) shutil.rmtree(option.temp_dir) -- cgit From 19dfeba86b9dda6f1960ba9b3dba4708565d27ad Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Mon, 17 May 2021 14:28:38 -0700 Subject: Fixing a crash after applying the wrong TLS configuration. When an invalid TLS configuration is applied (such as the conf_commands feature), nxt_cert_store_get() creates a buffer to send a certificate request to the main process and adds its default completion handler to an asynchronous queue to free the allocated buffer. However, if configuration fails, nxt_router_conf_error() removes the memory pool used to allocate the buffer, causing a crash when the completion handler is dispatched. Assertion "src/nxt_buf.c:208 assertion failed: data == b->parent" is triggered when is NXT_DEBUG enabled in the configure script. This patch uses a reference counter to retain the memory pool and redefines the completion handler to free the buffer before releasing the memory pool. --- src/nxt_cert.c | 19 +++++++++++++++++++ src/nxt_router.c | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/nxt_cert.c b/src/nxt_cert.c index 3cdb69c1..1806bc19 100644 --- a/src/nxt_cert.c +++ b/src/nxt_cert.c @@ -48,6 +48,7 @@ static nxt_conf_value_t *nxt_cert_name_details(nxt_mp_t *mp, X509 *x509, nxt_bool_t issuer); static nxt_conf_value_t *nxt_cert_alt_names_details(nxt_mp_t *mp, STACK_OF(GENERAL_NAME) *alt_names); +static void nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data); static nxt_lvlhsh_t nxt_cert_info; @@ -1073,6 +1074,9 @@ nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp, goto fail; } + nxt_mp_retain(mp); + b->completion_handler = nxt_cert_buf_completion; + nxt_buf_cpystr(b, name); *b->mem.free++ = '\0'; @@ -1102,6 +1106,21 @@ fail: } +static void +nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data) +{ + nxt_mp_t *mp; + nxt_buf_t *b; + + b = obj; + mp = b->data; + nxt_assert(b->next == NULL); + + nxt_mp_free(mp, b); + nxt_mp_release(mp); +} + + void nxt_cert_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) { diff --git a/src/nxt_router.c b/src/nxt_router.c index da38aac0..2bbe87b8 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -773,7 +773,7 @@ fail: msg->port_msg.stream, 0, NULL); if (tmcf != NULL) { - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } cleanup: @@ -1061,7 +1061,7 @@ nxt_router_conf_ready(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_mp_destroy(rtcf->mem_pool); } - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } @@ -1120,7 +1120,7 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_router_conf_send(task, tmcf, NXT_PORT_MSG_RPC_ERROR); - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } -- cgit From ead6ed999a87fac6fc9ad3f4f94a6cec1d287b5e Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Tue, 18 May 2021 10:14:43 +0000 Subject: Ruby: changing deprecated rb_cData to rb_cObject. Ruby 3.0 deprecated rb_cData with the intention to remove it in release 3.1. This commit changes references of rb_cData to rb_cObject. This was done so we can support distributions that package Ruby 3.0, such as Fedora 34. We also need to call rb_undef_alloc_func because we're no longer deriving from rb_cData. This prevents unnecessary allocations. See: https://docs.ruby-lang.org/en/3.0.0/doc/extension_rdoc.html "It is recommended that klass derives from a special class called Data (rb_cData) but not from Object or other ordinal classes. If it doesn't, you have to call rb_undef_alloc_func(klass)." --- docs/changes.xml | 6 ++++++ src/ruby/nxt_ruby_stream_io.c | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index c69523f9..a4db9ec5 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -50,6 +50,12 @@ points on a per-request basis during static file serving. + + +compatibility with Ruby 3.0. + + + a segmentation fault might have occurred in the PHP module if diff --git a/src/ruby/nxt_ruby_stream_io.c b/src/ruby/nxt_ruby_stream_io.c index 69bf289e..82ad3908 100644 --- a/src/ruby/nxt_ruby_stream_io.c +++ b/src/ruby/nxt_ruby_stream_io.c @@ -25,7 +25,9 @@ nxt_ruby_stream_io_input_init(void) { VALUE stream_io; - stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cData); + stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cObject); + + rb_undef_alloc_func(stream_io); rb_gc_register_address(&stream_io); @@ -46,7 +48,9 @@ nxt_ruby_stream_io_error_init(void) { VALUE stream_io; - stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cData); + stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cObject); + + rb_undef_alloc_func(stream_io); rb_gc_register_address(&stream_io); -- cgit From 2f0cca2e2b48f3f96056bac14e216f1248f8d4a8 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 18 May 2021 16:35:54 +0100 Subject: Tests: added test to check port release. --- test/test_configuration.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/test_configuration.py b/test/test_configuration.py index 7feb3adb..880aef6c 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,5 +1,6 @@ import pytest +import socket from unit.control import TestControl @@ -261,6 +262,33 @@ class TestConfiguration(TestControl): } ), 'explicit ipv6' + def test_listeners_port_release(self): + for i in range(10): + fail = False + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + self.conf( + { + "listeners": {"127.0.0.1:7080": {"pass": "routes"}}, + "routes": [], + } + ) + + resp = self.conf({"listeners": {}, "applications": {}}) + + try: + s.bind(('127.0.0.1', 7080)) + s.listen() + + except OSError: + fail = True + + if fail: + pytest.fail('cannot bind or listen to the address') + + assert 'success' in resp, 'port release' + @pytest.mark.skip('not yet, unsafe') def test_listeners_no_port(self): assert 'error' in self.conf( -- cgit From f60389a782470e31dc555ab864784b536f2544ca Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 20 May 2021 13:02:45 +0000 Subject: Python: support for multiple targets. --- docs/changes.xml | 6 ++ src/nxt_application.h | 1 + src/nxt_conf_validation.c | 132 +++++++++++++++++++++++++-- src/nxt_main_process.c | 6 ++ src/python/nxt_python.c | 164 +++++++++++++++++++++++++++------- src/python/nxt_python.h | 17 +++- src/python/nxt_python_asgi.c | 37 ++++---- src/python/nxt_python_asgi.h | 4 +- src/python/nxt_python_asgi_lifespan.c | 93 +++++++++++++++---- src/python/nxt_python_wsgi.c | 5 +- 10 files changed, 386 insertions(+), 79 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index a4db9ec5..528ab483 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.24.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +multiple "targets" in Python applications. + + + a shim for automatic overriding "http" and "websocket" modules in Node.js. diff --git a/src/nxt_application.h b/src/nxt_application.h index 632c5632..45e7fa48 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -54,6 +54,7 @@ typedef struct { nxt_str_t protocol; uint32_t threads; uint32_t thread_stack_size; + nxt_conf_value_t *targets; } nxt_python_app_conf_t; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 1f654e53..f8381cde 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -100,6 +100,14 @@ static nxt_int_t nxt_conf_vldt_return(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_python(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_python_targets_exclusive( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt, @@ -518,7 +526,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_external_members[] = { }; -static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { +static nxt_conf_vldt_object_t nxt_conf_vldt_python_common_members[] = { { .name = nxt_string("home"), .type = NXT_CONF_VLDT_STRING, @@ -526,13 +534,6 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { .name = nxt_string("path"), .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, .validator = nxt_conf_vldt_python_path, - }, { - .name = nxt_string("module"), - .type = NXT_CONF_VLDT_STRING, - .flags = NXT_CONF_VLDT_REQUIRED, - }, { - .name = nxt_string("callable"), - .type = NXT_CONF_VLDT_STRING, }, { .name = nxt_string("protocol"), .type = NXT_CONF_VLDT_STRING, @@ -550,6 +551,54 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) }; +static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .validator = nxt_conf_vldt_python_targets_exclusive, + .u.string = "module", + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + .validator = nxt_conf_vldt_python_targets_exclusive, + .u.string = "callable", + }, { + .name = nxt_string("targets"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_python_targets, + }, + + NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members) +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + }, + + NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members) +}; + static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { { @@ -1419,6 +1468,71 @@ nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, } +static nxt_int_t +nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_conf_value_t *targets; + + static nxt_str_t targets_str = nxt_string("targets"); + + targets = nxt_conf_get_object_member(value, &targets_str, NULL); + + if (targets != NULL) { + return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_python_members); + } + + return nxt_conf_vldt_object(vldt, value, + nxt_conf_vldt_python_notargets_members); +} + + +static nxt_int_t +nxt_conf_vldt_python_targets_exclusive(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " + "with the \"targets\" object.", data); +} + + +static nxt_int_t +nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + nxt_uint_t n; + + n = nxt_conf_object_members_count(value); + + if (n > 254) { + return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " + "contain more than 254 members."); + } + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_python_target); +} + + +static nxt_int_t +nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, + "The Python target name must not be empty."); + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"%V\" Python target must be " + "an object.", name); + } + + return nxt_conf_vldt_object(vldt, value, + &nxt_conf_vldt_python_target_members); +} + + static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) @@ -1959,7 +2073,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, } types[] = { { nxt_conf_vldt_object, nxt_conf_vldt_external_members }, - { nxt_conf_vldt_object, nxt_conf_vldt_python_members }, + { nxt_conf_vldt_python, NULL }, { nxt_conf_vldt_php, NULL }, { nxt_conf_vldt_object, nxt_conf_vldt_perl_members }, { nxt_conf_vldt_object, nxt_conf_vldt_ruby_members }, diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index f20f2c2c..00f336f6 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -210,6 +210,12 @@ static nxt_conf_map_t nxt_python_app_conf[] = { offsetof(nxt_common_app_conf_t, u.python.threads), }, + { + nxt_string("targets"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.python.targets), + }, + { nxt_string("thread_stack_size"), NXT_CONF_MAP_INT32, diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index d8204937..588a147a 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -24,6 +24,8 @@ typedef struct { static nxt_int_t nxt_python_start(nxt_task_t *task, nxt_process_data_t *data); +static nxt_int_t nxt_python_set_target(nxt_task_t *task, + nxt_python_target_t *target, nxt_conf_value_t *conf); static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value); static int nxt_python_init_threads(nxt_python_app_conf_t *c); static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx); @@ -49,7 +51,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { }; static PyObject *nxt_py_stderr_flush; -PyObject *nxt_py_application; +nxt_python_targets_t *nxt_py_targets; #if PY_MAJOR_VERSION == 3 static wchar_t *nxt_py_home; @@ -66,18 +68,19 @@ static nxt_int_t nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) { int rc; - char *nxt_py_module; - size_t len; + size_t len, size; + uint32_t next; PyObject *obj, *module; - nxt_str_t proto; - const char *callable; + nxt_str_t proto, probe_proto, name; + nxt_int_t ret, n, i; nxt_unit_ctx_t *unit_ctx; nxt_unit_init_t python_init; + nxt_conf_value_t *cv; + nxt_python_targets_t *targets; nxt_common_app_conf_t *app_conf; nxt_python_app_conf_t *c; #if PY_MAJOR_VERSION == 3 char *path; - size_t size; nxt_int_t pep405; static const char pyvenv[] = "/pyvenv.cfg"; @@ -190,38 +193,42 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) Py_CLEAR(obj); - nxt_py_module = nxt_alloca(c->module.length + 1); - nxt_memcpy(nxt_py_module, c->module.start, c->module.length); - nxt_py_module[c->module.length] = '\0'; + n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 1); - module = PyImport_ImportModule(nxt_py_module); - if (nxt_slow_path(module == NULL)) { - nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); - nxt_python_print_exception(); + size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t); + + targets = nxt_unit_malloc(NULL, size); + if (nxt_slow_path(targets == NULL)) { + nxt_alert(task, "Could not allocate targets"); goto fail; } - callable = (c->callable != NULL) ? c->callable : "application"; + memset(targets, 0, size); - obj = PyDict_GetItemString(PyModule_GetDict(module), callable); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to get \"%s\" " - "from module \"%s\"", callable, nxt_py_module); - goto fail; - } + targets->count = n; + nxt_py_targets = targets; - if (nxt_slow_path(PyCallable_Check(obj) == 0)) { - nxt_alert(task, "\"%s\" in module \"%s\" " - "is not a callable object", callable, nxt_py_module); - goto fail; - } + if (c->targets != NULL) { + next = 0; - nxt_py_application = obj; - obj = NULL; + for (i = 0; /* void */; i++) { + cv = nxt_conf_next_object_member(c->targets, &name, &next); + if (cv == NULL) { + break; + } - Py_INCREF(nxt_py_application); + ret = nxt_python_set_target(task, &targets->target[i], cv); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } - Py_CLEAR(module); + } else { + ret = nxt_python_set_target(task, &targets->target[0], app_conf->self); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } nxt_unit_default_init(task, &python_init); @@ -232,7 +239,18 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) proto = c->protocol; if (proto.length == 0) { - proto = nxt_python_asgi_check(nxt_py_application) ? asgi : wsgi; + proto = nxt_python_asgi_check(targets->target[0].application) + ? asgi : wsgi; + + for (i = 1; i < targets->count; i++) { + probe_proto = nxt_python_asgi_check(targets->target[i].application) + ? asgi : wsgi; + if (probe_proto.start != proto.start) { + nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, " + "specify protocol in config if incorrect"); + goto fail; + } + } } if (nxt_strstr_eq(&proto, &asgi)) { @@ -298,6 +316,81 @@ fail: } +static nxt_int_t +nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, + nxt_conf_value_t *conf) +{ + char *callable, *module_name; + PyObject *module, *obj; + nxt_str_t str; + nxt_conf_value_t *value; + + static nxt_str_t module_str = nxt_string("module"); + static nxt_str_t callable_str = nxt_string("callable"); + + module = obj = NULL; + + value = nxt_conf_get_object_member(conf, &module_str, NULL); + if (nxt_slow_path(value == NULL)) { + goto fail; + } + + nxt_conf_get_string(value, &str); + + module_name = nxt_alloca(str.length + 1); + nxt_memcpy(module_name, str.start, str.length); + module_name[str.length] = '\0'; + + module = PyImport_ImportModule(module_name); + if (nxt_slow_path(module == NULL)) { + nxt_alert(task, "Python failed to import module \"%s\"", module_name); + nxt_python_print_exception(); + goto fail; + } + + value = nxt_conf_get_object_member(conf, &callable_str, NULL); + if (value == NULL) { + callable = nxt_alloca(12); + nxt_memcpy(callable, "application", 12); + + } else { + nxt_conf_get_string(value, &str); + + callable = nxt_alloca(str.length + 1); + nxt_memcpy(callable, str.start, str.length); + callable[str.length] = '\0'; + } + + obj = PyDict_GetItemString(PyModule_GetDict(module), callable); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"", + callable, module); + goto fail; + } + + if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", + callable, module); + goto fail; + } + + target->application = obj; + obj = NULL; + + Py_INCREF(target->application); + Py_CLEAR(module); + + return NXT_OK; + +fail: + + Py_XDECREF(obj); + Py_XDECREF(module); + + return NXT_ERROR; +} + + static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value) { @@ -596,12 +689,21 @@ nxt_python_done_strings(nxt_python_string_t *pstr) static void nxt_python_atexit(void) { + nxt_int_t i; + if (nxt_py_proto.done != NULL) { nxt_py_proto.done(); } Py_XDECREF(nxt_py_stderr_flush); - Py_XDECREF(nxt_py_application); + + if (nxt_py_targets != NULL) { + for (i = 0; i < nxt_py_targets->count; i++) { + Py_XDECREF(nxt_py_targets->target[i].application); + } + + nxt_unit_free(NULL, nxt_py_targets); + } Py_Finalize(); diff --git a/src/python/nxt_python.h b/src/python/nxt_python.h index b581dd46..a5c1d9a6 100644 --- a/src/python/nxt_python.h +++ b/src/python/nxt_python.h @@ -37,13 +37,28 @@ #define NXT_HAVE_ASGI 1 #endif -extern PyObject *nxt_py_application; + +typedef struct { + PyObject *application; + nxt_bool_t asgi_legacy; +} nxt_python_target_t; + + +typedef struct { + nxt_int_t count; + nxt_python_target_t target[0]; +} nxt_python_targets_t; + + +extern nxt_python_targets_t *nxt_py_targets; + typedef struct { nxt_str_t string; PyObject **object_p; } nxt_python_string_t; + typedef struct { int (*ctx_data_alloc)(void **pdata); void (*ctx_data_free)(void *data); diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index a6f94507..1d220678 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -43,8 +43,6 @@ static void nxt_py_asgi_shm_ack_handler(nxt_unit_ctx_t *ctx); static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args); static void nxt_python_asgi_done(void); - -int nxt_py_asgi_legacy; static PyObject *nxt_py_port_read; static nxt_unit_port_t *nxt_py_shared_port; @@ -137,6 +135,7 @@ int nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) { PyObject *func; + nxt_int_t i; PyCodeObject *code; nxt_unit_debug(NULL, "asgi_init"); @@ -161,21 +160,23 @@ nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) return NXT_UNIT_ERROR; } - func = nxt_python_asgi_get_func(nxt_py_application); - if (nxt_slow_path(func == NULL)) { - nxt_unit_alert(NULL, "Python cannot find function for callable"); - return NXT_UNIT_ERROR; - } + for (i = 0; i < nxt_py_targets->count; i++) { + func = nxt_python_asgi_get_func(nxt_py_targets->target[i].application); + if (nxt_slow_path(func == NULL)) { + nxt_unit_alert(NULL, "Python cannot find function for callable"); + return NXT_UNIT_ERROR; + } - code = (PyCodeObject *) PyFunction_GET_CODE(func); + code = (PyCodeObject *) PyFunction_GET_CODE(func); - if ((code->co_flags & CO_COROUTINE) == 0) { - nxt_unit_debug(NULL, "asgi: callable is not a coroutine function " - "switching to legacy mode"); - nxt_py_asgi_legacy = 1; - } + if ((code->co_flags & CO_COROUTINE) == 0) { + nxt_unit_debug(NULL, "asgi: callable is not a coroutine function " + "switching to legacy mode"); + nxt_py_targets->target[i].asgi_legacy = 1; + } - Py_DECREF(func); + Py_DECREF(func); + } init->callbacks.request_handler = nxt_py_asgi_request_handler; init->callbacks.data_handler = nxt_py_asgi_http_data_handler; @@ -408,6 +409,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) { PyObject *scope, *res, *task, *receive, *send, *done, *asgi; PyObject *stage2; + nxt_python_target_t *target; nxt_py_asgi_ctx_data_t *ctx_data; if (req->request->websocket_handshake) { @@ -456,17 +458,18 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } req->data = asgi; + target = &nxt_py_targets->target[req->request->app_target]; - if (!nxt_py_asgi_legacy) { + if (!target->asgi_legacy) { nxt_unit_req_debug(req, "Python call ASGI 3.0 application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, + res = PyObject_CallFunctionObjArgs(target->application, scope, receive, send, NULL); } else { nxt_unit_req_debug(req, "Python call legacy application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); + res = PyObject_CallFunctionObjArgs(target->application, scope, NULL); if (nxt_slow_path(res == NULL)) { nxt_unit_req_error(req, "Python failed to call legacy app stage1"); diff --git a/src/python/nxt_python_asgi.h b/src/python/nxt_python_asgi.h index 37f2a099..20702065 100644 --- a/src/python/nxt_python_asgi.h +++ b/src/python/nxt_python_asgi.h @@ -33,7 +33,7 @@ typedef struct { PyObject *loop_remove_reader; PyObject *quit_future; PyObject *quit_future_set_result; - PyObject *lifespan; + PyObject **target_lifespans; nxt_unit_port_t *port; } nxt_py_asgi_ctx_data_t; @@ -69,6 +69,4 @@ int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data); int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx); -extern int nxt_py_asgi_legacy; - #endif /* _NXT_PYTHON_ASGI_H_INCLUDED_ */ diff --git a/src/python/nxt_python_asgi_lifespan.c b/src/python/nxt_python_asgi_lifespan.c index 506eaf4d..1fc0e6b7 100644 --- a/src/python/nxt_python_asgi_lifespan.c +++ b/src/python/nxt_python_asgi_lifespan.c @@ -27,7 +27,10 @@ typedef struct { PyObject *receive_future; } nxt_py_asgi_lifespan_t; - +static PyObject *nxt_py_asgi_lifespan_target_startup( + nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target); +static int nxt_py_asgi_lifespan_target_shutdown( + nxt_py_asgi_lifespan_t *lifespan); static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none); static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict); static PyObject *nxt_py_asgi_lifespan_send_startup( @@ -69,24 +72,60 @@ static PyTypeObject nxt_py_asgi_lifespan_type = { int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) { - int rc; + size_t size; + PyObject *lifespan; + PyObject **target_lifespans; + nxt_int_t i; + nxt_python_target_t *target; + + size = nxt_py_targets->count * sizeof(PyObject*); + + target_lifespans = nxt_unit_malloc(NULL, size); + if (nxt_slow_path(target_lifespans == NULL)) { + nxt_unit_alert(NULL, "Failed to allocate lifespan data"); + return NXT_UNIT_ERROR; + } + + memset(target_lifespans, 0, size); + + for (i = 0; i < nxt_py_targets->count; i++) { + target = &nxt_py_targets->target[i]; + + lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target); + if (nxt_slow_path(lifespan == NULL)) { + return NXT_UNIT_ERROR; + } + + target_lifespans[i] = lifespan; + } + + ctx_data->target_lifespans = target_lifespans; + + return NXT_UNIT_OK; +} + + +static PyObject * +nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data, + nxt_python_target_t *target) +{ PyObject *scope, *res, *py_task, *receive, *send, *done; PyObject *stage2; - nxt_py_asgi_lifespan_t *lifespan; + nxt_py_asgi_lifespan_t *lifespan, *ret; if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) { nxt_unit_alert(NULL, "Python failed to initialize the 'asgi_lifespan' type object"); - return NXT_UNIT_ERROR; + return NULL; } lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type); if (nxt_slow_path(lifespan == NULL)) { nxt_unit_alert(NULL, "Python failed to create lifespan object"); - return NXT_UNIT_ERROR; + return NULL; } - rc = NXT_UNIT_ERROR; + ret = NULL; receive = PyObject_GetAttrString((PyObject *) lifespan, "receive"); if (nxt_slow_path(receive == NULL)) { @@ -130,23 +169,25 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) goto release_future; } - if (!nxt_py_asgi_legacy) { + if (!target->asgi_legacy) { nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, + res = PyObject_CallFunctionObjArgs(target->application, scope, receive, send, NULL); } else { nxt_unit_req_debug(NULL, "Python call legacy application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); + res = PyObject_CallFunctionObjArgs(target->application, scope, NULL); if (nxt_slow_path(res == NULL)) { nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, "ASGI Lifespan processing exception"); nxt_python_print_exception(); lifespan->disabled = 1; - rc = NXT_UNIT_OK; + + Py_INCREF(lifespan); + ret = lifespan; goto release_scope; } @@ -211,10 +252,9 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) Py_DECREF(res); if (lifespan->startup_sent == 1 || lifespan->disabled) { - ctx_data->lifespan = (PyObject *) lifespan; - Py_INCREF(ctx_data->lifespan); + Py_INCREF(lifespan); - rc = NXT_UNIT_OK; + ret = lifespan; } release_task: @@ -232,20 +272,41 @@ release_receive: release_lifespan: Py_DECREF(lifespan); - return rc; + return (PyObject *) ret; } int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx) { - PyObject *msg, *future, *res; + nxt_int_t i, ret; nxt_py_asgi_lifespan_t *lifespan; nxt_py_asgi_ctx_data_t *ctx_data; ctx_data = ctx->data; - lifespan = (nxt_py_asgi_lifespan_t *) ctx_data->lifespan; + for (i = 0; i < nxt_py_targets->count; i++) { + lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i]; + + ret = nxt_py_asgi_lifespan_target_shutdown(lifespan); + if (nxt_slow_path(ret != NXT_UNIT_OK)) { + return NXT_UNIT_ERROR; + } + } + + nxt_unit_free(NULL, ctx_data->target_lifespans); + + return NXT_UNIT_OK; +} + + +static int +nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan) +{ + PyObject *msg, *future, *res; + nxt_py_asgi_ctx_data_t *ctx_data; + + ctx_data = lifespan->ctx_data; if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) { return NXT_UNIT_OK; diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c index 77c45af5..b80d10fa 100644 --- a/src/python/nxt_python_wsgi.c +++ b/src/python/nxt_python_wsgi.c @@ -302,7 +302,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req) { int rc; PyObject *environ, *args, *response, *iterator, *item; - PyObject *close, *result; + PyObject *close, *result, *application; nxt_bool_t prepare_environ; nxt_python_ctx_t *pctx; @@ -348,7 +348,8 @@ nxt_python_request_handler(nxt_unit_request_info_t *req) Py_INCREF(pctx->start_resp); PyTuple_SET_ITEM(args, 1, pctx->start_resp); - response = PyObject_CallObject(nxt_py_application, args); + application = nxt_py_targets->target[req->request->app_target].application; + response = PyObject_CallObject(application, args); Py_DECREF(args); -- cgit From e50bb120e2a2dfad55d28724133656264ef13dc8 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Thu, 20 May 2021 13:03:12 +0000 Subject: Tests: Python targets. --- test/python/lifespan/empty/asgi.py | 16 ++++-- test/python/targets/asgi.py | 54 ++++++++++++++++++++ test/python/targets/wsgi.py | 8 +++ test/test_asgi_lifespan.py | 95 +++++++++++++++++++++++++---------- test/test_asgi_targets.py | 92 +++++++++++++++++++++++++++++++++ test/test_python_targets.py | 51 +++++++++++++++++++ test/unit/applications/lang/python.py | 11 +++- 7 files changed, 295 insertions(+), 32 deletions(-) create mode 100644 test/python/targets/asgi.py create mode 100644 test/python/targets/wsgi.py create mode 100644 test/test_asgi_targets.py create mode 100644 test/test_python_targets.py diff --git a/test/python/lifespan/empty/asgi.py b/test/python/lifespan/empty/asgi.py index ea43af13..8ceecc2f 100644 --- a/test/python/lifespan/empty/asgi.py +++ b/test/python/lifespan/empty/asgi.py @@ -1,19 +1,19 @@ import os -async def application(scope, receive, send): +async def handler(prefix, scope, receive, send): if scope['type'] == 'lifespan': - with open('version', 'w+') as f: + with open(prefix + 'version', 'w+') as f: f.write( scope['asgi']['version'] + ' ' + scope['asgi']['spec_version'] ) while True: message = await receive() if message['type'] == 'lifespan.startup': - os.remove('startup') + os.remove(prefix + 'startup') await send({'type': 'lifespan.startup.complete'}) elif message['type'] == 'lifespan.shutdown': - os.remove('shutdown') + os.remove(prefix + 'shutdown') await send({'type': 'lifespan.shutdown.complete'}) return @@ -25,3 +25,11 @@ async def application(scope, receive, send): 'headers': [(b'content-length', b'0'),], } ) + + +async def application(scope, receive, send): + return await handler('', scope, receive, send) + + +async def application2(scope, receive, send): + return await handler('app2_', scope, receive, send) diff --git a/test/python/targets/asgi.py b/test/python/targets/asgi.py new file mode 100644 index 00000000..b51f3964 --- /dev/null +++ b/test/python/targets/asgi.py @@ -0,0 +1,54 @@ +async def application_201(scope, receive, send): + assert scope['type'] == 'http' + + await send( + { + 'type': 'http.response.start', + 'status': 201, + 'headers': [(b'content-length', b'0')], + } + ) + + +async def application_200(scope, receive, send): + assert scope['type'] == 'http' + + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0')], + } + ) + + +def legacy_application_200(scope): + assert scope['type'] == 'http' + + return legacy_app_http_200 + + +async def legacy_app_http_200(receive, send): + await send( + { + 'type': 'http.response.start', + 'status': 200, + 'headers': [(b'content-length', b'0')], + } + ) + + +def legacy_application_201(scope, receive=None, send=None): + assert scope['type'] == 'http' + + return legacy_app_http_201 + + +async def legacy_app_http_201(receive, send): + await send( + { + 'type': 'http.response.start', + 'status': 201, + 'headers': [(b'content-length', b'0')], + } + ) diff --git a/test/python/targets/wsgi.py b/test/python/targets/wsgi.py new file mode 100644 index 00000000..fa17ab87 --- /dev/null +++ b/test/python/targets/wsgi.py @@ -0,0 +1,8 @@ +def wsgi_target_a(env, start_response): + start_response('200', [('Content-Length', '1')]) + return [b'1'] + + +def wsgi_target_b(env, start_response): + start_response('200', [('Content-Length', '1')]) + return [b'2'] diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py index 43286e22..90866ec3 100644 --- a/test/test_asgi_lifespan.py +++ b/test/test_asgi_lifespan.py @@ -14,45 +14,88 @@ class TestASGILifespan(TestApplicationPython): } load_module = 'asgi' - def test_asgi_lifespan(self): - self.load('lifespan/empty') + def setup_cookies(self, prefix): + base_dir = option.test_dir + '/python/lifespan/empty' - startup_path = option.test_dir + '/python/lifespan/empty/startup' - shutdown_path = option.test_dir + '/python/lifespan/empty/shutdown' - version_path = option.test_dir + '/python/lifespan/empty/version' + os.chmod(base_dir, 0o777) - os.chmod(option.test_dir + '/python/lifespan/empty', 0o777) + for name in ['startup', 'shutdown', 'version']: + path = option.test_dir + '/python/lifespan/empty/' + prefix + name + open(path, 'a').close() + os.chmod(path, 0o777) - open(startup_path, 'a').close() - os.chmod(startup_path, 0o777) + def assert_cookies(self, prefix): + for name in ['startup', 'shutdown']: + path = option.test_dir + '/python/lifespan/empty/' + prefix + name + exists = os.path.isfile(path) + if exists: + os.remove(path) - open(shutdown_path, 'a').close() - os.chmod(shutdown_path, 0o777) + assert not exists, name - open(version_path, 'a').close() - os.chmod(version_path, 0o777) + path = option.test_dir + '/python/lifespan/empty/' + prefix + 'version' - assert self.get()['status'] == 204 + with open(path, 'r') as f: + version = f.read() - unit_stop() + os.remove(path) - is_startup = os.path.isfile(startup_path) - is_shutdown = os.path.isfile(shutdown_path) + assert version == '3.0 2.0', 'version' - if is_startup: - os.remove(startup_path) + def test_asgi_lifespan(self): + self.load('lifespan/empty') - if is_shutdown: - os.remove(shutdown_path) + self.setup_cookies('') - with open(version_path, 'r') as f: - version = f.read() + assert self.get()['status'] == 204 - os.remove(version_path) + unit_stop() - assert not is_startup, 'startup' - assert not is_shutdown, 'shutdown' - assert version == '3.0 2.0', 'version' + self.assert_cookies('') + + def test_asgi_lifespan_targets(self): + assert 'success' in self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"uri": "/1"}, + "action": {"pass": "applications/targets/1"}, + }, + { + "match": {"uri": "/2"}, + "action": {"pass": "applications/targets/2"}, + }, + ], + "applications": { + "targets": { + "type": "python", + "processes": {"spare": 0}, + "working_directory": option.test_dir + + "/python/lifespan/empty", + "path": option.test_dir + '/python/lifespan/empty', + "targets": { + "1": {"module": "asgi", "callable": "application"}, + "2": { + "module": "asgi", + "callable": "application2", + }, + }, + } + }, + } + ) + + self.setup_cookies('') + self.setup_cookies('app2_') + + assert self.get(url="/1")['status'] == 204 + assert self.get(url="/2")['status'] == 204 + + unit_stop() + + self.assert_cookies('') + self.assert_cookies('app2_') def test_asgi_lifespan_failed(self): self.load('lifespan/failed') diff --git a/test/test_asgi_targets.py b/test/test_asgi_targets.py new file mode 100644 index 00000000..a0eb1f84 --- /dev/null +++ b/test/test_asgi_targets.py @@ -0,0 +1,92 @@ +from distutils.version import LooseVersion + +import pytest + +from unit.applications.lang.python import TestApplicationPython +from unit.option import option + + +class TestASGITargets(TestApplicationPython): + prerequisites = { + 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')} + } + load_module = 'asgi' + + @pytest.fixture(autouse=True) + def setup_method_fixture(self): + assert 'success' in self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"uri": "/1"}, + "action": {"pass": "applications/targets/1"}, + }, + { + "match": {"uri": "/2"}, + "action": {"pass": "applications/targets/2"}, + }, + ], + "applications": { + "targets": { + "type": "python", + "processes": {"spare": 0}, + "working_directory": option.test_dir + + "/python/targets/", + "path": option.test_dir + '/python/targets/', + "protocol": "asgi", + "targets": { + "1": { + "module": "asgi", + "callable": "application_200", + }, + "2": { + "module": "asgi", + "callable": "application_201", + }, + }, + } + }, + } + ) + + def conf_targets(self, targets): + assert 'success' in self.conf(targets, 'applications/targets/targets') + + def test_asgi_targets(self): + assert self.get(url='/1')['status'] == 200 + assert self.get(url='/2')['status'] == 201 + + def test_asgi_targets_legacy(self): + self.conf_targets( + { + "1": {"module": "asgi", "callable": "legacy_application_200"}, + "2": {"module": "asgi", "callable": "legacy_application_201"}, + } + ) + + assert self.get(url='/1')['status'] == 200 + assert self.get(url='/2')['status'] == 201 + + def test_asgi_targets_mix(self): + self.conf_targets( + { + "1": {"module": "asgi", "callable": "application_200"}, + "2": {"module": "asgi", "callable": "legacy_application_201"}, + } + ) + + assert self.get(url='/1')['status'] == 200 + assert self.get(url='/2')['status'] == 201 + + def test_asgi_targets_broken(self, skip_alert): + skip_alert(r'Python failed to get "blah" from module') + + self.conf_targets( + { + "1": {"module": "asgi", "callable": "application_200"}, + "2": {"module": "asgi", "callable": "blah"}, + } + ) + + assert self.get(url='/1')['status'] != 200 diff --git a/test/test_python_targets.py b/test/test_python_targets.py new file mode 100644 index 00000000..ca736c0d --- /dev/null +++ b/test/test_python_targets.py @@ -0,0 +1,51 @@ +import pytest + +from unit.applications.lang.python import TestApplicationPython +from unit.option import option + + +class TestPythonTargets(TestApplicationPython): + prerequisites = {'modules': {'python': 'all'}} + + def test_python_targets(self): + assert 'success' in self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"uri": "/1"}, + "action": {"pass": "applications/targets/1"}, + }, + { + "match": {"uri": "/2"}, + "action": {"pass": "applications/targets/2"}, + }, + ], + "applications": { + "targets": { + "type": "python", + "working_directory": option.test_dir + + "/python/targets/", + "path": option.test_dir + '/python/targets/', + "targets": { + "1": { + "module": "wsgi", + "callable": "wsgi_target_a", + }, + "2": { + "module": "wsgi", + "callable": "wsgi_target_b", + }, + }, + } + }, + } + ) + + resp = self.get(url='/1') + assert resp['status'] == 200 + assert resp['body'] == '1' + + resp = self.get(url='/2') + assert resp['status'] == 200 + assert resp['body'] == '2' diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index 287d23f0..b399dffd 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -42,8 +42,15 @@ class TestApplicationPython(TestApplicationProto): "module": module, } - for attr in ('callable', 'home', 'limits', 'path', 'protocol', - 'threads'): + for attr in ( + 'callable', + 'home', + 'limits', + 'path', + 'protocol', + 'targets', + 'threads', + ): if attr in kwargs: app[attr] = kwargs.pop(attr) -- cgit From 539551c89f4028ed70e5881400fc9151af3d12e8 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 21 May 2021 14:41:35 +0300 Subject: PHP: adopted "file_handle" to Zend API changes in 8.1.0-dev. This fixes building module with the development version of PHP after the change: https://github.com/php/php-src/commit/c732ab400af92c54eee47c487a56009f1d79dd5d --- src/nxt_php_sapi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 23c148c8..3fb3b0db 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -1101,10 +1101,20 @@ nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) nxt_memzero(&file_handle, sizeof(file_handle)); file_handle.type = ZEND_HANDLE_FILENAME; +#if (PHP_VERSION_ID >= 80100) + file_handle.filename = zend_string_init((char *) ctx->script_filename.start, + ctx->script_filename.length, 0); + file_handle.primary_script = 1; +#else file_handle.filename = (char *) ctx->script_filename.start; +#endif php_execute_script(&file_handle TSRMLS_CC); +#if (PHP_VERSION_ID >= 80100) + zend_destroy_file_handle(&file_handle); +#endif + /* Prevention of consuming possible unread request body. */ #if (PHP_VERSION_ID < 50600) read_post = sapi_module.read_post; -- cgit From d6439002371d0bc73183c9d3d28df4f62ce0b972 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 24 May 2021 04:33:42 +0100 Subject: Tests: minor fixes. --- test/test_routing.py | 90 ++++++++++-------------------------------------- test/test_share_mount.py | 12 +++---- 2 files changed, 25 insertions(+), 77 deletions(-) diff --git a/test/test_routing.py b/test/test_routing.py index 7392c1ab..eaa0a134 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1041,83 +1041,31 @@ class TestRouting(TestApplicationProto): } ) + def check_headers(hds): + hds = dict({"Host": "localhost", "Connection": "close"}, **hds) + assert ( + self.get(headers=hds)['status'] == 200 + ), 'headers array match' + + def check_headers_404(hds): + hds = dict({"Host": "localhost", "Connection": "close"}, **hds) + assert ( + self.get(headers=hds)['status'] == 404 + ), 'headers array no match' + assert self.get()['status'] == 404, 'match headers array' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header1": "foo123", - "Connection": "close", - } - )['status'] - == 200 - ), 'match headers array 2' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header2": "bar", - "Connection": "close", - } - )['status'] - == 200 - ), 'match headers array 3' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header3": "bar", - "Connection": "close", - } - )['status'] - == 200 - ), 'match headers array 4' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header1": "bar", - "Connection": "close", - } - )['status'] - == 404 - ), 'match headers array 5' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header1": "bar", - "x-header4": "foo", - "Connection": "close", - } - )['status'] - == 200 - ), 'match headers array 6' + check_headers({"x-header1": "foo123"}) + check_headers({"x-header2": "bar"}) + check_headers({"x-header3": "bar"}) + check_headers_404({"x-header1": "bar"}) + check_headers({"x-header1": "bar", "x-header4": "foo"}) assert 'success' in self.conf_delete( 'routes/0/match/headers/1' ), 'match headers array configure 2' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header2": "bar", - "Connection": "close", - } - )['status'] - == 404 - ), 'match headers array 7' - assert ( - self.get( - headers={ - "Host": "localhost", - "x-header3": "foo", - "Connection": "close", - } - )['status'] - == 200 - ), 'match headers array 8' + check_headers_404({"x-header2": "bar"}) + check_headers({"x-header3": "foo"}) def test_routes_match_arguments(self): self.route_match({"arguments": {"foo": "bar"}}) diff --git a/test/test_share_mount.py b/test/test_share_mount.py index f46e1279..f22fbe75 100644 --- a/test/test_share_mount.py +++ b/test/test_share_mount.py @@ -70,8 +70,8 @@ class TestShareMount(TestApplicationProto): skip_alert(r'opening.*failed') resp = self.get(url='/mount/') - resp['status'] == 200 - resp['body'] == 'mount' + assert resp['status'] == 200 + assert resp['body'] == 'mount' assert 'success' in self.conf( {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, @@ -86,8 +86,8 @@ class TestShareMount(TestApplicationProto): ), 'configure mount enable' resp = self.get(url='/mount/') - resp['status'] == 200 - resp['body'] == 'mount' + assert resp['status'] == 200 + assert resp['body'] == 'mount' def test_share_mount_two_blocks(self, temp_dir, skip_alert): skip_alert(r'opening.*failed') @@ -128,7 +128,7 @@ class TestShareMount(TestApplicationProto): 'routes/0/action', ), 'configure chroot mount default' - self.get(url='/mount/')['status'] == 200, 'chroot' + assert self.get(url='/mount/')['status'] == 200, 'chroot' assert 'success' in self.conf( { @@ -139,4 +139,4 @@ class TestShareMount(TestApplicationProto): 'routes/0/action', ), 'configure chroot mount disable' - self.get(url='/mount/')['status'] == 403, 'chroot mount' + assert self.get(url='/mount/')['status'] == 403, 'chroot mount' -- cgit From c160ea11e4ece4db52731ac8b83dd09ca2d1ef11 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Mon, 24 May 2021 09:01:42 +0000 Subject: Node.js: renamed "require_shim" to "loader". --- docs/changes.xml | 2 +- src/nodejs/unit-http/loader.js | 27 +++++++++++++++++++ src/nodejs/unit-http/loader.mjs | 18 +++++++++++++ src/nodejs/unit-http/require_shim.js | 27 ------------------- src/nodejs/unit-http/require_shim.mjs | 18 ------------- test/node/loader/es_modules_http/app.mjs | 6 +++++ test/node/loader/es_modules_http_indirect/app.js | 1 + .../loader/es_modules_http_indirect/module.mjs | 6 +++++ test/node/loader/es_modules_websocket/app.mjs | 30 ++++++++++++++++++++++ .../loader/es_modules_websocket_indirect/app.js | 1 + .../es_modules_websocket_indirect/module.mjs | 30 ++++++++++++++++++++++ test/node/loader/transitive_dependency/app.js | 1 + .../transitive_dependency/transitive_http.js | 8 ++++++ test/node/loader/unit_http/app.js | 4 +++ test/node/require_shim/es_modules_http/app.mjs | 6 ----- .../require_shim/es_modules_http_indirect/app.js | 1 - .../es_modules_http_indirect/module.mjs | 6 ----- .../node/require_shim/es_modules_websocket/app.mjs | 30 ---------------------- .../es_modules_websocket_indirect/app.js | 1 - .../es_modules_websocket_indirect/module.mjs | 30 ---------------------- .../node/require_shim/transitive_dependency/app.js | 1 - .../transitive_dependency/transitive_http.js | 8 ------ test/node/require_shim/unit_http/app.js | 4 --- test/test_node_application.py | 8 +++--- test/test_node_es_modules.py | 12 ++++----- test/unit/applications/lang/node.py | 6 ++--- 26 files changed, 146 insertions(+), 146 deletions(-) create mode 100644 src/nodejs/unit-http/loader.js create mode 100644 src/nodejs/unit-http/loader.mjs delete mode 100644 src/nodejs/unit-http/require_shim.js delete mode 100644 src/nodejs/unit-http/require_shim.mjs create mode 100644 test/node/loader/es_modules_http/app.mjs create mode 100644 test/node/loader/es_modules_http_indirect/app.js create mode 100644 test/node/loader/es_modules_http_indirect/module.mjs create mode 100644 test/node/loader/es_modules_websocket/app.mjs create mode 100644 test/node/loader/es_modules_websocket_indirect/app.js create mode 100644 test/node/loader/es_modules_websocket_indirect/module.mjs create mode 100644 test/node/loader/transitive_dependency/app.js create mode 100644 test/node/loader/transitive_dependency/transitive_http.js create mode 100644 test/node/loader/unit_http/app.js delete mode 100644 test/node/require_shim/es_modules_http/app.mjs delete mode 100644 test/node/require_shim/es_modules_http_indirect/app.js delete mode 100644 test/node/require_shim/es_modules_http_indirect/module.mjs delete mode 100644 test/node/require_shim/es_modules_websocket/app.mjs delete mode 100644 test/node/require_shim/es_modules_websocket_indirect/app.js delete mode 100644 test/node/require_shim/es_modules_websocket_indirect/module.mjs delete mode 100644 test/node/require_shim/transitive_dependency/app.js delete mode 100644 test/node/require_shim/transitive_dependency/transitive_http.js delete mode 100644 test/node/require_shim/unit_http/app.js diff --git a/docs/changes.xml b/docs/changes.xml index 528ab483..58181173 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -39,7 +39,7 @@ multiple "targets" in Python applications. -a shim for automatic overriding "http" and "websocket" modules in Node.js. +a loader for automatic overriding "http" and "websocket" modules in Node.js. diff --git a/src/nodejs/unit-http/loader.js b/src/nodejs/unit-http/loader.js new file mode 100644 index 00000000..e5aa3558 --- /dev/null +++ b/src/nodejs/unit-http/loader.js @@ -0,0 +1,27 @@ +// can only be ran as part of a --require param on the node process +if (module.parent && module.parent.id === "internal/preload") { + const { Module } = require("module") + + if (!Module.prototype.require.__unit_loader) { + const http = require("./http") + const websocket = require("./websocket") + + const original = Module.prototype.require; + + Module.prototype.require = function (id) { + switch(id) { + case "http": + case "unit-http": + return http + + case "websocket": + case "unit-http/websocket": + return websocket + } + + return original.apply(this, arguments); + } + + Module.prototype.require.__unit_loader = true; + } +} diff --git a/src/nodejs/unit-http/loader.mjs b/src/nodejs/unit-http/loader.mjs new file mode 100644 index 00000000..067d63d4 --- /dev/null +++ b/src/nodejs/unit-http/loader.mjs @@ -0,0 +1,18 @@ +// must be ran as part of a --loader or --experimental-loader param +export async function resolve(specifier, context, defaultResolver) { + switch (specifier) { + case "websocket": + return { + url: new URL("./websocket.js", import.meta.url).href, + format: "cjs" + } + + case "http": + return { + url: new URL("./http.js", import.meta.url).href, + format: "cjs" + } + } + + return defaultResolver(specifier, context, defaultResolver) +} diff --git a/src/nodejs/unit-http/require_shim.js b/src/nodejs/unit-http/require_shim.js deleted file mode 100644 index 2b307629..00000000 --- a/src/nodejs/unit-http/require_shim.js +++ /dev/null @@ -1,27 +0,0 @@ -// can only be ran as part of a --require param on the node process -if (module.parent && module.parent.id === "internal/preload") { - const { Module } = require("module") - - if (!Module.prototype.require.__unit_shim) { - const http = require("./http") - const websocket = require("./websocket") - - const original = Module.prototype.require; - - Module.prototype.require = function (id) { - switch(id) { - case "http": - case "unit-http": - return http - - case "websocket": - case "unit-http/websocket": - return websocket - } - - return original.apply(this, arguments); - } - - Module.prototype.require.__unit_shim = true; - } -} diff --git a/src/nodejs/unit-http/require_shim.mjs b/src/nodejs/unit-http/require_shim.mjs deleted file mode 100644 index 067d63d4..00000000 --- a/src/nodejs/unit-http/require_shim.mjs +++ /dev/null @@ -1,18 +0,0 @@ -// must be ran as part of a --loader or --experimental-loader param -export async function resolve(specifier, context, defaultResolver) { - switch (specifier) { - case "websocket": - return { - url: new URL("./websocket.js", import.meta.url).href, - format: "cjs" - } - - case "http": - return { - url: new URL("./http.js", import.meta.url).href, - format: "cjs" - } - } - - return defaultResolver(specifier, context, defaultResolver) -} diff --git a/test/node/loader/es_modules_http/app.mjs b/test/node/loader/es_modules_http/app.mjs new file mode 100644 index 00000000..c7bcfe49 --- /dev/null +++ b/test/node/loader/es_modules_http/app.mjs @@ -0,0 +1,6 @@ +import http from "http" + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/loader/es_modules_http_indirect/app.js b/test/node/loader/es_modules_http_indirect/app.js new file mode 100644 index 00000000..535befba --- /dev/null +++ b/test/node/loader/es_modules_http_indirect/app.js @@ -0,0 +1 @@ +import("./module.mjs") diff --git a/test/node/loader/es_modules_http_indirect/module.mjs b/test/node/loader/es_modules_http_indirect/module.mjs new file mode 100644 index 00000000..c7bcfe49 --- /dev/null +++ b/test/node/loader/es_modules_http_indirect/module.mjs @@ -0,0 +1,6 @@ +import http from "http" + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/loader/es_modules_websocket/app.mjs b/test/node/loader/es_modules_websocket/app.mjs new file mode 100644 index 00000000..a71ffa9d --- /dev/null +++ b/test/node/loader/es_modules_websocket/app.mjs @@ -0,0 +1,30 @@ +import http from "http" +import websocket from "websocket" + +let server = http.createServer(function() {}); +let webSocketServer = websocket.server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/loader/es_modules_websocket_indirect/app.js b/test/node/loader/es_modules_websocket_indirect/app.js new file mode 100644 index 00000000..535befba --- /dev/null +++ b/test/node/loader/es_modules_websocket_indirect/app.js @@ -0,0 +1 @@ +import("./module.mjs") diff --git a/test/node/loader/es_modules_websocket_indirect/module.mjs b/test/node/loader/es_modules_websocket_indirect/module.mjs new file mode 100644 index 00000000..a71ffa9d --- /dev/null +++ b/test/node/loader/es_modules_websocket_indirect/module.mjs @@ -0,0 +1,30 @@ +import http from "http" +import websocket from "websocket" + +let server = http.createServer(function() {}); +let webSocketServer = websocket.server; + +server.listen(7080, function() {}); + +var wsServer = new webSocketServer({ + maxReceivedMessageSize: 0x1000000000, + maxReceivedFrameSize: 0x1000000000, + fragmentOutgoingMessages: false, + fragmentationThreshold: 0x1000000000, + httpServer: server, +}); + +wsServer.on('request', function(request) { + var connection = request.accept(null); + + connection.on('message', function(message) { + if (message.type === 'utf8') { + connection.send(message.utf8Data); + } else if (message.type === 'binary') { + connection.send(message.binaryData); + } + + }); + + connection.on('close', function(r) {}); +}); diff --git a/test/node/loader/transitive_dependency/app.js b/test/node/loader/transitive_dependency/app.js new file mode 100644 index 00000000..aaca5216 --- /dev/null +++ b/test/node/loader/transitive_dependency/app.js @@ -0,0 +1 @@ +require("./transitive_http") diff --git a/test/node/loader/transitive_dependency/transitive_http.js b/test/node/loader/transitive_dependency/transitive_http.js new file mode 100644 index 00000000..f1eb98e5 --- /dev/null +++ b/test/node/loader/transitive_dependency/transitive_http.js @@ -0,0 +1,8 @@ +const http = require("http"); + +http.createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); + +module.exports = http; diff --git a/test/node/loader/unit_http/app.js b/test/node/loader/unit_http/app.js new file mode 100644 index 00000000..9172e44f --- /dev/null +++ b/test/node/loader/unit_http/app.js @@ -0,0 +1,4 @@ +require("unit-http").createServer(function (req, res) { + res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) + .end('Hello World\n'); +}).listen(7080); diff --git a/test/node/require_shim/es_modules_http/app.mjs b/test/node/require_shim/es_modules_http/app.mjs deleted file mode 100644 index c7bcfe49..00000000 --- a/test/node/require_shim/es_modules_http/app.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import http from "http" - -http.createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) - .end('Hello World\n'); -}).listen(7080); diff --git a/test/node/require_shim/es_modules_http_indirect/app.js b/test/node/require_shim/es_modules_http_indirect/app.js deleted file mode 100644 index 535befba..00000000 --- a/test/node/require_shim/es_modules_http_indirect/app.js +++ /dev/null @@ -1 +0,0 @@ -import("./module.mjs") diff --git a/test/node/require_shim/es_modules_http_indirect/module.mjs b/test/node/require_shim/es_modules_http_indirect/module.mjs deleted file mode 100644 index c7bcfe49..00000000 --- a/test/node/require_shim/es_modules_http_indirect/module.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import http from "http" - -http.createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) - .end('Hello World\n'); -}).listen(7080); diff --git a/test/node/require_shim/es_modules_websocket/app.mjs b/test/node/require_shim/es_modules_websocket/app.mjs deleted file mode 100644 index a71ffa9d..00000000 --- a/test/node/require_shim/es_modules_websocket/app.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import http from "http" -import websocket from "websocket" - -let server = http.createServer(function() {}); -let webSocketServer = websocket.server; - -server.listen(7080, function() {}); - -var wsServer = new webSocketServer({ - maxReceivedMessageSize: 0x1000000000, - maxReceivedFrameSize: 0x1000000000, - fragmentOutgoingMessages: false, - fragmentationThreshold: 0x1000000000, - httpServer: server, -}); - -wsServer.on('request', function(request) { - var connection = request.accept(null); - - connection.on('message', function(message) { - if (message.type === 'utf8') { - connection.send(message.utf8Data); - } else if (message.type === 'binary') { - connection.send(message.binaryData); - } - - }); - - connection.on('close', function(r) {}); -}); diff --git a/test/node/require_shim/es_modules_websocket_indirect/app.js b/test/node/require_shim/es_modules_websocket_indirect/app.js deleted file mode 100644 index 535befba..00000000 --- a/test/node/require_shim/es_modules_websocket_indirect/app.js +++ /dev/null @@ -1 +0,0 @@ -import("./module.mjs") diff --git a/test/node/require_shim/es_modules_websocket_indirect/module.mjs b/test/node/require_shim/es_modules_websocket_indirect/module.mjs deleted file mode 100644 index a71ffa9d..00000000 --- a/test/node/require_shim/es_modules_websocket_indirect/module.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import http from "http" -import websocket from "websocket" - -let server = http.createServer(function() {}); -let webSocketServer = websocket.server; - -server.listen(7080, function() {}); - -var wsServer = new webSocketServer({ - maxReceivedMessageSize: 0x1000000000, - maxReceivedFrameSize: 0x1000000000, - fragmentOutgoingMessages: false, - fragmentationThreshold: 0x1000000000, - httpServer: server, -}); - -wsServer.on('request', function(request) { - var connection = request.accept(null); - - connection.on('message', function(message) { - if (message.type === 'utf8') { - connection.send(message.utf8Data); - } else if (message.type === 'binary') { - connection.send(message.binaryData); - } - - }); - - connection.on('close', function(r) {}); -}); diff --git a/test/node/require_shim/transitive_dependency/app.js b/test/node/require_shim/transitive_dependency/app.js deleted file mode 100644 index aaca5216..00000000 --- a/test/node/require_shim/transitive_dependency/app.js +++ /dev/null @@ -1 +0,0 @@ -require("./transitive_http") diff --git a/test/node/require_shim/transitive_dependency/transitive_http.js b/test/node/require_shim/transitive_dependency/transitive_http.js deleted file mode 100644 index f1eb98e5..00000000 --- a/test/node/require_shim/transitive_dependency/transitive_http.js +++ /dev/null @@ -1,8 +0,0 @@ -const http = require("http"); - -http.createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) - .end('Hello World\n'); -}).listen(7080); - -module.exports = http; diff --git a/test/node/require_shim/unit_http/app.js b/test/node/require_shim/unit_http/app.js deleted file mode 100644 index 9172e44f..00000000 --- a/test/node/require_shim/unit_http/app.js +++ /dev/null @@ -1,4 +0,0 @@ -require("unit-http").createServer(function (req, res) { - res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'}) - .end('Hello World\n'); -}).listen(7080); diff --git a/test/test_node_application.py b/test/test_node_application.py index 59601ff0..48ed8d3d 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -19,13 +19,13 @@ class TestNodeApplication(TestApplicationNode): self.assert_basic_application() - def test_node_application_require_shim_unit_http(self): - self.load('require_shim/unit_http') + def test_node_application_loader_unit_http(self): + self.load('loader/unit_http') self.assert_basic_application() - def test_node_application_require_shim_transitive_dependency(self): - self.load('require_shim/transitive_dependency') + def test_node_application_loader_transitive_dependency(self): + self.load('loader/transitive_dependency') self.assert_basic_application() diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py index ce27e474..0945a967 100644 --- a/test/test_node_es_modules.py +++ b/test/test_node_es_modules.py @@ -20,18 +20,18 @@ class TestNodeESModules(TestApplicationNode): assert resp['headers']['Content-Type'] == 'text/plain', 'basic header' assert resp['body'] == 'Hello World\n', 'basic body' - def test_node_es_modules_require_shim_http(self): - self.load('require_shim/es_modules_http', name="app.mjs") + def test_node_es_modules_loader_http(self): + self.load('loader/es_modules_http', name="app.mjs") self.assert_basic_application() - def test_node_es_modules_require_shim_http_indirect(self): - self.load('require_shim/es_modules_http_indirect', name="app.js") + def test_node_es_modules_loader_http_indirect(self): + self.load('loader/es_modules_http_indirect', name="app.js") self.assert_basic_application() - def test_node_es_modules_require_shim_websockets(self): - self.load('require_shim/es_modules_websocket', name="app.mjs") + def test_node_es_modules_loader_websockets(self): + self.load('loader/es_modules_websocket', name="app.mjs") message = 'blah' diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index 3254f3d4..5d05c70c 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -31,14 +31,14 @@ class TestApplicationNode(TestApplicationProto): arguments = [ "node", "--loader", - "unit-http/require_shim.mjs", + "unit-http/loader.mjs", "--require", - "unit-http/require_shim", + "unit-http/loader", name, ] else: - arguments = ["node", "--require", "unit-http/require_shim", name] + arguments = ["node", "--require", "unit-http/loader", name] self._load_conf( { -- cgit From 1154ede862824331d24591c717c270e779a2b08c Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 24 May 2021 05:26:15 +0100 Subject: Tests: test_settings_send_timeout improved. Data length adjusts depending on socket buffer size when it's possible. --- test/python/body_generate/wsgi.py | 6 ++++++ test/test_settings.py | 25 +++++++++++++++++-------- test/unit/utils.py | 11 +++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 test/python/body_generate/wsgi.py diff --git a/test/python/body_generate/wsgi.py b/test/python/body_generate/wsgi.py new file mode 100644 index 00000000..73462be6 --- /dev/null +++ b/test/python/body_generate/wsgi.py @@ -0,0 +1,6 @@ +def application(env, start_response): + length = env.get('HTTP_X_LENGTH', '10') + bytes = b'X' * int(length) + + start_response('200', [('Content-Length', length)]) + return [bytes] diff --git a/test/test_settings.py b/test/test_settings.py index d129dec9..ef4136e3 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -5,6 +5,7 @@ import time import pytest from unit.applications.lang.python import TestApplicationPython +from unit.utils import sysctl class TestSettings(TestApplicationPython): @@ -147,27 +148,35 @@ Connection: close assert resp['status'] == 200, 'status body read timeout update' def test_settings_send_timeout(self, temp_dir): - self.load('mirror') + self.load('body_generate') + + sysctl_out = sysctl() + values = re.findall( + r'net.core.[rw]mem_(?:max|default).*?(\d+)', sysctl_out + ) + values = [int(v) for v in values] - data_len = 1048576 + data_len = 1048576 if len(values) == 0 else 10 * max(values) self.conf({'http': {'send_timeout': 1}}, 'settings') addr = temp_dir + '/sock' - self.conf({"unix:" + addr: {'application': 'mirror'}}, 'listeners') + self.conf( + {"unix:" + addr: {'application': 'body_generate'}}, 'listeners' + ) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(addr) - req = """POST / HTTP/1.1 + req = ( + """GET / HTTP/1.1 Host: localhost -Content-Type: text/html -Content-Length: %d +X-Length: %d Connection: close -""" % data_len + ( - 'X' * data_len +""" + % data_len ) sock.sendall(req.encode()) diff --git a/test/unit/utils.py b/test/unit/utils.py index e80fc469..a627e9f5 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -61,6 +61,17 @@ def findmnt(): return out +def sysctl(): + try: + out = subprocess.check_output( + ['sysctl', '-a'], stderr=subprocess.STDOUT + ).decode() + except FileNotFoundError: + pytest.skip('requires sysctl') + + return out + + def waitformount(template, wait=50): for i in range(wait): if findmnt().find(template) != -1: -- cgit From 39c0fda24c23b7b5358f473349d04752a70ceecc Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 24 May 2021 22:28:23 +0100 Subject: Tests: added additional check in tests with timeouts. --- test/test_settings.py | 195 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 76 deletions(-) diff --git a/test/test_settings.py b/test/test_settings.py index ef4136e3..49041b62 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -14,33 +14,42 @@ class TestSettings(TestApplicationPython): def test_settings_header_read_timeout(self): self.load('empty') - self.conf({'http': {'header_read_timeout': 2}}, 'settings') - - (resp, sock) = self.http( - b"""GET / HTTP/1.1 + def req(): + (resp, sock) = self.http( + b"""GET / HTTP/1.1 """, - start=True, - read_timeout=1, - raw=True, - ) + start=True, + read_timeout=1, + raw=True, + ) - time.sleep(3) + time.sleep(3) - resp = self.http( - b"""Host: localhost + return self.http( + b"""Host: localhost Connection: close -""", - sock=sock, - raw=True, + """, + sock=sock, + raw=True, + ) + + assert 'success' in self.conf( + {'http': {'header_read_timeout': 2}}, 'settings' ) + assert req()['status'] == 408, 'status header read timeout' - assert resp['status'] == 408, 'status header read timeout' + assert 'success' in self.conf( + {'http': {'header_read_timeout': 7}}, 'settings' + ) + assert req()['status'] == 200, 'status header read timeout 2' def test_settings_header_read_timeout_update(self): self.load('empty') - self.conf({'http': {'header_read_timeout': 4}}, 'settings') + assert 'success' in self.conf( + {'http': {'header_read_timeout': 4}}, 'settings' + ) (resp, sock) = self.http( b"""GET / HTTP/1.1 @@ -91,31 +100,40 @@ Connection: close def test_settings_body_read_timeout(self): self.load('empty') - self.conf({'http': {'body_read_timeout': 2}}, 'settings') - - (resp, sock) = self.http( - b"""POST / HTTP/1.1 + def req(): + (resp, sock) = self.http( + b"""POST / HTTP/1.1 Host: localhost Content-Length: 10 Connection: close """, - start=True, - raw_resp=True, - read_timeout=1, - raw=True, - ) + start=True, + raw_resp=True, + read_timeout=1, + raw=True, + ) - time.sleep(3) + time.sleep(3) - resp = self.http(b"""0123456789""", sock=sock, raw=True) + return self.http(b"""0123456789""", sock=sock, raw=True) - assert resp['status'] == 408, 'status body read timeout' + assert 'success' in self.conf( + {'http': {'body_read_timeout': 2}}, 'settings' + ) + assert req()['status'] == 408, 'status body read timeout' + + assert 'success' in self.conf( + {'http': {'body_read_timeout': 7}}, 'settings' + ) + assert req()['status'] == 200, 'status body read timeout 2' def test_settings_body_read_timeout_update(self): self.load('empty') - self.conf({'http': {'body_read_timeout': 4}}, 'settings') + assert 'success' in self.conf( + {'http': {'body_read_timeout': 4}}, 'settings' + ) (resp, sock) = self.http( b"""POST / HTTP/1.1 @@ -150,6 +168,32 @@ Connection: close def test_settings_send_timeout(self, temp_dir): self.load('body_generate') + def req(addr, data_len): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(addr) + + req = ( + """GET / HTTP/1.1 +Host: localhost +X-Length: %d +Connection: close + +""" + % data_len + ) + + sock.sendall(req.encode()) + + data = sock.recv(16).decode() + + time.sleep(3) + + data += self.recvall(sock).decode() + + sock.close() + + return data + sysctl_out = sysctl() values = re.findall( r'net.core.[rw]mem_(?:max|default).*?(\d+)', sysctl_out @@ -158,83 +202,80 @@ Connection: close data_len = 1048576 if len(values) == 0 else 10 * max(values) - self.conf({'http': {'send_timeout': 1}}, 'settings') - addr = temp_dir + '/sock' - self.conf( + assert 'success' in self.conf( {"unix:" + addr: {'application': 'body_generate'}}, 'listeners' ) - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(addr) - - req = ( - """GET / HTTP/1.1 -Host: localhost -X-Length: %d -Connection: close - -""" - % data_len + assert 'success' in self.conf( + {'http': {'send_timeout': 1}}, 'settings' ) - sock.sendall(req.encode()) - - data = sock.recv(16).decode() - - time.sleep(3) + data = req(addr, data_len) + assert re.search(r'200 OK', data), 'send timeout status' + assert len(data) < data_len, 'send timeout data ' - data += self.recvall(sock).decode() + self.conf({'http': {'send_timeout': 7}}, 'settings') - sock.close() - - assert re.search(r'200 OK', data), 'status send timeout' - assert len(data) < data_len, 'data send timeout' + data = req(addr, data_len) + assert re.search(r'200 OK', data), 'send timeout status 2' + assert len(data) > data_len, 'send timeout data 2' def test_settings_idle_timeout(self): self.load('empty') - assert self.get()['status'] == 200, 'init' + def req(): + (resp, sock) = self.get( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) - self.conf({'http': {'idle_timeout': 2}}, 'settings') + time.sleep(3) - (resp, sock) = self.get( - headers={'Host': 'localhost', 'Connection': 'keep-alive'}, - start=True, - read_timeout=1, - ) + return self.get(sock=sock) - time.sleep(3) + assert self.get()['status'] == 200, 'init' - resp = self.get( - headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock + assert 'success' in self.conf( + {'http': {'idle_timeout': 2}}, 'settings' ) + assert req()['status'] == 408, 'status idle timeout' - assert resp['status'] == 408, 'status idle timeout' + assert 'success' in self.conf( + {'http': {'idle_timeout': 7}}, 'settings' + ) + assert req()['status'] == 200, 'status idle timeout 2' def test_settings_idle_timeout_2(self): self.load('empty') - assert self.get()['status'] == 200, 'init' + def req(): + _, sock = self.http(b'', start=True, raw=True, no_recv=True) - self.conf({'http': {'idle_timeout': 1}}, 'settings') + time.sleep(3) - _, sock = self.http(b'', start=True, raw=True, no_recv=True) + return self.get(sock=sock) - time.sleep(3) + assert self.get()['status'] == 200, 'init' - assert ( - self.get( - headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock - )['status'] - == 408 - ), 'status idle timeout' + assert 'success' in self.conf( + {'http': {'idle_timeout': 1}}, 'settings' + ) + assert req()['status'] == 408, 'status idle timeout' + + assert 'success' in self.conf( + {'http': {'idle_timeout': 7}}, 'settings' + ) + assert req()['status'] == 200, 'status idle timeout 2' def test_settings_max_body_size(self): self.load('empty') - self.conf({'http': {'max_body_size': 5}}, 'settings') + assert 'success' in self.conf( + {'http': {'max_body_size': 5}}, 'settings' + ) assert self.post(body='01234')['status'] == 200, 'status size' assert self.post(body='012345')['status'] == 413, 'status size max' @@ -242,7 +283,9 @@ Connection: close def test_settings_max_body_size_large(self): self.load('mirror') - self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings') + assert 'success' in self.conf( + {'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings' + ) body = '0123456789abcdef' * 4 * 64 * 1024 resp = self.post(body=body, read_buffer_size=1024 * 1024) -- cgit From 2fe76afaa69a0a992fd376c03c345f1ceb04b80e Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Tue, 25 May 2021 13:21:29 +0000 Subject: Configuration: generalized application "targets" validation. --- src/nxt_conf_validation.c | 178 ++++++++++++++++++---------------------------- 1 file changed, 68 insertions(+), 110 deletions(-) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index f8381cde..25f6f4fe 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -102,12 +102,6 @@ static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_python_targets_exclusive( - nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt, - nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt, @@ -164,16 +158,16 @@ static nxt_int_t nxt_conf_vldt_array_iterator(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_targets_exclusive( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_targets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_target(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_targets_exclusive( - nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, - nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, @@ -210,8 +204,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[]; #endif static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_php_target_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_processes_members[]; @@ -555,17 +551,18 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { { .name = nxt_string("module"), .type = NXT_CONF_VLDT_STRING, - .validator = nxt_conf_vldt_python_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "module", }, { .name = nxt_string("callable"), .type = NXT_CONF_VLDT_STRING, - .validator = nxt_conf_vldt_python_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "callable", }, { .name = nxt_string("targets"), .type = NXT_CONF_VLDT_OBJECT, - .validator = nxt_conf_vldt_python_targets, + .validator = nxt_conf_vldt_targets, + .u.members = nxt_conf_vldt_python_target_members }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members) @@ -604,22 +601,23 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { { .name = nxt_string("root"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "root", }, { .name = nxt_string("script"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "script", }, { .name = nxt_string("index"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "index", }, { .name = nxt_string("targets"), .type = NXT_CONF_VLDT_OBJECT, - .validator = nxt_conf_vldt_php_targets, + .validator = nxt_conf_vldt_targets, + .u.members = nxt_conf_vldt_php_target_members }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_php_common_members) @@ -1487,52 +1485,6 @@ nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, } -static nxt_int_t -nxt_conf_vldt_python_targets_exclusive(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data) -{ - return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " - "with the \"targets\" object.", data); -} - - -static nxt_int_t -nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data) -{ - nxt_uint_t n; - - n = nxt_conf_object_members_count(value); - - if (n > 254) { - return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " - "contain more than 254 members."); - } - - return nxt_conf_vldt_object_iterator(vldt, value, - &nxt_conf_vldt_python_target); -} - - -static nxt_int_t -nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt, nxt_str_t *name, - nxt_conf_value_t *value) -{ - if (name->length == 0) { - return nxt_conf_vldt_error(vldt, - "The Python target name must not be empty."); - } - - if (nxt_conf_type(value) != NXT_CONF_OBJECT) { - return nxt_conf_vldt_error(vldt, "The \"%V\" Python target must be " - "an object.", name); - } - - return nxt_conf_vldt_object(vldt, value, - &nxt_conf_vldt_python_target_members); -} - - static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) @@ -2399,6 +2351,57 @@ nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name, } +static nxt_int_t +nxt_conf_vldt_targets_exclusive(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " + "with the \"targets\" object.", data); +} + + +static nxt_int_t +nxt_conf_vldt_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_int_t ret; + nxt_uint_t n; + + n = nxt_conf_object_members_count(value); + + if (n > 254) { + return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " + "contain more than 254 members."); + } + + vldt->ctx = data; + + ret = nxt_conf_vldt_object_iterator(vldt, value, &nxt_conf_vldt_target); + + vldt->ctx = NULL; + + return ret; +} + + +static nxt_int_t +nxt_conf_vldt_target(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, + "The target name must not be empty."); + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"%V\" target must be " + "an object.", name); + } + + return nxt_conf_vldt_object(vldt, value, vldt->ctx); +} + + static nxt_int_t nxt_conf_vldt_clone_namespaces(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) @@ -2566,51 +2569,6 @@ nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, } -static nxt_int_t -nxt_conf_vldt_php_targets_exclusive(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data) -{ - return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " - "with the \"targets\" object.", data); -} - - -static nxt_int_t -nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, - void *data) -{ - nxt_uint_t n; - - n = nxt_conf_object_members_count(value); - - if (n > 254) { - return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " - "contain more than 254 members."); - } - - return nxt_conf_vldt_object_iterator(vldt, value, - &nxt_conf_vldt_php_target); -} - - -static nxt_int_t -nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, nxt_str_t *name, - nxt_conf_value_t *value) -{ - if (name->length == 0) { - return nxt_conf_vldt_error(vldt, - "The PHP target name must not be empty."); - } - - if (nxt_conf_type(value) != NXT_CONF_OBJECT) { - return nxt_conf_vldt_error(vldt, "The \"%V\" PHP target must be " - "an object.", name); - } - - return nxt_conf_vldt_object(vldt, value, &nxt_conf_vldt_php_target_members); -} - - static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value) -- cgit From 155e22da05f01eb51b9dc082e9c8e8bff9b5ec8d Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 25 May 2021 18:00:59 +0300 Subject: Go: fixing tests for Go 1.16. In Go 1.16, the module-aware mode is enabled by default; to fall back to previous behavior, the GO111MODULE environment variable should be set to 'auto'. Details: https://golang.org/doc/go1.16 --- auto/modules/go | 2 +- test/unit/applications/lang/go.py | 1 + test/unit/check/go.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/auto/modules/go b/auto/modules/go index 8bb9216e..7324ffbe 100644 --- a/auto/modules/go +++ b/auto/modules/go @@ -111,7 +111,7 @@ install: ${NXT_GO}-install ${NXT_GO}: ${NXT_GO}-install: ${NXT_GO}-install-src ${NXT_GO}-install-env - GOPATH=\$(DESTDIR)\$(GOPATH) ${NXT_GO} build ${NXT_GO_PKG} + GOPATH=\$(DESTDIR)\$(GOPATH) GO111MODULE=auto ${NXT_GO} build ${NXT_GO_PKG} ${NXT_GO}-install-src: install -d \$(DESTDIR)\$(NXT_GO_DST)/src/${NXT_GO_PKG} diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index a17b1af4..6be1667b 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -13,6 +13,7 @@ class TestApplicationGo(TestApplicationProto): env = os.environ.copy() env['GOPATH'] = option.current_dir + '/build/go' env['GOCACHE'] = option.cache_dir + '/go' + env['GO111MODULE'] = 'auto' if static: args = [ diff --git a/test/unit/check/go.py b/test/unit/check/go.py index 35b0c2d5..309091c0 100644 --- a/test/unit/check/go.py +++ b/test/unit/check/go.py @@ -8,6 +8,7 @@ def check_go(current_dir, temp_dir, test_dir): env = os.environ.copy() env['GOPATH'] = current_dir + '/build/go' + env['GO111MODULE'] = 'auto' try: process = subprocess.Popen( -- cgit From 24905c1a00dbf8f62c902d1b248279c5a31cf199 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 25 May 2021 18:01:00 +0300 Subject: Fixing racing condition on listen socket close in router (v2). This patch fixes a possible race between the nxt_router_conf_wait() and nxt_router_listen_socket_release() function calls and improves the 7f1b2eaa2d58 commit fix. --- src/nxt_router.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index 2bbe87b8..c597863e 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -3285,17 +3285,17 @@ nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data) joint = lev->socket.data; lev->socket.data = NULL; + /* 'task' refers to lev->task and we cannot use after nxt_free() */ + task = &task->thread->engine->task; + + nxt_router_listen_socket_release(task, joint->socket_conf); + job = joint->close_job; job->work.next = NULL; job->work.handler = nxt_router_conf_wait; nxt_event_engine_post(job->tmcf->engine, &job->work); - /* 'task' refers to lev->task and we cannot use after nxt_free() */ - task = &task->thread->engine->task; - - nxt_router_listen_socket_release(task, joint->socket_conf); - nxt_router_listen_event_release(task, lev, joint); } -- cgit From 81e31872e3d05c912551dbf1382e3c78f4f65f4b Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Wed, 26 May 2021 16:48:05 +0000 Subject: MIME: added PHP. --- docs/changes.xml | 6 ++++++ src/nxt_http_static.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/changes.xml b/docs/changes.xml index 58181173..5717f1bd 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,12 @@ NGINX Unit updated to 1.24.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> + + +PHP added to the default MIME type list. + + + multiple "targets" in Python applications. diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index c961bb97..08f451b6 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -643,6 +643,8 @@ nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) { nxt_string("application/octet-stream"), ".deb" }, { nxt_string("application/octet-stream"), ".rpm" }, + + { nxt_string("application/x-httpd-php"), ".php" }, }; for (i = 0; i < nxt_nitems(default_types); i++) { -- cgit From d67a0c871157454d591fa1d2a8b2d831b32e4040 Mon Sep 17 00:00:00 2001 From: Oisin Canty Date: Wed, 26 May 2021 16:48:11 +0000 Subject: Static: handled unknown MIME types when MIME-filtering active. --- src/nxt_conf_validation.c | 2 +- src/nxt_http_static.c | 32 ++++++++++++++------------------ test/test_share_types.py | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 25f6f4fe..14066fb0 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -1220,7 +1220,7 @@ nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, dup_type = nxt_http_static_mtypes_hash_find(&ctx->hash, &ext); - if (dup_type != NULL) { + if (dup_type->length != 0) { return nxt_conf_vldt_error(vldt, "The \"%V\" file extension has been " "declared for \"%V\" and \"%V\" " "MIME types at the same time.", diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index 08f451b6..c8b73fac 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -84,28 +84,22 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, &extension); - if (mtype != NULL) { - ret = nxt_http_route_test_rule(r, action->u.share.types, - mtype->start, mtype->length); - if (ret == 1) { - goto mime_ok; - } + ret = nxt_http_route_test_rule(r, action->u.share.types, + mtype->start, mtype->length); + if (nxt_slow_path(ret == NXT_ERROR)) { + goto fail; + } - if (nxt_slow_path(ret == NXT_ERROR)) { - goto fail; + if (ret == 0) { + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; } - } - if (action->u.share.fallback != NULL) { - return action->u.share.fallback; + nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN); + return NULL; } - - nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN); - return NULL; } -mime_ok: - length = action->name.length + r->path->length + index.length; fname = nxt_mp_nget(r->mem_pool, length + 1); @@ -298,7 +292,7 @@ mime_ok: &extension); } - if (mtype != NULL) { + if (mtype->length != 0) { field = nxt_list_zero_add(r->resp.fields); if (nxt_slow_path(field == NULL)) { goto fail; @@ -711,6 +705,8 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) nxt_lvlhsh_query_t lhq; nxt_http_static_mtype_t *mtype; + static nxt_str_t empty = nxt_string(""); + lhq.key = *extension; lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); lhq.proto = &nxt_http_static_mtypes_hash_proto; @@ -720,7 +716,7 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) return mtype->type; } - return NULL; + return ∅ } diff --git a/test/test_share_types.py b/test/test_share_types.py index 98ad106b..b5ed97a0 100644 --- a/test/test_share_types.py +++ b/test/test_share_types.py @@ -128,7 +128,7 @@ class TestShareTypes(TestApplicationProto): { "action": { "share": temp_dir + "/assets", - "types": ["!application/php"], + "types": ["!application/x-httpd-php"], "fallback": {"proxy": "http://127.0.0.1:7081"}, } }, -- cgit From 3efffddd95e564fe10f59e1de45afc2b551a5cba Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Wed, 26 May 2021 11:11:58 -0700 Subject: Fixing crash during TLS connection shutdown. A crash was caused by an incorrect timer handler nxt_h1p_idle_timeout() if SSL_shutdown() returned SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE. The flag SSL_RECEIVED_SHUTDOWN is used to avoid getting SSL_ERROR_WANT_READ, so the server won't wait for a close notification from a client. For SSL_ERROR_WANT_WRITE, a correct timer handler is set up. --- docs/changes.xml | 6 ++++++ src/nxt_h1proto.c | 0 src/nxt_openssl.c | 32 ++++++++++++++++++++++++++------ src/nxt_router.c | 1 + src/nxt_tls.h | 2 ++ 5 files changed, 35 insertions(+), 6 deletions(-) mode change 100644 => 100755 src/nxt_h1proto.c diff --git a/docs/changes.xml b/docs/changes.xml index 5717f1bd..cbe6269a 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -68,6 +68,12 @@ compatibility with Ruby 3.0. + + +the router process could crash while closing TLS connection. + + + a segmentation fault might have occurred in the PHP module if diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c old mode 100644 new mode 100755 diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 9b86150f..af4dc24a 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -66,6 +66,8 @@ static void nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data); static nxt_int_t nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, nxt_err_t sys_err, nxt_openssl_io_t io); +static void nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj, + void *data); static void nxt_cdecl nxt_openssl_conn_error(nxt_task_t *task, nxt_err_t err, const char *fmt, ...); static nxt_uint_t nxt_openssl_log_error_level(nxt_err_t err); @@ -839,11 +841,7 @@ nxt_openssl_conn_init(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_conn_t *c) c->sendfile = NXT_CONN_SENDFILE_OFF; nxt_openssl_conn_handshake(task, c, c->socket.data); - /* - * TLS configuration might be destroyed after the TLS connection - * is established. - */ - tls->conf = NULL; + return; fail: @@ -1099,6 +1097,10 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data) SSL_set_quiet_shutdown(s, quiet); + if (tls->conf->no_wait_shutdown) { + mode |= SSL_RECEIVED_SHUTDOWN; + } + once = 1; for ( ;; ) { @@ -1153,7 +1155,8 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data) break; case NXT_AGAIN: - nxt_timer_add(task->thread->engine, &c->read_timer, 5000); + c->write_timer.handler = nxt_openssl_conn_io_shutdown_timeout; + nxt_timer_add(task->thread->engine, &c->write_timer, 5000); return; default: @@ -1237,6 +1240,23 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, } +static void +nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj, void *data) +{ + nxt_conn_t *c; + nxt_timer_t *timer; + + timer = obj; + + nxt_debug(task, "openssl conn shutdown timeout"); + + c = nxt_write_timer_conn(timer); + + c->socket.timedout = 1; + nxt_openssl_conn_io_shutdown(task, c, NULL); +} + + static void nxt_cdecl nxt_openssl_conn_error(nxt_task_t *task, nxt_err_t err, const char *fmt, ...) { diff --git a/src/nxt_router.c b/src/nxt_router.c index c597863e..122fd523 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -2477,6 +2477,7 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, goto fail; } + tlscf->no_wait_shutdown = 1; rpc->socket_conf->tls = tlscf; } else { diff --git a/src/nxt_tls.h b/src/nxt_tls.h index c44bfe56..2a29f3ca 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -69,6 +69,8 @@ struct nxt_tls_conf_s { char *ca_certificate; size_t buffer_size; + + uint8_t no_wait_shutdown; /* 1 bit */ }; -- cgit From 3f7ccf142ff4d1a11b807a344bcb1e3cb6c3284b Mon Sep 17 00:00:00 2001 From: Andrey Suvorov Date: Wed, 26 May 2021 11:19:47 -0700 Subject: Enabling SSL_CTX configuration by using SSL_CONF_cmd(). To perform various configuration operations on SSL_CTX, OpenSSL provides SSL_CONF_cmd(). Specifically, to configure ciphers for a listener, "CipherString" and "Ciphersuites" file commands are used: https://www.openssl.org/docs/man1.1.1/man3/SSL_CONF_cmd.html This feature can be configured in the "tls/conf_commands" section. --- auto/ssltls | 14 ++++++ docs/changes.xml | 6 +++ src/nxt_conf_validation.c | 46 +++++++++++++++++++ src/nxt_openssl.c | 114 ++++++++++++++++++++++++++++++++++++++++++---- src/nxt_router.c | 90 ++++++++++++++++-------------------- src/nxt_tls.h | 6 ++- 6 files changed, 215 insertions(+), 61 deletions(-) diff --git a/auto/ssltls b/auto/ssltls index f034b758..f9363dde 100644 --- a/auto/ssltls +++ b/auto/ssltls @@ -52,6 +52,20 @@ if [ $NXT_OPENSSL = YES ]; then $echo exit 1; fi + + + nxt_feature="OpenSSL SSL_CONF_cmd()" + nxt_feature_name=NXT_HAVE_OPENSSL_CONF_CMD + nxt_feature_run= + nxt_feature_incs= + nxt_feature_libs="$NXT_OPENSSL_LIBS" + nxt_feature_test="#include + + int main() { + SSL_CONF_cmd(NULL, NULL, NULL); + return 0; + }" + . auto/feature fi diff --git a/docs/changes.xml b/docs/changes.xml index cbe6269a..2dcaf4dd 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -37,6 +37,12 @@ PHP added to the default MIME type list. + + +arbitrary configuration of TLS connections via OpenSSL commands. + + + multiple "targets" in Python applications. diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 14066fb0..06ae2847 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -89,6 +89,10 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, #if (NXT_TLS) static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +#if (NXT_HAVE_OPENSSL_CONF_CMD) +static nxt_int_t nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +#endif static nxt_int_t nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); #endif @@ -363,7 +367,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { { .name = nxt_string("certificate"), .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + .flags = NXT_CONF_VLDT_REQUIRED, .validator = nxt_conf_vldt_certificate, + }, { + .name = nxt_string("conf_commands"), + .type = NXT_CONF_VLDT_OBJECT, +#if (NXT_HAVE_OPENSSL_CONF_CMD) + .validator = nxt_conf_vldt_object_conf_commands, +#else + .validator = nxt_conf_vldt_unsupported, + .u.string = "conf_commands", +#endif }, NXT_CONF_VLDT_END @@ -1971,6 +1985,38 @@ nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt, return NXT_OK; } + +#if (NXT_HAVE_OPENSSL_CONF_CMD) + +static nxt_int_t +nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + uint32_t index; + nxt_int_t ret; + nxt_str_t name; + nxt_conf_value_t *member; + + index = 0; + + for ( ;; ) { + member = nxt_conf_next_object_member(value, &name, &index); + + if (member == NULL) { + break; + } + + ret = nxt_conf_vldt_type(vldt, &name, member, NXT_CONF_VLDT_STRING); + if (ret != NXT_OK) { + return ret; + } + } + + return NXT_OK; +} + +#endif + #endif diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index af4dc24a..2fd5d1bf 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -42,9 +43,14 @@ static unsigned long nxt_openssl_thread_id(void); static void nxt_openssl_locks_free(void); #endif static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, - nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t last); + nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, + nxt_bool_t last); static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t single); +#if (NXT_HAVE_OPENSSL_CONF_CMD) +static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, + nxt_conf_value_t *value, nxt_mp_t *mp); +#endif static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp); static nxt_int_t nxt_openssl_bundle_hash_test(nxt_lvlhsh_query_t *lhq, @@ -260,7 +266,7 @@ nxt_openssl_locks_free(void) static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, - nxt_mp_t *mp, nxt_bool_t last) + nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, nxt_bool_t last) { SSL_CTX *ctx; const char *ciphers, *ca_certificate; @@ -320,6 +326,7 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, goto fail; } */ + ciphers = (conf->ciphers != NULL) ? conf->ciphers : "HIGH:!aNULL:!MD5"; if (SSL_CTX_set_cipher_list(ctx, ciphers) == 0) { @@ -329,6 +336,14 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, goto fail; } +#if (NXT_HAVE_OPENSSL_CONF_CMD) + if (conf_cmds != NULL + && nxt_ssl_conf_commands(task, ctx, conf_cmds, mp) != NXT_OK) + { + goto fail; + } +#endif + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); if (conf->ca_certificate != NULL) { @@ -484,6 +499,89 @@ clean: } +#if (NXT_HAVE_OPENSSL_CONF_CMD) + +static nxt_int_t +nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, nxt_conf_value_t *conf, + nxt_mp_t *mp) +{ + int ret; + char *zcmd, *zvalue; + uint32_t index; + nxt_str_t cmd, value; + SSL_CONF_CTX *cctx; + nxt_conf_value_t *member; + + cctx = SSL_CONF_CTX_new(); + if (nxt_slow_path(cctx == NULL)) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "SSL_CONF_CTX_new() failed"); + return NXT_ERROR; + } + + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE + | SSL_CONF_FLAG_SERVER + | SSL_CONF_FLAG_CERTIFICATE + | SSL_CONF_FLAG_SHOW_ERRORS); + + SSL_CONF_CTX_set_ssl_ctx(cctx, ctx); + + index = 0; + + for ( ;; ) { + member = nxt_conf_next_object_member(conf, &cmd, &index); + if (nxt_slow_path(member == NULL)) { + break; + } + + nxt_conf_get_string(member, &value); + + zcmd = nxt_str_cstrz(mp, &cmd); + zvalue = nxt_str_cstrz(mp, &value); + + if (nxt_slow_path(zcmd == NULL || zvalue == NULL)) { + goto fail; + } + + ret = SSL_CONF_cmd(cctx, zcmd, zvalue); + if (ret == -2) { + nxt_openssl_log_error(task, NXT_LOG_ERR, + "unknown command \"%s\" in " + "\"conf_commands\" option", zcmd); + goto fail; + } + + if (ret <= 0) { + nxt_openssl_log_error(task, NXT_LOG_ERR, + "invalid value \"%s\" for command \"%s\" " + "in \"conf_commands\" option", + zvalue, zcmd); + goto fail; + } + + nxt_debug(task, "SSL_CONF_cmd(\"%s\", \"%s\")", zcmd, zvalue); + } + + if (SSL_CONF_CTX_finish(cctx) != 1) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "SSL_CONF_finish() failed"); + goto fail; + } + + SSL_CONF_CTX_free(cctx); + + return NXT_OK; + +fail: + + SSL_CONF_CTX_free(cctx); + + return NXT_ERROR; +} + +#endif + + static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp) @@ -550,7 +648,7 @@ nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, NULL, 0); if (len <= 0) { nxt_log(task, NXT_LOG_WARN, "certificate \"%V\" has neither " - "Subject Alternative Name nor Common Name", bundle->name); + "Subject Alternative Name nor Common Name", &bundle->name); return NXT_OK; } @@ -629,7 +727,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, if (item->name.length == 0 || item->name.start[0] != '.') { nxt_log(task, NXT_LOG_WARN, "ignored invalid name \"%V\" " "in certificate \"%V\": missing \".\" " - "after wildcard symbol", &str, item->bundle->name); + "after wildcard symbol", &str, &item->bundle->name); return NXT_OK; } } @@ -644,7 +742,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, ret = nxt_lvlhsh_insert(lvlhsh, &lhq); if (nxt_fast_path(ret == NXT_OK)) { nxt_debug(task, "name \"%V\" for certificate \"%V\" is inserted", - &str, item->bundle->name); + &str, &item->bundle->name); return NXT_OK; } @@ -653,7 +751,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, if (old->bundle != item->bundle) { nxt_log(task, NXT_LOG_WARN, "ignored duplicate name \"%V\" " "in certificate \"%V\", identical name appears in \"%V\"", - &str, old->bundle->name, item->bundle->name); + &str, &old->bundle->name, &item->bundle->name); old->bundle = item->bundle; } @@ -730,8 +828,8 @@ nxt_openssl_servername(SSL *s, int *ad, void *arg) if (bundle != NULL) { nxt_debug(c->socket.task, "new tls context found for \"%V\": \"%V\" " - "(old: \"%V\")", &str, bundle->name, - conf->bundle->name); + "(old: \"%V\")", &str, &bundle->name, + &conf->bundle->name); if (bundle != conf->bundle) { if (SSL_set_SSL_CTX(s, bundle->ctx) == NULL) { diff --git a/src/nxt_router.c b/src/nxt_router.c index 122fd523..015ae226 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -41,8 +41,11 @@ typedef struct { #if (NXT_TLS) typedef struct { - nxt_str_t name; - nxt_socket_conf_t *conf; + nxt_str_t name; + nxt_socket_conf_t *socket_conf; + nxt_router_temp_conf_t *temp_conf; + nxt_conf_value_t *conf_cmds; + nxt_bool_t last; nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */ } nxt_router_tlssock_t; @@ -117,12 +120,11 @@ static void nxt_router_listen_socket_ready(nxt_task_t *task, static void nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); #if (NXT_TLS) -static void nxt_router_tls_rpc_create(nxt_task_t *task, - nxt_router_temp_conf_t *tmcf, nxt_router_tlssock_t *tls, nxt_bool_t last); static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *value, nxt_socket_conf_t *skcf); + nxt_conf_value_t *value, nxt_socket_conf_t *skcf, + nxt_conf_value_t * conf_cmds); #endif static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app); @@ -954,8 +956,10 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data) tls = nxt_queue_link_data(qlk, nxt_router_tlssock_t, link); - nxt_router_tls_rpc_create(task, tmcf, tls, - nxt_queue_is_empty(&tmcf->tls)); + tls->last = nxt_queue_is_empty(&tmcf->tls); + + nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, + nxt_router_tls_rpc_handler, tls); return; } #endif @@ -1337,7 +1341,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_t *router; nxt_app_joint_t *app_joint; #if (NXT_TLS) - nxt_conf_value_t *certificate; + nxt_conf_value_t *certificate, *conf_cmds; #endif nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; @@ -1358,6 +1362,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_str_t access_log_path = nxt_string("/access_log"); #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); + static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands"); #endif static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); @@ -1736,6 +1741,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, certificate = nxt_conf_get_path(listener, &certificate_path); if (certificate != NULL) { + conf_cmds = nxt_conf_get_path(listener, &conf_commands_path); + if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) { n = nxt_conf_array_elements_count(certificate); @@ -1744,7 +1751,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_assert(value != NULL); - ret = nxt_router_conf_tls_insert(tmcf, value, skcf); + ret = nxt_router_conf_tls_insert(tmcf, value, skcf, + conf_cmds); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1752,7 +1760,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } else { /* NXT_CONF_STRING */ - ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf); + ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf, + conf_cmds); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1846,25 +1855,20 @@ fail: static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *value, nxt_socket_conf_t *skcf) + nxt_conf_value_t *value, nxt_socket_conf_t *skcf, + nxt_conf_value_t *conf_cmds) { - nxt_mp_t *mp; - nxt_str_t str; nxt_router_tlssock_t *tls; - mp = tmcf->router_conf->mem_pool; - - tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t)); + tls = nxt_mp_get(tmcf->mem_pool, sizeof(nxt_router_tlssock_t)); if (nxt_slow_path(tls == NULL)) { return NXT_ERROR; } - tls->conf = skcf; - nxt_conf_get_string(value, &str); - - if (nxt_slow_path(nxt_str_dup(mp, &tls->name, &str) == NULL)) { - return NXT_ERROR; - } + tls->socket_conf = skcf; + tls->conf_cmds = conf_cmds; + tls->temp_conf = tmcf; + nxt_conf_get_string(value, &tls->name); nxt_queue_insert_tail(&tmcf->tls, &tls->link); @@ -2427,28 +2431,6 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, #if (NXT_TLS) -static void -nxt_router_tls_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, - nxt_router_tlssock_t *tls, nxt_bool_t last) -{ - nxt_socket_rpc_t *rpc; - - rpc = nxt_mp_alloc(tmcf->mem_pool, sizeof(nxt_socket_rpc_t)); - if (rpc == NULL) { - nxt_router_conf_error(task, tmcf); - return; - } - - rpc->name = &tls->name; - rpc->socket_conf = tls->conf; - rpc->temp_conf = tmcf; - rpc->last = last; - - nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, - nxt_router_tls_rpc_handler, rpc); -} - - static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) @@ -2456,14 +2438,14 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_mp_t *mp; nxt_int_t ret; nxt_tls_conf_t *tlscf; - nxt_socket_rpc_t *rpc; + nxt_router_tlssock_t *tls; nxt_tls_bundle_conf_t *bundle; nxt_router_temp_conf_t *tmcf; nxt_debug(task, "tls rpc handler"); - rpc = data; - tmcf = rpc->temp_conf; + tls = data; + tmcf = tls->temp_conf; if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) { goto fail; @@ -2471,17 +2453,17 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, mp = tmcf->router_conf->mem_pool; - if (rpc->socket_conf->tls == NULL){ + if (tls->socket_conf->tls == NULL){ tlscf = nxt_mp_zget(mp, sizeof(nxt_tls_conf_t)); if (nxt_slow_path(tlscf == NULL)) { goto fail; } tlscf->no_wait_shutdown = 1; - rpc->socket_conf->tls = tlscf; + tls->socket_conf->tls = tlscf; } else { - tlscf = rpc->socket_conf->tls; + tlscf = tls->socket_conf->tls; } bundle = nxt_mp_get(mp, sizeof(nxt_tls_bundle_conf_t)); @@ -2489,12 +2471,16 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, goto fail; } - bundle->name = rpc->name; + if (nxt_slow_path(nxt_str_dup(mp, &bundle->name, &tls->name) == NULL)) { + goto fail; + } + bundle->chain_file = msg->fd[0]; bundle->next = tlscf->bundle; tlscf->bundle = bundle; - ret = task->thread->runtime->tls->server_init(task, tlscf, mp, rpc->last); + ret = task->thread->runtime->tls->server_init(task, tlscf, mp, + tls->conf_cmds, tls->last); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } diff --git a/src/nxt_tls.h b/src/nxt_tls.h index 2a29f3ca..63c49ee4 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -8,6 +8,9 @@ #define _NXT_TLS_H_INCLUDED_ +#include + + /* * The SSL/TLS libraries lack vector I/O interface yet add noticeable * overhead to each SSL/TLS record so buffering allows to decrease the @@ -32,6 +35,7 @@ typedef struct { nxt_int_t (*server_init)(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_mp_t *mp, + nxt_conf_value_t *conf_cmds, nxt_bool_t last); void (*server_free)(nxt_task_t *task, nxt_tls_conf_t *conf); @@ -49,7 +53,7 @@ struct nxt_tls_bundle_conf_s { void *ctx; nxt_fd_t chain_file; - nxt_str_t *name; + nxt_str_t name; nxt_tls_bundle_conf_t *next; }; -- cgit From 753ce145f7cfbfffab34d18ff74236f09e471f18 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 26 May 2021 21:47:12 +0100 Subject: Tests: added TLS test without close notify. --- test/test_tls.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/test_tls.py b/test/test_tls.py index 3ab6f7d7..0cfeaded 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -2,6 +2,7 @@ import io import re import ssl import subprocess +import time import pytest @@ -501,6 +502,28 @@ basicConstraints = critical,CA:TRUE""" assert resp['body'] == '0123456789', 'keepalive 2' + def test_tls_no_close_notify(self): + self.certificate() + + assert 'success' in self.conf( + { + "listeners": { + "*:7080": { + "pass": "routes", + "tls": {"certificate": "default"}, + } + }, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ), 'load application configuration' + + (resp, sock) = self.get_ssl(start=True) + + time.sleep(5) + + sock.close() + @pytest.mark.skip('not yet') def test_tls_keepalive_certificate_remove(self): self.load('empty') -- cgit From e00ad18d8082f9db5c49c220d796c78beab53cae Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 27 May 2021 13:12:52 +0300 Subject: Packages: added Ubuntu 21.04 "hirsute" support. --- docs/Makefile | 5 +- docs/changes.xml | 26 ++++++++ pkg/deb/Makefile | 15 +++++ pkg/deb/Makefile.jsc-common | 2 +- pkg/deb/Makefile.jsc16 | 71 ++++++++++++++++++++++ pkg/deb/Makefile.jsc17 | 71 ++++++++++++++++++++++ pkg/deb/Makefile.python39 | 46 ++++++++++++++ pkg/deb/debian.module/unit.example-jsc16-config | 15 +++++ pkg/deb/debian.module/unit.example-jsc17-config | 15 +++++ .../debian.module/unit.example-python3.9-config | 16 +++++ 10 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 pkg/deb/Makefile.jsc16 create mode 100644 pkg/deb/Makefile.jsc17 create mode 100644 pkg/deb/Makefile.python39 create mode 100644 pkg/deb/debian.module/unit.example-jsc16-config create mode 100644 pkg/deb/debian.module/unit.example-jsc17-config create mode 100644 pkg/deb/debian.module/unit.example-python3.9-config diff --git a/docs/Makefile b/docs/Makefile index db63eec4..d27e69be 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,12 +7,13 @@ PACKAGES= unit \ unit-php \ unit-python unit-python2.7 unit-python3.4 \ unit-python3.5 unit-python3.6 unit-python3.7 \ - unit-python3.8 \ + unit-python3.8 unit-python3.9 \ unit-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 \ unit-go1.12 unit-go1.13 \ unit-perl \ unit-ruby \ - unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 + unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 \ + unit-jsc13 unit-jsc14 unit-jsc15 unit-jsc16 unit-jsc17 CURDATE:=$(shell date +"%Y-%m-%d") CURTIME:=$(shell date +"%H:%M:%S %z") diff --git a/docs/changes.xml b/docs/changes.xml index 2dcaf4dd..0a666a3e 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,32 @@ + + + + +Initial release of Java 17 module for NGINX Unit. + + + + + + + + + + +Initial release of Java 16 module for NGINX Unit. + + + + + + Date: Thu, 27 May 2021 13:30:51 +0100 Subject: Tests: added tests for TLS "conf_commands" option. --- test/test_tls_conf_command.py | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 test/test_tls_conf_command.py diff --git a/test/test_tls_conf_command.py b/test/test_tls_conf_command.py new file mode 100644 index 00000000..ccae09ad --- /dev/null +++ b/test/test_tls_conf_command.py @@ -0,0 +1,112 @@ +import ssl + +import pytest + +from unit.applications.tls import TestApplicationTLS + + +class TestTLSConfCommand(TestApplicationTLS): + prerequisites = {'modules': {'openssl': 'any'}} + + @pytest.fixture(autouse=True) + def setup_method_fixture(self, request): + self.certificate() + + assert 'success' in self.conf( + { + "listeners": { + "*:7080": { + "pass": "routes", + "tls": {"certificate": "default"}, + } + }, + "routes": [{"action": {"return": 200}}], + "applications": {}, + } + ), 'load application configuration' + + def test_tls_conf_command(self): + def check_no_connection(): + try: + self.get_ssl() + pytest.fail('Unexpected connection.') + + except (ssl.SSLError, ConnectionRefusedError): + pass + + # Set one conf_commands (disable protocol). + + (resp, sock) = self.get_ssl(start=True) + + shared_ciphers = sock.shared_ciphers() + protocols = list(set(c[1] for c in shared_ciphers)) + protocol = sock.cipher()[1] + + if '/' in protocol: + pytest.skip('Complex protocol format.') + + assert 'success' in self.conf( + { + "certificate": "default", + "conf_commands": {"protocol": '-' + protocol}, + }, + 'listeners/*:7080/tls', + ), 'protocol disabled' + + sock.close() + + if len(protocols) > 1: + (resp, sock) = self.get_ssl(start=True) + + cipher = sock.cipher() + assert cipher[1] != protocol, 'new protocol used' + + shared_ciphers = sock.shared_ciphers() + ciphers = list(set(c for c in shared_ciphers if c[1] == cipher[1])) + + sock.close() + else: + check_no_connection() + pytest.skip('One TLS protocol available only.') + + # Set two conf_commands (disable protocol and cipher). + + assert 'success' in self.conf( + { + "certificate": "default", + "conf_commands": { + "protocol": '-' + protocol, + "cipherstring": cipher[1] + ":!" + cipher[0], + }, + }, + 'listeners/*:7080/tls', + ), 'cipher disabled' + + if len(ciphers) > 1: + (resp, sock) = self.get_ssl(start=True) + + cipher_new = sock.cipher() + assert cipher_new[1] == cipher[1], 'previous protocol used' + assert cipher_new[0] != cipher[0], 'new cipher used' + + sock.close() + + else: + check_no_connection() + + def test_tls_conf_command_invalid(self, skip_alert): + skip_alert(r'SSL_CONF_cmd', r'failed to apply new conf') + + def check_conf_commands(conf_commands): + assert 'error' in self.conf( + {"certificate": "default", "conf_commands": conf_commands}, + 'listeners/*:7080/tls', + ), 'ivalid conf_commands' + + check_conf_commands([]) + check_conf_commands("blah") + check_conf_commands({"": ""}) + check_conf_commands({"blah": ""}) + check_conf_commands({"protocol": {}}) + check_conf_commands({"protocol": "blah"}) + check_conf_commands({"protocol": "TLSv1.2", "blah": ""}) -- cgit From 3b0fa832a39955c8a642188b29ab7456129a1b41 Mon Sep 17 00:00:00 2001 From: Artem Konev Date: Thu, 27 May 2021 13:53:58 +0100 Subject: Grammar fixes and improvements in changes.xml. --- docs/changes.xml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 0a666a3e..27fdca68 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -77,20 +77,21 @@ multiple "targets" in Python applications. -a loader for automatic overriding "http" and "websocket" modules in Node.js. +a loader for automatically overriding the "http" and "websocket" modules in +Node.js. -ability to limit serving of static files by MIME types. +the ability to limit static file serving by MIME types. -support for chrooting, rejecting symlinks, and rejecting crossing mounting -points on a per-request basis during static file serving. +support for chrooting, rejecting symlinks, and rejecting mount +point traversal on a per-request basis when serving static files. @@ -102,7 +103,7 @@ compatibility with Ruby 3.0. -the router process could crash while closing TLS connection. +the router process could crash while closing a TLS connection. -- cgit From ff15f258390b36d4cccded8576b49e281722495c Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 27 May 2021 16:05:42 +0300 Subject: Reordered changes for 1.24.0 by significance (subjective). --- docs/changes.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/changes.xml b/docs/changes.xml index 27fdca68..ad1526f4 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -71,27 +71,27 @@ arbitrary configuration of TLS connections via OpenSSL commands. -multiple "targets" in Python applications. +the ability to limit static file serving by MIME types. -a loader for automatically overriding the "http" and "websocket" modules in -Node.js. +support for chrooting, rejecting symlinks, and rejecting mount +point traversal on a per-request basis when serving static files. -the ability to limit static file serving by MIME types. +a loader for automatically overriding the "http" and "websocket" modules in +Node.js. -support for chrooting, rejecting symlinks, and rejecting mount -point traversal on a per-request basis when serving static files. +multiple "targets" in Python applications. -- cgit From 340955a75f333a0ccfe8b1d212640a2e0ac3b3e3 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 27 May 2021 16:06:01 +0300 Subject: Added version 1.24.0 CHANGES. --- CHANGES | 28 ++++++++++++++++++++++++++++ docs/changes.xml | 8 ++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b411e816..cbc6678e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,32 @@ +Changes with Unit 1.24.0 27 May 2021 + + *) Change: PHP added to the default MIME type list. + + *) Feature: arbitrary configuration of TLS connections via OpenSSL + commands. + + *) Feature: the ability to limit static file serving by MIME types. + + *) Feature: support for chrooting, rejecting symlinks, and rejecting + mount point traversal on a per-request basis when serving static + files. + + *) Feature: a loader for automatically overriding the "http" and + "websocket" modules in Node.js. + + *) Feature: multiple "targets" in Python applications. + + *) Feature: compatibility with Ruby 3.0. + + *) Bugfix: the router process could crash while closing a TLS + connection. + + *) Bugfix: a segmentation fault might have occurred in the PHP module if + fastcgi_finish_request() was used with the "auto_globals_jit" option + enabled. + + Changes with Unit 1.23.0 25 Mar 2021 *) Feature: support for multiple certificate bundles on a listener via diff --git a/docs/changes.xml b/docs/changes.xml index ad1526f4..3707194e 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -6,7 +6,7 @@ @@ -19,7 +19,7 @@ Initial release of Java 17 module for NGINX Unit. @@ -41,7 +41,7 @@ Initial release of Java 16 module for NGINX Unit. unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 unit-jsc13 unit-jsc14 unit-jsc15" ver="1.24.0" rev="1" - date="" time="" + date="2021-05-27" time="18:00:00 +0300" packager="Andrei Belov <defan@nginx.com>"> @@ -54,7 +54,7 @@ NGINX Unit updated to 1.24.0. -- cgit From 25d8e102b0b3ca6cfa15b752397566fdbe2f7eaa Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 27 May 2021 16:07:15 +0300 Subject: Generated Dockerfiles for Unit 1.24.0. --- pkg/docker/Dockerfile.go1.15 | 2 +- pkg/docker/Dockerfile.jsc11 | 2 +- pkg/docker/Dockerfile.minimal | 2 +- pkg/docker/Dockerfile.node15 | 2 +- pkg/docker/Dockerfile.perl5.32 | 2 +- pkg/docker/Dockerfile.php8.0 | 2 +- pkg/docker/Dockerfile.python3.9 | 2 +- pkg/docker/Dockerfile.ruby2.7 | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/docker/Dockerfile.go1.15 b/pkg/docker/Dockerfile.go1.15 index 45642662..d446a934 100644 --- a/pkg/docker/Dockerfile.go1.15 +++ b/pkg/docker/Dockerfile.go1.15 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.jsc11 b/pkg/docker/Dockerfile.jsc11 index 4cfc541d..b66ebe73 100644 --- a/pkg/docker/Dockerfile.jsc11 +++ b/pkg/docker/Dockerfile.jsc11 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index 59aaa6a3..69a70e33 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.node15 b/pkg/docker/Dockerfile.node15 index 7eddfb26..1e3846a3 100644 --- a/pkg/docker/Dockerfile.node15 +++ b/pkg/docker/Dockerfile.node15 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.perl5.32 b/pkg/docker/Dockerfile.perl5.32 index aa000f63..2fccbf63 100644 --- a/pkg/docker/Dockerfile.perl5.32 +++ b/pkg/docker/Dockerfile.perl5.32 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.php8.0 b/pkg/docker/Dockerfile.php8.0 index 4534f9b5..02db27cf 100644 --- a/pkg/docker/Dockerfile.php8.0 +++ b/pkg/docker/Dockerfile.php8.0 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.python3.9 b/pkg/docker/Dockerfile.python3.9 index e8650d44..44472a12 100644 --- a/pkg/docker/Dockerfile.python3.9 +++ b/pkg/docker/Dockerfile.python3.9 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ diff --git a/pkg/docker/Dockerfile.ruby2.7 b/pkg/docker/Dockerfile.ruby2.7 index aa8cdb3f..7875c470 100644 --- a/pkg/docker/Dockerfile.ruby2.7 +++ b/pkg/docker/Dockerfile.ruby2.7 @@ -8,7 +8,7 @@ RUN set -ex \ && mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \ && hg clone https://hg.nginx.org/unit \ && cd unit \ - && hg up 1.23.0 \ + && hg up 1.24.0 \ && NCPU="$(getconf _NPROCESSORS_ONLN)" \ && DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \ && CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \ -- cgit From d06e55dfa3692e27a92ff6c2534bb083416bc0c8 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 27 May 2021 16:59:54 +0300 Subject: Unit 1.24.0 release. --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 91de390f..b7cf1b6b 100644 --- a/.hgtags +++ b/.hgtags @@ -30,3 +30,4 @@ b391df5f0102aa6afe660cfc863729c1b1111c9e 1.12.0 f804aaf7eee10a7d8116820840d6312dd4914a41 1.21.0 331bdadeca30a49dd11b86af99124c2ffeb22d05 1.22.0 49ee24c03f5749f8a1b69dc1c600ad48517d9d7a 1.23.0 +847c88d10f26765b45149c14f88c2274adfc3f42 1.24.0 -- cgit