MySQLのデータベースに入ってるデータをグラフにしてwebから見られるようにしたくて、pythonでCGIを書こうとしたけど躓いたのでその対処の備忘録です。
pythonでwebアプリを作る場合、一般的にはdjango等のフレームワークを使うのが今時だと思うし、実際別件でdjangoを使い始めてる。でもフレームワークはフレームワークで使いこなすまでが大変だったりして、現にdjangoはやりたいことは頭に浮かんでて、データフローやHTML、javascript、pythonでの処理もわかってるのにdjangoのしきたりに合わせた書き方をしようとするとファイルの依存関係とかがややこしく感じてしまってなかなか進まない。。。長く継続的にフレームワークを触り続けるならいいんだろうけど、たまにしか使わない自分にはなかなかハードルが高い気がして、今回はフレームワークを使わない方法を採用した。
環境
今回の環境は以下の通り。
- OS:Ubuntu 22.04.1 LTS(確認コマンド:lsb_release -a)
- apache:Apache/2.4.55(確認コマンド:apache2 -V)
- python:3.10.12(確認コマンド:python -V)
pythonは以下のエイリアスを切って使ってる。
alias python="/usr/bin/python3"
alias pip="/usr/bin/pip3"
apache2の設定
pythonに限らず、CGIを使うためにはapacheの設定が必要なので、まずはServer Worldさんのapache2でCGIを使う手順を参考にapacheの設定から。
CGIモジュールを有効にする
kirin@cf-n10$ sudo a2enmod cgid
[sudo] kirin のパスワード:
Enabling module cgid.
To activate the new configuration, you need to run:
systemctl restart apache2
CGIの実行を許可するディレクトリを設定する
/etc/apache2/conf-available/cgi-enabled.confを作成して、実行するCGIを置くフォルダを設定する。
CGIを置くフォルダは/var/www/html/hogehoeとし、拡張子がcgi、pyのファイルを実行できるように設定した。
<Directory "/var/www/html/hogehoe"> Options +ExecCGI AddHandler cgi-script .cgi .py
CGI の実行を許可する
a2enconfでCGIの実行を許可し、apacheに設定を反映させる。
kirin@cf-n10$ sudo a2enconf cgi-enabled Enabling conf cgi-enabled. To activate the new configuration, you need to run: systemctl reload apache2 kirin@cf-n10$ sudo systemctl reload apache2
動作確認
/var/www/html/hogehoeの下に、下記のtest01.pyファイルを作成。
#!/usr/bin/python3 print ("Content-Type: text/html\n\n") print ("hello klirin!!")
スクリプトのパーミッションを設定。
chmod 755 test01.py
curlでアクセスしてみると正常に表示された。
kirin@cf-n10:$ curl http://xx.xx.xx.xx/hogehoge/test01.py
hello klirin!!
データベースにアクセスするスクリプトを作る
apacheの準備が整ったのでMySQLのdbからレコードを読み出して表示するスクリプトを作ったところ、500 Internal Server Errorになって動かなかったのでその対処のメモ。
MySQLを使うためにモジュールをインストールする
pip install mysql-connector-python
dbからレコードを読みだして表示するスクリプトを作成する
@IKEHさんの記事を参考にスクリプトを作ってみた。
コピペで作ったのがこれ(tmp2.py)。
#!/usr/bin/python3 import mysql.connector import cgitb; cgitb.enable() print ("Content-Type: text/html\n\n") # DBへ接続 conn = mysql.connector.connect( user='root', ← MySQLのid password='password', ← MySQLのパスワード host='localhost', database='hogehoge' ← db名 ) # DBの接続確認 if not conn.is_connected(): raise Exception("MySQLサーバへの接続に失敗しました") cur = conn.cursor(dictionary=True) # 取得結果を辞書型で扱う設定 query__for_fetching = """ SELECT time, name FROM counters ORDER BY time LIMIT 5 ; """ cur.execute(query__for_fetching) for fetched_line in cur.fetchall(): time = fetched_line['time'] name = fetched_line['name'] print(f'{time}:{name}<br>')
パーミッションを設定を忘れずに、
chmod 755 tmp2.py
動作確認
とりあえず、コマンドラインで実行してみると、意図した通の結果が得られた。
kirin@cf-n10:/var/www/html/hogehoge$ python tmp2.py
Content-Type: text/html
2023-02-20 00:15:05:counter100
2023-02-20 00:15:30:counter100
2023-02-20 00:16:35:counter101
2023-02-20 00:17:05:counter102
2023-02-20 00:17:20:counter100
curlを使ってwebアクセスしてみると、、、なんと500 Internal Server Errorが。
kirin@cf-n10:/var/www/html/hogehoge$ curl http://xx.xx.xx.xx/hogehoge/tmp2.py
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator at
webmaster@localhost to inform them of the time this error occurred,
and the actions you performed just before this error.</p>
<p>More information about this error may be available
in the server error log.</p>
<hr>
<address>Apache/2.4.55 (Ubuntu) Server at xx.xx.xx.xx Port 80</address>
</body></html>
500 Internal Server Errorの調査
エラーの発生個所だけに絞った下記スクリプト(tmp1.py)で原因の調査をはじめた。先に原因を書くと問題はモジュールのimportだった。
#!/usr/bin/python3 import mysql.connector import cgitb; cgitb.enable() print ("Content-Type: text/html\n\n") print ("hello kirin!!")
上記スクリプトにブラウザでアクセスすとInternal Server Errorになる。
もちろん、コマンドラインでpython tmp1.pyとすると正常に動作する。
CGIとして動かしているということは、apacheがスクリプトを実行しているわけで、apacheの実行ユーザ(www-data)では動かない何かがあるのかなといろいろ試してみた。
rootでは動くのか?
kirin@cf-n10:/var/www/html/hogehoge$ sudo -u root ./tmp1.py Traceback (most recent call last): File "/var/www/html/hogehoge/./tmp1.py", line 3, inimport mysql.connector ModuleNotFoundError: No module named 'mysql'だ
ダメだった。
まずは、モジュールが無いというエラーなので、モジュールのインストールパスを調べる。
kirin@cf-n10:/var/www/html/hogehoge$ python -m pip -V
pip 23.3.1 from /home/kirin/.local/lib/python3.10/site-packages/pip (python 3.10)
モジュールのインストールをしたのは、rootではなく一般ユーザなので、そのユーザのホームの下にある。これまで意識したことはなかった。。。
モジュールのパスを設定する方法をググったところ、レバテックのteratailにsys.path.append()を使えばできると書いてあった。
tmp1.pyにモジュールパスを追加するために赤字のコードを追加した。
#!/usr/bin/python3
import sys
sys.path.append("/home/kirin/.local/lib/python3.10/site-packages/")
import mysql.connector
import cgitb; cgitb.enable()
print ("Content-Type: text/html\n\n")
print ("hello kirin!!")
rootで試してみると動いた。
kirin@cf-n10:/var/www/html/hogehoge$ sudo -u root ./tmp1.py
Content-Type: text/html
hello kirin!!
パスの追加が効果あることはわかった。ではapacheの実行ユーザであるwww-dataで試してみる。
kirin@cf-n10:/var/www/html/hogehoge$ sudo -u www-data ./tmp1.py Traceback (most recent call last): File "/var/www/html/hogehoge/./tmp1.py", line 6, inimport mysql.connector ModuleNotFoundError: No module named 'mysql'や
やっぱりだめ。
多分、モジュールのフォルダのアクセス権が無いんじゃないかと試してみたら、予想通りだった。
kirin@cf-n10:/var/www/html/hogehoge$ sudo -u www-data ls /home/kirin/.local/lib/python3.10/site-packages/
ls: '/home/kirin/.local/lib/python3.10/site-packages/' にアクセスできません: 許可がありません
いろいろとググってみると、2つの対処法がありそうなことが分かった。
(1) モジュールをインストールしたユーザのグループにwww-dataを追加する
(2) rootにモジュールをインストールする
どっちが正当な対処方かというと、セキュリティ、管理面ともに(2)だなぁ。
500 Internal Server Errorの対策
rootにpythonのモジュールをインストールする。
まず、rootにmysql-connector-pythonがインストールされていないことを確認。
kirin@cf-n10:/var/www/html/hogehoge$ sudo pip list | grep mysql
kirin@cf-n10:/var/www/html/hogehoge$
MySQLに関するモジュールは何も入ってなかった。
mysql-connector-pythonモジュールをインストールする。
sudo pip3 install --upgrade pip
sudo pip install mysql-connector-python
インストールされたことを確認。
kirin@cf-n10:/var/www/html/hogehoge$ sudo pip list | grep mysql
mysql-connector-python 8.2.0
動作確認
モジュールのパスを追加していたコードはいらなくなるので、コメントアウト。
#!/usr/bin/python3
# import sys
# sys.path.append("/home/kirin/.local/lib/python3.10/site-packages/")
import mysql.connector
import cgitb; cgitb.enable()
print ("Content-Type: text/html\n\n")
print ("hello kirin!!")
www-dataで実行してみる。
kirin@cf-n10:/var/www/html/hogehoge$ sudo -u www-data ./mon.py
Content-Type: text/html
hello kirin!!
動いた。
ブラウザからアクセスしてみる。
kirin@cf-n10:/var/www/html/hogehoge$ curl http://xx.xx.xx.xx/hogehoge/tmp1.py
hello kirin!!
動いた。chromeからも同様に動いた。
結論は、apacheの設定の他に、スクリプトで使うモジュールをrootにもインストールする必要があったということ。
ここまで動けば、後はアプリを作っていくだけ。その過程でモジュールを追加した際にrootにインストールし忘れないようにしないと。