Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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).
  • Loading branch information...
commit f6b3d8fff90f0aa58a7c00ca1dda36447b0f7c87 2 parents cfae632 + bc78497
@fperez fperez authored
View
15 IPython/frontend/html/notebook/handlers.py
@@ -139,7 +139,16 @@ def read_only(self):
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 @@ def post(self):
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 @@ def post(self, kernel_id, action):
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()
View
18 IPython/frontend/html/notebook/notebookapp.py
@@ -147,7 +147,6 @@ def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
'port': 'NotebookApp.port',
'keyfile': 'NotebookApp.keyfile',
'certfile': 'NotebookApp.certfile',
- 'ws-hostname': 'NotebookApp.ws_hostname',
'notebook-dir': 'NotebookManager.notebook_dir',
})
@@ -155,7 +154,7 @@ def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
# 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 @@ def _ip_changed(self, name, old, new):
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 @@ def _ip_changed(self, name, old, new):
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:
View
59 IPython/frontend/html/notebook/static/js/kernel.js
@@ -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;
};
Please sign in to comment.
Something went wrong with that request. Please try again.