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