Webアプリのネットワーク設定に半日はまった話

フロントエンドにReact、バックエンドにFlaskを使用してWebアプリを作ろうとしたときハマったことを、備忘録としてまとめておきます。間違っている部分、追加部分ございましたらどんどん教えてください。

ハマった内容

1. post先の関数名が間違っている。
2. テーブルへのデータ書き込みは事前に行う
3. `net::ERR_EMPTY_RESPONSE` が起きる。
4. CORS エラーが起きる。

## Postを送る前に確認すべきこと

### 1. 関数名間違い

Postの送り先の関数名が合っているか確認する（大文字、小文字も合わせる）。

フロントエンド側
![フロントエンド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310198/3ec4123a-dff5-aa41-4eb8-b793b99b134d.png)

バックエンド側
![バックエンド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310198/2c4be0be-0056-f288-74c9-8e70c18790dd.png)

### 2. テーブルへのデータ書き込み確認

バックエンド側で事前にデバッグを行っておくと、フロントエンド側との連携がスムーズになります。テーブルにデータを書き込む確認は、以下のように簡単に行うことができます。

```python
import os

from flask import Flask, flash, jsonify, render_template, request
from flask_cors import CORS
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import IntegrityError, SQLAlchemyError

from src.utils import get_db_connect_url

URI = get_db_connect_url("postgres")

def create_app(uri: str) -> Flask:
    """
    Flaskアプリを作成する
    """
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = uri
    app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")
    return app

app = create_app(URI)
db = SQLAlchemy(app)
ma = Marshmallow(app)
CORS(app, origins=["http://127.0.0.1:5173"])

class Base(db.Model):
    __abstract__ = True

class tableUser(Base):
    """
    ユーザーのテーブル
    """

    __tablename__ = "User"
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)

if __name__ == "__main__":
		# 値の書き込み確認
		username = "hogehoge"
		email = "hogehoge@gmial.com"
		password = "hogehoge"
		new_user = tableUser(username=username, email=email, password=password)
```

relationを使っている場合、この段階でエラーが発生しデータの書き込みができません。フロントエンド側と合わせてデバッグを行おうとすると、CORS エラーなど他のエラーとしてブラウザ側に表示されるため、発見が難しいです（この辺り、まとめてデバッグする方法もあるかもしれません…）。よって、事前のデバッグがおすすめです。

# Docker を使用した開発で確認すべきこと

## 1. `net::ERR_EMPTY_RESPONSE` が起き

### 1.1. 原因

Dockerコンテナは -p コマンドもしくはdocker-compose.ymlの設定を使用してhostマシンとコンテナのポートを繋げておかないと（マッピングという）、コンテナの外部から内部にアクセスができない仕組みになっています。

例えば、docker run を使用してコンテナを構築する際には以下のようなコマンドを使用することができます。

`docker run -d --name http_server -p 3000:3000 python:3.11 python -m http.server -b localhost`

docker-compose.ymlを使用する場合は、以下のように記述します。

```python
version: "3"

services:
  react-app:
    build:
      args:
        - BASE_IMAGE=node:latest
      context: .
      dockerfile: ./app/react_Dockerfile
    tty: true
    volumes:
      - ../app:/home/
    ports:
      - 3000:3000
```

この設定により、host側のポート3000をコンテナ側のポート3000にマッピングしました。しかし、この状態でlocalhost:3000にアクセスしてもコンテナ内のサーバにまでアクセスすることはできません。これは、ホスト側のlocalhostとコンテナ側のlocalhostが異なるためです。下記の記事で詳細に解説されています。

* [docker上のアプリにlocalhostでアクセスしたらERR_EMPTY_RESPONSEが出る - Qiita](https://qiita.com/amuyikam/items/01a8c16e3ddbcc734a46)

### 1.2. 解決方法

記事で述べられている通り、コンテナ側のサーバのIPアドレスを0.0.0.0にすることで問題を解決できました。

Flask の場合、以下のようにすることでサーバのIPアドレスとポートを指定することができます。

```python
app.run(debug=True, host="0.0.0.0", port=5000)
```

## 2. CORS エラーが起きる

### 2.1. 原因

上記のエラーと同様、フロントエンド側からバックエンド側にアクセスする際に発生するエラーです。メッセージは以下の通りです。

```python
Access to XMLHttpRequest at 'http://localhost:5000/register' from origin 'http://127.0.0.1:5173' has been blocked by CORS policy
```

CORSとは、オリジン間リソース共有 (Cross-Origin Resource Sharing) の略で、異なるオリジンからのアクセスを制限する仕組みのこと。

> オリジンとは、URL中の*スキーム* （プロトコル）、 *ホスト* （ドメイン）、 *ポート番号の組み合わせのこと*
> 
> - `http://example.com` の場合
>     - スキーム：`http`
>     - ホスト：`example.com`
>     - ポート: `80` (省略可能)
> - 以下のようにスキーム (`http`) とホスト名 (`example.com`) が同じ場合は同一オリジン
>     - `http://example.com/app1/index.html`
>     - `http://example.com/app2/index.html`

下記の記事を参考にしました。

* [CORSエラーが出てしまったらヘッダー情報を追加しよう - Qiita](https://qiita.com/mtoutside/items/cee708841cad7e02f85c)

* [CORSについてざっくり理解する - Qiita](https://qiita.com/eshow/items/e47e7214dc12f130f456)

* [Origin (オリジン) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN](https://developer.mozilla.org/ja/docs/Glossary/Origin)
* [オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)

### 2.2. 解決方法

バックエンド側に`flask_cors`を追加することで解決しました。

以下、GitHub Cpilotの回答になります。

> この問題を解決するには、FlaskアプリケーションでCORSを許可する必要があります。Flask-CORSという拡張を使用すると、簡単にCORSを設定できます。
> 
> 
> まず、Flask-CORSをインストールします：
> 
> ```python
> pip install -U flask-cors
> ```
> 
> 次に、FlaskアプリケーションでCORSを有効にします：
> 
> ```python
> from flask_cors import CORS
> 
> app = Flask(__name__)
> CORS(app)
> ```
> 
> これで、すべてのオリジンからのリクエストが許可されます。特定のオリジンのみを許可するには、CORS関数にオリジンを指定します：
> 
> ```python
> CORS(app, origins=["http://127.0.0.1:5173"])
> ```
> 
> これで、CORSエラーが解消されるはずです。
> 

`CORS(app, origins=["****"])` の * * * * 部分はフロントエンド側のデバッグの際にブラウザに表示されているURLとポートを指定してください。

![url.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310198/a43e2702-bd73-f84e-36f6-cf42ee7862bc.png)


# まとめ

以上が今回ハマったエラーになります。Webアプリは初心者のため1回ハマると1つ解決するだけでもとても時間がかかりました。この情報が何かお役に立てればと思います。