Notebook with custom comms between Browser JS and python Kernel - ZMQ/WebSockets bridge?

266 views
Skip to first unread message

Jeremy W

unread,
Jan 6, 2020, 12:00:40 PM1/6/20
to Project Jupyter
Hi,

We have a python app and a JS front-end which currently talk using WebSockets:
- Python serves a port using flask and flask websockets.  
- The Browser loads some static JS from Flask, and the JS uses the same port to send & receive websockets. 
-- ie the connection is inbound from JS -> Python but once established the messages go both ways, mostly Python -> JS.

We would like to incorporate the JS front-end into a notebook.  We can currently only do this by getting the python to listen on a separate port from the Notebook Server.  This is manageable  on hosts which we completely control (eg a Google Cloud compute instance), but we would like to make it available on services like
- MyBinder (where we get reasonable control of a VM / container, but not inbound ports) 
- and maybe CoLab (where we don't control the VM).

So is there a way of "bridging" our own message channel from the Browser JS Client all the way through to the Kernel?

Kernel <---- ZMQ ----> Notebook Server <--- WebSockets---> Browser JavaScript App

We want to keep the notebook setting, ie I don't think we want to ditch the notebook in favour of Jupyter Kernel Gateway (if I'm reading it correctly).

For example is it possible to somehow set up a custom channel in the ZMQ/WebSockets bridge?

We could of course replace our existing WebSockets code at the Python end with something equivalent.

Should we perhaps wrap the JS into a IPyWidget?  This looks more complicated than we really need.  

Here's a couple of related questions, but they are somewhat old:

Our project is open source - https://gitlab.aicrowd.com/flatland  :)

Thanks!

Jeremy.

Travis DePrato

unread,
Jan 6, 2020, 12:33:53 PM1/6/20
to jup...@googlegroups.com
I think the best approach here would be to write a wrapper around your communication channel. This is kind of what we do for WebIO.jl (which is the foundation for Julia's version of ipywidgets, aka Interact.jl).

Essentially, we abstract the communication model as a "bidirectional stream of messages," typically using JSON encoding (though that's also not set in stone if a connection wanted to use a different serialization). Then, essentially, the different types of connection encapsulate everything about how the messages are actually transmitted. So, in your case, you might have a WebSocketConnection (which encapsulates what you have now) and a JupyterConnection (which deals with sending messages over comms).

A huuuuuuge limitation of going down that route though would be that Jupyter communication is inherently sequential. You **cannot** process messages while code is running in a code cell. Also, the JS has to be distributed separately (for the most part).

An alternative, if you don't need to interact with the code that's being run in Jupyter, would be to install a server extension which exposes your WebSocket at a particular path (e.g. localhost:8000/flatland/ws). This would require integrating with Jupyter's tornado web handling. You could either have the server extension launch another process and forward messages, or you could have the serverside stuff running in the same process as the Jupyter server. The former is a bit more complex but the latter runs the risk of blocking the notebook process if you have any long running computation (e.g., if you have a computation that takes ten seconds, the Jupyter server won't respond to requests for ten seconds, unless you integrate well with Tornado's async stuff).

--
You received this message because you are subscribed to the Google Groups "Project Jupyter" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jupyter+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jupyter/16038bf5-cdee-4f33-95d9-a62cc40b2f50%40googlegroups.com.


--
Travis DePrato (he/him/his)
Experiential Learning Software Architect
University of Michigan '19

Jeremy W

unread,
Jan 8, 2020, 6:24:14 PM1/8/20
to Project Jupyter
Hi thanks for the suggestions Travis.

I guess your final paragraph might provide a route - if we can just get some code to run in the Jupyter server, and hook into its WebSockets server, since our messages would be on a separate channel they wouldn't interfere with Jupyter, and our code could then forward it somehow to our kernel, maybe by starting another local port for the kernel to connect to, or a Jupyter mechanism.  The downside being that we have to write code to run on the Juypter server.

We also had the thought that we could embed our JS inside a Ipywidget and use the existing widget mechanism for keeping data models in sync between the kernel and JS; this isn't exactly what we need but it might be easier than trying to break into the transport mechanisms.

Best wishes
Jeremy


On Monday, 6 January 2020 17:33:53 UTC, Travis DePrato wrote:
I think the best approach here would be to write a wrapper around your communication channel. This is kind of what we do for WebIO.jl (which is the foundation for Julia's version of ipywidgets, aka Interact.jl).

Essentially, we abstract the communication model as a "bidirectional stream of messages," typically using JSON encoding (though that's also not set in stone if a connection wanted to use a different serialization). Then, essentially, the different types of connection encapsulate everything about how the messages are actually transmitted. So, in your case, you might have a WebSocketConnection (which encapsulates what you have now) and a JupyterConnection (which deals with sending messages over comms).

A huuuuuuge limitation of going down that route though would be that Jupyter communication is inherently sequential. You **cannot** process messages while code is running in a code cell. Also, the JS has to be distributed separately (for the most part).

An alternative, if you don't need to interact with the code that's being run in Jupyter, would be to install a server extension which exposes your WebSocket at a particular path (e.g. localhost:8000/flatland/ws). This would require integrating with Jupyter's tornado web handling. You could either have the server extension launch another process and forward messages, or you could have the serverside stuff running in the same process as the Jupyter server. The former is a bit more complex but the latter runs the risk of blocking the notebook process if you have any long running computation (e.g., if you have a computation that takes ten seconds, the Jupyter server won't respond to requests for ten seconds, unless you integrate well with Tornado's async stuff).

On Mon, Jan 6, 2020 at 12:01 PM Jeremy W <jeremy....@gmail.com> wrote:
Hi,

We have a python app and a JS front-end which currently talk using WebSockets:
- Python serves a port using flask and flask websockets.  
- The Browser loads some static JS from Flask, and the JS uses the same port to send & receive websockets. 
-- ie the connection is inbound from JS -> Python but once established the messages go both ways, mostly Python -> JS.

We would like to incorporate the JS front-end into a notebook.  We can currently only do this by getting the python to listen on a separate port from the Notebook Server.  This is manageable  on hosts which we completely control (eg a Google Cloud compute instance), but we would like to make it available on services like
- MyBinder (where we get reasonable control of a VM / container, but not inbound ports) 
- and maybe CoLab (where we don't control the VM).

So is there a way of "bridging" our own message channel from the Browser JS Client all the way through to the Kernel?

Kernel <---- ZMQ ----> Notebook Server <--- WebSockets---> Browser JavaScript App

We want to keep the notebook setting, ie I don't think we want to ditch the notebook in favour of Jupyter Kernel Gateway (if I'm reading it correctly).

For example is it possible to somehow set up a custom channel in the ZMQ/WebSockets bridge?

We could of course replace our existing WebSockets code at the Python end with something equivalent.

Should we perhaps wrap the JS into a IPyWidget?  This looks more complicated than we really need.  

Here's a couple of related questions, but they are somewhat old:

Our project is open source - https://gitlab.aicrowd.com/flatland  :)

Thanks!

Jeremy.

--
You received this message because you are subscribed to the Google Groups "Project Jupyter" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jup...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages