"""Cache and response-processing paths must survive disabled-path optimization.""" from concurrent.futures import ThreadPoolExecutor import copy import gzip import io import json import time import zlib from common import http def run(check, rust, peer): port = rust.reverse_port before = peer.count("GET", "/fresh") results = [http(port, "/fresh") for _ in range(2)] check("cache.global_off", all(s == 200 and h.get("x-cache") != "HIT" for s, h, b in results) and peer.count("GET", "/fresh") - before == 2) config = copy.deepcopy(rust.base) config["cache"] = {"enabled": True, "expose_status": True, "expose_hits": True, "expose_cache_status": True, "http_invalidation_allowed_cidrs": ["127.0.0.1/32"]} config["web_services"][0]["cache_control"] = "public, max-age=60" rust.apply(config, "cache-enabled") first = http(port, "/fresh") second = http(port, "/fresh") check("cache.miss_then_hit", first[0] == 200 and first[1].get("x-cache") == "MISS" and second[1].get("x-cache") == "HIT" and first[2] == second[2], first=first[1].get("x-cache"), second=second[1].get("x-cache")) results = {} for language in ["en", "pl", "en", "pl"]: response = http(port, "/vary", headers={"Accept-Language": language}) previous = results.get(language) if previous: check("cache.vary_hit." + language, response[1].get("x-cache") == "HIT" and response[2] == previous[2], cache=response[1].get("x-cache")) results[language] = response check("cache.vary_isolation", results["en"][2] != results["pl"][2]) for path in ["/private", "/no-store", "/cookie"]: before = peer.count("GET", path) first, second = http(port, path), http(port, path) check("cache.nonpublic_bypass." + path, first[0] == second[0] == 200 and first[1].get("x-cache") != "HIT" and second[1].get("x-cache") != "HIT" and peer.count("GET", path) - before == 2, first=first[1].get("x-cache"), second=second[1].get("x-cache")) for name, headers in [("authorization", {"Authorization": "Bearer fixture-value"}), ("cookie", {"Cookie": "application_session=fixture"})]: before = peer.count("GET", "/fresh") s, h, b = http(port, "/fresh", headers=headers) check("cache.request_bypass." + name, s == 200 and h.get("x-cache") != "HIT" and peer.count("GET", "/fresh") - before == 1, cache=h.get("x-cache")) before = peer.count("GET", "/fresh") s, h, b = http(port, "/fresh", "GET", {"Content-Type": "application/octet-stream"}, b"nonempty-get-body") check("cache.nonempty_get_bypass_and_preserve", s == 200 and h.get("x-cache") != "HIT" and h.get("x-received-body-bytes") == "17" and peer.count("GET", "/fresh") - before == 1, cache=h.get("x-cache"), origin_body_bytes=h.get("x-received-body-bytes")) s, h, b = http(port, "/fresh", "PURGE") after = http(port, "/fresh") check("cache.http_purge", s in (200, 202, 204) and after[1].get("x-cache") == "MISS", status=s) with ThreadPoolExecutor(max_workers=8) as pool: results = list(pool.map(lambda _: http(port, "/concurrent"), range(8))) check("cache.concurrent_fill", all(item[0] == 200 for item in results) and len({item[2] for item in results}) == 1, origin_requests=peer.count("GET", "/concurrent")) first = http(port, "/revalidate") time.sleep(1.2) second = http(port, "/revalidate") check("cache.expired_validator_revalidation", first[0] == second[0] == 200 and first[2] == second[2] and peer.count("GET", "/revalidate") >= 2, cache=second[1].get("x-cache")) first, second = http(rust.static_port), http(rust.static_port) check("cache.static_hit", first[0] == second[0] == 200 and second[1].get("x-cache") == "HIT" and first[2] == second[2], cache=second[1].get("x-cache")) empty_first, empty_second = http(rust.static_port, "/empty.txt"), http(rust.static_port, "/empty.txt") check("cache.static_empty_hit", empty_first[0] == empty_second[0] == 200 and empty_second[1].get("x-cache") == "HIT" and empty_second[2] == b"", first=empty_first[1].get("x-cache"), second=empty_second[1].get("x-cache")) hs, hh, hb = http(rust.static_port, "/download.txt", "HEAD") gs, gh, gb = http(rust.static_port, "/download.txt") check("cache.static_head_cannot_fill_empty_get", hs == gs == 200 and hb == b"" and gb == (rust.fixture.site / "download.txt").read_bytes(), head_status=hs, get_status=gs) s, h, b = http(rust.static_port, headers={"Range": "bytes=0-9"}) check("cache.static_range_survives", s == 206 and b == first[2][:10], status=s) s, h, b = http(rust.static_port, headers={"If-None-Match": first[1]["etag"]}) check("cache.static_conditional_survives", s == 304 and b == b"", status=s) config["vhosts"][0]["cache"] = {"enabled": False} rust.apply(config, "cache-vhost-disabled") first, second = http(port, "/fresh"), http(port, "/fresh") check("cache.vhost_off", all(response[1].get("x-cache") != "HIT" for response in [first, second]) and first[2] != second[2]) config["vhosts"][0]["cache"] = {"enabled": True} config["vhosts"][0]["routes"] = [{"id": "fresh-no-cache", "path_prefix": "/fresh", "cache": False}] rust.apply(config, "cache-route-disabled") first, second = http(port, "/fresh"), http(port, "/fresh") check("cache.route_off", all(response[1].get("x-cache") != "HIT" for response in [first, second]) and first[2] != second[2]) config["cache"]["enabled"] = False config["vhosts"][0]["routes"] = [] rust.apply(config, "cache-global-disabled-vhost-enabled") first, second = http(port, "/fresh"), http(port, "/fresh") check("cache.vhost_cannot_override_global_off", first[2] != second[2] and second[1].get("x-cache") != "HIT") def transforms(check, rust, peer): config = copy.deepcopy(rust.base) original = (rust.fixture.site / "app.json").read_bytes() for port, source in [(rust.reverse_port, "proxy"), (rust.static_port, "static")]: s, h, b = http(port, "/app.json") check("transform.off." + source, s == 200 and b == original, status=s) config["transform"] = {"enabled": True, "json": True, "html": True} for vhost in config["vhosts"]: vhost["transform"] = {"enabled": True, "json": True, "html": True} rust.apply(config, "transform-enabled-cache-off") for port, source in [(rust.reverse_port, "proxy"), (rust.static_port, "static")]: s, h, b = http(port, "/app.json") check("transform.on." + source, s == 200 and json.loads(b) == json.loads(original) and len(b) < len(original), original_bytes=len(original), result_bytes=len(b), status=s) s, h, b = http(port, "/large.bin") check("transform.binary_unchanged." + source, s == 200 and b == (rust.fixture.site / "large.bin").read_bytes()) s, h, b = http(port, "/app.json", headers={"Range": "bytes=0-9"}) check("transform.range_exclusion." + source, s == 206 and b == original[:10], status=s) config["cache"] = {"enabled": True, "expose_status": True} rust.apply(config, "transform-enabled-cache-on") first, second = http(rust.reverse_port, "/app.json"), http(rust.reverse_port, "/app.json") check("transform.cache_combination", second[0] == 200 and second[1].get("x-cache") == "HIT" and first[2] == second[2] and json.loads(second[2]) == json.loads(original), cache=second[1].get("x-cache")) rust.reset() expected = (rust.fixture.site / "compress.txt").read_bytes() for port, source in [(rust.reverse_port, "proxy"), (rust.static_port, "static")]: s, h, b = http(port, "/compress.txt", headers={"Accept-Encoding": "gzip"}) check("compression.gzip." + source, s == 200 and h.get("content-encoding") == "gzip" and gzip.decompress(b) == expected and len(b) < len(expected), bytes=len(b)) s, h, b = http(port, "/compress.txt", headers={"Accept-Encoding": "gzip;q=0, identity"}) check("compression.identity_qvalue." + source, s == 200 and "content-encoding" not in h and b == expected) s, h, b = http(port, "/compress.txt", headers={"Accept-Encoding": "gzip", "Range": "bytes=0-9"}) check("compression.range_exclusion." + source, s == 206 and "content-encoding" not in h and b == expected[:10]) s, h, b = http(rust.reverse_port, "/no-transform", headers={"Accept-Encoding": "gzip"}) check("compression.no_transform_exclusion", s == 200 and "content-encoding" not in h) config = copy.deepcopy(rust.base) config["vhosts"][0]["compression"] = {"enabled": False} rust.apply(config, "compression-vhost-disabled") s, h, b = http(rust.reverse_port, "/compress.txt", headers={"Accept-Encoding": "gzip"}) check("compression.vhost_off", s == 200 and "content-encoding" not in h and b == expected) config["compression"] = {"gzip": False, "br": False, "zstd": False, "deflate": False} config["vhosts"][0]["compression"] = {"enabled": True, "gzip": True} rust.apply(config, "compression-global-disabled") s, h, b = http(rust.reverse_port, "/compress.txt", headers={"Accept-Encoding": "gzip"}) check("compression.global_codec_ceiling", s == 200 and "content-encoding" not in h and b == expected) config = copy.deepcopy(rust.base) config["compression"] = {"gzip": True, "br": True, "zstd": True, "deflate": True} config["cache"] = {"enabled": True, "expose_status": True} rust.apply(config, "compression-cache-enabled") for codec in ["gzip", "deflate", "br", "zstd"]: decoder = None if codec == "gzip": decoder = gzip.decompress elif codec == "deflate": decoder = zlib.decompress elif codec == "br": try: import brotli decoder = brotli.decompress except ImportError: pass else: try: import zstandard decoder = lambda data: zstandard.ZstdDecompressor().stream_reader(io.BytesIO(data)).read() except ImportError: pass if decoder is None: check("compression.codec." + codec, None, skip_reason="optional Python decoder is not installed") continue first = http(rust.reverse_port, "/compress.txt", headers={"Accept-Encoding": codec}) second = http(rust.reverse_port, "/compress.txt", headers={"Accept-Encoding": codec}) check("compression.codec." + codec, second[0] == 200 and second[1].get("content-encoding") == codec and decoder(first[2]) == decoder(second[2]) == expected, cache=second[1].get("x-cache"))