##// END OF EJS Templates
Merge branch 'enh/httpauth' of https://github.com/satra/ipython into satra-enh/httpauth
Brian E. Granger -
r4723:06032c55 merge
parent child Browse files
Show More
@@ -0,0 +1,66
1 <!DOCTYPE HTML>
2 <html>
3
4 <head>
5 <meta charset="utf-8">
6
7 <title>IPython Notebook</title>
8
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 <!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
11 <!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
12
13 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
14 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
15 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
16 <script type="text/javascript" charset="utf-8">
17 function add_next_to_action(){
18 // add 'next' argument to action url, to preserve redirect
19 var query = location.search.substring(1);
20 var form = document.forms[0];
21 var action = form.getAttribute("action");
22 form.setAttribute("action", action + '?' + query);
23 }
24 </script>
25 </head>
26
27 <body onload="add_next_to_action()">
28
29 <div id="header">
30 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
31 </div>
32
33 <div id="header_border"></div>
34
35 <div id="main_app">
36
37 <div id="app_hbox">
38
39 <div id="left_panel">
40 </div>
41
42 <div id="content_panel">
43 <form action="/login" method="post">
44 Name: <input type="text" name="name" value="{{user_id}}">
45 Password: <input type="password" name="password">
46 <input type="submit" value="Sign in">
47 </form>
48 </div>
49 <div id="right_panel">
50 </div>
51
52 </div>
53
54 </div>
55
56 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
57 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
58 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
61
62 </body>
63
64 </html>
65
66
@@ -1,334 +1,419
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 import logging
20 import Cookie
21
19 22 from tornado import web
20 23 from tornado import websocket
21 24
22 25 from zmq.eventloop import ioloop
23 26 from zmq.utils import jsonapi
24 27
25 28 from IPython.zmq.session import Session
26 29
27 30 try:
28 31 from docutils.core import publish_string
29 32 except ImportError:
30 33 publish_string = None
31 34
32 35
33 36
34 37 #-----------------------------------------------------------------------------
35 38 # Top-level handlers
36 39 #-----------------------------------------------------------------------------
37 40
38
39 class NBBrowserHandler(web.RequestHandler):
41 class AuthenticatedHandler(web.RequestHandler):
42 """A RequestHandler with an authenticated user."""
43 def get_current_user(self):
44 password = self.get_secure_cookie("password")
45 if password is None:
46 # cookie doesn't exist, or is invalid. Clear to prevent repeated
47 # 'Invalid cookie signature' warnings.
48 self.clear_cookie('password')
49 self.clear_cookie("user_id")
50 if self.application.password and self.application.password != password:
51 return None
52 return self.get_secure_cookie("user") or 'anonymous'
53
54 class NBBrowserHandler(AuthenticatedHandler):
55 @web.authenticated
40 56 def get(self):
41 57 nbm = self.application.notebook_manager
42 58 project = nbm.notebook_dir
43 59 self.render('nbbrowser.html', project=project)
44 60
61 class LoginHandler(AuthenticatedHandler):
62 def get(self):
63 user_id = self.get_secure_cookie("user") or ''
64 self.render('login.html', user_id=user_id)
45 65
46 class NewHandler(web.RequestHandler):
66 def post(self):
67 self.set_secure_cookie("user", self.get_argument("name", default=u''))
68 self.set_secure_cookie("password", self.get_argument("password", default=u''))
69 url = self.get_argument("next", default="/")
70 self.redirect(url)
71
72 class NewHandler(AuthenticatedHandler):
73 @web.authenticated
47 74 def get(self):
48 75 notebook_id = self.application.notebook_manager.new_notebook()
49 76 self.render('notebook.html', notebook_id=notebook_id)
50 77
51 78
52 class NamedNotebookHandler(web.RequestHandler):
79 class NamedNotebookHandler(AuthenticatedHandler):
80 @web.authenticated
53 81 def get(self, notebook_id):
54 82 nbm = self.application.notebook_manager
55 83 if not nbm.notebook_exists(notebook_id):
56 84 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
57 85 self.render('notebook.html', notebook_id=notebook_id)
58 86
59 87
60 88 #-----------------------------------------------------------------------------
61 89 # Kernel handlers
62 90 #-----------------------------------------------------------------------------
63 91
64 92
65 class MainKernelHandler(web.RequestHandler):
93 class MainKernelHandler(AuthenticatedHandler):
66 94
95 @web.authenticated
67 96 def get(self):
68 97 km = self.application.kernel_manager
69 98 self.finish(jsonapi.dumps(km.kernel_ids))
70 99
100 @web.authenticated
71 101 def post(self):
72 102 km = self.application.kernel_manager
73 103 notebook_id = self.get_argument('notebook', default=None)
74 104 kernel_id = km.start_kernel(notebook_id)
75 105 ws_url = self.application.ipython_app.get_ws_url()
76 106 data = {'ws_url':ws_url,'kernel_id':kernel_id}
77 107 self.set_header('Location', '/'+kernel_id)
78 108 self.finish(jsonapi.dumps(data))
79 109
80 110
81 class KernelHandler(web.RequestHandler):
111 class KernelHandler(AuthenticatedHandler):
82 112
83 113 SUPPORTED_METHODS = ('DELETE')
84 114
115 @web.authenticated
85 116 def delete(self, kernel_id):
86 117 km = self.application.kernel_manager
87 118 km.kill_kernel(kernel_id)
88 119 self.set_status(204)
89 120 self.finish()
90 121
91 122
92 class KernelActionHandler(web.RequestHandler):
123 class KernelActionHandler(AuthenticatedHandler):
93 124
125 @web.authenticated
94 126 def post(self, kernel_id, action):
95 127 km = self.application.kernel_manager
96 128 if action == 'interrupt':
97 129 km.interrupt_kernel(kernel_id)
98 130 self.set_status(204)
99 131 if action == 'restart':
100 132 new_kernel_id = km.restart_kernel(kernel_id)
101 133 ws_url = self.application.ipython_app.get_ws_url()
102 134 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
103 135 self.set_header('Location', '/'+new_kernel_id)
104 136 self.write(jsonapi.dumps(data))
105 137 self.finish()
106 138
107 139
108 140 class ZMQStreamHandler(websocket.WebSocketHandler):
109 141
110 142 def _reserialize_reply(self, msg_list):
111 143 """Reserialize a reply message using JSON.
112 144
113 145 This takes the msg list from the ZMQ socket, unserializes it using
114 146 self.session and then serializes the result using JSON. This method
115 147 should be used by self._on_zmq_reply to build messages that can
116 148 be sent back to the browser.
117 149 """
118 150 idents, msg_list = self.session.feed_identities(msg_list)
119 151 msg = self.session.unserialize(msg_list)
120 152 try:
121 153 msg['header'].pop('date')
122 154 except KeyError:
123 155 pass
124 156 try:
125 157 msg['parent_header'].pop('date')
126 158 except KeyError:
127 159 pass
128 160 msg.pop('buffers')
129 161 return jsonapi.dumps(msg)
130 162
131 163 def _on_zmq_reply(self, msg_list):
132 164 try:
133 165 msg = self._reserialize_reply(msg_list)
134 166 except:
135 167 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
136 168 else:
137 169 self.write_message(msg)
138 170
171 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
172 def open(self, kernel_id):
173 self.kernel_id = kernel_id
174 self.session = Session()
175 self.save_on_message = self.on_message
176 self.on_message = self.on_first_message
177
178 def get_current_user(self):
179 password = self.get_secure_cookie("password")
180 if password is None:
181 # clear cookies, to prevent future Invalid cookie signature warnings
182 self._cookies = Cookie.SimpleCookie()
183 if self.application.password and self.application.password != password:
184 return None
185 return self.get_secure_cookie("user") or 'anonymous'
186
187 def _inject_cookie_message(self, msg):
188 """Inject the first message, which is the document cookie,
189 for authentication."""
190 if isinstance(msg, unicode):
191 # Cookie can't constructor doesn't accept unicode strings for some reason
192 msg = msg.encode('utf8', 'replace')
193 try:
194 self._cookies = Cookie.SimpleCookie(msg)
195 except:
196 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
139 197
140 class IOPubHandler(ZMQStreamHandler):
198 def on_first_message(self, msg):
199 self._inject_cookie_message(msg)
200 if self.get_current_user() is None:
201 logging.warn("Couldn't authenticate WebSocket connection")
202 raise web.HTTPError(403)
203 self.on_message = self.save_on_message
204
205
206 class IOPubHandler(AuthenticatedZMQStreamHandler):
141 207
142 208 def initialize(self, *args, **kwargs):
143 209 self._kernel_alive = True
144 210 self._beating = False
145 211 self.iopub_stream = None
146 212 self.hb_stream = None
147 213
148 def open(self, kernel_id):
214 def on_first_message(self, msg):
215 try:
216 super(IOPubHandler, self).on_first_message(msg)
217 except web.HTTPError:
218 self.close()
219 return
149 220 km = self.application.kernel_manager
150 self.kernel_id = kernel_id
151 self.session = Session()
152 221 self.time_to_dead = km.time_to_dead
222 kernel_id = self.kernel_id
153 223 try:
154 224 self.iopub_stream = km.create_iopub_stream(kernel_id)
155 225 self.hb_stream = km.create_hb_stream(kernel_id)
156 226 except web.HTTPError:
157 227 # WebSockets don't response to traditional error codes so we
158 228 # close the connection.
159 229 if not self.stream.closed():
160 230 self.stream.close()
231 self.close()
161 232 else:
162 233 self.iopub_stream.on_recv(self._on_zmq_reply)
163 234 self.start_hb(self.kernel_died)
164 235
236 def on_message(self, msg):
237 pass
238
165 239 def on_close(self):
166 240 # This method can be called twice, once by self.kernel_died and once
167 241 # from the WebSocket close event. If the WebSocket connection is
168 242 # closed before the ZMQ streams are setup, they could be None.
169 243 self.stop_hb()
170 244 if self.iopub_stream is not None and not self.iopub_stream.closed():
171 245 self.iopub_stream.on_recv(None)
172 246 self.iopub_stream.close()
173 247 if self.hb_stream is not None and not self.hb_stream.closed():
174 248 self.hb_stream.close()
175 249
176 250 def start_hb(self, callback):
177 251 """Start the heartbeating and call the callback if the kernel dies."""
178 252 if not self._beating:
179 253 self._kernel_alive = True
180 254
181 255 def ping_or_dead():
182 256 if self._kernel_alive:
183 257 self._kernel_alive = False
184 258 self.hb_stream.send(b'ping')
185 259 else:
186 260 try:
187 261 callback()
188 262 except:
189 263 pass
190 264 finally:
191 265 self._hb_periodic_callback.stop()
192 266
193 267 def beat_received(msg):
194 268 self._kernel_alive = True
195 269
196 270 self.hb_stream.on_recv(beat_received)
197 271 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
198 272 self._hb_periodic_callback.start()
199 273 self._beating= True
200 274
201 275 def stop_hb(self):
202 276 """Stop the heartbeating and cancel all related callbacks."""
203 277 if self._beating:
204 278 self._hb_periodic_callback.stop()
205 279 if not self.hb_stream.closed():
206 280 self.hb_stream.on_recv(None)
207 281
208 282 def kernel_died(self):
209 283 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
210 284 self.write_message(
211 285 {'header': {'msg_type': 'status'},
212 286 'parent_header': {},
213 287 'content': {'execution_state':'dead'}
214 288 }
215 289 )
216 290 self.on_close()
217 291
218 292
219 class ShellHandler(ZMQStreamHandler):
293 class ShellHandler(AuthenticatedZMQStreamHandler):
220 294
221 295 def initialize(self, *args, **kwargs):
222 296 self.shell_stream = None
223 297
224 def open(self, kernel_id):
298 def on_first_message(self, msg):
299 try:
300 super(ShellHandler, self).on_first_message(msg)
301 except web.HTTPError:
302 self.close()
303 return
225 304 km = self.application.kernel_manager
226 305 self.max_msg_size = km.max_msg_size
227 self.kernel_id = kernel_id
306 kernel_id = self.kernel_id
228 307 try:
229 308 self.shell_stream = km.create_shell_stream(kernel_id)
230 309 except web.HTTPError:
231 310 # WebSockets don't response to traditional error codes so we
232 311 # close the connection.
233 312 if not self.stream.closed():
234 313 self.stream.close()
314 self.close()
235 315 else:
236 self.session = Session()
237 316 self.shell_stream.on_recv(self._on_zmq_reply)
238 317
239 318 def on_message(self, msg):
240 319 if len(msg) < self.max_msg_size:
241 320 msg = jsonapi.loads(msg)
242 321 self.session.send(self.shell_stream, msg)
243 322
244 323 def on_close(self):
245 324 # Make sure the stream exists and is not already closed.
246 325 if self.shell_stream is not None and not self.shell_stream.closed():
247 326 self.shell_stream.close()
248 327
249 328
250 329 #-----------------------------------------------------------------------------
251 330 # Notebook web service handlers
252 331 #-----------------------------------------------------------------------------
253 332
254 class NotebookRootHandler(web.RequestHandler):
333 class NotebookRootHandler(AuthenticatedHandler):
255 334
335 @web.authenticated
256 336 def get(self):
257 337 nbm = self.application.notebook_manager
258 338 files = nbm.list_notebooks()
259 339 self.finish(jsonapi.dumps(files))
260 340
341 @web.authenticated
261 342 def post(self):
262 343 nbm = self.application.notebook_manager
263 344 body = self.request.body.strip()
264 345 format = self.get_argument('format', default='json')
265 346 name = self.get_argument('name', default=None)
266 347 if body:
267 348 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
268 349 else:
269 350 notebook_id = nbm.new_notebook()
270 351 self.set_header('Location', '/'+notebook_id)
271 352 self.finish(jsonapi.dumps(notebook_id))
272 353
273 354
274 class NotebookHandler(web.RequestHandler):
355 class NotebookHandler(AuthenticatedHandler):
275 356
276 357 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
277 358
359 @web.authenticated
278 360 def get(self, notebook_id):
279 361 nbm = self.application.notebook_manager
280 362 format = self.get_argument('format', default='json')
281 363 last_mod, name, data = nbm.get_notebook(notebook_id, format)
282 364 if format == u'json':
283 365 self.set_header('Content-Type', 'application/json')
284 366 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
285 367 elif format == u'py':
286 368 self.set_header('Content-Type', 'application/x-python')
287 369 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
288 370 self.set_header('Last-Modified', last_mod)
289 371 self.finish(data)
290 372
373 @web.authenticated
291 374 def put(self, notebook_id):
292 375 nbm = self.application.notebook_manager
293 376 format = self.get_argument('format', default='json')
294 377 name = self.get_argument('name', default=None)
295 378 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
296 379 self.set_status(204)
297 380 self.finish()
298 381
382 @web.authenticated
299 383 def delete(self, notebook_id):
300 384 nbm = self.application.notebook_manager
301 385 nbm.delete_notebook(notebook_id)
302 386 self.set_status(204)
303 387 self.finish()
304 388
305 389 #-----------------------------------------------------------------------------
306 390 # RST web service handlers
307 391 #-----------------------------------------------------------------------------
308 392
309 393
310 class RSTHandler(web.RequestHandler):
394 class RSTHandler(AuthenticatedHandler):
311 395
396 @web.authenticated
312 397 def post(self):
313 398 if publish_string is None:
314 399 raise web.HTTPError(503, u'docutils not available')
315 400 body = self.request.body.strip()
316 401 source = body
317 402 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
318 403 defaults = {'file_insertion_enabled': 0,
319 404 'raw_enabled': 0,
320 405 '_disable_config': 1,
321 406 'stylesheet_path': 0
322 407 # 'template': template_path
323 408 }
324 409 try:
325 410 html = publish_string(source, writer_name='html',
326 411 settings_overrides=defaults
327 412 )
328 413 except:
329 414 raise web.HTTPError(400, u'Invalid RST')
330 415 print html
331 416 self.set_header('Content-Type', 'text/html')
332 417 self.finish(html)
333 418
334 419
@@ -1,280 +1,288
1 1 """A tornado based IPython notebook server.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import errno
20 20 import logging
21 21 import os
22 22 import signal
23 23 import socket
24 24 import sys
25 25
26 26 import zmq
27 27
28 28 # Install the pyzmq ioloop. This has to be done before anything else from
29 29 # tornado is imported.
30 30 from zmq.eventloop import ioloop
31 31 import tornado.ioloop
32 32 tornado.ioloop = ioloop
33 33
34 34 from tornado import httpserver
35 35 from tornado import web
36 36
37 37 from .kernelmanager import MappingKernelManager
38 from .handlers import (
38 from .handlers import (LoginHandler,
39 39 NBBrowserHandler, NewHandler, NamedNotebookHandler,
40 40 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
41 41 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
42 42 )
43 43 from .notebookmanager import NotebookManager
44 44
45 45 from IPython.core.application import BaseIPythonApplication
46 46 from IPython.core.profiledir import ProfileDir
47 47 from IPython.zmq.session import Session
48 48 from IPython.zmq.zmqshell import ZMQInteractiveShell
49 49 from IPython.zmq.ipkernel import (
50 50 flags as ipkernel_flags,
51 51 aliases as ipkernel_aliases,
52 52 IPKernelApp
53 53 )
54 54 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Module globals
58 58 #-----------------------------------------------------------------------------
59 59
60 60 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
61 61 _kernel_action_regex = r"(?P<action>restart|interrupt)"
62 62 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
63 63
64 64 LOCALHOST = '127.0.0.1'
65 65
66 66 _examples = """
67 67 ipython notebook # start the notebook
68 68 ipython notebook --profile=sympy # use the sympy profile
69 69 ipython notebook --pylab=inline # pylab in inline plotting mode
70 70 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
71 71 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
72 72 """
73 73
74 74 #-----------------------------------------------------------------------------
75 75 # The Tornado web application
76 76 #-----------------------------------------------------------------------------
77 77
78 78 class NotebookWebApplication(web.Application):
79 79
80 80 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
81 81 handlers = [
82 82 (r"/", NBBrowserHandler),
83 (r"/login", LoginHandler),
83 84 (r"/new", NewHandler),
84 85 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
85 86 (r"/kernels", MainKernelHandler),
86 87 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
87 88 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
88 89 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
89 90 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
90 91 (r"/notebooks", NotebookRootHandler),
91 92 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
92 93 (r"/rstservice/render", RSTHandler)
93 94 ]
94 95 settings = dict(
95 96 template_path=os.path.join(os.path.dirname(__file__), "templates"),
96 97 static_path=os.path.join(os.path.dirname(__file__), "static"),
98 cookie_secret=os.urandom(1024),
99 login_url="/login",
97 100 )
98 101 web.Application.__init__(self, handlers, **settings)
99 102
100 103 self.kernel_manager = kernel_manager
101 104 self.log = log
102 105 self.notebook_manager = notebook_manager
103 106 self.ipython_app = ipython_app
104 107
105 108
106 109 #-----------------------------------------------------------------------------
107 110 # Aliases and Flags
108 111 #-----------------------------------------------------------------------------
109 112
110 113 flags = dict(ipkernel_flags)
111 114
112 115 # the flags that are specific to the frontend
113 116 # these must be scrubbed before being passed to the kernel,
114 117 # or it will raise an error on unrecognized flags
115 118 notebook_flags = []
116 119
117 120 aliases = dict(ipkernel_aliases)
118 121
119 122 aliases.update({
120 123 'ip': 'IPythonNotebookApp.ip',
121 124 'port': 'IPythonNotebookApp.port',
122 125 'keyfile': 'IPythonNotebookApp.keyfile',
123 126 'certfile': 'IPythonNotebookApp.certfile',
124 127 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
125 'notebook-dir': 'NotebookManager.notebook_dir'
128 'notebook-dir': 'NotebookManager.notebook_dir',
126 129 })
127 130
128 131 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
129 132 u'notebook-dir']
130 133
131 134 #-----------------------------------------------------------------------------
132 135 # IPythonNotebookApp
133 136 #-----------------------------------------------------------------------------
134 137
135 138 class IPythonNotebookApp(BaseIPythonApplication):
136 139
137 140 name = 'ipython-notebook'
138 141 default_config_file_name='ipython_notebook_config.py'
139 142
140 143 description = """
141 144 The IPython HTML Notebook.
142 145
143 146 This launches a Tornado based HTML Notebook Server that serves up an
144 147 HTML5/Javascript Notebook client.
145 148 """
146 149 examples = _examples
147 150
148 151 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
149 152 MappingKernelManager, NotebookManager]
150 153 flags = Dict(flags)
151 154 aliases = Dict(aliases)
152 155
153 156 kernel_argv = List(Unicode)
154 157
155 158 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
156 159 default_value=logging.INFO,
157 160 config=True,
158 161 help="Set the log level by value or name.")
159 162
160 163 # Network related information.
161 164
162 165 ip = Unicode(LOCALHOST, config=True,
163 166 help="The IP address the notebook server will listen on."
164 167 )
165 168
166 169 def _ip_changed(self, name, old, new):
167 170 if new == u'*': self.ip = u''
168 171
169 172 port = Int(8888, config=True,
170 173 help="The port the notebook server will listen on."
171 174 )
172 175
173 176 ws_hostname = Unicode(LOCALHOST, config=True,
174 177 help="""The FQDN or IP for WebSocket connections. The default will work
175 178 fine when the server is listening on localhost, but this needs to
176 179 be set if the ip option is used. It will be used as the hostname part
177 180 of the WebSocket url: ws://hostname/path."""
178 181 )
179 182
180 183 certfile = Unicode(u'', config=True,
181 184 help="""The full path to an SSL/TLS certificate file."""
182 185 )
183 186
184 187 keyfile = Unicode(u'', config=True,
185 188 help="""The full path to a private key file for usage with SSL/TLS."""
186 189 )
187 190
191 password = Unicode(u'', config=True,
192 help="""Password to use for web authentication"""
193 )
194
188 195 def get_ws_url(self):
189 196 """Return the WebSocket URL for this server."""
190 197 if self.certfile:
191 198 prefix = u'wss://'
192 199 else:
193 200 prefix = u'ws://'
194 201 return prefix + self.ws_hostname + u':' + unicode(self.port)
195 202
196 203 def parse_command_line(self, argv=None):
197 204 super(IPythonNotebookApp, self).parse_command_line(argv)
198 205 if argv is None:
199 206 argv = sys.argv[1:]
200 207
201 208 self.kernel_argv = list(argv) # copy
202 209 # Kernel should inherit default config file from frontend
203 210 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
204 211 # Scrub frontend-specific flags
205 212 for a in argv:
206 213 if a.startswith('-') and a.lstrip('-') in notebook_flags:
207 214 self.kernel_argv.remove(a)
208 215 for a in argv:
209 216 if a.startswith('-'):
210 217 alias = a.lstrip('-').split('=')[0]
211 218 if alias in notebook_aliases:
212 219 self.kernel_argv.remove(a)
213 220
214 221 def init_configurables(self):
215 222 # Don't let Qt or ZMQ swallow KeyboardInterupts.
216 223 signal.signal(signal.SIGINT, signal.SIG_DFL)
217 224
218 225 # Create a KernelManager and start a kernel.
219 226 self.kernel_manager = MappingKernelManager(
220 227 config=self.config, log=self.log, kernel_argv=self.kernel_argv
221 228 )
222 229 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
223 230 self.notebook_manager.list_notebooks()
224 231
225 232 def init_logging(self):
226 233 super(IPythonNotebookApp, self).init_logging()
227 234 # This prevents double log messages because tornado use a root logger that
228 235 # self.log is a child of. The logging module dipatches log messages to a log
229 236 # and all of its ancenstors until propagate is set to False.
230 237 self.log.propagate = False
231 238
232 239 def initialize(self, argv=None):
233 240 super(IPythonNotebookApp, self).initialize(argv)
234 241 self.init_configurables()
235 242 self.web_app = NotebookWebApplication(
236 243 self, self.kernel_manager, self.notebook_manager, self.log
237 244 )
238 245 if self.certfile:
239 246 ssl_options = dict(certfile=self.certfile)
240 247 if self.keyfile:
241 248 ssl_options['keyfile'] = self.keyfile
242 249 else:
243 250 ssl_options = None
251 self.web_app.password = self.password
244 252 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
245 253 if ssl_options is None and not self.ip:
246 254 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
247 255 'but not using any encryption or authentication. This is highly '
248 256 'insecure and not recommended.')
249 257
250 258 # Try random ports centered around the default.
251 259 from random import randint
252 260 n = 50 # Max number of attempts, keep reasonably large.
253 261 for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
254 262 try:
255 263 self.http_server.listen(port, self.ip)
256 264 except socket.error, e:
257 265 if e.errno != errno.EADDRINUSE:
258 266 raise
259 267 self.log.info('The port %i is already in use, trying another random port.' % port)
260 268 else:
261 269 self.port = port
262 270 break
263 271
264 272 def start(self):
265 273 ip = self.ip if self.ip else '[all ip addresses on your system]'
266 274 proto = 'https' if self.certfile else 'http'
267 275 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
268 276 ip,
269 277 self.port))
270 278 ioloop.IOLoop.instance().start()
271 279
272 280 #-----------------------------------------------------------------------------
273 281 # Main entry point
274 282 #-----------------------------------------------------------------------------
275 283
276 284 def launch_new_instance():
277 285 app = IPythonNotebookApp()
278 286 app.initialize()
279 287 app.start()
280 288
@@ -1,160 +1,166
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 var Kernel = function () {
17 17 this.kernel_id = null;
18 18 this.base_url = "/kernels";
19 19 this.kernel_url = null;
20 20 this.shell_channel = null;
21 21 this.iopub_channel = null;
22 22 this.running = false;
23 23
24 24 this.username = "username";
25 25 this.session_id = utils.uuid();
26 26
27 27 if (typeof(WebSocket) !== 'undefined') {
28 28 this.WebSocket = WebSocket
29 29 } else if (typeof(MozWebSocket) !== 'undefined') {
30 30 this.WebSocket = MozWebSocket
31 31 } else {
32 32 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.');
33 33 };
34 34 };
35 35
36 36
37 37 Kernel.prototype.get_msg = function (msg_type, content) {
38 38 var msg = {
39 39 header : {
40 40 msg_id : utils.uuid(),
41 41 username : this.username,
42 42 session : this.session_id,
43 43 msg_type : msg_type
44 44 },
45 45 content : content,
46 46 parent_header : {}
47 47 };
48 48 return msg;
49 49 }
50 50
51 51 Kernel.prototype.start = function (notebook_id, callback) {
52 52 var that = this;
53 53 if (!this.running) {
54 54 var qs = $.param({notebook:notebook_id});
55 55 $.post(this.base_url + '?' + qs,
56 56 function (kernel_id) {
57 57 that._handle_start_kernel(kernel_id, callback);
58 58 },
59 59 'json'
60 60 );
61 61 };
62 62 };
63 63
64 64
65 65 Kernel.prototype.restart = function (callback) {
66 66 IPython.kernel_status_widget.status_restarting();
67 67 var url = this.kernel_url + "/restart";
68 68 var that = this;
69 69 if (this.running) {
70 70 this.stop_channels();
71 71 $.post(url,
72 72 function (kernel_id) {
73 73 that._handle_start_kernel(kernel_id, callback);
74 74 },
75 75 'json'
76 76 );
77 77 };
78 78 };
79 79
80 80
81 81 Kernel.prototype._handle_start_kernel = function (json, callback) {
82 82 this.running = true;
83 83 this.kernel_id = json.kernel_id;
84 84 this.ws_url = json.ws_url;
85 85 this.kernel_url = this.base_url + "/" + this.kernel_id;
86 86 this.start_channels();
87 87 callback();
88 88 IPython.kernel_status_widget.status_idle();
89 89 };
90 90
91 91
92 92 Kernel.prototype.start_channels = function () {
93 93 this.stop_channels();
94 94 var ws_url = this.ws_url + this.kernel_url;
95 95 console.log("Starting WS:", ws_url);
96 96 this.shell_channel = new this.WebSocket(ws_url + "/shell");
97 97 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
98 send_cookie = function(){
99 this.send(document.cookie);
100 console.log(this);
101 }
102 this.shell_channel.onopen = send_cookie;
103 this.iopub_channel.onopen = send_cookie;
98 104 };
99 105
100 106
101 107 Kernel.prototype.stop_channels = function () {
102 108 if (this.shell_channel !== null) {
103 109 this.shell_channel.close();
104 110 this.shell_channel = null;
105 111 };
106 112 if (this.iopub_channel !== null) {
107 113 this.iopub_channel.close();
108 114 this.iopub_channel = null;
109 115 };
110 116 };
111 117
112 118 Kernel.prototype.execute = function (code) {
113 119 var content = {
114 120 code : code,
115 121 silent : false,
116 122 user_variables : [],
117 123 user_expressions : {}
118 124 };
119 125 var msg = this.get_msg("execute_request", content);
120 126 this.shell_channel.send(JSON.stringify(msg));
121 127 return msg.header.msg_id;
122 128 }
123 129
124 130
125 131 Kernel.prototype.complete = function (line, cursor_pos) {
126 132 var content = {
127 133 text : '',
128 134 line : line,
129 135 cursor_pos : cursor_pos
130 136 };
131 137 var msg = this.get_msg("complete_request", content);
132 138 this.shell_channel.send(JSON.stringify(msg));
133 139 return msg.header.msg_id;
134 140 }
135 141
136 142
137 143 Kernel.prototype.interrupt = function () {
138 144 if (this.running) {
139 145 $.post(this.kernel_url + "/interrupt");
140 146 };
141 147 };
142 148
143 149
144 150 Kernel.prototype.kill = function () {
145 151 if (this.running) {
146 152 this.running = false;
147 153 var settings = {
148 154 cache : false,
149 155 type : "DELETE",
150 156 };
151 157 $.ajax(this.kernel_url, settings);
152 158 };
153 159 };
154 160
155 161 IPython.Kernel = Kernel;
156 162
157 163 return IPython;
158 164
159 165 }(IPython));
160 166
General Comments 0
You need to be logged in to leave comments. Login now