Pythonでローカルアプリケーションサーバを作る。
この手の話になるとどうもソリューションが大げさになりがち。
真っ当なサーバを用意したくない・出来ない。フレームワークを覚えるのが面倒だ等々。
手軽に何とかするために仕組みを作ります。
黒い画面が怖がられる場合も多いかと思うのでTkinterのGUIを使えるように調整します。
とはいえ run_forever
を停止させる方法がわからんみたいなのは一度は通る道かと思うのでその辺りどうにかします。
httpサーバ
職場によってはセキュリティポリシーなどでHTMLを直接開けない場合などもあり、httpdが解決の糸口(抜け道)になったりしますのでまずはそこから。
ここまで実装すれば普通にPythonのCGIも動作します。(ここでは詳しいことには触れません)
httpd.py
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
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
は標準モジュールではないのでインストールします。
pip install websocket-server
websocket.py
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
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側の受け取り部はこんな感じになりますね。
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で部分的な書き換え指示を投げても良いかと思います。
何かしらのヒントになれば幸いです。