瀏覽代碼

feat: start_server() support multiple target

wangweimin 5 年之前
父節點
當前提交
e2ae850fc3

+ 1 - 1
pywebio/html/index.html

@@ -94,7 +94,7 @@
         outputAnimation: !urlparams.get('_pywebio_disable_animate'),
         httpPullInterval: parseInt(urlparams.get('_pywebio_http_pull_interval') || 1000)
     };
-    WebIO.startWebIOClient($('#markdown-body'), $('#input-container'), config);
+    WebIO.startWebIOClient($('#markdown-body'), $('#input-container'), urlparams.get('app') || 'index', config);
 
 </script>
 

File diff suppressed because it is too large
+ 0 - 0
pywebio/html/js/pywebio.min.js


File diff suppressed because it is too large
+ 0 - 0
pywebio/html/js/pywebio.min.js.map


+ 22 - 17
pywebio/platform/aiohttp.py

@@ -9,9 +9,10 @@ from urllib.parse import urlparse
 from aiohttp import web
 
 from .tornado import open_webbrowser_on_server_started
+from .utils import make_applications
 from ..session import CoroutineBasedSession, ThreadBasedSession, register_session_implement_for_target, Session
 from ..session.base import get_session_info_from_headers
-from ..utils import get_free_port, STATIC_PATH
+from ..utils import get_free_port, STATIC_PATH, iscoroutinefunction, isgeneratorfunction
 
 logger = logging.getLogger(__name__)
 
@@ -36,11 +37,10 @@ def _is_same_site(origin, host):
     return origin == host
 
 
-def _webio_handler(target, session_cls, websocket_settings, check_origin_func=_is_same_site):
+def _webio_handler(applications, websocket_settings, check_origin_func=_is_same_site):
     """获取用于Tornado进行整合的RequestHandle类
 
-    :param target: 任务函数
-    :param session_cls: 会话实现类
+    :param dict applications: 任务名->任务函数 的映射
     :param callable check_origin_func: check_origin_func(origin, handler) -> bool
     :return: Tornado RequestHandle类
     """
@@ -71,16 +71,18 @@ def _webio_handler(target, session_cls, websocket_settings, check_origin_func=_i
         session_info['user_ip'] = request.remote
         session_info['request'] = request
         session_info['backend'] = 'aiohttp'
-        if session_cls is CoroutineBasedSession:
-            session = CoroutineBasedSession(target, session_info=session_info,
+
+        app_name = request.query.getone('app', 'index')
+        application = applications.get(app_name) or applications['index']
+
+        if iscoroutinefunction(application) or isgeneratorfunction(application):
+            session = CoroutineBasedSession(application, session_info=session_info,
                                             on_task_command=send_msg_to_client,
                                             on_session_close=close_from_session)
-        elif session_cls is ThreadBasedSession:
-            session = ThreadBasedSession(target, session_info=session_info,
+        else:
+            session = ThreadBasedSession(application, session_info=session_info,
                                          on_task_command=send_msg_to_client,
                                          on_session_close=close_from_session, loop=ioloop)
-        else:
-            raise RuntimeError("Don't support session type:%s" % session_cls)
 
         async for msg in ws:
             if msg.type == web.WSMsgType.text:
@@ -99,11 +101,11 @@ def _webio_handler(target, session_cls, websocket_settings, check_origin_func=_i
     return wshandle
 
 
-def webio_handler(target, allowed_origins=None, check_origin=None, websocket_settings=None):
+def webio_handler(applications, allowed_origins=None, check_origin=None, websocket_settings=None):
     """获取在aiohttp中运行PyWebIO任务函数的 `Request Handle <https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-handler>`_ 协程。
     Request Handle基于WebSocket协议与浏览器进行通讯。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
         来源包含协议和域名和端口部分,允许使用 Unix shell 风格的匹配模式:
 
@@ -118,7 +120,9 @@ def webio_handler(target, allowed_origins=None, check_origin=None, websocket_set
     :param dict websocket_settings: 创建 aiohttp WebSocketResponse 时使用的参数。见 https://docs.aiohttp.org/en/stable/web_reference.html#websocketresponse
     :return: aiohttp Request Handler
     """
-    session_cls = register_session_implement_for_target(target)
+    applications = make_applications(applications)
+    for target in applications.values():
+        register_session_implement_for_target(target)
 
     websocket_settings = websocket_settings or {}
 
@@ -127,7 +131,8 @@ def webio_handler(target, allowed_origins=None, check_origin=None, websocket_set
     else:
         check_origin_func = lambda origin, handler: _is_same_site(origin, handler) or check_origin(origin)
 
-    return _webio_handler(target=target, session_cls=session_cls, check_origin_func=check_origin_func,
+    return _webio_handler(applications=applications,
+                          check_origin_func=check_origin_func,
                           websocket_settings=websocket_settings)
 
 
@@ -144,14 +149,14 @@ def static_routes(static_path):
     return routes
 
 
-def start_server(target, port=0, host='', debug=False,
+def start_server(applications, port=0, host='', debug=False,
                  allowed_origins=None, check_origin=None,
                  auto_open_webbrowser=False,
                  websocket_settings=None,
                  **aiohttp_settings):
     """启动一个 aiohttp server 将 ``target`` 任务函数作为Web服务提供。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
     :param int port: server bind port. set ``0`` to find a free port number to use
     :param str host: server bind host. ``host`` may be either an IP address or hostname.  If it's a hostname,
@@ -181,7 +186,7 @@ def start_server(target, port=0, host='', debug=False,
     if port == 0:
         port = get_free_port()
 
-    handler = webio_handler(target, allowed_origins=allowed_origins, check_origin=check_origin,
+    handler = webio_handler(applications, allowed_origins=allowed_origins, check_origin=check_origin,
                             websocket_settings=websocket_settings)
 
     app = web.Application(**aiohttp_settings)

+ 10 - 8
pywebio/platform/django.py

@@ -8,6 +8,7 @@ from django.http import HttpResponse, HttpRequest
 from .httpbased import HttpContext, HttpHandler, run_event_loop
 from ..session import register_session_implement_for_target
 from ..utils import STATIC_PATH, iscoroutinefunction, isgeneratorfunction, get_free_port
+from .utils import make_applications
 
 logger = logging.getLogger(__name__)
 
@@ -71,14 +72,14 @@ class DjangoHttpContext(HttpContext):
         return self.request.META.get('REMOTE_ADDR')
 
 
-def webio_view(target,
+def webio_view(applications,
                session_expire_seconds=None,
                session_cleanup_interval=None,
                allowed_origins=None, check_origin=None):
     """获取在django中运行PyWebIO任务的视图函数。
     基于http请求与前端进行通讯
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param int session_expire_seconds: 会话不活跃过期时间。
     :param int session_cleanup_interval: 会话清理间隔。
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
@@ -94,8 +95,7 @@ def webio_view(target,
         返回 ``True/False`` 。若设置了 ``check_origin`` , ``allowed_origins`` 参数将被忽略
     :return: Django视图函数
     """
-    session_cls = register_session_implement_for_target(target)
-    handler = HttpHandler(target=target, session_cls=session_cls,
+    handler = HttpHandler(applications=applications,
                           session_expire_seconds=session_expire_seconds,
                           session_cleanup_interval=session_cleanup_interval,
                           allowed_origins=allowed_origins, check_origin=check_origin)
@@ -113,7 +113,7 @@ def webio_view(target,
 urlpatterns = []
 
 
-def start_server(target, port=8080, host='localhost',
+def start_server(applications, port=8080, host='localhost',
                  allowed_origins=None, check_origin=None,
                  disable_asyncio=False,
                  session_cleanup_interval=None,
@@ -121,7 +121,7 @@ def start_server(target, port=8080, host='localhost',
                  debug=False, **django_options):
     """启动一个 Django server 将 ``target`` 任务函数作为Web服务提供。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param int port: server bind port. set ``0`` to find a free port number to use
     :param str host: server bind host. ``host`` may be either an IP address or hostname.  If it's a hostname,
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
@@ -189,7 +189,7 @@ def start_server(target, port=8080, host='localhost',
     settings.configure(**django_options)
 
     webio_view_func = webio_view(
-        target,
+        applications=applications,
         session_expire_seconds=session_expire_seconds,
         session_cleanup_interval=session_cleanup_interval,
         allowed_origins=allowed_origins,
@@ -204,7 +204,9 @@ def start_server(target, port=8080, host='localhost',
 
     get_wsgi_application()
 
-    if not disable_asyncio and (iscoroutinefunction(target) or isgeneratorfunction(target)):
+    has_coro_target = any(iscoroutinefunction(target) or isgeneratorfunction(target) for
+                          target in make_applications(applications).values())
+    if not disable_asyncio and has_coro_target:
         threading.Thread(target=run_event_loop, daemon=True).start()
 
     call_command('runserver', '%s:%d' % (host, port))

+ 11 - 8
pywebio/platform/flask.py

@@ -13,6 +13,7 @@ from flask import Flask, request, send_from_directory, Response
 from .httpbased import HttpContext, HttpHandler, run_event_loop
 from ..session import register_session_implement_for_target
 from ..utils import STATIC_PATH, iscoroutinefunction, isgeneratorfunction
+from .utils import make_applications
 
 logger = logging.getLogger(__name__)
 
@@ -76,13 +77,13 @@ class FlaskHttpContext(HttpContext):
         return request.remote_addr
 
 
-def webio_view(target,
+def webio_view(applications,
                session_expire_seconds=None,
                session_cleanup_interval=None,
                allowed_origins=None, check_origin=None):
     """获取在Flask中运行PyWebIO任务的视图函数。基于http请求与前端进行通讯
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param int session_expire_seconds: 会话不活跃过期时间。
     :param int session_cleanup_interval: 会话清理间隔。
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
@@ -98,8 +99,8 @@ def webio_view(target,
         返回 ``True/False`` 。若设置了 ``check_origin`` , ``allowed_origins`` 参数将被忽略
     :return: Flask视图函数
     """
-    session_cls = register_session_implement_for_target(target)
-    handler = HttpHandler(target=target, session_cls=session_cls,
+
+    handler = HttpHandler(applications=applications,
                           session_expire_seconds=session_expire_seconds,
                           session_cleanup_interval=session_cleanup_interval,
                           allowed_origins=allowed_origins, check_origin=check_origin)
@@ -112,7 +113,7 @@ def webio_view(target,
     return view_func
 
 
-def start_server(target, port=8080, host='localhost',
+def start_server(applications, port=8080, host='localhost',
                  allowed_origins=None, check_origin=None,
                  disable_asyncio=False,
                  session_cleanup_interval=None,
@@ -120,7 +121,7 @@ def start_server(target, port=8080, host='localhost',
                  debug=False, **flask_options):
     """启动一个 Flask server 将 ``target`` 任务函数作为Web服务提供。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
     :param int port: server bind port. set ``0`` to find a free port number to use
     :param str host: server bind host. ``host`` may be either an IP address or hostname.  If it's a hostname,
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
@@ -149,7 +150,7 @@ def start_server(target, port=8080, host='localhost',
 
     app = Flask(__name__)
     app.add_url_rule('/io', 'webio_view', webio_view(
-        target,
+        applications=applications,
         session_expire_seconds=session_expire_seconds,
         session_cleanup_interval=session_cleanup_interval,
         allowed_origins=allowed_origins,
@@ -161,7 +162,9 @@ def start_server(target, port=8080, host='localhost',
     def serve_static_file(static_file='index.html'):
         return send_from_directory(STATIC_PATH, static_file)
 
-    if not disable_asyncio and (iscoroutinefunction(target) or isgeneratorfunction(target)):
+    has_coro_target = any(iscoroutinefunction(target) or isgeneratorfunction(target) for
+                          target in make_applications(applications).values())
+    if not disable_asyncio and has_coro_target:
         threading.Thread(target=run_event_loop, daemon=True).start()
 
     if not debug:

+ 21 - 9
pywebio/platform/httpbased.py

@@ -20,9 +20,11 @@ import threading
 from typing import Dict
 
 import time
-from ..session import CoroutineBasedSession, Session, register_session_implement_for_target
+
+from .utils import make_applications
+from ..session import CoroutineBasedSession, Session, ThreadBasedSession, register_session_implement_for_target
 from ..session.base import get_session_info_from_headers
-from ..utils import random_str, LRUDict
+from ..utils import random_str, LRUDict, isgeneratorfunction, iscoroutinefunction
 
 
 class HttpContext:
@@ -46,7 +48,7 @@ class HttpContext:
         """返回当前请求的URL参数"""
         pass
 
-    def request_json(self):
+    def request_json(self) -> dict:
         """返回当前请求的json反序列化后的内容,若请求数据不为json格式,返回None"""
         pass
 
@@ -161,7 +163,15 @@ class HttpHandler:
             session_info['user_ip'] = context.get_client_ip()
             session_info['request'] = context.request_obj()
             session_info['backend'] = context.backend_name
-            webio_session = self.session_cls(self.target, session_info=session_info)
+
+            app_name = context.request_url_parameter('app', 'index')
+            application = self.applications.get(app_name) or self.applications['index']
+
+            if iscoroutinefunction(application) or isgeneratorfunction(application):
+                session_cls = CoroutineBasedSession
+            else:
+                session_cls = ThreadBasedSession
+            webio_session = session_cls(application, session_info=session_info)
             cls._webio_sessions[webio_session_id] = webio_session
         elif request_headers['webio-session-id'] not in cls._webio_sessions:  # WebIOSession deleted
             context.set_content([dict(command='close_session')], json_type=True)
@@ -173,7 +183,7 @@ class HttpHandler:
         if context.request_method() == 'POST':  # client push event
             if context.request_json() is not None:
                 webio_session.send_client_event(context.request_json())
-                time.sleep(cls.WAIT_MS_ON_POST / 1000.0)
+                time.sleep(cls.WAIT_MS_ON_POST / 1000.0)  # 等待session输出完毕
         elif context.request_method() == 'GET':  # client pull messages
             pass
 
@@ -190,13 +200,13 @@ class HttpHandler:
 
         return context.get_response()
 
-    def __init__(self, target, session_cls,
+    def __init__(self, applications,
                  session_expire_seconds=None,
                  session_cleanup_interval=None,
                  allowed_origins=None, check_origin=None):
         """获取用于与后端实现进行整合的view函数,基于http请求与前端进行通讯
 
-        :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现
+        :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表
         :param int session_expire_seconds: 会话不活跃过期时间。
         :param int session_cleanup_interval: 会话清理间隔。
         :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
@@ -213,12 +223,14 @@ class HttpHandler:
         """
         cls = type(self)
 
-        self.target = target
-        self.session_cls = session_cls
+        self.applications = make_applications(applications)
         self.check_origin = check_origin
         self.session_expire_seconds = session_expire_seconds or cls.DEFAULT_SESSION_EXPIRE_SECONDS
         self.session_cleanup_interval = session_cleanup_interval or cls.SESSIONS_CLEANUP_INTERVAL
 
+        for target in self.applications.values():
+            register_session_implement_for_target(target)
+
         if check_origin is None:
             self.check_origin = lambda origin: any(
                 fnmatch.fnmatch(origin, patten)

+ 28 - 18
pywebio/platform/tornado.py

@@ -17,7 +17,8 @@ from tornado.websocket import WebSocketHandler
 from ..session import CoroutineBasedSession, ThreadBasedSession, ScriptModeSession, \
     register_session_implement_for_target, Session
 from ..session.base import get_session_info_from_headers
-from ..utils import get_free_port, wait_host_port, STATIC_PATH
+from ..utils import get_free_port, wait_host_port, STATIC_PATH, iscoroutinefunction, isgeneratorfunction
+from .utils import make_applications
 
 logger = logging.getLogger(__name__)
 
@@ -51,11 +52,10 @@ def _is_same_site(origin, handler: WebSocketHandler):
     return origin == host
 
 
-def _webio_handler(target, session_cls, check_origin_func=_is_same_site):
+def _webio_handler(applications, check_origin_func=_is_same_site):
     """获取用于Tornado进行整合的RequestHandle类
 
-    :param target: 任务函数
-    :param session_cls: 会话实现类
+    :param dict applications: 任务名->任务函数 的字典
     :param callable check_origin_func: check_origin_func(origin, handler) -> bool
     :return: Tornado RequestHandle类
     """
@@ -83,17 +83,18 @@ def _webio_handler(target, session_cls, check_origin_func=_is_same_site):
             session_info['user_ip'] = self.request.remote_ip
             session_info['request'] = self.request
             session_info['backend'] = 'tornado'
-            if session_cls is CoroutineBasedSession:
-                self.session = CoroutineBasedSession(target, session_info=session_info,
+
+            app_name = self.get_query_argument('app', 'index')
+            application = applications.get(app_name) or applications['index']
+            if iscoroutinefunction(application) or isgeneratorfunction(application):
+                self.session = CoroutineBasedSession(application, session_info=session_info,
                                                      on_task_command=self.send_msg_to_client,
                                                      on_session_close=self.close_from_session)
-            elif session_cls is ThreadBasedSession:
-                self.session = ThreadBasedSession(target, session_info=session_info,
+            else:
+                self.session = ThreadBasedSession(application, session_info=session_info,
                                                   on_task_command=self.send_msg_to_client,
                                                   on_session_close=self.close_from_session,
                                                   loop=asyncio.get_event_loop())
-            else:
-                raise RuntimeError("Don't support session type:%s" % session_cls)
 
         def on_message(self, message):
             data = json.loads(message)
@@ -112,10 +113,10 @@ def _webio_handler(target, session_cls, check_origin_func=_is_same_site):
     return WSHandler
 
 
-def webio_handler(target, allowed_origins=None, check_origin=None):
+def webio_handler(applications, allowed_origins=None, check_origin=None):
     """获取在Tornado中运行PyWebIO任务的RequestHandle类。RequestHandle类基于WebSocket协议与浏览器进行通讯。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现。
+    :param callable/list/dict applications: PyWebIO应用
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
         来源包含协议和域名和端口部分,允许使用 Unix shell 风格的匹配模式:
 
@@ -129,14 +130,16 @@ def webio_handler(target, allowed_origins=None, check_origin=None):
         返回 ``True/False`` 。若设置了 ``check_origin`` , ``allowed_origins`` 参数将被忽略
     :return: Tornado RequestHandle类
     """
-    session_cls = register_session_implement_for_target(target)
+    applications = make_applications(applications)
+    for target in applications.values():
+        register_session_implement_for_target(target)
 
     if check_origin is None:
         check_origin_func = partial(_check_origin, allowed_origins=allowed_origins or [])
     else:
         check_origin_func = lambda origin, handler: _is_same_site(origin, handler) or check_origin(origin)
 
-    return _webio_handler(target=target, session_cls=session_cls, check_origin_func=check_origin_func)
+    return _webio_handler(applications=applications, check_origin_func=check_origin_func)
 
 
 async def open_webbrowser_on_server_started(host, port):
@@ -163,7 +166,7 @@ def _setup_server(webio_handler, port=0, host='', **tornado_app_settings):
     return server, port
 
 
-def start_server(target, port=0, host='', debug=False,
+def start_server(applications, port=0, host='', debug=False,
                  allowed_origins=None, check_origin=None,
                  auto_open_webbrowser=False,
                  websocket_max_message_size=None,
@@ -172,7 +175,14 @@ def start_server(target, port=0, host='', debug=False,
                  **tornado_app_settings):
     """启动一个 Tornado server 将 ``target`` 任务函数作为Web服务提供。
 
-    :param target: 任务函数。任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现。
+    :param list/dict/callable applications: PyWebIO应用. 可以是任务函数或者任务函数的字典或列表。
+
+       类型为字典时,字典键为任务名,可以通过 ``app`` URL参数选择要运行的任务函数,默认使用运行 ``index`` 任务函数,
+       当 ``index`` 键不存在时,PyWebIO会提供一个默认的索引页作为主页。
+
+       类型为列表时,函数名为任务名
+
+       任务函数为协程函数时,使用 :ref:`基于协程的会话实现 <coroutine_based_session>` ;任务函数为普通函数时,使用基于线程的会话实现。
     :param list allowed_origins: 除当前域名外,服务器还允许的请求的来源列表。
     :param int port: server bind port. set ``0`` to find a free port number to use
     :param str host: server bind host. ``host`` may be either an IP address or hostname.  If it's a hostname,
@@ -211,7 +221,7 @@ def start_server(target, port=0, host='', debug=False,
         if kwargs[opt] is not None:
             tornado_app_settings[opt] = kwargs[opt]
 
-    handler = webio_handler(target, allowed_origins=allowed_origins, check_origin=check_origin)
+    handler = webio_handler(applications, allowed_origins=allowed_origins, check_origin=check_origin)
     _, port = _setup_server(webio_handler=handler, port=port, host=host, **tornado_app_settings)
     if auto_open_webbrowser:
         tornado.ioloop.IOLoop.current().spawn_callback(open_webbrowser_on_server_started, host or 'localhost', port)
@@ -226,7 +236,7 @@ def start_server_in_current_thread_session():
     websocket_conn_opened = threading.Event()
     thread = threading.current_thread()
 
-    class SingleSessionWSHandler(_webio_handler(target=None, session_cls=None)):
+    class SingleSessionWSHandler(_webio_handler(applications={})):
         session = None
         instance = None
 

+ 39 - 0
pywebio/platform/utils.py

@@ -0,0 +1,39 @@
+from collections.abc import Mapping, Sequence
+from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name
+import inspect
+
+
+def _generate_index(applications):
+    """生成默认的主页任务函数"""
+
+    md_text = "## Application index\n"
+    for name, task in applications.items():
+        # todo 保留当前页面的设置项
+        md_text += "- [{name}](?app={name}): {desc}\n".format(name=name, desc=(inspect.getdoc(task) or ''))
+
+    def index():
+        from pywebio.output import put_markdown
+        put_markdown(md_text)
+
+    return index
+
+
+def make_applications(applications):
+    """格式化 applications 为 任务名->任务函数 的映射, 并提供默认主页
+
+    :param applications: 接受 单一任务函数、字典、列表 类型
+    :return dict: 任务名->任务函数 的映射
+    """
+    if isinstance(applications, Sequence):  # 列表 类型
+        applications = {get_function_name(func): func for func in applications}
+    elif not isinstance(applications, Mapping):  # 单一任务函数 类型
+        applications = {'index': applications}
+
+    for app in applications.values():
+        assert iscoroutinefunction(app) or isgeneratorfunction(app) or callable(app), \
+            "Don't support application type:%s" % type(app)
+
+    if 'index' not in applications:
+        applications['index'] = _generate_index(applications)
+
+    return applications

+ 3 - 3
webiojs/src/main.ts

@@ -35,7 +35,7 @@ function set_up_session(webio_session: Session, output_container_elem: JQuery, i
     });
 }
 
-function startWebIOClient(output_container_elem: JQuery, input_container_elem: JQuery, config: { [name: string]: any }) {
+function startWebIOClient(output_container_elem: JQuery, input_container_elem: JQuery, app_name: string, config: { [name: string]: any }) {
     for (let key in config) {
         // @ts-ignore
         appConfig[key] = config[key];
@@ -44,9 +44,9 @@ function startWebIOClient(output_container_elem: JQuery, input_container_elem: J
     is_http_backend(backend_addr).then(function (http_backend) {
         let session;
         if (http_backend)
-            session = new HttpSession(backend_addr, appConfig.httpPullInterval);
+            session = new HttpSession(backend_addr, app_name, appConfig.httpPullInterval);
         else
-            session = new WebSocketSession(backend_addr);
+            session = new WebSocketSession(backend_addr, app_name);
         set_up_session(session, output_container_elem, input_container_elem);
         session.start_session(appConfig.debug);
     });

+ 6 - 2
webiojs/src/session.ts

@@ -40,7 +40,7 @@ export class WebSocketSession implements Session {
     private _on_session_close: (this: WebSocket, ev: CloseEvent) => any = ()=>{};
     private _on_server_message: (msg: Command) => any = ()=>{};
 
-    constructor(public ws_api: string) {
+    constructor(public ws_api: string, app_name: string = 'index') {
         this.ws = null;
         this.debug = false;
         this._closed = false;
@@ -50,6 +50,7 @@ export class WebSocketSession implements Session {
             let protocol = url.protocol || window.location.protocol;
             url.protocol = protocol.replace('https', 'wss').replace('http', 'ws');
         }
+        url.search = "?app=" + app_name;
         this.ws_api = url.href;
     }
 
@@ -115,7 +116,10 @@ export class HttpSession implements Session {
     private _on_server_message: (msg: Command) => void = ()=>{};
 
 
-    constructor(public api_url: string, public pull_interval_ms = 1000) {
+    constructor(public api_url: string, app_name = 'index', public pull_interval_ms = 1000) {
+        let url = new URL(api_url, window.location.href);
+        url.search = "?app=" + app_name;
+        this.api_url = url.href;
     }
 
     on_session_create(callback: () => void): void {

Some files were not shown because too many files changed in this diff