##// END OF EJS Templates
remove on_first_message authentication...
MinRK -
Show More
@@ -1,214 +1,202 b''
1 1 """Tornado handlers for WebSocket <-> ZMQ sockets."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import json
7 7
8 8 try:
9 9 from urllib.parse import urlparse # Py 3
10 10 except ImportError:
11 11 from urlparse import urlparse # Py 2
12 12
13 13 try:
14 14 from http.cookies import SimpleCookie # Py 3
15 15 except ImportError:
16 16 from Cookie import SimpleCookie # Py 2
17 17 import logging
18 18
19 19 import tornado
20 20 from tornado import ioloop
21 21 from tornado import web
22 22 from tornado import websocket
23 23
24 24 from IPython.kernel.zmq.session import Session
25 25 from IPython.utils.jsonutil import date_default
26 26 from IPython.utils.py3compat import PY3, cast_unicode
27 27
28 28 from .handlers import IPythonHandler
29 29
30 30
31 31 class ZMQStreamHandler(websocket.WebSocketHandler):
32 32
33 33 def check_origin(self, origin):
34 34 """Check Origin == Host or Access-Control-Allow-Origin.
35 35
36 36 Tornado >= 4 calls this method automatically, raising 403 if it returns False.
37 37 We call it explicitly in `open` on Tornado < 4.
38 38 """
39 39 if self.allow_origin == '*':
40 40 return True
41 41
42 42 host = self.request.headers.get("Host")
43 43
44 44 # If no header is provided, assume we can't verify origin
45 45 if origin is None:
46 46 self.log.warn("Missing Origin header, rejecting WebSocket connection.")
47 47 return False
48 48 if host is None:
49 49 self.log.warn("Missing Host header, rejecting WebSocket connection.")
50 50 return False
51 51
52 52 origin = origin.lower()
53 53 origin_host = urlparse(origin).netloc
54 54
55 55 # OK if origin matches host
56 56 if origin_host == host:
57 57 return True
58 58
59 59 # Check CORS headers
60 60 if self.allow_origin:
61 61 allow = self.allow_origin == origin
62 62 elif self.allow_origin_pat:
63 63 allow = bool(self.allow_origin_pat.match(origin))
64 64 else:
65 65 # No CORS headers deny the request
66 66 allow = False
67 67 if not allow:
68 68 self.log.warn("Blocking Cross Origin WebSocket Attempt. Origin: %s, Host: %s",
69 69 origin, host,
70 70 )
71 71 return allow
72 72
73 73 def clear_cookie(self, *args, **kwargs):
74 74 """meaningless for websockets"""
75 75 pass
76 76
77 77 def _reserialize_reply(self, msg_list):
78 78 """Reserialize a reply message using JSON.
79 79
80 80 This takes the msg list from the ZMQ socket, unserializes it using
81 81 self.session and then serializes the result using JSON. This method
82 82 should be used by self._on_zmq_reply to build messages that can
83 83 be sent back to the browser.
84 84 """
85 85 idents, msg_list = self.session.feed_identities(msg_list)
86 86 msg = self.session.unserialize(msg_list)
87 87 try:
88 88 msg['header'].pop('date')
89 89 except KeyError:
90 90 pass
91 91 try:
92 92 msg['parent_header'].pop('date')
93 93 except KeyError:
94 94 pass
95 95 msg.pop('buffers')
96 96 return json.dumps(msg, default=date_default)
97 97
98 98 def _on_zmq_reply(self, msg_list):
99 99 # Sometimes this gets triggered when the on_close method is scheduled in the
100 100 # eventloop but hasn't been called.
101 101 if self.stream.closed(): return
102 102 try:
103 103 msg = self._reserialize_reply(msg_list)
104 104 except Exception:
105 105 self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
106 106 else:
107 107 self.write_message(msg)
108 108
109 109 def allow_draft76(self):
110 110 """Allow draft 76, until browsers such as Safari update to RFC 6455.
111 111
112 112 This has been disabled by default in tornado in release 2.2.0, and
113 113 support will be removed in later versions.
114 114 """
115 115 return True
116 116
117 117 # ping interval for keeping websockets alive (30 seconds)
118 118 WS_PING_INTERVAL = 30000
119 119
120 120 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
121 121 ping_callback = None
122 122 last_ping = 0
123 123 last_pong = 0
124 124
125 125 @property
126 126 def ping_interval(self):
127 127 """The interval for websocket keep-alive pings.
128 128
129 129 Set ws_ping_interval = 0 to disable pings.
130 130 """
131 131 return self.settings.get('ws_ping_interval', WS_PING_INTERVAL)
132 132
133 133 @property
134 134 def ping_timeout(self):
135 135 """If no ping is received in this many milliseconds,
136 136 close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
137 137 Default is max of 3 pings or 30 seconds.
138 138 """
139 139 return self.settings.get('ws_ping_timeout',
140 140 max(3 * self.ping_interval, WS_PING_INTERVAL)
141 141 )
142 142
143 143 def set_default_headers(self):
144 144 """Undo the set_default_headers in IPythonHandler
145 145
146 146 which doesn't make sense for websockets
147 147 """
148 148 pass
149
150 def open(self, kernel_id):
151 self.kernel_id = cast_unicode(kernel_id, 'ascii')
149
150 def get(self, *args, **kwargs):
152 151 # Check to see that origin matches host directly, including ports
153 152 # Tornado 4 already does CORS checking
154 153 if tornado.version_info[0] < 4:
155 154 if not self.check_origin(self.get_origin()):
156 155 raise web.HTTPError(403)
157
156
157 # authenticate the request before opening the websocket
158 if self.get_current_user() is None:
159 self.log.warn("Couldn't authenticate WebSocket connection")
160 raise web.HTTPError(403)
161
162 if self.get_argument('session_id'):
163 self.session.session = cast_unicode(self.get_argument('session_id'))
164 else:
165 self.log.warn("No session ID specified")
166
167 return super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
168
169 def initialize(self):
158 170 self.session = Session(config=self.config)
159 self.save_on_message = self.on_message
160 self.on_message = self.on_first_message
171
172 def open(self, kernel_id):
173 self.kernel_id = cast_unicode(kernel_id, 'ascii')
161 174
162 175 # start the pinging
163 176 if self.ping_interval > 0:
164 177 self.last_ping = ioloop.IOLoop.instance().time() # Remember time of last ping
165 178 self.last_pong = self.last_ping
166 179 self.ping_callback = ioloop.PeriodicCallback(self.send_ping, self.ping_interval)
167 180 self.ping_callback.start()
168 181
169 182 def send_ping(self):
170 183 """send a ping to keep the websocket alive"""
171 184 if self.stream.closed() and self.ping_callback is not None:
172 185 self.ping_callback.stop()
173 186 return
174 187
175 188 # check for timeout on pong. Make sure that we really have sent a recent ping in
176 189 # case the machine with both server and client has been suspended since the last ping.
177 190 now = ioloop.IOLoop.instance().time()
178 191 since_last_pong = 1e3 * (now - self.last_pong)
179 192 since_last_ping = 1e3 * (now - self.last_ping)
180 193 if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout:
181 194 self.log.warn("WebSocket ping timeout after %i ms.", since_last_pong)
182 195 self.close()
183 196 return
184 197
185 198 self.ping(b'')
186 199 self.last_ping = now
187 200
188 201 def on_pong(self, data):
189 202 self.last_pong = ioloop.IOLoop.instance().time()
190
191 def _inject_cookie_message(self, msg):
192 """Inject the first message, which is the document cookie,
193 for authentication."""
194 if not PY3 and isinstance(msg, unicode):
195 # Cookie constructor doesn't accept unicode strings
196 # under Python 2.x for some reason
197 msg = msg.encode('utf8', 'replace')
198 try:
199 identity, msg = msg.split(':', 1)
200 self.session.session = cast_unicode(identity, 'ascii')
201 except Exception:
202 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
203
204 try:
205 self.request._cookies = SimpleCookie(msg)
206 except:
207 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
208
209 def on_first_message(self, msg):
210 self._inject_cookie_message(msg)
211 if self.get_current_user() is None:
212 self.log.warn("Couldn't authenticate WebSocket connection")
213 raise web.HTTPError(403)
214 self.on_message = self.save_on_message
@@ -1,234 +1,230 b''
1 1 """Tornado handlers for kernels."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import json
7 7 import logging
8 8 from tornado import web
9 9
10 10 from IPython.utils.jsonutil import date_default
11 11 from IPython.utils.py3compat import string_types
12 12 from IPython.html.utils import url_path_join, url_escape
13 13
14 14 from ...base.handlers import IPythonHandler, json_errors
15 15 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
16 16
17 17 from IPython.core.release import kernel_protocol_version
18 18
19 19 class MainKernelHandler(IPythonHandler):
20 20
21 21 @web.authenticated
22 22 @json_errors
23 23 def get(self):
24 24 km = self.kernel_manager
25 25 self.finish(json.dumps(km.list_kernels()))
26 26
27 27 @web.authenticated
28 28 @json_errors
29 29 def post(self):
30 30 model = self.get_json_body()
31 31 if model is None:
32 32 raise web.HTTPError(400, "No JSON data provided")
33 33 try:
34 34 name = model['name']
35 35 except KeyError:
36 36 raise web.HTTPError(400, "Missing field in JSON data: name")
37 37
38 38 km = self.kernel_manager
39 39 kernel_id = km.start_kernel(kernel_name=name)
40 40 model = km.kernel_model(kernel_id)
41 41 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
42 42 self.set_header('Location', url_escape(location))
43 43 self.set_status(201)
44 44 self.finish(json.dumps(model))
45 45
46 46
47 47 class KernelHandler(IPythonHandler):
48 48
49 49 SUPPORTED_METHODS = ('DELETE', 'GET')
50 50
51 51 @web.authenticated
52 52 @json_errors
53 53 def get(self, kernel_id):
54 54 km = self.kernel_manager
55 55 km._check_kernel_id(kernel_id)
56 56 model = km.kernel_model(kernel_id)
57 57 self.finish(json.dumps(model))
58 58
59 59 @web.authenticated
60 60 @json_errors
61 61 def delete(self, kernel_id):
62 62 km = self.kernel_manager
63 63 km.shutdown_kernel(kernel_id)
64 64 self.set_status(204)
65 65 self.finish()
66 66
67 67
68 68 class KernelActionHandler(IPythonHandler):
69 69
70 70 @web.authenticated
71 71 @json_errors
72 72 def post(self, kernel_id, action):
73 73 km = self.kernel_manager
74 74 if action == 'interrupt':
75 75 km.interrupt_kernel(kernel_id)
76 76 self.set_status(204)
77 77 if action == 'restart':
78 78 km.restart_kernel(kernel_id)
79 79 model = km.kernel_model(kernel_id)
80 80 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
81 81 self.write(json.dumps(model))
82 82 self.finish()
83 83
84 84
85 85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
86 86
87 87 def __repr__(self):
88 88 return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
89 89
90 90 def create_stream(self):
91 91 km = self.kernel_manager
92 92 meth = getattr(km, 'connect_%s' % self.channel)
93 93 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
94 94 # Create a kernel_info channel to query the kernel protocol version.
95 95 # This channel will be closed after the kernel_info reply is received.
96 96 self.kernel_info_channel = None
97 97 self.kernel_info_channel = km.connect_shell(self.kernel_id)
98 98 self.kernel_info_channel.on_recv(self._handle_kernel_info_reply)
99 99 self._request_kernel_info()
100 100
101 101 def _request_kernel_info(self):
102 102 """send a request for kernel_info"""
103 103 self.log.debug("requesting kernel info")
104 104 self.session.send(self.kernel_info_channel, "kernel_info_request")
105 105
106 106 def _handle_kernel_info_reply(self, msg):
107 107 """process the kernel_info_reply
108 108
109 109 enabling msg spec adaptation, if necessary
110 110 """
111 111 idents,msg = self.session.feed_identities(msg)
112 112 try:
113 113 msg = self.session.unserialize(msg)
114 114 except:
115 115 self.log.error("Bad kernel_info reply", exc_info=True)
116 116 self._request_kernel_info()
117 117 return
118 118 else:
119 119 if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in msg['content']:
120 120 self.log.error("Kernel info request failed, assuming current %s", msg['content'])
121 121 else:
122 122 protocol_version = msg['content']['protocol_version']
123 123 if protocol_version != kernel_protocol_version:
124 124 self.session.adapt_version = int(protocol_version.split('.')[0])
125 125 self.log.info("adapting kernel to %s" % protocol_version)
126 126 self.kernel_info_channel.close()
127 127 self.kernel_info_channel = None
128 128
129
130 def initialize(self, *args, **kwargs):
129 def initialize(self):
130 super(ZMQChannelHandler, self).initialize()
131 131 self.zmq_stream = None
132 132
133 def on_first_message(self, msg):
134 try:
135 super(ZMQChannelHandler, self).on_first_message(msg)
136 except web.HTTPError:
137 self.close()
138 return
133 def open(self, kernel_id):
134 super(ZMQChannelHandler, self).open(kernel_id)
139 135 try:
140 136 self.create_stream()
141 137 except web.HTTPError:
142 138 # WebSockets don't response to traditional error codes so we
143 139 # close the connection.
144 140 if not self.stream.closed():
145 141 self.stream.close()
146 142 self.close()
147 143 else:
148 144 self.zmq_stream.on_recv(self._on_zmq_reply)
149 145
150 146 def on_message(self, msg):
151 147 if self.zmq_stream is None:
152 148 return
153 149 elif self.zmq_stream.closed():
154 150 self.log.info("%s closed, closing websocket.", self)
155 151 self.close()
156 152 return
157 153 msg = json.loads(msg)
158 154 self.session.send(self.zmq_stream, msg)
159 155
160 156 def on_close(self):
161 157 # This method can be called twice, once by self.kernel_died and once
162 158 # from the WebSocket close event. If the WebSocket connection is
163 159 # closed before the ZMQ streams are setup, they could be None.
164 160 if self.zmq_stream is not None and not self.zmq_stream.closed():
165 161 self.zmq_stream.on_recv(None)
166 162 # close the socket directly, don't wait for the stream
167 163 socket = self.zmq_stream.socket
168 164 self.zmq_stream.close()
169 165 socket.close()
170 166
171 167
172 168 class IOPubHandler(ZMQChannelHandler):
173 169 channel = 'iopub'
174 170
175 171 def create_stream(self):
176 172 super(IOPubHandler, self).create_stream()
177 173 km = self.kernel_manager
178 174 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
179 175 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
180 176
181 177 def on_close(self):
182 178 km = self.kernel_manager
183 179 if self.kernel_id in km:
184 180 km.remove_restart_callback(
185 181 self.kernel_id, self.on_kernel_restarted,
186 182 )
187 183 km.remove_restart_callback(
188 184 self.kernel_id, self.on_restart_failed, 'dead',
189 185 )
190 186 super(IOPubHandler, self).on_close()
191 187
192 188 def _send_status_message(self, status):
193 189 msg = self.session.msg("status",
194 190 {'execution_state': status}
195 191 )
196 192 self.write_message(json.dumps(msg, default=date_default))
197 193
198 194 def on_kernel_restarted(self):
199 195 logging.warn("kernel %s restarted", self.kernel_id)
200 196 self._send_status_message('restarting')
201 197
202 198 def on_restart_failed(self):
203 199 logging.error("kernel %s restarted failed!", self.kernel_id)
204 200 self._send_status_message('dead')
205 201
206 202 def on_message(self, msg):
207 203 """IOPub messages make no sense"""
208 204 pass
209 205
210 206
211 207 class ShellHandler(ZMQChannelHandler):
212 208 channel = 'shell'
213 209
214 210
215 211 class StdinHandler(ZMQChannelHandler):
216 212 channel = 'stdin'
217 213
218 214
219 215 #-----------------------------------------------------------------------------
220 216 # URL to handler mappings
221 217 #-----------------------------------------------------------------------------
222 218
223 219
224 220 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
225 221 _kernel_action_regex = r"(?P<action>restart|interrupt)"
226 222
227 223 default_handlers = [
228 224 (r"/api/kernels", MainKernelHandler),
229 225 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
230 226 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
231 227 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
232 228 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
233 229 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
234 230 ]
@@ -1,1017 +1,1014 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'services/kernels/js/comm',
9 9 'widgets/js/init',
10 10 ], function(IPython, $, utils, comm, widgetmanager) {
11 11 "use strict";
12 12
13 13 /**
14 14 * A Kernel class to communicate with the Python kernel. This
15 15 * should generally not be constructed directly, but be created
16 16 * by. the `Session` object. Once created, this object should be
17 17 * used to communicate with the kernel.
18 18 *
19 19 * @class Kernel
20 20 * @param {string} kernel_service_url - the URL to access the kernel REST api
21 21 * @param {string} ws_url - the websockets URL
22 22 * @param {Notebook} notebook - notebook object
23 23 * @param {string} name - the kernel type (e.g. python3)
24 24 */
25 25 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
26 26 this.events = notebook.events;
27 27
28 28 this.id = null;
29 29 this.name = name;
30 30
31 31 this.channels = {
32 32 'shell': null,
33 33 'iopub': null,
34 34 'stdin': null
35 35 };
36 36
37 37 this.kernel_service_url = kernel_service_url;
38 38 this.kernel_url = null;
39 39 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
40 40 if (!this.ws_url) {
41 41 // trailing 's' in https will become wss for secure web sockets
42 42 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
43 43 }
44 44
45 45 this.username = "username";
46 46 this.session_id = utils.uuid();
47 47 this._msg_callbacks = {};
48 48
49 49 if (typeof(WebSocket) !== 'undefined') {
50 50 this.WebSocket = WebSocket;
51 51 } else if (typeof(MozWebSocket) !== 'undefined') {
52 52 this.WebSocket = MozWebSocket;
53 53 } else {
54 54 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.');
55 55 }
56 56
57 57 this.bind_events();
58 58 this.init_iopub_handlers();
59 59 this.comm_manager = new comm.CommManager(this);
60 60 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
61 61
62 62 this.last_msg_id = null;
63 63 this.last_msg_callbacks = {};
64 64
65 65 this._autorestart_attempt = 0;
66 66 this._reconnect_attempt = 0;
67 67 };
68 68
69 69 /**
70 70 * @function _get_msg
71 71 */
72 72 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
73 73 var msg = {
74 74 header : {
75 75 msg_id : utils.uuid(),
76 76 username : this.username,
77 77 session : this.session_id,
78 78 msg_type : msg_type,
79 79 version : "5.0"
80 80 },
81 81 metadata : metadata || {},
82 82 content : content,
83 83 parent_header : {}
84 84 };
85 85 return msg;
86 86 };
87 87
88 88 /**
89 89 * @function bind_events
90 90 */
91 91 Kernel.prototype.bind_events = function () {
92 92 var that = this;
93 93 this.events.on('send_input_reply.Kernel', function(evt, data) {
94 94 that.send_input_reply(data);
95 95 });
96 96
97 97 var record_status = function (evt, info) {
98 98 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
99 99 };
100 100
101 101 this.events.on('kernel_created.Kernel', record_status);
102 102 this.events.on('kernel_reconnecting.Kernel', record_status);
103 103 this.events.on('kernel_connected.Kernel', record_status);
104 104 this.events.on('kernel_starting.Kernel', record_status);
105 105 this.events.on('kernel_restarting.Kernel', record_status);
106 106 this.events.on('kernel_autorestarting.Kernel', record_status);
107 107 this.events.on('kernel_interrupting.Kernel', record_status);
108 108 this.events.on('kernel_disconnected.Kernel', record_status);
109 109 // these are commented out because they are triggered a lot, but can
110 110 // be uncommented for debugging purposes
111 111 //this.events.on('kernel_idle.Kernel', record_status);
112 112 //this.events.on('kernel_busy.Kernel', record_status);
113 113 this.events.on('kernel_ready.Kernel', record_status);
114 114 this.events.on('kernel_killed.Kernel', record_status);
115 115 this.events.on('kernel_dead.Kernel', record_status);
116 116
117 117 this.events.on('kernel_ready.Kernel', function () {
118 118 that._autorestart_attempt = 0;
119 119 });
120 120 this.events.on('kernel_connected.Kernel', function () {
121 121 that._reconnect_attempt = 0;
122 122 });
123 123 };
124 124
125 125 /**
126 126 * Initialize the iopub handlers.
127 127 *
128 128 * @function init_iopub_handlers
129 129 */
130 130 Kernel.prototype.init_iopub_handlers = function () {
131 131 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
132 132 this._iopub_handlers = {};
133 133 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
134 134 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
135 135
136 136 for (var i=0; i < output_msg_types.length; i++) {
137 137 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
138 138 }
139 139 };
140 140
141 141 /**
142 142 * GET /api/kernels
143 143 *
144 144 * Get the list of running kernels.
145 145 *
146 146 * @function list
147 147 * @param {function} [success] - function executed on ajax success
148 148 * @param {function} [error] - functon executed on ajax error
149 149 */
150 150 Kernel.prototype.list = function (success, error) {
151 151 $.ajax(this.kernel_service_url, {
152 152 processData: false,
153 153 cache: false,
154 154 type: "GET",
155 155 dataType: "json",
156 156 success: success,
157 157 error: this._on_error(error)
158 158 });
159 159 };
160 160
161 161 /**
162 162 * POST /api/kernels
163 163 *
164 164 * Start a new kernel.
165 165 *
166 166 * In general this shouldn't be used -- the kernel should be
167 167 * started through the session API. If you use this function and
168 168 * are also using the session API then your session and kernel
169 169 * WILL be out of sync!
170 170 *
171 171 * @function start
172 172 * @param {params} [Object] - parameters to include in the query string
173 173 * @param {function} [success] - function executed on ajax success
174 174 * @param {function} [error] - functon executed on ajax error
175 175 */
176 176 Kernel.prototype.start = function (params, success, error) {
177 177 var url = this.kernel_service_url;
178 178 var qs = $.param(params || {}); // query string for sage math stuff
179 179 if (qs !== "") {
180 180 url = url + "?" + qs;
181 181 }
182 182
183 183 var that = this;
184 184 var on_success = function (data, status, xhr) {
185 185 that.events.trigger('kernel_created.Kernel', {kernel: that});
186 186 that._kernel_created(data);
187 187 if (success) {
188 188 success(data, status, xhr);
189 189 }
190 190 };
191 191
192 192 $.ajax(url, {
193 193 processData: false,
194 194 cache: false,
195 195 type: "POST",
196 196 data: JSON.stringify({name: this.name}),
197 197 dataType: "json",
198 198 success: this._on_success(on_success),
199 199 error: this._on_error(error)
200 200 });
201 201
202 202 return url;
203 203 };
204 204
205 205 /**
206 206 * GET /api/kernels/[:kernel_id]
207 207 *
208 208 * Get information about the kernel.
209 209 *
210 210 * @function get_info
211 211 * @param {function} [success] - function executed on ajax success
212 212 * @param {function} [error] - functon executed on ajax error
213 213 */
214 214 Kernel.prototype.get_info = function (success, error) {
215 215 $.ajax(this.kernel_url, {
216 216 processData: false,
217 217 cache: false,
218 218 type: "GET",
219 219 dataType: "json",
220 220 success: this._on_success(success),
221 221 error: this._on_error(error)
222 222 });
223 223 };
224 224
225 225 /**
226 226 * DELETE /api/kernels/[:kernel_id]
227 227 *
228 228 * Shutdown the kernel.
229 229 *
230 230 * If you are also using sessions, then this function shoul NOT be
231 231 * used. Instead, use Session.delete. Otherwise, the session and
232 232 * kernel WILL be out of sync.
233 233 *
234 234 * @function kill
235 235 * @param {function} [success] - function executed on ajax success
236 236 * @param {function} [error] - functon executed on ajax error
237 237 */
238 238 Kernel.prototype.kill = function (success, error) {
239 239 this.events.trigger('kernel_killed.Kernel', {kernel: this});
240 240 this._kernel_dead();
241 241 $.ajax(this.kernel_url, {
242 242 processData: false,
243 243 cache: false,
244 244 type: "DELETE",
245 245 dataType: "json",
246 246 success: this._on_success(success),
247 247 error: this._on_error(error)
248 248 });
249 249 };
250 250
251 251 /**
252 252 * POST /api/kernels/[:kernel_id]/interrupt
253 253 *
254 254 * Interrupt the kernel.
255 255 *
256 256 * @function interrupt
257 257 * @param {function} [success] - function executed on ajax success
258 258 * @param {function} [error] - functon executed on ajax error
259 259 */
260 260 Kernel.prototype.interrupt = function (success, error) {
261 261 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
262 262
263 263 var that = this;
264 264 var on_success = function (data, status, xhr) {
265 265 // get kernel info so we know what state the kernel is in
266 266 that.kernel_info();
267 267 if (success) {
268 268 success(data, status, xhr);
269 269 }
270 270 };
271 271
272 272 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
273 273 $.ajax(url, {
274 274 processData: false,
275 275 cache: false,
276 276 type: "POST",
277 277 dataType: "json",
278 278 success: this._on_success(on_success),
279 279 error: this._on_error(error)
280 280 });
281 281 };
282 282
283 283 /**
284 284 * POST /api/kernels/[:kernel_id]/restart
285 285 *
286 286 * Restart the kernel.
287 287 *
288 288 * @function interrupt
289 289 * @param {function} [success] - function executed on ajax success
290 290 * @param {function} [error] - functon executed on ajax error
291 291 */
292 292 Kernel.prototype.restart = function (success, error) {
293 293 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
294 294 this.stop_channels();
295 295
296 296 var that = this;
297 297 var on_success = function (data, status, xhr) {
298 298 that.events.trigger('kernel_created.Kernel', {kernel: that});
299 299 that._kernel_created(data);
300 300 if (success) {
301 301 success(data, status, xhr);
302 302 }
303 303 };
304 304
305 305 var on_error = function (xhr, status, err) {
306 306 that.events.trigger('kernel_dead.Kernel', {kernel: that});
307 307 that._kernel_dead();
308 308 if (error) {
309 309 error(xhr, status, err);
310 310 }
311 311 };
312 312
313 313 var url = utils.url_join_encode(this.kernel_url, 'restart');
314 314 $.ajax(url, {
315 315 processData: false,
316 316 cache: false,
317 317 type: "POST",
318 318 dataType: "json",
319 319 success: this._on_success(on_success),
320 320 error: this._on_error(on_error)
321 321 });
322 322 };
323 323
324 324 /**
325 325 * Reconnect to a disconnected kernel. This is not actually a
326 326 * standard HTTP request, but useful function nonetheless for
327 327 * reconnecting to the kernel if the connection is somehow lost.
328 328 *
329 329 * @function reconnect
330 330 */
331 331 Kernel.prototype.reconnect = function () {
332 332 this.events.trigger('kernel_reconnecting.Kernel', {kernel: this});
333 333 setTimeout($.proxy(this.start_channels, this), 3000);
334 334 };
335 335
336 336 /**
337 337 * Handle a successful AJAX request by updating the kernel id and
338 338 * name from the response, and then optionally calling a provided
339 339 * callback.
340 340 *
341 341 * @function _on_success
342 342 * @param {function} success - callback
343 343 */
344 344 Kernel.prototype._on_success = function (success) {
345 345 var that = this;
346 346 return function (data, status, xhr) {
347 347 if (data) {
348 348 that.id = data.id;
349 349 that.name = data.name;
350 350 }
351 351 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
352 352 if (success) {
353 353 success(data, status, xhr);
354 354 }
355 355 };
356 356 };
357 357
358 358 /**
359 359 * Handle a failed AJAX request by logging the error message, and
360 360 * then optionally calling a provided callback.
361 361 *
362 362 * @function _on_error
363 363 * @param {function} error - callback
364 364 */
365 365 Kernel.prototype._on_error = function (error) {
366 366 return function (xhr, status, err) {
367 367 utils.log_ajax_error(xhr, status, err);
368 368 if (error) {
369 369 error(xhr, status, err);
370 370 }
371 371 };
372 372 };
373 373
374 374 /**
375 375 * Perform necessary tasks once the kernel has been started,
376 376 * including actually connecting to the kernel.
377 377 *
378 378 * @function _kernel_created
379 379 * @param {Object} data - information about the kernel including id
380 380 */
381 381 Kernel.prototype._kernel_created = function (data) {
382 382 this.id = data.id;
383 383 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
384 384 this.start_channels();
385 385 };
386 386
387 387 /**
388 388 * Perform necessary tasks once the connection to the kernel has
389 389 * been established. This includes requesting information about
390 390 * the kernel.
391 391 *
392 392 * @function _kernel_connected
393 393 */
394 394 Kernel.prototype._kernel_connected = function () {
395 395 this.events.trigger('kernel_connected.Kernel', {kernel: this});
396 396 this.events.trigger('kernel_starting.Kernel', {kernel: this});
397 397 // get kernel info so we know what state the kernel is in
398 398 var that = this;
399 399 this.kernel_info(function () {
400 400 that.events.trigger('kernel_ready.Kernel', {kernel: that});
401 401 });
402 402 };
403 403
404 404 /**
405 405 * Perform necessary tasks after the kernel has died. This closing
406 406 * communication channels to the kernel if they are still somehow
407 407 * open.
408 408 *
409 409 * @function _kernel_dead
410 410 */
411 411 Kernel.prototype._kernel_dead = function () {
412 412 this.stop_channels();
413 413 };
414 414
415 415 /**
416 416 * Start the `shell`and `iopub` channels.
417 417 * Will stop and restart them if they already exist.
418 418 *
419 419 * @function start_channels
420 420 */
421 421 Kernel.prototype.start_channels = function () {
422 422 var that = this;
423 423 this.stop_channels();
424 424 var ws_host_url = this.ws_url + this.kernel_url;
425 425
426 426 console.log("Starting WebSockets:", ws_host_url);
427
428 this.channels.shell = new this.WebSocket(
429 this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
430 );
431 this.channels.stdin = new this.WebSocket(
432 this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
433 );
434 this.channels.iopub = new this.WebSocket(
435 this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
436 );
427
428 var channel_url = function(channel) {
429 return [
430 that.ws_url,
431 utils.url_join_encode(that.kernel_url, channel),
432 "?session_id=" + that.session_id
433 ].join('');
434 };
435 this.channels.shell = new this.WebSocket(channel_url("shell"));
436 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
437 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
437 438
438 439 var already_called_onclose = false; // only alert once
439 440 var ws_closed_early = function(evt){
440 441 if (already_called_onclose){
441 442 return;
442 443 }
443 444 already_called_onclose = true;
444 445 if ( ! evt.wasClean ){
445 446 // If the websocket was closed early, that could mean
446 447 // that the kernel is actually dead. Try getting
447 448 // information about the kernel from the API call --
448 449 // if that fails, then assume the kernel is dead,
449 450 // otherwise just follow the typical websocket closed
450 451 // protocol.
451 452 that.get_info(function () {
452 453 that._ws_closed(ws_host_url, false);
453 454 }, function () {
454 455 that.events.trigger('kernel_dead.Kernel', {kernel: that});
455 456 that._kernel_dead();
456 457 });
457 458 }
458 459 };
459 460 var ws_closed_late = function(evt){
460 461 if (already_called_onclose){
461 462 return;
462 463 }
463 464 already_called_onclose = true;
464 465 if ( ! evt.wasClean ){
465 466 that._ws_closed(ws_host_url, false);
466 467 }
467 468 };
468 469 var ws_error = function(evt){
469 470 if (already_called_onclose){
470 471 return;
471 472 }
472 473 already_called_onclose = true;
473 474 that._ws_closed(ws_host_url, true);
474 475 };
475 476
476 477 for (var c in this.channels) {
477 478 this.channels[c].onopen = $.proxy(this._ws_opened, this);
478 479 this.channels[c].onclose = ws_closed_early;
479 480 this.channels[c].onerror = ws_error;
480 481 }
481 482 // switch from early-close to late-close message after 1s
482 483 setTimeout(function() {
483 484 for (var c in that.channels) {
484 485 if (that.channels[c] !== null) {
485 486 that.channels[c].onclose = ws_closed_late;
486 487 }
487 488 }
488 489 }, 1000);
489 490 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
490 491 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
491 492 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
492 493 };
493 494
494 495 /**
495 * Handle a websocket entering the open state sends session and
496 * cookie authentication info as first message.
496 * Handle a websocket entering the open state,
497 * signaling that the kernel is connected when all channels are open.
497 498 *
498 499 * @function _ws_opened
499 500 */
500 501 Kernel.prototype._ws_opened = function (evt) {
501 // send the session id so the Session object Python-side
502 // has the same identity
503 evt.target.send(this.session_id + ':' + document.cookie);
504
505 502 if (this.is_connected()) {
506 503 // all events ready, trigger started event.
507 504 this._kernel_connected();
508 505 }
509 506 };
510 507
511 508 /**
512 509 * Handle a websocket entering the closed state. This closes the
513 510 * other communication channels if they are open. If the websocket
514 511 * was not closed due to an error, try to reconnect to the kernel.
515 512 *
516 513 * @function _ws_closed
517 514 * @param {string} ws_url - the websocket url
518 515 * @param {bool} error - whether the connection was closed due to an error
519 516 */
520 517 Kernel.prototype._ws_closed = function(ws_url, error) {
521 518 this.stop_channels();
522 519
523 520 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
524 521 if (error) {
525 522 console.log('WebSocket connection failed: ', ws_url);
526 523 this._reconnect_attempt = this._reconnect_attempt + 1;
527 524 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
528 525 }
529 526 this.reconnect();
530 527 };
531 528
532 529 /**
533 530 * Close the websocket channels. After successful close, the value
534 531 * in `this.channels[channel_name]` will be null.
535 532 *
536 533 * @function stop_channels
537 534 */
538 535 Kernel.prototype.stop_channels = function () {
539 536 var that = this;
540 537 var close = function (c) {
541 538 return function () {
542 539 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
543 540 that.channels[c] = null;
544 541 }
545 542 };
546 543 };
547 544 for (var c in this.channels) {
548 545 if ( this.channels[c] !== null ) {
549 546 if (this.channels[c].readyState === WebSocket.OPEN) {
550 547 this.channels[c].onclose = close(c);
551 548 this.channels[c].close();
552 549 } else {
553 550 close(c)();
554 551 }
555 552 }
556 553 }
557 554 };
558 555
559 556 /**
560 557 * Check whether there is a connection to the kernel. This
561 558 * function only returns true if all channel objects have been
562 559 * created and have a state of WebSocket.OPEN.
563 560 *
564 561 * @function is_connected
565 562 * @returns {bool} - whether there is a connection
566 563 */
567 564 Kernel.prototype.is_connected = function () {
568 565 for (var c in this.channels) {
569 566 // if any channel is not ready, then we're not connected
570 567 if (this.channels[c] === null) {
571 568 return false;
572 569 }
573 570 if (this.channels[c].readyState !== WebSocket.OPEN) {
574 571 return false;
575 572 }
576 573 }
577 574 return true;
578 575 };
579 576
580 577 /**
581 578 * Check whether the connection to the kernel has been completely
582 579 * severed. This function only returns true if all channel objects
583 580 * are null.
584 581 *
585 582 * @function is_fully_disconnected
586 583 * @returns {bool} - whether the kernel is fully disconnected
587 584 */
588 585 Kernel.prototype.is_fully_disconnected = function () {
589 586 for (var c in this.channels) {
590 587 if (this.channels[c] === null) {
591 588 return true;
592 589 }
593 590 }
594 591 return false;
595 592 };
596 593
597 594 /**
598 595 * Send a message on the Kernel's shell channel
599 596 *
600 597 * @function send_shell_message
601 598 */
602 599 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
603 600 if (!this.is_connected()) {
604 601 throw new Error("kernel is not connected");
605 602 }
606 603 var msg = this._get_msg(msg_type, content, metadata);
607 604 this.channels.shell.send(JSON.stringify(msg));
608 605 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
609 606 return msg.header.msg_id;
610 607 };
611 608
612 609 /**
613 610 * Get kernel info
614 611 *
615 612 * @function kernel_info
616 613 * @param callback {function}
617 614 *
618 615 * When calling this method, pass a callback function that expects one argument.
619 616 * The callback will be passed the complete `kernel_info_reply` message documented
620 617 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
621 618 */
622 619 Kernel.prototype.kernel_info = function (callback) {
623 620 var callbacks;
624 621 if (callback) {
625 622 callbacks = { shell : { reply : callback } };
626 623 }
627 624 return this.send_shell_message("kernel_info_request", {}, callbacks);
628 625 };
629 626
630 627 /**
631 628 * Get info on an object
632 629 *
633 630 * When calling this method, pass a callback function that expects one argument.
634 631 * The callback will be passed the complete `inspect_reply` message documented
635 632 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
636 633 *
637 634 * @function inspect
638 635 * @param code {string}
639 636 * @param cursor_pos {integer}
640 637 * @param callback {function}
641 638 */
642 639 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
643 640 var callbacks;
644 641 if (callback) {
645 642 callbacks = { shell : { reply : callback } };
646 643 }
647 644
648 645 var content = {
649 646 code : code,
650 647 cursor_pos : cursor_pos,
651 648 detail_level : 0
652 649 };
653 650 return this.send_shell_message("inspect_request", content, callbacks);
654 651 };
655 652
656 653 /**
657 654 * Execute given code into kernel, and pass result to callback.
658 655 *
659 656 * @async
660 657 * @function execute
661 658 * @param {string} code
662 659 * @param [callbacks] {Object} With the following keys (all optional)
663 660 * @param callbacks.shell.reply {function}
664 661 * @param callbacks.shell.payload.[payload_name] {function}
665 662 * @param callbacks.iopub.output {function}
666 663 * @param callbacks.iopub.clear_output {function}
667 664 * @param callbacks.input {function}
668 665 * @param {object} [options]
669 666 * @param [options.silent=false] {Boolean}
670 667 * @param [options.user_expressions=empty_dict] {Dict}
671 668 * @param [options.allow_stdin=false] {Boolean} true|false
672 669 *
673 670 * @example
674 671 *
675 672 * The options object should contain the options for the execute
676 673 * call. Its default values are:
677 674 *
678 675 * options = {
679 676 * silent : true,
680 677 * user_expressions : {},
681 678 * allow_stdin : false
682 679 * }
683 680 *
684 681 * When calling this method pass a callbacks structure of the
685 682 * form:
686 683 *
687 684 * callbacks = {
688 685 * shell : {
689 686 * reply : execute_reply_callback,
690 687 * payload : {
691 688 * set_next_input : set_next_input_callback,
692 689 * }
693 690 * },
694 691 * iopub : {
695 692 * output : output_callback,
696 693 * clear_output : clear_output_callback,
697 694 * },
698 695 * input : raw_input_callback
699 696 * }
700 697 *
701 698 * Each callback will be passed the entire message as a single
702 699 * arugment. Payload handlers will be passed the corresponding
703 700 * payload and the execute_reply message.
704 701 */
705 702 Kernel.prototype.execute = function (code, callbacks, options) {
706 703 var content = {
707 704 code : code,
708 705 silent : true,
709 706 store_history : false,
710 707 user_expressions : {},
711 708 allow_stdin : false
712 709 };
713 710 callbacks = callbacks || {};
714 711 if (callbacks.input !== undefined) {
715 712 content.allow_stdin = true;
716 713 }
717 714 $.extend(true, content, options);
718 715 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
719 716 return this.send_shell_message("execute_request", content, callbacks);
720 717 };
721 718
722 719 /**
723 720 * When calling this method, pass a function to be called with the
724 721 * `complete_reply` message as its only argument when it arrives.
725 722 *
726 723 * `complete_reply` is documented
727 724 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
728 725 *
729 726 * @function complete
730 727 * @param code {string}
731 728 * @param cursor_pos {integer}
732 729 * @param callback {function}
733 730 */
734 731 Kernel.prototype.complete = function (code, cursor_pos, callback) {
735 732 var callbacks;
736 733 if (callback) {
737 734 callbacks = { shell : { reply : callback } };
738 735 }
739 736 var content = {
740 737 code : code,
741 738 cursor_pos : cursor_pos
742 739 };
743 740 return this.send_shell_message("complete_request", content, callbacks);
744 741 };
745 742
746 743 /**
747 744 * @function send_input_reply
748 745 */
749 746 Kernel.prototype.send_input_reply = function (input) {
750 747 if (!this.is_connected()) {
751 748 throw new Error("kernel is not connected");
752 749 }
753 750 var content = {
754 751 value : input
755 752 };
756 753 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
757 754 var msg = this._get_msg("input_reply", content);
758 755 this.channels.stdin.send(JSON.stringify(msg));
759 756 return msg.header.msg_id;
760 757 };
761 758
762 759 /**
763 760 * @function register_iopub_handler
764 761 */
765 762 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
766 763 this._iopub_handlers[msg_type] = callback;
767 764 };
768 765
769 766 /**
770 767 * Get the iopub handler for a specific message type.
771 768 *
772 769 * @function get_iopub_handler
773 770 */
774 771 Kernel.prototype.get_iopub_handler = function (msg_type) {
775 772 return this._iopub_handlers[msg_type];
776 773 };
777 774
778 775 /**
779 776 * Get callbacks for a specific message.
780 777 *
781 778 * @function get_callbacks_for_msg
782 779 */
783 780 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
784 781 if (msg_id == this.last_msg_id) {
785 782 return this.last_msg_callbacks;
786 783 } else {
787 784 return this._msg_callbacks[msg_id];
788 785 }
789 786 };
790 787
791 788 /**
792 789 * Clear callbacks for a specific message.
793 790 *
794 791 * @function clear_callbacks_for_msg
795 792 */
796 793 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
797 794 if (this._msg_callbacks[msg_id] !== undefined ) {
798 795 delete this._msg_callbacks[msg_id];
799 796 }
800 797 };
801 798
802 799 /**
803 800 * @function _finish_shell
804 801 */
805 802 Kernel.prototype._finish_shell = function (msg_id) {
806 803 var callbacks = this._msg_callbacks[msg_id];
807 804 if (callbacks !== undefined) {
808 805 callbacks.shell_done = true;
809 806 if (callbacks.iopub_done) {
810 807 this.clear_callbacks_for_msg(msg_id);
811 808 }
812 809 }
813 810 };
814 811
815 812 /**
816 813 * @function _finish_iopub
817 814 */
818 815 Kernel.prototype._finish_iopub = function (msg_id) {
819 816 var callbacks = this._msg_callbacks[msg_id];
820 817 if (callbacks !== undefined) {
821 818 callbacks.iopub_done = true;
822 819 if (callbacks.shell_done) {
823 820 this.clear_callbacks_for_msg(msg_id);
824 821 }
825 822 }
826 823 };
827 824
828 825 /**
829 826 * Set callbacks for a particular message.
830 827 * Callbacks should be a struct of the following form:
831 828 * shell : {
832 829 *
833 830 * }
834 831 *
835 832 * @function set_callbacks_for_msg
836 833 */
837 834 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
838 835 this.last_msg_id = msg_id;
839 836 if (callbacks) {
840 837 // shallow-copy mapping, because we will modify it at the top level
841 838 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
842 839 cbcopy.shell = callbacks.shell;
843 840 cbcopy.iopub = callbacks.iopub;
844 841 cbcopy.input = callbacks.input;
845 842 cbcopy.shell_done = (!callbacks.shell);
846 843 cbcopy.iopub_done = (!callbacks.iopub);
847 844 } else {
848 845 this.last_msg_callbacks = {};
849 846 }
850 847 };
851 848
852 849 /**
853 850 * @function _handle_shell_reply
854 851 */
855 852 Kernel.prototype._handle_shell_reply = function (e) {
856 853 var reply = $.parseJSON(e.data);
857 854 this.events.trigger('shell_reply.Kernel', {kernel: this, reply: reply});
858 855 var content = reply.content;
859 856 var metadata = reply.metadata;
860 857 var parent_id = reply.parent_header.msg_id;
861 858 var callbacks = this.get_callbacks_for_msg(parent_id);
862 859 if (!callbacks || !callbacks.shell) {
863 860 return;
864 861 }
865 862 var shell_callbacks = callbacks.shell;
866 863
867 864 // signal that shell callbacks are done
868 865 this._finish_shell(parent_id);
869 866
870 867 if (shell_callbacks.reply !== undefined) {
871 868 shell_callbacks.reply(reply);
872 869 }
873 870 if (content.payload && shell_callbacks.payload) {
874 871 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
875 872 }
876 873 };
877 874
878 875 /**
879 876 * @function _handle_payloads
880 877 */
881 878 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
882 879 var l = payloads.length;
883 880 // Payloads are handled by triggering events because we don't want the Kernel
884 881 // to depend on the Notebook or Pager classes.
885 882 for (var i=0; i<l; i++) {
886 883 var payload = payloads[i];
887 884 var callback = payload_callbacks[payload.source];
888 885 if (callback) {
889 886 callback(payload, msg);
890 887 }
891 888 }
892 889 };
893 890
894 891 /**
895 892 * @function _handle_status_message
896 893 */
897 894 Kernel.prototype._handle_status_message = function (msg) {
898 895 var execution_state = msg.content.execution_state;
899 896 var parent_id = msg.parent_header.msg_id;
900 897
901 898 // dispatch status msg callbacks, if any
902 899 var callbacks = this.get_callbacks_for_msg(parent_id);
903 900 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
904 901 try {
905 902 callbacks.iopub.status(msg);
906 903 } catch (e) {
907 904 console.log("Exception in status msg handler", e, e.stack);
908 905 }
909 906 }
910 907
911 908 if (execution_state === 'busy') {
912 909 this.events.trigger('kernel_busy.Kernel', {kernel: this});
913 910
914 911 } else if (execution_state === 'idle') {
915 912 // signal that iopub callbacks are (probably) done
916 913 // async output may still arrive,
917 914 // but only for the most recent request
918 915 this._finish_iopub(parent_id);
919 916
920 917 // trigger status_idle event
921 918 this.events.trigger('kernel_idle.Kernel', {kernel: this});
922 919
923 920 } else if (execution_state === 'starting') {
924 921 this.events.trigger('kernel_starting.Kernel', {kernel: this});
925 922 var that = this;
926 923 this.kernel_info(function () {
927 924 that.events.trigger('kernel_ready.Kernel', {kernel: that});
928 925 });
929 926
930 927 } else if (execution_state === 'restarting') {
931 928 // autorestarting is distinct from restarting,
932 929 // in that it means the kernel died and the server is restarting it.
933 930 // kernel_restarting sets the notification widget,
934 931 // autorestart shows the more prominent dialog.
935 932 this._autorestart_attempt = this._autorestart_attempt + 1;
936 933 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
937 934 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
938 935
939 936 } else if (execution_state === 'dead') {
940 937 this.events.trigger('kernel_dead.Kernel', {kernel: this});
941 938 this._kernel_dead();
942 939 }
943 940 };
944 941
945 942 /**
946 943 * Handle clear_output message
947 944 *
948 945 * @function _handle_clear_output
949 946 */
950 947 Kernel.prototype._handle_clear_output = function (msg) {
951 948 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
952 949 if (!callbacks || !callbacks.iopub) {
953 950 return;
954 951 }
955 952 var callback = callbacks.iopub.clear_output;
956 953 if (callback) {
957 954 callback(msg);
958 955 }
959 956 };
960 957
961 958 /**
962 959 * handle an output message (execute_result, display_data, etc.)
963 960 *
964 961 * @function _handle_output_message
965 962 */
966 963 Kernel.prototype._handle_output_message = function (msg) {
967 964 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
968 965 if (!callbacks || !callbacks.iopub) {
969 966 return;
970 967 }
971 968 var callback = callbacks.iopub.output;
972 969 if (callback) {
973 970 callback(msg);
974 971 }
975 972 };
976 973
977 974 /**
978 975 * Dispatch IOPub messages to respective handlers. Each message
979 976 * type should have a handler.
980 977 *
981 978 * @function _handle_iopub_message
982 979 */
983 980 Kernel.prototype._handle_iopub_message = function (e) {
984 981 var msg = $.parseJSON(e.data);
985 982
986 983 var handler = this.get_iopub_handler(msg.header.msg_type);
987 984 if (handler !== undefined) {
988 985 handler(msg);
989 986 }
990 987 };
991 988
992 989 /**
993 990 * @function _handle_input_request
994 991 */
995 992 Kernel.prototype._handle_input_request = function (e) {
996 993 var request = $.parseJSON(e.data);
997 994 var header = request.header;
998 995 var content = request.content;
999 996 var metadata = request.metadata;
1000 997 var msg_type = header.msg_type;
1001 998 if (msg_type !== 'input_request') {
1002 999 console.log("Invalid input request!", request);
1003 1000 return;
1004 1001 }
1005 1002 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1006 1003 if (callbacks) {
1007 1004 if (callbacks.input) {
1008 1005 callbacks.input(request);
1009 1006 }
1010 1007 }
1011 1008 };
1012 1009
1013 1010 // Backwards compatability.
1014 1011 IPython.Kernel = Kernel;
1015 1012
1016 1013 return {'Kernel': Kernel};
1017 1014 });
General Comments 0
You need to be logged in to leave comments. Login now