【问题标题】:Jupyter for WebSocket communication用于 WebSocket 通信的 Jupyter
【发布时间】:2015-09-01 09:44:53
【问题描述】:

我正在开发一个需要 WebSockets API 的应用程序,并且还将集成 Jupyter(前 IPython)笔记本作为一个相对较小的功能。既然 Jupyter 已经使用 WebSockets 进行通信,那么将它集成为一个通用库以单独为其他 WebSockets API 提供服务会有多困难?还是我最好使用另一个库,例如aiohttp?我正在寻找与此最佳实践相关的任何建议和提示。谢谢!

【问题讨论】:

    标签: python-3.x websocket ipython ipython-notebook jupyter


    【解决方案1】:

    您可以将 WebSockets 从您的主应用程序代理到 Jupyter。

    你使用什么技术来服务 WebSockets 并不重要,代理循环将非常相似(等待消息,推送消息转发)。但是,它将依赖于 Web 服务器,因为 Python 没有类似于 WSGI 的 WebSocket 标准。

    我在pyramid_notebook project 做了一个。必须在自己的进程中运行 Jupyter,至少在编写代码时,将 Jupyter 直接嵌入到您的应用程序是不可行的。我不确定最新版本是否改变了这一点。 Jupyter 本身正在使用 Tornado。

    """UWSGI websocket proxy."""
    from urllib.parse import urlparse, urlunparse
    import logging
    import time
    
    import uwsgi
    from pyramid import httpexceptions
    from ws4py import WS_VERSION
    from ws4py.client import WebSocketBaseClient
    
    
    #: HTTP headers we need to proxy to upstream websocket server when the Connect: upgrade is performed
    CAPTURE_CONNECT_HEADERS = ["sec-websocket-extensions", "sec-websocket-key", "origin"]
    
    
    logger = logging.getLogger(__name__)
    
    
    class ProxyClient(WebSocketBaseClient):
        """Proxy between upstream WebSocket server and downstream UWSGI."""
    
        @property
        def handshake_headers(self):
            """
            List of headers appropriate for the upgrade
            handshake.
            """
            headers = [
                ('Host', self.host),
                ('Connection', 'Upgrade'),
                ('Upgrade', 'WebSocket'),
                ('Sec-WebSocket-Key', self.key.decode('utf-8')),
                # Origin is proxyed from the downstream server, don't set it twice
                # ('Origin', self.url),
                ('Sec-WebSocket-Version', str(max(WS_VERSION)))
                ]
    
            if self.protocols:
                headers.append(('Sec-WebSocket-Protocol', ','.join(self.protocols)))
    
            if self.extra_headers:
                headers.extend(self.extra_headers)
    
            logger.info("Handshake headers: %s", headers)
            return headers
    
        def received_message(self, m):
            """Push upstream messages to downstream."""
    
            # TODO: No support for binary messages
            m = str(m)
            logger.debug("Incoming upstream WS: %s", m)
            uwsgi.websocket_send(m)
            logger.debug("Send ok")
    
        def handshake_ok(self):
            """
            Called when the upgrade handshake has completed
            successfully.
    
            Starts the client's thread.
            """
            self.run()
    
        def terminate(self):
            super(ProxyClient, self).terminate()
    
        def run(self):
            """Combine async uwsgi message loop with ws4py message loop.
    
            TODO: This could do some serious optimizations and behave asynchronously correct instead of just sleep().
            """
    
            self.sock.setblocking(False)
            try:
                while not self.terminated:
                    logger.debug("Doing nothing")
                    time.sleep(0.050)
    
                    logger.debug("Asking for downstream msg")
                    msg = uwsgi.websocket_recv_nb()
                    if msg:
                        logger.debug("Incoming downstream WS: %s", msg)
                        self.send(msg)
    
                    s = self.stream
    
                    self.opened()
    
                    logger.debug("Asking for upstream msg")
                    try:
                        bytes = self.sock.recv(self.reading_buffer_size)
                        if bytes:
                            self.process(bytes)
                    except BlockingIOError:
                        pass
    
            except Exception as e:
                logger.exception(e)
            finally:
                logger.info("Terminating WS proxy loop")
                self.terminate()
    
    
    def serve_websocket(request, port):
        """Start UWSGI websocket loop and proxy."""
        env = request.environ
    
        # Send HTTP response 101 Switch Protocol downstream
        uwsgi.websocket_handshake(env['HTTP_SEC_WEBSOCKET_KEY'], env.get('HTTP_ORIGIN', ''))
    
        # Map the websocket URL to the upstream localhost:4000x Notebook instance
        parts = urlparse(request.url)
        parts = parts._replace(scheme="ws", netloc="localhost:{}".format(port))
        url = urlunparse(parts)
    
        # Proxy initial connection headers
        headers = [(header, value) for header, value in request.headers.items() if header.lower() in CAPTURE_CONNECT_HEADERS]
    
        logger.info("Connecting to upstream websockets: %s, headers: %s", url, headers)
    
        ws = ProxyClient(url, headers=headers)
        ws.connect()
    
        # TODO: Will complain loudly about already send headers - how to abort?
        return httpexceptions.HTTPOk()
    

    【讨论】:

    • 您好 Mikko,谢谢您的回答。事实上,经过一番挖掘,我意识到我的问题并不是我真正想问的。因此,我对重用 iPython 的 comm 对象与外部应用程序中的前端进行通信的经验非常好奇。话虽如此,您的回答仍然非常有用,并为在此过程中出现的其他问题提供了答案,所以我会接受它并创建一个新问题。
    猜你喜欢
    • 2013-01-24
    • 2022-09-24
    • 1970-01-01
    • 2023-02-12
    • 1970-01-01
    • 2018-06-20
    • 1970-01-01
    • 2021-02-12
    • 1970-01-01
    相关资源
    最近更新 更多