手动启动 HTTP 文件服务

在把程序写成自动运行的系统服务前,通常先在命令行手动跑一次,验证参数、端口和目录路径能不能正常工作。我们要用 Python 内置的 HTTP 模块把 /srv/bulletin 目录发布到本机 8000 端口,并把访问日志写进文件:

crow@opsbox:~$ mkdir -p ~/work
crow@opsbox:~$ cd ~/work
crow@opsbox:~/work$ python3 -u -m http.server 8000 --bind 127.0.0.1 --directory /srv/bulletin > manual.log 2>&1 &
[1] 2300

这条命令各个参数的含义:

  • python3 -m http.server 8000:启动内置 HTTP 静态文件服务并监听 TCP 8000 端口(Linux 中 1~1023 是特权端口,需要 root 权限才能绑定;1024~65535 普通账号即可直接监听);
  • --bind 127.0.0.1:只绑定本机环回地址,只允许本机内部访问;
  • --directory /srv/bulletin:指定对外提供文件的根目录;
  • -u:关闭 Python 的输出缓冲区(Unbuffered),让每条日志立刻写入文件;
  • > manual.log 2>&1:把标准输出(1)和标准错误(2,http.server 默认把访问日志打在标准错误上)都重定向到 manual.log;
  • &:放到后台运行,记下回显的 PID(这里是 2300)。

检查端口并访问

启动后先用 ss(Socket Statistics)看一眼 8000 端口有没有处于监听状态:

crow@opsbox:~/work$ ss -ltnp 'sport = :8000'
State   Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN  0      5      127.0.0.1:8000     0.0.0.0:*    users:(("python3",pid=2300,fd=3))

常用的四个参数可以连记为 -ltnp:

  • -l(listening):只看处于监听状态的套接字;
  • -t(tcp):只看 TCP 协议(看 UDP 用 -u);
  • -n(numeric):直接显示数字形式的 IP 和端口号,不做服务名反解;
  • -p(processes):显示占用该端口的进程名与 PID(查看其他用户的进程需要加 sudo)。

确认 127.0.0.1:8000 正在监听后,用 curl 发一个请求测试:

crow@opsbox:~/work$ curl -sS http://127.0.0.1:8000/hours.txt
图书馆开放时间:09:00-18:00
crow@opsbox:~/work$ tail -n 2 manual.log
127.0.0.1 - - [...] "GET /hours.txt HTTP/1.1" 200 -

curl -sS 拿到了文件内容,manual.log 里也记下了这次 GET /hours.txt 请求和状态码 200。如果想把文件直接下载保存到本地磁盘,可以用 wget -O 指定保存文件名:

crow@opsbox:~/work$ wget -O downloaded-hours.txt http://127.0.0.1:8000/hours.txt
crow@opsbox:~/work$ cat downloaded-hours.txt
图书馆开放时间:09:00-18:00

常见故障排查

如果客户端请求了不存在的文件,服务会返回 404:

crow@opsbox:~/work$ curl -I http://127.0.0.1:8000/not-here.txt
HTTP/1.0 404 File not found

curl -I 只请求 HTTP 响应头。收到 404 说明网络连通性和服务进程都没问题,只是按 URL 路径找不到对应文件——排查时核对文件名大小写、--directory 指定的目录路径,以及文件是否确实在该目录下即可。

另一类常见报错是端口被占用。在 8000 端口还没释放时再起一个相同端口的服务,就会报错:

crow@opsbox:~/work$ python3 -m http.server 8000 --bind 127.0.0.1 --directory /srv/bulletin
Traceback (most recent call last):
  ...
OSError: [Errno 98] Address already in use

同一个 IP 地址上的同一个端口号,同一时刻只能被一个服务绑定。遇到 Address already in use 时,运行 sudo ss -ltnp 'sport = :端口号' 找出占着端口的进程 PID,看它是残留的旧进程还是其他服务,再决定杀掉旧进程或换个端口。

现在手动测试已经跑通,把刚才启动的测试进程停掉(PID 换成实际数值):

crow@opsbox:~/work$ kill 2300
crow@opsbox:~/work$ ss -ltnp 'sport = :8000'

确认没有 LISTEN 输出后,8000 端口就释放出来了。接下来我们把服务正式交给 systemd 管理。

本章阅读

阅读标记