From 4463ea0ce35f249b78b8810c28b246ba1dc3319a Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 27 Feb 2023 16:48:31 +0400 Subject: [PATCH 01/14] reworked `vhost` tests with new framework. --- vhost/test_add_hdr.py | 514 ++++++++++++++++++++++++++++-------------- 1 file changed, 344 insertions(+), 170 deletions(-) diff --git a/vhost/test_add_hdr.py b/vhost/test_add_hdr.py index 646046b17..b451e6334 100644 --- a/vhost/test_add_hdr.py +++ b/vhost/test_add_hdr.py @@ -1,227 +1,401 @@ """Functional tests for adding user difined headers.""" -from __future__ import print_function - -from helpers import chains -from testers import functional - __author__ = "Tempesta Technologies, Inc." -__copyright__ = "Copyright (C) 2017 Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2017-2023 Tempesta Technologies, Inc." __license__ = "GPL2" +from framework import tester + + +class TestReqAddHeader(tester.TempestaTest): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2; + +server ${server_ip}:8000; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + clients = [ + { + "id": "deproxy-1", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", + }, + ] + + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "\r\n" + ) + headers = [("x-my-hdr", "some text"), ("x-my-hdr-2", "some other text")] + cache = False + directive = "req_hdr_add" + steps = 1 + + def update_tempesta_config(self, config: str): + tempesta = self.get_tempesta() -class TestReqAddHeader(functional.FunctionalTest): + cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" - location = "/" - directive = "req_hdr_add" + tempesta.config.defconfig += config + "\n" + cache + + def base_scenario(self, config: str, headers: list): + client = self.get_client("deproxy-1") + server = self.get_server("deproxy") - def make_def_config(self): - self.config = "" - - def config_append_directive(self, hdrs, location=None): - if location is not None: - self.config = self.config + ('location prefix "%s" {\n' % location) - for (name, val) in hdrs: - self.config = self.config + ('%s %s "%s";\n' % (self.directive, name, val)) - if location is not None: - self.config = self.config + "}\n" - - def make_chain(self, hdrs): - chain = chains.proxy() - for (name, val) in hdrs: - chain.fwd_request.headers[name] = val - chain.fwd_request.update() - self.msg_chain = [chain] - - def add_hdrs(self, hdrs, location=None): - self.make_def_config() - self.config_append_directive(hdrs, location) - self.make_chain(hdrs) + self.update_tempesta_config(config=config) + + self.start_all_services() + + for _ in range(self.steps): + client.send_request(self.request, "200") + + for header in headers: + if self.directive in ["req_hdr_set", "req_hdr_add"]: + self.assertIn(header, server.last_request.headers.items()) + self.assertNotIn(header, client.last_response.headers.items()) + else: + self.assertIn(header, client.last_response.headers.items()) + self.assertNotIn(header, server.last_request.headers.items()) + + return client, server def test_add_one_hdr(self): - hdrs = [("X-My-Hdr", "some text")] - self.add_hdrs(hdrs) - self.generic_test_routine(self.config, self.msg_chain) + client, server = self.base_scenario( + config=(f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n'), + headers=[self.headers[0]], + ) + return client, server def test_add_some_hdrs(self): - hdrs = [("X-My-Hdr", "some text"), ("X-My-Hdr-2", "some other text")] - self.add_hdrs(hdrs) - self.generic_test_routine(self.config, self.msg_chain) + client, server = self.base_scenario( + config=( + f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' + f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' + ), + headers=self.headers, + ) + return client, server def test_add_some_hdrs_custom_location(self): - hdrs = [("X-My-Hdr", "some text"), ("X-My-Hdr-2", "some other text")] - self.add_hdrs(hdrs, self.location) - self.generic_test_routine(self.config, self.msg_chain) + client, server = self.base_scenario( + config=( + 'location prefix "/" {\n' + f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' + f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' + "}\n" + ), + headers=self.headers, + ) + return client, server def test_add_hdrs_derive_config(self): - """Derive general settings to custom location.""" - hdrs = [("X-My-Hdr", "some text")] - self.make_def_config() - self.config_append_directive(hdrs) - self.config_append_directive([], self.location) - self.make_chain(hdrs) - self.generic_test_routine(self.config, self.msg_chain) + client, server = self.base_scenario( + config=( + f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' + 'location prefix "/" {}\n' + ), + headers=[self.headers[0]], + ) + return client, server def test_add_hdrs_override_config(self): - """Override general settings to custom location.""" - hdrs = [("X-My-Hdr", "some text")] - o_hdrs = [("X-My-Hdr-2", "some other text")] - self.make_def_config() - self.config_append_directive(hdrs) - self.config_append_directive(o_hdrs, self.location) - self.make_chain(o_hdrs) - self.generic_test_routine(self.config, self.msg_chain) + client, server = self.base_scenario( + config=( + f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' + 'location prefix "/" {\n' + f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' + "}\n" + ), + headers=[self.headers[1]], + ) + + if self.directive == "req_hdr_add": + self.assertNotIn(self.headers[0], server.last_request.headers.items()) + else: + self.assertNotIn(self.headers[0], client.last_response.headers.items()) + + return client, server class TestRespAddHeader(TestReqAddHeader): directive = "resp_hdr_add" - def make_chain(self, hdrs): - chain = chains.proxy() - for (name, val) in hdrs: - chain.response.headers[name] = val - chain.response.update() - self.msg_chain = [chain] +class TestCachedRespAddHeader(TestReqAddHeader): + cache = True + directive = "resp_hdr_add" + steps = 2 -class TestCachedRespAddHeader(TestRespAddHeader): - """Response is served from cache.""" - def make_def_config(self): - self.config = "cache 2;\n" "cache_fulfill * *;\n" +class TestReqSetHeader(TestReqAddHeader): - def make_chain(self, hdrs): - chain_p = chains.proxy() - chain_c = chains.cache() - for (name, val) in hdrs: - chain_p.response.headers[name] = val - chain_c.response.headers[name] = val - chain_p.response.update() - chain_c.response.update() - self.msg_chain = [chain_p, chain_c] + directive = "req_hdr_set" + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "\r\n" + ) + + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] -class TestReqSetHeader(TestReqAddHeader): + def test_add_one_hdr(self): + client, server = super(TestReqSetHeader, self).test_add_one_hdr() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) - directive = "req_hdr_set" + def test_add_some_hdrs(self): + client, server = super(TestReqSetHeader, self).test_add_some_hdrs() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_some_hdrs_custom_location(self): + client, server = super(TestReqSetHeader, self).test_add_some_hdrs_custom_location() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_hdrs_derive_config(self): + client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_hdrs_override_config(self): + client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain = chains.proxy() - for (name, val) in orig_hdrs: - chain.request.headers[name] = val - chain.fwd_request.headers[name] = val - for (name, val) in hdrs: - chain.fwd_request.headers[name] = val - chain.request.update() - chain.fwd_request.update() - self.msg_chain = [chain] + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) class TestRespSetHeader(TestReqSetHeader): directive = "resp_hdr_set" - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain = chains.proxy() - for (name, val) in orig_hdrs: - chain.server_response.headers[name] = val - chain.response.headers[name] = val - for (name, val) in hdrs: - chain.response.headers[name] = val - chain.server_response.update() - chain.response.update() - self.msg_chain = [chain] - class TestCachedRespSetHeader(TestRespSetHeader): - """Response is served from cache.""" - - def make_def_config(self): - self.config = "cache 2;\n" "cache_fulfill * *;\n" - - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain_p = chains.proxy() - chain_c = chains.cache() - for (name, val) in orig_hdrs: - chain_p.server_response.headers[name] = val - chain_p.response.headers[name] = val - chain_c.response.headers[name] = val - for (name, val) in hdrs: - chain_p.response.headers[name] = val - chain_c.response.headers[name] = val - chain_p.server_response.update() - chain_p.response.update() - chain_c.response.update() - self.msg_chain = [chain_p, chain_c] + cache = True + steps = 2 class TestReqDelHeader(TestReqAddHeader): directive = "req_hdr_set" - def config_append_directive(self, hdrs, location=None): - if location is not None: - self.config = self.config + ('location prefix "%s" {\n' % location) - for (name, val) in hdrs: - self.config = self.config + ("%s %s;\n" % (self.directive, name)) - if location is not None: - self.config = self.config + "}\n" - - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain = chains.proxy() - for (name, val) in orig_hdrs: - chain.request.headers[name] = val - chain.fwd_request.headers[name] = val - for (name, val) in hdrs: - del chain.fwd_request.headers[name] - chain.request.update() - chain.fwd_request.update() - self.msg_chain = [chain] + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "\r\n" + ) + + def test_add_one_hdr(self): + client, server = self.base_scenario( + config=(f"{self.directive} {self.headers[0][0]};\n"), + headers=[("x-my-hdr-2", "other original text")], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) + else: + self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) + + return client, server + + def test_add_some_hdrs(self): + client, server = self.base_scenario( + config=( + f"{self.directive} {self.headers[0][0]};\n" + f"{self.directive} {self.headers[1][0]};\n" + ), + headers=[], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) + self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) + else: + self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) + self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) + + return client, server + + def test_add_some_hdrs_custom_location(self): + client, server = self.base_scenario( + config=( + 'location prefix "/" {\n' + f"{self.directive} {self.headers[0][0]};\n" + f"{self.directive} {self.headers[1][0]};\n" + "}\n" + ), + headers=[], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) + self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) + else: + self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) + self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) + + return client, server + + def test_add_hdrs_derive_config(self): + client, server = self.base_scenario( + config=(f"{self.directive} {self.headers[0][0]};\n" 'location prefix "/" {}\n'), + headers=[("x-my-hdr-2", "other original text")], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) + else: + self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) + + return client, server + + def test_add_hdrs_override_config(self): + client, server = self.base_scenario( + config=( + f"{self.directive} {self.headers[0][0]};\n" + 'location prefix "/" {\n' + f"{self.directive} {self.headers[1][0]};\n" + "}\n" + ), + headers=[("x-my-hdr", "original text")], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) + else: + self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) + + return client, server class TestRespDelHeader(TestReqDelHeader): - directive = "resp_hdr_set" + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "\r\n" + ) - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain = chains.proxy() - for (name, val) in orig_hdrs: - chain.server_response.headers[name] = val - chain.response.headers[name] = val - for (name, val) in hdrs: - del chain.response.headers[name] - chain.server_response.update() - chain.response.update() - self.msg_chain = [chain] + directive = "resp_hdr_set" class TestCachedRespDelHeader(TestRespDelHeader): - """Response is served from cache.""" - - def make_def_config(self): - self.config = "cache 2;\n" "cache_fulfill * *;\n" - - def make_chain(self, hdrs): - orig_hdrs = [("X-My-Hdr", "original text"), ("X-My-Hdr-2", "other original text")] - chain_p = chains.proxy() - chain_c = chains.cache() - for (name, val) in orig_hdrs: - chain_p.server_response.headers[name] = val - chain_p.response.headers[name] = val - chain_c.response.headers[name] = val - for (name, val) in hdrs: - del chain_p.response.headers[name] - del chain_c.response.headers[name] - chain_p.server_response.update() - chain_p.response.update() - chain_c.response.update() - self.msg_chain = [chain_p, chain_c] + cache = True + steps = 2 # TODO: add tests for different vhosts, when vhosts will be implemented. From 555b73ce9f83a4a457e22f517c1f1f78d977c242 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 27 Feb 2023 17:28:36 +0400 Subject: [PATCH 02/14] parameterized vhost tests for h2 connection. --- vhost/test_add_hdr_h2.py | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 vhost/test_add_hdr_h2.py diff --git a/vhost/test_add_hdr_h2.py b/vhost/test_add_hdr_h2.py new file mode 100644 index 000000000..64b64693f --- /dev/null +++ b/vhost/test_add_hdr_h2.py @@ -0,0 +1,75 @@ +"""Functional tests for adding user difined headers in h2 connection.""" + +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from vhost import test_add_hdr + + +class H2Config: + clients = [ + { + "id": "deproxy-1", + "type": "deproxy_h2", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, + }, + ] + request = [ + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ] + + +class TestReqAddHeaderH2(H2Config, test_add_hdr.TestReqAddHeader): + pass + + +class TestRespAddHeaderH2(H2Config, test_add_hdr.TestRespAddHeader): + pass + + +class TestCachedRespAddHeaderH2(H2Config, test_add_hdr.TestCachedRespAddHeader): + pass + + +class TestReqSetHeaderH2(H2Config, test_add_hdr.TestReqSetHeader): + request = [ + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("x-my-hdr", "original text"), + ("x-my-hdr-2", "other original text"), + ] + + +class TestRespSetHeaderH2(H2Config, test_add_hdr.TestRespSetHeader): + pass + + +class TestCachedRespSetHeaderH2(H2Config, test_add_hdr.TestCachedRespSetHeader): + pass + + +class TestReqDelHeaderH2(H2Config, test_add_hdr.TestReqDelHeader): + request = [ + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("x-my-hdr", "original text"), + ("x-my-hdr-2", "other original text"), + ] + + +class TestRespDelHeaderH2(H2Config, test_add_hdr.TestRespDelHeader): + pass + + +class TestCachedRespDelHeaderH2(H2Config, test_add_hdr.TestCachedRespDelHeader): + pass From b155d1fde1b019e88d103234f985bc554ca840ee Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 28 Feb 2023 16:58:51 +0400 Subject: [PATCH 03/14] renamed directory and files - old name does not reflect essence of tests. Now all tests are inherited from base class - AddHeaderBase. These directives and other config tests are separated. --- t_modify_http_headers/__init__.py | 3 + t_modify_http_headers/test_common_config.py | 184 ++++++++ .../test_common_config_h2.py | 39 ++ t_modify_http_headers/utils.py | 92 ++++ vhost/__init__.py | 3 - vhost/test_add_hdr.py | 401 ------------------ vhost/test_add_hdr_h2.py | 75 ---- 7 files changed, 318 insertions(+), 479 deletions(-) create mode 100644 t_modify_http_headers/__init__.py create mode 100644 t_modify_http_headers/test_common_config.py create mode 100644 t_modify_http_headers/test_common_config_h2.py create mode 100644 t_modify_http_headers/utils.py delete mode 100644 vhost/__init__.py delete mode 100644 vhost/test_add_hdr.py delete mode 100644 vhost/test_add_hdr_h2.py diff --git a/t_modify_http_headers/__init__.py b/t_modify_http_headers/__init__.py new file mode 100644 index 000000000..27a2bd736 --- /dev/null +++ b/t_modify_http_headers/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["test_common_config", "test_common_config_h2"] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/t_modify_http_headers/test_common_config.py b/t_modify_http_headers/test_common_config.py new file mode 100644 index 000000000..1f7e93cf7 --- /dev/null +++ b/t_modify_http_headers/test_common_config.py @@ -0,0 +1,184 @@ +"""Functional tests for adding user difined headers.""" + +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2017-2023 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from t_modify_http_headers.utils import AddHeaderBase + + +class TestReqAddHeader(AddHeaderBase): + cache = False + directive = "req_hdr_add" + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "\r\n" + ) + + def test_add_one_hdr(self): + client, server = self.base_scenario( + config=f'{self.directive} x-my-hdr "some text";\n', + expected_headers=[("x-my-hdr", "some text")], + ) + return client, server + + def test_add_some_hdrs(self): + client, server = self.base_scenario( + config=( + f'{self.directive} x-my-hdr "some text";\n' + f'{self.directive} x-my-hdr-2 "some other text";\n' + ), + expected_headers=[("x-my-hdr", "some text"), ("x-my-hdr-2", "some other text")], + ) + return client, server + + def test_add_some_hdrs_custom_location(self): + client, server = self.base_scenario( + config=( + 'location prefix "/" {\n' + f'{self.directive} x-my-hdr "some text";\n' + f'{self.directive} x-my-hdr-2 "some other text";\n' + "}\n" + ), + expected_headers=[("x-my-hdr", "some text"), ("x-my-hdr-2", "some other text")], + ) + return client, server + + def test_add_hdrs_derive_config(self): + client, server = self.base_scenario( + config=(f'{self.directive} x-my-hdr "some text";\n' 'location prefix "/" {}\n'), + expected_headers=[("x-my-hdr", "some text")], + ) + return client, server + + def test_add_hdrs_override_config(self): + client, server = self.base_scenario( + config=( + f'{self.directive} x-my-hdr "some text";\n' + 'location prefix "/" {\n' + f'{self.directive} x-my-hdr-2 "some other text";\n' + "}\n" + ), + expected_headers=[("x-my-hdr-2", "some other text")], + ) + + if self.directive == "req_hdr_add": + self.assertNotIn(("x-my-hdr", "some text"), server.last_request.headers.items()) + else: + self.assertNotIn(("x-my-hdr", "some text"), client.last_response.headers.items()) + return client, server + + +class TestRespAddHeader(TestReqAddHeader): + directive = "resp_hdr_add" + + +class TestCachedRespAddHeader(TestReqAddHeader): + cache = True + + +class TestReqSetHeader(TestReqAddHeader): + directive = "req_hdr_set" + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "\r\n" + ) + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr-2: other original text\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + def test_add_one_hdr(self): + client, server = super(TestReqSetHeader, self).test_add_one_hdr() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + + def test_add_some_hdrs(self): + client, server = super(TestReqSetHeader, self).test_add_some_hdrs() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_some_hdrs_custom_location(self): + client, server = super(TestReqSetHeader, self).test_add_some_hdrs_custom_location() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_hdrs_derive_config(self): + client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() + + if self.directive == "req_hdr_set": + self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + def test_add_hdrs_override_config(self): + client, server = super(TestReqSetHeader, self).test_add_hdrs_override_config() + + if self.directive == "req_hdr_set": + self.assertIn(("x-my-hdr", "original text"), server.last_request.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), server.last_request.headers.items() + ) + else: + self.assertIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + self.assertNotIn( + ("x-my-hdr-2", "other original text"), client.last_response.headers.items() + ) + + +class TestRespSetHeader(TestReqSetHeader): + directive = "resp_hdr_set" + + +class TestCachedRespSetHeader(TestRespSetHeader): + cache = True + + +# TODO: add tests for different vhosts, when vhosts will be implemented. diff --git a/t_modify_http_headers/test_common_config_h2.py b/t_modify_http_headers/test_common_config_h2.py new file mode 100644 index 000000000..03abe3873 --- /dev/null +++ b/t_modify_http_headers/test_common_config_h2.py @@ -0,0 +1,39 @@ +"""Functional tests for adding user difined headers in h2 connection.""" + +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from t_modify_http_headers import test_common_config +from t_modify_http_headers.utils import H2Config + + +class TestReqAddHeaderH2(H2Config, test_common_config.TestReqAddHeader): + pass + + +class TestRespAddHeaderH2(H2Config, test_common_config.TestRespAddHeader): + pass + + +class TestCachedRespAddHeaderH2(H2Config, test_common_config.TestCachedRespAddHeader): + pass + + +class TestReqSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): + request = [ + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("x-my-hdr", "original text"), + ("x-my-hdr-2", "other original text"), + ] + + +class TestRespSetHeaderH2(H2Config, test_common_config.TestRespSetHeader): + pass + + +class TestCachedRespSetHeaderH2(H2Config, test_common_config.TestCachedRespSetHeader): + pass diff --git a/t_modify_http_headers/utils.py b/t_modify_http_headers/utils.py new file mode 100644 index 000000000..298b74410 --- /dev/null +++ b/t_modify_http_headers/utils.py @@ -0,0 +1,92 @@ +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from framework import tester + + +class AddHeaderBase(tester.TempestaTest): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2; + +server ${server_ip}:8000; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + clients = [ + { + "id": "deproxy-1", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", + }, + ] + + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + request: str or list + cache: bool + directive: str + + def update_tempesta_config(self, config: str): + tempesta = self.get_tempesta() + cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" + tempesta.config.defconfig += config + "\n" + cache + + def base_scenario(self, config: str, expected_headers: list): + client = self.get_client("deproxy-1") + server = self.get_server("deproxy") + + self.update_tempesta_config(config=config) + + self.start_all_services() + + for _ in range(2 if self.cache else 1): + client.send_request(self.request, "200") + + for header in expected_headers: + if self.directive in ["req_hdr_set", "req_hdr_add"]: + self.assertIn(header, server.last_request.headers.items()) + self.assertNotIn(header, client.last_response.headers.items()) + else: + self.assertIn(header, client.last_response.headers.items()) + self.assertNotIn(header, server.last_request.headers.items()) + + return client, server + + +class H2Config: + clients = [ + { + "id": "deproxy-1", + "type": "deproxy_h2", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, + }, + ] + request = [ + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ] diff --git a/vhost/__init__.py b/vhost/__init__.py deleted file mode 100644 index 77dc3ee24..000000000 --- a/vhost/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ["test_add_hdr"] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/vhost/test_add_hdr.py b/vhost/test_add_hdr.py deleted file mode 100644 index b451e6334..000000000 --- a/vhost/test_add_hdr.py +++ /dev/null @@ -1,401 +0,0 @@ -"""Functional tests for adding user difined headers.""" - -__author__ = "Tempesta Technologies, Inc." -__copyright__ = "Copyright (C) 2017-2023 Tempesta Technologies, Inc." -__license__ = "GPL2" - -from framework import tester - - -class TestReqAddHeader(tester.TempestaTest): - tempesta = { - "config": """ -listen 80; -listen 443 proto=h2; - -server ${server_ip}:8000; - -tls_certificate ${tempesta_workdir}/tempesta.crt; -tls_certificate_key ${tempesta_workdir}/tempesta.key; -tls_match_any_server_name; -""", - } - - clients = [ - { - "id": "deproxy-1", - "type": "deproxy", - "addr": "${tempesta_ip}", - "port": "80", - }, - ] - - backends = [ - { - "id": "deproxy", - "type": "deproxy", - "port": "8000", - "response": "static", - "response_content": ( - "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" - + "Content-Length: 0\r\n\r\n" - ), - } - ] - - request = ( - f"GET / HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "\r\n" - ) - headers = [("x-my-hdr", "some text"), ("x-my-hdr-2", "some other text")] - cache = False - directive = "req_hdr_add" - steps = 1 - - def update_tempesta_config(self, config: str): - tempesta = self.get_tempesta() - - cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" - - tempesta.config.defconfig += config + "\n" + cache - - def base_scenario(self, config: str, headers: list): - client = self.get_client("deproxy-1") - server = self.get_server("deproxy") - - self.update_tempesta_config(config=config) - - self.start_all_services() - - for _ in range(self.steps): - client.send_request(self.request, "200") - - for header in headers: - if self.directive in ["req_hdr_set", "req_hdr_add"]: - self.assertIn(header, server.last_request.headers.items()) - self.assertNotIn(header, client.last_response.headers.items()) - else: - self.assertIn(header, client.last_response.headers.items()) - self.assertNotIn(header, server.last_request.headers.items()) - - return client, server - - def test_add_one_hdr(self): - client, server = self.base_scenario( - config=(f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n'), - headers=[self.headers[0]], - ) - return client, server - - def test_add_some_hdrs(self): - client, server = self.base_scenario( - config=( - f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' - f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' - ), - headers=self.headers, - ) - return client, server - - def test_add_some_hdrs_custom_location(self): - client, server = self.base_scenario( - config=( - 'location prefix "/" {\n' - f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' - f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' - "}\n" - ), - headers=self.headers, - ) - return client, server - - def test_add_hdrs_derive_config(self): - client, server = self.base_scenario( - config=( - f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' - 'location prefix "/" {}\n' - ), - headers=[self.headers[0]], - ) - return client, server - - def test_add_hdrs_override_config(self): - client, server = self.base_scenario( - config=( - f'{self.directive} {self.headers[0][0]} "{self.headers[0][1]}";\n' - 'location prefix "/" {\n' - f'{self.directive} {self.headers[1][0]} "{self.headers[1][1]}";\n' - "}\n" - ), - headers=[self.headers[1]], - ) - - if self.directive == "req_hdr_add": - self.assertNotIn(self.headers[0], server.last_request.headers.items()) - else: - self.assertNotIn(self.headers[0], client.last_response.headers.items()) - - return client, server - - -class TestRespAddHeader(TestReqAddHeader): - - directive = "resp_hdr_add" - - -class TestCachedRespAddHeader(TestReqAddHeader): - cache = True - directive = "resp_hdr_add" - steps = 2 - - -class TestReqSetHeader(TestReqAddHeader): - - directive = "req_hdr_set" - - request = ( - f"GET / HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr-2: other original text\r\n" - + "\r\n" - ) - - backends = [ - { - "id": "deproxy", - "type": "deproxy", - "port": "8000", - "response": "static", - "response_content": ( - "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr-2: other original text\r\n" - + "Content-Length: 0\r\n\r\n" - ), - } - ] - - def test_add_one_hdr(self): - client, server = super(TestReqSetHeader, self).test_add_one_hdr() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) - - def test_add_some_hdrs(self): - client, server = super(TestReqSetHeader, self).test_add_some_hdrs() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) - - def test_add_some_hdrs_custom_location(self): - client, server = super(TestReqSetHeader, self).test_add_some_hdrs_custom_location() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) - - def test_add_hdrs_derive_config(self): - client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) - - def test_add_hdrs_override_config(self): - client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) - - -class TestRespSetHeader(TestReqSetHeader): - - directive = "resp_hdr_set" - - -class TestCachedRespSetHeader(TestRespSetHeader): - cache = True - steps = 2 - - -class TestReqDelHeader(TestReqAddHeader): - - directive = "req_hdr_set" - - request = ( - f"GET / HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr-2: other original text\r\n" - + "\r\n" - ) - - def test_add_one_hdr(self): - client, server = self.base_scenario( - config=(f"{self.directive} {self.headers[0][0]};\n"), - headers=[("x-my-hdr-2", "other original text")], - ) - - if self.directive == "req_hdr_set": - self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) - else: - self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) - - return client, server - - def test_add_some_hdrs(self): - client, server = self.base_scenario( - config=( - f"{self.directive} {self.headers[0][0]};\n" - f"{self.directive} {self.headers[1][0]};\n" - ), - headers=[], - ) - - if self.directive == "req_hdr_set": - self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) - self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) - else: - self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) - self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) - - return client, server - - def test_add_some_hdrs_custom_location(self): - client, server = self.base_scenario( - config=( - 'location prefix "/" {\n' - f"{self.directive} {self.headers[0][0]};\n" - f"{self.directive} {self.headers[1][0]};\n" - "}\n" - ), - headers=[], - ) - - if self.directive == "req_hdr_set": - self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) - self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) - else: - self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) - self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) - - return client, server - - def test_add_hdrs_derive_config(self): - client, server = self.base_scenario( - config=(f"{self.directive} {self.headers[0][0]};\n" 'location prefix "/" {}\n'), - headers=[("x-my-hdr-2", "other original text")], - ) - - if self.directive == "req_hdr_set": - self.assertNotIn(self.headers[0][0], server.last_request.headers.keys()) - else: - self.assertNotIn(self.headers[0][0], client.last_response.headers.keys()) - - return client, server - - def test_add_hdrs_override_config(self): - client, server = self.base_scenario( - config=( - f"{self.directive} {self.headers[0][0]};\n" - 'location prefix "/" {\n' - f"{self.directive} {self.headers[1][0]};\n" - "}\n" - ), - headers=[("x-my-hdr", "original text")], - ) - - if self.directive == "req_hdr_set": - self.assertNotIn(self.headers[1][0], server.last_request.headers.keys()) - else: - self.assertNotIn(self.headers[1][0], client.last_response.headers.keys()) - - return client, server - - -class TestRespDelHeader(TestReqDelHeader): - - backends = [ - { - "id": "deproxy", - "type": "deproxy", - "port": "8000", - "response": "static", - "response_content": ( - "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr-2: other original text\r\n" - + "Content-Length: 0\r\n\r\n" - ), - } - ] - - request = ( - f"GET / HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "\r\n" - ) - - directive = "resp_hdr_set" - - -class TestCachedRespDelHeader(TestRespDelHeader): - cache = True - steps = 2 - - -# TODO: add tests for different vhosts, when vhosts will be implemented. diff --git a/vhost/test_add_hdr_h2.py b/vhost/test_add_hdr_h2.py deleted file mode 100644 index 64b64693f..000000000 --- a/vhost/test_add_hdr_h2.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Functional tests for adding user difined headers in h2 connection.""" - -__author__ = "Tempesta Technologies, Inc." -__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." -__license__ = "GPL2" - -from vhost import test_add_hdr - - -class H2Config: - clients = [ - { - "id": "deproxy-1", - "type": "deproxy_h2", - "addr": "${tempesta_ip}", - "port": "443", - "ssl": True, - }, - ] - request = [ - (":authority", "example.com"), - (":path", "/"), - (":scheme", "https"), - (":method", "GET"), - ] - - -class TestReqAddHeaderH2(H2Config, test_add_hdr.TestReqAddHeader): - pass - - -class TestRespAddHeaderH2(H2Config, test_add_hdr.TestRespAddHeader): - pass - - -class TestCachedRespAddHeaderH2(H2Config, test_add_hdr.TestCachedRespAddHeader): - pass - - -class TestReqSetHeaderH2(H2Config, test_add_hdr.TestReqSetHeader): - request = [ - (":authority", "example.com"), - (":path", "/"), - (":scheme", "https"), - (":method", "GET"), - ("x-my-hdr", "original text"), - ("x-my-hdr-2", "other original text"), - ] - - -class TestRespSetHeaderH2(H2Config, test_add_hdr.TestRespSetHeader): - pass - - -class TestCachedRespSetHeaderH2(H2Config, test_add_hdr.TestCachedRespSetHeader): - pass - - -class TestReqDelHeaderH2(H2Config, test_add_hdr.TestReqDelHeader): - request = [ - (":authority", "example.com"), - (":path", "/"), - (":scheme", "https"), - (":method", "GET"), - ("x-my-hdr", "original text"), - ("x-my-hdr-2", "other original text"), - ] - - -class TestRespDelHeaderH2(H2Config, test_add_hdr.TestRespDelHeader): - pass - - -class TestCachedRespDelHeaderH2(H2Config, test_add_hdr.TestCachedRespDelHeader): - pass From cc588bc7d79a577d5c5038096794af63825050d1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 1 Mar 2023 12:41:29 +0400 Subject: [PATCH 04/14] added tests for base logic. --- t_modify_http_headers/test_logic.py | 167 ++++++++++++++++++++++++++++ t_modify_http_headers/utils.py | 14 ++- 2 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 t_modify_http_headers/test_logic.py diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py new file mode 100644 index 000000000..fdcc85f52 --- /dev/null +++ b/t_modify_http_headers/test_logic.py @@ -0,0 +1,167 @@ +"""Functional tests of header modification logic.""" + +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from t_modify_http_headers.utils import AddHeaderBase, H2Config + + +class TestReqHeader(AddHeaderBase): + directive = "req" + cache = False + request = ( + f"GET / HTTP/1.1\r\n" + + "host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "Content-Encoding: gzip\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr: original text\r\n" + + "\r\n" + ) + + def test_set_non_exist_header(self): + """ + Header will be added in request/response if it is missing from base request/response. + """ + self.directive = f"{self.directive}_hdr_set" + self.base_scenario( + config=f'{self.directive} non-exist-header "qwe";\n', + expected_headers=[("non-exist-header", "qwe")], + ) + + def test_delete_headers(self): + """ + Headers must be removed from base request/response if header is in base request/response. + """ + self.directive = f"{self.directive}_hdr_set" + client, server = self.base_scenario( + config=f"{self.directive} x-my-hdr;\n", + expected_headers=[], + ) + + if self.directive == "req_hdr_set": + self.assertNotIn("x-my-hdr", server.last_request.headers.keys()) + else: + self.assertNotIn("x-my-hdr", client.last_response.headers.keys()) + + def test_delete_non_exist_header(self): + """Request/response does not modify if header is missing from base request/response.""" + self.directive = f"{self.directive}_hdr_set" + self.base_scenario( + config=f"{self.directive} non-exist-header;\n", + expected_headers=[("x-my-hdr", "original text")], + ) + + def test_overwrite_raw_header(self): + """New value for header must be added to old value as list.""" + self.directive = f"{self.directive}_hdr_add" + client, server = self.base_scenario( + config=f'{self.directive} x-my-hdr "some text";\n', + expected_headers=[("x-my-hdr", "original text, some text")], + ) + + if self.directive == "req_hdr_add": + self.assertNotIn( + "original text", list(server.last_request.headers.find_all("x-my-hdr")) + ) + else: + self.assertNotIn( + "original text", + list(client.last_response.headers.find_all("x-my-hdr")), + ) + + def test_overwrite_singular_header(self): + """New value for header must not be added.""" + self.directive = f"{self.directive}_hdr_add" + client, server = self.base_scenario( + config=f'{self.directive} host "tempesta";\n', + expected_headers=[("host", "localhost")], + ) + + self.assertNotIn("tempesta", list(server.last_request.headers.find_all("host"))) + + def test_overwrite_non_singular_header(self): + """New value for header must be added to old value as list.""" + self.directive = f"{self.directive}_hdr_add" + client, server = self.base_scenario( + config=f'{self.directive} Content-Encoding "br";\n', + expected_headers=[("content-encoding", "gzip, br")], + ) + if self.directive == "req_hdr_add": + self.assertNotIn("gzip", list(server.last_request.headers.find_all("content-encoding"))) + else: + self.assertNotIn("gzip", list(client.last_response.headers.items("content-encoding"))) + + def test_add_exist_header(self): + """Header will be modified if 'header-value' is in base request/response.""" + self.directive = f"{self.directive}_hdr_add" + self.base_scenario( + config=f'{self.directive} x-my-hdr "original text";\n', + expected_headers=[("x-my-hdr", "original text, original text")], + ) + + +class TestRespHeader(TestReqHeader): + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "x-my-hdr: original text\r\n" + + "x-my-hdr: original text\r\n" + + 'Etag: "qwe"\r\n' + + "Content-Encoding: gzip\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "\r\n" + ) + directive = "resp" + + def test_overwrite_singular_header(self): + """New value for header must not be added.""" + self.directive = f"{self.directive}_hdr_add" + client, server = self.base_scenario( + config=f'{self.directive} Etag "asd";\n', + expected_headers=[("Etag", '"qwe"')], + ) + + self.assertNotIn('"asd"', list(client.last_response.headers.find_all("Etag"))) + self.assertNotIn('"qwe", "asd"', list(client.last_response.headers.find_all("Etag"))) + + +class TestCachedRespHeader(TestRespHeader): + cache = True + + +class TestReqHeaderH2(H2Config, TestReqHeader): + request = [ + (":authority", "localhost"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("x-my-hdr", "original text"), + ("x-my-hdr", "original text"), + ("content-encoding", "gzip"), + ] + + +class TestRespHeaderH2(H2Config, TestRespHeader): + pass + + +class TestCachedRespHeaderH2(H2Config, TestCachedRespHeader): + pass diff --git a/t_modify_http_headers/utils.py b/t_modify_http_headers/utils.py index 298b74410..1e6ca9b0f 100644 --- a/t_modify_http_headers/utils.py +++ b/t_modify_http_headers/utils.py @@ -65,11 +65,15 @@ def base_scenario(self, config: str, expected_headers: list): for header in expected_headers: if self.directive in ["req_hdr_set", "req_hdr_add"]: - self.assertIn(header, server.last_request.headers.items()) - self.assertNotIn(header, client.last_response.headers.items()) + self.assertIn(header[1], list(server.last_request.headers.find_all(header[0]))) + self.assertNotIn( + header[1], list(client.last_response.headers.find_all(header[0])) + ) else: - self.assertIn(header, client.last_response.headers.items()) - self.assertNotIn(header, server.last_request.headers.items()) + self.assertIn(header[1], list(client.last_response.headers.find_all(header[0]))) + self.assertNotIn( + header[1], list(server.last_request.headers.find_all(header[0])) + ) return client, server @@ -85,7 +89,7 @@ class H2Config: }, ] request = [ - (":authority", "example.com"), + (":authority", "localhost"), (":path", "/"), (":scheme", "https"), (":method", "GET"), From 36702eabb0d07a3e19443d3ab5a0f56aa77108a6 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 1 Mar 2023 16:51:53 +0400 Subject: [PATCH 05/14] added tests: - set large header; - multiple appending to same header; - multiple replacing; - add header from static table; - add header from dynamic table; --- t_modify_http_headers/test_logic.py | 61 ++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index fdcc85f52..5dda66c54 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -102,6 +102,42 @@ def test_add_exist_header(self): expected_headers=[("x-my-hdr", "original text, original text")], ) + def test_add_large_header(self): + self.directive = f"{self.directive}_hdr_set" + self.base_scenario( + config=f'{self.directive} x-my-hdr "{"12345" * 2000}";\n', + expected_headers=[("x-my-hdr", "12345" * 2000)], + ) + + def test_multiple_appending_same_header(self): + """All headers must be added in request/response as list.""" + self.directive = f"{self.directive}_hdr_add" + self.base_scenario( + config=( + f'{self.directive} x-my-hdr-2 "first text";\n' + f'{self.directive} x-my-hdr-2 "second text";\n' + f'{self.directive} x-my-hdr-2 "third text";\n' + ), + expected_headers=[("x-my-hdr-2", "first text, second text, third text")], + ) + + def test_multiple_replacing_same_header(self): + """Only last header must be added in request/response.""" + self.directive = f"{self.directive}_hdr_set" + client, server = self.base_scenario( + config=( + f'{self.directive} x-my-hdr-2 "first text";\n' + f'{self.directive} x-my-hdr-2 "second text";\n' + f'{self.directive} x-my-hdr-2 "third text";\n' + ), + expected_headers=[("x-my-hdr-2", "third text")], + ) + + if self.directive == "req_hdr_set": + self.assertEqual(1, len(list(server.last_request.headers.find_all("x-my-hdr-2")))) + else: + self.assertEqual(1, len(list(client.last_response.headers.find_all("x-my-hdr-2")))) + class TestRespHeader(TestReqHeader): backends = [ @@ -160,7 +196,30 @@ class TestReqHeaderH2(H2Config, TestReqHeader): class TestRespHeaderH2(H2Config, TestRespHeader): - pass + def test_add_header_from_static_table(self): + """Tempesta must add header from static table as byte.""" + self.directive = f"{self.directive}_hdr_add" + client, server = self.base_scenario( + config=f'{self.directive} cache-control "no-cache";\n', + expected_headers=[("cache-control", "no-cache")], + ) + + self.assertIn(b"X/08no-cache", client.response_buffer) + + def test_add_header_from_dynamic_table(self): + """Tempesta must add header from dynamic table for second response.""" + self.directive = f"{self.directive}_hdr_add" + self.update_tempesta_config(config=f'{self.directive} x-my-hdr-3 "text";\n') + self.start_all_services() + + client = self.get_client("deproxy-1") + + client.send_request(self.request, "200") + self.assertIn(b"@\nx-my-hdr-3\x04text", client.response_buffer) + + client.send_request(self.request, "200") + self.assertNotIn(b"@\nx-my-hdr-3\x04text", client.response_buffer) + self.assertIn(b"\xbe", client.response_buffer) class TestCachedRespHeaderH2(H2Config, TestCachedRespHeader): From fac91cd4dc6f7280e2af012019d42689baf2824b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 2 Mar 2023 16:33:26 +0400 Subject: [PATCH 06/14] updated tests: - test_overwrite_non_singular_header - singular header must not change. - other - added more checks. --- t_modify_http_headers/__init__.py | 2 +- t_modify_http_headers/test_logic.py | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/t_modify_http_headers/__init__.py b/t_modify_http_headers/__init__.py index 27a2bd736..b4a696142 100644 --- a/t_modify_http_headers/__init__.py +++ b/t_modify_http_headers/__init__.py @@ -1,3 +1,3 @@ -__all__ = ["test_common_config", "test_common_config_h2"] +__all__ = ["test_common_config", "test_common_config_h2", "test_logic"] # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index 5dda66c54..3521017b7 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -83,26 +83,34 @@ def test_overwrite_singular_header(self): self.assertNotIn("tempesta", list(server.last_request.headers.find_all("host"))) def test_overwrite_non_singular_header(self): - """New value for header must be added to old value as list.""" + """New value for header must not be added.""" self.directive = f"{self.directive}_hdr_add" client, server = self.base_scenario( config=f'{self.directive} Content-Encoding "br";\n', - expected_headers=[("content-encoding", "gzip, br")], + expected_headers=[("content-encoding", "gzip")], ) if self.directive == "req_hdr_add": - self.assertNotIn("gzip", list(server.last_request.headers.find_all("content-encoding"))) + self.assertNotIn("br", list(server.last_request.headers.find_all("content-encoding"))) else: - self.assertNotIn("gzip", list(client.last_response.headers.items("content-encoding"))) + self.assertNotIn("br", list(client.last_response.headers.find_all("content-encoding"))) def test_add_exist_header(self): """Header will be modified if 'header-value' is in base request/response.""" self.directive = f"{self.directive}_hdr_add" - self.base_scenario( + client, server = self.base_scenario( config=f'{self.directive} x-my-hdr "original text";\n', expected_headers=[("x-my-hdr", "original text, original text")], ) + if self.directive == "req_hdr_add": + self.assertNotIn( + "original text", list(server.last_request.headers.find_all("x-my-hdr")) + ) + else: + self.assertNotIn( + "original text", list(client.last_response.headers.find_all("x-my-hdr")) + ) - def test_add_large_header(self): + def test_set_large_header(self): self.directive = f"{self.directive}_hdr_set" self.base_scenario( config=f'{self.directive} x-my-hdr "{"12345" * 2000}";\n', @@ -176,6 +184,7 @@ def test_overwrite_singular_header(self): ) self.assertNotIn('"asd"', list(client.last_response.headers.find_all("Etag"))) + self.assertNotIn("asd", list(client.last_response.headers.find_all("Etag"))) self.assertNotIn('"qwe", "asd"', list(client.last_response.headers.find_all("Etag"))) @@ -189,6 +198,7 @@ class TestReqHeaderH2(H2Config, TestReqHeader): (":path", "/"), (":scheme", "https"), (":method", "GET"), + ("host", "localhost"), ("x-my-hdr", "original text"), ("x-my-hdr", "original text"), ("content-encoding", "gzip"), @@ -222,5 +232,5 @@ def test_add_header_from_dynamic_table(self): self.assertIn(b"\xbe", client.response_buffer) -class TestCachedRespHeaderH2(H2Config, TestCachedRespHeader): - pass +class TestCachedRespHeaderH2(TestRespHeaderH2): + cache = True From 1dffca94d0c10ed8969044db00911c92f076ed44 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 May 2023 17:35:38 +0400 Subject: [PATCH 07/14] reworked tests - old version is too hard. Added tests: - adding non-exist header for `..._hdr_add`; - removing one and many headers; - removing special headers; - removing non-exist header; - h2 request tests with dynamic table; Removed `test_multiple_replacing_same_header`, `test_overwrite_non_singular_header`, `test_overwrite_singular_header`, `test_overwrite_raw_header` tests because now it is low priority logic. --- t_modify_http_headers/test_logic.py | 353 +++++++++++++++------------- 1 file changed, 196 insertions(+), 157 deletions(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index 3521017b7..d52c566dc 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -4,30 +4,128 @@ __copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." __license__ = "GPL2" -from t_modify_http_headers.utils import AddHeaderBase, H2Config +from framework import tester -class TestReqHeader(AddHeaderBase): - directive = "req" - cache = False - request = ( +def generate_http1_request(optional_headers=[]) -> str: + return ( f"GET / HTTP/1.1\r\n" - + "host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "Content-Encoding: gzip\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr: original text\r\n" + + "Host: localhost\r\n" + + "".join(f"{header[0]}: {header[1]}\r\n" for header in optional_headers) + "\r\n" ) + +def generate_response(optional_headers=[]) -> str: + return ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "".join(f"{header[0]}: {header[1]}\r\n" for header in optional_headers) + + "Content-Length: 0\r\n\r\n" + ) + + +def generate_h2_request(optional_headers=[]) -> list: + return [ + (":authority", "localhost"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ] + optional_headers + + +class TestLogicBase(tester.TempestaTest, base=True): + tempesta = { + "config": """ + listen 80; + listen 443 proto=h2; + + server ${server_ip}:8000; + + tls_certificate ${tempesta_workdir}/tempesta.crt; + tls_certificate_key ${tempesta_workdir}/tempesta.key; + tls_match_any_server_name; + """, + } + + clients = [ + { + "id": "deproxy-1", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", + }, + ] + + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": "", + } + ] + + cache: bool + requests_n: int + directive: str + h2: bool + + def update_tempesta_config(self, config: str): + tempesta = self.get_tempesta() + cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" + tempesta.config.defconfig += config + "\n" + cache + + def base_scenario(self, config: str, optional_headers: list, expected_headers: list): + self.update_tempesta_config(config=config) + self.start_all_services() + + server = self.get_server("deproxy") + client = self.get_client("deproxy-1") + + server.set_response(generate_response(optional_headers)) + + # send 2 requests for checking cache and dynamic table + for _ in range(2 if self.cache else self.requests_n): + client.send_request( + generate_h2_request(optional_headers) + if self.h2 + else generate_http1_request(optional_headers), + "200", + ) + + self.check_response_and_request(expected_headers, client, server) + + return client, server + + def check_response_and_request(self, expected_headers: list, client, server): + for header in expected_headers: + if self.directive == "req": + self.assertIn(header[1], list(server.last_request.headers.find_all(header[0]))) + self.assertNotIn(header[1], list(client.last_response.headers.find_all(header[0]))) + else: + self.assertIn(header[1], list(client.last_response.headers.find_all(header[0]))) + self.assertNotIn(header[1], list(server.last_request.headers.find_all(header[0]))) + def test_set_non_exist_header(self): """ Header will be added in request/response if it is missing from base request/response. """ - self.directive = f"{self.directive}_hdr_set" self.base_scenario( - config=f'{self.directive} non-exist-header "qwe";\n', + config=f'{self.directive}_hdr_set non-exist-header "qwe";\n', + optional_headers=[], + expected_headers=[("non-exist-header", "qwe")], + ) + + def test_add_non_exist_header(self): + """ + Header will be added in request/response if it is missing from base request/response. + """ + self.base_scenario( + config=f'{self.directive}_hdr_add non-exist-header "qwe";\n', + optional_headers=[], expected_headers=[("non-exist-header", "qwe")], ) @@ -35,202 +133,143 @@ def test_delete_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - self.directive = f"{self.directive}_hdr_set" + client, server = self.base_scenario( - config=f"{self.directive} x-my-hdr;\n", + config=f"{self.directive}_hdr_set x-my-hdr;\n", + optional_headers=[("x-my-hdr", "original header")], expected_headers=[], ) - if self.directive == "req_hdr_set": + if self.directive == "req": self.assertNotIn("x-my-hdr", server.last_request.headers.keys()) else: self.assertNotIn("x-my-hdr", client.last_response.headers.keys()) - def test_delete_non_exist_header(self): - """Request/response does not modify if header is missing from base request/response.""" - self.directive = f"{self.directive}_hdr_set" - self.base_scenario( - config=f"{self.directive} non-exist-header;\n", - expected_headers=[("x-my-hdr", "original text")], - ) + def test_delete_many_headers(self): + """ + Headers must be removed from base request/response if header is in base request/response. + """ - def test_overwrite_raw_header(self): - """New value for header must be added to old value as list.""" - self.directive = f"{self.directive}_hdr_add" client, server = self.base_scenario( - config=f'{self.directive} x-my-hdr "some text";\n', - expected_headers=[("x-my-hdr", "original text, some text")], + config=f"{self.directive}_hdr_set x-my-hdr;\n", + optional_headers=[("x-my-hdr", "original header"), ("x-my-hdr", "original header")], + expected_headers=[], ) - if self.directive == "req_hdr_add": - self.assertNotIn( - "original text", list(server.last_request.headers.find_all("x-my-hdr")) - ) + if self.directive == "req": + self.assertNotIn("x-my-hdr", server.last_request.headers.keys()) else: - self.assertNotIn( - "original text", - list(client.last_response.headers.find_all("x-my-hdr")), - ) - - def test_overwrite_singular_header(self): - """New value for header must not be added.""" - self.directive = f"{self.directive}_hdr_add" - client, server = self.base_scenario( - config=f'{self.directive} host "tempesta";\n', - expected_headers=[("host", "localhost")], - ) - - self.assertNotIn("tempesta", list(server.last_request.headers.find_all("host"))) + self.assertNotIn("x-my-hdr", client.last_response.headers.keys()) - def test_overwrite_non_singular_header(self): - """New value for header must not be added.""" - self.directive = f"{self.directive}_hdr_add" + def test_delete_special_headers(self): + """ + Headers must be removed from base request/response if header is in base request/response. + """ client, server = self.base_scenario( - config=f'{self.directive} Content-Encoding "br";\n', - expected_headers=[("content-encoding", "gzip")], + config=f"{self.directive}_hdr_set Etag;\n", + optional_headers=[("Erag", '"qwe"')], + expected_headers=[], ) - if self.directive == "req_hdr_add": - self.assertNotIn("br", list(server.last_request.headers.find_all("content-encoding"))) - else: - self.assertNotIn("br", list(client.last_response.headers.find_all("content-encoding"))) - def test_add_exist_header(self): - """Header will be modified if 'header-value' is in base request/response.""" - self.directive = f"{self.directive}_hdr_add" - client, server = self.base_scenario( - config=f'{self.directive} x-my-hdr "original text";\n', - expected_headers=[("x-my-hdr", "original text, original text")], - ) - if self.directive == "req_hdr_add": - self.assertNotIn( - "original text", list(server.last_request.headers.find_all("x-my-hdr")) - ) + if self.directive == "req": + self.assertNotIn("etag", server.last_request.headers.keys()) else: - self.assertNotIn( - "original text", list(client.last_response.headers.find_all("x-my-hdr")) - ) + self.assertNotIn("etag", client.last_response.headers.keys()) - def test_set_large_header(self): - self.directive = f"{self.directive}_hdr_set" + def test_delete_non_exist_header(self): + """Request/response does not modify if header is missing from base request/response.""" self.base_scenario( - config=f'{self.directive} x-my-hdr "{"12345" * 2000}";\n', - expected_headers=[("x-my-hdr", "12345" * 2000)], + config=f"{self.directive}_hdr_set non-exist-header;\n", + optional_headers=[], + expected_headers=[], ) - def test_multiple_appending_same_header(self): - """All headers must be added in request/response as list.""" - self.directive = f"{self.directive}_hdr_add" + def test_set_large_header(self): self.base_scenario( - config=( - f'{self.directive} x-my-hdr-2 "first text";\n' - f'{self.directive} x-my-hdr-2 "second text";\n' - f'{self.directive} x-my-hdr-2 "third text";\n' - ), - expected_headers=[("x-my-hdr-2", "first text, second text, third text")], - ) - - def test_multiple_replacing_same_header(self): - """Only last header must be added in request/response.""" - self.directive = f"{self.directive}_hdr_set" - client, server = self.base_scenario( - config=( - f'{self.directive} x-my-hdr-2 "first text";\n' - f'{self.directive} x-my-hdr-2 "second text";\n' - f'{self.directive} x-my-hdr-2 "third text";\n' - ), - expected_headers=[("x-my-hdr-2", "third text")], + config=f'{self.directive}_hdr_set x-my-hdr "{"12" * 2000}";\n', + optional_headers=[], + expected_headers=[("x-my-hdr", "12" * 2000)], ) - if self.directive == "req_hdr_set": - self.assertEqual(1, len(list(server.last_request.headers.find_all("x-my-hdr-2")))) - else: - self.assertEqual(1, len(list(client.last_response.headers.find_all("x-my-hdr-2")))) - -class TestRespHeader(TestReqHeader): - backends = [ +class H2Config: + clients = [ { - "id": "deproxy", - "type": "deproxy", - "port": "8000", - "response": "static", - "response_content": ( - "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" - + "x-my-hdr: original text\r\n" - + "x-my-hdr: original text\r\n" - + 'Etag: "qwe"\r\n' - + "Content-Encoding: gzip\r\n" - + "Content-Length: 0\r\n\r\n" - ), - } + "id": "deproxy-1", + "type": "deproxy_h2", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, + }, ] - request = ( - f"GET / HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Connection: keep-alive\r\n" - + "Accept: */*\r\n" - + "\r\n" - ) - directive = "resp" - def test_overwrite_singular_header(self): - """New value for header must not be added.""" - self.directive = f"{self.directive}_hdr_add" - client, server = self.base_scenario( - config=f'{self.directive} Etag "asd";\n', - expected_headers=[("Etag", '"qwe"')], - ) - self.assertNotIn('"asd"', list(client.last_response.headers.find_all("Etag"))) - self.assertNotIn("asd", list(client.last_response.headers.find_all("Etag"))) - self.assertNotIn('"qwe", "asd"', list(client.last_response.headers.find_all("Etag"))) +class TestReqHeader(TestLogicBase): + directive = "req" + cache = False + requests_n = 1 + h2 = False + + +class TestRespHeader(TestLogicBase): + directive = "resp" + cache = False + requests_n = 1 + h2 = False -class TestCachedRespHeader(TestRespHeader): +class TestCachedRespHeader(TestLogicBase): + directive = "resp" cache = True + requests_n = 2 + h2 = False -class TestReqHeaderH2(H2Config, TestReqHeader): - request = [ - (":authority", "localhost"), - (":path", "/"), - (":scheme", "https"), - (":method", "GET"), - ("host", "localhost"), - ("x-my-hdr", "original text"), - ("x-my-hdr", "original text"), - ("content-encoding", "gzip"), - ] +class TestReqHeaderH2(H2Config, TestLogicBase): + directive = "req" + cache = False + requests_n = 2 + h2 = True -class TestRespHeaderH2(H2Config, TestRespHeader): +class TestRespHeaderH2(H2Config, TestLogicBase): + directive = "resp" + cache = False + requests_n = 1 + h2 = True + def test_add_header_from_static_table(self): """Tempesta must add header from static table as byte.""" - self.directive = f"{self.directive}_hdr_add" client, server = self.base_scenario( - config=f'{self.directive} cache-control "no-cache";\n', + config=f'{self.directive}_hdr_set cache-control "no-cache";\n', + optional_headers=[], expected_headers=[("cache-control", "no-cache")], ) - self.assertIn(b"X/08no-cache", client.response_buffer) + self.assertIn(b"\x08no-cache", client.response_buffer) def test_add_header_from_dynamic_table(self): """Tempesta must add header from dynamic table for second response.""" - self.directive = f"{self.directive}_hdr_add" - self.update_tempesta_config(config=f'{self.directive} x-my-hdr-3 "text";\n') + self.update_tempesta_config(config=f'{self.directive}_hdr_set x-my-hdr "text";\n') self.start_all_services() + optional_headers = [("x-my-hdr", "text")] + request = generate_h2_request(optional_headers) + client = self.get_client("deproxy-1") + server = self.get_server("deproxy") + server.set_response(generate_response(optional_headers)) - client.send_request(self.request, "200") - self.assertIn(b"@\nx-my-hdr-3\x04text", client.response_buffer) + client.send_request(request, "200") + self.assertIn(b"\x08x-my-hdr\x04text", client.response_buffer) - client.send_request(self.request, "200") - self.assertNotIn(b"@\nx-my-hdr-3\x04text", client.response_buffer) + client.send_request(request, "200") + self.assertNotIn(b"\nx-my-hdr\x04text", client.response_buffer) self.assertIn(b"\xbe", client.response_buffer) -class TestCachedRespHeaderH2(TestRespHeaderH2): +class TestCachedRespHeaderH2(H2Config, TestLogicBase): + directive = "resp" cache = True + requests_n = 2 + h2 = True From 532eb7cd0b8c4b1a50b86c8135f0b9c94e3d249b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 May 2023 16:50:46 +0400 Subject: [PATCH 08/14] added tests for two special headers. --- t_modify_http_headers/test_logic.py | 37 ++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index d52c566dc..d313bb4a0 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -133,7 +133,6 @@ def test_delete_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - client, server = self.base_scenario( config=f"{self.directive}_hdr_set x-my-hdr;\n", optional_headers=[("x-my-hdr", "original header")], @@ -142,14 +141,15 @@ def test_delete_headers(self): if self.directive == "req": self.assertNotIn("x-my-hdr", server.last_request.headers.keys()) + self.assertIn("x-my-hdr", client.last_response.headers.keys()) else: self.assertNotIn("x-my-hdr", client.last_response.headers.keys()) + self.assertIn("x-my-hdr", server.last_request.headers.keys()) def test_delete_many_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - client, server = self.base_scenario( config=f"{self.directive}_hdr_set x-my-hdr;\n", optional_headers=[("x-my-hdr", "original header"), ("x-my-hdr", "original header")], @@ -158,23 +158,48 @@ def test_delete_many_headers(self): if self.directive == "req": self.assertNotIn("x-my-hdr", server.last_request.headers.keys()) + self.assertIn("x-my-hdr", client.last_response.headers.keys()) else: self.assertNotIn("x-my-hdr", client.last_response.headers.keys()) + self.assertIn("x-my-hdr", server.last_request.headers.keys()) def test_delete_special_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ + header_name = "set-cookie" if self.directive == "resp" else "if-match" + header_value = "test=cookie" if self.directive == "resp" else '"qwe"' + client, server = self.base_scenario( + config=f"{self.directive}_hdr_set {header_name};\n", + optional_headers=[(header_name, header_value)], + expected_headers=[], + ) + + if self.directive == "req": + self.assertNotIn(header_name, server.last_request.headers.keys()) + self.assertIn(header_name, client.last_response.headers.keys()) + else: + self.assertNotIn(header_name, client.last_response.headers.keys()) + self.assertIn(header_name, server.last_request.headers.keys()) + + def test_delete_many_special_headers(self): + """ + Headers must be removed from base request/response if header is in base request/response. + """ + header_name = "set-cookie" if self.directive == "resp" else "if-match" + header_value = "test=cookie" if self.directive == "resp" else '"qwe"' client, server = self.base_scenario( - config=f"{self.directive}_hdr_set Etag;\n", - optional_headers=[("Erag", '"qwe"')], + config=f"{self.directive}_hdr_set {header_name};\n", + optional_headers=[(header_name, header_value), (header_name, header_value)], expected_headers=[], ) if self.directive == "req": - self.assertNotIn("etag", server.last_request.headers.keys()) + self.assertNotIn(header_name, server.last_request.headers.keys()) + self.assertIn(header_name, client.last_response.headers.keys()) else: - self.assertNotIn("etag", client.last_response.headers.keys()) + self.assertNotIn(header_name, client.last_response.headers.keys()) + self.assertIn(header_name, server.last_request.headers.keys()) def test_delete_non_exist_header(self): """Request/response does not modify if header is missing from base request/response.""" From 350c6df748d9626f0d9123bff2cbefceee267009 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 May 2023 18:54:03 +0400 Subject: [PATCH 09/14] added checks for all headers. --- t_modify_http_headers/test_logic.py | 77 ++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index d313bb4a0..ec9022fde 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -5,12 +5,14 @@ __license__ = "GPL2" from framework import tester +from helpers.deproxy import H2Response, HttpMessage, Request, Response def generate_http1_request(optional_headers=[]) -> str: return ( f"GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + "".join(f"{header[0]}: {header[1]}\r\n" for header in optional_headers) + "\r\n" ) @@ -19,8 +21,8 @@ def generate_http1_request(optional_headers=[]) -> str: def generate_response(optional_headers=[]) -> str: return ( "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" + + f"Date: {HttpMessage.date_time_string()}\r\n" + + "Server: Tempesta FW/pre-0.7.0\r\n" + "".join(f"{header[0]}: {header[1]}\r\n" for header in optional_headers) + "Content-Length: 0\r\n\r\n" ) @@ -96,18 +98,71 @@ def base_scenario(self, config: str, optional_headers: list, expected_headers: l "200", ) - self.check_response_and_request(expected_headers, client, server) + expected_response = self.check_response(optional_headers, expected_headers, client) + expected_request = self.get_expected_request(optional_headers, expected_headers, client) + + self.assertEqual(expected_response, client.last_response) + self.assertEqual(expected_request, server.last_request) return client, server - def check_response_and_request(self, expected_headers: list, client, server): - for header in expected_headers: - if self.directive == "req": - self.assertIn(header[1], list(server.last_request.headers.find_all(header[0]))) - self.assertNotIn(header[1], list(client.last_response.headers.find_all(header[0]))) - else: - self.assertIn(header[1], list(client.last_response.headers.find_all(header[0]))) - self.assertNotIn(header[1], list(server.last_request.headers.find_all(header[0]))) + def check_response( + self, optional_headers: list, expected_headers: list, client + ) -> Response or H2Response: + if client.proto == "h2": + tempesta_headers = [ + ("via", "2.0 tempesta_fw (Tempesta FW pre-0.7.0)"), + ] + else: + tempesta_headers = [ + ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), + ("Connection", "keep-alive"), + ] + + if self.cache: + tempesta_headers.append(("age", client.last_response.headers["age"])) + + if self.directive == "req": + expected_response = generate_response( + optional_headers=tempesta_headers + optional_headers + ) + else: + expected_response = generate_response( + optional_headers=tempesta_headers + expected_headers + ) + + if client.proto == "h2": + expected_response = H2Response( + expected_response.replace("HTTP/1.1 200 OK", ":status: 200") + ) + else: + expected_response = Response(expected_response) + expected_response.set_expected() + return expected_response + + def get_expected_request( + self, optional_headers: list, expected_headers: list, client + ) -> Request: + tempesta_headers = [ + ("X-Forwarded-For", "127.0.0.1"), + ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), + ] + if self.directive == "req": + expected_request = generate_http1_request( + optional_headers=tempesta_headers + expected_headers + ) + else: + expected_request = generate_http1_request( + optional_headers=tempesta_headers + optional_headers + ) + + if client.proto == "h2": + expected_request = expected_request.replace("Connection: keep-alive\r\n", "") + + expected_request = Request(expected_request) + expected_request.set_expected() + + return expected_request def test_set_non_exist_header(self): """ From af48fabe45ca2b4ca1d1b645ae176bd35c375e6b Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 10 May 2023 12:17:58 +0400 Subject: [PATCH 10/14] moved common class and removed utils.py --- t_modify_http_headers/test_common_config.py | 91 +++++++++++++++++- .../test_common_config_h2.py | 44 +++++++-- t_modify_http_headers/utils.py | 96 ------------------- 3 files changed, 120 insertions(+), 111 deletions(-) delete mode 100644 t_modify_http_headers/utils.py diff --git a/t_modify_http_headers/test_common_config.py b/t_modify_http_headers/test_common_config.py index 1f7e93cf7..960690c01 100644 --- a/t_modify_http_headers/test_common_config.py +++ b/t_modify_http_headers/test_common_config.py @@ -4,7 +4,86 @@ __copyright__ = "Copyright (C) 2017-2023 Tempesta Technologies, Inc." __license__ = "GPL2" -from t_modify_http_headers.utils import AddHeaderBase +from framework import tester + + +class AddHeaderBase(tester.TempestaTest): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2; + +server ${server_ip}:8000; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + clients = [ + { + "id": "deproxy-1", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", + }, + ] + + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + request = ( + f"GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: keep-alive\r\n" + + "Accept: */*\r\n" + + "\r\n" + ) + cache: bool + directive: str + + def update_tempesta_config(self, config: str): + tempesta = self.get_tempesta() + cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" + tempesta.config.defconfig += config + "\n" + cache + + def base_scenario(self, config: str, expected_headers: list): + client = self.get_client("deproxy-1") + server = self.get_server("deproxy") + + self.update_tempesta_config(config=config) + + self.start_all_services() + + for _ in range(2 if self.cache else 1): + client.send_request(self.request, "200") + + for header in expected_headers: + if self.directive in ["req_hdr_set", "req_hdr_add"]: + self.assertIn(header[1], list(server.last_request.headers.find_all(header[0]))) + self.assertNotIn( + header[1], list(client.last_response.headers.find_all(header[0])) + ) + else: + self.assertIn(header[1], list(client.last_response.headers.find_all(header[0]))) + self.assertNotIn( + header[1], list(server.last_request.headers.find_all(header[0])) + ) + + return client, server class TestReqAddHeader(AddHeaderBase): @@ -73,15 +152,18 @@ def test_add_hdrs_override_config(self): class TestRespAddHeader(TestReqAddHeader): + cache = False directive = "resp_hdr_add" class TestCachedRespAddHeader(TestReqAddHeader): cache = True + directive = "resp_hdr_add" class TestReqSetHeader(TestReqAddHeader): directive = "req_hdr_set" + cache = False request = ( f"GET / HTTP/1.1\r\n" + "Host: localhost\r\n" @@ -175,10 +257,9 @@ def test_add_hdrs_override_config(self): class TestRespSetHeader(TestReqSetHeader): directive = "resp_hdr_set" + cache = False -class TestCachedRespSetHeader(TestRespSetHeader): +class TestCachedRespSetHeader(TestReqSetHeader): cache = True - - -# TODO: add tests for different vhosts, when vhosts will be implemented. + directive = "resp_hdr_set" diff --git a/t_modify_http_headers/test_common_config_h2.py b/t_modify_http_headers/test_common_config_h2.py index 03abe3873..da89d9520 100644 --- a/t_modify_http_headers/test_common_config_h2.py +++ b/t_modify_http_headers/test_common_config_h2.py @@ -5,22 +5,44 @@ __license__ = "GPL2" from t_modify_http_headers import test_common_config -from t_modify_http_headers.utils import H2Config + + +class H2Config: + clients = [ + { + "id": "deproxy-1", + "type": "deproxy_h2", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, + }, + ] + request = [ + (":authority", "localhost"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ] class TestReqAddHeaderH2(H2Config, test_common_config.TestReqAddHeader): - pass + cache = False + directive = "req_hdr_add" -class TestRespAddHeaderH2(H2Config, test_common_config.TestRespAddHeader): - pass +class TestRespAddHeaderH2(H2Config, test_common_config.TestReqAddHeader): + cache = False + directive = "resp_hdr_add" -class TestCachedRespAddHeaderH2(H2Config, test_common_config.TestCachedRespAddHeader): - pass +class TestCachedRespAddHeaderH2(H2Config, test_common_config.TestReqAddHeader): + cache = True + directive = "resp_hdr_add" class TestReqSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): + cache = False + directive = "req_hdr_set" request = [ (":authority", "example.com"), (":path", "/"), @@ -31,9 +53,11 @@ class TestReqSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): ] -class TestRespSetHeaderH2(H2Config, test_common_config.TestRespSetHeader): - pass +class TestRespSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): + cache = False + directive = "resp_hdr_set" -class TestCachedRespSetHeaderH2(H2Config, test_common_config.TestCachedRespSetHeader): - pass +class TestCachedRespSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): + cache = True + directive = "resp_hdr_set" diff --git a/t_modify_http_headers/utils.py b/t_modify_http_headers/utils.py deleted file mode 100644 index 1e6ca9b0f..000000000 --- a/t_modify_http_headers/utils.py +++ /dev/null @@ -1,96 +0,0 @@ -__author__ = "Tempesta Technologies, Inc." -__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." -__license__ = "GPL2" - -from framework import tester - - -class AddHeaderBase(tester.TempestaTest): - tempesta = { - "config": """ -listen 80; -listen 443 proto=h2; - -server ${server_ip}:8000; - -tls_certificate ${tempesta_workdir}/tempesta.crt; -tls_certificate_key ${tempesta_workdir}/tempesta.key; -tls_match_any_server_name; -""", - } - - clients = [ - { - "id": "deproxy-1", - "type": "deproxy", - "addr": "${tempesta_ip}", - "port": "80", - }, - ] - - backends = [ - { - "id": "deproxy", - "type": "deproxy", - "port": "8000", - "response": "static", - "response_content": ( - "HTTP/1.1 200 OK\r\n" - + "Date: test\r\n" - + "Server: debian\r\n" - + "Content-Length: 0\r\n\r\n" - ), - } - ] - - request: str or list - cache: bool - directive: str - - def update_tempesta_config(self, config: str): - tempesta = self.get_tempesta() - cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" - tempesta.config.defconfig += config + "\n" + cache - - def base_scenario(self, config: str, expected_headers: list): - client = self.get_client("deproxy-1") - server = self.get_server("deproxy") - - self.update_tempesta_config(config=config) - - self.start_all_services() - - for _ in range(2 if self.cache else 1): - client.send_request(self.request, "200") - - for header in expected_headers: - if self.directive in ["req_hdr_set", "req_hdr_add"]: - self.assertIn(header[1], list(server.last_request.headers.find_all(header[0]))) - self.assertNotIn( - header[1], list(client.last_response.headers.find_all(header[0])) - ) - else: - self.assertIn(header[1], list(client.last_response.headers.find_all(header[0]))) - self.assertNotIn( - header[1], list(server.last_request.headers.find_all(header[0])) - ) - - return client, server - - -class H2Config: - clients = [ - { - "id": "deproxy-1", - "type": "deproxy_h2", - "addr": "${tempesta_ip}", - "port": "443", - "ssl": True, - }, - ] - request = [ - (":authority", "localhost"), - (":path", "/"), - (":scheme", "https"), - (":method", "GET"), - ] From 755c2bbe19ebfb2822b32fb0b51586a426e1620c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 11 May 2023 13:44:18 +0400 Subject: [PATCH 11/14] test_logic.py - typo fixed. `test_add_some_hdrs` is duplicate.Removed. --- t_modify_http_headers/test_common_config.py | 89 ++++++------------- .../test_common_config_h2.py | 4 +- t_modify_http_headers/test_logic.py | 6 +- 3 files changed, 32 insertions(+), 67 deletions(-) diff --git a/t_modify_http_headers/test_common_config.py b/t_modify_http_headers/test_common_config.py index 960690c01..239e1add5 100644 --- a/t_modify_http_headers/test_common_config.py +++ b/t_modify_http_headers/test_common_config.py @@ -97,13 +97,6 @@ class TestReqAddHeader(AddHeaderBase): + "\r\n" ) - def test_add_one_hdr(self): - client, server = self.base_scenario( - config=f'{self.directive} x-my-hdr "some text";\n', - expected_headers=[("x-my-hdr", "some text")], - ) - return client, server - def test_add_some_hdrs(self): client, server = self.base_scenario( config=( @@ -190,76 +183,48 @@ class TestReqSetHeader(TestReqAddHeader): } ] - def test_add_one_hdr(self): - client, server = super(TestReqSetHeader, self).test_add_one_hdr() + def header_not_in(self, header): + server = self.get_server("deproxy") + self.assertNotIn(header, server.last_request.headers.items()) - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) + def header_in(self, header): + server = self.get_server("deproxy") + self.assertIn(header, server.last_request.headers.items()) def test_add_some_hdrs(self): - client, server = super(TestReqSetHeader, self).test_add_some_hdrs() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) + super(TestReqSetHeader, self).test_add_some_hdrs() + self.header_not_in(("x-my-hdr", "original text")) + self.header_not_in(("x-my-hdr-2", "other original text")) def test_add_some_hdrs_custom_location(self): - client, server = super(TestReqSetHeader, self).test_add_some_hdrs_custom_location() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) + super(TestReqSetHeader, self).test_add_some_hdrs_custom_location() + self.header_not_in(("x-my-hdr", "original text")) + self.header_not_in(("x-my-hdr-2", "other original text")) def test_add_hdrs_derive_config(self): - client, server = super(TestReqSetHeader, self).test_add_hdrs_derive_config() - - if self.directive == "req_hdr_set": - self.assertNotIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertNotIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) + super(TestReqSetHeader, self).test_add_hdrs_derive_config() + self.header_not_in(("x-my-hdr", "original text")) + self.header_in(("x-my-hdr-2", "other original text")) def test_add_hdrs_override_config(self): - client, server = super(TestReqSetHeader, self).test_add_hdrs_override_config() - - if self.directive == "req_hdr_set": - self.assertIn(("x-my-hdr", "original text"), server.last_request.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), server.last_request.headers.items() - ) - else: - self.assertIn(("x-my-hdr", "original text"), client.last_response.headers.items()) - self.assertNotIn( - ("x-my-hdr-2", "other original text"), client.last_response.headers.items() - ) + super(TestReqSetHeader, self).test_add_hdrs_override_config() + self.header_in(("x-my-hdr", "original text")) + self.header_not_in(("x-my-hdr-2", "other original text")) class TestRespSetHeader(TestReqSetHeader): directive = "resp_hdr_set" cache = False + def header_in(self, header): + client = self.get_client("deproxy-1") + self.assertIn(header, client.last_response.headers.items()) + + def header_not_in(self, header): + client = self.get_client("deproxy-1") + self.assertNotIn(header, client.last_response.headers.items()) + -class TestCachedRespSetHeader(TestReqSetHeader): +class TestCachedRespSetHeader(TestRespSetHeader): cache = True directive = "resp_hdr_set" diff --git a/t_modify_http_headers/test_common_config_h2.py b/t_modify_http_headers/test_common_config_h2.py index da89d9520..be1283015 100644 --- a/t_modify_http_headers/test_common_config_h2.py +++ b/t_modify_http_headers/test_common_config_h2.py @@ -53,11 +53,11 @@ class TestReqSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): ] -class TestRespSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): +class TestRespSetHeaderH2(H2Config, test_common_config.TestRespSetHeader): cache = False directive = "resp_hdr_set" -class TestCachedRespSetHeaderH2(H2Config, test_common_config.TestReqSetHeader): +class TestCachedRespSetHeaderH2(H2Config, test_common_config.TestRespSetHeader): cache = True directive = "resp_hdr_set" diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index ec9022fde..de9cb85ba 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -98,7 +98,7 @@ def base_scenario(self, config: str, optional_headers: list, expected_headers: l "200", ) - expected_response = self.check_response(optional_headers, expected_headers, client) + expected_response = self.get_expected_response(optional_headers, expected_headers, client) expected_request = self.get_expected_request(optional_headers, expected_headers, client) self.assertEqual(expected_response, client.last_response) @@ -106,7 +106,7 @@ def base_scenario(self, config: str, optional_headers: list, expected_headers: l return client, server - def check_response( + def get_expected_response( self, optional_headers: list, expected_headers: list, client ) -> Response or H2Response: if client.proto == "h2": @@ -222,7 +222,7 @@ def test_delete_special_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - header_name = "set-cookie" if self.directive == "resp" else "if-match" + header_name = "set-cookie" if self.directive == "resp" else "if-none-match" header_value = "test=cookie" if self.directive == "resp" else '"qwe"' client, server = self.base_scenario( config=f"{self.directive}_hdr_set {header_name};\n", From f1da420c184a286616af942f7ccddf69acb5c44b Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 16 May 2023 17:54:59 +0400 Subject: [PATCH 12/14] added test with many header modifications. --- t_modify_http_headers/test_logic.py | 242 ++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 68 deletions(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index de9cb85ba..1917265d6 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -37,6 +37,65 @@ def generate_h2_request(optional_headers=[]) -> list: ] + optional_headers +def get_expected_response( + optional_headers: list, expected_headers: list, client, cache: bool, directive: str +) -> Response or H2Response: + if client.proto == "h2": + tempesta_headers = [ + ("via", "2.0 tempesta_fw (Tempesta FW pre-0.7.0)"), + ] + else: + tempesta_headers = [ + ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), + ("Connection", "keep-alive"), + ] + + if cache: + tempesta_headers.append(("age", client.last_response.headers["age"])) + + if directive == "req": + expected_response = generate_response(optional_headers=tempesta_headers + optional_headers) + else: + expected_response = generate_response(optional_headers=tempesta_headers + expected_headers) + + if client.proto == "h2": + expected_response = H2Response(expected_response.replace("HTTP/1.1 200 OK", ":status: 200")) + else: + expected_response = Response(expected_response) + expected_response.set_expected() + return expected_response + + +def get_expected_request( + optional_headers: list, expected_headers: list, client, directive: str +) -> Request: + tempesta_headers = [ + ("X-Forwarded-For", "127.0.0.1"), + ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), + ] + if directive == "req": + expected_request = generate_http1_request( + optional_headers=tempesta_headers + expected_headers + ) + else: + expected_request = generate_http1_request( + optional_headers=tempesta_headers + optional_headers + ) + + if client.proto == "h2": + expected_request = expected_request.replace("Connection: keep-alive\r\n", "") + + expected_request = Request(expected_request) + expected_request.set_expected() + + return expected_request + + +def update_tempesta_config(tempesta, config: str, cache: bool): + cache = "cache 2;\ncache_fulfill * *;\n" if cache else "cache 0;" + tempesta.config.defconfig += config + "\n" + cache + + class TestLogicBase(tester.TempestaTest, base=True): tempesta = { "config": """ @@ -75,13 +134,8 @@ class TestLogicBase(tester.TempestaTest, base=True): directive: str h2: bool - def update_tempesta_config(self, config: str): - tempesta = self.get_tempesta() - cache = "cache 2;\ncache_fulfill * *;\n" if self.cache else "cache 0;" - tempesta.config.defconfig += config + "\n" + cache - def base_scenario(self, config: str, optional_headers: list, expected_headers: list): - self.update_tempesta_config(config=config) + update_tempesta_config(tempesta=self.get_tempesta(), config=config, cache=self.cache) self.start_all_services() server = self.get_server("deproxy") @@ -98,72 +152,20 @@ def base_scenario(self, config: str, optional_headers: list, expected_headers: l "200", ) - expected_response = self.get_expected_response(optional_headers, expected_headers, client) - expected_request = self.get_expected_request(optional_headers, expected_headers, client) + expected_response = get_expected_response( + optional_headers, expected_headers, client, self.cache, self.directive + ) + expected_request = get_expected_request( + optional_headers, expected_headers, client, self.directive + ) + client.last_response.headers.delete_all("Date") + expected_response.headers.delete_all("Date") self.assertEqual(expected_response, client.last_response) self.assertEqual(expected_request, server.last_request) return client, server - def get_expected_response( - self, optional_headers: list, expected_headers: list, client - ) -> Response or H2Response: - if client.proto == "h2": - tempesta_headers = [ - ("via", "2.0 tempesta_fw (Tempesta FW pre-0.7.0)"), - ] - else: - tempesta_headers = [ - ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), - ("Connection", "keep-alive"), - ] - - if self.cache: - tempesta_headers.append(("age", client.last_response.headers["age"])) - - if self.directive == "req": - expected_response = generate_response( - optional_headers=tempesta_headers + optional_headers - ) - else: - expected_response = generate_response( - optional_headers=tempesta_headers + expected_headers - ) - - if client.proto == "h2": - expected_response = H2Response( - expected_response.replace("HTTP/1.1 200 OK", ":status: 200") - ) - else: - expected_response = Response(expected_response) - expected_response.set_expected() - return expected_response - - def get_expected_request( - self, optional_headers: list, expected_headers: list, client - ) -> Request: - tempesta_headers = [ - ("X-Forwarded-For", "127.0.0.1"), - ("via", "1.1 tempesta_fw (Tempesta FW pre-0.7.0)"), - ] - if self.directive == "req": - expected_request = generate_http1_request( - optional_headers=tempesta_headers + expected_headers - ) - else: - expected_request = generate_http1_request( - optional_headers=tempesta_headers + optional_headers - ) - - if client.proto == "h2": - expected_request = expected_request.replace("Connection: keep-alive\r\n", "") - - expected_request = Request(expected_request) - expected_request.set_expected() - - return expected_request - def test_set_non_exist_header(self): """ Header will be added in request/response if it is missing from base request/response. @@ -174,6 +176,24 @@ def test_set_non_exist_header(self): expected_headers=[("non-exist-header", "qwe")], ) + def test_set_exist_header(self): + self.base_scenario( + config=f'{self.directive}_hdr_set exist-header "qwe";\n', + optional_headers=[("exist-header", "123")], + expected_headers=[("exist-header", "qwe")], + ) + + def test_set_exist_special_header(self): + header_name = "set-cookie" if self.directive == "resp" else "if-none-match" + header_value = "test=cookie" if self.directive == "resp" else '"qwe"' + new_hdr_value = '"test=cookie2"' if self.directive == "resp" else r'"\"asd\""' + expected_new_hdr_value = "test=cookie2" if self.directive == "resp" else r'"asd"' + self.base_scenario( + config=f"{self.directive}_hdr_set {header_name} {new_hdr_value};\n", + optional_headers=[(header_name, header_value)], + expected_headers=[(header_name, expected_new_hdr_value)], + ) + def test_add_non_exist_header(self): """ Header will be added in request/response if it is missing from base request/response. @@ -241,7 +261,7 @@ def test_delete_many_special_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - header_name = "set-cookie" if self.directive == "resp" else "if-match" + header_name = "set-cookie" if self.directive == "resp" else "if-none-match" header_value = "test=cookie" if self.directive == "resp" else '"qwe"' client, server = self.base_scenario( config=f"{self.directive}_hdr_set {header_name};\n", @@ -305,6 +325,67 @@ class TestCachedRespHeader(TestLogicBase): h2 = False +class TestManyRequestHeaders(tester.TempestaTest): + tempesta = TestLogicBase.tempesta + + clients = TestLogicBase.clients + + backends = TestLogicBase.backends + + cache = False + directive = "req" + h2 = False + requests_n = 1 + __max_headers = 64 + __headers_n = 64 // 4 + + def test_many_headers(self): + set_headers = [(f"set-header-{step}", str(step) * 2000) for step in range(self.__headers_n)] + add_headers = [(f"add-header-{step}", str(step) * 2000) for step in range(self.__headers_n)] + exist_header = [ + (f"exist-header-{step}", str(step) * 1000) for step in range(self.__headers_n) + ] + changed_headers = [ + (f"changed-header-{step}", f"{step}a") for step in range(self.__headers_n) + ] + expected_changed_header = [ + (header[0], header[1].replace("a", "")) for header in changed_headers + ] + + config = [ + f'{self.directive}_hdr_set {header[0]} "{header[1]}";\n' for header in set_headers + ] + config.extend( + f'{self.directive}_hdr_add {header[0]} "{header[1]}";\n' for header in add_headers + ) + config.extend(f"{self.directive}_hdr_set {header[0]};\n" for header in exist_header) + config.extend( + f'{self.directive}_hdr_set {header[0]} "{header[1]}";\n' + for header in expected_changed_header + ) + + TestLogicBase.base_scenario( + self, + config="".join(config), + optional_headers=exist_header + changed_headers, + expected_headers=set_headers + add_headers + expected_changed_header, + ) + + +class TestManyResponseHeaders(TestManyRequestHeaders): + cache = False + directive = "resp" + h2 = False + requests_n = 1 + + +class TestManyCachedResponseHeaders(TestManyRequestHeaders): + cache = True + directive = "resp" + h2 = False + requests_n = 2 + + class TestReqHeaderH2(H2Config, TestLogicBase): directive = "req" cache = False @@ -330,7 +411,11 @@ def test_add_header_from_static_table(self): def test_add_header_from_dynamic_table(self): """Tempesta must add header from dynamic table for second response.""" - self.update_tempesta_config(config=f'{self.directive}_hdr_set x-my-hdr "text";\n') + update_tempesta_config( + tempesta=self.get_tempesta(), + config=f'{self.directive}_hdr_set x-my-hdr "text";\n', + cache=self.cache, + ) self.start_all_services() optional_headers = [("x-my-hdr", "text")] @@ -353,3 +438,24 @@ class TestCachedRespHeaderH2(H2Config, TestLogicBase): cache = True requests_n = 2 h2 = True + + +class TestManyRequestHeadersH2(H2Config, TestManyRequestHeaders): + cache = False + directive = "req" + h2 = True + requests_n = 2 + + +class TestManyResponseHeadersH2(H2Config, TestManyRequestHeaders): + cache = False + directive = "resp" + h2 = True + requests_n = 2 + + +class TestManyCachedResponseHeadersH2(H2Config, TestManyRequestHeaders): + cache = True + directive = "resp" + h2 = True + requests_n = 2 From 4359e0fd0cc34705cc24ab1ad48464d3688cbd23 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 22 May 2023 11:51:47 +0400 Subject: [PATCH 13/14] added tests with long header name and header from static table with cache enabled. --- http2_general/test_h2_headers.py | 26 +++++++++++ http_general/test_headers.py | 67 ++++++++++++++++++++++++++++- t_modify_http_headers/test_logic.py | 57 +++++++++++++++++++++--- 3 files changed, 143 insertions(+), 7 deletions(-) diff --git a/http2_general/test_h2_headers.py b/http2_general/test_h2_headers.py index 70a851f5d..42a82bb90 100644 --- a/http2_general/test_h2_headers.py +++ b/http2_general/test_h2_headers.py @@ -129,6 +129,32 @@ def test_transfer_encoding_header_in_request(self): "400", ) + def test_long_header_name_in_request(self): + """Max length for header name - 1024. See fw/http_parser.c HTTP_MAX_HDR_NAME_LEN""" + for length, status_code in ((1023, "200"), (1024, "200"), (1025, "400")): + with self.subTest(length=length, status_code=status_code): + self.start_all_services() + + client = self.get_client("deproxy") + client.send_request(self.post_request + [("a" * length, "text")], status_code) + + def test_long_header_name_in_response(self): + """Max length for header name - 1024. See fw/http_parser.c HTTP_MAX_HDR_NAME_LEN""" + for length, status_code in ((1023, "200"), (1024, "200"), (1025, "502")): + with self.subTest(length=length, status_code=status_code): + self.start_all_services() + + client = self.get_client("deproxy") + server = self.get_server("deproxy") + server.set_response( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + f"{'a' * length}: text\r\n" + + "Content-Length: 0\r\n\r\n" + ) + client.send_request(self.post_request, status_code) + class DuplicateSingularHeader(H2Base): def test_two_header_as_bytes_from_dynamic_table(self): diff --git a/http_general/test_headers.py b/http_general/test_headers.py index 66afe9a43..eff644a86 100644 --- a/http_general/test_headers.py +++ b/http_general/test_headers.py @@ -5,7 +5,7 @@ from framework import tester __author__ = "Tempesta Technologies, Inc." -__copyright__ = "Copyright (C) 2022 Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." __license__ = "GPL2" @@ -291,3 +291,68 @@ def test_forwarded_and_empty_host_header(self): ), expected_status_code="400", ) + + +class TestHeadersParsing(tester.TempestaTest): + backends = [ + { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": ( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + "Content-Length: 0\r\n\r\n" + ), + } + ] + + tempesta = { + "config": """ + listen 80; + server ${server_ip}:8000; + + block_action attack reply; + block_action error reply; + """ + } + + clients = [ + { + "id": "deproxy", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", + }, + ] + + def test_long_header_name_in_request(self): + """Max length for header name - 1024. See fw/http_parser.c HTTP_MAX_HDR_NAME_LEN""" + for length, status_code in ((1023, "200"), (1024, "200"), (1025, "400")): + with self.subTest(length=length, status_code=status_code): + self.start_all_services() + + client = self.get_client("deproxy") + client.send_request( + f"GET / HTTP/1.1\r\nHost: localhost\r\n{'a' * length}: text\r\n\r\n", + status_code, + ) + + def test_long_header_name_in_response(self): + """Max length for header name - 1024. See fw/http_parser.c HTTP_MAX_HDR_NAME_LEN""" + for length, status_code in ((1023, "200"), (1024, "200"), (1025, "502")): + with self.subTest(length=length, status_code=status_code): + self.start_all_services() + + client = self.get_client("deproxy") + server = self.get_server("deproxy") + server.set_response( + "HTTP/1.1 200 OK\r\n" + + "Date: test\r\n" + + "Server: debian\r\n" + + f"{'a' * length}: text\r\n" + + "Content-Length: 0\r\n\r\n" + ) + client.send_request(f"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n", status_code) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index 1917265d6..1d715e194 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -4,9 +4,13 @@ __copyright__ = "Copyright (C) 2023 Tempesta Technologies, Inc." __license__ = "GPL2" +import unittest + from framework import tester from helpers.deproxy import H2Response, HttpMessage, Request, Response +MAX_HEADER_NAME = 1024 # See fw/http_parser.c HTTP_MAX_HDR_NAME_LEN + def generate_http1_request(optional_headers=[]) -> str: return ( @@ -261,8 +265,8 @@ def test_delete_many_special_headers(self): """ Headers must be removed from base request/response if header is in base request/response. """ - header_name = "set-cookie" if self.directive == "resp" else "if-none-match" - header_value = "test=cookie" if self.directive == "resp" else '"qwe"' + header_name = "set-cookie" if self.directive == "resp" else "forwarded" + header_value = "test=cookie" if self.directive == "resp" else "for=tempesta.com" client, server = self.base_scenario( config=f"{self.directive}_hdr_set {header_name};\n", optional_headers=[(header_name, header_value), (header_name, header_value)], @@ -291,6 +295,44 @@ def test_set_large_header(self): expected_headers=[("x-my-hdr", "12" * 2000)], ) + def test_set_header_name_greater_than_1024(self): + self.base_scenario( + config=f'{self.directive}_hdr_set {"a" * (MAX_HEADER_NAME + 10)} "value";\n', + optional_headers=[], + expected_headers=[("a" * (MAX_HEADER_NAME + 10), "value")], + ) + + def test_set_long_header_name(self): + self.base_scenario( + config=( + f'{self.directive}_hdr_set {"a" * MAX_HEADER_NAME} "value";\n' + f'{self.directive}_hdr_set {"b" * MAX_HEADER_NAME} "value";\n' + f'{self.directive}_hdr_set {"c" * MAX_HEADER_NAME} "value";\n' + f'{self.directive}_hdr_set {"d" * MAX_HEADER_NAME} "value";\n' + ), + optional_headers=[], + expected_headers=[ + ("a" * MAX_HEADER_NAME, "value"), + ("b" * MAX_HEADER_NAME, "value"), + ("c" * MAX_HEADER_NAME, "value"), + ("d" * MAX_HEADER_NAME, "value"), + ], + ) + + def test_long_header_name(self): + self.base_scenario( + config=f'{self.directive}_hdr_set {"a" * MAX_HEADER_NAME} "value1";\n', + optional_headers=[ + ("a" * MAX_HEADER_NAME, "value"), + ("a" * MAX_HEADER_NAME, "value"), + ("a" * MAX_HEADER_NAME, "value"), + ("a" * MAX_HEADER_NAME, "value"), + ], + expected_headers=[ + ("a" * MAX_HEADER_NAME, "value1"), + ], + ) + class H2Config: clients = [ @@ -407,7 +449,7 @@ def test_add_header_from_static_table(self): expected_headers=[("cache-control", "no-cache")], ) - self.assertIn(b"\x08no-cache", client.response_buffer) + self.assertIn(b"\x08no-cache", client.last_response_buffer) def test_add_header_from_dynamic_table(self): """Tempesta must add header from dynamic table for second response.""" @@ -426,11 +468,11 @@ def test_add_header_from_dynamic_table(self): server.set_response(generate_response(optional_headers)) client.send_request(request, "200") - self.assertIn(b"\x08x-my-hdr\x04text", client.response_buffer) + self.assertIn(b"\x08x-my-hdr\x04text", client.last_response_buffer) client.send_request(request, "200") - self.assertNotIn(b"\nx-my-hdr\x04text", client.response_buffer) - self.assertIn(b"\xbe", client.response_buffer) + self.assertNotIn(b"x-my-hdr\x04text", client.last_response_buffer) + self.assertIn(b"\xbe", client.last_response_buffer) class TestCachedRespHeaderH2(H2Config, TestLogicBase): @@ -439,6 +481,9 @@ class TestCachedRespHeaderH2(H2Config, TestLogicBase): requests_n = 2 h2 = True + def test_add_header_from_static_table(self): + TestRespHeaderH2.test_add_header_from_static_table(self) + class TestManyRequestHeadersH2(H2Config, TestManyRequestHeaders): cache = False From 955f479226abf5a0feda531967e22f1b926b677d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 May 2023 17:21:44 +0400 Subject: [PATCH 14/14] disabled tests and decreased size of headers because response exceeds SETTINGS_MAX_HEADER_LIST_SIZE. --- t_modify_http_headers/test_logic.py | 6 +++--- tests_disabled.json | 12 ++++++++++++ tests_disabled_tcpseg.json | 4 ++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/t_modify_http_headers/test_logic.py b/t_modify_http_headers/test_logic.py index 1d715e194..bda5cd6d2 100644 --- a/t_modify_http_headers/test_logic.py +++ b/t_modify_http_headers/test_logic.py @@ -382,10 +382,10 @@ class TestManyRequestHeaders(tester.TempestaTest): __headers_n = 64 // 4 def test_many_headers(self): - set_headers = [(f"set-header-{step}", str(step) * 2000) for step in range(self.__headers_n)] - add_headers = [(f"add-header-{step}", str(step) * 2000) for step in range(self.__headers_n)] + set_headers = [(f"set-header-{step}", str(step) * 1000) for step in range(self.__headers_n)] + add_headers = [(f"add-header-{step}", str(step) * 1000) for step in range(self.__headers_n)] exist_header = [ - (f"exist-header-{step}", str(step) * 1000) for step in range(self.__headers_n) + (f"exist-header-{step}", str(step) * 500) for step in range(self.__headers_n) ] changed_headers = [ (f"changed-header-{step}", f"{step}a") for step in range(self.__headers_n) diff --git a/tests_disabled.json b/tests_disabled.json index ce35559a9..85d4787b6 100644 --- a/tests_disabled.json +++ b/tests_disabled.json @@ -524,6 +524,18 @@ { "name": "http2_general.test_h2_headers.TestSplitCookies.test_split_cookies", "reason": "Disabled by issue #1736" + }, + { + "name": "t_modify_http_headers.test_logic.TestManyResponseHeaders", + "reason": "Disabled by issue #1103" + }, + { + "name": "t_modify_http_headers.test_logic.TestManyCachedResponseHeaders", + "reason": "Disabled by issue #1103" + }, + { + "name": "t_modify_http_headers.test_logic.TestManyRequestHeadersH2", + "reason": "Disabled by issue #1103" } ] } diff --git a/tests_disabled_tcpseg.json b/tests_disabled_tcpseg.json index 43fdf3a8e..e989517b5 100644 --- a/tests_disabled_tcpseg.json +++ b/tests_disabled_tcpseg.json @@ -196,6 +196,10 @@ { "name": "t_frang.test_http_trailer_split_allowed", "reason": "Disabled by issue #464" + }, + { + "name": "t_modify_http_headers.test_logic.TestManyRequestHeaders", + "reason": "Disabled by issue #1103" } ] }