Pythonスクリプト同士でwebsocketのserver-client通信

やっとできた。ポイントは

(1)通常のserver.py+index.html構成のうち、index.htmlを無視したものが欲しいサーバプログラム。index.htmlのscript領域に記載したプログラムの挙動が、欲しいclientプログラム。
clientプログラムの実装方式は任意。ws4pyライブラリ+tornadoとか、
lirisのwebsocket-clientライブラリとか。
(2)Clientの接続時にHandshake status 200エラーが出る→ルート(ベースルータ)ではなくWebsocketトランスポートにアクセスする。Websocketトランスポートのアドレスはサーバ側で指定する
→tornadoだとapplication = tornado.web.Applicationの設定で

  (r'/ws', WSHandler),

の様に/wsに割り当てる。
(参考:https://github.com/mrjoes/tornadio/issues/21)

1. サーバ側: tornado使用

http://www.remwebdevelopment.com/blog/python/simple-websocket-server-in-python-144.html
server.py

import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.template

class MainHandler(tornado.web.RequestHandler):
  def get(self):
    loader = tornado.template.Loader(".")
    self.write(loader.load("index.html").generate())

class WSHandler(tornado.websocket.WebSocketHandler):
  def open(self):
    print 'connection opened...'
    self.write_message("The server says: 'Hello'. Connection was accepted.")

  def on_message(self, message):
    self.write_message("The server says: " + message + " back at you")
    print 'received:', message

  def on_close(self):
    print 'connection closed...'

application = tornado.web.Application([
  (r'/ws', WSHandler),
  (r'/', MainHandler),
  (r"/(.*)", tornado.web.StaticFileHandler, {"path": "./resources"}),
])

if __name__ == "__main__":
  application.listen(9090)
  tornado.ioloop.IOLoop.instance().start()

index.htmlはコピペ(or script領域を抜く→あとで好きな挙動に入れ替える)
index.html(今は使わないけど置いとく)

<!DOCTYPE html>
<html>
<head>
  <title>WebSockets Client</title>  
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
Enter text to send to the websocket server:
<div id="send">
    <input type="text" id="data" size="100"/><br>
    <input type="button" id="sendtext" value="send text"/>
</div>
<div id="output"></div>
</body>
</html>
<script>

jQuery(function($){

  if (!("WebSocket" in window)) {
    alert("Your browser does not support web sockets");
  }else{
    setup();
  }


  function setup(){
   
    // Note: You have to change the host var 
    // if your client runs on a different machine than the websocket server
    
    var host = "ws://localhost:9090/ws";
    var socket = new WebSocket(host);
    //console.log("socket status: " + socket.readyState);   
    
    var $txt = $("#data");
    var $btnSend = $("#sendtext");

    $txt.focus();

    // event handlers for UI
    $btnSend.on('click',function(){
      var text = $txt.val();
      if(text == ""){
        return;
      }
      socket.send(text);
      $txt.val("");    
    });

    $txt.keypress(function(evt){
      if(evt.which == 13){
        $btnSend.click();
      }
    });

    // event handlers for websocket
    if(socket){

      socket.onopen = function(){
        //alert("connection opened....");
      }

      socket.onmessage = function(msg){
        showServerResponse(msg.data);
      }

      socket.onclose = function(){
        //alert("connection closed....");
        showServerResponse("The connection has been closed.");
      }

    }else{
      console.log("invalid socket");
    }

    function showServerResponse(txt){
      var p = document.createElement('p');
      p.innerHTML = txt;
      document.getElementById('output').appendChild(p); 
    }	


  }

});

</script>

2-a. クライアント側その1→websocket-clientライブラリ

client.py
https://github.com/liris/websocket-client

import websocket
import thread
import time

def on_message(ws, message):
    print message

def on_error(ws, error):
    print error

def on_close(ws):
    print "### closed ###"

def on_open(ws):
    def run(*args):
        for i in range(5):
            time.sleep(1)
            ws.send("Hello %d" % i)
        time.sleep(1)
        ws.close()
        print "thread terminating..."
    thread.start_new_thread(run, ())


if __name__ == "__main__":
    websocket.enableTrace(True)
    ws = websocket.WebSocketApp("ws://localhost:9090/ws",
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    ws.on_open = on_open
    ws.run_forever()

2-b. クライアント側その2→ws4py+tornado

こっちの方がOctoPrintとかと連動させるには良さそう
https://ws4py.readthedocs.org/en/latest/sources/clienttutorial/#tornado
client.py

from ws4py.client.tornadoclient import TornadoWebSocketClient
from tornado import ioloop

class MyClient(TornadoWebSocketClient):
     def opened(self):
         for i in range(0, 200, 25):
             self.send("*" * i)

     def received_message(self, m): 
         print m
         if len(m) == 175:
             self.close(reason='Bye bye')

     def closed(self, code, reason=None):
         ioloop.IOLoop.instance().stop()

ws = MyClient('ws://localhost:9090/ws', protocols=['http-only', 'chat'])
ws.connect()

ioloop.IOLoop.instance().start()

実行

$ python server.py
$ python client.py