#!/usr/bin/env bash # Exercises a RUNNING instance (java -jar ... on :8080, PostgreSQL and Redis up) and writes every transcript in output/. # Each section resets the per-client rate-limit bucket first, so sections do not affect each other. set -uo pipefail cd "$(dirname "$0")/.." H=http://127.0.0.1:8080 PSQL=${PSQL:-"psql -h /tmp -p 5433 -U pgrun -d urlshort -X -q"} mkdir -p output reset_bucket() { redis-cli --raw DEL rl:127.0.0.1 > /dev/null; } # show METHOD PATH [json] -> status line + the headers that matter + body show() { local method=$1 path=$2 body=${3:-} echo "\$ curl -si -X $method $path${body:+ -d '$body'}" if [ -n "$body" ]; then curl -si -X "$method" -H 'Content-Type: application/json' -d "$body" "$H$path" else curl -si -X "$method" "$H$path" fi | tr -d '\r' | grep -E '^(HTTP/|Location:|Retry-After:|X-RateLimit-Remaining:|\{|$)' | grep -v '^$' echo } code_of() { sed -n 's/.*"code":"\([^"]*\)".*/\1/p'; } { reset_bucket BODY='{"url":"https://ankurm.com/java-interview-preparation-guide/"}' echo "\$ curl -si -X POST /api/v1/urls -d '$BODY'" RESP=$(curl -si -X POST -H 'Content-Type: application/json' -d "$BODY" $H/api/v1/urls | tr -d '\r') echo "$RESP" | grep -E '^(HTTP/|Location:|X-RateLimit-Remaining:|\{)'; echo C=$(echo "$RESP" | code_of) show GET "/$C" } > output/01-create-and-redirect.txt 2>&1 { reset_bucket C=$(curl -s -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com/cache-demo"}' $H/api/v1/urls | code_of) stat() { curl -s $H/api/v1/debug/cache-stats | sed 's/.*"hits":\([0-9]*\).*"misses":\([0-9]*\).*/\1 \2/; s/.*"misses":\([0-9]*\).*"hits":\([0-9]*\).*/\2 \1/'; } read h0 m0 < <(stat) delta() { read h m < <(stat); echo "cache hits +$((h-h0)), misses +$((m-m0))"; } echo "code = $C" echo "\$ redis-cli EXISTS url:$C # before the first redirect" redis-cli EXISTS "url:$C" curl -s -o /dev/null "$H/$C" echo "--- after 1st redirect (cache miss, database read, cache filled)" echo "\$ redis-cli GET url:$C"; redis-cli GET "url:$C" echo "\$ redis-cli TTL url:$C"; redis-cli TTL "url:$C" delta for i in 1 2 3 4 5 6 7 8 9; do curl -s -o /dev/null "$H/$C"; done echo "--- after 9 more redirects" delta echo "\$ redis-cli GET clicks:$C"; redis-cli GET "clicks:$C" echo "\$ curl -s $H/api/v1/urls/$C/stats"; curl -s "$H/api/v1/urls/$C/stats" | sed 's/"createdAt".*/.../'; echo } > output/02-cache-aside-and-clicks.txt 2>&1 { before=$(curl -s $H/api/v1/debug/cache-stats | sed 's/.*"misses":\([0-9]*\).*/\1/') show GET /Zq9Zq9 mid=$(curl -s $H/api/v1/debug/cache-stats | sed 's/.*"misses":\([0-9]*\).*/\1/') show GET /Zq9Zq9 after=$(curl -s $H/api/v1/debug/cache-stats | sed 's/.*"misses":\([0-9]*\).*/\1/') echo "cache misses: before=$before after-first-404=$mid after-second-404=$after" echo "\$ redis-cli GET url:Zq9Zq9 # the negative-cache marker"; redis-cli GET url:Zq9Zq9 echo "\$ redis-cli TTL url:Zq9Zq9"; redis-cli TTL url:Zq9Zq9 } > output/03-negative-cache.txt 2>&1 { reset_bucket redis-cli --raw DEL url:demo-link > /dev/null; $PSQL -c "DELETE FROM urls WHERE code='demo-link'" > /dev/null show POST /api/v1/urls '{"url":"https://example.com/one","alias":"demo-link"}' show POST /api/v1/urls '{"url":"https://example.com/two","alias":"demo-link"}' show POST /api/v1/urls '{"url":"ftp://example.com/file"}' show POST /api/v1/urls '{"url":"https://example.com","alias":"api"}' } > output/04-alias-and-validation.txt 2>&1 { reset_bucket echo "capacity 5, refill 0.5 tokens/second (one every 2 s)" for i in 1 2 3 4 5 6 7; do echo -n "request $i -> " curl -si -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com/rl"}' $H/api/v1/urls \ | tr -d '\r' | grep -E '^(HTTP/|Retry-After:|X-RateLimit-Remaining:)' | paste -sd' ' - done echo "\$ redis-cli HGETALL rl:127.0.0.1"; redis-cli HGETALL rl:127.0.0.1 echo "--- sleeping 2.2 s"; sleep 2.2 echo -n "request 8 -> " curl -si -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com/rl"}' $H/api/v1/urls \ | tr -d '\r' | grep -E '^(HTTP/|Retry-After:|X-RateLimit-Remaining:)' | paste -sd' ' - echo "--- redirects are not limited: 30 GETs in a row" reset_bucket C=$(curl -s -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com/free"}' $H/api/v1/urls | code_of) for i in $(seq 1 30); do curl -s -o /dev/null -w '%{http_code} ' "$H/${C:-x}"; done; echo } > output/05-rate-limit.txt 2>&1 { reset_bucket C=$(curl -s -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com/temp","ttlSeconds":2}' $H/api/v1/urls | code_of) echo "created $C with ttlSeconds=2" echo -n "t=0s -> "; curl -s -o /dev/null -w '%{http_code}\n' "$H/$C" echo "\$ redis-cli TTL url:$C # capped at the link's remaining life, not the 1 h default"; redis-cli TTL "url:$C" sleep 3 echo -n "t=3s -> "; curl -s -o /dev/null -w '%{http_code}\n' "$H/$C" } > output/06-expiry.txt 2>&1 echo "transcripts 01-06 written to output/ (run scripts/id-generation-demo.sh for 07 and 08)"