##// END OF EJS Templates
don't assume ioloop.install is defined (pyzmq < 2.1.7)
MinRK -
Show More
@@ -1,332 +1,338 b''
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import signal
23 import signal
24 import socket
24 import socket
25 import sys
25 import sys
26 import threading
26 import threading
27 import webbrowser
27 import webbrowser
28
28
29 # Third party
29 # Third party
30 import zmq
30 import zmq
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 # when pyzmq dependency is updated beyond that.
37 if hasattr(ioloop, 'install'):
35 ioloop.install()
38 ioloop.install()
39 else:
40 import tornado.ioloop
41 tornado.ioloop.IOLoop = ioloop.IOLoop
36
42
37 from tornado import httpserver
43 from tornado import httpserver
38 from tornado import web
44 from tornado import web
39
45
40 # Our own libraries
46 # Our own libraries
41 from .kernelmanager import MappingKernelManager
47 from .kernelmanager import MappingKernelManager
42 from .handlers import (LoginHandler, LogoutHandler,
48 from .handlers import (LoginHandler, LogoutHandler,
43 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
44 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
45 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
46 )
52 )
47 from .notebookmanager import NotebookManager
53 from .notebookmanager import NotebookManager
48
54
49 from IPython.config.application import catch_config_error
55 from IPython.config.application import catch_config_error
50 from IPython.core.application import BaseIPythonApplication
56 from IPython.core.application import BaseIPythonApplication
51 from IPython.core.profiledir import ProfileDir
57 from IPython.core.profiledir import ProfileDir
52 from IPython.zmq.session import Session, default_secure
58 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
59 from IPython.zmq.zmqshell import ZMQInteractiveShell
54 from IPython.zmq.ipkernel import (
60 from IPython.zmq.ipkernel import (
55 flags as ipkernel_flags,
61 flags as ipkernel_flags,
56 aliases as ipkernel_aliases,
62 aliases as ipkernel_aliases,
57 IPKernelApp
63 IPKernelApp
58 )
64 )
59 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
65 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
60
66
61 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
62 # Module globals
68 # Module globals
63 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
64
70
65 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
71 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
66 _kernel_action_regex = r"(?P<action>restart|interrupt)"
72 _kernel_action_regex = r"(?P<action>restart|interrupt)"
67 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
73 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
68
74
69 LOCALHOST = '127.0.0.1'
75 LOCALHOST = '127.0.0.1'
70
76
71 _examples = """
77 _examples = """
72 ipython notebook # start the notebook
78 ipython notebook # start the notebook
73 ipython notebook --profile=sympy # use the sympy profile
79 ipython notebook --profile=sympy # use the sympy profile
74 ipython notebook --pylab=inline # pylab in inline plotting mode
80 ipython notebook --pylab=inline # pylab in inline plotting mode
75 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
81 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
76 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
82 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
77 """
83 """
78
84
79 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
80 # The Tornado web application
86 # The Tornado web application
81 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
82
88
83 class NotebookWebApplication(web.Application):
89 class NotebookWebApplication(web.Application):
84
90
85 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
91 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
86 handlers = [
92 handlers = [
87 (r"/", ProjectDashboardHandler),
93 (r"/", ProjectDashboardHandler),
88 (r"/login", LoginHandler),
94 (r"/login", LoginHandler),
89 (r"/logout", LogoutHandler),
95 (r"/logout", LogoutHandler),
90 (r"/new", NewHandler),
96 (r"/new", NewHandler),
91 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
97 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
92 (r"/kernels", MainKernelHandler),
98 (r"/kernels", MainKernelHandler),
93 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
99 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
94 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
100 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
95 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
101 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
96 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
102 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
97 (r"/notebooks", NotebookRootHandler),
103 (r"/notebooks", NotebookRootHandler),
98 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
104 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
99 (r"/rstservice/render", RSTHandler)
105 (r"/rstservice/render", RSTHandler)
100 ]
106 ]
101 settings = dict(
107 settings = dict(
102 template_path=os.path.join(os.path.dirname(__file__), "templates"),
108 template_path=os.path.join(os.path.dirname(__file__), "templates"),
103 static_path=os.path.join(os.path.dirname(__file__), "static"),
109 static_path=os.path.join(os.path.dirname(__file__), "static"),
104 cookie_secret=os.urandom(1024),
110 cookie_secret=os.urandom(1024),
105 login_url="/login",
111 login_url="/login",
106 )
112 )
107 web.Application.__init__(self, handlers, **settings)
113 web.Application.__init__(self, handlers, **settings)
108
114
109 self.kernel_manager = kernel_manager
115 self.kernel_manager = kernel_manager
110 self.log = log
116 self.log = log
111 self.notebook_manager = notebook_manager
117 self.notebook_manager = notebook_manager
112 self.ipython_app = ipython_app
118 self.ipython_app = ipython_app
113 self.read_only = self.ipython_app.read_only
119 self.read_only = self.ipython_app.read_only
114
120
115
121
116 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
117 # Aliases and Flags
123 # Aliases and Flags
118 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
119
125
120 flags = dict(ipkernel_flags)
126 flags = dict(ipkernel_flags)
121 flags['no-browser']=(
127 flags['no-browser']=(
122 {'NotebookApp' : {'open_browser' : False}},
128 {'NotebookApp' : {'open_browser' : False}},
123 "Don't open the notebook in a browser after startup."
129 "Don't open the notebook in a browser after startup."
124 )
130 )
125 flags['read-only'] = (
131 flags['read-only'] = (
126 {'NotebookApp' : {'read_only' : True}},
132 {'NotebookApp' : {'read_only' : True}},
127 """Allow read-only access to notebooks.
133 """Allow read-only access to notebooks.
128
134
129 When using a password to protect the notebook server, this flag
135 When using a password to protect the notebook server, this flag
130 allows unauthenticated clients to view the notebook list, and
136 allows unauthenticated clients to view the notebook list, and
131 individual notebooks, but not edit them, start kernels, or run
137 individual notebooks, but not edit them, start kernels, or run
132 code.
138 code.
133
139
134 If no password is set, the server will be entirely read-only.
140 If no password is set, the server will be entirely read-only.
135 """
141 """
136 )
142 )
137
143
138 # the flags that are specific to the frontend
144 # the flags that are specific to the frontend
139 # these must be scrubbed before being passed to the kernel,
145 # these must be scrubbed before being passed to the kernel,
140 # or it will raise an error on unrecognized flags
146 # or it will raise an error on unrecognized flags
141 notebook_flags = ['no-browser', 'read-only']
147 notebook_flags = ['no-browser', 'read-only']
142
148
143 aliases = dict(ipkernel_aliases)
149 aliases = dict(ipkernel_aliases)
144
150
145 aliases.update({
151 aliases.update({
146 'ip': 'NotebookApp.ip',
152 'ip': 'NotebookApp.ip',
147 'port': 'NotebookApp.port',
153 'port': 'NotebookApp.port',
148 'keyfile': 'NotebookApp.keyfile',
154 'keyfile': 'NotebookApp.keyfile',
149 'certfile': 'NotebookApp.certfile',
155 'certfile': 'NotebookApp.certfile',
150 'notebook-dir': 'NotebookManager.notebook_dir',
156 'notebook-dir': 'NotebookManager.notebook_dir',
151 })
157 })
152
158
153 # remove ipkernel flags that are singletons, and don't make sense in
159 # remove ipkernel flags that are singletons, and don't make sense in
154 # multi-kernel evironment:
160 # multi-kernel evironment:
155 aliases.pop('f', None)
161 aliases.pop('f', None)
156
162
157 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
163 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
158 u'notebook-dir']
164 u'notebook-dir']
159
165
160 #-----------------------------------------------------------------------------
166 #-----------------------------------------------------------------------------
161 # NotebookApp
167 # NotebookApp
162 #-----------------------------------------------------------------------------
168 #-----------------------------------------------------------------------------
163
169
164 class NotebookApp(BaseIPythonApplication):
170 class NotebookApp(BaseIPythonApplication):
165
171
166 name = 'ipython-notebook'
172 name = 'ipython-notebook'
167 default_config_file_name='ipython_notebook_config.py'
173 default_config_file_name='ipython_notebook_config.py'
168
174
169 description = """
175 description = """
170 The IPython HTML Notebook.
176 The IPython HTML Notebook.
171
177
172 This launches a Tornado based HTML Notebook Server that serves up an
178 This launches a Tornado based HTML Notebook Server that serves up an
173 HTML5/Javascript Notebook client.
179 HTML5/Javascript Notebook client.
174 """
180 """
175 examples = _examples
181 examples = _examples
176
182
177 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
183 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
178 MappingKernelManager, NotebookManager]
184 MappingKernelManager, NotebookManager]
179 flags = Dict(flags)
185 flags = Dict(flags)
180 aliases = Dict(aliases)
186 aliases = Dict(aliases)
181
187
182 kernel_argv = List(Unicode)
188 kernel_argv = List(Unicode)
183
189
184 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
190 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
185 default_value=logging.INFO,
191 default_value=logging.INFO,
186 config=True,
192 config=True,
187 help="Set the log level by value or name.")
193 help="Set the log level by value or name.")
188
194
189 # Network related information.
195 # Network related information.
190
196
191 ip = Unicode(LOCALHOST, config=True,
197 ip = Unicode(LOCALHOST, config=True,
192 help="The IP address the notebook server will listen on."
198 help="The IP address the notebook server will listen on."
193 )
199 )
194
200
195 def _ip_changed(self, name, old, new):
201 def _ip_changed(self, name, old, new):
196 if new == u'*': self.ip = u''
202 if new == u'*': self.ip = u''
197
203
198 port = Integer(8888, config=True,
204 port = Integer(8888, config=True,
199 help="The port the notebook server will listen on."
205 help="The port the notebook server will listen on."
200 )
206 )
201
207
202 certfile = Unicode(u'', config=True,
208 certfile = Unicode(u'', config=True,
203 help="""The full path to an SSL/TLS certificate file."""
209 help="""The full path to an SSL/TLS certificate file."""
204 )
210 )
205
211
206 keyfile = Unicode(u'', config=True,
212 keyfile = Unicode(u'', config=True,
207 help="""The full path to a private key file for usage with SSL/TLS."""
213 help="""The full path to a private key file for usage with SSL/TLS."""
208 )
214 )
209
215
210 password = Unicode(u'', config=True,
216 password = Unicode(u'', config=True,
211 help="""Hashed password to use for web authentication.
217 help="""Hashed password to use for web authentication.
212
218
213 To generate, type in a python/IPython shell:
219 To generate, type in a python/IPython shell:
214
220
215 from IPython.lib import passwd; passwd()
221 from IPython.lib import passwd; passwd()
216
222
217 The string should be of the form type:salt:hashed-password.
223 The string should be of the form type:salt:hashed-password.
218 """
224 """
219 )
225 )
220
226
221 open_browser = Bool(True, config=True,
227 open_browser = Bool(True, config=True,
222 help="Whether to open in a browser after starting.")
228 help="Whether to open in a browser after starting.")
223
229
224 read_only = Bool(False, config=True,
230 read_only = Bool(False, config=True,
225 help="Whether to prevent editing/execution of notebooks."
231 help="Whether to prevent editing/execution of notebooks."
226 )
232 )
227
233
228 def parse_command_line(self, argv=None):
234 def parse_command_line(self, argv=None):
229 super(NotebookApp, self).parse_command_line(argv)
235 super(NotebookApp, self).parse_command_line(argv)
230 if argv is None:
236 if argv is None:
231 argv = sys.argv[1:]
237 argv = sys.argv[1:]
232
238
233 self.kernel_argv = list(argv) # copy
239 self.kernel_argv = list(argv) # copy
234 # Kernel should inherit default config file from frontend
240 # Kernel should inherit default config file from frontend
235 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
241 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
236 # Scrub frontend-specific flags
242 # Scrub frontend-specific flags
237 for a in argv:
243 for a in argv:
238 if a.startswith('-') and a.lstrip('-') in notebook_flags:
244 if a.startswith('-') and a.lstrip('-') in notebook_flags:
239 self.kernel_argv.remove(a)
245 self.kernel_argv.remove(a)
240 swallow_next = False
246 swallow_next = False
241 for a in argv:
247 for a in argv:
242 if swallow_next:
248 if swallow_next:
243 self.kernel_argv.remove(a)
249 self.kernel_argv.remove(a)
244 swallow_next = False
250 swallow_next = False
245 continue
251 continue
246 if a.startswith('-'):
252 if a.startswith('-'):
247 split = a.lstrip('-').split('=')
253 split = a.lstrip('-').split('=')
248 alias = split[0]
254 alias = split[0]
249 if alias in notebook_aliases:
255 if alias in notebook_aliases:
250 self.kernel_argv.remove(a)
256 self.kernel_argv.remove(a)
251 if len(split) == 1:
257 if len(split) == 1:
252 # alias passed with arg via space
258 # alias passed with arg via space
253 swallow_next = True
259 swallow_next = True
254
260
255 def init_configurables(self):
261 def init_configurables(self):
256 # Don't let Qt or ZMQ swallow KeyboardInterupts.
262 # Don't let Qt or ZMQ swallow KeyboardInterupts.
257 signal.signal(signal.SIGINT, signal.SIG_DFL)
263 signal.signal(signal.SIGINT, signal.SIG_DFL)
258
264
259 # force Session default to be secure
265 # force Session default to be secure
260 default_secure(self.config)
266 default_secure(self.config)
261 # Create a KernelManager and start a kernel.
267 # Create a KernelManager and start a kernel.
262 self.kernel_manager = MappingKernelManager(
268 self.kernel_manager = MappingKernelManager(
263 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
269 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
264 connection_dir = self.profile_dir.security_dir,
270 connection_dir = self.profile_dir.security_dir,
265 )
271 )
266 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
272 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
267 self.notebook_manager.list_notebooks()
273 self.notebook_manager.list_notebooks()
268
274
269 def init_logging(self):
275 def init_logging(self):
270 super(NotebookApp, self).init_logging()
276 super(NotebookApp, self).init_logging()
271 # This prevents double log messages because tornado use a root logger that
277 # This prevents double log messages because tornado use a root logger that
272 # self.log is a child of. The logging module dipatches log messages to a log
278 # self.log is a child of. The logging module dipatches log messages to a log
273 # and all of its ancenstors until propagate is set to False.
279 # and all of its ancenstors until propagate is set to False.
274 self.log.propagate = False
280 self.log.propagate = False
275
281
276 @catch_config_error
282 @catch_config_error
277 def initialize(self, argv=None):
283 def initialize(self, argv=None):
278 super(NotebookApp, self).initialize(argv)
284 super(NotebookApp, self).initialize(argv)
279 self.init_configurables()
285 self.init_configurables()
280 self.web_app = NotebookWebApplication(
286 self.web_app = NotebookWebApplication(
281 self, self.kernel_manager, self.notebook_manager, self.log
287 self, self.kernel_manager, self.notebook_manager, self.log
282 )
288 )
283 if self.certfile:
289 if self.certfile:
284 ssl_options = dict(certfile=self.certfile)
290 ssl_options = dict(certfile=self.certfile)
285 if self.keyfile:
291 if self.keyfile:
286 ssl_options['keyfile'] = self.keyfile
292 ssl_options['keyfile'] = self.keyfile
287 else:
293 else:
288 ssl_options = None
294 ssl_options = None
289 self.web_app.password = self.password
295 self.web_app.password = self.password
290 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
296 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
291 if ssl_options is None and not self.ip:
297 if ssl_options is None and not self.ip:
292 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
298 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
293 'but not using any encryption or authentication. This is highly '
299 'but not using any encryption or authentication. This is highly '
294 'insecure and not recommended.')
300 'insecure and not recommended.')
295
301
296 # Try random ports centered around the default.
302 # Try random ports centered around the default.
297 from random import randint
303 from random import randint
298 n = 50 # Max number of attempts, keep reasonably large.
304 n = 50 # Max number of attempts, keep reasonably large.
299 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
305 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
300 try:
306 try:
301 self.http_server.listen(port, self.ip)
307 self.http_server.listen(port, self.ip)
302 except socket.error, e:
308 except socket.error, e:
303 if e.errno != errno.EADDRINUSE:
309 if e.errno != errno.EADDRINUSE:
304 raise
310 raise
305 self.log.info('The port %i is already in use, trying another random port.' % port)
311 self.log.info('The port %i is already in use, trying another random port.' % port)
306 else:
312 else:
307 self.port = port
313 self.port = port
308 break
314 break
309
315
310 def start(self):
316 def start(self):
311 ip = self.ip if self.ip else '[all ip addresses on your system]'
317 ip = self.ip if self.ip else '[all ip addresses on your system]'
312 proto = 'https' if self.certfile else 'http'
318 proto = 'https' if self.certfile else 'http'
313 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
319 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
314 ip,
320 ip,
315 self.port))
321 self.port))
316 if self.open_browser:
322 if self.open_browser:
317 ip = self.ip or '127.0.0.1'
323 ip = self.ip or '127.0.0.1'
318 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
324 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
319 new=2)
325 new=2)
320 threading.Thread(target=b).start()
326 threading.Thread(target=b).start()
321
327
322 ioloop.IOLoop.instance().start()
328 ioloop.IOLoop.instance().start()
323
329
324 #-----------------------------------------------------------------------------
330 #-----------------------------------------------------------------------------
325 # Main entry point
331 # Main entry point
326 #-----------------------------------------------------------------------------
332 #-----------------------------------------------------------------------------
327
333
328 def launch_new_instance():
334 def launch_new_instance():
329 app = NotebookApp()
335 app = NotebookApp()
330 app.initialize()
336 app.initialize()
331 app.start()
337 app.start()
332
338
General Comments 0
You need to be logged in to leave comments. Login now