tech.sinayaka.com

pythonのwssサーバーにhtmlで接続する

2023-03-22
2025-07-13
5分
950語
Python python

最近pythonニンゲンなので、webアプリ(ブラウザ)でpythonを使えるように、 framework虚弱体質なのでFlaskなどに頼らない第一歩としてpythonでsecureなwebsocketを構築しました。

  • クライアント → HTML(Javascript)
  • サーバー → Apache + SSL + python

サーバーの/var/www/wssにindex.htmlを設置し、 Javascriptで同一サーバー内のwebsocketへアクセスし、pythonを動かすのが目標です。

diagram
diagram

クライアント

クライアントはJavascriptで接続します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>WSS Client</title>
  </head>
  <body>
    <h1>WSS Client</h1>
    <form>
      <input type="text" id="message" placeholder="Enter message" />
      <button type="submit">Send</button>
    </form>

    <ul id="messages"></ul>

    <script>
      const ws = new WebSocket("wss://HOST:9001");

      ws.onopen = function () {
        console.log("Connected to WSS server");
      };

      ws.onmessage = function (event) {
        const messages = document.getElementById("messages");
        const li = document.createElement("li");
        li.appendChild(document.createTextNode(event.data));
        messages.appendChild(li);
      };

      document.querySelector("form").addEventListener("submit", function (event) {
        event.preventDefault();
        const input = document.getElementById("message");
        ws.send(input.value);
        input.value = "";
      });
    </script>
  </body>
</html>

サーバー(Apacheの設定)

とりあえずhttp接続できるようにし、websocketサーバーへのproxyも記述しておきます。 websocketサーバーへの接続は、ホスト名とポート指定だけするので、 ProxyPassディレクティブの/wssはなんでも構わないのですが、/だけだとエラーになるので適当に。

<VirtualHost *:80>
    ServerName HOST
    DocumentRoot /var/www/wss
    <Directory "/var/www/wss">
        Options -Indexes FollowSymLinks
        Order allow,deny
        Allow from all
    </Directory>
    ProxyPass /wss ws://localhost:9001/
</VirtualHost>

apacheを再読み込みし、設定したindex.htmlにブラウザからhttpアクセスできることを確認します。 この時、コンソールログにはwebsocket接続エラーが出ていると思います。

http access
http access

確認できたらcertbotを利用してLet’s EncryptのSSL証明書を取得しておきます。
certbotを使えばカンタンに無料でSSL証明書を作成でき、webサーバーの設定も行ってくれるので便利な時代です。

secureなwebsocketserverではSSL証明書を読み込む必要がありますが、2通りの方法の選択を迫られます。

一般ユーザーでwebsocketserverを動かし、/etc/letsencrypt/archive/HOST/cert1.pem/etc/letsencrypt/archive/HOST/privkey1.pemを一般ユーザーが読み込めるようにするか、rootでwebsocketserverを動かすのかです。
一般ユーザーで動かす場合は下記のように証明書置き場を閲覧可能にし、privateキーファイルも閲覧可能にする必要があります。

chmod 711 /etc/letsencrypt/archive
chmod 644 /etc/letsencrypt/archive/HOST/privkey1.pem

外部から接続できるようにポート設定もしておきます。

-A INPUT -m state --state NEW -m tcp -p tcp --dport 9001 -j ACCEPT
firewall-cmd --permanent --add-port=9001/tcp
firewall-cmd --reload

サーバー(pythonプログラム)

とりあえず、接続テスト用のwebsocketserverの本体のプログラムです。どこに設置してもいいです。

import asyncio
import ssl
import websockets

# SSL/TLS証明書のパスを指定します
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(
'/etc/letsencrypt/archive/HOST/cert1.pem',
'/etc/letsencrypt/archive/HOST/privkey1.pem')

# WebSocket接続を処理するコルーチンです
async def handler(websocket, path):
    async for message in websocket:
        # クライアントからのメッセージを受信します
        print(f"Received message: {message}")

        # クライアントにメッセージを送信します
        response = f"Response to {message}"
        await websocket.send(response)
        print(f"Sent message: {response}")

# サーバーの起動とWebSocket接続の待機を行います
async def main():
    async with websockets.serve(handler, 'HOST', 9001, ssl=ssl_context):
        print("Server started")
        await asyncio.Future()  # サーバーが終了しないように無限ループします

# asyncioイベントループを開始します
#asyncio.run(main()) # python3.7以上の場合はこの行だけでOK
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

接続テスト

websocketserverを起動します。

>>python wss.py
Server started

Webブラウザでアクセスします。これまでの設定が問題なければ、コンソールログには「Connected to WSS server」と出ているはずです。

connect ok
connect ok

テキストボックスに「hello」と打ち込み「Send」すると、応答が返ってきます。

response
response

サーバーのコンソールにもログが残っているはずです。

>>python wss.py
Server started
Received message: hello
Sent message: Response to hello

おわりに

webブラウザからpythonを使うためにwebsocketserverを構築してみました。 ここからseleniumでスクレイピングしたり、機械学習を利用するWebアプリを作ることができます。
このままでは誰でも利用できてしまうので、IPアドレスで規制したりサインインシステムで覆ったりしなければなりません。




Copyright 2025
サイトマップ