asyncio之使用SSL
asyncio内置支持在套接字上启用SSL通信。通过将SSLContext实例传递给协程来创建服务器或者客户端连接,可支持并确保在将套接字交给应用程序使用之前启用SSL协议。
比以前章节中以协程为基础的服务端和客户端更新了一些小变化。首先创建证书和key文件。使用下面命令生成自签名证书。
1
| $ openssl req -newkey rsa:2048 -nodes -keyout pymotw.key -x509 -days 365 -out pymotw.crt
|
openssl命令将提示生成证书的几个值,然后生成需要的输出文件。
在以前的服务端例子中,不安全的socket使用start_server()来创建监听的套接字。
1 2
| factory = asyncio.start_server(echo, *SERVER_ADDRESS) server = event_loop.run_until_complete(factory)
|
为了加入编码认证,需要创建带证书和带刚生成key的SSLContext,然后传递给start_server()方法。
1 2 3 4 5 6 7 8 9 10
| ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.check_hostname = False ssl_context.load_cert_chain('pymotw.crt', 'pymotw.key') factory = asyncio.start_server(echo, *SERVER_ADDRESS, ssl=ssl_context)
|
客户端需要相似的改变。老版本使用open_connection()来创建套接字链接到服务端。
1
| reader, writer = await asyncio.open_connection(*address)
|
也需要SSLContext来保护客户端的socket。客户端身份不需要强制,只需要加载证书。
1 2 3 4 5 6 7 8 9 10 11
| ssl_context = ssl.create_default_context( ssl.Purpose.SERVER_AUTH, ) ssl_context.check_hostname = False ssl_context.load_verify_locations('pymotw.crt') reader, writer = await asyncio.open_connection( *server_address, ssl=ssl_context )
|
在客户端需要一个其他小的改变。因为SSL连接不支持发送end-of-file(EOF),客户端使用NULL字节作为消息终止。
老版本的客户端使用write_eof()发送给loop。
1 2 3 4 5 6 7 8 9
| for msg in messages: writer.write(msg) log.debug('sending {!r}'.format(msg)) if writer.can_write_eof(): writer.write_eof() await writer.drain()
|
新版本发送零字节(b’\x00’).
1 2 3 4 5 6 7 8 9 10
| for msg in messages: writer.write(msg) log.debug('sending {!r}'.format(msg)) writer.write(b'\x00') await writer.drain()
|
服务端的echo()协程方法必须寻找NULL字节,在接收到时关闭客户端连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async def echo(reader, writer): address = writer.get_extra_info('peername') log = logging.getLogger('echo_{}_{}'.format(*address)) log.debug('connection accepted') while True: data = await reader.read(128) terminate = data.endswith(b'\x00') data = data.rstrip(b'\x00') if data: log.debug('received {!r}'.format(data)) writer.write(data) await writer.drain() log.debug('sent {!r}'.format(data)) if not data or terminate: log.debug('message terminated, closing connection') writer.close() return
|
在一个窗口启动服务端,在另一个窗口运行客户端,如下输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $ python3 asyncio_echo_server_ssl.py asyncio: Using selector: KqueueSelector main: starting up on localhost port 10000 echo_::1_53957: connection accepted echo_::1_53957: received b'This is the message. ' echo_::1_53957: sent b'This is the message. ' echo_::1_53957: received b'It will be sent in parts. ' echo_::1_53957: sent b'It will be sent in parts.' echo_::1_53957: message terminated, closing connection $ python3 asyncio_echo_client_ssl.py asyncio: Using selector: KqueueSelector echo_client: connecting to localhost port 10000 echo_client: sending b'This is the message.' echo_client: sending b'It will be sent ' echo_client: sending b'in parts.' echo_client: waiting for response echo_client: received b'This is the message.' echo_client: received b'It will be sent in parts.' echo_client: closing main: closing event loop
|
原文链接