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