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