PythonでWebアプリ
最近、Pythonじゃないと不便なことが少しずつ増えてきて、PythonコードでさらっとWeb応答返したいときがあります。なるべく手間をかけずに構築してみました。
環境
Ubuntu 18.04.1 LTS
Nginx 1.14.0
uWSGI 2.0.15
Let's Encryptの証明書
何はともあれインストール
UbuntuはAWSやAzureでポチっとすれば、すぐに用意できますね。
ひとまず必要なものをインストールしましょう。
# apt install nginx-light certbot python3-certbot-nginx uwsgi-plugin-python3
証明書のインストール
Let's Encryptのおかげで本当に楽になりました。Azure環境で実行した内容を貼り付けておきます。
実行すると、/etc/letsencrypt
以下にサーバー証明書を自動的に取得してくれる上に、/etc/nginx/sites-enabled/default
に設定してくれます。xxxx.yyyy.cloudapp.azure.comは、実際のサーバーのURLに合わせましょう。
下の引用ではわかりにくいのですが、コマンドを実行後、対話型で4回入力をしています。
- 自分のメールアドレス
- 利用規約に合意するかどうか (A)
- メールアドレスをElectronic Frontier(Let's Encryptの支援企業)に開示して良いか (Y/N)
- httpを全部httpsにリダイレクトするか (1/2)
# certbot -d xxxx.yyyy.cloudapp.azure.com --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): (自分のメールアドレス)
-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A
-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: Y
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for xxxx.yyyy.cloudapp.azure.com
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/default
-------------------------------------------------------------------------------
Congratulations! You have successfully enabled
https://xxxx.yyyy.cloudapp.azure.com
You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=xxxx.yyyy.cloudapp.azure.com
-------------------------------------------------------------------------------
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/xxxx.yyyy.cloudapp.azure.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/xxxx.yyyy.cloudapp.azure.com/privkey.pem
Your cert will expire on 2018-11-07. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
http2 on nginx
せっかくnginxだし、http2にしておきましょう。/etc/nginx/sites-enabled/default
の以下の部分を書き換えます。
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
sslの後にhttp2を入れるだけです。ついでにuwsgiの設定も入れておきましょう。ここではUnix Socketにしていますが、もちろんTCP/IPでも問題ありません。
listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
listen 443 ssl http2; # managed by Certbot
location /uwsgi/ {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}
設定が終わったら、nginxの再読み込み。
# service nginx reload
uWSGI
nginxでは、通常、起動効率が悪いCGIは使わない(使えない)のですが、代わりの流儀としてFastCGI, SGI, WSGIがあります。ここは多機能さでもWSGIでしょう。
もう最初のapt
でインストールしてあるので、単体で動くことを確認しましょう。
# uwsgi --plugin python3 --http-socket :8000 --wsgi-file test.py
#!env python3
# coding: utf-8
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
yield '<html><head><title>テストページ</title></head><body>はーい!</body></html>'.encode('utf-8')
これ(http://localhost:8000/)が問題なく動いていれば、nginxとの連携はuWSGIをデーモン起動させておくだけです。
パラメータはまとめてiniファイル化しておくのが良さそう。
[uwsgi]
socket = /tmp/uwsgi.sock
chdir = /home/myhome/app
plugin = python3
wsgi-file = /home/myhome/app/test.py
uWSGIを起動しておきます。一般ユーザーが良いでしょう。
$ uwsgi --ini uwsgi.ini -d uwsgi.log
Unix Socketを使ってしまったので、/tmp/uwsgi.sock
のパーミッションがnginxから書き込み可能じゃないとエラーになります。デフォルトのnginxの実行ユーザはwww-dataなので、デフォルトのumask 0002の場合には実行ユーザをusermod -G www-data -a xxxx
としてwww-dataグループに入れてから、実行時にnewgrp www-data
として実行するか、もしくはumask 0
にします。
Python3のWeb処理コード
application関数の第1引数にリクエストパラメーターが渡ってきます。ライトな処理ならそのままゴリゴリ対応しても良いし、しっかり作るならFlaskやDjangoを使うのも良いでしょう。
参考のため、生のリクエストパラメーターの中身はこんな感じです。(一部ホスト名やIPアドレスは伏字化してあります)
{
'QUERY_STRING': '',
'REQUEST_METHOD': 'GET',
'CONTENT_TYPE': '',
'CONTENT_LENGTH': '',
'REQUEST_URI': '/uwsgi/script',
'PATH_INFO': '/uwsgi/script',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_PROTOCOL': 'HTTP/2.0',
'REQUEST_SCHEME': 'https',
'HTTPS': 'on',
'REMOTE_ADDR': '***.***.***.***',
'REMOTE_PORT': '****',
'SERVER_PORT': '443',
'SERVER_NAME': 'xxxx.yyyy.cloudapp.azure.com',
'HTTP_HOST': 'xxxx.yyyy.cloudapp.azure.com',
'HTTP_CACHE_CONTROL': 'max-age=0',
'HTTP_UPGRADE_INSECURE_REQUESTS': '1',
'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.32 Safari/537.36',
'HTTP_SEC_METADATA': 'cause="forced", destination="document", target="top-level", site="cross-site"',
'HTTP_DNT': '1',
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','HTTP_ACCEPT_ENCODING': 'gzip,deflate,br',
'HTTP_ACCEPT_LANGUAGE': 'ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7',
'wsgi.input': <uwsgi._Input object at 0x7f5a991928d0>,
'wsgi.file_wrapper': <built-in function uwsgi_sendfile>,
'wsgi.version': (1,0),
'wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>,
'wsgi.run_once': False,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.url_scheme': 'https',
'uwsgi.version': b'2.0.15-debian',
'uwsgi.node': b'xxxx'
}