Python Tricks —— 简单的 HTTP 和 FTP 服务

之前有位同事,需要在两台电脑间传输一个很大的文件,前提是只开放了 80 端口。我以为他会配个 Apache 之类的 HTTP 服务或者 WebDAV 什么的。
他只用了一条命令:python -m http.server 80
后来我想也是,有时候面对问题,一个很简单的且还算完美的方案,即便不是通用的或者完备的,就解决问题而言,未尝不是一个好的选择。

http.server

http.server
) 是 Python3 的一个内置模块,源代码在 Lib/http/server.py 中,它定义了用来实现 HTTP 服务的类。

其中的 SimpleHTTPRequestHandler 类可以用来在当前目录下创建一个基本的 HTTP 服务:

1
2
3
4
5
6
7
8
9
10
import http.server
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("serving at port", PORT)
httpd.serve_forever()

运行效果如下:

1
2
3
4
5
$ python test.py
serving at port 8000
127.0.0.1 - - [06/Apr/2019 22:55:26] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [06/Apr/2019 22:55:41] "GET /pyqt/ HTTP/1.1" 200 -
127.0.0.1 - - [06/Apr/2019 22:55:56] "GET /test.py HTTP/1.1" 200 -

SimpleHTTPRequestHandler
因为当前目录下没有 index.html 文件,所以显示的是类似 FTP 站点上的那种文件列表。

当然,http.server 也可以通过 Python 的 -m 选项在命令行中直接调用:
$ python -m http.server

默认会在 8000 端口打开 HTTP 服务,也可以手动指定端口:
$ python -m http.server 8080

同时,该服务会绑定本机的所有网络接口(0.0.0.0),也可以手动指定:
$ python -m http.server 8000 -b 127.0.0.1

其他支持的命令行选项可以参考帮助信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python -m http.server -h
usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]

positional arguments:
port Specify alternate port [default: 8000]

optional arguments:
-h, --help show this help message and exit
--cgi Run as CGI Server
--bind ADDRESS, -b ADDRESS
Specify alternate bind address [default: all
interfaces]
--directory DIRECTORY, -d DIRECTORY
Specify alternative directory [default:current
directory]

pyftpdlib

pyftpdlib 是一个非常高效的异步的 FTP 库,可以为 Python 编写 FTP 服务提供高级的可移植的编程接口。
pyftpdlib 是 Python 的第三方库,需要使用包管理器安装:
pip install pyftpdlib

安装成功以后,一个最基本的 FTP 服务器可以通过以下代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import os

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer


def main():
# 初始化验证器
authorizer = DummyAuthorizer()

# 创建有读写权限的用户和只读权限的匿名用户
authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
authorizer.add_anonymous(os.getcwd())

# 实例化 FTPHandler 类
handler = FTPHandler
handler.authorizer = authorizer

# 自定义提示信息
handler.banner = "pyftpdlib based ftpd ready."

# Specify a masquerade address and the range of ports to use for
# passive connections. Decomment in case you're behind a NAT.
#handler.masquerade_address = '151.25.42.11'
#handler.passive_ports = range(60000, 65535)

# FTP 服务监听于 0.0.0.0:2121
address = ('', 2121)
server = FTPServer(address, handler)

# 连接限制
server.max_cons = 256
server.max_cons_per_ip = 5

# 开启 FTP 服务
server.serve_forever()

if __name__ == '__main__':
main()

效果如下:

1
2
3
4
5
6
7
8
9
$ python ftp.py
[I 2019-04-07 15:08:37] >>> starting FTP server on :::2121, pid=9952 <<<
[I 2019-04-07 15:08:37] concurrency model: async
[I 2019-04-07 15:08:37] masquerade (NAT) address: None
[I 2019-04-07 15:08:37] passive ports: None
[I 2019-04-07 15:08:44] ::1:63265-[] FTP session opened (connect)
[I 2019-04-07 15:08:44] ::1:63265-[anonymous] USER 'anonymous' logged in.
[I 2019-04-07 15:08:44] ::1:63265-[anonymous] CWD D:\Program\python 250
[I 2019-04-07 15:08:44] ::1:63265-[anonymous] FTP session closed (disconnect).

pyftpdlib

pyftpdlib 从 0.6.0 版本开始是完全支持 FTPS (FTP over TLS/SSL) 的,需要提前安装好 PyOpenSSL 模块:
pip install pyopenssl

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""
An RFC-4217 asynchronous FTPS server supporting both SSL and TLS.
Requires PyOpenSSL module (http://pypi.python.org/pypi/pyOpenSSL).
"""

from pyftpdlib.servers import FTPServer
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import TLS_FTPHandler


def main():
authorizer = DummyAuthorizer()
authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
authorizer.add_anonymous('.')
handler = TLS_FTPHandler
handler.certfile = 'keycert.pem'
handler.authorizer = authorizer
# requires SSL for both control and data channel
#handler.tls_control_required = True
#handler.tls_data_required = True
server = FTPServer(('', 21), handler)
server.serve_forever()

if __name__ == '__main__':
main()

TLS_FTPHandler 类需要至少一个 certfile 和一个可选的 keyfile。可以参考 Apache FAQs 自行生成证书文件,也可以直接下载 pyftpdlib 提供的示例文件 keycert.pem

FTP 客户端可以使用 WinSCP,截图如下:
WinSCP

运行效果:

1
2
3
4
5
6
7
8
$ python ftps.py
[I 2019-04-07 17:20:24] >>> starting FTP+SSL server on :::21, pid=5028 <<<
[I 2019-04-07 17:20:24] concurrency model: async
[I 2019-04-07 17:20:24] masquerade (NAT) address: None
[I 2019-04-07 17:20:24] passive ports: None
[I 2019-04-07 17:20:36] ::1:64934-[] FTP session opened (connect)
[I 2019-04-07 17:20:36] ::1:64934-[user] USER 'user' logged in.
[I 2019-04-07 17:20:36] ::1:64934-[user] CWD D:\Program\python 250

FTPS

同 http.server 类似,pyftpdlib 也可以在命令行中通过 python -m 直接调用:

1
2
3
4
5
$ python -m pyftpdlib
[I 2019-04-07 17:27:51] >>> starting FTP server on 0.0.0.0:2121, pid=1100 <<<
[I 2019-04-07 17:27:51] concurrency model: async
[I 2019-04-07 17:27:51] masquerade (NAT) address: None
[I 2019-04-07 17:27:51] passive ports: None

匿名用户有写权限:
$ python -m pyftpdlib -w

设置特定的监听地址、端口号和 home 目录:
$ python -m pyftpdlib -i localhost -p 8021 -d /home/someone

设置登录用户和密码(匿名用户则会被禁用)
$ python -m pyftpdlib -u username -P password

获取帮助信息:
$ python -m pyftpdlib -h

拓展资料

http.server 源码
http.server 文档
pyftpdlib 文档