Автор оригинала: Doug Hellmann.
asyncio
имеет встроенную поддержку для включения связи SSL через сокеты. Передача экземпляра SSLContext
сопрограммам, которые создают серверные или клиентские соединения, включает поддержку и гарантирует, что настройка протокола SSL позаботится о настройке протокола до того, как сокет будет представлен как готовый для использования приложением.
Эхо-сервер и клиент на основе сопрограмм из предыдущего раздела можно обновить с помощью нескольких небольших изменений. Первым шагом является создание файлов сертификата и ключей. Самозаверяющий сертификат можно создать с помощью такой команды:
$ openssl req -newkey rsa:2048 -nodes -keyout pymotw.key \ -x509 -days 365 -out pymotw.crt
Команда openssl
запросит несколько значений, которые используются для создания сертификата, а затем создаст запрошенные выходные файлы.
Небезопасная установка сокета в предыдущем примере сервера использует start_server ()
для создания слушающего сокета.
factory asyncio.start_server(echo, *SERVER_ADDRESS) server event_loop.run_until_complete(factory)
Чтобы добавить шифрование, создайте SSLContext
с только что сгенерированным сертификатом и ключом, а затем передайте контекст в start_server ()
.
# The certificate is created with pymotw.com as the hostname, # which will not match when the example code runs elsewhere, # so disable hostname verification. ssl_context ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.check_hostname False ssl_context.load_cert_chain('pymotw.crt', 'pymotw.key') # Create the server and let the loop finish the coroutine before # starting the real event loop. factory asyncio.start_server(echo, *SERVER_ADDRESS, sslssl_context)
Аналогичные изменения нужны и в клиенте. Старая версия использует open_connection ()
для создания сокета, подключенного к серверу.
reader, writer await asyncio.open_connection(*address)
SSLContext
снова необходим для защиты клиентской стороны сокета. Идентификация клиента не применяется, поэтому необходимо загрузить только сертификат.
# The certificate is created with pymotw.com as the hostname, # which will not match when the example code runs # elsewhere, so disable hostname verification. 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, sslssl_context)
Еще одно небольшое изменение необходимо в клиенте. Поскольку SSL-соединение не поддерживает отправку конца файла (EOF), клиент вместо этого использует нулевой байт в качестве признака завершения сообщения.
Старая версия клиентского цикла отправки использует write_eof ()
.
# This could be writer.writelines() except that # would make it harder to show each part of the message # being sent. 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'
).
# This could be writer.writelines() except that # would make it harder to show each part of the message # being sent. for msg in messages: writer.write(msg) log.debug('sending {!r}'.format(msg)) # SSL does not support EOF, so send a null byte to indicate # the end of the message. writer.write(b'\x00') await writer.drain()
Сопрограмма echo ()
на сервере должна искать NULL-байт и закрывать клиентское соединение при его получении.
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
Такой вывод дает запуск сервера в одном окне и клиента в другом.
$ 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