Skip to content

Instantly share code, notes, and snippets.

@calston
Created May 5, 2026 10:34
Show Gist options
  • Select an option

  • Save calston/ea98706e6f210c3e640103ddba9ed4f0 to your computer and use it in GitHub Desktop.

Select an option

Save calston/ea98706e6f210c3e640103ddba9ed4f0 to your computer and use it in GitHub Desktop.
A bash webserver
#!/bin/bash
# Simple bash web server that serves files from ./static
PORT=${1:-8080}
STATIC_DIR="./static"
PIPE="/tmp/bashserver_$$"
NC_PID=""
# Cleanup on exit
cleanup() {
echo -e "\nShutting down server..."
# Kill any running nc process
[[ -n "$NC_PID" ]] && kill "$NC_PID" 2>/dev/null
# Kill all child processes
pkill -P $$ 2>/dev/null
rm -f "$PIPE"
echo "Server stopped."
exit 0
}
trap cleanup INT TERM EXIT
# Create named pipe
rm -f "$PIPE"
mkfifo "$PIPE"
echo "Starting server on port $PORT..."
echo "Serving files from $STATIC_DIR"
echo "Press Ctrl+C to stop"
get_mime_type() {
case "$1" in
*.html) echo "text/html" ;;
*.css) echo "text/css" ;;
*.js) echo "application/javascript" ;;
*.json) echo "application/json" ;;
*.png) echo "image/png" ;;
*.jpg|*.jpeg) echo "image/jpeg" ;;
*.gif) echo "image/gif" ;;
*.svg) echo "image/svg+xml" ;;
*.txt) echo "text/plain" ;;
*) echo "application/octet-stream" ;;
esac
}
handle_request() {
local request_line path method filepath mime_type content_length
read -r request_line
# Parse the request
method=$(echo "$request_line" | cut -d' ' -f1)
path=$(echo "$request_line" | cut -d' ' -f2)
# Read and discard headers
while read -r header; do
header=$(echo "$header" | tr -d '\r')
[ -z "$header" ] && break
done
# Default to index.html for root
[ "$path" = "/" ] && path="/index.html"
# Build file path
filepath="$STATIC_DIR$path"
# Security: prevent directory traversal
if [[ "$path" == *".."* ]]; then
printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n403 Forbidden"
return
fi
echo "[$(date)] $method $path" >&2
if [ -f "$filepath" ]; then
mime_type=$(get_mime_type "$filepath")
content_length=$(wc -c < "$filepath" | tr -d ' ')
printf "HTTP/1.1 200 OK\r\n"
printf "Content-Type: %s\r\n" "$mime_type"
printf "Content-Length: %s\r\n" "$content_length"
printf "Connection: close\r\n"
printf "\r\n"
cat "$filepath"
else
local body="404 Not Found: $path"
printf "HTTP/1.1 404 Not Found\r\n"
printf "Content-Type: text/plain\r\n"
printf "Content-Length: %s\r\n" "${#body}"
printf "Connection: close\r\n"
printf "\r\n"
printf "%s" "$body"
fi
}
# Main server loop using named pipe
while true; do
nc -l "$PORT" < "$PIPE" | handle_request > "$PIPE" &
NC_PID=$!
wait $NC_PID 2>/dev/null
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment