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 服务:

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()

运行效果如下:

$ 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

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

$ 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 服务器可以通过以下代码实现:

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()

效果如下:

$ 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

示例代码如下:

"""
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

运行效果:

$ 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 直接调用:

$ 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 文档

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容