#!/bin/bash # slow-server - start a web server that responds after a configurable delay # # Usage: # slow-server [port] # slow-server -h # # Options: # port Port to listen on (default: 8080) # -h, --help Show help message # # Starts a socat-based HTTP server where the last path segment of each request # is the delay in milliseconds. Requests without a numeric path return 404 # e.g. `curl http://localhost:8080/2500` responds after ~2.5 seconds _slow_server() ( local SCRIPT_NAME; SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" case "${BASH_SOURCE[0]}" in /dev/*|/proc/*) SCRIPT_NAME="" ;; esac case "$SCRIPT_NAME" in ""|bash|sh|zsh|dash) SCRIPT_NAME="slow-server" ;; esac _error() { echo "[ERR][$SCRIPT_NAME] $*" >&2; } _show_help() { local s; [ -t 1 ] && s=$'\033[4m' local r; [ -t 1 ] && r=$'\033[24m' echo "NAME" echo " $SCRIPT_NAME - start a web server that responds after a configurable delay" echo "SYNOPSIS" echo " $SCRIPT_NAME [${s}port${r}]" echo " $SCRIPT_NAME -h" echo "DESCRIPTION" echo " Starts a socat-based HTTP server on the given port (default 8080)." echo " The last path segment of each request is the delay in milliseconds." echo " Requests without a numeric path return 404." echo "EXAMPLES" echo " $SCRIPT_NAME 3000" echo " curl http://localhost:3000/2500 # responds after ~2.5 seconds" echo "OPTIONS" echo " ${s}port${r} Port to listen on (default: 8080)" echo " -h, --help Show this help message" echo "EXIT STATUS" echo " 0 Success (server exited cleanly)" echo " 3 Dependency error (socat missing)" echo " * socat exit code on other failures" echo "DEPENDENCIES" echo " socat, sleep (must support fractional seconds)" } # Function invoked by socat below on each request. Exported for socat's # SYSTEM: subshell to see it # shellcheck disable=SC2317,SC2329 # "Command appears to be unreachable. Check usage (or ignore if invoked indirectly)." / "This function is never invoked. Check usage (or ignored if invoked indirectly)." -- invoked indirectly via socat SYSTEM:_slow_response _slow_response() { _is_number() { case "$1" in ''|*[!0-9]*) return 1 ;; # not a number *) return 0 ;; # is a number esac } local method local path local _version read -r method path _version local last; last="$(basename "$path")" printf %s "$method $path ($last)" >&2 # Ignore other paths, return not found if ! _is_number "$last"; then echo " [ignored]" >&2 # append to previous line printf 'HTTP/1.1 404 Not Found\r\n\r\n' return 0 else local delay_ms="$last" printf '\n' >&2 # move to next line fi local seconds="$((delay_ms / 1000)).$((delay_ms % 1000))" sleep "$seconds" printf 'HTTP/1.1 200 OK\r\n\r\n%s\n' "$last" echo "Done: $method $path ($delay_ms)" >&2 unset -f _is_number } case "$1" in -h|--help) _show_help; return 0 ;; esac # Function arguments local port="${1:-8080}" # Check for socat dependency if ! command -v socat >/dev/null 2>&1; then _error "socat is required" return 3 fi export -f _slow_response # exported for socat # Start simple server echo "Starting server at http://localhost:$port" socat "TCP-LISTEN:$port,fork,reuseaddr" "SYSTEM:_slow_response" # _slow_response and any exports die with the wrapper subshell on return ) _slow_server "$@" __slow_server_rc=$? unset -f _slow_server if [ -n "${BASH_SOURCE[0]}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then eval "unset __slow_server_rc; return $__slow_server_rc" fi eval "unset __slow_server_rc; exit $__slow_server_rc"