Pythonでローカルアプリケーションサーバを作る。

# Pythonでローカルアプリケーションサーバを作る。

この手の話になるとどうもソリューションが大げさになりがち。
真っ当なサーバを用意したくない・出来ない。フレームワークを覚えるのが面倒だ等々。
手軽に何とかするために仕組みを作ります。

黒い画面が怖がられる場合も多いかと思うのでTkinterのGUIを使えるように調整します。
とはいえ ```run_forever``` を停止させる方法がわからんみたいなのは一度は通る道かと思うのでその辺りどうにかします。

## httpサーバ

職場によってはセキュリティポリシーなどでHTMLを直接開けない場合などもあり、httpdが解決の糸口（抜け道）になったりしますのでまずはそこから。
ここまで実装すれば普通にPythonのCGIも動作します。（ここでは詳しいことには触れません）

**httpd.py**

```python
import http.server
import threading
import os
import time

class httpmod(http.server.HTTPServer):
    def run(self):
        try:
            self.serve_forever()
        except keyboardInterrupt:
            pass
        finally:
            self.server_close()

class HTTPd(threading.Thread):
    def __init__(self, threadID):
        threading.Thread.__init__(self)
        self.threadID = threadID

    def run(self):
        hostIP = ""
        addr = (hostIP, 8000)
        handler = http.server.CGIHTTPRequestHandler
        handler.cgi_derectories = ["/"]
        self.server = httpmod(addr, handler)

        self.wst = threading.Thread(target=self.server.run)
        self.wst.daemon = True
        self.wst.start()

    def stop(self):
        self.server.shutdown()
        self.wst.join()
```

Tkinterに絡めた使い方は次の様な感じ。

**httpd_test.pyw**

```python
import httpd
import tkinter as tk
import os
import sys

class App:
    def __init__(self, root):
        self.root = root
        self.server = httpd.HTTPd(0)
        self.server.run()
        self.root.protcol("WM_DELETE_WINDOW", self.quit)

    def quit(self):
        self.server.stop()
        self.server = None
        self.root.destroy()

if __name__ == "__main__":
    sys.stdout = sys.stderr = open(os.devnull, "w")
    root = tk.Tk()
    App(root)
    root.mainloop()
```

このようになります。
カレントディレクトリに```index.html```など用意してブラウザで```127.0.0.1:8000```を開くとブラウズ出来ることを確認します。

## webSocketサーバ

あくまでPython主体で何かするならwebSocketが便利なので実装します。
※httpdと同じくwebSocketも止まらないので止めるための仕組みを入れました。

```websocket_server``` は標準モジュールではないのでインストールします。

```sh
pip install websocket-server
```


**websocket.py**

```python
import websocket_server
import threading
import json

class WebSocket(threading.Thread):
    def __init__(self, ThreadID, host="", port=50001):
        threading.Thread.__init__(self)
        self.threadID = ThreadID
        self.host = host
        self.port = port

    def newClient(self, client, server):
        dat = {"hello": "welcome"}
        self.send(dat)

    def msgReceived(self, client, server, message):
        dat = {"hello": "i'm received (%s)" % message}
        self.send(dat)

    def send(self, data):
        self.wss.send_message_to_all(json.dumps(data))

    def run(self):
        self.wss = websocket_server.WebsocketServer(port=self.port, host=self.host)
        self.wss.set_fn_new_client(self.newClient)
        self.wss.set_fn_message_received(self.msgReceived)

        self.wsst = threading.Thread(target=self.wss.run_forever)
        self.wsst.daemon = True
        self.wsst.start()

    def stop(self):
        self.wss.shutdown()
        self.wsst.join()
```

データのやり取りにはjsonを使うものとして、sendだけ実装してみました。
httpdとwebsocketを取り込むとこんな感じになります。

**httpd_test2.pyw**

```python
import httpd
import websocket
import tkinter as tk
import os
import sys

class App:
    def __init__(self, root):
        self.root = root
        self.server = httpd.HTTPd(0)
        self.server.run()
        self.wsserver = websocket.WebSocket(0)
        self.wsserver.run()
        self.root.protocol("WM_DELETE_WINDOW", self.quit)

    def quit(self):
        self.server.stop()
        self.server = None
        self.wsserver.stop()
        self.wsserver = None
        self.root.destroy()

if __name__ == "__main__":
    sys.stdout = sys.stderr = open(os.devnull, "w")
    root = tk.Tk()
    App(root)
    root.mainloop()
```

javascript側の受け取り部はこんな感じになりますね。

```javascript
con = new WebSocket("ws://127.0.0.1:50001/ws")

con.onmessage = function(e) {
    dat = JSON.parse(e.data);
    console.log(dat);
}
```

ブラウザ側からコンソールで```con.send("hello")```等するとwebsocket.pyのmsgReceiveが動作して返事が返ってくるのを確認します。

websocket.pyについては目的によってmsgReceivedの処理を変えたい場合などもあると思います、その場合はWebSocketクラスを継承し、msgRecievedをオーバーライドすれば良いです。

## とあるひとつの利用ケース

Tkinterがあるのでわざわざこんな仕組みを使わなくても大抵のことは可能ですが、それでもこの仕組みを使った方が良いというものには**「帳票印刷」**なんてものがあります。
Python、印刷系弱いですからね。（時代はペーパーレスですしね）

SVGを利用すれば1つのHTMLだけで印刷フォーマットを作ることが出来、pyAutoGui等を使えば自動的なプリント操作も可能です。
帳票の内容は動的に作成してもいいですし、webSocketで部分的な書き換え指示を投げても良いかと思います。

何かしらのヒントになれば幸いです。