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