Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Websocket Adjustments #955

Merged
merged 4 commits into from

3 participants

Min RK Brian E. Granger Fernando Perez
Min RK
Owner

Two principal changes:

  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).

  1. 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 is one approach to closing #952, but there might be better ways to do it (e.g. dialogs that are not base alert calls, which block window interaction).

minrk added some commits
Min RK minrk remove superfluous ws-hostname parameter from notebook
This made the notebook server artificially and unnecessarily brittle to tunneling, and non-local hostname resolution.  The ws_url is now defined based on the Origin of the request.  This allows tunneled hosts and ports to preserve connections, as the ws_url always matches the one in use by the client.

Implemented as a property, to facilitate future cases where it might behave differently.
f00c904
Min RK minrk alert client on failed and lost web socket connections
A long message is given if the connection fails within 1s.  Otherwise, it is a short 'connection closed unexpectedly'.

This also means that clients are notified on server termination.
81cfbe8
Brian E. Granger
Owner

@minrk: removing ws-hostname is fine. Originally, I thought we might need to run the ws stuff in a different process, but that didn't pan out.

Min RK
Owner

I changed the error message to be a jQuery dialog rather than a plain alert, so it's less obstructive.

Min RK
Owner

@ellisonbg - that makes sense. It should still be easy to change, in environments where the websocket host and/or port differ from the rest.

Fernando Perez
Owner

Mh, I tried running a notebook with this branch on and I got this traceback:

ERROR:root:Uncaught exception POST /kernels?notebook=69f0f1e7-5cbf-4d0c-b7fe-7807e1023886 (128.32.52.133)
HTTPRequest(protocol='https', host='longs.berkeley.edu:9999', method='POST', uri='/kernels?notebook=69f0f1e7-5cbf-4d0c-b7fe-7807e1023886', version='HTTP/1.1', remote_ip='128.32.52.133', body='', headers={'Referer': 'https://longs.berkeley.edu:9999/69f0f1e7-5cbf-4d0c-b7fe-7807e1023886', 'Content-Length': '0', 'Accept-Language': 'en-us,en;q=0.5', 'Accept-Encoding': 'gzip, deflate', 'Host': 'longs.berkeley.edu:9999', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1', 'Accept-Charset': 'UTF-8,*', 'Connection': 'keep-alive', 'X-Requested-With': 'XMLHttpRequest', 'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 'Cookie': '__utma=245566491.1143703340.1319585252.1319585252.1320105682.2; __utmz=245566491.1319585252.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); username=YWUyZGI4NjctNDQzMS00ZGIxLWI1MDItNjkwNDk2MzJmNGY1|1320968643|33efb552dd2899f965dbcf4deb1d81d612e3a06f'})
Traceback (most recent call last):
  File "/home/fperez/usr/local/lib/python2.6/site-packages/tornado-2.1-py2.6.egg/tornado/web.py", line 954, in _execute
    getattr(self, self.request.method.lower())(*args, **kwargs)
  File "/home/fperez/usr/local/lib/python2.6/site-packages/tornado-2.1-py2.6.egg/tornado/web.py", line 1667, in wrapper
    return method(self, *args, **kwargs)
  File "/home/fperez/usr/lib/python2.6/site-packages/IPython/frontend/html/notebook/handlers.py", line 232, in post
    data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
  File "/home/fperez/usr/lib/python2.6/site-packages/IPython/frontend/html/notebook/handlers.py", line 150, in ws_url
    return self.request.headers.get('Origin').replace('http', 'ws', 1)
AttributeError: 'NoneType' object has no attribute 'replace'
ERROR:root:500 POST /kernels?notebook=69f0f1e7-5cbf-4d0c-b7fe-7807e1023886 (128.32.52.133) 39.75ms

I can't actually execute any cells. Do you get the same? Note that the traceback occurs on notebook connection, before any attempt to even execute code.

Min RK
Owner

Apparently Firefox doesn't send the Origin header, so I changed it to write protocol / host directly from the request. This will still work for tunnels, but I think it probably won't work if the notebook server is behind a reverse-proxy (e.g. if you have apache set up to forward http://server/ipnb to http://localhost:8888).

Fernando Perez
Owner

Great, tested and merging now, everything seems to work. Not having to pass the websocket argument is nice.

Fernando Perez fperez merged commit f6b3d8f into from
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 31, 2011
  1. Min RK

    remove superfluous ws-hostname parameter from notebook

    minrk authored
    This made the notebook server artificially and unnecessarily brittle to tunneling, and non-local hostname resolution.  The ws_url is now defined based on the Origin of the request.  This allows tunneled hosts and ports to preserve connections, as the ws_url always matches the one in use by the client.
    
    Implemented as a property, to facilitate future cases where it might behave differently.
  2. Min RK

    alert client on failed and lost web socket connections

    minrk authored
    A long message is given if the connection fails within 1s.  Otherwise, it is a short 'connection closed unexpectedly'.
    
    This also means that clients are notified on server termination.
Commits on Nov 1, 2011
  1. Min RK
Commits on Nov 11, 2011
  1. Min RK
This page is out of date. Refresh to see the latest.
15 IPython/frontend/html/notebook/handlers.py
View
@@ -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()
18 IPython/frontend/html/notebook/notebookapp.py
View
@@ -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:
59 IPython/frontend/html/notebook/static/js/kernel.js
View
@@ -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;
};
Something went wrong with that request. Please try again.