[Work/Class/ES2015で動的Webアプリ/WebSocketを用いた動的Webアプリ]

ES2015でWebSocket - TornadoでSecure WebSocket

https://で接続するときにはwss://

現在ChromeでセキュアHTTP(https://)接続をしようとすると,WebSocketも強制的にSecure WebSocketになるので,その対応.

Tornado側の対応

セキュア接続に必要な証明書と鍵(.crtファイルと.keyファイル)はサーバ毎に用意されている(取得しておく)もので,そのコピーを持って来て読み込める場所に設置しておき,Tornadoのサーバ生成を定義する段階,つまりtornado.web.Application()関数を実行してapplicationインスタンスを作り,それを回し始める時にssl_optionで読み込ませる.

ただし,外からhttpで見えるフォルダ,例えばクライアントhtml,jsファイルと同じフォルダに置いておくと,セキュリティ上問題があるので,外からは見えない(例えばpublic_htmlの上の階層など)フォルダに置いて置いて,そこから読み込む必要がある.

サーバ側のコードが今までの非セキュアなWebSocketとはちょっと違うことに注意.

インスタンスを生成するために証明書と鍵(certファイルとkeyファイル)を読み込む必要があり,前項のようなtornado.web.Application()関数に全部突っ込んでインスタンスを生成するための書法が2017年11月現在まだ完成していないため,暫定的にこうなっていると思われる.

そのため事前にapplicationインスタンスを作っておく必要がある.

(WebSocketサービスだけなら今までのようにtonado.web.Applicationクラスのインスタンスで回せていたが,Secureになると,applicationを生成しておいてから,それをtornado.httpserver.HTTPServerクラスのインスタンスを使って回す形になる)

コード例

# -*- coding:utf-8 -*-
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.template
import tornado.httpserver

server_client_list = []
class SecureWSServer(tornado.websocket.WebSocketHandler):
    def check_origin(self, origin):
        return True

    def open(self):
        server_client_list.append(self)
        print("New Client Connected.")

    def on_message(self, message):
        print("Received message:" + message)
        for a_server_client in server_client_list:
            a_server_client.write_message(message)

    def on_close(self):
        if self in server_client_list:
            server_client_list.remove(self)
            print("Client Connection Closed")

application = tornado.web.Application(\
    [(r'/ws_secure_connect', SecureWSServer)])

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(\
        application, ssl_options={
            "certfile": "mycert.crt",\
            "keyfile":  "mykey.key"})
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html>
<!-- WSSecureConnect_Client.html -->

<html lang="ja">
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta charset="utf-8" />

  <title>Secure WebSocket</title>
  <script type="text/javascript" src="WSSecureConnect_Client.js">
  </script>
  
</head>
<body>
  <div id="controll_field">
    <p>
      <input type="button" value="接続開始" onclick="startConnection()" />
      <input type="button" value="接続終了" onclick="exitConnection()" />
    </p>
    <form>
      <input type="text" name="text_field" value="ここに入力して「送信」ボタンを押してください." />
      <input type="button" name="send_button" value="送信" onclick="sendMessage(this.form.elements['text_field'].value)" />
    </form>
  </div>
  <div id="view_field">
    <p>初期テキスト</p>
  </div>
</body>
</html>
// WSSecureConnect_Client.js
'use strict';
window.addEventListener('load', function(){
    window.webSocket = null;
    window.wsServerUrl = 'wss://localhost:8888/ws_secure_connect'; // SecureWS
}, false);

window.addEventListener('unload', function(event){
    window.exitConnection();
}, false);

window.startConnection = function(event){
    window.webSocket = new WebSocket(wsServerUrl);
    window.webSocket.onopen = function(event){
       window.document.getElementById("view_field").innerHTML =
            '<p>Success to connect WebSocket Server via SecureWS!</p>'
    };

    window.webSocket.onmessage = function(event){
	alert(event.data);
	window.document.getElementById("view_field").innerHTML =
	    '<p>Server: ' + event.data + '</p>'
    };

    window.webSocket.onerror = function(error){
	document.getElementById("view_field").innerHTML = 
	    '<p>WebSocket Error: ' + error + '</p>';
    };

    window.webSocket.onclose = function(event){
	if(event.wasClean){ //切断が完全に完了したかどうかを確認
	    document.getElementById("view_field").innerHTML =
	    '<p>切断完了</p><dl><dt>Close code</dt><dd>' + 
		event.Code + 
		'</dd><dt>Close Reason</dt><dd>' + 
		event.reason +
		'</dd></dl>';
	    webSocket = null;
	}
	else
	    document.getElementById("view_field").innerHTML =
	    '<p>切断未完了</p>';
    };

}

window.exitConnection = function(event){
    if(window.webSocket != null)
	window.webSocket.close(1000, '通常終了'); //onclose関数が呼ばれる
}

window.sendMessage = function(sendingMessage){
    if(window.webSocket != null)
	window.webSocket.send(sendingMessage);
}