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