diff --git a/CMakeLists.txt b/CMakeLists.txt index b6d32f28c80..698816b0f18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -390,7 +390,7 @@ SET(MODULE_LIST "modules/metadata/mod_expires+I+Expires header control" "modules/metadata/mod_headers+A+HTTP header control" "modules/metadata/mod_ident+O+RFC 1413 identity check" - "modules/metadata/mod_mime_magic+O+automagically determining MIME type" + "modules/metadata/mod_mime_magic+I+automagically determining MIME type" "modules/metadata/mod_remoteip+I+translate header contents to an apparent client remote_ip" "modules/metadata/mod_setenvif+A+basing ENV vars on headers" "modules/metadata/mod_unique_id+I+per-request unique ids" @@ -410,6 +410,7 @@ SET(MODULE_LIST "modules/proxy/mod_proxy_hcheck+I+Apache proxy Health check module. Requires and is enabled by --enable-proxy." "modules/proxy/mod_proxy_http+I+Apache proxy HTTP module. Requires and is enabled by --enable-proxy." "modules/proxy/mod_proxy_scgi+I+Apache proxy SCGI module. Requires and is enabled by --enable-proxy." + "modules/proxy/mod_proxy_uwsgi+I+Apache proxy SCGI module. Requires and is enabled by --enable-proxy." "modules/proxy/mod_proxy_wstunnel+I+Apache proxy Websocket Tunnel module. Requires and is enabled by --enable-proxy." "modules/http2/mod_proxy_http2+i+Apache proxy HTTP/2 module. Requires --enable-proxy." "modules/proxy/mod_serf+O+Reverse proxy module using Serf" @@ -609,6 +610,7 @@ IF(LIBXML2_FOUND) SET(mod_proxy_html_extra_libs "${LIBXML2_LIBRARIES};${LIBXML2_ICONV_LIBRARIES}") ENDIF() SET(mod_proxy_scgi_extra_libs mod_proxy) +SET(mod_proxy_uwsgi_extra_libs mod_proxy) SET(mod_proxy_wstunnel_extra_libs mod_proxy) SET(mod_lbmethod_bybusyness_extra_libs mod_proxy) SET(mod_lbmethod_bytraffic_extra_libs mod_proxy) diff --git a/test/modules/proxy/test_03_uwsgi.py b/test/modules/proxy/test_03_uwsgi.py index 20f501e0d4a..fff082321f5 100644 --- a/test/modules/proxy/test_03_uwsgi.py +++ b/test/modules/proxy/test_03_uwsgi.py @@ -1,4 +1,3 @@ - """ Tests for mod_proxy_uwsgi fix. @@ -31,6 +30,7 @@ def _proxy_uwsgi_setup(env): conf.add([ "LogLevel proxy_uwsgi:debug", f"ProxyPass / uwsgi://127.0.0.1:{UWSGI_PORT}/", + "LogLevel trace4", ]) conf.end_vhost() conf.install() @@ -66,94 +66,7 @@ def test_proxy_03_003(self, env): """Transfer-Encoding is stripped by mod_proxy_uwsgi.""" r = env.curl_get( f"http://{env.d_reverse}:{env.http_port}/?te=1", - 5, - ) - - print("response =", r.response) - print("stderr =", getattr(r, "stderr", None)) - print("stdout =", getattr(r, "stdout", None)) - print("exitcode =", getattr(r, "exit_code", None)) - print("json =", getattr(r, "json", None)) - - assert r.response is not None - assert r.response["status"] == 200 - assert "transfer-encoding" not in r.response["header"] - - assert r.json["host"] == "uwsgi-faker" - - assert env.httpd_error_log.scan_recent( - re.compile( - r".*uwsgi: removing hop-by-hop header 'Transfer-Encoding'" - ) - ) -""" -Tests for mod_proxy_uwsgi fix. - -Per PEP 3333, WSGI applications must not generate hop-by-hop -headers. Remove any such headers received from the backend. -""" - -import re - -import pytest - -from pyhttpd.conf import HttpdConf -from .uwsgi_faker import UwsgiFaker - - -UWSGI_PORT = 5200 - - -@pytest.fixture(autouse=True, scope="class") -def _proxy_uwsgi_setup(env): - faker = UwsgiFaker(port=UWSGI_PORT) - faker.start() - - conf = HttpdConf(env) - conf.start_vhost( - domains=[env.d_reverse], - port=env.http_port, - with_ssl=False, - ) - conf.add([ - "LogLevel proxy_uwsgi:debug", - f"ProxyPass / uwsgi://127.0.0.1:{UWSGI_PORT}/", - ]) - conf.end_vhost() - conf.install() - - assert env.apache_restart() == 0 - - yield - - faker.stop() - - -class TestProxyUwsgi: - - def test_proxy_03_001(self, env): - """uWSGI backend response is forwarded correctly.""" - r = env.curl_get( - f"http://{env.d_reverse}:{env.http_port}/", - 5, - ) - assert r.response["status"] == 200 - assert r.json["host"] == "uwsgi-faker" - - def test_proxy_03_002(self, env): - """No Transfer-Encoding header reaches the client.""" - r = env.curl_get( - f"http://{env.d_reverse}:{env.http_port}/", - 5, - ) - assert r.response["status"] == 200 - assert "transfer-encoding" not in r.response["header"] - - def test_proxy_03_003(self, env): - """Transfer-Encoding is stripped by mod_proxy_uwsgi.""" - r = env.curl_get( - f"http://{env.d_reverse}:{env.http_port}/?te=1", - 5, + 5, ) print("response =", r.response) diff --git a/test/modules/proxy/uwsgi_faker.py b/test/modules/proxy/uwsgi_faker.py index e09870cabdf..34ea6d7ab80 100644 --- a/test/modules/proxy/uwsgi_faker.py +++ b/test/modules/proxy/uwsgi_faker.py @@ -112,152 +112,6 @@ def _handle(conn): conn.close() -class UwsgiFaker: - """Fake uWSGI backend running in a daemon thread.""" - - def __init__(self, port: int): - self._port = port - self._done = False - self._sock = None - self._thread = None - - def start(self): - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._sock.bind(("127.0.0.1", self._port)) - self._sock.listen(16) - self._thread = threading.Thread(target=self._serve, daemon=True) - self._thread.start() - - def stop(self): - self._done = True - try: - self._sock.close() - except OSError: - pass - - def _serve(self): - while not self._done: - try: - conn, _ = self._sock.accept() - except OSError: - break - t = threading.Thread(target=_handle, args=(conn,), daemon=True) - t.start() -""" -Minimal fake uWSGI backend for testing mod_proxy_uwsgi. - -Listens on a TCP port and speaks the uWSGI wire protocol. mod_proxy_uwsgi -sends a binary uwsgi packet (4-byte header + CGI key/value pairs) followed by -the optional request body, and expects a plain HTTP/1.x response back. - -The QUERY_STRING CGI variable controls what the response looks like: - - QUERY_STRING="" - normal 200 response, no Transfer-Encoding - QUERY_STRING="te=1" - 200 response with Transfer-Encoding: chunked injected - (simulates a buggy Python app; mod_proxy_uwsgi must - strip it before forwarding to the client) -""" -import socket -import struct -import threading - - -BODY = b'{"host": "uwsgi-faker"}' - - -def _parse_uwsgi_vars(data): - """Parse the CGI key/value payload from a uwsgi packet. - - Packet layout (from the uWSGI protocol spec): - [modifier1: u8][payload_len: u16le][modifier2: u8][payload: bytes] - Payload is a sequence of: - [keylen: u16le][key: bytes][vallen: u16le][val: bytes] - - Returns a dict of the decoded variables, or {} on parse error. - """ - if len(data) < 4: - return {} - payload_len = struct.unpack_from(" end: - break - klen = struct.unpack_from(" end: - break - key = data[pos:pos + klen].decode("latin-1") - pos += klen - if pos + 2 > end: - break - vlen = struct.unpack_from(" end: - break - val = data[pos:pos + vlen].decode("latin-1") - pos += vlen - vars_[key] = val - return vars_ - - -def _recv_uwsgi_request(conn): - """Read a complete uwsgi request packet from the connection. - - Returns the raw bytes of the complete packet (header + payload), or b"" - on connection close. - """ - # Read the 4-byte uwsgi header - header = b"" - while len(header) < 4: - chunk = conn.recv(4 - len(header)) - if not chunk: - return b"" - header += chunk - - payload_len = struct.unpack_from("