Help us understand the problem. What is going on with this article?

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

More than 3 years have 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)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした