mirror of
https://github.com/jupyter/notebook.git
synced 2025-01-12 11:45:38 +08:00
Merge pull request #955 from minrk/websocket
Websocket fixes: 1. alert client on failed and lost web socket connections A long message is given if the connection fails within 1s, which assumes the connection did not succeed. Otherwise, it is a short 'connection closed unexpectedly'. This also means that clients are notified on server termination (for better or worse). 2. remove superfluous ws-hostname parameter from notebook This made the notebook server artificially and unnecessarily brittle against tunneling and explicit hostname resolution. Now, the ws_url is defined based on the Origin of the request for the url, so it always matches the http[s] url. This means that it will follow the same tunnel, and the hostname will be already resolved. Resolving the hostname twice makes no sense at all unless the websockets are going to a different server than the http requests. Implemented as a property, so it should still be easy to change for future cases where it might behave differently (e.g. websockets on a different host, or at a non-root url).
This commit is contained in:
commit
1bb4c726c3
@ -139,7 +139,16 @@ class AuthenticatedHandler(web.RequestHandler):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def ws_url(self):
|
||||
"""websocket url matching the current request
|
||||
|
||||
turns http[s]://host[:port] into
|
||||
ws[s]://host[:port]
|
||||
"""
|
||||
proto = self.request.protocol.replace('http', 'ws')
|
||||
return "%s://%s" % (proto, self.request.host)
|
||||
|
||||
|
||||
class ProjectDashboardHandler(AuthenticatedHandler):
|
||||
@ -221,8 +230,7 @@ class MainKernelHandler(AuthenticatedHandler):
|
||||
km = self.application.kernel_manager
|
||||
notebook_id = self.get_argument('notebook', default=None)
|
||||
kernel_id = km.start_kernel(notebook_id)
|
||||
ws_url = self.application.ipython_app.get_ws_url()
|
||||
data = {'ws_url':ws_url,'kernel_id':kernel_id}
|
||||
data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
|
||||
self.set_header('Location', '/'+kernel_id)
|
||||
self.finish(jsonapi.dumps(data))
|
||||
|
||||
@ -249,8 +257,7 @@ class KernelActionHandler(AuthenticatedHandler):
|
||||
self.set_status(204)
|
||||
if action == 'restart':
|
||||
new_kernel_id = km.restart_kernel(kernel_id)
|
||||
ws_url = self.application.ipython_app.get_ws_url()
|
||||
data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
|
||||
data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
|
||||
self.set_header('Location', '/'+new_kernel_id)
|
||||
self.write(jsonapi.dumps(data))
|
||||
self.finish()
|
||||
|
@ -147,7 +147,6 @@ aliases.update({
|
||||
'port': 'NotebookApp.port',
|
||||
'keyfile': 'NotebookApp.keyfile',
|
||||
'certfile': 'NotebookApp.certfile',
|
||||
'ws-hostname': 'NotebookApp.ws_hostname',
|
||||
'notebook-dir': 'NotebookManager.notebook_dir',
|
||||
})
|
||||
|
||||
@ -155,7 +154,7 @@ aliases.update({
|
||||
# multi-kernel evironment:
|
||||
aliases.pop('f', None)
|
||||
|
||||
notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
|
||||
notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
|
||||
u'notebook-dir']
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@ -200,13 +199,6 @@ class NotebookApp(BaseIPythonApplication):
|
||||
help="The port the notebook server will listen on."
|
||||
)
|
||||
|
||||
ws_hostname = Unicode(LOCALHOST, config=True,
|
||||
help="""The FQDN or IP for WebSocket connections. The default will work
|
||||
fine when the server is listening on localhost, but this needs to
|
||||
be set if the ip option is used. It will be used as the hostname part
|
||||
of the WebSocket url: ws://hostname/path."""
|
||||
)
|
||||
|
||||
certfile = Unicode(u'', config=True,
|
||||
help="""The full path to an SSL/TLS certificate file."""
|
||||
)
|
||||
@ -226,14 +218,6 @@ class NotebookApp(BaseIPythonApplication):
|
||||
help="Whether to prevent editing/execution of notebooks."
|
||||
)
|
||||
|
||||
def get_ws_url(self):
|
||||
"""Return the WebSocket URL for this server."""
|
||||
if self.certfile:
|
||||
prefix = u'wss://'
|
||||
else:
|
||||
prefix = u'ws://'
|
||||
return prefix + self.ws_hostname + u':' + unicode(self.port)
|
||||
|
||||
def parse_command_line(self, argv=None):
|
||||
super(NotebookApp, self).parse_command_line(argv)
|
||||
if argv is None:
|
||||
|
@ -27,7 +27,7 @@ var IPython = (function (IPython) {
|
||||
} else if (typeof(MozWebSocket) !== 'undefined') {
|
||||
this.WebSocket = MozWebSocket
|
||||
} else {
|
||||
alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
|
||||
alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
|
||||
};
|
||||
};
|
||||
|
||||
@ -87,8 +87,37 @@ var IPython = (function (IPython) {
|
||||
IPython.kernel_status_widget.status_idle();
|
||||
};
|
||||
|
||||
Kernel.prototype._websocket_closed = function(ws_url, early){
|
||||
var msg;
|
||||
var parent_item = $('body');
|
||||
if (early) {
|
||||
msg = "Websocket connection to " + ws_url + " could not be established.<br/>" +
|
||||
" You will NOT be able to run code.<br/>" +
|
||||
" Your browser may not be compatible with the websocket version in the server," +
|
||||
" or if the url does not look right, there could be an error in the" +
|
||||
" server's configuration."
|
||||
} else {
|
||||
msg = "Websocket connection closed unexpectedly.<br/>" +
|
||||
" The kernel will no longer be responsive."
|
||||
}
|
||||
var dialog = $('<div/>');
|
||||
dialog.html(msg);
|
||||
parent_item.append(dialog);
|
||||
dialog.dialog({
|
||||
resizable: false,
|
||||
modal: true,
|
||||
title: "Websocket closed",
|
||||
buttons : {
|
||||
"Okay": function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Kernel.prototype.start_channels = function () {
|
||||
var that = this;
|
||||
this.stop_channels();
|
||||
var ws_url = this.ws_url + this.kernel_url;
|
||||
console.log("Starting WS:", ws_url);
|
||||
@ -97,17 +126,45 @@ var IPython = (function (IPython) {
|
||||
send_cookie = function(){
|
||||
this.send(document.cookie);
|
||||
}
|
||||
var already_called_onclose = false; // only alert once
|
||||
ws_closed_early = function(evt){
|
||||
if (already_called_onclose){
|
||||
return;
|
||||
}
|
||||
already_called_onclose = true;
|
||||
if ( ! evt.wasClean ){
|
||||
that._websocket_closed(ws_url, true);
|
||||
}
|
||||
}
|
||||
ws_closed_late = function(evt){
|
||||
if (already_called_onclose){
|
||||
return;
|
||||
}
|
||||
already_called_onclose = true;
|
||||
if ( ! evt.wasClean ){
|
||||
that._websocket_closed(ws_url, false);
|
||||
}
|
||||
}
|
||||
this.shell_channel.onopen = send_cookie;
|
||||
this.shell_channel.onclose = ws_closed_early;
|
||||
this.iopub_channel.onopen = send_cookie;
|
||||
this.iopub_channel.onclose = ws_closed_early;
|
||||
// switch from early-close to late-close message after 1s
|
||||
setTimeout(function(){
|
||||
that.shell_channel.onclose = ws_closed_late;
|
||||
that.iopub_channel.onclose = ws_closed_late;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
Kernel.prototype.stop_channels = function () {
|
||||
if (this.shell_channel !== null) {
|
||||
this.shell_channel.onclose = function (evt) {null};
|
||||
this.shell_channel.close();
|
||||
this.shell_channel = null;
|
||||
};
|
||||
if (this.iopub_channel !== null) {
|
||||
this.iopub_channel.onclose = function (evt) {null};
|
||||
this.iopub_channel.close();
|
||||
this.iopub_channel = null;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user