Python

asyncio を使って簡易の HTTPS サーバーを立てる

More than 1 year has passed since last update.

概要

イベントループのための asyncio モジュールおよび uvloop の練習のために簡易の HTTPS サーバーを立ててみました。uvloop は asyncio および libuv をもとにしたモジュールです。2016年以降、uvloop を採用した Sanic や japronto などの新しいマイクロフレームワークが台頭しました。

前提

Python 3.6 を前提とします。Python 3.6 で ssl モジュールの SSLContext のデフォルト値の変更や定数が整理統合されました。

uvloop について

uvloop は asyncio.AbstractEventLoop を実装し、uvloop と互換性のある API を提供しています。libuv の制約から uvloop は Windows をサポートしないため、asyncio が uvloop フォールバックの役割を担っています。

自己署名証明書をつくる

次のワンライナーで自己署名証明書をつくることができます。

# https://stackoverflow.com/a/41366949/531320
openssl req -x509 -newkey rsa:4096 -sha256 \
-nodes -keyout server.key -out server.crt \
-subj "/CN=example.com" -days 3650

HTTPS サーバーを立てる

HTTP メソッドに関係なく文字列を返す echo サーバーを立ててみましょう。

server.py
import asyncio
import ssl

async def request_handler(reader, writer):
    msg = (
      'HTTP/1.1 200 OK\r\n'
      'Content-Type: text/plain; charset=utf-8\r\n'
      '\r\n'
      'hello\r\n'
    )
    writer.write(msg.encode())
    await writer.drain()
    writer.close()

host = '127.0.0.1'
port = 8000

ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain('server.crt', keyfile='server.key')
ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1

loop = asyncio.get_event_loop()
coro = asyncio.start_server(
  request_handler, 
  host, port, ssl=ctx, loop=loop
)
server = loop.run_until_complete(coro)

print('Serving on {}'.format(server.sockets[0].getsockname()))

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

HTTPS クライアントをつくる

今度は HTTPS クライアントをつくってみましょう。

client.py
import asyncio
import ssl

async def http_client(host, port, msg, ctx, loop):
    reader, writer = await asyncio.open_connection(
        host, port, ssl=ctx, loop=loop
    )

    writer.write(msg.encode())
    data = await reader.read()
    print("Received: %r" % data.decode())
    writer.close()

host = '127.0.0.1'
port = 8000
msg = (
  'GET / HTTP/1.1\r\n'
  'Host: localhost:8000\r\n'
  '\r\n'
  '\r\n'
)

ctx = ssl.create_default_context()
ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

loop = asyncio.get_event_loop()
loop.run_until_complete(http_client(host, port, msg, ctx, loop))
loop.close()

aiohttp

aiohttp は asyncio をもとに開発された HTTP モジュールで、アプリケーションの開発機能も附属します。インストールは次のとおりです。

pip3 install aiohttp

HTML ファイルを表示するサーバーを立ててみましょう。

server.py
from aiohttp import web
from pathlib import Path
import ssl

host='localhost'
port=8000
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain('server.crt', keyfile='server.key')
ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1

app = web.Application()
app.router.add_static('/static', path=str(Path.cwd().joinpath('static')), show_index=True)
web.run_app(app, host=host, port=port, ssl_context=ctx)