1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Dockerでの開発で、Http通信は正常なのに、Socket通信が機能しない。WebSocketは独自にCORS設定が必要。

Last updated at Posted at 2023-07-12

注意

この記事は初心者が書いてるので、鵜呑みにしないでください。

環境

  • Windows 10 Home
  • WSL2
  • この記事は全てローカルのPCでの話をしています。デプロイはしていません。

背景

docker-compose upでアプリを起動したら、http通信はうまくいくけど、socketioの通信は機能しない。

結論

socketioのためには、下記のように独自にCORS設定をする必要がある。
WSLバージョン
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*") 

CORS設定の理解

簡単に説明

通常、あるアプリにアクセスできる「オリジン」は、そのアプリと同一の「オリジン」のみである。しかし、CORS設定を変えれば、アクセスできる「オリジン」を追加することができる。「オリジン」については、下記のリンクなどが参考になりそう。

https://zenn.dev/syo_yamamoto/articles/445ce152f05b02

例で確認する

ディレクトリ構造
- app.py
- get_data.py
- templates/
    - home.html
app.py
from flask import Flask, render_template

app = Flask(__name__)

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

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)
get_data.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app) #ここが大事

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)
home.html
<!DOCTYPE html>
<html>
<head>
  <title>CORS Test</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
  <h2>CORS Test</h2>
  <button id="testButton">Test CORS</button>

  <script>
    $(document).ready(function(){
      $("#testButton").click(function(){
        $.get("http://localhost:8080", function(data, status){
          alert("Data: " + data + "\nStatus: " + status);
        });
      });
    });
  </script>
</body>
</html>

上記のファイルを作る。「py get_data.py」でget_data.pyを動かしつつ、「py app.py」でapp.pyを動かす。http://127.0.0.1:5000/にアクセスすると、app.pyが表示させてるhome.htmlを見ることができる。そこにあるボタンを押すと、http://127.0.0.1:8080/(get_data.py)にアクセスして、情報を取得してalertに表示させることができる。これができるのは、get_data.pyのCORS(app) の部分のおかげである。CORS(app)がなければ、異なるオリジンであるので(http://127.0.0.1:5000/、http://127.0.0.1:8080/)、http://127.0.0.1:8080/(get_data.py)にアクセスすることはできない。CORS(app)がなければ、ボタンを押してもalertは表示されないはずである。

websocketは同一オリジンでも、CORS設定が必要???

ディレクトリ構造
static
    -styles.css
templates
    -home.html
app.py
docker-compose.yml
Dockerfile
nginx.conf
requirements.txt
app.py
from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*") #ここが大事

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

@socketio.on('message from js')
def handle_message(message):
    emit('message from py', message, broadcast=True)

if __name__ == '__main__':
    socketio.run(app, debug=True, host='0.0.0.0', port=5000)
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_DATABASE: aaa
      MYSQL_USER: bbb
      MYSQL_PASSWORD: ccc
      MYSQL_ROOT_PASSWORD: ddd
    volumes:
      - db_data:/var/lib/mysql

  web:
    build: .
    command: python app.py
    volumes:
      - .:/cors_test_2
    #ports:
      #- "5000:5000"
    depends_on:
      - db

  nginx:
    image: nginx:latest
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      #- ./static:/static
      - .:/cors_test_2
    ports:
      - "8010:7010"
    depends_on:
      - web

volumes:
  db_data:
Dockerfile
FROM python:3.9

WORKDIR /cors_test_2

ADD requirements.txt .
RUN pip install -r requirements.txt

ADD . .

CMD [ "python", "./app.py" ]
nginx.conf
user  nginx;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include /etc/nginx/mime.types; 
    client_max_body_size 20M; 

    server {
        listen 7010;
        location / {
            proxy_pass http://web:5000;
            proxy_http_version 1.1;  
            proxy_set_header Upgrade $http_upgrade;  # WebSocketを使用するための設定
            proxy_set_header Connection "upgrade";  # WebSocketを使用するための設定
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Origin $http_origin;  # オリジンを伝えるのに必要?
        }
        location /static/ {
            alias /cors_test_2/static/;
        }
    }
}
requirements.txt
Flask==2.1.1
flask-socketio==5.1.1
home.html
<!DOCTYPE html>
<html>
<head>
    <title>cors_test_2</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <h1 class="red-text">cors_test_2</h1>
    <button id="sendButton">Send Message</button>
    <h4 id="message_show" class="green-text"></h4>

    <script type="text/javascript">
        var socket = io();
        $(document).ready(function(){
            $("#sendButton").click(function(){
                socket.emit('message from js', {'msg':'Hello from the client!'});
            });
        });
        socket.on('message from py', (data) => {
            document.getElementById('message_show').textContent = data['msg'];
        });
    </script>
</body>
</html>
styles.css
.red-text {
    color: red;
}
.blue-text {
    color: blue;
}
.green-text {
    color: green;
}

「docker-compopse build」と「docker-compose up」を実行することで、アプリが起動する。ホストマシンの8010ポート、すなわちhttp://127.0.0.1:8010/にアクセスする。ページが表示されて、ボタンを押して、文字が表示されれば、Socket通信が成功している。通信の流れとしては、ホストマシンの8010ポート、nginxコンテナの7010ポート、webコンテナ(flaskコンテナ)の5000ポートの順番である。nginxコンテナの7010ポート、webコンテナ(flaskコンテナ)の5000ポートは、ホストマシンのポートではなく、そのコンテナのポートを意味しており、このアプリ全体で占有しているホストマシンのポートは8010ポートだけであることに注意。

ここで、CORS設定の考え方で重要になる「オリジン」についてだが、このアプリを構築しているdockerネットワークは、ホストマシンの8010ポート(http://127.0.0.1:8010/)と、同一「オリジン」に属しているらしい。なので、http通信がwebコンテナ(flaskコンテナ)の5000ポートにアクセスするために、追加のCORS設定は必要ない。つまり、CORS(app)という記述は必要ない。しかし、ここで自分は引っかかった。Socket通信は独自にCORS設定をしないと、webコンテナ(flaskコンテナ)の5000ポートにアクセスできないらしい。具体的には、「cors_allowed_origins="*"」という記述が必要である。これによって、Socket通信が可能となる。

「cors_allowed_origins="*"」は、あらゆるオリジンのアクセスを許可してしまうので、本番環境では、アクセスを許可したいオリジンのみを指定してください。

疑問

なぜwebsocketだけ特別なCORS設定が必要なのか?

http通信は、アプリのdockerネットワーク(nginxコンテナ、flaskコンテナ、dbコンテナ(dbを使っている場合))を一つのオリジンとみなしている。しかし、Socket通信では、flaskコンテナが、nginxコンテナからの通信を受け取れない。つまり、同一オリジンとして扱われていない。感覚的には、Socket通信においても、webコンテナ(flaskコンテナ)へのアクセスは、同一のオリジンだから問題ないということになりそうだけど...。分からないです。

参考

  • GPT-4
  • https://zenn.dev/syo_yamamoto/articles/445ce152f05b02
  • https://qiita.com/att55/items/2154a8aad8bf1409db2b
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?