Pure Shell HTTP Server

baobao1270 · 昨天 22:28 · 927 次点击
  1. 这是一个纯 shell 实现的 HTTP 服务器,能够使用 GET/POST 请求读写 Working directory 下的文件。
  2. 可以通过 PORT 和 KEY 环境变量设置监听的端口和写入操作的 API Key 。如果没有指定,那么会自动生成一个 UUID 作为 API Key ,并且在控制台打印出来。写入操作应该设置 Authorization 头为: APIKey 你的 API 密钥
  3. 对于中文或者没有正确 URL Encode 的请求支持应该不太好。
  4. 如果想要结束服务器,请在服务器目录下运行 kill -9 $(cat server.pid)
  5. 虽然做了一些安全措施,但是 shell 写的东西总归有些危险,不建议在有个人数据且暴露公网的设备上长期运行。
  6. 当然,这个服务器很简陋,也不支持多进程、多连接、可能有 race condition 问题,但是 just for fun ,更多的是作为一个「 shell 也可以写 HTTP 服务器」的概念验证。如果对代码有什么建议也欢迎提出。
#!/bin/bash
function server {
	read il
	echo "recv_il: $il" >&2
	method=$( echo "$il" | cut -d" " -f1)
	path=$(   echo "$il" | cut -d" " -f2)
	proto=$(  echo "$il" | cut -d" " -f3)
	echo "method: $method" >&2
	echo "path:   $path"   >&2
	echo "proto:  $proto"  >&2

	declare -A hdr
	while read line; do
		sline=`echo $line | tr -d '[\r\n]'`
		[ -z "$sline" ] && break
		echo "recv_hdr: $line" >&2
		hdr_k=$(echo $line | cut -d":" -f1)
		hdr_v=$(echo $line | cut -d":" -f2- | cut -c2- | tr -d '[\r\n]')
		hdr[$hdr_k]="$hdr_v"
	done
	echo "recv_hdr_end" >&2

	relpath=$(realpath "$(pwd)$path")
	if [[ "$relpath" != "$(pwd)"* ]]; then
		echo "possible path traversal attack: $relpath" >&2
		echo "pwd:     $(pwd)"   >&2
		echo "relpath: $relpath" >&2
		echo -ne "HTTP/1.1 403 Forbidden\r\n"
		echo -ne "\r\n"
		echo -ne "possible path traversal attack: $relpath"
		exit 0
	fi
	echo "relpath: $relpath" >&2
	if [ $method = "GET" ]; then
		if [ ! -f "$relpath" ]; then
			echo -ne "HTTP/1.1 404 Not Found\r\n"
			echo -ne "\r\n"
			echo -ne "not found: $relpath"
			exit 0
		fi
		echo -ne "HTTP/1.1 200\r\n"
		echo -ne "\r\n"
		cat  "$relpath"
		exit 0
	fi

	if [ $method != "POST" ]; then
		echo -ne "HTTP/1.1 405 Method Not Allowed\r\n"
		echo -ne "\r\n"
		exit 0
	fi

	if [ "${hdr[Authorization]}" != "APIKey $KEY" ]; then
		echo -ne "HTTP/1.1 401 Unauthorized\r\n"
		echo -ne "\r\n"
		exit 0
	fi

	body_file=$(mktemp)
	body_len=0
	if [ ! -z "${hdr[Content-Length]}" ]; then
		echo "hdr_cl > body_len"   >&2
		body_len="${hdr[Content-Length]}"
	fi
	echo "body_len:  $body_len"    >&2
	echo "body_file: $body_file"   >&2
	dd of=$body_file bs=1 count=$body_len

	mkdir -p $(dirname "$relpath") >&2
	cp -v $body_file "$relpath"    >&2

	echo -ne "HTTP/1.1 201 Created\r\n"
	echo -ne "\r\n"
	echo -ne "created: $relpath\r\n"
	rm -rvf $body_file             >&2
}

if [ -z "$PORT" ]; then
	PORT=3000
fi

if [ -z "$KEY" ]; then
	KEY=$(uuidgen)
fi

if [ "$EXEC" = "server" ]; then
	server
	exit 0
fi

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
PID=$$
PIDFILE="$SCRIPT_DIR/server.pid"

if [[ -f "$PIDFILE" && -d "/proc/$(cat $PIDFILE)" ]]; then
	echo "one instance is running, refuse to start another"
	exit 1
fi

if [ -f "server.sh" ]; then
	echo "DO NOT START SERVER WHEN CURRENT WORKING DIRECTORY IS SAME AS SCRIPT DIRECTORY"
	echo "THIS MAY CAUSE UNEXPECTED OVERWRITING SERVER AND RCE"
	echo "EXITING"
	exit 1
fi

echo "PID=$PID"
echo $PID > $PIDFILE
echo "PIDFILE=$PIDFILE"
echo "KEY=$KEY"

while true; do
	nc -vlp $PORT -c "EXEC=server KEY=$KEY $0"
done
举报· 927 次点击
登录 注册 站外分享
8 条回复  
w568w 小成 2 小时前
cool ,这才是真正的 shell 另有一些语法风格上的建议: 1. function 关键字是兼容一些远古 shell 给出的。既然指定了 bash ,用 server() {} 就好了; 2. 函数内的变量最好用 local 声明,否则作用域会泄漏到函数外; 3. 可以用 shellcheck 过一遍,可能有其他忽略的点
zsh2517 小成 3 小时前
@zsh2517 网路 -> 网络,键盘输入丢了个 o
zsh2517 小成 3 小时前
@baobao1270 偏个题,如果考虑编程环境,并且需要上传的话,可以选择 https://pypi.org/project/uploadserver/ ,用法和 http.server 一样 python3 -m uploadserver ,不过不是标准库,需要 python 环境并且 pip 装包。 --- 我之前也想过,有文件、网路 IO 的情况下,shell 是不是也能作为 web 服务器,结果今天真看到有人实现出来了
sagaxu 初学 昨天 23:54
@baobao1270 大部分 Linux 发行版依赖 Python ,不用另外安装。以前写 web 服务的时候,直接用 linux 自带的 inetd 监听端口,收到请求时调用 CGI 调用处理程序,CGI 可以是任何语言写的,只要这个语言能读 stdio 和环境变量以及写入 stdout 。像 Ubuntu 标准版自带的 busybox 也自带了一个 httpd ,可以提供 CGI 转发。
baobao1270 楼主 小成 昨天 23:28
@sagaxu 你这个要装编程环境啊,而且只能读不能写
sagaxu 初学 昨天 23:07
python -m http.server $PORT php -S localhost:$PORT jwebserver -p $PORT ruby -run -e httpd -p $PORT
heimoshuiyu 小成 昨天 22:53
太酷辣
trepwq 小成 昨天 22:42
nc 换成 socat ,可以端口复用
返回顶部