#!/usr/bin/env bash declare -A clients http_codes=( [200]=OK [400]="Bad Request" [403]=Forbidden [404]="Not Found" [405]="Method Not Allowed" ) enable -f "${SHERVER_SO_PATH-.}"/sherver.so sherver_{bind,select,accept} if ! sherver-bind -p 8080 -h 0.0.0.0 -v server_fd; then exit fi while true; do if ! sherver-select -iv ready_fds "$server_fd" "${!clients[@]}"; then continue fi for fd in "${ready_fds[@]}"; do if (( fd == server_fd )) && sherver-accept -v client_info "$server_fd"; then declare -A __client_{req,resp}_"${client_info[0]}"_ declare -n client_req=__client_req_${client_info[0]}_ client_req[ip]=${client_info[1]} client_req[port]=${client_info[2]} clients[${client_info[0]}]=0 else declare -n client_req=__client_req_${fd}_ declare -n client_resp=__client_resp_${fd}_ if ! IFS= read -ru "$fd" line || (( ! ${#line} )); then unset "__client_"{req,resp}"_${fd}_" "clients[$fd]" exec {fd}>&- else (( clients[$fd] += ${#line} + 1 )) line=${line%$'\r'} if [[ ! -v client_req[method] ]]; then read -ra req_line <<< "$line" client_req[method]=${req_line[0],,} client_req[req-file]=${req_line[1]} client_req[version]=${req_line[2]-HTTP/1.0} case ${client_req[method]} in get) client_resp[has-payload]=yes ;; head) client_resp[has-payload]=no ;; *) client_req[error]=yes client_resp[code]=405 esac if [[ ${client_req[req-file]} = /* ]]; then client_req[real-file]=.${client_req[req-file]} if [[ ${client_req[req-file]} = */ ]]; then client_req[real-file]+=index.html elif [[ -d client_req[real-file] ]]; then client_req[real-file]+=/index.html fi else client_req[error]=yes client_resp[code]=400 fi if [[ ${client_req[error]} != yes ]]; then if [[ -f ${client_req[real-file]} ]]; then if [[ -r ${client_req[real-file]} ]]; then client_resp[code]=200 else client_req[error]=yes client_resp[code]=403 fi else client_req[error]=yes client_resp[code]=404 fi fi elif [[ $line = *": "* ]]; then hdr_key=${line%%:*} hdr_key=${hdr_key,,} client_req[hdr-$hdr_key]=${line#*: } elif [[ -z $line ]]; then req_host=${client_req[hdr-host]-"$HOSTNAME"} ver=${client_req[version]} code=${client_resp[code]} if [[ ${client_req[error]} = yes ]]; then client_req[real-file]=./error.html client_resp[has-payload]=yes fi payload=$(<"${client_req[real-file]}") client_resp[hdr-Content-Length]=${#payload} case ${client_req[req-file]} in *.html) client_resp[hdr-Content-Type]=text/html ;; *.txt) client_resp[hdr-Content-Type]=text/plain ;; *) client_resp[hdr-Content-Type]=application/octet-stream esac printf -v 'client_resp[hdr-Date]' '%(%Y-%m-%d %H:%M:%S)T' -1 printf '%s %d %s\r\n' "$ver" "$code" "${http_codes[$code]}" >&"$fd" for hdr in "${!client_resp[@]}"; do if [[ $hdr = hdr-* ]]; then printf '%s: %s\r\n' "${hdr#hdr-}" "${client_resp[$hdr]}" >&"$fd" fi done printf '\r\n' >&"$fd" if [[ ${client_resp[has-payload]} = yes ]]; then printf %s "$payload" >&"$fd" fi printf '[%s] %s has requested http://%s%s (%s)\n' "${client_resp[hdr-Date]}" "${client_req[ip]}" "$req_host" "${client_req[req-file]}" "${client_req[real-file]}" fi fi fi done done