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)
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 optionalIOLoop
instance to use for scheduling IO calls, defaults toIOLoop.current()
.executor
: An optional executor for schedulingperspective.Server
message processing calls from websocketClient
s.
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,
... })
... ])
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.
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)),
])
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.
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.
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.