見出し画像

レンタルサーバでWebアプリ作り:SocketIOでテケテケ表示

ロリポップレンタルサーバではできることが限られていたので、ホスティングサービスを使ってWebアプリを公開するまでの道のりを記録します。

データ分析を仕事としているのでパソコンやITのことは多少は詳しいですが、インフラまわりは全くの素人です。そんな人が見て参考になる情報をまとめていきたいと思います。

背景

前回、インプットボックスの値をpythonに投げて、python側でその値をファイル保存するWebアプリを作りました。
前回記事:https://note.com/cryptoscore/n/ndf16838d6596

今回は、ChatGPTのテケテケ表示をするための前哨戦=socketIOで通信することを試みます。


ゴール=テケテケ表示させる

いきなりChatGPTでテケテケさせるとうまくいかない気がしたので、まずはシンプルなテケテケ=「pythonで10カウントし、その経過をHTMLに表示させる」という非同期処理を仕込みます。

socketIOで仕込もうとしていたのですが、全然うまくいかず、サポートに問い合わせたところ、以下のような回答をいただきました。
ポイント=「 long polling HTTP requests は、app platform ではサポートされていない」という点です。

We understand you are facing issues with your app. Can you kindly confirm if you are using long polling HTTP requests at socket.io? This is the typical default for that service and unfortunately they are not supported in app platform. In this case you would need to make sure you are not using long polling HTTP requests for this to work.

DigitalOceanのサポートからの回答

「 long polling HTTP ってなんじゃ?」という感じでしたが、GPT先生がまたまた助けてくれて、無事解決しました。詳しくは後ほど。


事前準備

「FLASK_SECRET_KEY」を作る
SocketIOを使う場合、シークレットキーなるものがいるようです。
その作り方をGPT先生に聞きました。

secret_keyの一般的な設定方法を教えてください。
load_dotenv関数を使ってください。

GPT先生に投げ込んだプロンプト

以下の内容を教えてくれたので、まずはこれを流してsecret_keyをprintし、その内容を .env ファイルに書き込みます。

import secrets
secret_key = secrets.token_urlsafe(16)
print(secret_key)

一度流してsecret_keyを作ったら、もう使わないのでそのpyファイルは削除してOKです。

.envファイルの内容は以下の通り。FLASK_SECRET_KEYを追加します。

OPENAI_API_KEY='sk-*******'
FLASK_SECRET_KEY='HLd-*******'


プログラム作り

app_teketeke.py と teketeke.html を新規作成します。
今回はテストなので、app.py や index.html には手を付けずに、 gunicorn から app_teketeke.py を起動するようにします。

フォルダ構成
test_digitalocean/
├ requirements.txt
├ Pipfile ※pipが自動作成
├ Pipfile.lock ※pipが自動作成
├ app.py   ※今回は使わない
├ app_teketeke.py
├ Procfile
├ gunicorn_config.py
├ templates/
│ ┗index.html   ※今回は使わない
│ ┗teketeke.html  
├ .env

requirements.txt
Flask-SocketIO と eventlet を使いますので、追加しましょう。

Flask
gunicorn
openai
python-dotenv
Flask-SocketIO
eventlet

pipを忘れずに。

pipenv install -r requirements.txt
pipenv lock


PythonとHTML文
以下のような内容でGPT先生に聞きました。

Flaskを使ったサンプルプログラムを教えてください。
機能は、以下の通りです。
・HTMLにボタンが1つあり、押すと10という数字がPythonに渡される
・Pythonは、HTMLから渡された数字の数だけfor文を繰り返す。
・Pythonは、定期的に現在のカウント数をHTMLに渡す
・HTMLは、Pythonから渡された現在のカウント数を、Pythonから投げられた都度表示する。
・Pythonは、HTMLから渡された数字までカウントしたらその処理を終了し、HTMLに処理終了を伝える
・HTMLは、処理終了のサインを受け取ったらソケット通信を閉じる

GPT先生に投げ込んだプロンプト

Pythonのほうはまるっとそのまま使えましたが、HTMLのほうはGPT先生が教えてくれた内容ではだめでした。ローカルでは動くのですがDigitalOceanでは動きません。

app_teketeke.py
まずは、Pythonから。

from flask import Flask, render_template
from flask_socketio import SocketIO
from dotenv import load_dotenv
import os

# 環境変数を読み込む
load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY') #環境変数から設定
socketio = SocketIO(app)


@app.route('/')
def index():
    return render_template('teketeke.html')

def background_task(n):
    """バックグラウンドで数を数えるタスク"""
    count = 0
    for i in range(n):
        count += 1
        socketio.sleep(0.1)  # 0.1秒ごとに更新
        socketio.emit('count_response', {'count': count})
    socketio.emit('count_response', {'count': '完了'})

@socketio.on('start_count')
def start_count(message):
    """クライアントからのカウント開始要求を受ける"""
    n = int(message['count'])
    socketio.start_background_task(background_task, n)

if __name__ == '__main__':
    socketio.run(app)

teketeke.html
こっちが厄介だった。。冒頭に書いた「long polling HTTPがサポートされていない」という点をGPT先生に聞いたら、「websocketのみを使うようにオプション指定すればよい」と言われました。具体的には、以下のoptionsのところです。さっぱり意味が分かりませんが、このオプションを付けたらうまいこと動きました。

<!DOCTYPE html>
<html>
<head>
    <title>Count Example</title>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function() {
            var socketUrl;
            var options = {
                transports: ['websocket'] // WebSocketのみを使用
            };

            if (location.protocol === 'https:') {
                // DigitalOceanの設定(HTTPS)
                socketUrl = 'https://' + document.domain;
            } else {
                // ローカル環境の設定(HTTP、ポート5000)
                socketUrl = 'http://' + document.domain + ':5000';
            }

            var socket = io.connect(socketUrl, options);


            socket.on('count_response', function(msg) {
                var logElement = document.getElementById('log');
                logElement.textContent = 'カウント: ' + msg.count;
            });

            document.getElementById('startButton').addEventListener('click', function() {
                socket.emit('start_count', {count: 10});
            });
        });

    </script>


</head>
<body>
    <button id="startButton">Start Counting</button>
    <div id="log"></div>
</body>
</html>


Procfile
GPT先生曰く、「gunicornはFlask-SocketIOをサポートしていないので、eventletを挟まないといけない」とのこと。具体的には以下のようなコマンド。

web: gunicorn -k eventlet -w 1 --worker-tmp-dir /dev/shm app_teketeke:app

gunicorn_config.py
こちらにも、eventletのことを指定する必要があるとのこと。また、workersの数も1に変更しております。

WebSocketを使用する場合、特に長期間の接続を維持するため、ワーカー数は1に制限することを推奨します。

GPT先生のアドバイス
bind = "0.0.0.0:5000"
workers = 1
worker_class = 'eventlet'  # eventletワーカークラスを使用


GitHubへプッシュ&新規デプロイ

GitHubへプッシュすると以前に作っていたアプリが自動更新されるはずなのですが、gunicornの起動コマンドを「app_teketeke:app」に変えており、そこがうまく取り込めなかったので、新規にWebApp作成してデプロイしました。
新規デプロイについては、過去の記事の「DigitalOceanへの取り込み」を参考にしてください。
過去の記事: https://note.com/cryptoscore/n/n2689730c3caa

以下のようにテケテケ表示してくれたら成功です。

テケテケ表示

最後まで見ていただきありがとうございました!

DigitalOcean固有の問題は、GPT先生でもなかなか難しいようですね。。

また、DigitalOceanのサポートのレスポンスがとても速いというのが良い意味でサプライズでした。ただ、そのサポートへの問い合わせリンクがなかなか見つかりません。
トップページの右下にある「Ask a question」に行き、そのページの一番下(欄外っぽいところ)にひっそりと「Support」というリンクがあります(Contact内)。そのページの一番最後に「Contact Support」ボタンがあります。

かなり分かりづらいので、リンクを載せておきます♪

https://cloudsupport.digitalocean.com/s/

次はいよいよChatGPTのテケテケ表示です!


DigitalOceanのアカウント登録
(200ドル分チケット付き)

以下は紹介リンクですが、ここから手続きを進めてもらえると200ドル分の無料チケット(有効期間2ヶ月)がもらえるようですので、ぜひご活用ください。
※ 2023/12/29時点

https://m.do.co/c/a8b31ed34b75

ちなみに、1日ガチャガチャいじって7セントでした。安い!

この記事が気に入ったらサポートをしてみませんか?