Custom front-end talking to remote iPython Kernel: Blank response instead of 'execute_reply'

Hi All,

I’m building a web python ‘notebook’ that would execute python code against a remote iPython kernel.

I am running to issues with communicating to the remote kernel from my custom front-end.

For development, I have both front-end (web notebook) and ipython kernel running in local. iPython kernel is deployed and running in a docker container. Front-end is a simple nodejs program that uses zeroMQ to send requests to iPython kernel. I have studied the messaging protocol and understand that the python code to be executed has to sent as msg type ‘execute_request’ and the response would be of msg type ‘execute_reply’.
I’m able to send a valid ‘execute_request’ after signing the message but the response I receive is blank. Though I can see that the remote iPython kernel generating the ‘execute_reply’ message.

ipython kernel log:

[IPKernelApp] Created profile dir: '/home/myuser/.ipython/profile_default'
[IPKernelApp] Searching path ['/home/myuser/.ipython/profile_default', '/usr/etc/ipython', '/usr/local/etc/ipython', '/etc/ipython'] for config files
[IPKernelApp] Attempting to load config file: ipython_config.py
[IPKernelApp] Looking for ipython_config in /etc/ipython
[IPKernelApp] Looking for ipython_config in /usr/local/etc/ipython
[IPKernelApp] Looking for ipython_config in /usr/etc/ipython
[IPKernelApp] Looking for ipython_config in /home/myuser/.ipython/profile_default
[IPKernelApp] Attempting to load config file: ipython_kernel_config.py
[IPKernelApp] Looking for ipython_kernel_config in /etc/ipython
[IPKernelApp] Looking for ipython_kernel_config in /usr/local/etc/ipython
[IPKernelApp] Looking for ipython_kernel_config in /usr/etc/ipython
[IPKernelApp] Looking for ipython_kernel_config in /home/myuser/.ipython/profile_default
[IPKernelApp] Loading connection file /home/myuser/connection-file.json
[IPKernelApp] Starting the kernel at pid: 1
[IPKernelApp] shell ROUTER Channel on port: 39807
[IPKernelApp] stdin ROUTER Channel on port: 45359
[IPKernelApp] control ROUTER Channel on port: 40673
[IPKernelApp] iopub PUB Channel on port: 41097
[IPKernelApp] Heartbeat REP Channel on port: 49695
[IPKernelApp] Writing connection file: /home/myuser/connection-file.json
[IPKernelApp] To connect another client to this kernel, use:
[IPKernelApp]     --existing /home/myuser/connection-file.json
NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.

To exit, you will have to explicitly quit this process, by either sending
"quit" from a client, or using Ctrl-\ in UNIX-like environments.

To read more about this, see https://github.com/ipython/ipython/issues/2049


To connect another client to this kernel, use:
    --existing /home/myuser/connection-file.json
[IPKernelApp] Seeing logger to stderr, rerouting to raw filedescriptor.
[IPKernelApp] Loading IPython extensions...
[IPKernelApp] Loading IPython extension: storemagic
[IPKernelApp] 
*** MESSAGE TYPE:execute_request***
[IPKernelApp]    Content: {'code': 'print("Hello world!")', 'silent': False, 'store_history': True, 'user_expressions': {}, 'allow_stdin': False, 'stop_on_error': True}
   --->
   
[IPKernelApp] execute_request: {'header': {'msg_id': 'db3792b4-0728-405c-9cac-5347b2724ad6', 'msg_type': 'execute_request', 'date': datetime.datetime(2022, 8, 4, 22, 40, 14, 903096, tzinfo=datetime.timezone.utc), 'version': '5.0'}, 'msg_id': 'db3792b4-0728-405c-9cac-5347b2724ad6', 'msg_type': 'execute_request', 'parent_header': {}, 'metadata': {}, 'content': {'code': 'print("Hello world!")', 'silent': False, 'store_history': True, 'user_expressions': {}, 'allow_stdin': False, 'stop_on_error': True}, 'buffers': []}
[IPKernelApp] {'header': {'msg_id': '7ed44e24-674afba1bf1df2f4a224aff3_1_5', 'msg_type': 'execute_reply', 'username': 'username', 'session': '7ed44e24-674afba1bf1df2f4a224aff3', 'date': datetime.datetime(2022, 8, 4, 22, 40, 14, 917864, tzinfo=datetime.timezone.utc), 'version': '5.3'}, 'msg_id': '7ed44e24-674afba1bf1df2f4a224aff3_1_5', 'msg_type': 'execute_reply', 'parent_header': {'msg_id': 'db3792b4-0728-405c-9cac-5347b2724ad6', 'msg_type': 'execute_request', 'date': datetime.datetime(2022, 8, 4, 22, 40, 14, 903096, tzinfo=datetime.timezone.utc), 'version': '5.0'}, 'content': {'status': 'ok', 'execution_count': 1, 'user_expressions': {}, 'payload': []}, 'metadata': {'started': datetime.datetime(2022, 8, 4, 22, 40, 14, 904511, tzinfo=datetime.timezone.utc), 'dependencies_met': True, 'engine': 'aac4c779-9b2d-4ec6-a932-7c230c31e6e6', 'status': 'ok'}, 'tracker': <zmq.sugar.tracker.MessageTracker object at 0x7f9a3468a890>}
[IPKernelApp] 
*** MESSAGE TYPE:execute_request***
[IPKernelApp]    Content: {'code': 'print("Hello world!")', 'silent': False, 'store_history': True, 'user_expressions': {}, 'allow_stdin': False, 'stop_on_error': True}
   --->

This is my nodejs program:

const zmq = require("zeromq");
const crypto = require("crypto");
const { v4: uuidv4 } = require('uuid');

const shell_socket = zmq.socket('dealer');
let shell_addr = "tcp://localhost:39807";

console.log("\nConnecting shell socket at " + shell_addr + "...");
shell_socket.connect(shell_addr);

shell_socket.on("message", function(msg){
  console.log("\nShell socket says:\n");
  console.log("output:", msg.toString())
});

let key = <key>;
let header = {"msg_id": uuidv4(), "msg_type": "execute_request"};
let parent_header = {};
let metadata = {};
let content = {'code': 'print("Hello world!)")',
                'silent': false,
                'store_history': true,
                'user_expressions': {},
                'allow_stdin': false,
                'stop_on_error': true };

let sign = JSON.stringify(header)
              + JSON.stringify(parent_header)
              + JSON.stringify(metadata)
              + JSON.stringify(content);
let hmac_digest = crypto.createHmac("sha256", key).update(sign).digest("hex");

shell_socket.send(["","<IDS|MSG>",hmac_digest,
          JSON.stringify(header),
          JSON.stringify(parent_header),
          JSON.stringify(metadata),
          JSON.stringify(content)]);

Output:

~/workspace/web_notebook$ node index.js 

Connecting shell socket at tcp://localhost:39807...

Shell socket says:

output: 


I am not able to figure out what I’m doing wrong. Could someone guide me or point to any resource that can help? I looked online for a working example but nothing exists for a non python client.
I have tried both ‘req’ and ‘dealer’ socket with zeroMQ and the behavior is the same.

Note: Using jupyter console pointed to the remote kernel in the same setup works fine.

/workspace/web_notebook$ jupyter console --existing connection-file.json 
Jupyter console 6.4.4

Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: print("Hello world!")
Hello world!

In [2]:

connection-file.json

{
  "shell_port": 39807,
  "iopub_port": 41097,
  "stdin_port": 45359,
  "control_port": 40673,
  "hb_port": 49695,
  "ip": "0.0.0.0",
  "key": <key>,
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "kernel_name": ""
}

As the reference implementation, the jupyter_client code base has the most concrete and up-to-date implementation information, and is de facto the “real” specification.

In the nodejs world, there’s also the ijavascript stack which has some pieces which may be relevant.

Either way, the debugging effort might benefit from getting wireshark up and running so that traces can be compared between different implementations.

@bollwyvl Thank you for your suggestion. Let me have a look at the ijavascript and see if it can help me.

Meanwhile, I placed the nodejs program and ipython within the same docker container and ran the program, the response is still blank.

I also figured there is an open stack overflow thread from years ago with the same issue that I am running into.