V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
baobao1270
V2EX  ›  分享创造

Pure Shell HTTP Server

  •  
  •   baobao1270 ·
    baobao1270 · 2 天前 · 1205 次点击
    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
    
    11 条回复    2025-01-16 15:30:54 +08:00
    trepwq
        1
    trepwq  
       2 天前 via iPhone   ❤️ 1
    nc 换成 socat ,可以端口复用
    heimoshuiyu
        2
    heimoshuiyu  
       2 天前
    太酷辣
    sagaxu
        3
    sagaxu  
       2 天前
    python -m http.server $PORT
    php -S localhost:$PORT
    jwebserver -p $PORT
    ruby -run -e httpd -p $PORT
    baobao1270
        4
    baobao1270  
    OP
       2 天前
    @sagaxu 你这个要装编程环境啊,而且只能读不能写
    sagaxu
        5
    sagaxu  
       2 天前
    @baobao1270 大部分 Linux 发行版依赖 Python ,不用另外安装。以前写 web 服务的时候,直接用 linux 自带的 inetd 监听端口,收到请求时调用 CGI 调用处理程序,CGI 可以是任何语言写的,只要这个语言能读 stdio 和环境变量以及写入 stdout 。像 Ubuntu 标准版自带的 busybox 也自带了一个 httpd ,可以提供 CGI 转发。
    zsh2517
        6
    zsh2517  
       2 天前   ❤️ 1
    @baobao1270 偏个题,如果考虑编程环境,并且需要上传的话,可以选择 https://pypi.org/project/uploadserver/ ,用法和 http.server 一样 python3 -m uploadserver ,不过不是标准库,需要 python 环境并且 pip 装包。

    ---

    我之前也想过,有文件、网路 IO 的情况下,shell 是不是也能作为 web 服务器,结果今天真看到有人实现出来了
    zsh2517
        7
    zsh2517  
       2 天前
    @zsh2517 网路 -> 网络,键盘输入丢了个 o
    w568w
        8
    w568w  
       2 天前   ❤️ 1
    cool ,这才是真正的 shell

    另有一些语法风格上的建议:

    1. function 关键字是兼容一些远古 shell 给出的。既然指定了 bash ,用 server() {} 就好了;

    2. 函数内的变量最好用 local 声明,否则作用域会泄漏到函数外;

    3. 可以用 shellcheck 过一遍,可能有其他忽略的点
    dianso
        9
    dianso  
       2 天前   ❤️ 1
    shell 做不到的。
    你这个依赖 netcat 啊

    和 python go 开个 https 有啥区别。
    ruzztok
        10
    ruzztok  
       2 天前
    nc ,可不是所有系统都自带哦。。
    ai277014717
        11
    ai277014717  
       1 天前
    可以再交给 GPT 润色一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2866 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 05:49 · PVG 13:49 · LAX 21:49 · JFK 00:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.