Bläddra i källkod

Rename to PyWebIO

wangweimin 5 år sedan
förälder
incheckning
d4e55a3d01
43 ändrade filer med 19 tillägg och 598 borttagningar
  1. 0 54
      chat_room.py
  2. 0 46
      doc/log.md
  3. 0 124
      doc/spec.md
  4. 0 78
      doc/构想.md
  5. 0 17
      doc/表单交互.md
  6. 0 0
      pywebio/__init__.py
  7. 0 0
      pywebio/framework.py
  8. 5 0
      pywebio/html/bs.html
  9. 0 0
      pywebio/html/codemirror/active-line.js
  10. 0 0
      pywebio/html/codemirror/darcula.css
  11. 0 0
      pywebio/html/codemirror/loadmode.js
  12. 0 0
      pywebio/html/codemirror/matchbrackets.js
  13. 0 0
      pywebio/html/codemirror/material-ocean.css
  14. 0 0
      pywebio/html/codemirror/nginx.js
  15. 0 0
      pywebio/html/codemirror/python.js
  16. 0 0
      pywebio/html/css/codemirror.css
  17. 0 0
      pywebio/html/css/mditor.css
  18. 0 0
      pywebio/html/css/mditor.css.map
  19. 0 0
      pywebio/html/css/mditor.min.css
  20. 0 0
      pywebio/html/css/mditor.min.css.map
  21. 0 0
      pywebio/html/font/674f50d287a8c48dc19ba404d20fe713.eot
  22. 0 0
      pywebio/html/font/912ec66d7572ff821749319396470bde.svg
  23. 0 0
      pywebio/html/font/a48ac41620cd818c5020d0f4302489ff.ttf
  24. 0 0
      pywebio/html/font/af7ae505a9eed503f8b8e6982036873e.woff2
  25. 0 0
      pywebio/html/font/b06871f281fee6b241d60582ae9369b9.ttf
  26. 0 0
      pywebio/html/font/fee66e712a8a08eef5805a46892932ad.woff
  27. 1 1
      pywebio/html/index.html
  28. 0 0
      pywebio/html/js/FileSaver.min.js
  29. 0 0
      pywebio/html/js/app.js
  30. 0 0
      pywebio/html/js/codemirror.js
  31. 3 3
      pywebio/html/js/form.js
  32. 0 0
      pywebio/html/js/mditor.min.js
  33. 0 0
      pywebio/html/js/mustache.min.js
  34. 0 0
      pywebio/input_ctrl.py
  35. 0 0
      pywebio/interact.py
  36. 2 2
      pywebio/ioloop.py
  37. 0 0
      pywebio/output.py
  38. 0 0
      pywebio/platform/__init__.py
  39. 3 3
      pywebio/platform/tornado.py
  40. 5 5
      setup.py
  41. 0 82
      test.py
  42. 0 28
      ws-clent.py
  43. 0 155
      ws-server.py

+ 0 - 54
chat_room.py

@@ -1,54 +0,0 @@
-from tornado import gen
-from tornado.ioloop import IOLoop
-from tornado import websocket
-import json
-
-from wsrepl.ioloop import start_ioloop
-from wsrepl.interact import *
-from tornado.gen import sleep
-
-import asyncio
-
-chat_msgs = []  # 聊天记录 (name, msg)
-
-
-async def refresh_msg(my_name):
-    last_idx = len(chat_msgs)
-    while True:
-        await asyncio.sleep(0.5)
-        for m in chat_msgs[last_idx:]:
-            if m[0] != my_name:  # 仅刷新其他人的新信息
-                text_print('%s:%s' % m)
-        last_idx = len(chat_msgs)
-
-
-# 业务逻辑 协程
-@asyncio.coroutine
-def main():
-    """
-    有返回值的交互函数需要yield from
-    :return:
-    """
-    set_title("Chat Room")
-    text_print("欢迎来到聊天室,你可以和当前所有在线的人聊天")
-    nickname = yield from input("请输入你的昵称", required=True)
-
-    chat_msgs.append(('*系统*', '%s加入房间' % nickname))
-    text_print("*系统*: %s加入房间" % nickname)
-    run_async(refresh_msg(nickname))
-
-    while True:
-        data = yield from input_group('输入消息', [
-            input('', name='msg'),
-            actions('', name='cmd', buttons=['发送', '退出'])
-        ])
-        if data['cmd'] == '退出':
-            break
-
-        text_print('%s:%s' % (nickname, data['msg']))
-        chat_msgs.append((nickname, data['msg']))
-
-    text_print("你已经退出聊天室")
-
-
-start_ioloop(main)

+ 0 - 46
doc/log.md

@@ -1,46 +0,0 @@
-2/7
-应该不需要msg_id,ws连接是有状态的,不需要msg_id来标示状态。当在多个状态之间切换时,前后端周知,可以同步切换状态。
-
-每个ws连接维护一个coro栈,栈顶为当前活跃coro,来消息后激活该coro;前端维护输入栈,栈顶为当前活跃表单,表单控制消息作用于栈顶表单。
-触发上区的一些事件回调时,产生新的coro,压栈;前端当前表单未提交成功又来新表单时,当前表单压栈,显示新表单
-每个ws连接维护一个回调列表。当在上区输出带有回调的UI元素时,保存回调;当上区清屏时,同时清空回调列表,回调列表相保存锚点信息,clear_before(pos) clear_after
-
-设计哲学:下区,阻塞等待回应区,同步交互,栈式结构;上区,瀑布流UI区,通过回调函数交互。
-
-2/8
-需要 msg_id,  或者说是 coro_id/thread_id
-每个ws连接维护一个coros字典,每次根据消息的coro_id判断进入哪一个coro;前端维护form字典: coro_id -> form_handler栈,根据指令coro_id判断作用于哪一个表单,并将其置顶。
-触发上区的一些事件回调时,产生新的coro;前端当前表单未提交成功又来新表单时,当前表单隐藏,显示新表单
-
-
-
-
-2/9
-NOTE: 
-含有yield的函数一定是生成器,不管会不会执行到 (比如在分支里)
-
-coro.send 内部可能还会存在 激活协程的调用,要禁止嵌套创建协程Task或者将Global改成栈式存储
-
-
-2/10
-当前问题:
-    对于tornado coro的支持不是很友好:连续 yield tornado coro时,无法在yield间隙调度到其他coro执行 [todo]
-    使用tornado Future的callback应该可以解决
-    
-对于yield input()和 yield input_group([input(), input()])语法的实现:
-    input()返回一个msg对象,task接收到后,处理 发送
-    比上述更好地实现 [ok]
-    
-    
-2/11
-用户输入函数中,对结果无影响的非法参数可以以warnning而不是异常的方式提示用户 [ok]
-
-
-
-2/12 
-发现tornado对于一个ws连接,若on——message不结束,无法进行下一个
-
-2/14
-代码编辑
-https://codemirror.net/
-textarea(code_style)

+ 0 - 124
doc/spec.md

@@ -1,124 +0,0 @@
-服务器->客户端
-{
-    command: ""
-    coro_id: ""
-   	spec: {}
-}
-命令名:
-    参数1:
-    参数2:
-    
-
-
-## 命令
-
-继承关系:
-全局:
-    <button>
-    输入类:
-        <input>
-        <select>
-        <textarea>
-
-全局参数 (带*号的必须, ~可选, ^为非html属性)
-    *^label
-    ^help_text
-    ^invalid_feedback
-    ^valid_feedback
-    输入类全局参数
-
-
-<input> 类命令  // 全局 <input> 参数  ref: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input
-    *name
-    *type
-    readonly/disabled:bool 禁用的控件的值在提交表单时也不会被提交
-    required:
-    value:
-    placeholder: placeholder 属性是提示用户内容的输入格式。某些情况下 placeholder 属性对用户不可见, 所以当没有它时也需要保证form能被理解。
-    ^inline  // type==checkbox,radio
-    ^options // type==checkbox,radio , 字典列表 {*value:, *label:, selected,disabled }
-
-
-
-type=<select>
-ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/select
-    multiple
-
-type=<textarea>
-ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/textarea
-
-<button>
-ref https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button
-
-type=actions
-    label
-    name
-    buttons 字典列表 {*value:, *label:, disabled}
-如果表单最后一个输入元素为actions组件,则隐藏默认的"提交"/"重置"按钮
-
-
-input_group:
-    label: # todo change to label
-    inputs: [ <input>, ] // 若只有一个input 则可以忽略其label属性
-
-
-
-控制类指令
-update_input:
-    target_name: input主键name
-    ~target_value:str 用于checkbox, radio, button 过滤input 
-    attributes: {
-        valid_status: bool 输入值的有效性,通过/不通过
-        value:
-        placeholder:
-        ...  // 不支持 on_focus on_blur inline label
-    }
-    
-
-destroy_form:
-    无spec
-
-output:
-    type: text
-    content: ''
-    ----
-    type: buttons
-    callback_id:  
-    buttons:[ {value:, label:, },...]
-    ----
-    type: file
-    name: 
-    content: 
-    
-
-output_ctl:
-    title
-    
-
-
-客户端->服务器
-{
-    event: ""
-    coro_id: ""
-   	data: {}
-}
-事件名:
-    数据项1:
-    数据项2:
-
-input_event
-    event_name: blur, click
-    name:
-    value:
-
-event: callback
-coro_id: callback_id
-data: value
-    
-
-checkbox_radio 不产生blur事件
-
-from_submit:
-    
-
-

+ 0 - 78
doc/构想.md

@@ -1,78 +0,0 @@
-## 
-消息以json通讯
-
-{
-    command: ""
-    spec: {
-        msg_id: 输入类命令会有
-    }
-}
-
-## command:
-
-|      |      |      |
-| ---- | ---- | ---- |
-|      |      |      |
-|      |      |      |
-|      |      |      |
-
-### 输入类
-
-### text_input
-msg_id: 输入类命令会有
-prompt: 
-
-
-### 输出类
-### ctrl 
-title
-
-
-
-### text_print
-content
-
-
-
-
-## 回应:
-{ msg_id: , data: }
-
-
-## 后端
-
-
-
-
-## idea
-可以设置 static_root (所有session共享)
-
-
-
-
-
-ws 通讯
-```python
-import tornado.websocket
-
-class EchoWebSocket(tornado.websocket.WebSocketHandler):
-    def open(self):
-        print("WebSocket opened")
-
-    def on_message(self, message):
-        self.write_message(u"You said: " + message)
-
-    def on_close(self):
-        print("WebSocket closed")
-```
-
-        
-```js
-var ws = new WebSocket("ws://localhost:8080/test");
-ws.onopen = function() {
-   ws.send("Hello, world");
-};
-ws.onmessage = function (evt) {
-   console.log(">>>", evt.data);
-};
-```

+ 0 - 17
doc/表单交互.md

@@ -1,17 +0,0 @@
->>>
-input_group:
-    text_input: {name:age }
-    select: {name:gender}
-    
-<<<
-onblur:
-    name:
-    value:
-    
->>>
-
-
-
-<<< 
-callback:
-    

+ 0 - 0
wsrepl/__init__.py → pywebio/__init__.py


+ 0 - 0
wsrepl/framework.py → pywebio/framework.py


+ 5 - 0
wsrepl/html/bs.html → pywebio/html/bs.html

@@ -253,9 +253,14 @@
 <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"
         integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
         crossorigin="anonymous"></script>
+<script src="https://cdn.jsdelivr.net/npm/bs-custom-file-input/dist/bs-custom-file-input.js"></script>
 
 <script src="js/codemirror.js"></script>
 <script>
+    $(document).ready(function () {
+        bsCustomFileInput.init()
+    })
+
     var editor = CodeMirror.fromTextArea(document.getElementById("exampleFormControlTextarea2"), {
         lineNumbers: true,
         mode: "text/html",

+ 0 - 0
wsrepl/html/codemirror/active-line.js → pywebio/html/codemirror/active-line.js


+ 0 - 0
wsrepl/html/codemirror/darcula.css → pywebio/html/codemirror/darcula.css


+ 0 - 0
wsrepl/html/codemirror/loadmode.js → pywebio/html/codemirror/loadmode.js


+ 0 - 0
wsrepl/html/codemirror/matchbrackets.js → pywebio/html/codemirror/matchbrackets.js


+ 0 - 0
wsrepl/html/codemirror/material-ocean.css → pywebio/html/codemirror/material-ocean.css


+ 0 - 0
wsrepl/html/codemirror/nginx.js → pywebio/html/codemirror/nginx.js


+ 0 - 0
wsrepl/html/codemirror/python.js → pywebio/html/codemirror/python.js


+ 0 - 0
wsrepl/html/css/codemirror.css → pywebio/html/css/codemirror.css


+ 0 - 0
wsrepl/html/css/mditor.css → pywebio/html/css/mditor.css


+ 0 - 0
wsrepl/html/css/mditor.css.map → pywebio/html/css/mditor.css.map


+ 0 - 0
wsrepl/html/css/mditor.min.css → pywebio/html/css/mditor.min.css


+ 0 - 0
wsrepl/html/css/mditor.min.css.map → pywebio/html/css/mditor.min.css.map


+ 0 - 0
wsrepl/html/font/674f50d287a8c48dc19ba404d20fe713.eot → pywebio/html/font/674f50d287a8c48dc19ba404d20fe713.eot


+ 0 - 0
wsrepl/html/font/912ec66d7572ff821749319396470bde.svg → pywebio/html/font/912ec66d7572ff821749319396470bde.svg


+ 0 - 0
wsrepl/html/font/a48ac41620cd818c5020d0f4302489ff.ttf → pywebio/html/font/a48ac41620cd818c5020d0f4302489ff.ttf


+ 0 - 0
wsrepl/html/font/af7ae505a9eed503f8b8e6982036873e.woff2 → pywebio/html/font/af7ae505a9eed503f8b8e6982036873e.woff2


+ 0 - 0
wsrepl/html/font/b06871f281fee6b241d60582ae9369b9.ttf → pywebio/html/font/b06871f281fee6b241d60582ae9369b9.ttf


+ 0 - 0
wsrepl/html/font/fee66e712a8a08eef5805a46892932ad.woff → pywebio/html/font/fee66e712a8a08eef5805a46892932ad.woff


+ 1 - 1
wsrepl/html/index.html → pywebio/html/index.html

@@ -107,7 +107,7 @@
 
     var ws = new WebSocket(get_ws_addr());
 
-    var ctrl = new WSREPL.WSREPLController(ws, md_body, $('#input-container'));
+    var ctrl = new WebIO.WebIOController(ws, md_body, $('#input-container'));
 
     var old_send = ws.send;
     ws.send = function (d) {

+ 0 - 0
wsrepl/html/js/FileSaver.min.js → pywebio/html/js/FileSaver.min.js


+ 0 - 0
wsrepl/html/js/app.js → pywebio/html/js/app.js


+ 0 - 0
wsrepl/html/js/codemirror.js → pywebio/html/js/codemirror.js


+ 3 - 3
wsrepl/html/js/form.js → pywebio/html/js/form.js

@@ -1,7 +1,7 @@
 (function (global, factory) {
     typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
         typeof define === 'function' && define.amd ? define(factory) :
-            (global = global || self, global.WSREPL = factory());
+            (global = global || self, global.WebIO = factory());
 }(this, (function () {
     'use strict';
 
@@ -748,7 +748,7 @@
     };
 
 
-    function WSREPLController(ws_client, output_container_elem, input_container_elem) {
+    function WebIOController(ws_client, output_container_elem, input_container_elem) {
         this.output_ctrl = new OutputController(ws_client, output_container_elem);
         this.input_ctrl = new FormsController(ws_client, input_container_elem);
 
@@ -765,7 +765,7 @@
     }
 
     return {
-        'WSREPLController': WSREPLController
+        'WebIOController': WebIOController
     }
 
 })));

+ 0 - 0
wsrepl/html/js/mditor.min.js → pywebio/html/js/mditor.min.js


+ 0 - 0
wsrepl/html/js/mustache.min.js → pywebio/html/js/mustache.min.js


+ 0 - 0
wsrepl/input_ctrl.py → pywebio/input_ctrl.py


+ 0 - 0
wsrepl/interact.py → pywebio/interact.py


+ 2 - 2
wsrepl/ioloop.py → pywebio/ioloop.py

@@ -4,8 +4,8 @@ import tornado.websocket
 from tornado.log import gen_log
 from tornado.web import StaticFileHandler
 
-from wsrepl.platform.tornado import ws_handler, STATIC_PATH
-from wsrepl import project_dir
+from .platform.tornado import ws_handler, STATIC_PATH
+from . import project_dir
 
 
 def start_ioloop(coro_func, port=8080, debug=True, tornado_app_args=None):

+ 0 - 0
wsrepl/output.py → pywebio/output.py


+ 0 - 0
wsrepl/platform/__init__.py → pywebio/platform/__init__.py


+ 3 - 3
wsrepl/platform/tornado.py → pywebio/platform/tornado.py

@@ -6,11 +6,11 @@ import tornado.websocket
 from tornado.gen import coroutine
 from tornado.log import gen_log
 
-from wsrepl.framework import Task
+from ..framework import Task
 
-from wsrepl import project_dir
+from .. import project_dir
 import sys, traceback
-from wsrepl.output import put_markdown
+from ..output import put_markdown
 
 STATIC_PATH = '%s/html' % project_dir
 

+ 5 - 5
setup.py

@@ -1,12 +1,12 @@
 from setuptools import setup, find_packages
 
 setup(
-    name='webio',
-    version='1.0.0',
-    description='Spider utils lib',
-    url='',
+    name='PyWebIO',
+    version='0.0.2',
+    description=u'Make your python interactive script be a web service.',
+    url='https://github.com/wang0618/pywebio',
     author='WangWeimin',
-    author_email='wangweimin@buaa.edu.com',
+    author_email='wang0.618@qq.com',
     license='MIT',
     packages=find_packages(),
     package_data={

+ 0 - 82
test.py

@@ -1,82 +0,0 @@
-from tornado import gen
-from tornado.ioloop import IOLoop
-from tornado import websocket
-import json
-
-from wsrepl.ioloop import start_ioloop
-from wsrepl.interact import *
-from wsrepl.output import *
-from tornado.gen import sleep
-
-
-# 业务逻辑 协程
-async def say_hello():
-    """
-    有返回值的交互函数需要yield from
-    :return:
-    """
-    set_title("This is title")
-    # 向用户输出文字
-    text_print("Welcome!!!")
-
-    put_table([[str(i)] * 4 for i in range(5)])
-
-    res = await textarea('Text area', value='value',codemirror={
-        'mode': "python",
-        'theme': 'darcula',  # 使用monokai模版 ,darcula:IDEA,
-    })
-    text_print(res)
-
-    res = await actions('Action button', [
-        {'value': '1', 'label': 'One', 'disabled': False},
-        {'value': '2', 'label': 'Two', 'disabled': False},
-        {'value': '3', 'label': 'Three', 'disabled': True},
-    ])
-    text_print('Your input:%s' % res)
-
-    res = await select('This is select input', [
-        {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
-        {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
-        {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=SELECT, multiple=False)
-    text_print('Your input:%s' % res)
-
-    res = await select('This is multiple select input', [
-        {'value': 1, 'label': 'one', 'selected': True, 'disabled': False},
-        {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
-        {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=SELECT, multiple=True)
-    text_print('Your input:%s' % res)
-
-    res = await select('This is RADIO input', [
-        {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
-        {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
-        {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=RADIO)
-    text_print('Your input:%s' % res)
-
-    res = await select('This is CHECKBOX input', [
-        {'value': 1, 'label': 'one', 'selected': False, 'disabled': False},
-        {'value': 2, 'label': 'two', 'selected': True, 'disabled': False},
-        {'value': 2, 'label': 'three disabled', 'selected': False, 'disabled': True},
-    ], type=CHECKBOX)
-
-    text_print('Your input:%s' % res)
-
-    res = await input('This is single input')
-    text_print('Your input:%s' % res)
-
-    res = await input('This is another single input')
-    text_print('Your input:%s' % res)
-
-    res = await input_group('Group input', [
-        input('Input 1', name='one'),
-        input('Input 2', name='two'),
-        select('Input 2', options=['A', 'B', 'C'], type=CHECKBOX, name='three')
-    ])
-
-    text_print('Your input:')
-    json_print(res)
-
-
-start_ioloop(say_hello)

+ 0 - 28
ws-clent.py

@@ -1,28 +0,0 @@
-from tornado import gen
-from tornado.ioloop import IOLoop
-from tornado import websocket
-import json
-
-
-@gen.coroutine
-def run():
-    url = 'ws://localhost:8080/test'
-    conn = yield websocket.websocket_connect(url)
-    print('connected!')
-    while True:
-        msg = yield conn.read_message()
-        if msg is None:
-            print('Connect closed')
-            return
-
-        data = json.loads(msg)
-        cmd = data['command']
-        if cmd == 'text_print':
-            print(data['spec']['content'])
-        elif cmd == 'text_input':
-            input_text = input(data['spec']['prompt'])
-            resp = dict(msg_id=data['spec']['msg_id'], data=input_text)
-            yield conn.write_message(json.dumps(resp))
-
-
-IOLoop.current().run_sync(run)

+ 0 - 155
ws-server.py

@@ -1,155 +0,0 @@
-import tornado.websocket
-import time, json
-from collections import defaultdict
-
-
-class Future:
-    def __init__(self):
-        self.result = None
-        self._callbacks = []
-
-    def add_done_callback(self, fn):
-        self._callbacks.append(fn)
-
-    def set_result(self, result):
-        self.result = result
-        for fn in self._callbacks:
-            fn(self)
-
-    def __iter__(self):
-        yield self
-        return self.result
-
-
-class Task:
-    def __init__(self, coro):
-        self.coro = coro
-        f = Future()
-        f.set_result(None)
-        self.step(f)
-
-        self.result = None  # 协程的返回值
-        self.on_task_finish = None  # 协程完毕的回调函数
-
-    def step(self, future):
-        try:
-            # send会进入到coro执行, 即fetch, 直到下次yield
-            # next_future 为yield返回的对象
-            next_future = self.coro.send(future.result)
-            next_future.add_done_callback(self.step)
-        except StopIteration as e:
-            if len(e.args) == 1:
-                self.result = e.args[0]
-            if self.on_task_finish:
-                self.on_task_finish(self.result)
-            return
-
-
-# 非阻塞协程工具库
-def text_input_coro(prompt):
-    """
-    yield出来的为Future对象,每次yield前注册event,event的callback为给该Future对象set-result
-    yield的返回值为改Future对象的值
-    :return:
-    """
-    # 注册event
-    msg_id = Msg.gen_msg_id()
-    msg = dict(command="text_input", data=dict(prompt=prompt, msg_id=msg_id))
-    f = Future()
-    Msg.add_callback(msg_id, f.set_result)
-
-    Global.active_ws.write_message(json.dumps(msg))
-
-    input_text = yield from f
-    Msg.unregister_msg(msg_id)
-
-    return input_text
-
-
-def text_print(text, *, ws=None):
-    msg = dict(command="text_print", data=text)
-    (ws or Global.active_ws).write_message(json.dumps(msg))
-
-
-# 业务逻辑 协程
-def my_coro():
-    text_print("Welcome to ws-repl")
-    name = yield from text_input_coro('input your name:')
-    text_print("go go go %s!" % name)
-
-    age = yield from text_input_coro('input your age:')
-    text_print("So young!!")
-
-
-class Msg:
-    mid2callback = defaultdict(list)
-
-    @staticmethod
-    def gen_msg_id():
-        mid = '%s-%s' % (Global.active_ws.sid, int(time.time()))
-        return mid
-
-    @classmethod
-    def add_callback(cls, msg_id, callback):
-        cls.mid2callback[msg_id].append(callback)
-
-    @classmethod
-    def get_callbacks(cls, msg_id):
-        return cls.mid2callback[msg_id]
-
-    @classmethod
-    def get_callbacks(cls, msg_id):
-        return cls.mid2callback[msg_id]
-
-    @classmethod
-    def unregister_msg(cls, msg_id):
-        del cls.mid2callback[msg_id]
-
-
-class Global:
-    active_ws: "EchoWebSocket"
-
-
-class EchoWebSocket(tornado.websocket.WebSocketHandler):
-    def check_origin(self, origin):
-        return True
-
-    def get_compression_options(self):
-        # Non-None enables compression with default options.
-        return {}
-
-    def open(self):
-        print("WebSocket opened")
-        self.set_nodelay(True)
-        ############
-        self.sid = int(time.time())
-        self.coro = my_coro()
-
-        Global.active_ws = self
-        self.task = Task(self.coro)
-        self.task.on_task_finish = self.on_task_finish
-
-    def on_task_finish(self, result):
-        text_print('Task finish, return: %s\nBye, bye!!' % result, ws=self)
-        self.close()
-
-    def on_message(self, message):
-        print('on_message', message)
-        # self.write_message(u"You said: " + message)
-        # { msg_id: , data: }
-        data = json.loads(message)
-
-        Global.active_ws = self
-        callbacks = Msg.get_callbacks(data['msg_id'])
-        for c in callbacks:
-            c(data['data'])
-
-    def on_close(self):
-        print("WebSocket closed")
-
-
-handlers = [(r"/test", EchoWebSocket)]
-app = tornado.web.Application(handlers=handlers, debug=True)
-http_server = tornado.httpserver.HTTPServer(app)
-http_server.listen(8080)
-tornado.ioloop.IOLoop.instance().start()