[Python]CnetOS7 + Apache + mod_wsgi + hug で簡単APIサーバ作成

簡単なAPIサーバを作りたいなーと思い、やってみました。

以前はPHPを少ししか書けなかったけど、もう少し色々な言語を使いたいと思いPythonで実装してみることにしました。

色々と調べてみるとPythonにもフレームワークは色々とあるようで、DjangoとかFlaskとかありましたが、簡単に実装できそうなhugを使うことにしました。

PHPだとWebサーバ上で動かすのは簡単だったのですが、Pythonだと割と大変だったので備忘録として残しておきます。

wsgiサーバはpythonだとgunicornとかuwsgiとかがポピュラーみたいでしたが、何やら設定が難しそうな印象を受けたので、なんとなく簡単そうなmod_wsgiを使うことにしました。

最終的にやりたかったBASIC認証までを追加することは出来ませんでしたが。。。

知っている方がいたら教えてほしいです。。

※20170914 追記 gunicorn使った方が簡単でした。

kittagon.hateblo.jp

Vagrantを使ってCentOSVMを作成

Vagrantfileはこんな感じです。

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.network "private_network", ip: "192.168.33.10"
"192.168.0.100" , :bridge => "en0: Wi-Fi (AirPort)"
  config.vm.synced_folder "./", "/vagrant", type: "virtualbox", :owner => "vagrant", :groupe => "vagrant", :mount_options => ['dmode=755', 'fmode=655']
  config.vm.provision "shell", run: "always", inline: "systemctl restart network.service"
end

Vagrant+Laravelでパーミッションで詰まった」とか、「vagrant + centos7 でprivate_networkで設定したIPに接続ができない」とかで躓いた経緯があったから、

config.vm.synced_folder "./", "/vagrant", type: "virtualbox", :owner => "vagrant", :groupe => "vagrant", :mount_options => ['dmode=755', 'fmode=655']
config.vm.provision "shell", run: "always", inline: "systemctl restart network.service"

とかを記載しています。

Python3系とApacheのインストール

hugはPython3系でしか動かないので、まずはPython3系をインストールします。

Apacheyumでインストールすればいいのですが、Pythonに関してはソースからコンパイルしたりyumで入れたりとか色々やり方はあったのですが、最終的に「Python 3 を CentOS 7 に yum でインストールする手順」のやり方を参考にしたら上手くいきました。

[Python] mod_wsgiを使ってPython3.6をApacheで動かす(CentOS6系)

このあたりを参考にソースからコンパイルする方法も試してみましたが、何故かpipでmod_wsgiをインストールするところでつまずいてしまいました。Cent0S6系と7系の違い?

とりあえず下記コマンドでインストールします。

# 色々インストール
$ yum groupinstall -y "Development tools"
$ yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel
# Apacheのインストール
$ yum install -y httpd httpd-devel
# OS起動時にApacheの起動を有効化
$ systemctl enable httpd.service
# Apacheの起動
$ systemctl start httpd.service
# Pyton3.6.xのインストール
$ yum install -y https://centos7.iuscommunity.org/ius-release.rpm
$ yum install -y python36u python36u-libs python36u-devel python36u-pip

$ python3.6 -V
Python 3.6.2

無事Python3.6とApacheがインストール出来ました。

mod_wsgiのインストール

こちらもソースからインストールしたりpip経由でのインストールがあるみたいですが、あえてソースからインストールするメリットも分からなかったので、pip経由でインストールすることにしました。

先程の[Python] mod_wsgiを使ってPython3.6をApacheで動かす(CentOS6系)の後半辺りを参考にしています。

$ pip3.6 install mod_wsgi

$ python3.6 -c "import sys; print(sys.path)"
['', '/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages']

インストールが完了し、パケージのインストール先が/usr/lib64/python3.6/site-packagesだと分かります。

$ cd /usr/lib64/python3.6/site-packages
$ ll
total 8
drwxr-xr-x. 6 root root 4096 Sep  5 13:19 lxml
drwxr-xr-x. 2 root root  131 Sep  5 13:19 lxml-3.8.0.dist-info
drwxr-xr-x. 6 root root   84 Sep  5 09:15 mod_wsgi
drwxr-xr-x. 2 root root  141 Sep  5 09:15 mod_wsgi-4.5.18-py3.6.egg-info
drwxr-xr-x. 2 root root    6 Jul 19 04:01 __pycache__
-rw-r--r--. 1 root root  119 Jul  8 03:33 README.txt

$ cd mod_wsgi/server
$ ll
total 1048
-rw-r--r--. 1 root root   1558 Sep  5 09:15 apxs_config.py
-rw-r--r--. 1 root root   3563 Mar 31  2016 environ.py
-rw-r--r--. 1 root root 128159 Aug 29 09:28 __init__.py
drwxr-xr-x. 4 root root     60 Sep  5 09:15 management
-rwxr-xr-x. 1 root root 932872 Sep  5 09:15 mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so
drwxr-xr-x. 2 root root    101 Sep  5 09:15 __pycache__

ここにある、mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.soを使います。

パスは/usr/lib64/python3.6/site-packages/mod_wsgi/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.soserverです。

Apacheの設定

これで必要なものはインストール出来たので、Apacheの設定をしていきます。

/etc/httpd/conf.d/配下にwsgi.conf設定ファイルを配置し、wsgiの設定を記述します。

LoadModule wsgi_module /usr/lib64/python3.6/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so

WSGIDaemonProcess myapp user=apache group=apache
WSGIProcessGroup myapp
WSGISocketPrefix /var/run/wsgi
WSGIScriptAlias / /var/www/app/app.py

<Directory /var/www/app/>

  Options ExecCGI MultiViews Indexes
  MultiViewsMatch Handlers

  AddHandler wsgi-script .py
  AddHandler wsgi-script .wsgi

  DirectoryIndex index.html index.py app.py

  Order allow,deny
  Allow from all

</Directory>

これを記述したらApacheを再起動しておきます。

$ apachectl restart

これで、ルートディレクト/を開いたときに、/var/www/app/app.pyを動作させるという設定になりました。

hugでapp.pyの作成

とりあえずこんな感じで書きました。

import sys,json,os
import hug
import util

@hug.get("/hoge")
def hoge(text):
    hoge_list = util.getHogeList(text)
    return json.dumps({"hoge":hoge_list})

今回はGETで受け取ったパラメータを他の関数に渡して処理してもらい、その結果を返すAPIを想定しているので、この様な書き方にしています。

実際に処理する関数はutil.pyに記載しています。

以下コマンドで、開発用サーバを起動します。

$ hug -f app.py

/#######################################################################\
          `.----``..-------..``.----.
         :/:::::--:---------:--::::://.
        .+::::----##/-/oo+:-##----:::://
        `//::-------/oosoo-------::://.       ##    ##  ##    ##    #####
          .-:------./++o/o-.------::-`   ```  ##    ##  ##    ##  ##
             `----.-./+o+:..----.     `.:///. ########  ##    ## ##
   ```        `----.-::::::------  `.-:::://. ##    ##  ##    ## ##   ####
  ://::--.``` -:``...-----...` `:--::::::-.`  ##    ##  ##   ##   ##    ##
  :/:::::::::-:-     `````      .:::::-.`     ##    ##    ####     ######
   ``.--:::::::.                .:::.`
         ``..::.                .::         EMBRACE THE APIs OF THE FUTURE
             ::-                .:-
             -::`               ::-                   VERSION 2.3.1
             `::-              -::`
              -::-`           -::-
\########################################################################/

 Copyright (C) 2016 Timothy Edmund Crosley
 Under the MIT License


Serving on port 8000...

開発用のサーバがポート8000番で立ち上がるので、 $ curl http://localhost:8000/hoge?text=hoge で結果が帰ってくればOKです。 また、http://192.168.33.10:8000/hoge?text=hogeにアクセスでも見れます。

開発用サーバではなく、mod_wsgi経由でApacheでも動作しているか確認したいので、mod_wsgiでも動くようにします。 mod_wsgiで動作させるには、mod_wsgiに対応したapplicationを作る必要があります。

mod_wsgiでhugを使い、APIを作るPython hug with apache mod_wsgiを参照。

app.pyの最下部に以下の一文を追記します。

application = __hug_wsgi__

ブラウザで、http://192.168.33.10/hoge?text=hogeにアクセスして同じ結果が帰ってくればOK。

・・・なのですが、何やら「Internal Server Error」のエラーが。

Apacheのエラーログを見てみると、

[Sat Sep 09 13:28:00.511142 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] mod_wsgi (pid=7361): Target WSGI script '/var/www/app/app.py' cannot be loaded as Python module.
[Sat Sep 09 13:28:00.511233 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] mod_wsgi (pid=7361): Exception occurred processing WSGI script '/var/www/app/app.py'.
[Sat Sep 09 13:28:00.511368 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] Traceback (most recent call last):
[Sat Sep 09 13:28:00.511434 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939]   File "/var/www/app/app.py", line 4, in <module>
[Sat Sep 09 13:28:00.511441 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939]     import util
[Sat Sep 09 13:28:00.511469 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] ModuleNotFoundError: No module named 'util'

なにやらutil.pyが読み込めていない模様。

Pythonで「ImportError: No module named …」が出た時の3つの対処法を参考に、sys.path.append('/path/to/dir')app.pyに追記すると直りました。

mod_wsgiでは若干挙動が違う?

Basic認証を追加

The guiding thought behind the architectureを参照すると、getの引数にhug.authentication.verify("userid","password")を与えるとBasic認証が出来るようになるみたいです。

そのままユーザーID、パスワードを記述するのも良くないので、.envファイルを作成してそこに環境変数を書くとします。

【GitHub】に載せたくない環境変数の書き方 Python こちらにdotenvの使い方が載っています。

$ pip3.6 install python-dotenv

.envの中身は↓にしました。

HUG_USER_NAME=uname
HUG_PASSWORD=pass

この中の環境変数は、以下のように取り出します。

import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

HUG_USER_NAME = os.environ.get("HUG_USER_NAME")
HUG_PASSWORD = os.environ.get("HUG_PASSWORD")

これらの変数を用いて以下のようにBasic認証の設定を記述します。

import sys,json,os
sys.path.append('/var/www/app')
import hug
import util
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

HUG_USER_NAME = os.environ.get("HUG_USER_NAME")
HUG_PASSWORD = os.environ.get("HUG_PASSWORD")

authentication = hug.authentication.basic(hug.authentication.verify(HUG_USER_NAME, HUG_PASSWORD))

@hug.get("/hoge",,requires=authentication)
def hoge(text):
    hoge_list = util.getHogeList(text)
    return json.dumps({"hoge":hoge_list})
    
application = __hug_wsgi__

開発用サーバを立ち上げて、ブラウザからhttp://192.168.33.10:8000/hoge?text=hogeに認証画面が出て認証できればOKです。

Apacheからの接続はどうかというと、http://192.168.33.10/hoge?text=hogeに接続すると、無事認証画面が出ました!!!

が、、IDとパスワードを入れても認証出来ません。。。。

いくら調べても原因が分からない。。なんでなんだ~。。。。

今回はここで断念しました。。。

まとめ

mod_wsgiとhugを使って、簡単なAPIサーバを立ち上げることが出来ました。

ただ、最終的にやりたかったBasic認証はなぜかmod_wsgiだと正常に動作しませんでした。

hugに組み込まれている開発用のサーバとmod_wsgiとでは若干挙動が違うようです。

解決策を知っている方が居ましたら教えて下さい。

参考ページ