uWSGIをインストールして動かす（Windows）

## 概要

みなさんもご存知でしょうが…　WebサーバーであるnginxはWindowsPCにもインストールができます。
でも、アプリケーションサーバーがないんですよねぇ… なので燃えてしまった！ そして動いた。 そして動いてしまえば後で考えると案外簡単だった！ の記録です。

### ポイント
* Python+Flaskで書いたWebAPIを、手元のWindowsPCで稼働させたい
* Webサーバーはnginxを想定しているが、nginx unit はWindowPCでは動かせそうにない
* uWSGIもWindowsプラットフォーム用のバイナリは無いが、cygwin上でコンパイルできる
* コンパイルできたuWSGIのWindowsサービス化も簡単

## 背景
**知らなかったのです。Windows10でIISを稼働させることができるなんて！**

IISを稼働させるには、Windowsのサーバー用のOSが必須なんだと思っていました。
しかし今は、Windows10などのサーバー用のOSでなくても、Webサーバーを建てたりすることができるんですね！？ Microsoftが提供しているのですから、接続数の制限などはありながらも…　手元のWindowsにWebサーバー機能を有効にして利用することがOKだったのですね…
　参考： [IIS 用に Python Web アプリを構築する](https://learn.microsoft.com/ja-jp/visualstudio/python/configure-web-apps-for-iis-windows?view=vs-2022)

なので、そんなWindowsには無知な私はIISを利用すれば実現可能なことを、nginx＋アプリケーションサーバーでPythonのプログラムでWebアプリケーションの開発・テスト環境を構築できないのか…　とトライしたら案外簡単にできてしまった＂記録＂なのです。
IISでできることと同じ事を…
　

そして、Windowsの利用規約ではここから解説する方法での利用は制限される可能性があります。そして２０台までのWindows環境からの接続ならばOKなのか？ IISを利用した環境であれば利用が認められるのか？ 等についても正確には理解できていません。難しい…


## 1.nginxのインストールとサービス化

### 1-1．nginxのインストール
nginxのインストールは、すこぶる簡単です。
圧縮されているファイルをダウンロードして伸張するだけです。

#### 1-1-1.ダウンロード
https://nginx.org/en/download.html から、Stable versionのzipファイル(nginx/Windows-1.22.1)をダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/f0d7564f-89d9-62ae-097d-a44727d3ae95.png)

#### 1-1-2.圧縮ファイルの伸張
zipファイルを伸張できるアプリケーションで、c:¥nginxに配置されるように伸張します。
っていうか、伸張後にnginxをディレクトリごと移動してもOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/5090f9d8-7ec3-a1a0-a2d4-54bacfa6af67.png)

#### 1-1-3.確認
コマンドプロンプトなどで、以下を実行。
```terminal:DOS窓
> cd c:¥nginx
> start nginx
>
```
こんな感じで、動いちゃいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/3dd4d3c8-c453-7395-4c83-867c23259b02.png)

### 1-2.nginxのサービス化
Windowsサービスは、プロセスをバックグラウンドで実行でき、ブート時の自動起動の設定などもでき、サーバープロセスを運用するうえでは必須と言っても良い機能でしょう。しかしGUIベースの普通のアプリケーションを利用することが多いユーザーにとっては縁遠い機能なのかもしれません。
Windowsサービス化も便利なツールが存在し、今では簡単に実現できます。

#### 1-2-1.winswのダウンロード
https://repo.jenkins-ci.org/artifactory/releases/com/sun/winsw/winsw/ から、最新のバージョンを選択し、実行ファイル（ winsw-2.9.0-bin.exe ）をダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/bebe6996-b59d-1f35-65a7-77f8329edd79.png)

#### 1-2-2.winswのリネーム
都合の良いディレクトリに配置し、実行ファイルのファイル名を変更します。
なぜファイル名を変更するかというと…　このファイルを実行する時に、拡張子の違う(.xml)同じファイル名の定義ファイルを参照するような仕組みになっているからなのです。

ここでは `winsw-2.9.0-nginx.exe`にリネームすることとしましょう。
```bat:DOS窓
>cp winsw-2.9.0-bin.exe winsw-2.9.0-nginx.exe
```

#### 1-2-3.サービス定義用XMLファイルの作成
＂拡張子の違う(.xml)同じファイル名の定義ファイル＂なので、winsw-2.9.0-nginx.xml というファイルに以下をコピペしてください。

&lt;executable&gt; と &lt;stopexecutable&gt; には、先ほど動作を確認した、`nginx.exe`をフルパスで指定しましょう。 &lt;logpath&gt;はログファイルのパスです。お好きなところに…
っで、それ以外はコピペそのままでOKです。

```xml:winsw-2.9.0-nginx.xml
<service>
  <id>nginx</id>
  <name>nginx</name>
  <description>nginx</description>
  <logpath>c:\nginx\logs</logpath>
  <logmode>roll</logmode>
  <depend></depend>
  <executable>c:\nginx\nginx.exe</executable>
  <startargument></startargument>
  <stopexecutable>c:\nginx\nginx.exe</stopexecutable>
  <stopargument>-s</stopargument>
  <stopargument>stop</stopargument>
</service>
```

#### 1-2-4.サービス化
以下のコマンドを実行します。
```bat:DOS窓
>winsw-2.9.0-nginx.exe install
```

#### 1-2-5.サービス化の確認とスタートアップの設定
「タスクマネージャ」や「サービス管理ツール」からスタートアップの種類の確認（＂自動＂）してサービスを起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/8965c11d-f1d1-95bb-247f-462b32b42771.png)

これで、PCを再起動しても nginx は自動で起動してくれ、何も操作しなくてもWebサーバーとして起動し続けてくれます。
簡単簡単！！


## 2.uWSGIのインストールとサービス化
さて、ここからが本題です。 Pythonでも利用できるアプリケーションサーバーである uWSGI を扱います。
OSがLinux系であれば、nginx の unit をインストールするだけなので、とりあえず flask で書いておけばWebアプリケーションのサーバーサイドはサクッと作れます。っが、Windowsには対応していません…　
　　https://unit.nginx.org/installation/

そんなんで困っていて、いろいろWebを探っていたら… なんと uWSGI なら Cygwin 上でコンパイルができてしまうということではないですか！？ **Cygwin** 懐かしい響きです。 まだあったんですね？！
期待半分諦め半分で、ちょりっと試してみましょう！！

### 2-1.Pythonのインストール
アプリケーションで想定しているメジャーバージョンのPythonをインストールします。
アプリケーションで想定しているバージョンでなくても良く…　uWSGIのインストール時に必要なのです。でも、ゆくゆくこのマシン上でアプリケーションをPythonで動かしたいとかあるかと思いますので、合わせておいた方が良い。というレベルです。
今回のアプリケーションは3.7で構築していましたので、 [https://www.python.org/downloads/](https://www.python.org/downloads/) などから取得しPython3.7.8をインストールします。

**※ インストールプロダクト**
* python37 3.7.8
* python37-devel 3.7.8

### 2-2.Cygwinのインストール

#### 2-2-1.ダウンロード
https://cygwin.com/install.html から setup-x86_64.exe (https://cygwin.com/setup-x86_64.exe) をダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/51b111fd-f1c4-df75-b150-b8107b4784b6.png)

#### 2-2-2.インストール
ダウンロードした setup-x86_64.exe を起動しインストールを始めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/ead4524f-4e2d-6b66-dd18-45ed4242d10c.png)

こんな感じでインストールが完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/9d534656-f053-d549-e857-fe378c43d124.png)


#### 2-2-3.環境変数の設定
cygwin のPATH（ C:¥cygwin64¥ ）を環境変数に追加しておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/a8ee6da4-9909-6664-bf47-ca49dc6a5254.png)

#### 2-2-4.Cygwinの必要なパッケージのインストール
この後uWSGIをCygwin上でコンパイルする上で、Cygwinにパッケージがインストールされていなければいけません。
以下のように Cygwin　のインストーラーを起動し…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/ec0229f5-c4f9-9172-d492-d5c5f29f35e2.png)
Select Packages の画面で、必要なパッケージをインストールします。
この時、2-1でインストールしたPythonと同じメジャーバージョンのPythonもインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/62cd6918-7039-97a6-dbe6-f4b9a0fa0aec.png)

**※ 必須パッケージ**
* gcc-core 9.3.0-2
* gcc-g++ 9.3.0-2
* gettext 0.21-1
* libintl-devel 0.21-1
* libreadline-devel 8.1-2
* libssl-devel 1.1.1l-1
* make 4.3-1
* unzip 6.0-17
* wget 1.21.2-1
* zlib 1.2.11-1
* zlib-devel 1.2.11-1
* python37　3.7.10−2
* python37-devel　3.7.10−2

### 2-3.uWSGIのインストール

#### 2-3-1.uWSGIのソースのダウンロード
[Getting uWSGI : https://uwsgi-docs.readthedocs.io/en/latest/Download.html](https://uwsgi-docs.readthedocs.io/en/latest/Download.html) からuWSGIのソースファイル **uwsgi-2.0.20.tar.gz** をダウンロードします。
このフローを確立するまでに、２種類ほどのuWSGIのコンパイルにトライしましたが、上手くいきませんでした。
しかしそれらのバージョンは全て2.0.19が前提でした。 しかしその後uWSGIを調べていると2.0.20が存在することに気づき…　2.0.20を前提として進めることにしました。もしかするとこれが良かったのかもしれません… が、正確なところは分かっていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/558f3557-6043-4cfb-c9c1-ac4f4a9c27a8.png)

#### 2-3-1.uWSGIソースファイルの伸長
この工程は、Cygwin で実行します。
2-3-1 でダウンロードしたファイルをCygwinで扱える適当なフォルダに移し伸張し、そのディレクトリに移ります。
```terminal:Cygwin
$ tar vxzf uwsgi-2.0.20.tar.gz
$ cd uwsgi-2.0.20
```

#### 2-3-2.uWSGIのインストール
ついに…　インストールです！
```terminal:Cygwin
$ python setup.py install
/us/lib/python3.7/distutils/dist.p:274: UserWarning: Unknown distribution option: "c warnings.warn(msg)
running install using profile: buildconf/default.ini detected include path: ['/usr/include'
'/usr/local/include'7
Patching "bin_name" to properly install_scripts dir detected CPU cores: 1 configured CFLAGS:
-02 -I. -Wall -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -Wextra -V
*** UWSGI compiling server core ***
[gcc] core/utils.o
[gcc] core/protocol.o
[gcc] core/socket.o
[gcc] core/logging.o
[gcc] core/master.o
[gcc] core/master_utils.o
[gcc] core/emperor.o
[gcc] core/notify.o
[gcc] core/mule.o
[gcc] core/subscription.o
[gcc] core/stats.o
[gcc] core/sendfile.o
[gcc] core/async.o
[gcc] core/master_checks.o
　　：
router_hash/router_hash.o plugins/router_expires/expires.o plugins/router_metrics/plugin.o plugins/transformation_template/tt.o plugins/stats_pusher_socket/plugin.o -lpthread -lm -rdynamic -lz -luuid -lssl -lcrypto -lcrypt -lintl -ldl -lpython3.7m -lcrypt

################# uWSGI configuration #################

kernel = CYGWIN_NT-10.0-16299
execinfo = False
ifaddrs = True
locking = windows_mutex
event = poll
timer = auto
filemonitor = auto
pcre = False
routing = False
capabilities = False
yaml = embedded
json = False
ssl = True
xml = False
debug = False
plugin_dir = .
zlib = True
ucontext = False
malloc = libc

############## end of uWSGI configuration #############
total build time: 327 seconds
*** uWSGI is ready, launch it with /usr/bin/uwsgi ***
running build
running build_py
creating build
creating build/lib
copying uwsgidecorators.py → build/lib
running install_lib
copying build/lib/uwsgidecorators.py → /usr/lib/python3.7/site-packages
byte-compiling /usr/lib/python3.7/site-packages/uwsgidecorators.py to uwsgidecorators.cpython-37.pyc
running install_egg_info
running egg_info
creating uWSGI.egg-info
writing uWSGI.egg-info/PKG-INFO
writing dependency_links to uWSGI.egg-info/dependency_links.txt
writing top-level names to uWSGI.egg-info/top_level.txt
writing manifest file 'uWSGI.egg-info/SOURCES.txt'
reading manifest file 'uWSGI.egg-info/SOURCES.txt'
writing manifest file 'uWSGI.egg-info/SOURCES.txt'
Copying uWSGI.egg-info to /usr/lib/python3.7/site-packages/uWSGI-2.0.20-py3.7.egg-info
running install_scripts
```

こんな感じでコンパイルが完了し、コンパイルされた実行ファイルは、以下にあります。
```terminal:Cygwin
$ ls -alh ./bin/
合計 13K
drwxr-xr-x 1 takizawa Domain Users   0 10月 15 20:23 .
drwxr-xr-x 1 takizawa Domain Users   0 10月 15 21:22 ..
-rwxr-xr-x 1 takizawa Domain Users 113 10月  6  2021 uwsgi
```

Cygwin上での実行ファイルはここにあるんかぁ？？
which で見つかるファイルが .exe というのはちょっと違和感がありますけどねぇ…
```terminal:Cygwin
$ which uwsgi
/usr/bin/uwsgi

$ ls -alh /usr/bin/uwsgi.exe
-rwxr-xr-x 1 takizawa Domain Users 1.3M 10月 20 00:50 /usr/bin/uwsgi.exe
```

そして、Windowsマシンで実行すべきファイルは、以下にあります。
```bat:DOS窓
>where uwsgi
C:\cygwin64\bin\uwsgi.exe

>dir C:\cygwin64\bin\uWSGI.*
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は ****** です

 C:\cygwin64\bin のディレクトリ

04/10/20  00:50         1,264,923 uwsgi.exe
               1 個のファイル           1,264,923 バイト
               0 個のディレクトリ  39,022,579,712 バイトの空き領域
```

#### 2-3-3.uWSGIの起動確認
起動の確認 （portは5001）を指定します。
```bat:DOS窓
>uwsgi --master —http 127.0.0.1:5001
** Starting uWSGI 2.0.20 (64bit) on [Tue Dec 20 20:26:58 2022]* *
compiled with version: 9.3.0 on 08 November 2021 12:14:07
os: CYGWIN_NT-10.0-16299-3.3.1-341.x86_64 2021-10-28 20:52 UTC
nodename: YRL54-39492
machine: x86_64
clock source: unix
detected number of CPU cores: 4
current working directory: /home/takizawa/uwsgi-2.0.20
detected binary path: /usr/bin/uwsgi
!!! no internal routing support, rebuild with pcre support !!!
your memory page size is 4096 bytes
detected max file descriptor number: 3200
lock engine: windows mutexes
thunder lock: disabled (you can enable it with —thunder-lock)
uWSGI http bound on 127.0.0.1:5001 fd 4
uwsgi socket 0 bound to TCP address 127.0.0.1:50743 (port auto-assigned) fd 3
Python version: 3.7.10 (default, May  5 2021, 11:43:58)  [GCC 10.2.0]
** Python threads support is disabled. You can enable it with —enable-threads* *
Python main interpreter initialized at 0x8000ad310
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 145840 bytes (142 KB) for 1 cores
** Operational MODE: single process* *
** no app loaded. going in full dynamic mode* *
** uWSGI is running in multiple interpreter mode* *
spawned uWSGI master process (pid: 3574)
spawned uWSGI worker 1 (pid: 3575, cores: 1)
spawned uWSGI http 1 (pid: 3576)
```

動いてますねぇ！！ （停止は、`Ctrl+c`）
curl でアクセスしてもレスポンスがあります。アプリケーションが無いので500エラーになりますが…　アプリケーションサーバーとしては動いていそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/80dde496-8205-04aa-0293-cc69ef479115.png)

### 2-4.uWSGIのサービス化
最後に…　uWSGI のサービス化です。
このサービス化は、nginxをサービス化した時と同じwinswを利用します。

…と、その前に、uWSGIを起動するにはいくつか設定した情報を渡してあげなくてはいけません。

#### 2-4-1.uwsgi.iniファイルの作成
起動時に渡すパラメータをファイルにまとめることができます。
サービス化した後に変更することも想定すると、ファイルにまとめておいた方が良いですよね。
とりあえずは、以下のように記載されていれば、今回の環境であれば問題なく動くはずです。
:warning: `c:\cygwin64\etc\conf\`や`c:\cygwin64\var\log\`フォルダが存在しなければmkdirしておいてください。
```ini:uwsgi.ini
[uwsgi]
module = index
master = true
http-socket = 127.0.0.1:5001
wsgi-file = \cygwin64\usr\share\nginx\app.py
pidfile = \cygwin64\etc\conf\uwsgi.pid
logto = \cygwin64\var\log\uwsgi.log
```

#### 2-4-2.app.py ファイルの作成
Webアプリケーションを記載する、Pythonのソースファイルです。
あれば良いという感じなので、この辺をコピペして置いておけば良いかと思います。
:warning: このテスト時はflaskとかは未導入でもOKです。500エラーになるだけです
```python:app.py
from flask import Flask ,render_template,request

application = Flask(__name__)

@application.route('/')
def index():
    return render_template('index.html')

if __name__=="__main__":
    application.run(host='0.0.0.0')
```

#### 2-4-3.アプリケーションサーバーのコマンドでの起動停止確認
iniファイルを参照し起動します。
アプリケーションを停止する場合は、指定したpidファイルを介して渡されるPIDにシグナルを送り停止します。

起動コマンド
`>c:\cygwin64\bin\uwsgi.exe --ini \cygwin64\etc\conf\uwsgi.ini`
停止コマンド
`>c:\cygwin64\bin\uwsgi.exe --stop \cygwin64\etc\conf\uwsgi.pid`

以下の①〜④の流れで、uWSGIの起動・停止を行うことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/bc5bbdbb-4bf9-b1bf-8507-8920aacb6482.png)

#### 2-4-4.winswのリネーム
都合の良いディレクトリに配置し、実行ファイルのファイル名を変更します。
なぜファイル名を変更するかというと…　このファイルを実行する時に、拡張子の違う(.xml)同じファイル名の定義ファイルを参照するような仕組みになっているからなのです。
1-2-2.winswのリネーム と同様に、定義ファイルと同じ名前にリネームします。
```bat:DOS窓
>cp winsw-2.9.0-bin.exe winsw-2.9.0-uwsgi.exe
```

#### 2-4-5.サービス定義用XMLファイルの作成
＂拡張子の違う(.xml)同じファイル名の定義ファイル＂なので、winsw-2.9.0-uwsgi.xmlに以下をコピペしましょう。

&lt;executable&gt; と &lt;stopexecutable&gt; には、2-4-3で確認した、起動コマンド・停止コマンドを指定します。 &lt;logpath&gt;のログファイルはnginxと同じところで良いでしょう…

```xml:winsw-2.9.0-uwsgi.xml
<service>
  <id>uWSGI</id>
  <name>uWSGI</name>
  <description>uWSGI</description>
  <logpath>\cygwin64\var\log</logpath**>
  <logmode>roll</logmode>
  <depend></depend>
  <executable>\cygwin64\bin\uwsgi.exe</executable>
  <startargument>--ini</startargument>
  <startargument>\cygwin64\etc\uwsgi.ini</startargument>
  <stopexecutable>\cygwin64\bin\uwsgi.exe</stopexecutable>
  <stopargument>--stop</stopargument>
  <stopargument>\cygwin64\etc\conf\uwsgi.pid</stopargument>
</service>
```

#### 2-4-6.サービス化
以下のコマンドを実行します。
```bat:DOS窓
>winsw-2.9.0-uwsgi.exe install
```
「タスクマネージャ」や「サービス管理ツール」からスタートアップの種類の確認（＂自動＂）してサービスを起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/52b00e12-97c3-763e-658c-f84a62214c69.png)

#### 2-4-7.確認
アプリケーションが適当なので、相変わらず500エラーですが…　アプリケーションサーバーは動いているようです。すごい！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726928/63b6afc8-ed52-809f-8319-729fcb550775.png)

#### 2-4-8.nginx と uWSGI の連携設定
では、せっかく nginx もサービス化していますので、連携させましょう。
nginx のコンフィグファイルの server のセクションにアプリケーションサーバーにプロキシーする設定を追記します。
その後、nginx を再起動して終了です！！

```conf:nginx.conf
http {
    include       mime.types;
    default_type  application/octet-stream;

    server {
        listen 443 ssl;
        server_name  api.server.name;

        ### "/API/" をuWSGI へプロキシー
        location /API {                                ## ここを追加
            proxy_pass http://127.0.0.1:5001/;         ## ここを追加
        }                                              ## ここを追加
    }
}
```

これで最初に『実現したい！』と思ったことは実現できました。
まぁ、案外簡単だった…　と書いた割には手間はかかっていますが、個人的には意外とスムーズに実現できた感じがしています。


## 最後に
背景でも言及しましたが、IISで実現すればもっと簡単だったかもしれません。
そして2022年の今では、dockerで実現するのがふつ〜なのかもしれません。
そして更にふつ〜に、WSLを利用すると更にシンプルだったのかもしれません。
最初っから、OSがLinuxである環境に構築すれば良かったのかもしれません。

そして、上記のどの構造で構築しどのような利用の仕方なら、利用規約の範囲に収まるのか？？ 分かりません…
目的はどれも一緒なんです。 一番遠回りしてしまったのかもしれませんが。

さらに…　こんな問題も明らかになりました。
* pythonのAWSのboto3パッケージはこの環境には対応できず、boto3のソースをいじってimportできるようにしました。
* os.path.join()の挙動が、pythonで直接扱う場合とuWSGIから実行する場合で差異があり工夫が必要です。
* uWSGI内で実行されるPythonはuWSGIのコンパイル時にpuluginとして埋め込まれるpythonが利用され、（onnxruntime等の）一部のPythonパッケージが正しくimportできません。

:warning: **皆さんは、真似しない方が良いと思います。 …ハイ**


## 参考文献
* [nginx > (windows) nginxのインストールとサービス常駐](https://qiita.com/sugasaki/items/83542f5614bc54a9475f)
* [windows に uswgi を インストール ](https://www.inmyzakki.com/entry/2017/06/02/213642)（今は参照できません）
* [Windows10上で動くサーバ環境を作ってみた](https://qiita.com/lucida3rd/items/04f0d6f16a4251e6f9f9#iInstUwsgi)（今は参照できません）