##// END OF EJS Templates
WebSocket url is now passed to browser when a kernel is started.
Brian E. Granger -
Show More
@@ -1,297 +1,326 b''
1 """Tornado handlers for the notebook."""
1 """Tornado handlers for the notebook."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7
7
8 from tornado import web
8 from tornado import web
9 from tornado import websocket
9 from tornado import websocket
10
10
11 from zmq.eventloop import ioloop
11 from zmq.eventloop import ioloop
12 from zmq.utils import jsonapi
12 from zmq.utils import jsonapi
13
13
14 from IPython.zmq.session import Session
14 from IPython.zmq.session import Session
15
15
16 try:
16 try:
17 from docutils.core import publish_string
17 from docutils.core import publish_string
18 except ImportError:
18 except ImportError:
19 publish_string = None
19 publish_string = None
20
20
21
21
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Top-level handlers
24 # Top-level handlers
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class NBBrowserHandler(web.RequestHandler):
28 class NBBrowserHandler(web.RequestHandler):
29 def get(self):
29 def get(self):
30 nbm = self.application.notebook_manager
30 nbm = self.application.notebook_manager
31 project = nbm.notebook_dir
31 project = nbm.notebook_dir
32 self.render('nbbrowser.html', project=project)
32 self.render('nbbrowser.html', project=project)
33
33
34
34
35 class NewHandler(web.RequestHandler):
35 class NewHandler(web.RequestHandler):
36 def get(self):
36 def get(self):
37 notebook_id = self.application.notebook_manager.new_notebook()
37 notebook_id = self.application.notebook_manager.new_notebook()
38 self.render('notebook.html', notebook_id=notebook_id)
38 self.render('notebook.html', notebook_id=notebook_id)
39
39
40
40
41 class NamedNotebookHandler(web.RequestHandler):
41 class NamedNotebookHandler(web.RequestHandler):
42 def get(self, notebook_id):
42 def get(self, notebook_id):
43 nbm = self.application.notebook_manager
43 nbm = self.application.notebook_manager
44 if not nbm.notebook_exists(notebook_id):
44 if not nbm.notebook_exists(notebook_id):
45 raise web.HTTPError(404)
45 raise web.HTTPError(404)
46 self.render('notebook.html', notebook_id=notebook_id)
46 self.render('notebook.html', notebook_id=notebook_id)
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Kernel handlers
50 # Kernel handlers
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53
53
54 class MainKernelHandler(web.RequestHandler):
54 class MainKernelHandler(web.RequestHandler):
55
55
56 def get(self):
56 def get(self):
57 km = self.application.kernel_manager
57 km = self.application.kernel_manager
58 self.finish(jsonapi.dumps(km.kernel_ids))
58 self.finish(jsonapi.dumps(km.kernel_ids))
59
59
60 def post(self):
60 def post(self):
61 km = self.application.kernel_manager
61 km = self.application.kernel_manager
62 notebook_id = self.get_argument('notebook', default=None)
62 notebook_id = self.get_argument('notebook', default=None)
63 kernel_id = km.start_kernel(notebook_id)
63 kernel_id = km.start_kernel(notebook_id)
64 ws_url = self.application.ipython_app.get_ws_url()
65 data = {'ws_url':ws_url,'kernel_id':kernel_id}
64 self.set_header('Location', '/'+kernel_id)
66 self.set_header('Location', '/'+kernel_id)
65 self.finish(jsonapi.dumps(kernel_id))
67 self.finish(jsonapi.dumps(data))
66
68
67
69
68 class KernelHandler(web.RequestHandler):
70 class KernelHandler(web.RequestHandler):
69
71
70 SUPPORTED_METHODS = ('DELETE')
72 SUPPORTED_METHODS = ('DELETE')
71
73
72 def delete(self, kernel_id):
74 def delete(self, kernel_id):
73 km = self.application.kernel_manager
75 km = self.application.kernel_manager
74 km.kill_kernel(kernel_id)
76 km.kill_kernel(kernel_id)
75 self.set_status(204)
77 self.set_status(204)
76 self.finish()
78 self.finish()
77
79
78
80
79 class KernelActionHandler(web.RequestHandler):
81 class KernelActionHandler(web.RequestHandler):
80
82
81 def post(self, kernel_id, action):
83 def post(self, kernel_id, action):
82 km = self.application.kernel_manager
84 km = self.application.kernel_manager
83 if action == 'interrupt':
85 if action == 'interrupt':
84 km.interrupt_kernel(kernel_id)
86 km.interrupt_kernel(kernel_id)
85 self.set_status(204)
87 self.set_status(204)
86 if action == 'restart':
88 if action == 'restart':
87 new_kernel_id = km.restart_kernel(kernel_id)
89 new_kernel_id = km.restart_kernel(kernel_id)
88 self.write(jsonapi.dumps(new_kernel_id))
90 ws_url = self.application.ipython_app.get_ws_url()
91 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
92 self.set_header('Location', '/'+new_kernel_id)
93 self.write(jsonapi.dumps(data))
89 self.finish()
94 self.finish()
90
95
91
96
92 class ZMQStreamHandler(websocket.WebSocketHandler):
97 class ZMQStreamHandler(websocket.WebSocketHandler):
93
98
94 def _reserialize_reply(self, msg_list):
99 def _reserialize_reply(self, msg_list):
95 """Reserialize a reply message using JSON.
100 """Reserialize a reply message using JSON.
96
101
97 This takes the msg list from the ZMQ socket, unserializes it using
102 This takes the msg list from the ZMQ socket, unserializes it using
98 self.session and then serializes the result using JSON. This method
103 self.session and then serializes the result using JSON. This method
99 should be used by self._on_zmq_reply to build messages that can
104 should be used by self._on_zmq_reply to build messages that can
100 be sent back to the browser.
105 be sent back to the browser.
101 """
106 """
102 idents, msg_list = self.session.feed_identities(msg_list)
107 idents, msg_list = self.session.feed_identities(msg_list)
103 msg = self.session.unserialize(msg_list)
108 msg = self.session.unserialize(msg_list)
104 try:
109 try:
105 msg['header'].pop('date')
110 msg['header'].pop('date')
106 except KeyError:
111 except KeyError:
107 pass
112 pass
108 try:
113 try:
109 msg['parent_header'].pop('date')
114 msg['parent_header'].pop('date')
110 except KeyError:
115 except KeyError:
111 pass
116 pass
112 msg.pop('buffers')
117 msg.pop('buffers')
113 return jsonapi.dumps(msg)
118 return jsonapi.dumps(msg)
114
119
115 def _on_zmq_reply(self, msg_list):
120 def _on_zmq_reply(self, msg_list):
116 try:
121 try:
117 msg = self._reserialize_reply(msg_list)
122 msg = self._reserialize_reply(msg_list)
118 except:
123 except:
119 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
124 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
120 else:
125 else:
121 self.write_message(msg)
126 self.write_message(msg)
122
127
123
128
124 class IOPubHandler(ZMQStreamHandler):
129 class IOPubHandler(ZMQStreamHandler):
125
130
126 def initialize(self, *args, **kwargs):
131 def initialize(self, *args, **kwargs):
127 self._kernel_alive = True
132 self._kernel_alive = True
128 self._beating = False
133 self._beating = False
134 self.iopub_stream = None
135 self.hb_stream = None
129
136
130 def open(self, kernel_id):
137 def open(self, kernel_id):
131 km = self.application.kernel_manager
138 km = self.application.kernel_manager
132 self.kernel_id = kernel_id
139 self.kernel_id = kernel_id
133 self.session = Session()
140 self.session = Session()
134 self.time_to_dead = km.time_to_dead
141 self.time_to_dead = km.time_to_dead
135 self.iopub_stream = km.create_iopub_stream(kernel_id)
142 try:
136 self.hb_stream = km.create_hb_stream(kernel_id)
143 self.iopub_stream = km.create_iopub_stream(kernel_id)
137 self.iopub_stream.on_recv(self._on_zmq_reply)
144 self.hb_stream = km.create_hb_stream(kernel_id)
138 self.start_hb(self.kernel_died)
145 except web.HTTPError:
146 # WebSockets don't response to traditional error codes so we
147 # close the connection.
148 if not self.stream.closed():
149 self.stream.close()
150 else:
151 self.iopub_stream.on_recv(self._on_zmq_reply)
152 self.start_hb(self.kernel_died)
139
153
140 def on_close(self):
154 def on_close(self):
155 # This method can be called twice, once by self.kernel_died and once
156 # from the WebSocket close event. If the WebSocket connection is
157 # closed before the ZMQ streams are setup, they could be None.
141 self.stop_hb()
158 self.stop_hb()
142 self.iopub_stream.close()
159 if self.iopub_stream is not None and not self.iopub_stream.closed():
143 self.hb_stream.close()
160 self.iopub_stream.on_recv(None)
161 self.iopub_stream.close()
162 if self.hb_stream is not None and not self.hb_stream.closed():
163 self.hb_stream.close()
144
164
145 def start_hb(self, callback):
165 def start_hb(self, callback):
146 """Start the heartbeating and call the callback if the kernel dies."""
166 """Start the heartbeating and call the callback if the kernel dies."""
147 if not self._beating:
167 if not self._beating:
148 self._kernel_alive = True
168 self._kernel_alive = True
149
169
150 def ping_or_dead():
170 def ping_or_dead():
151 if self._kernel_alive:
171 if self._kernel_alive:
152 self._kernel_alive = False
172 self._kernel_alive = False
153 self.hb_stream.send(b'ping')
173 self.hb_stream.send(b'ping')
154 else:
174 else:
155 try:
175 try:
156 callback()
176 callback()
157 except:
177 except:
158 pass
178 pass
159 finally:
179 finally:
160 self._hb_periodic_callback.stop()
180 self._hb_periodic_callback.stop()
161
181
162 def beat_received(msg):
182 def beat_received(msg):
163 self._kernel_alive = True
183 self._kernel_alive = True
164
184
165 self.hb_stream.on_recv(beat_received)
185 self.hb_stream.on_recv(beat_received)
166 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
186 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
167 self._hb_periodic_callback.start()
187 self._hb_periodic_callback.start()
168 self._beating= True
188 self._beating= True
169
189
170 def stop_hb(self):
190 def stop_hb(self):
171 """Stop the heartbeating and cancel all related callbacks."""
191 """Stop the heartbeating and cancel all related callbacks."""
172 if self._beating:
192 if self._beating:
173 self._hb_periodic_callback.stop()
193 self._hb_periodic_callback.stop()
174 if not self.hb_stream.closed():
194 if not self.hb_stream.closed():
175 self.hb_stream.on_recv(None)
195 self.hb_stream.on_recv(None)
176
196
177 def kernel_died(self):
197 def kernel_died(self):
178 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
198 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
179 self.write_message(
199 self.write_message(
180 {'header': {'msg_type': 'status'},
200 {'header': {'msg_type': 'status'},
181 'parent_header': {},
201 'parent_header': {},
182 'content': {'execution_state':'dead'}
202 'content': {'execution_state':'dead'}
183 }
203 }
184 )
204 )
185 self.on_close()
205 self.on_close()
186
206
187
207
188 class ShellHandler(ZMQStreamHandler):
208 class ShellHandler(ZMQStreamHandler):
189
209
190 def initialize(self, *args, **kwargs):
210 def initialize(self, *args, **kwargs):
191 pass
211 self.shell_stream = None
192
212
193 def open(self, kernel_id):
213 def open(self, kernel_id):
194 km = self.application.kernel_manager
214 km = self.application.kernel_manager
195 self.max_msg_size = km.max_msg_size
215 self.max_msg_size = km.max_msg_size
196 self.kernel_id = kernel_id
216 self.kernel_id = kernel_id
197 self.session = Session()
217 try:
198 self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
218 self.shell_stream = km.create_shell_stream(kernel_id)
199 self.shell_stream.on_recv(self._on_zmq_reply)
219 except web.HTTPError:
220 # WebSockets don't response to traditional error codes so we
221 # close the connection.
222 if not self.stream.closed():
223 self.stream.close()
224 else:
225 self.session = Session()
226 self.shell_stream.on_recv(self._on_zmq_reply)
200
227
201 def on_message(self, msg):
228 def on_message(self, msg):
202 if len(msg) < self.max_msg_size:
229 if len(msg) < self.max_msg_size:
203 msg = jsonapi.loads(msg)
230 msg = jsonapi.loads(msg)
204 self.session.send(self.shell_stream, msg)
231 self.session.send(self.shell_stream, msg)
205
232
206 def on_close(self):
233 def on_close(self):
207 self.shell_stream.close()
234 # Make sure the stream exists and is not already closed.
235 if self.shell_stream is not None and not self.shell_stream.closed():
236 self.shell_stream.close()
208
237
209
238
210 #-----------------------------------------------------------------------------
239 #-----------------------------------------------------------------------------
211 # Notebook web service handlers
240 # Notebook web service handlers
212 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
213
242
214 class NotebookRootHandler(web.RequestHandler):
243 class NotebookRootHandler(web.RequestHandler):
215
244
216 def get(self):
245 def get(self):
217 nbm = self.application.notebook_manager
246 nbm = self.application.notebook_manager
218 files = nbm.list_notebooks()
247 files = nbm.list_notebooks()
219 self.finish(jsonapi.dumps(files))
248 self.finish(jsonapi.dumps(files))
220
249
221 def post(self):
250 def post(self):
222 nbm = self.application.notebook_manager
251 nbm = self.application.notebook_manager
223 body = self.request.body.strip()
252 body = self.request.body.strip()
224 format = self.get_argument('format', default='json')
253 format = self.get_argument('format', default='json')
225 name = self.get_argument('name', default=None)
254 name = self.get_argument('name', default=None)
226 if body:
255 if body:
227 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
256 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
228 else:
257 else:
229 notebook_id = nbm.new_notebook()
258 notebook_id = nbm.new_notebook()
230 self.set_header('Location', '/'+notebook_id)
259 self.set_header('Location', '/'+notebook_id)
231 self.finish(jsonapi.dumps(notebook_id))
260 self.finish(jsonapi.dumps(notebook_id))
232
261
233
262
234 class NotebookHandler(web.RequestHandler):
263 class NotebookHandler(web.RequestHandler):
235
264
236 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
265 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
237
266
238 def get(self, notebook_id):
267 def get(self, notebook_id):
239 nbm = self.application.notebook_manager
268 nbm = self.application.notebook_manager
240 format = self.get_argument('format', default='json')
269 format = self.get_argument('format', default='json')
241 last_mod, name, data = nbm.get_notebook(notebook_id, format)
270 last_mod, name, data = nbm.get_notebook(notebook_id, format)
242 if format == u'json':
271 if format == u'json':
243 self.set_header('Content-Type', 'application/json')
272 self.set_header('Content-Type', 'application/json')
244 self.set_header('Content-Disposition','attachment; filename="%s.json"' % name)
273 self.set_header('Content-Disposition','attachment; filename="%s.json"' % name)
245 elif format == u'xml':
274 elif format == u'xml':
246 self.set_header('Content-Type', 'application/xml')
275 self.set_header('Content-Type', 'application/xml')
247 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
276 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
248 elif format == u'py':
277 elif format == u'py':
249 self.set_header('Content-Type', 'application/x-python')
278 self.set_header('Content-Type', 'application/x-python')
250 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
279 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
251 self.set_header('Last-Modified', last_mod)
280 self.set_header('Last-Modified', last_mod)
252 self.finish(data)
281 self.finish(data)
253
282
254 def put(self, notebook_id):
283 def put(self, notebook_id):
255 nbm = self.application.notebook_manager
284 nbm = self.application.notebook_manager
256 format = self.get_argument('format', default='json')
285 format = self.get_argument('format', default='json')
257 name = self.get_argument('name', default=None)
286 name = self.get_argument('name', default=None)
258 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
287 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
259 self.set_status(204)
288 self.set_status(204)
260 self.finish()
289 self.finish()
261
290
262 def delete(self, notebook_id):
291 def delete(self, notebook_id):
263 nbm = self.application.notebook_manager
292 nbm = self.application.notebook_manager
264 nbm.delete_notebook(notebook_id)
293 nbm.delete_notebook(notebook_id)
265 self.set_status(204)
294 self.set_status(204)
266 self.finish()
295 self.finish()
267
296
268 #-----------------------------------------------------------------------------
297 #-----------------------------------------------------------------------------
269 # RST web service handlers
298 # RST web service handlers
270 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
271
300
272
301
273 class RSTHandler(web.RequestHandler):
302 class RSTHandler(web.RequestHandler):
274
303
275 def post(self):
304 def post(self):
276 if publish_string is None:
305 if publish_string is None:
277 raise web.HTTPError(503)
306 raise web.HTTPError(503)
278 body = self.request.body.strip()
307 body = self.request.body.strip()
279 source = body
308 source = body
280 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
309 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
281 defaults = {'file_insertion_enabled': 0,
310 defaults = {'file_insertion_enabled': 0,
282 'raw_enabled': 0,
311 'raw_enabled': 0,
283 '_disable_config': 1,
312 '_disable_config': 1,
284 'stylesheet_path': 0
313 'stylesheet_path': 0
285 # 'template': template_path
314 # 'template': template_path
286 }
315 }
287 try:
316 try:
288 html = publish_string(source, writer_name='html',
317 html = publish_string(source, writer_name='html',
289 settings_overrides=defaults
318 settings_overrides=defaults
290 )
319 )
291 except:
320 except:
292 raise web.HTTPError(400)
321 raise web.HTTPError(400)
293 print html
322 print html
294 self.set_header('Content-Type', 'text/html')
323 self.set_header('Content-Type', 'text/html')
295 self.finish(html)
324 self.finish(html)
296
325
297
326
@@ -1,306 +1,320 b''
1 """A kernel manager for multiple kernels."""
1 """A kernel manager for multiple kernels."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import signal
14 import signal
15 import sys
15 import sys
16 import uuid
16 import uuid
17
17
18 import zmq
18 import zmq
19 from zmq.eventloop.zmqstream import ZMQStream
19 from zmq.eventloop.zmqstream import ZMQStream
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.config.configurable import LoggingConfigurable
23 from IPython.config.configurable import LoggingConfigurable
24 from IPython.zmq.ipkernel import launch_kernel
24 from IPython.zmq.ipkernel import launch_kernel
25 from IPython.utils.traitlets import Instance, Dict, List, Unicode, Float, Int
25 from IPython.utils.traitlets import Instance, Dict, List, Unicode, Float, Int
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Classes
28 # Classes
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class DuplicateKernelError(Exception):
31 class DuplicateKernelError(Exception):
32 pass
32 pass
33
33
34
34
35 class KernelManager(LoggingConfigurable):
35 class KernelManager(LoggingConfigurable):
36 """A class for managing multiple kernels."""
36 """A class for managing multiple kernels."""
37
37
38 context = Instance('zmq.Context')
38 context = Instance('zmq.Context')
39 def _context_default(self):
39 def _context_default(self):
40 return zmq.Context.instance()
40 return zmq.Context.instance()
41
41
42 _kernels = Dict()
42 _kernels = Dict()
43
43
44 @property
44 @property
45 def kernel_ids(self):
45 def kernel_ids(self):
46 """Return a list of the kernel ids of the active kernels."""
46 """Return a list of the kernel ids of the active kernels."""
47 return self._kernels.keys()
47 return self._kernels.keys()
48
48
49 def __len__(self):
49 def __len__(self):
50 """Return the number of running kernels."""
50 """Return the number of running kernels."""
51 return len(self.kernel_ids)
51 return len(self.kernel_ids)
52
52
53 def __contains__(self, kernel_id):
53 def __contains__(self, kernel_id):
54 if kernel_id in self.kernel_ids:
54 if kernel_id in self.kernel_ids:
55 return True
55 return True
56 else:
56 else:
57 return False
57 return False
58
58
59 def start_kernel(self, **kwargs):
59 def start_kernel(self, **kwargs):
60 """Start a new kernel."""
60 """Start a new kernel."""
61 kernel_id = unicode(uuid.uuid4())
61 kernel_id = unicode(uuid.uuid4())
62 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
62 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
63 # Store the information for contacting the kernel. This assumes the kernel is
63 # Store the information for contacting the kernel. This assumes the kernel is
64 # running on localhost.
64 # running on localhost.
65 d = dict(
65 d = dict(
66 process = process,
66 process = process,
67 stdin_port = stdin_port,
67 stdin_port = stdin_port,
68 iopub_port = iopub_port,
68 iopub_port = iopub_port,
69 shell_port = shell_port,
69 shell_port = shell_port,
70 hb_port = hb_port,
70 hb_port = hb_port,
71 ip = '127.0.0.1'
71 ip = '127.0.0.1'
72 )
72 )
73 self._kernels[kernel_id] = d
73 self._kernels[kernel_id] = d
74 return kernel_id
74 return kernel_id
75
75
76 def kill_kernel(self, kernel_id):
76 def kill_kernel(self, kernel_id):
77 """Kill a kernel by its kernel uuid.
77 """Kill a kernel by its kernel uuid.
78
78
79 Parameters
79 Parameters
80 ==========
80 ==========
81 kernel_id : uuid
81 kernel_id : uuid
82 The id of the kernel to kill.
82 The id of the kernel to kill.
83 """
83 """
84 kernel_process = self.get_kernel_process(kernel_id)
84 kernel_process = self.get_kernel_process(kernel_id)
85 if kernel_process is not None:
85 if kernel_process is not None:
86 # Attempt to kill the kernel.
86 # Attempt to kill the kernel.
87 try:
87 try:
88 kernel_process.kill()
88 kernel_process.kill()
89 except OSError, e:
89 except OSError, e:
90 # In Windows, we will get an Access Denied error if the process
90 # In Windows, we will get an Access Denied error if the process
91 # has already terminated. Ignore it.
91 # has already terminated. Ignore it.
92 if not (sys.platform == 'win32' and e.winerror == 5):
92 if not (sys.platform == 'win32' and e.winerror == 5):
93 raise
93 raise
94 del self._kernels[kernel_id]
94 del self._kernels[kernel_id]
95
95
96 def interrupt_kernel(self, kernel_id):
96 def interrupt_kernel(self, kernel_id):
97 """Interrupt (SIGINT) the kernel by its uuid.
97 """Interrupt (SIGINT) the kernel by its uuid.
98
98
99 Parameters
99 Parameters
100 ==========
100 ==========
101 kernel_id : uuid
101 kernel_id : uuid
102 The id of the kernel to interrupt.
102 The id of the kernel to interrupt.
103 """
103 """
104 kernel_process = self.get_kernel_process(kernel_id)
104 kernel_process = self.get_kernel_process(kernel_id)
105 if kernel_process is not None:
105 if kernel_process is not None:
106 if sys.platform == 'win32':
106 if sys.platform == 'win32':
107 from parentpoller import ParentPollerWindows as Poller
107 from parentpoller import ParentPollerWindows as Poller
108 Poller.send_interrupt(kernel_process.win32_interrupt_event)
108 Poller.send_interrupt(kernel_process.win32_interrupt_event)
109 else:
109 else:
110 kernel_process.send_signal(signal.SIGINT)
110 kernel_process.send_signal(signal.SIGINT)
111
111
112
112
113 def signal_kernel(self, kernel_id, signum):
113 def signal_kernel(self, kernel_id, signum):
114 """ Sends a signal to the kernel by its uuid.
114 """ Sends a signal to the kernel by its uuid.
115
115
116 Note that since only SIGTERM is supported on Windows, this function
116 Note that since only SIGTERM is supported on Windows, this function
117 is only useful on Unix systems.
117 is only useful on Unix systems.
118
118
119 Parameters
119 Parameters
120 ==========
120 ==========
121 kernel_id : uuid
121 kernel_id : uuid
122 The id of the kernel to signal.
122 The id of the kernel to signal.
123 """
123 """
124 kernel_process = self.get_kernel_process(kernel_id)
124 kernel_process = self.get_kernel_process(kernel_id)
125 if kernel_process is not None:
125 if kernel_process is not None:
126 kernel_process.send_signal(signum)
126 kernel_process.send_signal(signum)
127
127
128 def get_kernel_process(self, kernel_id):
128 def get_kernel_process(self, kernel_id):
129 """Get the process object for a kernel by its uuid.
129 """Get the process object for a kernel by its uuid.
130
130
131 Parameters
131 Parameters
132 ==========
132 ==========
133 kernel_id : uuid
133 kernel_id : uuid
134 The id of the kernel.
134 The id of the kernel.
135 """
135 """
136 d = self._kernels.get(kernel_id)
136 d = self._kernels.get(kernel_id)
137 if d is not None:
137 if d is not None:
138 return d['process']
138 return d['process']
139 else:
139 else:
140 raise KeyError("Kernel with id not found: %s" % kernel_id)
140 raise KeyError("Kernel with id not found: %s" % kernel_id)
141
141
142 def get_kernel_ports(self, kernel_id):
142 def get_kernel_ports(self, kernel_id):
143 """Return a dictionary of ports for a kernel.
143 """Return a dictionary of ports for a kernel.
144
144
145 Parameters
145 Parameters
146 ==========
146 ==========
147 kernel_id : uuid
147 kernel_id : uuid
148 The id of the kernel.
148 The id of the kernel.
149
149
150 Returns
150 Returns
151 =======
151 =======
152 port_dict : dict
152 port_dict : dict
153 A dict of key, value pairs where the keys are the names
153 A dict of key, value pairs where the keys are the names
154 (stdin_port,iopub_port,shell_port) and the values are the
154 (stdin_port,iopub_port,shell_port) and the values are the
155 integer port numbers for those channels.
155 integer port numbers for those channels.
156 """
156 """
157 d = self._kernels.get(kernel_id)
157 d = self._kernels.get(kernel_id)
158 if d is not None:
158 if d is not None:
159 dcopy = d.copy()
159 dcopy = d.copy()
160 dcopy.pop('process')
160 dcopy.pop('process')
161 dcopy.pop('ip')
161 dcopy.pop('ip')
162 return dcopy
162 return dcopy
163 else:
163 else:
164 raise KeyError("Kernel with id not found: %s" % kernel_id)
164 raise KeyError("Kernel with id not found: %s" % kernel_id)
165
165
166 def get_kernel_ip(self, kernel_id):
166 def get_kernel_ip(self, kernel_id):
167 """Return ip address for a kernel.
167 """Return ip address for a kernel.
168
168
169 Parameters
169 Parameters
170 ==========
170 ==========
171 kernel_id : uuid
171 kernel_id : uuid
172 The id of the kernel.
172 The id of the kernel.
173
173
174 Returns
174 Returns
175 =======
175 =======
176 ip : str
176 ip : str
177 The ip address of the kernel.
177 The ip address of the kernel.
178 """
178 """
179 d = self._kernels.get(kernel_id)
179 d = self._kernels.get(kernel_id)
180 if d is not None:
180 if d is not None:
181 return d['ip']
181 return d['ip']
182 else:
182 else:
183 raise KeyError("Kernel with id not found: %s" % kernel_id)
183 raise KeyError("Kernel with id not found: %s" % kernel_id)
184
184
185 def create_connected_stream(self, ip, port, socket_type):
185 def create_connected_stream(self, ip, port, socket_type):
186 sock = self.context.socket(socket_type)
186 sock = self.context.socket(socket_type)
187 addr = "tcp://%s:%i" % (ip, port)
187 addr = "tcp://%s:%i" % (ip, port)
188 self.log.info("Connecting to: %s" % addr)
188 self.log.info("Connecting to: %s" % addr)
189 sock.connect(addr)
189 sock.connect(addr)
190 return ZMQStream(sock)
190 return ZMQStream(sock)
191
191
192 def create_iopub_stream(self, kernel_id):
192 def create_iopub_stream(self, kernel_id):
193 ip = self.get_kernel_ip(kernel_id)
193 ip = self.get_kernel_ip(kernel_id)
194 ports = self.get_kernel_ports(kernel_id)
194 ports = self.get_kernel_ports(kernel_id)
195 iopub_stream = self.create_connected_stream(ip, ports['iopub_port'], zmq.SUB)
195 iopub_stream = self.create_connected_stream(ip, ports['iopub_port'], zmq.SUB)
196 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
196 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
197 return iopub_stream
197 return iopub_stream
198
198
199 def create_shell_stream(self, kernel_id):
199 def create_shell_stream(self, kernel_id):
200 ip = self.get_kernel_ip(kernel_id)
200 ip = self.get_kernel_ip(kernel_id)
201 ports = self.get_kernel_ports(kernel_id)
201 ports = self.get_kernel_ports(kernel_id)
202 shell_stream = self.create_connected_stream(ip, ports['shell_port'], zmq.XREQ)
202 shell_stream = self.create_connected_stream(ip, ports['shell_port'], zmq.XREQ)
203 return shell_stream
203 return shell_stream
204
204
205 def create_hb_stream(self, kernel_id):
205 def create_hb_stream(self, kernel_id):
206 ip = self.get_kernel_ip(kernel_id)
206 ip = self.get_kernel_ip(kernel_id)
207 ports = self.get_kernel_ports(kernel_id)
207 ports = self.get_kernel_ports(kernel_id)
208 hb_stream = self.create_connected_stream(ip, ports['hb_port'], zmq.REQ)
208 hb_stream = self.create_connected_stream(ip, ports['hb_port'], zmq.REQ)
209 return hb_stream
209 return hb_stream
210
210
211
211
212 class MappingKernelManager(KernelManager):
212 class MappingKernelManager(KernelManager):
213 """A KernelManager that handles notebok mapping and HTTP error handling"""
213 """A KernelManager that handles notebok mapping and HTTP error handling"""
214
214
215 kernel_argv = List(Unicode)
215 kernel_argv = List(Unicode)
216 kernel_manager = Instance(KernelManager)
216 kernel_manager = Instance(KernelManager)
217 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
217 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
218 max_msg_size = Int(65536, config=True, help="""
218 max_msg_size = Int(65536, config=True, help="""
219 The max raw message size accepted from the browser
219 The max raw message size accepted from the browser
220 over a WebSocket connection.
220 over a WebSocket connection.
221 """)
221 """)
222
222
223 _notebook_mapping = Dict()
223 _notebook_mapping = Dict()
224
224
225 #-------------------------------------------------------------------------
225 #-------------------------------------------------------------------------
226 # Methods for managing kernels and sessions
226 # Methods for managing kernels and sessions
227 #-------------------------------------------------------------------------
227 #-------------------------------------------------------------------------
228
228
229 def kernel_for_notebook(self, notebook_id):
229 def kernel_for_notebook(self, notebook_id):
230 """Return the kernel_id for a notebook_id or None."""
230 """Return the kernel_id for a notebook_id or None."""
231 return self._notebook_mapping.get(notebook_id)
231 return self._notebook_mapping.get(notebook_id)
232
232
233 def set_kernel_for_notebook(self, notebook_id, kernel_id):
233 def set_kernel_for_notebook(self, notebook_id, kernel_id):
234 """Associate a notebook with a kernel."""
234 """Associate a notebook with a kernel."""
235 if notebook_id is not None:
235 if notebook_id is not None:
236 self._notebook_mapping[notebook_id] = kernel_id
236 self._notebook_mapping[notebook_id] = kernel_id
237
237
238 def notebook_for_kernel(self, kernel_id):
238 def notebook_for_kernel(self, kernel_id):
239 """Return the notebook_id for a kernel_id or None."""
239 """Return the notebook_id for a kernel_id or None."""
240 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
240 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
241 if len(notebook_ids) == 1:
241 if len(notebook_ids) == 1:
242 return notebook_ids[0]
242 return notebook_ids[0]
243 else:
243 else:
244 return None
244 return None
245
245
246 def delete_mapping_for_kernel(self, kernel_id):
246 def delete_mapping_for_kernel(self, kernel_id):
247 """Remove the kernel/notebook mapping for kernel_id."""
247 """Remove the kernel/notebook mapping for kernel_id."""
248 notebook_id = self.notebook_for_kernel(kernel_id)
248 notebook_id = self.notebook_for_kernel(kernel_id)
249 if notebook_id is not None:
249 if notebook_id is not None:
250 del self._notebook_mapping[notebook_id]
250 del self._notebook_mapping[notebook_id]
251
251
252 def start_kernel(self, notebook_id=None):
252 def start_kernel(self, notebook_id=None):
253 """Start a kernel for a notebok an return its kernel_id.
253 """Start a kernel for a notebok an return its kernel_id.
254
254
255 Parameters
255 Parameters
256 ----------
256 ----------
257 notebook_id : uuid
257 notebook_id : uuid
258 The uuid of the notebook to associate the new kernel with. If this
258 The uuid of the notebook to associate the new kernel with. If this
259 is not None, this kernel will be persistent whenever the notebook
259 is not None, this kernel will be persistent whenever the notebook
260 requests a kernel.
260 requests a kernel.
261 """
261 """
262 kernel_id = self.kernel_for_notebook(notebook_id)
262 kernel_id = self.kernel_for_notebook(notebook_id)
263 if kernel_id is None:
263 if kernel_id is None:
264 kwargs = dict()
264 kwargs = dict()
265 kwargs['extra_arguments'] = self.kernel_argv
265 kwargs['extra_arguments'] = self.kernel_argv
266 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
266 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
267 self.set_kernel_for_notebook(notebook_id, kernel_id)
267 self.set_kernel_for_notebook(notebook_id, kernel_id)
268 self.log.info("Kernel started: %s" % kernel_id)
268 self.log.info("Kernel started: %s" % kernel_id)
269 self.log.debug("Kernel args: %r" % kwargs)
269 self.log.debug("Kernel args: %r" % kwargs)
270 else:
270 else:
271 self.log.info("Using existing kernel: %s" % kernel_id)
271 self.log.info("Using existing kernel: %s" % kernel_id)
272 return kernel_id
272 return kernel_id
273
273
274 def kill_kernel(self, kernel_id):
274 def kill_kernel(self, kernel_id):
275 """Kill a kernel and remove its notebook association."""
275 """Kill a kernel and remove its notebook association."""
276 if kernel_id not in self:
276 if kernel_id not in self:
277 raise web.HTTPError(404)
277 raise web.HTTPError(404)
278 super(MappingKernelManager, self).kill_kernel(kernel_id)
278 super(MappingKernelManager, self).kill_kernel(kernel_id)
279 self.delete_mapping_for_kernel(kernel_id)
279 self.delete_mapping_for_kernel(kernel_id)
280 self.log.info("Kernel killed: %s" % kernel_id)
280 self.log.info("Kernel killed: %s" % kernel_id)
281
281
282 def interrupt_kernel(self, kernel_id):
282 def interrupt_kernel(self, kernel_id):
283 """Interrupt a kernel."""
283 """Interrupt a kernel."""
284 if kernel_id not in self:
284 if kernel_id not in self:
285 raise web.HTTPError(404)
285 raise web.HTTPError(404)
286 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
286 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
287 self.log.info("Kernel interrupted: %s" % kernel_id)
287 self.log.info("Kernel interrupted: %s" % kernel_id)
288
288
289 def restart_kernel(self, kernel_id):
289 def restart_kernel(self, kernel_id):
290 """Restart a kernel while keeping clients connected."""
290 """Restart a kernel while keeping clients connected."""
291 if kernel_id not in self:
291 if kernel_id not in self:
292 raise web.HTTPError(404)
292 raise web.HTTPError(404)
293
293
294 # Get the notebook_id to preserve the kernel/notebook association.
294 # Get the notebook_id to preserve the kernel/notebook association.
295 notebook_id = self.notebook_for_kernel(kernel_id)
295 notebook_id = self.notebook_for_kernel(kernel_id)
296 # Create the new kernel first so we can move the clients over.
296 # Create the new kernel first so we can move the clients over.
297 new_kernel_id = self.start_kernel()
297 new_kernel_id = self.start_kernel()
298 # Now kill the old kernel.
298 # Now kill the old kernel.
299 self.kill_kernel(kernel_id)
299 self.kill_kernel(kernel_id)
300 # Now save the new kernel/notebook association. We have to save it
300 # Now save the new kernel/notebook association. We have to save it
301 # after the old kernel is killed as that will delete the mapping.
301 # after the old kernel is killed as that will delete the mapping.
302 self.set_kernel_for_notebook(notebook_id, new_kernel_id)
302 self.set_kernel_for_notebook(notebook_id, new_kernel_id)
303 self.log.debug("Kernel restarted: %s" % new_kernel_id)
303 self.log.info("Kernel restarted: %s" % new_kernel_id)
304 return new_kernel_id
304 return new_kernel_id
305
305
306 def create_iopub_stream(self, kernel_id):
307 if kernel_id not in self:
308 raise web.HTTPError(404)
309 return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
310
311 def create_shell_stream(self, kernel_id):
312 if kernel_id not in self:
313 raise web.HTTPError(404)
314 return super(MappingKernelManager, self).create_shell_stream(kernel_id)
315
316 def create_hb_stream(self, kernel_id):
317 if kernel_id not in self:
318 raise web.HTTPError(404)
319 return super(MappingKernelManager, self).create_hb_stream(kernel_id)
306
320
@@ -1,244 +1,261 b''
1 """A tornado based IPython notebook server."""
1 """A tornado based IPython notebook server."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import errno
14 import errno
15 import logging
15 import logging
16 import os
16 import os
17 import signal
17 import signal
18 import socket
18 import socket
19 import sys
19 import sys
20
20
21 import zmq
21 import zmq
22
22
23 # Install the pyzmq ioloop. This has to be done before anything else from
23 # Install the pyzmq ioloop. This has to be done before anything else from
24 # tornado is imported.
24 # tornado is imported.
25 from zmq.eventloop import ioloop
25 from zmq.eventloop import ioloop
26 import tornado.ioloop
26 import tornado.ioloop
27 tornado.ioloop = ioloop
27 tornado.ioloop = ioloop
28
28
29 from tornado import httpserver
29 from tornado import httpserver
30 from tornado import web
30 from tornado import web
31
31
32 from .kernelmanager import MappingKernelManager
32 from .kernelmanager import MappingKernelManager
33 from .handlers import (
33 from .handlers import (
34 NBBrowserHandler, NewHandler, NamedNotebookHandler,
34 NBBrowserHandler, NewHandler, NamedNotebookHandler,
35 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
35 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
36 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
36 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
37 )
37 )
38 from .notebookmanager import NotebookManager
38 from .notebookmanager import NotebookManager
39
39
40 from IPython.core.application import BaseIPythonApplication
40 from IPython.core.application import BaseIPythonApplication
41 from IPython.core.profiledir import ProfileDir
41 from IPython.core.profiledir import ProfileDir
42 from IPython.zmq.session import Session
42 from IPython.zmq.session import Session
43 from IPython.zmq.zmqshell import ZMQInteractiveShell
43 from IPython.zmq.zmqshell import ZMQInteractiveShell
44 from IPython.zmq.ipkernel import (
44 from IPython.zmq.ipkernel import (
45 flags as ipkernel_flags,
45 flags as ipkernel_flags,
46 aliases as ipkernel_aliases,
46 aliases as ipkernel_aliases,
47 IPKernelApp
47 IPKernelApp
48 )
48 )
49 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
49 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Module globals
52 # Module globals
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
55 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
56 _kernel_action_regex = r"(?P<action>restart|interrupt)"
56 _kernel_action_regex = r"(?P<action>restart|interrupt)"
57 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
57 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
58
58
59 LOCALHOST = '127.0.0.1'
59 LOCALHOST = '127.0.0.1'
60
60
61 _examples = """
61 _examples = """
62 ipython notebook # start the notebook
62 ipython notebook # start the notebook
63 ipython notebook --profile=sympy # use the sympy profile
63 ipython notebook --profile=sympy # use the sympy profile
64 ipython notebook --pylab=inline # pylab in inline plotting mode
64 ipython notebook --pylab=inline # pylab in inline plotting mode
65 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
65 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
66 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
66 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
67 """
67 """
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # The Tornado web application
70 # The Tornado web application
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73 class NotebookWebApplication(web.Application):
73 class NotebookWebApplication(web.Application):
74
74
75 def __init__(self, kernel_manager, notebook_manager, log):
75 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
76 handlers = [
76 handlers = [
77 (r"/", NBBrowserHandler),
77 (r"/", NBBrowserHandler),
78 (r"/new", NewHandler),
78 (r"/new", NewHandler),
79 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
79 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
80 (r"/kernels", MainKernelHandler),
80 (r"/kernels", MainKernelHandler),
81 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
81 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
82 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
82 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
83 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
83 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
84 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
84 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
85 (r"/notebooks", NotebookRootHandler),
85 (r"/notebooks", NotebookRootHandler),
86 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
86 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
87 (r"/rstservice/render", RSTHandler)
87 (r"/rstservice/render", RSTHandler)
88 ]
88 ]
89 settings = dict(
89 settings = dict(
90 template_path=os.path.join(os.path.dirname(__file__), "templates"),
90 template_path=os.path.join(os.path.dirname(__file__), "templates"),
91 static_path=os.path.join(os.path.dirname(__file__), "static"),
91 static_path=os.path.join(os.path.dirname(__file__), "static"),
92 )
92 )
93 web.Application.__init__(self, handlers, **settings)
93 web.Application.__init__(self, handlers, **settings)
94
94
95 self.kernel_manager = kernel_manager
95 self.kernel_manager = kernel_manager
96 self.log = log
96 self.log = log
97 self.notebook_manager = notebook_manager
97 self.notebook_manager = notebook_manager
98 self.ipython_app = ipython_app
98
99
99
100
100 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
101 # Aliases and Flags
102 # Aliases and Flags
102 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
103
104
104 flags = dict(ipkernel_flags)
105 flags = dict(ipkernel_flags)
105
106
106 # the flags that are specific to the frontend
107 # the flags that are specific to the frontend
107 # these must be scrubbed before being passed to the kernel,
108 # these must be scrubbed before being passed to the kernel,
108 # or it will raise an error on unrecognized flags
109 # or it will raise an error on unrecognized flags
109 notebook_flags = []
110 notebook_flags = []
110
111
111 aliases = dict(ipkernel_aliases)
112 aliases = dict(ipkernel_aliases)
112
113
113 aliases.update({
114 aliases.update({
114 'ip': 'IPythonNotebookApp.ip',
115 'ip': 'IPythonNotebookApp.ip',
115 'port': 'IPythonNotebookApp.port',
116 'port': 'IPythonNotebookApp.port',
116 'keyfile': 'IPythonNotebookApp.keyfile',
117 'keyfile': 'IPythonNotebookApp.keyfile',
117 'certfile': 'IPythonNotebookApp.certfile',
118 'certfile': 'IPythonNotebookApp.certfile',
119 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
118 'notebook-dir': 'NotebookManager.notebook_dir'
120 'notebook-dir': 'NotebookManager.notebook_dir'
119 })
121 })
120
122
121 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
122 # IPythonNotebookApp
124 # IPythonNotebookApp
123 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
124
126
125 class IPythonNotebookApp(BaseIPythonApplication):
127 class IPythonNotebookApp(BaseIPythonApplication):
126
128
127 name = 'ipython-notebook'
129 name = 'ipython-notebook'
128 default_config_file_name='ipython_notebook_config.py'
130 default_config_file_name='ipython_notebook_config.py'
129
131
130 description = """
132 description = """
131 The IPython HTML Notebook.
133 The IPython HTML Notebook.
132
134
133 This launches a Tornado based HTML Notebook Server that serves up an
135 This launches a Tornado based HTML Notebook Server that serves up an
134 HTML5/Javascript Notebook client.
136 HTML5/Javascript Notebook client.
135 """
137 """
136 examples = _examples
138 examples = _examples
137
139
138 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
140 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
139 MappingKernelManager, NotebookManager]
141 MappingKernelManager, NotebookManager]
140 flags = Dict(flags)
142 flags = Dict(flags)
141 aliases = Dict(aliases)
143 aliases = Dict(aliases)
142
144
143 kernel_argv = List(Unicode)
145 kernel_argv = List(Unicode)
144
146
145 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
147 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
146 default_value=logging.INFO,
148 default_value=logging.INFO,
147 config=True,
149 config=True,
148 help="Set the log level by value or name.")
150 help="Set the log level by value or name.")
149
151
150 # Network related information.
152 # Network related information.
151
153
152 ip = Unicode(LOCALHOST, config=True,
154 ip = Unicode(LOCALHOST, config=True,
153 help="The IP address the notebook server will listen on."
155 help="The IP address the notebook server will listen on."
154 )
156 )
155
157
156 def _ip_changed(self, name, old, new):
158 def _ip_changed(self, name, old, new):
157 if new == u'*': self.ip = u''
159 if new == u'*': self.ip = u''
158
160
159 port = Int(8888, config=True,
161 port = Int(8888, config=True,
160 help="The port the notebook server will listen on."
162 help="The port the notebook server will listen on."
161 )
163 )
162
164
165 ws_hostname = Unicode(LOCALHOST, config=True,
166 help="""The FQDN or IP for WebSocket connections. The default will work
167 fine when the server is listening on localhost, but this needs to
168 be set if the ip option is used. It will be used as the hostname part
169 of the WebSocket url: ws://hostname/path."""
170 )
171
163 certfile = Unicode(u'', config=True,
172 certfile = Unicode(u'', config=True,
164 help="""The full path to an SSL/TLS certificate file."""
173 help="""The full path to an SSL/TLS certificate file."""
165 )
174 )
166
175
167 keyfile = Unicode(u'', config=True,
176 keyfile = Unicode(u'', config=True,
168 help="""The full path to a private key file for usage with SSL/TLS."""
177 help="""The full path to a private key file for usage with SSL/TLS."""
169 )
178 )
170
179
180 def get_ws_url(self):
181 """Return the WebSocket URL for this server."""
182 if self.certfile:
183 prefix = u'wss://'
184 else:
185 prefix = u'ws://'
186 return prefix + self.ws_hostname + u':' + unicode(self.port)
187
171 def parse_command_line(self, argv=None):
188 def parse_command_line(self, argv=None):
172 super(IPythonNotebookApp, self).parse_command_line(argv)
189 super(IPythonNotebookApp, self).parse_command_line(argv)
173 if argv is None:
190 if argv is None:
174 argv = sys.argv[1:]
191 argv = sys.argv[1:]
175
192
176 self.kernel_argv = list(argv) # copy
193 self.kernel_argv = list(argv) # copy
177 # kernel should inherit default config file from frontend
194 # kernel should inherit default config file from frontend
178 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
195 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
179 # scrub frontend-specific flags
196 # scrub frontend-specific flags
180 for a in argv:
197 for a in argv:
181 if a.startswith('-') and a.lstrip('-') in notebook_flags:
198 if a.startswith('-') and a.lstrip('-') in notebook_flags:
182 self.kernel_argv.remove(a)
199 self.kernel_argv.remove(a)
183
200
184 def init_configurables(self):
201 def init_configurables(self):
185 # Don't let Qt or ZMQ swallow KeyboardInterupts.
202 # Don't let Qt or ZMQ swallow KeyboardInterupts.
186 signal.signal(signal.SIGINT, signal.SIG_DFL)
203 signal.signal(signal.SIGINT, signal.SIG_DFL)
187
204
188 # Create a KernelManager and start a kernel.
205 # Create a KernelManager and start a kernel.
189 self.kernel_manager = MappingKernelManager(
206 self.kernel_manager = MappingKernelManager(
190 config=self.config, log=self.log, kernel_argv=self.kernel_argv
207 config=self.config, log=self.log, kernel_argv=self.kernel_argv
191 )
208 )
192 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
209 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
193
210
194 def init_logging(self):
211 def init_logging(self):
195 super(IPythonNotebookApp, self).init_logging()
212 super(IPythonNotebookApp, self).init_logging()
196 # This prevents double log messages because tornado use a root logger that
213 # This prevents double log messages because tornado use a root logger that
197 # self.log is a child of. The logging module dipatches log messages to a log
214 # self.log is a child of. The logging module dipatches log messages to a log
198 # and all of its ancenstors until propagate is set to False.
215 # and all of its ancenstors until propagate is set to False.
199 self.log.propagate = False
216 self.log.propagate = False
200
217
201 def initialize(self, argv=None):
218 def initialize(self, argv=None):
202 super(IPythonNotebookApp, self).initialize(argv)
219 super(IPythonNotebookApp, self).initialize(argv)
203 self.init_configurables()
220 self.init_configurables()
204 self.web_app = NotebookWebApplication(
221 self.web_app = NotebookWebApplication(
205 self.kernel_manager, self.notebook_manager, self.log
222 self, self.kernel_manager, self.notebook_manager, self.log
206 )
223 )
207 if self.certfile:
224 if self.certfile:
208 ssl_options = dict(certfile=self.certfile)
225 ssl_options = dict(certfile=self.certfile)
209 if self.keyfile:
226 if self.keyfile:
210 ssl_options['keyfile'] = self.keyfile
227 ssl_options['keyfile'] = self.keyfile
211 else:
228 else:
212 ssl_options = None
229 ssl_options = None
213 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
230 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
214 if ssl_options is None and not self.ip:
231 if ssl_options is None and not self.ip:
215 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
232 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
216 'but not using any encryption or authentication. This is highly '
233 'but not using any encryption or authentication. This is highly '
217 'insecure and not recommended.')
234 'insecure and not recommended.')
218 for i in range(10):
235 for i in range(10):
219 try:
236 try:
220 port = self.port + i
237 port = self.port + i
221 self.http_server.listen(port, self.ip)
238 self.http_server.listen(port, self.ip)
222 except socket.error, e:
239 except socket.error, e:
223 if e.errno != errno.EADDRINUSE:
240 if e.errno != errno.EADDRINUSE:
224 raise
241 raise
225 self.log.info('The port %i is already in use, trying: %i' % (port, port+1))
242 self.log.info('The port %i is already in use, trying: %i' % (port, port+1))
226 else:
243 else:
227 self.port = port
244 self.port = port
228 break
245 break
229
246
230
247
231 def start(self):
248 def start(self):
232 ip = self.ip if self.ip else '[all ip addresses on your system]'
249 ip = self.ip if self.ip else '[all ip addresses on your system]'
233 self.log.info("The IPython Notebook is running at: http://%s:%i" % (ip, self.port))
250 self.log.info("The IPython Notebook is running at: http://%s:%i" % (ip, self.port))
234 ioloop.IOLoop.instance().start()
251 ioloop.IOLoop.instance().start()
235
252
236 #-----------------------------------------------------------------------------
253 #-----------------------------------------------------------------------------
237 # Main entry point
254 # Main entry point
238 #-----------------------------------------------------------------------------
255 #-----------------------------------------------------------------------------
239
256
240 def launch_new_instance():
257 def launch_new_instance():
241 app = IPythonNotebookApp()
258 app = IPythonNotebookApp()
242 app.initialize()
259 app.initialize()
243 app.start()
260 app.start()
244
261
@@ -1,141 +1,143 b''
1
1
2 //============================================================================
2 //============================================================================
3 // Kernel
3 // Kernel
4 //============================================================================
4 //============================================================================
5
5
6 var IPython = (function (IPython) {
6 var IPython = (function (IPython) {
7
7
8 var utils = IPython.utils;
8 var utils = IPython.utils;
9
9
10 var Kernel = function () {
10 var Kernel = function () {
11 this.kernel_id = null;
11 this.kernel_id = null;
12 this.base_url = "/kernels";
12 this.base_url = "/kernels";
13 this.kernel_url = null;
13 this.kernel_url = null;
14 this.shell_channel = null;
14 this.shell_channel = null;
15 this.iopub_channel = null;
15 this.iopub_channel = null;
16 this.running = false;
16 this.running = false;
17 };
17 };
18
18
19
19
20 Kernel.prototype.get_msg = function (msg_type, content) {
20 Kernel.prototype.get_msg = function (msg_type, content) {
21 var msg = {
21 var msg = {
22 header : {
22 header : {
23 msg_id : utils.uuid(),
23 msg_id : utils.uuid(),
24 username : "username",
24 username : "username",
25 session: this.session_id,
25 session: this.session_id,
26 msg_type : msg_type
26 msg_type : msg_type
27 },
27 },
28 content : content,
28 content : content,
29 parent_header : {}
29 parent_header : {}
30 };
30 };
31 return msg;
31 return msg;
32 }
32 }
33
33
34 Kernel.prototype.start = function (notebook_id, callback) {
34 Kernel.prototype.start = function (notebook_id, callback) {
35 var that = this;
35 var that = this;
36 if (!this.running) {
36 if (!this.running) {
37 var qs = $.param({notebook:notebook_id});
37 var qs = $.param({notebook:notebook_id});
38 $.post(this.base_url + '?' + qs,
38 $.post(this.base_url + '?' + qs,
39 function (kernel_id) {
39 function (kernel_id) {
40 that._handle_start_kernel(kernel_id, callback);
40 that._handle_start_kernel(kernel_id, callback);
41 },
41 },
42 'json'
42 'json'
43 );
43 );
44 };
44 };
45 };
45 };
46
46
47
47
48 Kernel.prototype.restart = function (callback) {
48 Kernel.prototype.restart = function (callback) {
49 IPython.kernel_status_widget.status_restarting();
49 IPython.kernel_status_widget.status_restarting();
50 var url = this.kernel_url + "/restart";
50 var url = this.kernel_url + "/restart";
51 var that = this;
51 var that = this;
52 if (this.running) {
52 if (this.running) {
53 this.stop_channels();
53 this.stop_channels();
54 $.post(url,
54 $.post(url,
55 function (kernel_id) {
55 function (kernel_id) {
56 that._handle_start_kernel(kernel_id, callback);
56 that._handle_start_kernel(kernel_id, callback);
57 },
57 },
58 'json'
58 'json'
59 );
59 );
60 };
60 };
61 };
61 };
62
62
63
63
64 Kernel.prototype._handle_start_kernel = function (kernel_id, callback) {
64 Kernel.prototype._handle_start_kernel = function (json, callback) {
65 this.running = true;
65 this.running = true;
66 this.kernel_id = kernel_id;
66 this.kernel_id = json.kernel_id;
67 this.ws_url = json.ws_url;
67 this.kernel_url = this.base_url + "/" + this.kernel_id;
68 this.kernel_url = this.base_url + "/" + this.kernel_id;
68 this.start_channels();
69 this.start_channels();
69 callback();
70 callback();
70 IPython.kernel_status_widget.status_idle();
71 IPython.kernel_status_widget.status_idle();
71 };
72 };
72
73
73
74
74 Kernel.prototype.start_channels = function () {
75 Kernel.prototype.start_channels = function () {
75 this.stop_channels();
76 this.stop_channels();
76 var ws_url = "ws://127.0.0.1:8888" + this.kernel_url;
77 var ws_url = this.ws_url + this.kernel_url;
78 console.log("Starting WS:", ws_url);
77 this.shell_channel = new WebSocket(ws_url + "/shell");
79 this.shell_channel = new WebSocket(ws_url + "/shell");
78 this.iopub_channel = new WebSocket(ws_url + "/iopub");
80 this.iopub_channel = new WebSocket(ws_url + "/iopub");
79 };
81 };
80
82
81
83
82 Kernel.prototype.stop_channels = function () {
84 Kernel.prototype.stop_channels = function () {
83 if (this.shell_channel !== null) {
85 if (this.shell_channel !== null) {
84 this.shell_channel.close();
86 this.shell_channel.close();
85 this.shell_channel = null;
87 this.shell_channel = null;
86 };
88 };
87 if (this.iopub_channel !== null) {
89 if (this.iopub_channel !== null) {
88 this.iopub_channel.close();
90 this.iopub_channel.close();
89 this.iopub_channel = null;
91 this.iopub_channel = null;
90 };
92 };
91 };
93 };
92
94
93 Kernel.prototype.execute = function (code) {
95 Kernel.prototype.execute = function (code) {
94 var content = {
96 var content = {
95 code : code,
97 code : code,
96 silent : false,
98 silent : false,
97 user_variables : [],
99 user_variables : [],
98 user_expressions : {}
100 user_expressions : {}
99 };
101 };
100 var msg = this.get_msg("execute_request", content);
102 var msg = this.get_msg("execute_request", content);
101 this.shell_channel.send(JSON.stringify(msg));
103 this.shell_channel.send(JSON.stringify(msg));
102 return msg.header.msg_id;
104 return msg.header.msg_id;
103 }
105 }
104
106
105
107
106 Kernel.prototype.complete = function (line, cursor_pos) {
108 Kernel.prototype.complete = function (line, cursor_pos) {
107 var content = {
109 var content = {
108 text : '',
110 text : '',
109 line : line,
111 line : line,
110 cursor_pos : cursor_pos
112 cursor_pos : cursor_pos
111 };
113 };
112 var msg = this.get_msg("complete_request", content);
114 var msg = this.get_msg("complete_request", content);
113 this.shell_channel.send(JSON.stringify(msg));
115 this.shell_channel.send(JSON.stringify(msg));
114 return msg.header.msg_id;
116 return msg.header.msg_id;
115 }
117 }
116
118
117
119
118 Kernel.prototype.interrupt = function () {
120 Kernel.prototype.interrupt = function () {
119 if (this.running) {
121 if (this.running) {
120 $.post(this.kernel_url + "/interrupt");
122 $.post(this.kernel_url + "/interrupt");
121 };
123 };
122 };
124 };
123
125
124
126
125 Kernel.prototype.kill = function () {
127 Kernel.prototype.kill = function () {
126 if (this.running) {
128 if (this.running) {
127 this.running = false;
129 this.running = false;
128 var settings = {
130 var settings = {
129 cache : false,
131 cache : false,
130 type : "DELETE",
132 type : "DELETE",
131 };
133 };
132 $.ajax(this.kernel_url, settings);
134 $.ajax(this.kernel_url, settings);
133 };
135 };
134 };
136 };
135
137
136 IPython.Kernel = Kernel;
138 IPython.Kernel = Kernel;
137
139
138 return IPython;
140 return IPython;
139
141
140 }(IPython));
142 }(IPython));
141
143
General Comments 0
You need to be logged in to leave comments. Login now