##// END OF EJS Templates
Backport PR #6105: only set allow_origin_pat if defined...
MinRK -
Show More
@@ -1,882 +1,883 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import re
27 import re
28 import select
28 import select
29 import signal
29 import signal
30 import socket
30 import socket
31 import sys
31 import sys
32 import threading
32 import threading
33 import time
33 import time
34 import webbrowser
34 import webbrowser
35
35
36
36
37 # Third party
37 # Third party
38 # check for pyzmq 2.1.11
38 # check for pyzmq 2.1.11
39 from IPython.utils.zmqrelated import check_for_zmq
39 from IPython.utils.zmqrelated import check_for_zmq
40 check_for_zmq('2.1.11', 'IPython.html')
40 check_for_zmq('2.1.11', 'IPython.html')
41
41
42 from jinja2 import Environment, FileSystemLoader
42 from jinja2 import Environment, FileSystemLoader
43
43
44 # Install the pyzmq ioloop. This has to be done before anything else from
44 # Install the pyzmq ioloop. This has to be done before anything else from
45 # tornado is imported.
45 # tornado is imported.
46 from zmq.eventloop import ioloop
46 from zmq.eventloop import ioloop
47 ioloop.install()
47 ioloop.install()
48
48
49 # check for tornado 3.1.0
49 # check for tornado 3.1.0
50 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 msg = "The IPython Notebook requires tornado >= 3.1.0"
51 try:
51 try:
52 import tornado
52 import tornado
53 except ImportError:
53 except ImportError:
54 raise ImportError(msg)
54 raise ImportError(msg)
55 try:
55 try:
56 version_info = tornado.version_info
56 version_info = tornado.version_info
57 except AttributeError:
57 except AttributeError:
58 raise ImportError(msg + ", but you have < 1.1.0")
58 raise ImportError(msg + ", but you have < 1.1.0")
59 if version_info < (3,1,0):
59 if version_info < (3,1,0):
60 raise ImportError(msg + ", but you have %s" % tornado.version)
60 raise ImportError(msg + ", but you have %s" % tornado.version)
61
61
62 from tornado import httpserver
62 from tornado import httpserver
63 from tornado import web
63 from tornado import web
64
64
65 # Our own libraries
65 # Our own libraries
66 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from IPython.html import DEFAULT_STATIC_FILES_PATH
67 from .base.handlers import Template404
67 from .base.handlers import Template404
68 from .log import log_request
68 from .log import log_request
69 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.kernels.kernelmanager import MappingKernelManager
70 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.nbmanager import NotebookManager
71 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.notebooks.filenbmanager import FileNotebookManager
72 from .services.clusters.clustermanager import ClusterManager
72 from .services.clusters.clustermanager import ClusterManager
73 from .services.sessions.sessionmanager import SessionManager
73 from .services.sessions.sessionmanager import SessionManager
74
74
75 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
76
76
77 from IPython.config import Config
77 from IPython.config import Config
78 from IPython.config.application import catch_config_error, boolean_flag
78 from IPython.config.application import catch_config_error, boolean_flag
79 from IPython.core.application import BaseIPythonApplication
79 from IPython.core.application import BaseIPythonApplication
80 from IPython.core.profiledir import ProfileDir
80 from IPython.core.profiledir import ProfileDir
81 from IPython.consoleapp import IPythonConsoleApp
81 from IPython.consoleapp import IPythonConsoleApp
82 from IPython.kernel import swallow_argv
82 from IPython.kernel import swallow_argv
83 from IPython.kernel.zmq.session import default_secure
83 from IPython.kernel.zmq.session import default_secure
84 from IPython.kernel.zmq.kernelapp import (
84 from IPython.kernel.zmq.kernelapp import (
85 kernel_flags,
85 kernel_flags,
86 kernel_aliases,
86 kernel_aliases,
87 )
87 )
88 from IPython.nbformat.sign import NotebookNotary
88 from IPython.nbformat.sign import NotebookNotary
89 from IPython.utils.importstring import import_item
89 from IPython.utils.importstring import import_item
90 from IPython.utils import submodule
90 from IPython.utils import submodule
91 from IPython.utils.traitlets import (
91 from IPython.utils.traitlets import (
92 Dict, Unicode, Integer, List, Bool, Bytes,
92 Dict, Unicode, Integer, List, Bool, Bytes,
93 DottedObjectName, TraitError,
93 DottedObjectName, TraitError,
94 )
94 )
95 from IPython.utils import py3compat
95 from IPython.utils import py3compat
96 from IPython.utils.path import filefind, get_ipython_dir
96 from IPython.utils.path import filefind, get_ipython_dir
97
97
98 from .utils import url_path_join
98 from .utils import url_path_join
99
99
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101 # Module globals
101 # Module globals
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103
103
104 _examples = """
104 _examples = """
105 ipython notebook # start the notebook
105 ipython notebook # start the notebook
106 ipython notebook --profile=sympy # use the sympy profile
106 ipython notebook --profile=sympy # use the sympy profile
107 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
107 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
108 """
108 """
109
109
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111 # Helper functions
111 # Helper functions
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113
113
114 def random_ports(port, n):
114 def random_ports(port, n):
115 """Generate a list of n random ports near the given port.
115 """Generate a list of n random ports near the given port.
116
116
117 The first 5 ports will be sequential, and the remaining n-5 will be
117 The first 5 ports will be sequential, and the remaining n-5 will be
118 randomly selected in the range [port-2*n, port+2*n].
118 randomly selected in the range [port-2*n, port+2*n].
119 """
119 """
120 for i in range(min(5, n)):
120 for i in range(min(5, n)):
121 yield port + i
121 yield port + i
122 for i in range(n-5):
122 for i in range(n-5):
123 yield max(1, port + random.randint(-2*n, 2*n))
123 yield max(1, port + random.randint(-2*n, 2*n))
124
124
125 def load_handlers(name):
125 def load_handlers(name):
126 """Load the (URL pattern, handler) tuples for each component."""
126 """Load the (URL pattern, handler) tuples for each component."""
127 name = 'IPython.html.' + name
127 name = 'IPython.html.' + name
128 mod = __import__(name, fromlist=['default_handlers'])
128 mod = __import__(name, fromlist=['default_handlers'])
129 return mod.default_handlers
129 return mod.default_handlers
130
130
131 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
132 # The Tornado web application
132 # The Tornado web application
133 #-----------------------------------------------------------------------------
133 #-----------------------------------------------------------------------------
134
134
135 class NotebookWebApplication(web.Application):
135 class NotebookWebApplication(web.Application):
136
136
137 def __init__(self, ipython_app, kernel_manager, notebook_manager,
137 def __init__(self, ipython_app, kernel_manager, notebook_manager,
138 cluster_manager, session_manager, log, base_url,
138 cluster_manager, session_manager, log, base_url,
139 settings_overrides, jinja_env_options):
139 settings_overrides, jinja_env_options):
140
140
141 settings = self.init_settings(
141 settings = self.init_settings(
142 ipython_app, kernel_manager, notebook_manager, cluster_manager,
142 ipython_app, kernel_manager, notebook_manager, cluster_manager,
143 session_manager, log, base_url, settings_overrides, jinja_env_options)
143 session_manager, log, base_url, settings_overrides, jinja_env_options)
144 handlers = self.init_handlers(settings)
144 handlers = self.init_handlers(settings)
145
145
146 super(NotebookWebApplication, self).__init__(handlers, **settings)
146 super(NotebookWebApplication, self).__init__(handlers, **settings)
147
147
148 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
148 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
149 cluster_manager, session_manager, log, base_url,
149 cluster_manager, session_manager, log, base_url,
150 settings_overrides, jinja_env_options=None):
150 settings_overrides, jinja_env_options=None):
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 # base_url will always be unicode, which will in turn
152 # base_url will always be unicode, which will in turn
153 # make the patterns unicode, and ultimately result in unicode
153 # make the patterns unicode, and ultimately result in unicode
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 # This enforces that base_url be ascii in that situation.
155 # This enforces that base_url be ascii in that situation.
156 #
156 #
157 # Note that the URLs these patterns check against are escaped,
157 # Note that the URLs these patterns check against are escaped,
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 base_url = py3compat.unicode_to_str(base_url, 'ascii')
159 base_url = py3compat.unicode_to_str(base_url, 'ascii')
160 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
160 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
161 jenv_opt = jinja_env_options if jinja_env_options else {}
161 jenv_opt = jinja_env_options if jinja_env_options else {}
162 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
162 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
163 settings = dict(
163 settings = dict(
164 # basics
164 # basics
165 log_function=log_request,
165 log_function=log_request,
166 base_url=base_url,
166 base_url=base_url,
167 template_path=template_path,
167 template_path=template_path,
168 static_path=ipython_app.static_file_path,
168 static_path=ipython_app.static_file_path,
169 static_handler_class = FileFindHandler,
169 static_handler_class = FileFindHandler,
170 static_url_prefix = url_path_join(base_url,'/static/'),
170 static_url_prefix = url_path_join(base_url,'/static/'),
171
171
172 # authentication
172 # authentication
173 cookie_secret=ipython_app.cookie_secret,
173 cookie_secret=ipython_app.cookie_secret,
174 login_url=url_path_join(base_url,'/login'),
174 login_url=url_path_join(base_url,'/login'),
175 password=ipython_app.password,
175 password=ipython_app.password,
176
176
177 # managers
177 # managers
178 kernel_manager=kernel_manager,
178 kernel_manager=kernel_manager,
179 notebook_manager=notebook_manager,
179 notebook_manager=notebook_manager,
180 cluster_manager=cluster_manager,
180 cluster_manager=cluster_manager,
181 session_manager=session_manager,
181 session_manager=session_manager,
182
182
183 # IPython stuff
183 # IPython stuff
184 nbextensions_path = ipython_app.nbextensions_path,
184 nbextensions_path = ipython_app.nbextensions_path,
185 mathjax_url=ipython_app.mathjax_url,
185 mathjax_url=ipython_app.mathjax_url,
186 config=ipython_app.config,
186 config=ipython_app.config,
187 jinja2_env=env,
187 jinja2_env=env,
188 )
188 )
189
189
190 # allow custom overrides for the tornado web app.
190 # allow custom overrides for the tornado web app.
191 settings.update(settings_overrides)
191 settings.update(settings_overrides)
192 return settings
192 return settings
193
193
194 def init_handlers(self, settings):
194 def init_handlers(self, settings):
195 # Load the (URL pattern, handler) tuples for each component.
195 # Load the (URL pattern, handler) tuples for each component.
196 handlers = []
196 handlers = []
197 handlers.extend(load_handlers('base.handlers'))
197 handlers.extend(load_handlers('base.handlers'))
198 handlers.extend(load_handlers('tree.handlers'))
198 handlers.extend(load_handlers('tree.handlers'))
199 handlers.extend(load_handlers('auth.login'))
199 handlers.extend(load_handlers('auth.login'))
200 handlers.extend(load_handlers('auth.logout'))
200 handlers.extend(load_handlers('auth.logout'))
201 handlers.extend(load_handlers('notebook.handlers'))
201 handlers.extend(load_handlers('notebook.handlers'))
202 handlers.extend(load_handlers('nbconvert.handlers'))
202 handlers.extend(load_handlers('nbconvert.handlers'))
203 handlers.extend(load_handlers('services.kernels.handlers'))
203 handlers.extend(load_handlers('services.kernels.handlers'))
204 handlers.extend(load_handlers('services.notebooks.handlers'))
204 handlers.extend(load_handlers('services.notebooks.handlers'))
205 handlers.extend(load_handlers('services.clusters.handlers'))
205 handlers.extend(load_handlers('services.clusters.handlers'))
206 handlers.extend(load_handlers('services.sessions.handlers'))
206 handlers.extend(load_handlers('services.sessions.handlers'))
207 handlers.extend(load_handlers('services.nbconvert.handlers'))
207 handlers.extend(load_handlers('services.nbconvert.handlers'))
208 # FIXME: /files/ should be handled by the Contents service when it exists
208 # FIXME: /files/ should be handled by the Contents service when it exists
209 nbm = settings['notebook_manager']
209 nbm = settings['notebook_manager']
210 if hasattr(nbm, 'notebook_dir'):
210 if hasattr(nbm, 'notebook_dir'):
211 handlers.extend([
211 handlers.extend([
212 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
212 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
213 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
213 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
214 ])
214 ])
215 # prepend base_url onto the patterns that we match
215 # prepend base_url onto the patterns that we match
216 new_handlers = []
216 new_handlers = []
217 for handler in handlers:
217 for handler in handlers:
218 pattern = url_path_join(settings['base_url'], handler[0])
218 pattern = url_path_join(settings['base_url'], handler[0])
219 new_handler = tuple([pattern] + list(handler[1:]))
219 new_handler = tuple([pattern] + list(handler[1:]))
220 new_handlers.append(new_handler)
220 new_handlers.append(new_handler)
221 # add 404 on the end, which will catch everything that falls through
221 # add 404 on the end, which will catch everything that falls through
222 new_handlers.append((r'(.*)', Template404))
222 new_handlers.append((r'(.*)', Template404))
223 return new_handlers
223 return new_handlers
224
224
225
225
226 class NbserverListApp(BaseIPythonApplication):
226 class NbserverListApp(BaseIPythonApplication):
227
227
228 description="List currently running notebook servers in this profile."
228 description="List currently running notebook servers in this profile."
229
229
230 flags = dict(
230 flags = dict(
231 json=({'NbserverListApp': {'json': True}},
231 json=({'NbserverListApp': {'json': True}},
232 "Produce machine-readable JSON output."),
232 "Produce machine-readable JSON output."),
233 )
233 )
234
234
235 json = Bool(False, config=True,
235 json = Bool(False, config=True,
236 help="If True, each line of output will be a JSON object with the "
236 help="If True, each line of output will be a JSON object with the "
237 "details from the server info file.")
237 "details from the server info file.")
238
238
239 def start(self):
239 def start(self):
240 if not self.json:
240 if not self.json:
241 print("Currently running servers:")
241 print("Currently running servers:")
242 for serverinfo in list_running_servers(self.profile):
242 for serverinfo in list_running_servers(self.profile):
243 if self.json:
243 if self.json:
244 print(json.dumps(serverinfo))
244 print(json.dumps(serverinfo))
245 else:
245 else:
246 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
246 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
247
247
248 #-----------------------------------------------------------------------------
248 #-----------------------------------------------------------------------------
249 # Aliases and Flags
249 # Aliases and Flags
250 #-----------------------------------------------------------------------------
250 #-----------------------------------------------------------------------------
251
251
252 flags = dict(kernel_flags)
252 flags = dict(kernel_flags)
253 flags['no-browser']=(
253 flags['no-browser']=(
254 {'NotebookApp' : {'open_browser' : False}},
254 {'NotebookApp' : {'open_browser' : False}},
255 "Don't open the notebook in a browser after startup."
255 "Don't open the notebook in a browser after startup."
256 )
256 )
257 flags['no-mathjax']=(
257 flags['no-mathjax']=(
258 {'NotebookApp' : {'enable_mathjax' : False}},
258 {'NotebookApp' : {'enable_mathjax' : False}},
259 """Disable MathJax
259 """Disable MathJax
260
260
261 MathJax is the javascript library IPython uses to render math/LaTeX. It is
261 MathJax is the javascript library IPython uses to render math/LaTeX. It is
262 very large, so you may want to disable it if you have a slow internet
262 very large, so you may want to disable it if you have a slow internet
263 connection, or for offline use of the notebook.
263 connection, or for offline use of the notebook.
264
264
265 When disabled, equations etc. will appear as their untransformed TeX source.
265 When disabled, equations etc. will appear as their untransformed TeX source.
266 """
266 """
267 )
267 )
268
268
269 # Add notebook manager flags
269 # Add notebook manager flags
270 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
270 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
271 'Auto-save a .py script everytime the .ipynb notebook is saved',
271 'Auto-save a .py script everytime the .ipynb notebook is saved',
272 'Do not auto-save .py scripts for every notebook'))
272 'Do not auto-save .py scripts for every notebook'))
273
273
274 # the flags that are specific to the frontend
274 # the flags that are specific to the frontend
275 # these must be scrubbed before being passed to the kernel,
275 # these must be scrubbed before being passed to the kernel,
276 # or it will raise an error on unrecognized flags
276 # or it will raise an error on unrecognized flags
277 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
277 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
278
278
279 aliases = dict(kernel_aliases)
279 aliases = dict(kernel_aliases)
280
280
281 aliases.update({
281 aliases.update({
282 'ip': 'NotebookApp.ip',
282 'ip': 'NotebookApp.ip',
283 'port': 'NotebookApp.port',
283 'port': 'NotebookApp.port',
284 'port-retries': 'NotebookApp.port_retries',
284 'port-retries': 'NotebookApp.port_retries',
285 'transport': 'KernelManager.transport',
285 'transport': 'KernelManager.transport',
286 'keyfile': 'NotebookApp.keyfile',
286 'keyfile': 'NotebookApp.keyfile',
287 'certfile': 'NotebookApp.certfile',
287 'certfile': 'NotebookApp.certfile',
288 'notebook-dir': 'NotebookApp.notebook_dir',
288 'notebook-dir': 'NotebookApp.notebook_dir',
289 'browser': 'NotebookApp.browser',
289 'browser': 'NotebookApp.browser',
290 })
290 })
291
291
292 # remove ipkernel flags that are singletons, and don't make sense in
292 # remove ipkernel flags that are singletons, and don't make sense in
293 # multi-kernel evironment:
293 # multi-kernel evironment:
294 aliases.pop('f', None)
294 aliases.pop('f', None)
295
295
296 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
296 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
297 u'notebook-dir', u'profile', u'profile-dir', 'browser']
297 u'notebook-dir', u'profile', u'profile-dir', 'browser']
298
298
299 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
300 # NotebookApp
300 # NotebookApp
301 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
302
302
303 class NotebookApp(BaseIPythonApplication):
303 class NotebookApp(BaseIPythonApplication):
304
304
305 name = 'ipython-notebook'
305 name = 'ipython-notebook'
306
306
307 description = """
307 description = """
308 The IPython HTML Notebook.
308 The IPython HTML Notebook.
309
309
310 This launches a Tornado based HTML Notebook Server that serves up an
310 This launches a Tornado based HTML Notebook Server that serves up an
311 HTML5/Javascript Notebook client.
311 HTML5/Javascript Notebook client.
312 """
312 """
313 examples = _examples
313 examples = _examples
314
314
315 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
315 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
316 FileNotebookManager, NotebookNotary]
316 FileNotebookManager, NotebookNotary]
317 flags = Dict(flags)
317 flags = Dict(flags)
318 aliases = Dict(aliases)
318 aliases = Dict(aliases)
319
319
320 subcommands = dict(
320 subcommands = dict(
321 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
321 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
322 )
322 )
323
323
324 kernel_argv = List(Unicode)
324 kernel_argv = List(Unicode)
325
325
326 def _log_level_default(self):
326 def _log_level_default(self):
327 return logging.INFO
327 return logging.INFO
328
328
329 def _log_format_default(self):
329 def _log_format_default(self):
330 """override default log format to include time"""
330 """override default log format to include time"""
331 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
331 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
332
332
333 # create requested profiles by default, if they don't exist:
333 # create requested profiles by default, if they don't exist:
334 auto_create = Bool(True)
334 auto_create = Bool(True)
335
335
336 # file to be opened in the notebook server
336 # file to be opened in the notebook server
337 file_to_run = Unicode('', config=True)
337 file_to_run = Unicode('', config=True)
338 def _file_to_run_changed(self, name, old, new):
338 def _file_to_run_changed(self, name, old, new):
339 path, base = os.path.split(new)
339 path, base = os.path.split(new)
340 if path:
340 if path:
341 self.file_to_run = base
341 self.file_to_run = base
342 self.notebook_dir = path
342 self.notebook_dir = path
343
343
344 # Network related information
344 # Network related information
345
345
346 allow_origin = Unicode('', config=True,
346 allow_origin = Unicode('', config=True,
347 help="""Set the Access-Control-Allow-Origin header
347 help="""Set the Access-Control-Allow-Origin header
348
348
349 Use '*' to allow any origin to access your server.
349 Use '*' to allow any origin to access your server.
350
350
351 Takes precedence over allow_origin_pat.
351 Takes precedence over allow_origin_pat.
352 """
352 """
353 )
353 )
354
354
355 allow_origin_pat = Unicode('', config=True,
355 allow_origin_pat = Unicode('', config=True,
356 help="""Use a regular expression for the Access-Control-Allow-Origin header
356 help="""Use a regular expression for the Access-Control-Allow-Origin header
357
357
358 Requests from an origin matching the expression will get replies with:
358 Requests from an origin matching the expression will get replies with:
359
359
360 Access-Control-Allow-Origin: origin
360 Access-Control-Allow-Origin: origin
361
361
362 where `origin` is the origin of the request.
362 where `origin` is the origin of the request.
363
363
364 Ignored if allow_origin is set.
364 Ignored if allow_origin is set.
365 """
365 """
366 )
366 )
367
367
368 allow_credentials = Bool(False, config=True,
368 allow_credentials = Bool(False, config=True,
369 help="Set the Access-Control-Allow-Credentials: true header"
369 help="Set the Access-Control-Allow-Credentials: true header"
370 )
370 )
371
371
372 ip = Unicode('localhost', config=True,
372 ip = Unicode('localhost', config=True,
373 help="The IP address the notebook server will listen on."
373 help="The IP address the notebook server will listen on."
374 )
374 )
375
375
376 def _ip_changed(self, name, old, new):
376 def _ip_changed(self, name, old, new):
377 if new == u'*': self.ip = u''
377 if new == u'*': self.ip = u''
378
378
379 port = Integer(8888, config=True,
379 port = Integer(8888, config=True,
380 help="The port the notebook server will listen on."
380 help="The port the notebook server will listen on."
381 )
381 )
382 port_retries = Integer(50, config=True,
382 port_retries = Integer(50, config=True,
383 help="The number of additional ports to try if the specified port is not available."
383 help="The number of additional ports to try if the specified port is not available."
384 )
384 )
385
385
386 certfile = Unicode(u'', config=True,
386 certfile = Unicode(u'', config=True,
387 help="""The full path to an SSL/TLS certificate file."""
387 help="""The full path to an SSL/TLS certificate file."""
388 )
388 )
389
389
390 keyfile = Unicode(u'', config=True,
390 keyfile = Unicode(u'', config=True,
391 help="""The full path to a private key file for usage with SSL/TLS."""
391 help="""The full path to a private key file for usage with SSL/TLS."""
392 )
392 )
393
393
394 cookie_secret = Bytes(b'', config=True,
394 cookie_secret = Bytes(b'', config=True,
395 help="""The random bytes used to secure cookies.
395 help="""The random bytes used to secure cookies.
396 By default this is a new random number every time you start the Notebook.
396 By default this is a new random number every time you start the Notebook.
397 Set it to a value in a config file to enable logins to persist across server sessions.
397 Set it to a value in a config file to enable logins to persist across server sessions.
398
398
399 Note: Cookie secrets should be kept private, do not share config files with
399 Note: Cookie secrets should be kept private, do not share config files with
400 cookie_secret stored in plaintext (you can read the value from a file).
400 cookie_secret stored in plaintext (you can read the value from a file).
401 """
401 """
402 )
402 )
403 def _cookie_secret_default(self):
403 def _cookie_secret_default(self):
404 return os.urandom(1024)
404 return os.urandom(1024)
405
405
406 password = Unicode(u'', config=True,
406 password = Unicode(u'', config=True,
407 help="""Hashed password to use for web authentication.
407 help="""Hashed password to use for web authentication.
408
408
409 To generate, type in a python/IPython shell:
409 To generate, type in a python/IPython shell:
410
410
411 from IPython.lib import passwd; passwd()
411 from IPython.lib import passwd; passwd()
412
412
413 The string should be of the form type:salt:hashed-password.
413 The string should be of the form type:salt:hashed-password.
414 """
414 """
415 )
415 )
416
416
417 open_browser = Bool(True, config=True,
417 open_browser = Bool(True, config=True,
418 help="""Whether to open in a browser after starting.
418 help="""Whether to open in a browser after starting.
419 The specific browser used is platform dependent and
419 The specific browser used is platform dependent and
420 determined by the python standard library `webbrowser`
420 determined by the python standard library `webbrowser`
421 module, unless it is overridden using the --browser
421 module, unless it is overridden using the --browser
422 (NotebookApp.browser) configuration option.
422 (NotebookApp.browser) configuration option.
423 """)
423 """)
424
424
425 browser = Unicode(u'', config=True,
425 browser = Unicode(u'', config=True,
426 help="""Specify what command to use to invoke a web
426 help="""Specify what command to use to invoke a web
427 browser when opening the notebook. If not specified, the
427 browser when opening the notebook. If not specified, the
428 default browser will be determined by the `webbrowser`
428 default browser will be determined by the `webbrowser`
429 standard library module, which allows setting of the
429 standard library module, which allows setting of the
430 BROWSER environment variable to override it.
430 BROWSER environment variable to override it.
431 """)
431 """)
432
432
433 webapp_settings = Dict(config=True,
433 webapp_settings = Dict(config=True,
434 help="Supply overrides for the tornado.web.Application that the "
434 help="Supply overrides for the tornado.web.Application that the "
435 "IPython notebook uses.")
435 "IPython notebook uses.")
436
436
437 jinja_environment_options = Dict(config=True,
437 jinja_environment_options = Dict(config=True,
438 help="Supply extra arguments that will be passed to Jinja environment.")
438 help="Supply extra arguments that will be passed to Jinja environment.")
439
439
440
440
441 enable_mathjax = Bool(True, config=True,
441 enable_mathjax = Bool(True, config=True,
442 help="""Whether to enable MathJax for typesetting math/TeX
442 help="""Whether to enable MathJax for typesetting math/TeX
443
443
444 MathJax is the javascript library IPython uses to render math/LaTeX. It is
444 MathJax is the javascript library IPython uses to render math/LaTeX. It is
445 very large, so you may want to disable it if you have a slow internet
445 very large, so you may want to disable it if you have a slow internet
446 connection, or for offline use of the notebook.
446 connection, or for offline use of the notebook.
447
447
448 When disabled, equations etc. will appear as their untransformed TeX source.
448 When disabled, equations etc. will appear as their untransformed TeX source.
449 """
449 """
450 )
450 )
451 def _enable_mathjax_changed(self, name, old, new):
451 def _enable_mathjax_changed(self, name, old, new):
452 """set mathjax url to empty if mathjax is disabled"""
452 """set mathjax url to empty if mathjax is disabled"""
453 if not new:
453 if not new:
454 self.mathjax_url = u''
454 self.mathjax_url = u''
455
455
456 base_url = Unicode('/', config=True,
456 base_url = Unicode('/', config=True,
457 help='''The base URL for the notebook server.
457 help='''The base URL for the notebook server.
458
458
459 Leading and trailing slashes can be omitted,
459 Leading and trailing slashes can be omitted,
460 and will automatically be added.
460 and will automatically be added.
461 ''')
461 ''')
462 def _base_url_changed(self, name, old, new):
462 def _base_url_changed(self, name, old, new):
463 if not new.startswith('/'):
463 if not new.startswith('/'):
464 self.base_url = '/'+new
464 self.base_url = '/'+new
465 elif not new.endswith('/'):
465 elif not new.endswith('/'):
466 self.base_url = new+'/'
466 self.base_url = new+'/'
467
467
468 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
468 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
469 def _base_project_url_changed(self, name, old, new):
469 def _base_project_url_changed(self, name, old, new):
470 self.log.warn("base_project_url is deprecated, use base_url")
470 self.log.warn("base_project_url is deprecated, use base_url")
471 self.base_url = new
471 self.base_url = new
472
472
473 extra_static_paths = List(Unicode, config=True,
473 extra_static_paths = List(Unicode, config=True,
474 help="""Extra paths to search for serving static files.
474 help="""Extra paths to search for serving static files.
475
475
476 This allows adding javascript/css to be available from the notebook server machine,
476 This allows adding javascript/css to be available from the notebook server machine,
477 or overriding individual files in the IPython"""
477 or overriding individual files in the IPython"""
478 )
478 )
479 def _extra_static_paths_default(self):
479 def _extra_static_paths_default(self):
480 return [os.path.join(self.profile_dir.location, 'static')]
480 return [os.path.join(self.profile_dir.location, 'static')]
481
481
482 @property
482 @property
483 def static_file_path(self):
483 def static_file_path(self):
484 """return extra paths + the default location"""
484 """return extra paths + the default location"""
485 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
485 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
486
486
487 nbextensions_path = List(Unicode, config=True,
487 nbextensions_path = List(Unicode, config=True,
488 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
488 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
489 )
489 )
490 def _nbextensions_path_default(self):
490 def _nbextensions_path_default(self):
491 return [os.path.join(get_ipython_dir(), 'nbextensions')]
491 return [os.path.join(get_ipython_dir(), 'nbextensions')]
492
492
493 mathjax_url = Unicode("", config=True,
493 mathjax_url = Unicode("", config=True,
494 help="""The url for MathJax.js."""
494 help="""The url for MathJax.js."""
495 )
495 )
496 def _mathjax_url_default(self):
496 def _mathjax_url_default(self):
497 if not self.enable_mathjax:
497 if not self.enable_mathjax:
498 return u''
498 return u''
499 static_url_prefix = self.webapp_settings.get("static_url_prefix",
499 static_url_prefix = self.webapp_settings.get("static_url_prefix",
500 url_path_join(self.base_url, "static")
500 url_path_join(self.base_url, "static")
501 )
501 )
502
502
503 # try local mathjax, either in nbextensions/mathjax or static/mathjax
503 # try local mathjax, either in nbextensions/mathjax or static/mathjax
504 for (url_prefix, search_path) in [
504 for (url_prefix, search_path) in [
505 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
505 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
506 (static_url_prefix, self.static_file_path),
506 (static_url_prefix, self.static_file_path),
507 ]:
507 ]:
508 self.log.debug("searching for local mathjax in %s", search_path)
508 self.log.debug("searching for local mathjax in %s", search_path)
509 try:
509 try:
510 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
510 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
511 except IOError:
511 except IOError:
512 continue
512 continue
513 else:
513 else:
514 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
514 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
515 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
515 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
516 return url
516 return url
517
517
518 # no local mathjax, serve from CDN
518 # no local mathjax, serve from CDN
519 if self.certfile:
519 if self.certfile:
520 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
520 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
521 host = u"https://c328740.ssl.cf1.rackcdn.com"
521 host = u"https://c328740.ssl.cf1.rackcdn.com"
522 else:
522 else:
523 host = u"http://cdn.mathjax.org"
523 host = u"http://cdn.mathjax.org"
524
524
525 url = host + u"/mathjax/latest/MathJax.js"
525 url = host + u"/mathjax/latest/MathJax.js"
526 self.log.info("Using MathJax from CDN: %s", url)
526 self.log.info("Using MathJax from CDN: %s", url)
527 return url
527 return url
528
528
529 def _mathjax_url_changed(self, name, old, new):
529 def _mathjax_url_changed(self, name, old, new):
530 if new and not self.enable_mathjax:
530 if new and not self.enable_mathjax:
531 # enable_mathjax=False overrides mathjax_url
531 # enable_mathjax=False overrides mathjax_url
532 self.mathjax_url = u''
532 self.mathjax_url = u''
533 else:
533 else:
534 self.log.info("Using MathJax: %s", new)
534 self.log.info("Using MathJax: %s", new)
535
535
536 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
536 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
537 config=True,
537 config=True,
538 help='The notebook manager class to use.')
538 help='The notebook manager class to use.')
539
539
540 trust_xheaders = Bool(False, config=True,
540 trust_xheaders = Bool(False, config=True,
541 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
541 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
542 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
542 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
543 )
543 )
544
544
545 info_file = Unicode()
545 info_file = Unicode()
546
546
547 def _info_file_default(self):
547 def _info_file_default(self):
548 info_file = "nbserver-%s.json"%os.getpid()
548 info_file = "nbserver-%s.json"%os.getpid()
549 return os.path.join(self.profile_dir.security_dir, info_file)
549 return os.path.join(self.profile_dir.security_dir, info_file)
550
550
551 notebook_dir = Unicode(py3compat.getcwd(), config=True,
551 notebook_dir = Unicode(py3compat.getcwd(), config=True,
552 help="The directory to use for notebooks and kernels."
552 help="The directory to use for notebooks and kernels."
553 )
553 )
554
554
555 def _notebook_dir_changed(self, name, old, new):
555 def _notebook_dir_changed(self, name, old, new):
556 """Do a bit of validation of the notebook dir."""
556 """Do a bit of validation of the notebook dir."""
557 if not os.path.isabs(new):
557 if not os.path.isabs(new):
558 # If we receive a non-absolute path, make it absolute.
558 # If we receive a non-absolute path, make it absolute.
559 self.notebook_dir = os.path.abspath(new)
559 self.notebook_dir = os.path.abspath(new)
560 return
560 return
561 if not os.path.isdir(new):
561 if not os.path.isdir(new):
562 raise TraitError("No such notebook dir: %r" % new)
562 raise TraitError("No such notebook dir: %r" % new)
563
563
564 # setting App.notebook_dir implies setting notebook and kernel dirs as well
564 # setting App.notebook_dir implies setting notebook and kernel dirs as well
565 self.config.FileNotebookManager.notebook_dir = new
565 self.config.FileNotebookManager.notebook_dir = new
566 self.config.MappingKernelManager.root_dir = new
566 self.config.MappingKernelManager.root_dir = new
567
567
568
568
569 def parse_command_line(self, argv=None):
569 def parse_command_line(self, argv=None):
570 super(NotebookApp, self).parse_command_line(argv)
570 super(NotebookApp, self).parse_command_line(argv)
571
571
572 if self.extra_args:
572 if self.extra_args:
573 arg0 = self.extra_args[0]
573 arg0 = self.extra_args[0]
574 f = os.path.abspath(arg0)
574 f = os.path.abspath(arg0)
575 self.argv.remove(arg0)
575 self.argv.remove(arg0)
576 if not os.path.exists(f):
576 if not os.path.exists(f):
577 self.log.critical("No such file or directory: %s", f)
577 self.log.critical("No such file or directory: %s", f)
578 self.exit(1)
578 self.exit(1)
579
579
580 # Use config here, to ensure that it takes higher priority than
580 # Use config here, to ensure that it takes higher priority than
581 # anything that comes from the profile.
581 # anything that comes from the profile.
582 c = Config()
582 c = Config()
583 if os.path.isdir(f):
583 if os.path.isdir(f):
584 c.NotebookApp.notebook_dir = f
584 c.NotebookApp.notebook_dir = f
585 elif os.path.isfile(f):
585 elif os.path.isfile(f):
586 c.NotebookApp.file_to_run = f
586 c.NotebookApp.file_to_run = f
587 self.update_config(c)
587 self.update_config(c)
588
588
589 def init_kernel_argv(self):
589 def init_kernel_argv(self):
590 """construct the kernel arguments"""
590 """construct the kernel arguments"""
591 # Scrub frontend-specific flags
591 # Scrub frontend-specific flags
592 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
592 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
593 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
593 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
594 self.log.warn('\n '.join([
594 self.log.warn('\n '.join([
595 "Starting all kernels in pylab mode is not recommended,",
595 "Starting all kernels in pylab mode is not recommended,",
596 "and will be disabled in a future release.",
596 "and will be disabled in a future release.",
597 "Please use the %matplotlib magic to enable matplotlib instead.",
597 "Please use the %matplotlib magic to enable matplotlib instead.",
598 "pylab implies many imports, which can have confusing side effects",
598 "pylab implies many imports, which can have confusing side effects",
599 "and harm the reproducibility of your notebooks.",
599 "and harm the reproducibility of your notebooks.",
600 ]))
600 ]))
601 # Kernel should inherit default config file from frontend
601 # Kernel should inherit default config file from frontend
602 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
602 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
603 # Kernel should get *absolute* path to profile directory
603 # Kernel should get *absolute* path to profile directory
604 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
604 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
605
605
606 def init_configurables(self):
606 def init_configurables(self):
607 # force Session default to be secure
607 # force Session default to be secure
608 default_secure(self.config)
608 default_secure(self.config)
609 self.kernel_manager = MappingKernelManager(
609 self.kernel_manager = MappingKernelManager(
610 parent=self, log=self.log, kernel_argv=self.kernel_argv,
610 parent=self, log=self.log, kernel_argv=self.kernel_argv,
611 connection_dir = self.profile_dir.security_dir,
611 connection_dir = self.profile_dir.security_dir,
612 )
612 )
613 kls = import_item(self.notebook_manager_class)
613 kls = import_item(self.notebook_manager_class)
614 self.notebook_manager = kls(parent=self, log=self.log)
614 self.notebook_manager = kls(parent=self, log=self.log)
615 self.session_manager = SessionManager(parent=self, log=self.log)
615 self.session_manager = SessionManager(parent=self, log=self.log)
616 self.cluster_manager = ClusterManager(parent=self, log=self.log)
616 self.cluster_manager = ClusterManager(parent=self, log=self.log)
617 self.cluster_manager.update_profiles()
617 self.cluster_manager.update_profiles()
618
618
619 def init_logging(self):
619 def init_logging(self):
620 # This prevents double log messages because tornado use a root logger that
620 # This prevents double log messages because tornado use a root logger that
621 # self.log is a child of. The logging module dipatches log messages to a log
621 # self.log is a child of. The logging module dipatches log messages to a log
622 # and all of its ancenstors until propagate is set to False.
622 # and all of its ancenstors until propagate is set to False.
623 self.log.propagate = False
623 self.log.propagate = False
624
624
625 # hook up tornado 3's loggers to our app handlers
625 # hook up tornado 3's loggers to our app handlers
626 for name in ('access', 'application', 'general'):
626 for name in ('access', 'application', 'general'):
627 logger = logging.getLogger('tornado.%s' % name)
627 logger = logging.getLogger('tornado.%s' % name)
628 logger.parent = self.log
628 logger.parent = self.log
629 logger.setLevel(self.log.level)
629 logger.setLevel(self.log.level)
630
630
631 def init_webapp(self):
631 def init_webapp(self):
632 """initialize tornado webapp and httpserver"""
632 """initialize tornado webapp and httpserver"""
633 self.webapp_settings['allow_origin'] = self.allow_origin
633 self.webapp_settings['allow_origin'] = self.allow_origin
634 if self.allow_origin_pat:
634 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
635 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
635 self.webapp_settings['allow_credentials'] = self.allow_credentials
636 self.webapp_settings['allow_credentials'] = self.allow_credentials
636
637
637 self.web_app = NotebookWebApplication(
638 self.web_app = NotebookWebApplication(
638 self, self.kernel_manager, self.notebook_manager,
639 self, self.kernel_manager, self.notebook_manager,
639 self.cluster_manager, self.session_manager,
640 self.cluster_manager, self.session_manager,
640 self.log, self.base_url, self.webapp_settings,
641 self.log, self.base_url, self.webapp_settings,
641 self.jinja_environment_options
642 self.jinja_environment_options
642 )
643 )
643 if self.certfile:
644 if self.certfile:
644 ssl_options = dict(certfile=self.certfile)
645 ssl_options = dict(certfile=self.certfile)
645 if self.keyfile:
646 if self.keyfile:
646 ssl_options['keyfile'] = self.keyfile
647 ssl_options['keyfile'] = self.keyfile
647 else:
648 else:
648 ssl_options = None
649 ssl_options = None
649 self.web_app.password = self.password
650 self.web_app.password = self.password
650 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
651 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
651 xheaders=self.trust_xheaders)
652 xheaders=self.trust_xheaders)
652 if not self.ip:
653 if not self.ip:
653 warning = "WARNING: The notebook server is listening on all IP addresses"
654 warning = "WARNING: The notebook server is listening on all IP addresses"
654 if ssl_options is None:
655 if ssl_options is None:
655 self.log.critical(warning + " and not using encryption. This "
656 self.log.critical(warning + " and not using encryption. This "
656 "is not recommended.")
657 "is not recommended.")
657 if not self.password:
658 if not self.password:
658 self.log.critical(warning + " and not using authentication. "
659 self.log.critical(warning + " and not using authentication. "
659 "This is highly insecure and not recommended.")
660 "This is highly insecure and not recommended.")
660 success = None
661 success = None
661 for port in random_ports(self.port, self.port_retries+1):
662 for port in random_ports(self.port, self.port_retries+1):
662 try:
663 try:
663 self.http_server.listen(port, self.ip)
664 self.http_server.listen(port, self.ip)
664 except socket.error as e:
665 except socket.error as e:
665 if e.errno == errno.EADDRINUSE:
666 if e.errno == errno.EADDRINUSE:
666 self.log.info('The port %i is already in use, trying another random port.' % port)
667 self.log.info('The port %i is already in use, trying another random port.' % port)
667 continue
668 continue
668 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
669 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
669 self.log.warn("Permission to listen on port %i denied" % port)
670 self.log.warn("Permission to listen on port %i denied" % port)
670 continue
671 continue
671 else:
672 else:
672 raise
673 raise
673 else:
674 else:
674 self.port = port
675 self.port = port
675 success = True
676 success = True
676 break
677 break
677 if not success:
678 if not success:
678 self.log.critical('ERROR: the notebook server could not be started because '
679 self.log.critical('ERROR: the notebook server could not be started because '
679 'no available port could be found.')
680 'no available port could be found.')
680 self.exit(1)
681 self.exit(1)
681
682
682 @property
683 @property
683 def display_url(self):
684 def display_url(self):
684 ip = self.ip if self.ip else '[all ip addresses on your system]'
685 ip = self.ip if self.ip else '[all ip addresses on your system]'
685 return self._url(ip)
686 return self._url(ip)
686
687
687 @property
688 @property
688 def connection_url(self):
689 def connection_url(self):
689 ip = self.ip if self.ip else 'localhost'
690 ip = self.ip if self.ip else 'localhost'
690 return self._url(ip)
691 return self._url(ip)
691
692
692 def _url(self, ip):
693 def _url(self, ip):
693 proto = 'https' if self.certfile else 'http'
694 proto = 'https' if self.certfile else 'http'
694 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
695 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
695
696
696 def init_signal(self):
697 def init_signal(self):
697 if not sys.platform.startswith('win'):
698 if not sys.platform.startswith('win'):
698 signal.signal(signal.SIGINT, self._handle_sigint)
699 signal.signal(signal.SIGINT, self._handle_sigint)
699 signal.signal(signal.SIGTERM, self._signal_stop)
700 signal.signal(signal.SIGTERM, self._signal_stop)
700 if hasattr(signal, 'SIGUSR1'):
701 if hasattr(signal, 'SIGUSR1'):
701 # Windows doesn't support SIGUSR1
702 # Windows doesn't support SIGUSR1
702 signal.signal(signal.SIGUSR1, self._signal_info)
703 signal.signal(signal.SIGUSR1, self._signal_info)
703 if hasattr(signal, 'SIGINFO'):
704 if hasattr(signal, 'SIGINFO'):
704 # only on BSD-based systems
705 # only on BSD-based systems
705 signal.signal(signal.SIGINFO, self._signal_info)
706 signal.signal(signal.SIGINFO, self._signal_info)
706
707
707 def _handle_sigint(self, sig, frame):
708 def _handle_sigint(self, sig, frame):
708 """SIGINT handler spawns confirmation dialog"""
709 """SIGINT handler spawns confirmation dialog"""
709 # register more forceful signal handler for ^C^C case
710 # register more forceful signal handler for ^C^C case
710 signal.signal(signal.SIGINT, self._signal_stop)
711 signal.signal(signal.SIGINT, self._signal_stop)
711 # request confirmation dialog in bg thread, to avoid
712 # request confirmation dialog in bg thread, to avoid
712 # blocking the App
713 # blocking the App
713 thread = threading.Thread(target=self._confirm_exit)
714 thread = threading.Thread(target=self._confirm_exit)
714 thread.daemon = True
715 thread.daemon = True
715 thread.start()
716 thread.start()
716
717
717 def _restore_sigint_handler(self):
718 def _restore_sigint_handler(self):
718 """callback for restoring original SIGINT handler"""
719 """callback for restoring original SIGINT handler"""
719 signal.signal(signal.SIGINT, self._handle_sigint)
720 signal.signal(signal.SIGINT, self._handle_sigint)
720
721
721 def _confirm_exit(self):
722 def _confirm_exit(self):
722 """confirm shutdown on ^C
723 """confirm shutdown on ^C
723
724
724 A second ^C, or answering 'y' within 5s will cause shutdown,
725 A second ^C, or answering 'y' within 5s will cause shutdown,
725 otherwise original SIGINT handler will be restored.
726 otherwise original SIGINT handler will be restored.
726
727
727 This doesn't work on Windows.
728 This doesn't work on Windows.
728 """
729 """
729 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
730 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
730 time.sleep(0.1)
731 time.sleep(0.1)
731 info = self.log.info
732 info = self.log.info
732 info('interrupted')
733 info('interrupted')
733 print(self.notebook_info())
734 print(self.notebook_info())
734 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
735 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
735 sys.stdout.flush()
736 sys.stdout.flush()
736 r,w,x = select.select([sys.stdin], [], [], 5)
737 r,w,x = select.select([sys.stdin], [], [], 5)
737 if r:
738 if r:
738 line = sys.stdin.readline()
739 line = sys.stdin.readline()
739 if line.lower().startswith('y') and 'n' not in line.lower():
740 if line.lower().startswith('y') and 'n' not in line.lower():
740 self.log.critical("Shutdown confirmed")
741 self.log.critical("Shutdown confirmed")
741 ioloop.IOLoop.instance().stop()
742 ioloop.IOLoop.instance().stop()
742 return
743 return
743 else:
744 else:
744 print("No answer for 5s:", end=' ')
745 print("No answer for 5s:", end=' ')
745 print("resuming operation...")
746 print("resuming operation...")
746 # no answer, or answer is no:
747 # no answer, or answer is no:
747 # set it back to original SIGINT handler
748 # set it back to original SIGINT handler
748 # use IOLoop.add_callback because signal.signal must be called
749 # use IOLoop.add_callback because signal.signal must be called
749 # from main thread
750 # from main thread
750 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
751 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
751
752
752 def _signal_stop(self, sig, frame):
753 def _signal_stop(self, sig, frame):
753 self.log.critical("received signal %s, stopping", sig)
754 self.log.critical("received signal %s, stopping", sig)
754 ioloop.IOLoop.instance().stop()
755 ioloop.IOLoop.instance().stop()
755
756
756 def _signal_info(self, sig, frame):
757 def _signal_info(self, sig, frame):
757 print(self.notebook_info())
758 print(self.notebook_info())
758
759
759 def init_components(self):
760 def init_components(self):
760 """Check the components submodule, and warn if it's unclean"""
761 """Check the components submodule, and warn if it's unclean"""
761 status = submodule.check_submodule_status()
762 status = submodule.check_submodule_status()
762 if status == 'missing':
763 if status == 'missing':
763 self.log.warn("components submodule missing, running `git submodule update`")
764 self.log.warn("components submodule missing, running `git submodule update`")
764 submodule.update_submodules(submodule.ipython_parent())
765 submodule.update_submodules(submodule.ipython_parent())
765 elif status == 'unclean':
766 elif status == 'unclean':
766 self.log.warn("components submodule unclean, you may see 404s on static/components")
767 self.log.warn("components submodule unclean, you may see 404s on static/components")
767 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
768 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
768
769
769 @catch_config_error
770 @catch_config_error
770 def initialize(self, argv=None):
771 def initialize(self, argv=None):
771 super(NotebookApp, self).initialize(argv)
772 super(NotebookApp, self).initialize(argv)
772 self.init_logging()
773 self.init_logging()
773 self.init_kernel_argv()
774 self.init_kernel_argv()
774 self.init_configurables()
775 self.init_configurables()
775 self.init_components()
776 self.init_components()
776 self.init_webapp()
777 self.init_webapp()
777 self.init_signal()
778 self.init_signal()
778
779
779 def cleanup_kernels(self):
780 def cleanup_kernels(self):
780 """Shutdown all kernels.
781 """Shutdown all kernels.
781
782
782 The kernels will shutdown themselves when this process no longer exists,
783 The kernels will shutdown themselves when this process no longer exists,
783 but explicit shutdown allows the KernelManagers to cleanup the connection files.
784 but explicit shutdown allows the KernelManagers to cleanup the connection files.
784 """
785 """
785 self.log.info('Shutting down kernels')
786 self.log.info('Shutting down kernels')
786 self.kernel_manager.shutdown_all()
787 self.kernel_manager.shutdown_all()
787
788
788 def notebook_info(self):
789 def notebook_info(self):
789 "Return the current working directory and the server url information"
790 "Return the current working directory and the server url information"
790 info = self.notebook_manager.info_string() + "\n"
791 info = self.notebook_manager.info_string() + "\n"
791 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
792 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
792 return info + "The IPython Notebook is running at: %s" % self.display_url
793 return info + "The IPython Notebook is running at: %s" % self.display_url
793
794
794 def server_info(self):
795 def server_info(self):
795 """Return a JSONable dict of information about this server."""
796 """Return a JSONable dict of information about this server."""
796 return {'url': self.connection_url,
797 return {'url': self.connection_url,
797 'hostname': self.ip if self.ip else 'localhost',
798 'hostname': self.ip if self.ip else 'localhost',
798 'port': self.port,
799 'port': self.port,
799 'secure': bool(self.certfile),
800 'secure': bool(self.certfile),
800 'base_url': self.base_url,
801 'base_url': self.base_url,
801 'notebook_dir': os.path.abspath(self.notebook_dir),
802 'notebook_dir': os.path.abspath(self.notebook_dir),
802 }
803 }
803
804
804 def write_server_info_file(self):
805 def write_server_info_file(self):
805 """Write the result of server_info() to the JSON file info_file."""
806 """Write the result of server_info() to the JSON file info_file."""
806 with open(self.info_file, 'w') as f:
807 with open(self.info_file, 'w') as f:
807 json.dump(self.server_info(), f, indent=2)
808 json.dump(self.server_info(), f, indent=2)
808
809
809 def remove_server_info_file(self):
810 def remove_server_info_file(self):
810 """Remove the nbserver-<pid>.json file created for this server.
811 """Remove the nbserver-<pid>.json file created for this server.
811
812
812 Ignores the error raised when the file has already been removed.
813 Ignores the error raised when the file has already been removed.
813 """
814 """
814 try:
815 try:
815 os.unlink(self.info_file)
816 os.unlink(self.info_file)
816 except OSError as e:
817 except OSError as e:
817 if e.errno != errno.ENOENT:
818 if e.errno != errno.ENOENT:
818 raise
819 raise
819
820
820 def start(self):
821 def start(self):
821 """ Start the IPython Notebook server app, after initialization
822 """ Start the IPython Notebook server app, after initialization
822
823
823 This method takes no arguments so all configuration and initialization
824 This method takes no arguments so all configuration and initialization
824 must be done prior to calling this method."""
825 must be done prior to calling this method."""
825 if self.subapp is not None:
826 if self.subapp is not None:
826 return self.subapp.start()
827 return self.subapp.start()
827
828
828 info = self.log.info
829 info = self.log.info
829 for line in self.notebook_info().split("\n"):
830 for line in self.notebook_info().split("\n"):
830 info(line)
831 info(line)
831 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
832 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
832
833
833 self.write_server_info_file()
834 self.write_server_info_file()
834
835
835 if self.open_browser or self.file_to_run:
836 if self.open_browser or self.file_to_run:
836 try:
837 try:
837 browser = webbrowser.get(self.browser or None)
838 browser = webbrowser.get(self.browser or None)
838 except webbrowser.Error as e:
839 except webbrowser.Error as e:
839 self.log.warn('No web browser found: %s.' % e)
840 self.log.warn('No web browser found: %s.' % e)
840 browser = None
841 browser = None
841
842
842 if self.file_to_run:
843 if self.file_to_run:
843 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
844 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
844 if not os.path.exists(fullpath):
845 if not os.path.exists(fullpath):
845 self.log.critical("%s does not exist" % fullpath)
846 self.log.critical("%s does not exist" % fullpath)
846 self.exit(1)
847 self.exit(1)
847
848
848 uri = url_path_join('notebooks', self.file_to_run)
849 uri = url_path_join('notebooks', self.file_to_run)
849 else:
850 else:
850 uri = 'tree'
851 uri = 'tree'
851 if browser:
852 if browser:
852 b = lambda : browser.open(url_path_join(self.connection_url, uri),
853 b = lambda : browser.open(url_path_join(self.connection_url, uri),
853 new=2)
854 new=2)
854 threading.Thread(target=b).start()
855 threading.Thread(target=b).start()
855 try:
856 try:
856 ioloop.IOLoop.instance().start()
857 ioloop.IOLoop.instance().start()
857 except KeyboardInterrupt:
858 except KeyboardInterrupt:
858 info("Interrupted...")
859 info("Interrupted...")
859 finally:
860 finally:
860 self.cleanup_kernels()
861 self.cleanup_kernels()
861 self.remove_server_info_file()
862 self.remove_server_info_file()
862
863
863
864
864 def list_running_servers(profile='default'):
865 def list_running_servers(profile='default'):
865 """Iterate over the server info files of running notebook servers.
866 """Iterate over the server info files of running notebook servers.
866
867
867 Given a profile name, find nbserver-* files in the security directory of
868 Given a profile name, find nbserver-* files in the security directory of
868 that profile, and yield dicts of their information, each one pertaining to
869 that profile, and yield dicts of their information, each one pertaining to
869 a currently running notebook server instance.
870 a currently running notebook server instance.
870 """
871 """
871 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
872 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
872 for file in os.listdir(pd.security_dir):
873 for file in os.listdir(pd.security_dir):
873 if file.startswith('nbserver-'):
874 if file.startswith('nbserver-'):
874 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
875 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
875 yield json.load(f)
876 yield json.load(f)
876
877
877 #-----------------------------------------------------------------------------
878 #-----------------------------------------------------------------------------
878 # Main entry point
879 # Main entry point
879 #-----------------------------------------------------------------------------
880 #-----------------------------------------------------------------------------
880
881
881 launch_new_instance = NotebookApp.launch_instance
882 launch_new_instance = NotebookApp.launch_instance
882
883
General Comments 0
You need to be logged in to leave comments. Login now