261 lines
6.6 KiB
Python
261 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import gzip
|
|
import hashlib
|
|
import io
|
|
import time
|
|
import zlib
|
|
|
|
import brotli
|
|
import requests
|
|
import zstandard
|
|
|
|
|
|
TESTS = {
|
|
"identity": "identity",
|
|
"gzip": "gzip",
|
|
"deflate": "deflate",
|
|
"brotli": "br",
|
|
"zstd": "zstd",
|
|
"all": "zstd, br, gzip, deflate",
|
|
}
|
|
|
|
|
|
def decompress(data, content_encoding):
|
|
encodings = [
|
|
e.strip().lower()
|
|
for e in content_encoding.split(",")
|
|
if e.strip() and e.strip().lower() != "identity"
|
|
]
|
|
|
|
# Content-Encoding usuwa się w odwrotnej kolejności.
|
|
for encoding in reversed(encodings):
|
|
if encoding == "gzip":
|
|
data = gzip.decompress(data)
|
|
|
|
elif encoding == "deflate":
|
|
try:
|
|
data = zlib.decompress(data)
|
|
except zlib.error:
|
|
# Niektóre serwery wysyłają raw DEFLATE.
|
|
data = zlib.decompress(data, -zlib.MAX_WBITS)
|
|
|
|
elif encoding == "br":
|
|
data = brotli.decompress(data)
|
|
|
|
elif encoding == "zstd":
|
|
dctx = zstandard.ZstdDecompressor()
|
|
with dctx.stream_reader(io.BytesIO(data)) as reader:
|
|
data = reader.read()
|
|
|
|
else:
|
|
raise RuntimeError(f"Nieznany Content-Encoding: {encoding}")
|
|
|
|
return data
|
|
|
|
|
|
def human_size(size):
|
|
if size < 1024:
|
|
return f"{size} B"
|
|
if size < 1024 * 1024:
|
|
return f"{size / 1024:.1f} KB"
|
|
return f"{size / 1024 / 1024:.2f} MB"
|
|
|
|
|
|
def request_test(url, accept_encoding, verify_ssl=True, timeout=15, revalidate=False):
|
|
headers = {
|
|
"Accept-Encoding": accept_encoding,
|
|
"User-Agent": "compression-tester/1.0",
|
|
}
|
|
if revalidate:
|
|
headers["Cache-Control"] = "no-cache"
|
|
|
|
start = time.perf_counter()
|
|
|
|
r = requests.get(
|
|
url,
|
|
headers=headers,
|
|
timeout=timeout,
|
|
verify=verify_ssl,
|
|
stream=True,
|
|
allow_redirects=True,
|
|
)
|
|
|
|
r.raw.decode_content = False
|
|
raw = r.raw.read()
|
|
|
|
elapsed = time.perf_counter() - start
|
|
|
|
content_encoding = r.headers.get("Content-Encoding", "identity")
|
|
|
|
try:
|
|
decoded = decompress(raw, content_encoding)
|
|
error = None
|
|
except Exception as exc:
|
|
decoded = b""
|
|
error = str(exc)
|
|
|
|
return {
|
|
"status": r.status_code,
|
|
"requested": accept_encoding,
|
|
"received": content_encoding,
|
|
"raw_size": len(raw),
|
|
"decoded_size": len(decoded),
|
|
"time": elapsed,
|
|
"decoded": decoded,
|
|
"error": error,
|
|
"headers": r.headers,
|
|
"final_url": r.url,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Tester HTTP Content-Encoding: zstd/br/gzip/deflate"
|
|
)
|
|
|
|
parser.add_argument("url", help="URL do przetestowania")
|
|
parser.add_argument(
|
|
"--insecure",
|
|
action="store_true",
|
|
help="Wyłącz weryfikację certyfikatu SSL",
|
|
)
|
|
parser.add_argument(
|
|
"--timeout",
|
|
type=int,
|
|
default=15,
|
|
help="Timeout HTTP w sekundach",
|
|
)
|
|
parser.add_argument(
|
|
"--revalidate",
|
|
action="store_true",
|
|
help="Wyslij Cache-Control: no-cache (wymusza rewalidacje i moze resetowac Age do 0)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.insecure:
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
print()
|
|
print(f"URL: {args.url}")
|
|
print("=" * 100)
|
|
|
|
results = {}
|
|
|
|
for name, accept_encoding in TESTS.items():
|
|
try:
|
|
result = request_test(
|
|
args.url,
|
|
accept_encoding,
|
|
verify_ssl=not args.insecure,
|
|
timeout=args.timeout,
|
|
revalidate=args.revalidate,
|
|
)
|
|
results[name] = result
|
|
except Exception as exc:
|
|
results[name] = {
|
|
"error": f"REQUEST ERROR: {exc}"
|
|
}
|
|
|
|
identity_body = None
|
|
|
|
if "identity" in results:
|
|
identity_body = results["identity"].get("decoded")
|
|
|
|
print(
|
|
f"{'TEST':<10}"
|
|
f"{'HTTP':<7}"
|
|
f"{'REQUEST':<27}"
|
|
f"{'RESPONSE':<12}"
|
|
f"{'RAW':<12}"
|
|
f"{'DECODED':<12}"
|
|
f"{'RATIO':<10}"
|
|
f"{'TIME':<10}"
|
|
f"{'BODY'}"
|
|
)
|
|
|
|
print("-" * 120)
|
|
|
|
failed = False
|
|
|
|
for name, result in results.items():
|
|
if result.get("error"):
|
|
print(f"{name:<10} ERROR: {result['error']}")
|
|
failed = True
|
|
continue
|
|
|
|
raw_size = result["raw_size"]
|
|
decoded_size = result["decoded_size"]
|
|
|
|
if decoded_size:
|
|
ratio = raw_size / decoded_size
|
|
ratio_text = f"{ratio * 100:.1f}%"
|
|
else:
|
|
ratio_text = "-"
|
|
|
|
if identity_body is not None and result["decoded"]:
|
|
if result["decoded"] == identity_body:
|
|
body_status = "OK"
|
|
else:
|
|
body_status = "DIFF"
|
|
else:
|
|
body_status = "-"
|
|
|
|
print(
|
|
f"{name:<10}"
|
|
f"{result['status']:<7}"
|
|
f"{result['requested']:<27}"
|
|
f"{result['received']:<12}"
|
|
f"{human_size(raw_size):<12}"
|
|
f"{human_size(decoded_size):<12}"
|
|
f"{ratio_text:<10}"
|
|
f"{result['time']:.3f}s "
|
|
f"{body_status}"
|
|
)
|
|
|
|
print()
|
|
print("=" * 100)
|
|
print("Szczegóły nagłówków:")
|
|
print()
|
|
|
|
for name, result in results.items():
|
|
if result.get("error"):
|
|
continue
|
|
|
|
headers = result["headers"]
|
|
|
|
print(f"[{name}]")
|
|
print(f" URL finalny: {result['final_url']}")
|
|
print(f" HTTP status: {result['status']}")
|
|
print(f" Content-Encoding: {headers.get('Content-Encoding', '-')}")
|
|
print(f" Content-Type: {headers.get('Content-Type', '-')}")
|
|
print(f" Content-Length: {headers.get('Content-Length', '-')}")
|
|
print(f" Vary: {headers.get('Vary', '-')}")
|
|
print(f" Cache-Control: {headers.get('Cache-Control', '-')}")
|
|
print(f" Age: {headers.get('Age', '-')}")
|
|
print(f" X-Cache: {headers.get('X-Cache', '-')}")
|
|
print(f" X-Cache-Hits: {headers.get('X-Cache-Hits', '-')}")
|
|
print(f" Cache-Status: {headers.get('Cache-Status', '-')}")
|
|
print(f" ETag: {headers.get('ETag', '-')}")
|
|
print(f" Server: {headers.get('Server', '-')}")
|
|
print()
|
|
|
|
print("SHA256 po dekompresji:")
|
|
print()
|
|
|
|
for name, result in results.items():
|
|
body = result.get("decoded")
|
|
|
|
if body:
|
|
sha = hashlib.sha256(body).hexdigest()
|
|
print(f"{name:<10} {sha}")
|
|
|
|
if failed:
|
|
raise SystemExit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|