python 搭建 https 服务器:方法、困难回顾
之前用 Python 3 实现过一个基于 TCP socket、然后手动解析 HTTP 协议数据包的简易 HTTP 服务器;这两天探索将其改装成支持 HTTPS 的版本。本文对过程中的基本方法、遇到的一系列问题等予以回顾。
一、基本思路:ssl
库的 wrap_socket()
ssl
库提供的 wrap_socket(...)
函数,能直接把原有的明文 TCP socket 包装成 ssl 加密的形式,且(似乎?)不影响其它功能。
注意,包装的是每次 accept 得到的连接、而不是一开始就创建的那个监听的连接。
假设原有 HTTP 服务器是:
import socket
class MyServer:
def __init__(self, port):
self.socket_1 = socket.socket(socket.AF_INET , socket.SOCK_STREAM)
self.socket_1.bind(('0.0.0.0', port))
self.socket_1.listen(10)
def run(self):
while True:
try:
conn, addr = self.socket_1.accept()
conn = self.wrap(conn) # 预埋 socket 的包装接口
req_bytes = conn.recv(1024)
# ...略
def wrap(self, conn):
return conn
则 HTTPS 版本直接继承,并利用包装接口进行包装:
class MyHttpsServer(MyServer):
def __init__(self, port, ssl_ctx):
super().__init__(port)
self.ssl_ctx = ssl_ctx
def wrap(self, conn):
return self.ssl_ctx.wrap_socket(conn, server_side=True)
这里的 ssl_ctx
是用 ssl
库创建的 context
对象:
# main.py
import ssl
from xxx import MyHttpsServer
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(certfile='/path/to/xxxx.pem')
server = MyHttpsServer(10022, ssl_ctx)
(端口10022是我随便选的;certfile
后面会详细介绍)
主要参考资料:
二、证书相关文件的获取、处理
我的 https 证书是在腾讯云申请的。申请后可以下载各种格式。
得到 .jks
文件及其密码文件后,我的做法是转换成 .pem
文件。
转换的主要参考资料(两种方式):
- (我成功) after-the-sunrise/JKS to PEM
- (我暂时没成功) Converting a Java Keystore Into PEM Format 的第4章《Converting a Single Certificate From a JKS Into PEM》
然后就可以传入上一章的 ssl_ctx.load_cert_chain()
函数里。
三、端口不通
注:端口不通问题,可以先用 HTTP 来测试,确保问题与 ssl 无关。
0 - 常用命令
netstat -antplo
lsof -i:端口号
都可以查看端口的状态信息。
1 - 云服务商的安全组
腾讯云、阿里云的控制台应该都有安全组可以配置,要去那里把对应的端口放通。
主要参考资料:
- 云服务器修改了安全组要重启吗
- 不需要,一般云服务器修改了安全组配置后很快就会生效,通常不需要重启服务器。
2 - linux服务器内的防火墙
如果是 linux 系统的服务器,首先要知道它可能有好几个防火墙,而且可能互相影响……请逐个排查。
主要参考资料:
- 关于ufw、firewalld及iptables之间的关系整理
- firewall和ufw可共同影响服务器,任一防火墙开启都会使端口无法连接
firewall-cmd --state
ufw status
- SELinux、Netfilter、iptables、firewall和UFW五者关系
- 第1篇:Linux防火墙-firewalld配置
- ubuntu20.04版本防火墙基本操作
ufw allow 10022
- Linux firewall-cmd开放和关闭端口
firewall-cmd --add-port=10022/tcp --permanent
之后还要firewall-cmd --reload
- 最全解决方案:Failed to start firewalld - dynamic firewall daemon
- 如果不是安装Python,那么一般都是firewalld的进程问题。
(同时感谢腾讯云技术咨询人工客服的解答!)
3 - 绑定的地址
不能用 localhost
、127.0.0.1
等,否则本机上 ssh 上去并 telnet localhost 10022
能连接、但公网访问就不能连接。
要用 0.0.0.0
(见第一章的示例代码)。
主要参考资料:
- 什么是 127.0.0.1 IP 地址,如何使用它?
- localhost与127.0.0.1的区别
- 这一篇还列表比较了
- python socket bind机器上所有ip的方法
- bind 0.0.0.0就可以绑定这机器上的所有IP
- 0.0.0.0
- 代表当前设备的IP
- 彻底明白ip地址,区分localhost、127.0.0.1和0.0.0.0
4 - netstat
看到是 tcp6
而非 tcp
?
看了一些资料。但我最后好像没改动这方面,应该不用管?
主要参考资料:
四、还遇到 SSLError
问题
第三章是先用 HTTP 来测试,所以(端口通了之后)改回 HTTPS 时,别忘了客户端也改回来(尤其是我用了非标准的 10022 端口,浏览器可能不知道是 HTTPS)。(否则这种报错很难看出来)
五、Address already in use
过了一段时间突然就好了。可能是上一次运行崩溃之后,没及时释放端口?