はじめに
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構築した際に悩んだポイントを説明します。
結論から言うと、app.py
のapp.config['APPLICATION_ROOT'] = '/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
のエラーが発生しています。つまり、リダイレクト先で処理されいてない状態です。
私のイメージとしては
- Apatchが
http://{EC2のIPアドレス}/api/
のリクエストを受け取る -
api_v1.conf
のWSGIScriptAlias /api "/var/www/api_v1/app.wsgi"
により、app.wsgi
に紐づけられ、Flaskに処理が渡される -
app.py
の@app.route('/api')
に紐づけられた関数が実行され、「Hello World!」が返される
というものでした。
しかし、どうやらapp.py
に処理が渡っていないようです。なぜでしょう?
ここで、Flaskの設定でAPPLICATION_ROOT
というものがあることに気づきました。
APPLICATION_ROOT
は、Webサーバのどのディレクトリに設置されているかをアプリケーションに知らせるものであり、デフォルトでは/
になっています。
つまり、今の状態は
- Flask側では
http://{EC2のIPアドレス}/
がアプリケーションルート - Apatch側では
http://{EC2のIPアドレス}/api/
がアプリケーションルート
といった設定になってしまっています。そのため、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で揃ったため、正常に動きます!