【API実装】Apatch + WSGI + FlaskでAPIを実装

# はじめに
AWSのEC2上にAPIを作成したときに、かなり苦労したので **「構築の仕方」** と **「陥った失敗」** について解説します。EC2上で実装していますが、この記事を読む上でEC2の知識は不要です。

# 目次
1. [APIとは](#APIとは)
1. [システムの全体像](#システムの全体像)
1. [構築の方法](#構築の方法)
1. [陥った失敗](#陥った失敗)

# APIとは
APIについて知っている人は読み飛ばしてしまっても構いません。初めて聞く人や聞いたことあるけど具体的に理解してない人向けに書きます。

APIとは、Application Programming Interfaceの略です。ここで、インターフェース (interface) とは**異なるシステムで情報のやり取りを可能にするための窓口です。**
想像しやすいインターフェースの例として、PCに付いているType-CやType-Aの差し込み口が挙げられます。これらは、PCとマウスやキーボード(つまり異なる製品)をつなぐ窓口の役割を担っています。**このように異なるもの同士をつなぐことがインターフェースの役割です。** 

そして、**アプリケーション同士をつなぐ役割を担っているインターフェース(窓口)をAPIと呼びます。** APIの目的は、**アプリケーションの内部機能を外部から利用できるようにすることです。** 例えば、「Twitter APIを利用して、ツイートを取得・投稿する」などです。


# システムの全体像
今回はAWSのEC2上でApatchをWebサーバーとしてPythonのフレームワークであるFlaskを用いて実装していきます。以下の図がシステム構成のイメージです。

![システムの全体像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967909/4f1ca6aa-9de8-1e9b-a874-ba203b748872.png)
<font color="#808080">*AWS EC2上にApatch HTTP ServerとFlaskを使用するAPIの全体像*</font>

また、本システムでは、HTTPリクエストを送ると「Hello World!」を返すようなAPIを実装します。
```command:ターミナル
$ curl http://{EC2のIPアドレス}/api/
Hello World!
```

# 構築の方法
ここからは、実際の構築方法を説明します。
### Step1. Apatch HTTP Serverをインストールし、起動
```command:ターミナル
$ sudo su root
```
現在のユーザーからスーパーユーザーに切り替えます。
- スーパーユーザーとは、システム上で最も高いレベルの権限を持つユーザーのことを指します。
- `sudo`により、一時的にスーパーユーザー権限を使用しており、`su root`により、ユーザー切り替えを行います
- ちなみに、`su`の由来はswitch userです

```command:ターミナル
# yum -y install httpd
```
次に、Apatch HTTP Serverをインストールします。
- `yum`はパッケージマネージャのコマンドです
- `-y`により、ユーザー確認を自動的にyesにしています
- `httpd`はApatch HTTP Serverのパッケージ名です
Apatch HTTP Serverを起動させる。また、システム起動時に自動的に起動するように設定変更する。

```command:ターミナル
# systemctl start httpd
```
Apatch HTTP Serverを起動します。
- `systemctl`はサービスの起動や停止などを行うためのコマンドです
- `start httpd`により、　`httpd`を起動しています

```command:ターミナル
# systemctl enable httpd
```
システム起動時にApatch HTTP Serverが自動的に起動するように設定します。**この設定は必須ではありません。**
- `enable`はサービスをシステム起動時に自動的に起動するように設定するためのコマンドです

### Step2. Flaskをインストール
```command:ターミナル
# pip3 install flask
```
Flaskをインストールします。

### Step3. mod_wsgiのインストール
#### WSGIとは
急に、WSGIという言葉が出てきました。これはなんでしょう？そして、なぜ必要なのでしょう？

まず、Apatch HTTP Server (Httpd) 上で特定のHTTPリクエストが来たときのみFlaskを呼び出すことは可能なのでしょうか？答えは、**NO**です。正確にいうと、**Apache HTTP ServerとFlaskのみでは無理** です。
ここで、WSGIが登場します。WSGIはWeb Server Gateway Interfaceの略です。WSGIとは、**WebサーバとPythonアプリケーション (FalskやDjangoなど) をつなぐためのインターフェース** です。WSGIは、2つのコンポーネントで構成されます。
1. **WSGIサーバ**
Webサーバーのことを指し、HTTPリクエストを受け取り、それをWSGIアプリケーションに渡す役割を果たします。
1. **WSGIアプリケーション**
Pythonで書かれたWebアプリケーションのことを指し、サーバーから受け取ったリクエストを処理し、レスポンスを生成して返します。

そして、`mod_wsgi`とは**WSGIに準拠したPythonのプログラムをApache HTTP Serverで動作させるためのモジュール** です。

ダラダラと書きましたが、結論としては **`mod_wsgi`を介すことで、Apache HTTP ServerとFlaskを接続できるようになります。**

#### mod_wsgiのインストール方法
```command:ターミナル
# yum -y install httpd-devel
# yum -y install gcc
# yum -y install python3-devel
# pip3 install mod_wsgi
```
`mod_wsgi`をインストールします。そのために`httpd-devel`・`gcc`・`python3-devel`もインストールします。

### Step4. `app.py`を作成
```command:ターミナル
# cd /var/www
# mkdir api_v1
# cd api_v1
```
`/var/www` 内に`api_v1`というフォルダを作成し、`api_v1`に移動します。`/var/www` は、Apatch HTTP Serverが**デフォルトでウェブコンテンツを配置するディレクトリです。** 
```txt:Apatch HTTP Serverの構成
/var/www/
    ├── html/      # デフォルトのドキュメントルート
    ├── site1/     # サイト1のファイル
    ├── site2/     # サイト2のファイル
```
例えば、`curl http://{EC2のIPアドレス}/`でHTMLファイルを開きたい場合、`/var/www/html/`にHTMLファイルを配置します。
今回は`site1`の場所に`api_v1`フォルダを作成し、そこにPythonファイルを置きます。

```command:ターミナル
# touch app.py
# vi app.py
```
`app.py`を作成します。

```python:app.py
from flask import Flask
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'

@app.route('/')
def index():
    return "Hello World!"
```
`app.py`の中身です。`http://{EC2のIPアドレス}/api/`のときに、`Hello World!`が表示されるようにしています。

### Step5. `app.wsgi`を作成

```command:ターミナル
# touch app.wsgi
# vi app.wsgi
```
WSGIとFlaskと連携するために`app.wsgi`を作成します。
```python:app.wsgi
import sys
sys.path.insert(0, '/var/www/api_v1')
from app import app as application
```
`from app import app as application`は`app.py`内のアプリケーションインスタンス (これ → `app = Flask(__name__)`) を指します。
そして最後に、`app` を `application` に代入します。これはWSGI仕様に従うためです。

例えば、`app_1.py`内に`app_2 = Flask(__name__)`として定義すれば、 `from app_1 import app_2 as application` となります。

### Step6. Apatch HTTP ServerとWSGIを連携
```command:ターミナル
# cd /etc/httpd/conf.d
# touch api_v1.conf
# vi api_v1.conf
```
Apatch HTTP ServerとWSGIを連携するために`api_v1.conf`という定義ファイルを`httpd`内に作成します。

```config:api_v1.conf
<VirtualHost *:80>
  WSGIScriptAlias /api "/var/www/api_v1/app.wsgi"
  <Directory "/var/www/api_v1">
    Require all granted
  </Directory>
</VirtualHost>
```
`api_v1.conf`の中身です。
- **\<VirtualHost \*:80>**
ポート80（HTTP）でリクエストを受け付けることを意味します。
- **WSGIScriptAlias /api "/var/www/api_v1/app.wsgi"**
Apacheに対し、`/api` というURLパスを、Pythonアプリケーションのエントリーポイントである`app.wsgi`に紐付けています。
- **\<Directory "/var/www/api_v1">**
`/var/www/api_v1` ディレクトリへのアクセス権限を設定します。
- **Require all granted**
`/var/www/api_v1` ディレクトリへのアクセスをすべてのクライアントに許可します。他にも`Require all denied`(すべてのクライアントからアクセス禁止)や`Require ip <IPアドレス>`(指定したIPアドレスからのみ許可)があります。


`LoadModule wsgi_module ~`を`/etc/httpd/conf/httpd.conf`に追加します。これを行わないと、エラーになります。というのも、WSGIScriptAliasの部分で`Syntax Error`が起きるからです。つまり、**WSGIはインストールしたけど、Apatchに連携できていない状態になっているということです。**
```diff_python:httpd.conf
# find /usr -name mod_wsgi-express
/usr/local/bin/mod_wsgi-express

# export PATH=$PATH:/usr/local/bin

# mod_wsgi-express module-config
+ LoadModule wsgi_module "/usr/local/lib64/python3.7/site-packages/mod_wsgi/server/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so"
WSGIPythonHome "/usr"
```
```command:ターミナル
# chown -R apache:apache /var/www/api_v1
# chmod -R 755 /var/www/api_v1
# systemctl restart httpd
```
最後に、アクセス権限を変更し、Apatch HTTP Serverを再起動します。
- `chown`コマンドはオーナーをapatchに変えています
- `-R` で`/var/www/api_v1`内の全てのファイルで変更しています
- `chmod` コマンドで実行権限を変えています
- `755`は所有者が読み取り・書き込み・実行可能であり、その他ユーザーは読み取り・実行のみ可能な設定に権限を変えます

### Step7. 動作確認
```command:ターミナル
$ curl http://{EC2のIPアドレス}/api/
Hello World!
```
想定通りに動きました！

# 陥った失敗
ここからは、私がAPI構築した際に上手くいかなかったポイントを共有したいと思います。
結論から言うと、`http://{EC2のIPアドレス}/`ではなく、`http://{EC2のIPアドレス}/api/`をトリガーとする部分でかなり手こずりました。
当時の私は、`APPLICATION_ROOT`という存在を知りませんでした。
```python:app.py
from flask import Flask
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api' # この行

@app.route('/')
def index():
    return "Hello World!"
```
Flaskのアプリケーションルートを知らなかった私は、最初は以下のような設定で実行していました。
```python:app.py
from flask import Flask
app = Flask(__name__)

@app.route('/api')
def index():
    return "Hello World!"
```
この状態で実行すると、次のようになります。

```command:ターミナル
$ curl http://{EC2のIPアドレス}/api/
<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
```
HTTPステータスコード`404`のエラーが発生しています。つまり、リダイレクト先で処理されいてない状態です。上手くいかない...

この原因は、
- Flask側のアプリケーションルートは、`/`
- Apatch側アプリケーションルートは、`/api/`

となっていることです。ここで、アプリケーションルートとは**アプリケーションがどの場所に配置されているかを示すものです。**
つまり、Apatchは`http://{EC2のIPアドレス}/api/`でFlask側に処理を任せますが、Flaskは`http://{EC2のIPアドレス}/`で自身に処理が流れてくると誤認しています。これが原因です。

そこで、Flaskの`APPLICATION_ROOT`を設定します。
`APPLICATION_ROOT`は、**Webサーバのどのディレクトリに設置されているかをアプリケーションに知らせるものであり、デフォルトでは`/`になっています。**

`app.py`を以下のように書き換えます。
```diff_python:app.py
from flask import Flask
app = Flask(__name__)
+ app.config['APPLICATION_ROOT'] = '/api'

- @app.route('/api')
+ @app.route('/')
def index():
    return "Hello World!"
```
これでアプリケーションルートがFlaskとApatchで揃ったため、正常に動きます！