Skip to content

Commit 263d1df

Browse files
committed
add compression and caching
1 parent d47b05e commit 263d1df

3 files changed

Lines changed: 361 additions & 51 deletions

File tree

scripts/validate.sh

Lines changed: 253 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
1313
ROOT_DIR="$SCRIPT_DIR/.."
1414
META_FILE="$ROOT_DIR/frameworks/$FRAMEWORK/meta.json"
1515
CERTS_DIR="$ROOT_DIR/certs"
16+
DATA_DIR="$ROOT_DIR/data"
1617

1718
cleanup() {
1819
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
@@ -41,14 +42,27 @@ else
4142
docker build -t "$IMAGE_NAME" "frameworks/$FRAMEWORK" || { echo "FAIL: Docker build failed"; exit 1; }
4243
fi
4344

44-
# Run
45+
# Mount volumes based on subscribed tests
4546
docker_args=(-d --name "$CONTAINER_NAME" -p "$PORT:8080")
46-
docker_args+=(-v "$ROOT_DIR/data/dataset.json:/data/dataset.json:ro")
47+
docker_args+=(-v "$DATA_DIR/dataset.json:/data/dataset.json:ro")
4748

48-
if has_test "baseline-h2" && [ -d "$CERTS_DIR" ]; then
49+
needs_h2=false
50+
if has_test "baseline-h2" || has_test "static-h2" || has_test "baseline-h3" || has_test "static-h3"; then
51+
needs_h2=true
52+
fi
53+
54+
if $needs_h2 && [ -d "$CERTS_DIR" ]; then
4955
docker_args+=(-p "$H2PORT:8443" -v "$CERTS_DIR:/certs:ro")
5056
fi
5157

58+
if has_test "compression"; then
59+
docker_args+=(-v "$DATA_DIR/dataset-large.json:/data/dataset-large.json:ro")
60+
fi
61+
62+
if has_test "static-h2" || has_test "static-h3"; then
63+
docker_args+=(-v "$DATA_DIR/static:/data/static:ro")
64+
fi
65+
5266
docker run "${docker_args[@]}" "$IMAGE_NAME"
5367

5468
# Wait for server to start
@@ -65,6 +79,8 @@ for i in $(seq 1 30); do
6579
done
6680
echo "[ready] Server is up"
6781

82+
# ───── Helpers ─────
83+
6884
check() {
6985
local label="$1"
7086
local expected_body="$2"
@@ -73,68 +89,109 @@ check() {
7389
response=$(curl -s -D- "$@")
7490
local body
7591
body=$(echo "$response" | tail -1)
76-
local server_hdr
77-
server_hdr=$(echo "$response" | grep -i "^server:" || true)
7892

79-
local ok=true
80-
81-
if [ "$body" != "$expected_body" ]; then
93+
if [ "$body" = "$expected_body" ]; then
94+
echo " PASS [$label]"
95+
PASS=$((PASS + 1))
96+
else
8297
echo " FAIL [$label]: expected body '$expected_body', got '$body'"
83-
ok=false
98+
FAIL=$((FAIL + 1))
8499
fi
100+
}
85101

86-
if [ -z "$server_hdr" ]; then
87-
echo " FAIL [$label]: missing Server header in response"
88-
ok=false
102+
check_status() {
103+
local label="$1"
104+
local expected_status="$2"
105+
shift 2
106+
local http_code
107+
http_code=$(curl -s -o /dev/null -w '%{http_code}' "$@")
108+
109+
if [ "$http_code" = "$expected_status" ]; then
110+
echo " PASS [$label] (HTTP $http_code)"
111+
PASS=$((PASS + 1))
112+
else
113+
echo " FAIL [$label]: expected HTTP $expected_status, got HTTP $http_code"
114+
FAIL=$((FAIL + 1))
89115
fi
116+
}
90117

91-
if $ok; then
92-
echo " PASS [$label]"
118+
check_header() {
119+
local label="$1"
120+
local header_name="$2"
121+
local expected_value="$3"
122+
shift 3
123+
local headers
124+
headers=$(curl -s -D- -o /dev/null "$@")
125+
local value
126+
value=$(echo "$headers" | grep -i "^${header_name}:" | sed 's/^[^:]*: *//' | tr -d '\r' || true)
127+
128+
if [ "$value" = "$expected_value" ]; then
129+
echo " PASS [$label] ($header_name: $value)"
93130
PASS=$((PASS + 1))
94131
else
132+
echo " FAIL [$label]: expected $header_name '$expected_value', got '$value'"
95133
FAIL=$((FAIL + 1))
96134
fi
97135
}
98136

99-
# baseline / limited-conn both use /baseline11
137+
wait_h2() {
138+
echo "[wait] Waiting for HTTPS port..."
139+
for i in $(seq 1 15); do
140+
if curl -sk -o /dev/null "https://localhost:$H2PORT/baseline2?a=1&b=1" 2>/dev/null; then
141+
return 0
142+
fi
143+
if [ "$i" -eq 15 ]; then
144+
echo " FAIL: HTTPS port $H2PORT not responding"
145+
FAIL=$((FAIL + 1))
146+
return 1
147+
fi
148+
sleep 1
149+
done
150+
}
151+
152+
# ───── Baseline (GET/POST /baseline11) ─────
153+
100154
if has_test "baseline" || has_test "limited-conn"; then
101155
echo "[test] baseline endpoints"
102156
check "GET /baseline11?a=13&b=42" "55" \
103157
"http://localhost:$PORT/baseline11?a=13&b=42"
104158

105-
check "POST /baseline11?a=13&b=42 + CL body=20" "75" \
159+
check "POST /baseline11?a=13&b=42 body=20" "75" \
106160
-X POST -H "Content-Type: text/plain" -d "20" \
107161
"http://localhost:$PORT/baseline11?a=13&b=42"
108162

109-
check "POST /baseline11?a=13&b=42 + chunked body=20" "75" \
163+
check "POST /baseline11?a=13&b=42 chunked body=20" "75" \
110164
-X POST -H "Content-Type: text/plain" -H "Transfer-Encoding: chunked" -d "20" \
111165
"http://localhost:$PORT/baseline11?a=13&b=42"
112166

113-
# Cache detection: same URL, different body — verify server reads the body
114-
echo "[test] cache detection"
167+
# Anti-cheat: randomized inputs to detect hardcoded responses
168+
echo "[test] baseline anti-cheat (randomized inputs)"
169+
A1=$((RANDOM % 900 + 100))
170+
B1=$((RANDOM % 900 + 100))
171+
check "GET /baseline11?a=$A1&b=$B1 (random)" "$((A1 + B1))" \
172+
"http://localhost:$PORT/baseline11?a=$A1&b=$B1"
173+
115174
BODY1=$((RANDOM % 900 + 100))
116175
BODY2=$((RANDOM % 900 + 100))
117176
while [ "$BODY1" -eq "$BODY2" ]; do BODY2=$((RANDOM % 900 + 100)); done
118-
EXPECTED1=$((13 + 42 + BODY1))
119-
EXPECTED2=$((13 + 42 + BODY2))
120-
121-
check "POST /baseline11?a=13&b=42 + body=$BODY1 (cache check 1)" "$EXPECTED1" \
177+
check "POST body=$BODY1 (cache check 1)" "$((13 + 42 + BODY1))" \
122178
-X POST -H "Content-Type: text/plain" -d "$BODY1" \
123179
"http://localhost:$PORT/baseline11?a=13&b=42"
124-
125-
check "POST /baseline11?a=13&b=42 + body=$BODY2 (cache check 2)" "$EXPECTED2" \
180+
check "POST body=$BODY2 (cache check 2)" "$((13 + 42 + BODY2))" \
126181
-X POST -H "Content-Type: text/plain" -d "$BODY2" \
127182
"http://localhost:$PORT/baseline11?a=13&b=42"
128183
fi
129184

130-
# pipelined uses /pipeline
185+
# ───── Pipelined (GET /pipeline) ─────
186+
131187
if has_test "pipelined"; then
132188
echo "[test] pipelined endpoint"
133189
check "GET /pipeline" "ok" \
134190
"http://localhost:$PORT/pipeline"
135191
fi
136192

137-
# json uses /json
193+
# ───── JSON Processing (GET /json) ─────
194+
138195
if has_test "json"; then
139196
echo "[test] json endpoint"
140197
response=$(curl -s "http://localhost:$PORT/json")
@@ -144,46 +201,191 @@ d = json.load(sys.stdin)
144201
count = d.get('count', 0)
145202
items = d.get('items', [])
146203
has_total = all('total' in item for item in items) if items else False
147-
print(f'{count} {has_total}')
148-
" 2>/dev/null || echo "0 False")
204+
# Verify total is computed correctly (price * quantity, rounded to 2 decimals)
205+
correct_totals = True
206+
for item in items:
207+
expected = round(item['price'] * item['quantity'], 2)
208+
if abs(item.get('total', 0) - expected) > 0.01:
209+
correct_totals = False
210+
break
211+
print(f'{count} {has_total} {correct_totals}')
212+
" 2>/dev/null || echo "0 False False")
149213
json_count=$(echo "$json_result" | cut -d' ' -f1)
150214
json_total=$(echo "$json_result" | cut -d' ' -f2)
215+
json_correct=$(echo "$json_result" | cut -d' ' -f3)
151216

152-
if [ "$json_count" = "50" ] && [ "$json_total" = "True" ]; then
153-
echo " PASS [GET /json]"
217+
if [ "$json_count" = "50" ] && [ "$json_total" = "True" ] && [ "$json_correct" = "True" ]; then
218+
echo " PASS [GET /json] (50 items, totals computed correctly)"
154219
PASS=$((PASS + 1))
155220
else
156-
echo " FAIL [GET /json]: count=$json_count, has_total=$json_total"
221+
echo " FAIL [GET /json]: count=$json_count, has_total=$json_total, correct_totals=$json_correct"
157222
FAIL=$((FAIL + 1))
158223
fi
224+
225+
# Check Content-Type header
226+
check_header "GET /json Content-Type" "Content-Type" "application/json" \
227+
"http://localhost:$PORT/json"
159228
fi
160229

161-
# baseline-h2 uses /baseline2 over HTTP/2 + TLS on port 8443
230+
# ───── Upload (POST /upload) ─────
231+
232+
if has_test "upload"; then
233+
echo "[test] upload endpoint"
234+
# Small upload: known CRC32
235+
UPLOAD_BODY="Hello, HttpArena!"
236+
EXPECTED_CRC=$(python3 -c "import zlib; print(format(zlib.crc32(b'$UPLOAD_BODY') & 0xFFFFFFFF, '08x'))")
237+
check "POST /upload small body" "$EXPECTED_CRC" \
238+
-X POST -H "Content-Type: application/octet-stream" --data-binary "$UPLOAD_BODY" \
239+
"http://localhost:$PORT/upload"
240+
241+
# Anti-cheat: random body to detect hardcoded CRC
242+
RANDOM_BODY=$(head -c 64 /dev/urandom | base64 | head -c 48)
243+
EXPECTED_RANDOM_CRC=$(echo -n "$RANDOM_BODY" | python3 -c "import sys,zlib; print(format(zlib.crc32(sys.stdin.buffer.read()) & 0xFFFFFFFF, '08x'))")
244+
ACTUAL_CRC=$(curl -s -X POST -H "Content-Type: application/octet-stream" --data-binary "$RANDOM_BODY" "http://localhost:$PORT/upload")
245+
if [ "$ACTUAL_CRC" = "$EXPECTED_RANDOM_CRC" ]; then
246+
echo " PASS [POST /upload random body] (CRC32: $ACTUAL_CRC)"
247+
PASS=$((PASS + 1))
248+
else
249+
echo " FAIL [POST /upload random body]: expected CRC '$EXPECTED_RANDOM_CRC', got '$ACTUAL_CRC'"
250+
FAIL=$((FAIL + 1))
251+
fi
252+
fi
253+
254+
# ───── Compression (GET /compression) ─────
255+
256+
if has_test "compression"; then
257+
echo "[test] compression endpoint"
258+
259+
# Must return Content-Encoding: gzip when Accept-Encoding: gzip is sent
260+
comp_headers=$(curl -s -D- -o /dev/null -H "Accept-Encoding: gzip" "http://localhost:$PORT/compression")
261+
comp_encoding=$(echo "$comp_headers" | grep -i "^content-encoding:" | tr -d '\r' | awk '{print tolower($2)}' || true)
262+
if [ "$comp_encoding" = "gzip" ]; then
263+
echo " PASS [compression Content-Encoding: gzip]"
264+
PASS=$((PASS + 1))
265+
else
266+
echo " FAIL [compression]: expected Content-Encoding gzip, got '$comp_encoding'"
267+
FAIL=$((FAIL + 1))
268+
fi
269+
270+
# Verify compressed response is valid JSON with items and totals
271+
comp_response=$(curl -s --compressed -H "Accept-Encoding: gzip" "http://localhost:$PORT/compression")
272+
comp_result=$(echo "$comp_response" | python3 -c "
273+
import sys, json
274+
d = json.load(sys.stdin)
275+
count = d.get('count', 0)
276+
items = d.get('items', [])
277+
has_total = all('total' in item for item in items) if items else False
278+
print(f'{count} {has_total}')
279+
" 2>/dev/null || echo "0 False")
280+
comp_count=$(echo "$comp_result" | cut -d' ' -f1)
281+
comp_total=$(echo "$comp_result" | cut -d' ' -f2)
282+
283+
if [ "$comp_count" = "6000" ] && [ "$comp_total" = "True" ]; then
284+
echo " PASS [compression response] (6000 items with totals)"
285+
PASS=$((PASS + 1))
286+
else
287+
echo " FAIL [compression response]: count=$comp_count, has_total=$comp_total"
288+
FAIL=$((FAIL + 1))
289+
fi
290+
291+
# Verify compressed size is reasonable (should be well under 1MB uncompressed ~1MB)
292+
comp_size=$(curl -s -o /dev/null -w '%{size_download}' -H "Accept-Encoding: gzip" "http://localhost:$PORT/compression")
293+
if [ "$comp_size" -lt 500000 ]; then
294+
echo " PASS [compression size] ($comp_size bytes < 500KB)"
295+
PASS=$((PASS + 1))
296+
else
297+
echo " FAIL [compression size]: $comp_size bytes — compression not effective"
298+
FAIL=$((FAIL + 1))
299+
fi
300+
fi
301+
302+
# ───── Caching (GET /caching with ETag) ─────
303+
304+
if has_test "caching"; then
305+
echo "[test] caching endpoint"
306+
307+
# Without If-None-Match: should return 200 with body "OK" and ETag header
308+
check_status "GET /caching without If-None-Match" "200" \
309+
"http://localhost:$PORT/caching"
310+
311+
check "GET /caching body" "OK" \
312+
"http://localhost:$PORT/caching"
313+
314+
check_header "GET /caching ETag header" "ETag" '"AOK"' \
315+
"http://localhost:$PORT/caching"
316+
317+
# With matching If-None-Match: should return 304
318+
check_status "GET /caching with matching If-None-Match" "304" \
319+
-H 'If-None-Match: "AOK"' "http://localhost:$PORT/caching"
320+
321+
# 304 should have ETag header
322+
check_header "GET /caching 304 ETag header" "ETag" '"AOK"' \
323+
-H 'If-None-Match: "AOK"' "http://localhost:$PORT/caching"
324+
325+
# 304 should have no body
326+
caching_304_body=$(curl -s -H 'If-None-Match: "AOK"' "http://localhost:$PORT/caching")
327+
if [ -z "$caching_304_body" ]; then
328+
echo " PASS [GET /caching 304 no body]"
329+
PASS=$((PASS + 1))
330+
else
331+
echo " FAIL [GET /caching 304 no body]: got body '$caching_304_body'"
332+
FAIL=$((FAIL + 1))
333+
fi
334+
335+
# With non-matching If-None-Match: should return 200
336+
check_status "GET /caching with non-matching If-None-Match" "200" \
337+
-H 'If-None-Match: "WRONG"' "http://localhost:$PORT/caching"
338+
fi
339+
340+
# ───── Baseline H2 (GET /baseline2 over HTTP/2 + TLS) ─────
341+
162342
if has_test "baseline-h2"; then
163343
echo "[test] baseline-h2 endpoint"
164-
# Wait for H2 port
165-
for i in $(seq 1 10); do
166-
if curl -sk -o /dev/null "https://localhost:$H2PORT/baseline2?a=1&b=1" 2>/dev/null; then
167-
break
168-
fi
169-
if [ "$i" -eq 10 ]; then
170-
echo " FAIL [baseline-h2]: HTTPS port $H2PORT not responding"
344+
if wait_h2; then
345+
check "GET /baseline2?a=13&b=42 over HTTP/2" "55" \
346+
-sk "https://localhost:$H2PORT/baseline2?a=13&b=42"
347+
348+
# Anti-cheat: randomized query params
349+
A3=$((RANDOM % 900 + 100))
350+
B3=$((RANDOM % 900 + 100))
351+
check "GET /baseline2?a=$A3&b=$B3 over HTTP/2 (random)" "$((A3 + B3))" \
352+
-sk "https://localhost:$H2PORT/baseline2?a=$A3&b=$B3"
353+
fi
354+
fi
355+
356+
# ───── Static Files H2 (GET /static/* over HTTP/2 + TLS) ─────
357+
358+
if has_test "static-h2"; then
359+
echo "[test] static-h2 endpoint"
360+
if wait_h2; then
361+
# Check a few static files exist and return correct Content-Type
362+
check_header "GET /static/reset.css Content-Type" "Content-Type" "text/css" \
363+
-sk "https://localhost:$H2PORT/static/reset.css"
364+
365+
check_header "GET /static/app.js Content-Type" "Content-Type" "application/javascript" \
366+
-sk "https://localhost:$H2PORT/static/app.js"
367+
368+
check_header "GET /static/manifest.json Content-Type" "Content-Type" "application/json" \
369+
-sk "https://localhost:$H2PORT/static/manifest.json"
370+
371+
# Check response size is non-zero
372+
static_size=$(curl -sk -o /dev/null -w '%{size_download}' "https://localhost:$H2PORT/static/reset.css")
373+
if [ "$static_size" -gt 0 ]; then
374+
echo " PASS [static-h2 response size] ($static_size bytes)"
375+
PASS=$((PASS + 1))
376+
else
377+
echo " FAIL [static-h2 response size]: empty response"
171378
FAIL=$((FAIL + 1))
172379
fi
173-
sleep 1
174-
done
175380

176-
check "GET /baseline2?a=13&b=42 over HTTP/2" "55" \
177-
-sk "https://localhost:$H2PORT/baseline2?a=13&b=42"
178-
179-
# Cache detection: same URL, different query params
180-
A3=$((RANDOM % 900 + 100))
181-
B3=$((RANDOM % 900 + 100))
182-
EXPECTED3=$((A3 + B3))
183-
check "GET /baseline2?a=$A3&b=$B3 over HTTP/2 (cache check)" "$EXPECTED3" \
184-
-sk "https://localhost:$H2PORT/baseline2?a=$A3&b=$B3"
381+
# 404 for missing files
382+
check_status "GET /static/nonexistent.txt" "404" \
383+
-sk "https://localhost:$H2PORT/static/nonexistent.txt"
384+
fi
185385
fi
186386

387+
# ───── Summary ─────
388+
187389
echo ""
188390
echo "=== Results: $PASS passed, $FAIL failed ==="
189391
[ "$FAIL" -eq 0 ] || exit 1

0 commit comments

Comments
 (0)