Flet のウェブアプリを Apache ウェブサーバのリバースプロキシで動かす

Python でサクッとデスクトップアプリが作れる Flet ですが、同じコードに少しの変更をするだけでブラウザで動かせるようになります。今回の記事は、公式サイトにはない、Apache ウェブサーバで Flet アプリを動かす (セルフホスティングする) 方法です。

まず簡単に Flet とは

GUI やウェブフロントエンドの知識・経験がほとんど無くても、お手軽にデスクトップアプリやウェブアプリが作れる Python のフレームワークです。本家 (?) は Flutter という、Google さんが Dart という言語向けに開発しているモバイルアプリ用フレームワークです。それを Python から利用できるようにしたのが Flet と考えて良さそうです。実際、Flet 出書いたコードから吐き出されるエラーをネットで検索すると、Flutter に関するポストが多くヒットします。本記事では Flet も Flutter も深く触れませんので、詳細は他のサイトを漁ってください。

この記事で説明すること

キモは、Apache の TCP ポート指定を利用したリバースプロキシで Flet のウェブアプリを公開する、というところです。公開と言ってもボクの環境では、LAN にある mac からブラウザでアクセスできるようにするまでですが、パブリックに公開している Apache ウェブサーバでも同様の方法で公開できます。Flet の公式サイトでは、こちらの Self Hosting で NGINX ウェブサーバを使用した公開方法が紹介されていますが、それの Apache 版ということです。本記事のタイトルそのものズバリを説明しているサイトが見つからなかったので、まとめました。

環境

  • Ubuntu 20.04 LTS
  • Apache 2.4.41

ざっくりとした手順

  1. Ubuntu サーバに、Flet アプリの実行に必要なパッケージをインストール
  2. Python の仮想環境を作り、Flet をインストール
  3. Flet アプリを準備
  4. リバースプロキシに必要なモジュールを Apache で有効化
  5. Apache のコンフィグファイルを書く
  6. 自動的に起動する設定を書く

細かい手順

Ubuntu サーバに、Flet アプリの実行必要なパッケージをインストール

公式サイトの通り、Linux で Flet を動かすためには GStreamer のインストールが必要です。ここはサクッと入れてしまいましょう。

sudo apt-get update
sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

Python の仮想環境を作り、Flet をインストール

ボクは pipenv を使っているので、こんな感じで仮想環境を作ります。手慣れた仮想環境とバージョンで、ここもサクッとどうぞ。サポートされている Python のバージョンは 3.8 以上です。

pipenv --python 3.11
pipenv shell
pip install flet

Flet アプリを準備

とりあえず、公式からそれっぽいものを持ってきましょう。インタラクションが確認できるので、ボクはこちらの Counter app を使用しました。とりあえず counter.py として保存し、最後の行を下のように編集します。

ft.app(target=main, view=None, port=8501)

簡単に説明すると、view=None でウィンドウやブラウザによる表示を行わず、ポート 8501 で待ち受けるよう指示をしています。ポートは同一サーバ上で重複が無ければ何でもかまいません。GUI とブラウザがインストールしてある環境であれば、python3 counter.py で実行すると、http://localhost:8501 にアクセスすればウェブアプリが開くと思います。次の手順以降で、外部にウェブアプリとして公開します。

リバースプロキシに必要なモジュールを Apache で有効化

Apache でリバースプロキシを行うには、いくつか必要なモジュールを追加する必要があります。Flet の場合、web socket も利用するため、wstunnel も必要です。以下は、モジュール追加、Apache の再起動、ステータス確認を実施しています。

sudo a2enmod proxy proxy_http proxy_wstunnel headers
sudo systemctl restart apache2
sudo systemctl status apache2

Apache のコンフィグファイルを書く

この例では、クライアント (PC 等のブラウザ) から flet.dev.peddals.com にアクセスすると Flet ウェブアプリが開く構成にしています。また、別記事に書いた様にこのドメインへの接続は HTTPS 接続になるので、Apache はポート 443 で待ち受け、内部的に 8501 ポートへリバースプロキシしています。このあたりはご自身の環境に合わせて指定してください。

13-14行目の wss:// の部分はひょっとしたら環境によっては必要ないかもしれません。

<VirtualHost *:443>
	ServerName flet.dev.peddals.com

	SSLEngine on
	SSLCertificateFile /etc/letsencrypt/live/dev.peddals.com/fullchain.pem	
	SSLCertificateKeyFile /etc/letsencrypt/live/dev.peddals.com/privkey.pem

	ProxyRequests Off
	ProxyPreserveHost On

	ProxyPass /ws ws://localhost:8501/ws
	ProxyPassReverse /ws ws://localhost:8501/ws
	ProxyPass /ws wss://localhost:8501/ws
	ProxyPassReverse /ws wss://localhost:8501/ws
	ProxyPass / http://localhost:8501/
	ProxyPassReverse / http://localhost:8501/

	ErrorLog ${APACHE_LOG_DIR}/flet.error.log
	CustomLog ${APACHE_LOG_DIR}/flet.log combined

</VirtualHost>

Apache に設定を読み込ませます。

sudo apachectl configtest
sudo systemctl reload apache2
sudo systemctl status apache2

この状態で一度 python3 counter.py で実行し、別のクライアント PC からサイトへアクセスし、動作するか確認してみましょう。wss:// の行を削除して読み込みが終わらない様でしたら追加してください。

自動的に起動する設定を書く

ここは公式のやり方を参考に編集します。自分の環境に合わせたものを貼っておきます。これを fletcounter.service として、counter.py と同じディレクトリに保存しています。

[Unit]
Description=Flet Counter Service
After=network.target

[Service]
User=handsome
Group=handsome
WorkingDirectory=/home/handsome/codes/flet
Environment="PATH=/home/handsome/.local/share/virtualenvs/flet-xuR7EMBP/bin/"
ExecStart=/home/handsome/.local/share/virtualenvs/flet-xuR7EMBP/bin/python3 /home/handsome/codes/flet/counter.py

[Install]
WantedBy=multi-user.target

いじる部分 (いじった内容) は以下の通りです:

  • Description= はご自由に
  • User=Group= には自分のユーザ名 (whoami)
  • WorkingDirectory= には、counter.py のあるディレクトリのパス
  • Environment="PATH= には、python3 のあるディレクトリのパス (which python3 の出力の bin/ まで)
  • ExecStart= の最初の引数は which python3 の出力全て、次の引数には counter.py のフルパス

そして最後にサービスとして起動、有効化します。これも公式のやり方に従います。シンボリックリンクの元ファイルは、上記のファイルを指定します。

cd /etc/systemd/system
sudo ln -s /home/handsome/codes/flet/fletcounter.service
sudo systemctl start fletcounter
sudo systemctl enable fletcounter
sudo systemctl status fletcounter

以上で設定はおしまいです。クライアント PC からアクセスし、カウンターが表示されれば OK です。可能であればサーバを再起動し、起動後にもカウンターが表示されることを確認しましょう。

ハマったところ

自分の環境で当初発生していた、読み込みが一生終わらない状態を解決するのにすごく時間がかかりました。原因は、ProxyPassProxyPassReverse にそれぞれ ws://http:// の両プロトコルだけしか指定していないことでした (公式の NGINX のリバースプロキシのコンフィグにも wss:// は無い) 。wss がウェブソケットのセキュア版 (http に対する https) と気づけなかったら諦めていたと思います。– なんて言いつつ、その後ラズパイの SSD が死に、リバースプロキシの設定をやり直したところ、wss:// の 2行が無くても問題なく動くようになっていました。ナゾ。

Image by Stable Diffusion

Date:
2023年11月25日 23:02:10

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
cartoon, clolorful,
modern ladies working at post office classifying letters

Exclude from Image:

Seed:
4084494267

Steps:
23

Guidance Scale:
11.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

LAN 内の開発用 Ubuntu に Let’s Encrypt の無料 SSL 証明書をインストール

HSTS preload の申請が通ったら、開発用サイトも HTTPS 接続必須となった (それは、そう)

会社で使っているドメインのセキュリティ向上のため HSTS preload の申請を行ったら良い感じでした。なので、個人所有のドメイン peddals.com も申請をしました。サブドメインを含め (includeSubDomains) 常に HTTPS で読み込む設定にしたため、クローズドな環境 (自宅 LAN 内の Raspberry Pi 3 model B) で http のみで運用していた開発サイトがアクセスできなくなりました。いくつか解決策を検討し、今回は Let’s Encrypt の無料 SSL 証明書を入れて対応してみたので、その手順を共有します。

開発環境と、その他必要だったもの

  • ラズパイ (Raspberry Pi 3 model B)
  • Ubuntu 20.04 LTS
  • Apache 2.4.41
  • ドメイン名
  • DNS サーバへのアクセス (TXT レコードの追加権限)

ざっくりとした手順

  1. Certbot をインストール
  2. certbot コマンドの実行 (DNS チャレンジ)
  3. 出力された値を DNS に TXT レコードとして記入
  4. Let’s Encrypt 証明書がインストールされる
  5. Apache で SSL を有効にする
  6. 開発サイトの Apache 設定ファイルのポートを 443 に変更し、SSL 証明書の内容を追記

細かい手順

証明書の取得 (CLI + DNS レコード追加)

まずは証明書のインストールに必要な certbot をインストールします (sudo bash で管理者権限を持ってから実行しています)。念のため、$# 以降が入力したコマンドで、それ以外は出力サンプルです。

$ sudo bash
# apt-get update
# apt-get install certbot

自分の場合、*.dev.peddals.com を対象にしたワイルドカード証明書をインストールしたいので、そのドメイン名を加えて以下コマンドで申請しました (別記事に書くかもしれませんが、mac では DNSmasq を動かしており、*.dev.peddals.com へのアクセスは全てラズパイに飛ばしています)。この例ではトップレベルドメインも含んでいますが、必要なわけではありません。

# certbot certonly --manual --preferred-challenges dns-01 -m mail@example.com -d '*.dev.peddals.com' -d peddals.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.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 our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

今回はなぜかここで止まって進まなかったので一度 ctrl + C で止め、再度同じ certbot コマンドを実行しています。問題なく進んだので原因は調べていません、あしからず。

メールアドレスの共有 (上の質問) は強制では無かったと思いますが、IP アドレスの登録 (下の質問) は必須なので Y を入力しましょう。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for dev.peddals.com
dns-01 challenge for peddals.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.dev.peddals.com with the following value:

(ここにコードが表示される)

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

ここで一度止まるので、指示の通りに DNS に TXT レコードとして追加します。こんな感じです。

ホスト名にドメイン名が入らないように注意

追加したら CLI に戻ってエンターキーを押します。自分の例ではトップレベルドメインも -d で追加していたので、もう一度 DNS レコード記入の指示が出ました。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.peddals.com with the following value:

(ここにコードが表示される)

Before continuing, verify the record is deployed.
(This must be set up in addition to the previous challenges; do not remove,
replace, or undo the previous challenge tasks yet. Note that you might be
asked to create multiple distinct TXT records with the same name. This is
permitted by DNS standards.)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

同じ要領で DNS に追加し、エンターキーを押します。うまくいけば以下のように表示されて、証明書の取得が完了です。

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/dev.peddals.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/dev.peddals.com/privkey.pem
   Your cert will expire on 2024-02-10. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - 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

秘密鍵や証明書の保存場所は IMPORTANT NOTES に書いてあります (忘れたら /etc/letsencrypt/live/ を見ましょう)。証明書の期限は 90日で、つど再取得の必要があります。再取得後も Apache の設定ファイルを書き直さなくて良いように、それぞれ実ファイルへのリンクになっています。

# ll /etc/letsencrypt/live/dev.peddals.com/
total 12
drwxr-xr-x 2 root root 4096 Nov 12 19:00 ./
drwx------ 3 root root 4096 Nov 12 19:00 ../
-rw-r--r-- 1 root root  692 Nov 12 19:00 README
lrwxrwxrwx 1 root root   39 Nov 12 19:00 cert.pem -> ../../archive/dev.peddals.com/cert1.pem
lrwxrwxrwx 1 root root   40 Nov 12 19:00 chain.pem -> ../../archive/dev.peddals.com/chain1.pem
lrwxrwxrwx 1 root root   44 Nov 12 19:00 fullchain.pem -> ../../archive/dev.peddals.com/fullchain1.pem
lrwxrwxrwx 1 root root   42 Nov 12 19:00 privkey.pem -> ../../archive/dev.peddals.com/privkey1.pem

Apache とサイトの設定ファイル

Apache で SSL が使えるようにします。最後の 2行で Apache を再起動 & 動いているか確認しています。

# a2enmod ssl
Considering dependency setenvif for ssl:
Module setenvif already enabled
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
See /usr/share/doc/apache2/README.Debian.gz on how to configure SSL and create self-signed certificates.
To activate the new configuration, you need to run:
  systemctl restart apache2
# systemctl restart apache2
# systemctl status apache2

サイトのコンフィグファイルに変更を加えます。ここでは変更、追加の部分のみ記載しています。先頭の TCP ポートを 443 に変更し、SSL の有効化、証明書と秘密鍵の指定を追加しています。最低限ブラウザでサイトが開くようにするための設定内容です。HSTS preload により全てのホストとの接続が HTTPS になるため、ポート 80 の設定ファイルに 443 へのリダイレクトを仕込むことも不要です。

<VirtualHost *:443>
	SSLEngine on
	SSLCertificateFile /etc/letsencrypt/live/dev.peddals.com/fullchain.pem	
	SSLCertificateKeyFile /etc/letsencrypt/live/dev.peddals.com/privkey.pem

設定ファイルに間違いが無いかテストし、Apache に設定を読み直させます。

# apachectl configtest
Syntax OK
# systemctl reload apache2

ブラウザで確認

最後に、ウェブブラウザで HTTPS で開ければ無事完成です。自動的に HTTPS 通信になるので、https:// を入力する必要はありません。

Safari でアクセス
もちろん Chrome でも開ける

Let’s Encrypt 証明書は 90日で期限が切れる

証明書の有効期限は 90日なので、なるべく切れる前に更新しましょう。期限切れまで 30日であれば更新できるようです。まぁ、開発用なのでとりあえずはつど手動で更新しましょうか。--dry-run オプションを付けてエラーが出ていなければ、オプション無しでもう一度実行して更新します。

# certbot renew --dry-run
# certbot renew

更新が必要ないときに実行すると、有効期限が切れる日付を表示してくれます。

# certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/dev.peddals.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/dev.peddals.com/fullchain.pem expires on 2024-02-10 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

自動化は crontab コマンドへの登録で実現できますが、今回は触れません。

ところで、HSTS の書き方・置き場所

本サイトはエックスサーバーに置いているので、ドキュメントルートに .htaccess を作り、そこに以下の一行を追加しています。includeSubDomains で全てのサブドメインを対象とし、preload でサイトへのアクセスは HTTPS に固定しています。

Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

まとめ

HSTS での設定以外にでも、開発環境に SSL 証明書をインストールしたい理由はいろいろあると思います。無料とはいえ本物の証明書が使えるので、この方法は試す価値大アリです。

Image by Stable Diffusion

Date:
2023年11月12日 17:59:44

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
cartoon, let’s encrypt, apache, success

Exclude from Image:

Seed:
356871963

Steps:
30

Guidance Scale:
11.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Copyright (C) Peddals.com