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