181 lines
11 KiB
Python
Executable File
181 lines
11 KiB
Python
Executable File
"""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"))
|