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