Skip to content

Commit bbfe2c7

Browse files
committed
Add joined IOPubAndShellHandler
Now only one SockJS connection is needed in the browser for both channels. The previous single-channel connections remain available.
1 parent e2f8f50 commit bbfe2c7

File tree

3 files changed

+65
-55
lines changed

3 files changed

+65
-55
lines changed

IPython/frontend/html/notebook/handlers.py

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ def application(self):
384384
def request(self):
385385
return self.session.handler.request
386386

387-
def _reserialize_reply(self, msg_list):
387+
def _reserialize_reply(self, msg_list, channel=None):
388388
"""Reserialize a reply message using JSON.
389389
390390
This takes the msg list from the ZMQ socket, unserializes it using
@@ -394,20 +394,13 @@ def _reserialize_reply(self, msg_list):
394394
"""
395395
idents, msg_list = self.ip_session.feed_identities(msg_list)
396396
msg = self.ip_session.unserialize(msg_list)
397-
try:
398-
msg['header'].pop('date')
399-
except KeyError:
400-
pass
401-
try:
402-
msg['parent_header'].pop('date')
403-
except KeyError:
404-
pass
405-
msg.pop('buffers')
397+
msg.pop('buffers', None)
398+
msg['channel'] = channel
406399
return jsonapi.dumps(msg, default=date_default)
407400

408-
def _on_zmq_reply(self, msg_list):
401+
def _on_zmq_reply(self, msg_list, channel=None):
409402
try:
410-
msg = self._reserialize_reply(msg_list)
403+
msg = self._reserialize_reply(msg_list, channel)
411404
except Exception:
412405
self.application.log.critical("Malformed message: %r" % msg_list, exc_info=True)
413406
else:
@@ -440,6 +433,12 @@ def on_open(self, info):
440433
self.ip_session = Session(config=cfg)
441434
self.save_on_message = self.on_message
442435
self.on_message = self.on_first_message
436+
437+
self.initialize()
438+
439+
def initialize(self):
440+
"""override in subclasses"""
441+
pass
443442

444443
def get_current_user(self):
445444
handler = self.session.handler
@@ -465,6 +464,11 @@ def on_first_message(self, cookie):
465464
logging.warn("Couldn't authenticate WebSocket connection")
466465
raise web.HTTPError(403)
467466
self.on_message = self.save_on_message
467+
self.create_streams()
468+
469+
def create_streams(self):
470+
"""override in subclass"""
471+
pass
468472

469473

470474
class IOPubHandler(AuthenticatedZMQStreamHandler):
@@ -473,28 +477,23 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
473477
_beating = False
474478
iopub_stream = None
475479
hb_stream = None
476-
477-
def on_first_message(self, msg):
478-
try:
479-
super(IOPubHandler, self).on_first_message(msg)
480-
except web.HTTPError:
481-
self.close()
482-
return
480+
481+
def initialize(self):
483482
km = self.application.kernel_manager
484483
self.time_to_dead = km.time_to_dead
485484
self.first_beat = km.first_beat
485+
486+
def create_streams(self):
487+
km = self.application.kernel_manager
486488
kernel_id = self.kernel_id
487489
try:
488490
self.iopub_stream = km.create_iopub_stream(kernel_id)
489491
self.hb_stream = km.create_hb_stream(kernel_id)
490-
except web.HTTPError:
491-
# WebSockets don't response to traditional error codes so we
492-
# close the connection.
493-
if not self.stream.closed():
494-
self.stream.close()
492+
except Exception:
493+
logging.error("Couldn't create streams", exc_info=True)
495494
self.close()
496495
else:
497-
self.iopub_stream.on_recv(self._on_zmq_reply)
496+
self.iopub_stream.on_recv(lambda msg: self._on_zmq_reply(msg, 'iopub'))
498497
self.start_hb(self.kernel_died)
499498

500499
def on_message(self, msg):
@@ -562,36 +561,32 @@ def kernel_died(self):
562561
self.send(json.dumps(
563562
{'header': {'msg_type': 'status'},
564563
'parent_header': {},
565-
'content': {'execution_state':'dead'}
564+
'content': {'execution_state':'dead'},
565+
'channel': 'iopub',
566566
}
567567
))
568568
self.on_close()
569569

570570

571571
class ShellHandler(AuthenticatedZMQStreamHandler):
572572

573-
def initialize(self, *args, **kwargs):
574-
self.shell_stream = None
575-
576-
def on_first_message(self, msg):
577-
try:
578-
super(ShellHandler, self).on_first_message(msg)
579-
except web.HTTPError:
580-
self.close()
581-
return
573+
shell_stream = None
574+
max_msg_size = 65535
575+
576+
def initialize(self):
582577
km = self.application.kernel_manager
583578
self.max_msg_size = km.max_msg_size
579+
580+
def create_streams(self):
581+
km = self.application.kernel_manager
584582
kernel_id = self.kernel_id
585583
try:
586584
self.shell_stream = km.create_shell_stream(kernel_id)
587-
except web.HTTPError:
588-
# WebSockets don't response to traditional error codes so we
589-
# close the connection.
590-
if not self.stream.closed():
591-
self.stream.close()
585+
except Exception:
586+
logging.error("Couldn't create shell stream", exc_info=True)
592587
self.close()
593588
else:
594-
self.shell_stream.on_recv(self._on_zmq_reply)
589+
self.shell_stream.on_recv(lambda msg: self._on_zmq_reply(msg, 'shell'))
595590

596591
def on_message(self, msg):
597592
if len(msg) < self.max_msg_size:
@@ -603,6 +598,20 @@ def on_close(self):
603598
if self.shell_stream is not None and not self.shell_stream.closed():
604599
self.shell_stream.close()
605600

601+
class IOPubAndShellHandler(ShellHandler, IOPubHandler):
602+
"""single SockJS handler for IOPub + Shell zmq channels"""
603+
def on_close(self):
604+
ShellHandler.on_close(self)
605+
IOPubHandler.on_close(self)
606+
607+
def initialize(self):
608+
ShellHandler.initialize(self)
609+
IOPubHandler.initialize(self)
610+
611+
def create_streams(self):
612+
ShellHandler.create_streams(self)
613+
IOPubHandler.create_streams(self)
614+
606615

607616
#-----------------------------------------------------------------------------
608617
# Notebook web service handlers

IPython/frontend/html/notebook/notebookapp.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
5353
RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
5454
MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
55-
FileFindHandler,
55+
FileFindHandler, IOPubAndShellHandler,
5656
)
5757
from .nbmanager import NotebookManager
5858
from .filenbmanager import FileNotebookManager
@@ -152,9 +152,11 @@ def __init__(self, ipython_app, kernel_manager, notebook_manager,
152152

153153
iopub_router = SockJSRouter(IOPubHandler, r"/kernels/%s/iopub" % _uuid_regex)
154154
shell_router = SockJSRouter(ShellHandler, r"/kernels/%s/shell" % _uuid_regex)
155+
sock_router = SockJSRouter(IOPubAndShellHandler, r"/kernels/%s/sock" % _uuid_regex)
155156

156157
handlers += shell_router.urls
157158
handlers += iopub_router.urls
159+
handlers += sock_router.urls
158160

159161
# Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
160162
# base_project_url will always be unicode, which will in turn

IPython/frontend/html/notebook/static/js/kernel.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ var IPython = (function (IPython) {
1818
var Kernel = function (base_url) {
1919
this.kernel_id = null;
2020
this.shell_channel = null;
21-
this.iopub_channel = null;
2221
this.base_url = base_url;
2322
this.running = false;
2423
this.username = "username";
@@ -71,13 +70,22 @@ var IPython = (function (IPython) {
7170

7271
Kernel.prototype._kernel_started = function (json) {
7372
console.log("Kernel started: ", json.kernel_id);
73+
var that = this
7474
this.running = true;
7575
this.kernel_id = json.kernel_id;
7676
this.ws_url = json.ws_url;
7777
this.kernel_url = this.base_url + "/" + this.kernel_id;
7878
this.start_channels();
79-
this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
80-
this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
79+
this.shell_channel.onmessage = function(e) {
80+
var msg = $.parseJSON(e.data);
81+
if (msg['channel'] == 'shell'){
82+
that._handle_shell_reply(e);
83+
} else if (msg['channel'] == 'iopub') {
84+
that._handle_iopub_reply(e);
85+
} else {
86+
console.log("Bad message", e);
87+
}
88+
};
8189
};
8290

8391

@@ -118,8 +126,7 @@ var IPython = (function (IPython) {
118126
this.stop_channels();
119127
var ws_url = this.ws_url + this.kernel_url;
120128
console.log("Starting SockJS:", ws_url);
121-
this.shell_channel = new SockJS(ws_url + "/shell");
122-
this.iopub_channel = new SockJS(ws_url + "/iopub");
129+
this.shell_channel = new SockJS(ws_url + "/sock");
123130
send_cookie = function(){
124131
this.send(document.cookie);
125132
};
@@ -144,12 +151,9 @@ var IPython = (function (IPython) {
144151
};
145152
this.shell_channel.onopen = send_cookie;
146153
this.shell_channel.onclose = ws_closed_early;
147-
this.iopub_channel.onopen = send_cookie;
148-
this.iopub_channel.onclose = ws_closed_early;
149154
// switch from early-close to late-close message after 1s
150155
setTimeout(function(){
151156
that.shell_channel.onclose = ws_closed_late;
152-
that.iopub_channel.onclose = ws_closed_late;
153157
}, 1000);
154158
};
155159

@@ -160,11 +164,6 @@ var IPython = (function (IPython) {
160164
this.shell_channel.close();
161165
this.shell_channel = null;
162166
};
163-
if (this.iopub_channel !== null) {
164-
this.iopub_channel.onclose = function (evt) {};
165-
this.iopub_channel.close();
166-
this.iopub_channel = null;
167-
};
168167
};
169168

170169
// Main public methods.

0 commit comments

Comments
 (0)