2015/04/17

pywebsocket でブラウザ ⇒ サーバに処理中止を指示(暫定)

[WebSocket][Python]
昨日の最後に書いた件、こんな方法で良いか自信ありませんが、とりあえずメモ。実行環境は昨日と同様 Windows 7 32bit + Python 2.7 + pywebsocket 0.7 + Firefox 36 ポータブル版です。ブラウザ ⇒ サーバに多少時間のかかる処理を指示し、進行状況を WebSocket で受信しつつ、必要があればブラウザから中止させるひな形。サーバがちゃんと中止処理を行ってから接続を切るようにします。

↓ ひな形の動作の様子。適当な文字列をブラウザから送り、サーバから一秒ごと・10回の適当なレスポンスを返します(時間のかかる処理と、進行状況表示の代わり)。途中で中止を指示すると、サーバ内の Python スクリプトが、「中止を指示された」「中止した」それぞれの時点でメッセージを送信して処理を止めます。中止の様子は動画の後半。

■ 20150417_demo.mp4


↓ 実行環境とファイル構成は昨日と同じで、index.html、main.js、test_wsh.py の三つの中身を変えただけ。他の二ファイルと合わせた一式は 20150417_demo.zip です。


以下、昨日からの変更点です。HTML はタイトル文字など若干のみ。

■ index.html
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Portable pywebsocket test 2</title>
<script type="text/javascript" src="main.js"></script>
<style type="text/css"><!--
body { font-family:monospace; padding:20px; font-size:13px }
#text1 { margin:15px 0 5px; width:60% }
#res { font-size:90%; line-height:1.5em; margin:10px }
--></style>
</head>
<body>
<div id="title"></div>
<input type="text" id="text1" value="" />
<input type="button" id="button1" />
<div id="res"></div>
</body>
</html>


↓ JavaScript は、中止指示を加えて少し長くなりました。disp_cancel 関数は、一つのボタンで送信/中止の二機能を切り替えるもの。文字列を送って WebSocket 通信を行う部分(action 関数)は基本的に昨日と同じですが、通信開始時にサーバからキャンセル用のキーを受け取ります。中止処理(cancel 関数)は、そのキーを新しい WebSocket 通信でサーバに送る仕組み。

■ main.js
// coding: utf-8

function $id(x) { return document.getElementById(x) }

function disp_cancel(x) {
var b1 = $id('button1');
if (x > 0) {
b1.value = '送る';
b1.style.color = 'black';
b1.onclick = action;
} else {
b1.value = '中止';
b1.style.color = 'red';
b1.onclick = cancel;
}
}

function action(){
var res = $id('res');
res.cancelkey = '';
res.url = 'ws://localhost:8180/test';
var socket = new WebSocket(res.url);

socket.onopen = function() {
socket.send($id('text1').value);
res.innerHTML = '受信開始...<br />';
disp_cancel(0);
}
socket.onmessage = function(e) {
if (e.data.indexOf('cancelkey=') == 0) {
res.cancelkey = e.data
return
}
res.innerHTML += e.data + '<br />';
}
socket.onerror = function() {
res.innerHTML += '<b>エラー発生</b><br />';
disp_cancel(1);
socket = undefined;
}
socket.onclose = function() {
res.innerHTML += '受信終了';
disp_cancel(1);
socket = undefined;
}
}

function cancel(){
var res = $id('res');
if (res.cancelkey == '') {
alert('no cancelkey');
return
}
var socket = new WebSocket(res.url);
socket.onopen = function() {
socket.send(res.cancelkey);
disp_cancel(1);
}
socket.onmessage = function(e) {
res.innerHTML += e.data + '<br />';
}
socket.onclose = function() {
socket = undefined;
}
}

window.addEventListener('load', function() {
$id('title').textContent = document.title;
disp_cancel(1);
$id('text1').focus();
});


↓ 最後がサーバ内の Python スクリプト。上で書いたキャンセル用のキーは、通信開始時に乱数から作り、これを名前とする空ファイル名を作成します。処理中の中止指示(キャンセル用のキー)を別の WebSocket 通信が受け取ったらファイルを削除。処理本体では、途中途中でファイルの存否を確認し、なければ処理を止めます。今回、別々の通信でキーが被る可能性は考慮していません。

■ test_wsh.py
# coding: utf-8

import datetime
import os
import time
import random

def web_socket_do_extra_handshake(request):
pass

def web_socket_transfer_data(request):
while True:
line = request.ws_stream.receive_message()
if isinstance(line, unicode):
response(request, line)
return

def response(request, line):
if cancel_1(request, line):
return

# キャンセル用に、乱数名でファイル作成
f_rand = str(random.random())
with open(f_rand, 'w') as f : pass
request.ws_stream.send_message('cancelkey=%s' % f_rand)

cnt = 10
for i in range(0, cnt):
if cancel_2(request, f_rand): return # 処理中止

# 以下、時間かかる処理の代わり
str_time = datetime.datetime.now().strftime('%H:%M:%S')
str_line = sorted(list(line), key=lambda k: random.random())
str_line = str_line[0 : random.randint(1, len(line) - 1)]
res = '%s (%d/%d) : %s' % (str_time, i+1, cnt, ''.join(str_line))
request.ws_stream.send_message(res)
time.sleep(1)

# キャンセル用ファイル削除
os.remove(f_rand)

def cancel_1(request, line):
keystr = 'cancelkey='
if line.find(keystr) == 0:
f_rand = line[ len(keystr) : ]
if os.path.isfile(f_rand):
os.remove(f_rand)
request.ws_stream.send_message(u'サーバ「中止を指示しました」')
return True
return False

def cancel_2(request, f_rand):
if os.path.isfile(f_rand) == False:
request.ws_stream.send_message(u'サーバ「中止しました」')
return True
return False


本来のターゲットは、昨日も書いたとおり PostgreSQL との連携。それに使う psycopg2 は非同期モードがあるので、今日の方法が無意味になるかもしれませんが、とりあえず原始的な方法のメモまで。あと一応、一つのブラウザ画面から並行して複数の(今回は処理本体と中止指示) WebSocket 通信を行うテストも兼ねました。
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。