107 lines
5.0 KiB
Bash
Executable File
107 lines
5.0 KiB
Bash
Executable File
#!/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)"
|