はじめに
AWSのEC2上にAPIを作成したときに、かなり苦労したので 「構築の仕方」 と 「陥った失敗」 について解説します。EC2上で実装していますが、この記事を読む上でEC2の知識は不要です。
目次
APIとは
APIについて知っている人は読み飛ばしてしまっても構いません。初めて聞く人や聞いたことあるけど具体的に理解してない人向けに書きます。
APIとは、Application Programming Interfaceの略です。ここで、インターフェース (interface) とは異なるシステムで情報のやり取りを可能にするための窓口です。
想像しやすいインターフェースの例として、PCに付いているType-CやType-Aの差し込み口が挙げられます。これらは、PCとマウスやキーボード(つまり異なる製品)をつなぐ窓口の役割を担っています。このように異なるもの同士をつなぐことがインターフェースの役割です。
そして、アプリケーション同士をつなぐ役割を担っているインターフェース(窓口)をAPIと呼びます。 APIの目的は、アプリケーションの内部機能を外部から利用できるようにすることです。 例えば、「Twitter APIを利用して、ツイートを取得・投稿する」などです。
システムの全体像
今回はAWSのEC2上でApatchをWebサーバーとしてPythonのフレームワークであるFlaskを用いて実装していきます。以下の図がシステム構成のイメージです。

AWS EC2上にApatch HTTP ServerとFlaskを使用するAPIの全体像
また、本システムでは、HTTPリクエストを送ると「Hello World!」を返すようなAPIを実装します。
$ curl http://{EC2のIPアドレス}/api/
Hello World!
構築の方法
ここからは、実際の構築方法を説明します。
Step1. Apatch HTTP Serverをインストールし、起動
$ sudo su root
現在のユーザーからスーパーユーザーに切り替えます。
- スーパーユーザーとは、システム上で最も高いレベルの権限を持つユーザーのことを指します。
-
sudoにより、一時的にスーパーユーザー権限を使用しており、su rootにより、ユーザー切り替えを行います - ちなみに、
suの由来はswitch userです
# yum -y install httpd
次に、Apatch HTTP Serverをインストールします。
-
yumはパッケージマネージャのコマンドです -
-yにより、ユーザー確認を自動的にyesにしています -
httpdはApatch HTTP Serverのパッケージ名です
Apatch HTTP Serverを起動させる。また、システム起動時に自動的に起動するように設定変更する。
# systemctl start httpd
Apatch HTTP Serverを起動します。
-
systemctlはサービスの起動や停止などを行うためのコマンドです -
start httpdにより、httpdを起動しています
# systemctl enable httpd
システム起動時にApatch HTTP Serverが自動的に起動するように設定します。この設定は必須ではありません。
-
enableはサービスをシステム起動時に自動的に起動するように設定するためのコマンドです
Step2. Flaskをインストール
# 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つのコンポーネントで構成されます。
-
WSGIサーバ
Webサーバーのことを指し、HTTPリクエストを受け取り、それをWSGIアプリケーションに渡す役割を果たします。 -
WSGIアプリケーション
Pythonで書かれたWebアプリケーションのことを指し、サーバーから受け取ったリクエストを処理し、レスポンスを生成して返します。
そして、mod_wsgiとはWSGIに準拠したPythonのプログラムをApache HTTP Serverで動作させるためのモジュール です。
ダラダラと書きましたが、結論としては mod_wsgiを介すことで、Apache HTTP ServerとFlaskを接続できるようになります。
mod_wsgiのインストール方法
# 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を作成
# cd /var/www
# mkdir api_v1
# cd api_v1
/var/www 内にapi_v1というフォルダを作成し、api_v1に移動します。/var/www は、Apatch HTTP Serverがデフォルトでウェブコンテンツを配置するディレクトリです。
/var/www/
├── html/ # デフォルトのドキュメントルート
├── site1/ # サイト1のファイル
├── site2/ # サイト2のファイル
例えば、curl http://{EC2のIPアドレス}/でHTMLファイルを開きたい場合、/var/www/html/にHTMLファイルを配置します。
今回はsite1の場所にapi_v1フォルダを作成し、そこにPythonファイルを置きます。
# touch app.py
# vi app.py
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を作成
# touch app.wsgi
# vi app.wsgi
WSGIとFlaskと連携するために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を連携
# cd /etc/httpd/conf.d
# touch api_v1.conf
# vi api_v1.conf
Apatch HTTP ServerとWSGIを連携するためにapi_v1.confという定義ファイルをhttpd内に作成します。
<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に連携できていない状態になっているということです。
# 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"
# 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. 動作確認
$ curl http://{EC2のIPアドレス}/api/
Hello World!
想定通りに動きました!
陥った失敗
ここからは、私がAPI構築した際に上手くいかなかったポイントを共有したいと思います。
結論から言うと、http://{EC2のIPアドレス}/ではなく、http://{EC2のIPアドレス}/api/をトリガーとする部分でかなり手こずりました。
当時の私は、APPLICATION_ROOTという存在を知りませんでした。
from flask import Flask
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api' # この行
@app.route('/')
def index():
return "Hello World!"
Flaskのアプリケーションルートを知らなかった私は、最初は以下のような設定で実行していました。
from flask import Flask
app = Flask(__name__)
@app.route('/api')
def index():
return "Hello World!"
この状態で実行すると、次のようになります。
$ 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を以下のように書き換えます。
from flask import Flask
app = Flask(__name__)
+ app.config['APPLICATION_ROOT'] = '/api'
- @app.route('/api')
+ @app.route('/')
def index():
return "Hello World!"
これでアプリケーションルートがFlaskとApatchで揃ったため、正常に動きます!