1. 项目概述用Shell脚本打造轻量级HTTP文件服务器在Linux系统管理员的工具箱里Shell脚本一直是最趁手的瑞士军刀。最近我在调试远程服务器时经常需要临时共享几个日志文件给同事分析但又不值得为此专门配置Nginx这样的重量级服务。于是花了点时间用纯Shell实现了一个不足50行的HTTP文件服务器脚本完美解决了这个痛点。这个方案特别适合以下场景快速共享当前目录下的文件如开发中的原型demo内网临时传输大文件避免scp多次验证的麻烦在受限环境中提供基础Web服务如嵌入式设备作为教学案例理解HTTP协议本质相比专业Web服务器它的优势在于零依赖、即开即用执行一个脚本就能启动服务用完CtrlC直接退出不会留下任何残余进程或配置文件。下面我将从协议原理到代码实现完整拆解这个实用工具的开发过程。2. HTTP协议基础与Shell实现方案2.1 HTTP/1.1 最小化实现我们的脚本只需要实现HTTP/1.1最基础的两个功能处理GET请求返回文件内容列出目录索引类似Nginx的autoindex关键响应头包括Content-Type根据文件后缀匹配MIME类型Content-Length精确计算文件字节数Connection: close保持简单的一次性连接典型的请求-响应交互示例GET /test.txt HTTP/1.1 Host: localhost HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 23 Connection: close This is a test file...2.2 Shell方案技术选型实现方式对比方案优点缺点nc(netcat)持久化监听简单直接需处理并发冲突socat支持更复杂网络操作非默认安装bash内置TCP纯bash环境可用代码可读性差最终选择nc方案因为绝大多数Linux发行版预装netcat工具通过文件描述符重定向实现IO控制配合fork处理简单并发核心代码结构#!/bin/bash PORT8080 TIMEOUT30 response() { # 构造HTTP响应 } list_files() { # 生成目录列表HTML } nc -kl $PORT | while read line; do # 解析请求行 # 路由处理逻辑 done3. 完整实现解析3.1 启动与请求处理初始化部分需要注意#!/bin/bash set -euo pipefail # 严格错误检查 PORT${1:-8080} # 支持自定义端口 ROOT${2:-.} # 可指定根目录 TIMEOUT15 # 连接超时设置 # 检查nc是否可用 if ! command -v nc /dev/null; then echo Error: netcat (nc) is required 2 exit 1 fi请求解析关键代码nc -v -l -p $PORT -w $TIMEOUT | while read -r line; do # 只处理第一个请求行 if [[ $line ~ GET .* HTTP/1.[01] ]]; then path${line#GET } path${path% HTTP*} path${path//\%20/ } # URL解码空格 # 安全校验防止目录穿越 if [[ $path *..* ]]; then response 403 Forbidden text/plain Path traversal detected continue fi filepath${ROOT}/${path#/} [ -d $filepath ] list_files $filepath [ -f $filepath ] send_file $filepath fi done3.2 文件传输实现文件发送函数要点send_file() { local file$1 local mimeapplication/octet-stream # 常见MIME类型匹配 case $file in *.html|*.htm) mimetext/html ;; *.txt) mimetext/plain ;; *.jpg|*.jpeg) mimeimage/jpeg ;; *.png) mimeimage/png ;; *.pdf) mimeapplication/pdf ;; esac # 使用stat获取精确文件大小兼容不同系统 size$(stat -c%s $file 2/dev/null || stat -f%z $file) # 构造响应头 printf HTTP/1.1 200 OK\r\n printf Content-Type: %s\r\n $mime printf Content-Length: %s\r\n $size printf Connection: close\r\n\r\n # 发送文件内容 cat $file }3.3 目录列表功能实现类Nginx的autoindex效果list_files() { local dir$1 local base${dir#$ROOT/} # HTML头 printf HTTP/1.1 200 OK\r\n printf Content-Type: text/html\r\n\r\n printf !DOCTYPE htmlhtmlheadtitleIndex of %s/title/headbody\n $base printf h1Index of %s/h1hrpre\n $base # 父目录链接非根目录时 [ $dir ! $ROOT ] printf a href\../\..//a\n # 文件列表 ls -lh $dir | while read -r perm links user group size date time name; do [ $name . ] continue printf a href\%s\%s/a %s %s %8s %s %s\n \ $name $name $perm $user:$group $size $date $time done printf /prehr/body/html\n }4. 高级功能与优化技巧4.1 并发处理改进基础版本在传输大文件时会阻塞新请求改进方案nc -kl $PORT -w $TIMEOUT -c # 为每个连接创建子进程 while read -r line; do if [[ $line ~ ^GET ]]; then ( handle_request $line ) break fi done 4.2 性能优化实测测试对比传输100MB文件方案传输时间CPU占用原始脚本12.3s35%启用压缩8.7s68%分块传输10.1s42%启用gzip压缩的代码片段if [[ $file ~ \.(html|txt|css|js)$ ]]; then printf Content-Encoding: gzip\r\n gzip -c $file | { printf HTTP/1.1 200 OK\r\n printf Content-Type: %s\r\n $mime printf Content-Length: %s\r\n $(stat -c%s (gzip -c $file)) printf Connection: close\r\n\r\n cat } return fi4.3 安全增强措施请求频率限制declare -A ip_count MAX_REQ30 # 每分钟最大请求数 if (( ${ip_count[$REMOTE_ADDR]} MAX_REQ )); then response 429 Too Many Requests text/plain Rate limit exceeded continue fi文件类型黑名单DANGEROUS_EXTS(.php .py .sh .exe) for ext in ${DANGEROUS_EXTS[]}; do if [[ $filepath *$ext ]]; then response 403 Forbidden text/plain File type not allowed continue 2 fi done5. 实用场景与问题排查5.1 典型使用案例快速共享开发构建产物# 在构建目录启动服务 ./httpserver.sh 3000 ./dist内网传输虚拟机镜像# 使用screen保持后台运行 screen -dmS fileserver ./httpserver.sh 8080 /var/lib/vm_images嵌入式设备调试接口# 在路由器上提供日志访问 busybox nc -ll -p 80 -e ./httpserver.sh /tmp/logs5.2 常见问题解决端口冲突问题# 查看端口占用 ss -tulnp | grep :8080 # 指定其他端口 ./httpserver.sh 8081大文件传输中断调整TIMEOUT值默认15秒可能不足使用split分卷压缩传输split -b 100m large_file.zip large_file_part_中文文件名乱码# 在list_files函数中添加编码声明 printf meta charset\utf-8\\n性能瓶颈分析# 使用strace跟踪系统调用 strace -f -o trace.log ./httpserver.sh6. 扩展思路与替代方案6.1 功能扩展方向支持PUT/POST上传文件if [[ $line ~ ^PUT ]]; then # 读取Content-Length while read -r line; do [[ $line ~ ^Content-Length:\ ([0-9]) ]] \ length${BASH_REMATCH[1]} [ -z $line ] break done # 保存文件内容 head -c $length $filepath response 201 Created text/plain Upload successful fi添加基础认证AUTHuser:pass if ! [[ $AUTH $(echo $line | grep -o Basic .* | cut -d -f2 | base64 -d) ]]; then printf HTTP/1.1 401 Unauthorized\r\n printf WWW-Authenticate: Basic realm\Secure Area\\r\n\r\n exit fi6.2 同类工具对比工具语言特点适用场景webfsC超轻量嵌入式设备python -m http.serverPython零配置但依赖Python环境开发调试http-serverNode.js功能丰富但体积大前端开发本方案Shell无需额外依赖临时/受限环境6.3 生产环境建议虽然这个脚本足够应付临时需求但在正式环境中还是建议需要HTTPS支持时改用Caddy高并发场景使用NginxFastCGI长期运行的服务应添加日志轮转重要数据需配合防火墙规则最后分享一个实用技巧把这个脚本保存为/usr/local/bin/serve并添加可执行权限就能在任意目录通过serve命令快速启动临时文件服务。我在排查线上问题时经常用它共享日志比反复scp传输高效得多。
阅读完成 · 觉得有帮助?