perspective.handlers.tornado

 1#  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
 2#  ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
 3#  ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
 4#  ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
 5#  ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
 6#  ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
 7#  ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
 8#  ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
 9#  ┃ This file is part of the Perspective library, distributed under the terms ┃
10#  ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11#  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13from tornado.websocket import WebSocketHandler, WebSocketClosedError
14from tornado.ioloop import IOLoop
15import perspective
16
17
18class PerspectiveTornadoHandler(WebSocketHandler):
19    """`PerspectiveTornadoHandler` is a `perspective.Server` API as a `tornado`
20    websocket handler.
21
22    Use it inside `tornado` routing to create a `perspective.Server` that can
23    connect to a JavaScript (Wasm) `Client`, providing a virtual interface to
24    the `Server`'s resources for e.g. `<perspective-viewer>`.
25
26    You may need to increase the `websocket_max_message_size` kwarg
27    to the `tornado.web.Application` constructor, as well as provide the
28    `max_buffer_size` optional arg, for large datasets.
29
30    # Arguments
31
32    -   `loop`: An optional `IOLoop` instance to use for scheduling IO calls,
33        defaults to `IOLoop.current()`.
34    -   `executor`: An optional executor for scheduling `perspective.Server`
35        message processing calls from websocket `Client`s.
36
37    # Examples
38
39    >>> server = psp.Server()
40    >>> client = server.new_local_client()
41    >>> client.table(pd.read_csv("superstore.csv"), name="data_source_one")
42    >>> app = tornado.web.Application([
43    ...     (r"/", MainHandler),
44    ...     (r"/websocket", PerspectiveTornadoHandler, {
45    ...         "perspective_server": server,
46    ...     })
47    ... ])
48    """
49
50    def check_origin(self, origin):
51        return True
52
53    def initialize(
54        self,
55        perspective_server=perspective.GLOBAL_SERVER,
56        loop=None,
57        executor=None,
58        max_buffer_size=None,
59    ):
60        self.server = perspective_server
61        self.loop = loop or IOLoop.current()
62        self.executor = executor
63        if max_buffer_size is not None:
64            self.request.connection.stream.max_buffer_size = max_buffer_size
65
66    def open(self):
67        def write(msg):
68            try:
69                self.write_message(msg, binary=True)
70            except WebSocketClosedError:
71                self.close()
72
73        def send_response(msg):
74            self.loop.add_callback(write, msg)
75
76        self.session = self.server.new_session(send_response)
77
78    def on_close(self) -> None:
79        self.session.close()
80        del self.session
81
82    def on_message(self, msg: bytes):
83        if not isinstance(msg, bytes):
84            return
85
86        if self.executor is None:
87            self.session.handle_request(msg)
88        else:
89            self.executor.submit(self.session.handle_request, msg)
class PerspectiveTornadoHandler(tornado.websocket.WebSocketHandler):
19class PerspectiveTornadoHandler(WebSocketHandler):
20    """`PerspectiveTornadoHandler` is a `perspective.Server` API as a `tornado`
21    websocket handler.
22
23    Use it inside `tornado` routing to create a `perspective.Server` that can
24    connect to a JavaScript (Wasm) `Client`, providing a virtual interface to
25    the `Server`'s resources for e.g. `<perspective-viewer>`.
26
27    You may need to increase the `websocket_max_message_size` kwarg
28    to the `tornado.web.Application` constructor, as well as provide the
29    `max_buffer_size` optional arg, for large datasets.
30
31    # Arguments
32
33    -   `loop`: An optional `IOLoop` instance to use for scheduling IO calls,
34        defaults to `IOLoop.current()`.
35    -   `executor`: An optional executor for scheduling `perspective.Server`
36        message processing calls from websocket `Client`s.
37
38    # Examples
39
40    >>> server = psp.Server()
41    >>> client = server.new_local_client()
42    >>> client.table(pd.read_csv("superstore.csv"), name="data_source_one")
43    >>> app = tornado.web.Application([
44    ...     (r"/", MainHandler),
45    ...     (r"/websocket", PerspectiveTornadoHandler, {
46    ...         "perspective_server": server,
47    ...     })
48    ... ])
49    """
50
51    def check_origin(self, origin):
52        return True
53
54    def initialize(
55        self,
56        perspective_server=perspective.GLOBAL_SERVER,
57        loop=None,
58        executor=None,
59        max_buffer_size=None,
60    ):
61        self.server = perspective_server
62        self.loop = loop or IOLoop.current()
63        self.executor = executor
64        if max_buffer_size is not None:
65            self.request.connection.stream.max_buffer_size = max_buffer_size
66
67    def open(self):
68        def write(msg):
69            try:
70                self.write_message(msg, binary=True)
71            except WebSocketClosedError:
72                self.close()
73
74        def send_response(msg):
75            self.loop.add_callback(write, msg)
76
77        self.session = self.server.new_session(send_response)
78
79    def on_close(self) -> None:
80        self.session.close()
81        del self.session
82
83    def on_message(self, msg: bytes):
84        if not isinstance(msg, bytes):
85            return
86
87        if self.executor is None:
88            self.session.handle_request(msg)
89        else:
90            self.executor.submit(self.session.handle_request, msg)

PerspectiveTornadoHandler is a perspective.Server API as a tornado websocket handler.

Use it inside tornado routing to create a perspective.Server that can connect to a JavaScript (Wasm) Client, providing a virtual interface to the Server's resources for e.g. <perspective-viewer>.

You may need to increase the websocket_max_message_size kwarg to the tornado.web.Application constructor, as well as provide the max_buffer_size optional arg, for large datasets.

Arguments

  • loop: An optional IOLoop instance to use for scheduling IO calls, defaults to IOLoop.current().
  • executor: An optional executor for scheduling perspective.Server message processing calls from websocket Clients.

Examples

>>> server = psp.Server()
>>> client = server.new_local_client()
>>> client.table(pd.read_csv("superstore.csv"), name="data_source_one")
>>> app = tornado.web.Application([
...     (r"/", MainHandler),
...     (r"/websocket", PerspectiveTornadoHandler, {
...         "perspective_server": server,
...     })
... ])
def check_origin(self, origin):
51    def check_origin(self, origin):
52        return True

Override to enable support for allowing alternate origins.

The origin argument is the value of the Origin HTTP header, the url responsible for initiating this request. This method is not called for clients that do not send this header; such requests are always allowed (because all browsers that implement WebSockets support this header, and non-browser clients do not have the same cross-site security concerns).

Should return True to accept the request or False to reject it. By default, rejects all requests with an origin on a host other than this one.

This is a security protection against cross site scripting attacks on browsers, since WebSockets are allowed to bypass the usual same-origin policies and don't use CORS headers.

This is an important security measure; don't disable it without understanding the security implications. In particular, if your authentication is cookie-based, you must either restrict the origins allowed by check_origin() or implement your own XSRF-like protection for websocket connections. See these articles for more.

To accept all cross-origin traffic (which was the default prior to Tornado 4.0), simply override this method to always return True::

def check_origin(self, origin):
    return True

To allow connections from any subdomain of your site, you might do something like::

def check_origin(self, origin):
    parsed_origin = urllib.parse.urlparse(origin)
    return parsed_origin.netloc.endswith(".mydomain.com")

New in version 4.0.

def initialize( self, perspective_server=<perspective.Server object>, loop=None, executor=None, max_buffer_size=None):
54    def initialize(
55        self,
56        perspective_server=perspective.GLOBAL_SERVER,
57        loop=None,
58        executor=None,
59        max_buffer_size=None,
60    ):
61        self.server = perspective_server
62        self.loop = loop or IOLoop.current()
63        self.executor = executor
64        if max_buffer_size is not None:
65            self.request.connection.stream.max_buffer_size = max_buffer_size

Hook for subclass initialization. Called for each request.

A dictionary passed as the third argument of a URLSpec will be supplied as keyword arguments to initialize().

Example::

class ProfileHandler(RequestHandler):
    def initialize(self, database):
        self.database = database

    def get(self, username):
        ...

app = Application([
    (r'/user/(.*)', ProfileHandler, dict(database=database)),
    ])
def open(self):
67    def open(self):
68        def write(msg):
69            try:
70                self.write_message(msg, binary=True)
71            except WebSocketClosedError:
72                self.close()
73
74        def send_response(msg):
75            self.loop.add_callback(write, msg)
76
77        self.session = self.server.new_session(send_response)

Invoked when a new WebSocket is opened.

The arguments to open are extracted from the tornado.web.URLSpec regular expression, just like the arguments to tornado.web.RequestHandler.get.

open may be a coroutine. on_message will not be called until open has returned.

Changed in version 5.1: open may be a coroutine.

def on_close(self) -> None:
79    def on_close(self) -> None:
80        self.session.close()
81        del self.session

Invoked when the WebSocket is closed.

If the connection was closed cleanly and a status code or reason phrase was supplied, these values will be available as the attributes self.close_code and self.close_reason.

Changed in version 4.0: Added close_code and close_reason attributes.

def on_message(self, msg: bytes):
83    def on_message(self, msg: bytes):
84        if not isinstance(msg, bytes):
85            return
86
87        if self.executor is None:
88            self.session.handle_request(msg)
89        else:
90            self.executor.submit(self.session.handle_request, msg)

Handle incoming messages on the WebSocket

This method must be overridden.

Changed in version 4.5: on_message can be a coroutine.