##// END OF EJS Templates
Fix for CVE-2014-3566 'POODLE' SSL attack, original commit '22c4922f4796078c5613de9e07e66b8764549cad'
Brian Drawert -
Show More
@@ -1,1127 +1,1131 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 datetime
10 import datetime
11 import errno
11 import errno
12 import importlib
12 import importlib
13 import io
13 import io
14 import json
14 import json
15 import logging
15 import logging
16 import os
16 import os
17 import random
17 import random
18 import re
18 import re
19 import select
19 import select
20 import signal
20 import signal
21 import socket
21 import socket
22 import ssl
22 import sys
23 import sys
23 import threading
24 import threading
24 import webbrowser
25 import webbrowser
25
26
26
27
27 # check for pyzmq
28 # check for pyzmq
28 from IPython.utils.zmqrelated import check_for_zmq
29 from IPython.utils.zmqrelated import check_for_zmq
29 check_for_zmq('13', 'IPython.html')
30 check_for_zmq('13', 'IPython.html')
30
31
31 from jinja2 import Environment, FileSystemLoader
32 from jinja2 import Environment, FileSystemLoader
32
33
33 # Install the pyzmq ioloop. This has to be done before anything else from
34 # Install the pyzmq ioloop. This has to be done before anything else from
34 # tornado is imported.
35 # tornado is imported.
35 from zmq.eventloop import ioloop
36 from zmq.eventloop import ioloop
36 ioloop.install()
37 ioloop.install()
37
38
38 # check for tornado 3.1.0
39 # check for tornado 3.1.0
39 msg = "The IPython Notebook requires tornado >= 4.0"
40 msg = "The IPython Notebook requires tornado >= 4.0"
40 try:
41 try:
41 import tornado
42 import tornado
42 except ImportError:
43 except ImportError:
43 raise ImportError(msg)
44 raise ImportError(msg)
44 try:
45 try:
45 version_info = tornado.version_info
46 version_info = tornado.version_info
46 except AttributeError:
47 except AttributeError:
47 raise ImportError(msg + ", but you have < 1.1.0")
48 raise ImportError(msg + ", but you have < 1.1.0")
48 if version_info < (4,0):
49 if version_info < (4,0):
49 raise ImportError(msg + ", but you have %s" % tornado.version)
50 raise ImportError(msg + ", but you have %s" % tornado.version)
50
51
51 from tornado import httpserver
52 from tornado import httpserver
52 from tornado import web
53 from tornado import web
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54 from tornado.log import LogFormatter, app_log, access_log, gen_log
54
55
55 from IPython.html import (
56 from IPython.html import (
56 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_TEMPLATE_PATH_LIST,
58 DEFAULT_TEMPLATE_PATH_LIST,
58 )
59 )
59 from .base.handlers import Template404
60 from .base.handlers import Template404
60 from .log import log_request
61 from .log import log_request
61 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.config import ConfigManager
63 from .services.config import ConfigManager
63 from .services.contents.manager import ContentsManager
64 from .services.contents.manager import ContentsManager
64 from .services.contents.filemanager import FileContentsManager
65 from .services.contents.filemanager import FileContentsManager
65 from .services.clusters.clustermanager import ClusterManager
66 from .services.clusters.clustermanager import ClusterManager
66 from .services.sessions.sessionmanager import SessionManager
67 from .services.sessions.sessionmanager import SessionManager
67
68
68 from .auth.login import LoginHandler
69 from .auth.login import LoginHandler
69 from .auth.logout import LogoutHandler
70 from .auth.logout import LogoutHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
71 from .base.handlers import IPythonHandler, FileFindHandler
71
72
72 from IPython.config import Config
73 from IPython.config import Config
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import (
75 from IPython.core.application import (
75 BaseIPythonApplication, base_flags, base_aliases,
76 BaseIPythonApplication, base_flags, base_aliases,
76 )
77 )
77 from IPython.core.profiledir import ProfileDir
78 from IPython.core.profiledir import ProfileDir
78 from IPython.kernel import KernelManager
79 from IPython.kernel import KernelManager
79 from IPython.kernel.kernelspec import KernelSpecManager
80 from IPython.kernel.kernelspec import KernelSpecManager
80 from IPython.kernel.zmq.session import Session
81 from IPython.kernel.zmq.session import Session
81 from IPython.nbformat.sign import NotebookNotary
82 from IPython.nbformat.sign import NotebookNotary
82 from IPython.utils.importstring import import_item
83 from IPython.utils.importstring import import_item
83 from IPython.utils import submodule
84 from IPython.utils import submodule
84 from IPython.utils.process import check_pid
85 from IPython.utils.process import check_pid
85 from IPython.utils.traitlets import (
86 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 TraitError, Type,
88 TraitError, Type,
88 )
89 )
89 from IPython.utils import py3compat
90 from IPython.utils import py3compat
90 from IPython.utils.path import filefind, get_ipython_dir
91 from IPython.utils.path import filefind, get_ipython_dir
91 from IPython.utils.sysinfo import get_sys_info
92 from IPython.utils.sysinfo import get_sys_info
92
93
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 from .utils import url_path_join
95 from .utils import url_path_join
95
96
96 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
97 # Module globals
98 # Module globals
98 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
99
100
100 _examples = """
101 _examples = """
101 ipython notebook # start the notebook
102 ipython notebook # start the notebook
102 ipython notebook --profile=sympy # use the sympy profile
103 ipython notebook --profile=sympy # use the sympy profile
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 """
105 """
105
106
106 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
107 # Helper functions
108 # Helper functions
108 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
109
110
110 def random_ports(port, n):
111 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
112 """Generate a list of n random ports near the given port.
112
113
113 The first 5 ports will be sequential, and the remaining n-5 will be
114 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
115 randomly selected in the range [port-2*n, port+2*n].
115 """
116 """
116 for i in range(min(5, n)):
117 for i in range(min(5, n)):
117 yield port + i
118 yield port + i
118 for i in range(n-5):
119 for i in range(n-5):
119 yield max(1, port + random.randint(-2*n, 2*n))
120 yield max(1, port + random.randint(-2*n, 2*n))
120
121
121 def load_handlers(name):
122 def load_handlers(name):
122 """Load the (URL pattern, handler) tuples for each component."""
123 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.html.' + name
124 name = 'IPython.html.' + name
124 mod = __import__(name, fromlist=['default_handlers'])
125 mod = __import__(name, fromlist=['default_handlers'])
125 return mod.default_handlers
126 return mod.default_handlers
126
127
127 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
128 # The Tornado web application
129 # The Tornado web application
129 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
130
131
131 class NotebookWebApplication(web.Application):
132 class NotebookWebApplication(web.Application):
132
133
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
135 cluster_manager, session_manager, kernel_spec_manager,
135 config_manager, log,
136 config_manager, log,
136 base_url, default_url, settings_overrides, jinja_env_options):
137 base_url, default_url, settings_overrides, jinja_env_options):
137
138
138 settings = self.init_settings(
139 settings = self.init_settings(
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 default_url, settings_overrides, jinja_env_options)
142 default_url, settings_overrides, jinja_env_options)
142 handlers = self.init_handlers(settings)
143 handlers = self.init_handlers(settings)
143
144
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
146
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
148 cluster_manager, session_manager, kernel_spec_manager,
148 config_manager,
149 config_manager,
149 log, base_url, default_url, settings_overrides,
150 log, base_url, default_url, settings_overrides,
150 jinja_env_options=None):
151 jinja_env_options=None):
151
152
152 _template_path = settings_overrides.get(
153 _template_path = settings_overrides.get(
153 "template_path",
154 "template_path",
154 ipython_app.template_file_path,
155 ipython_app.template_file_path,
155 )
156 )
156 if isinstance(_template_path, str):
157 if isinstance(_template_path, str):
157 _template_path = (_template_path,)
158 _template_path = (_template_path,)
158 template_path = [os.path.expanduser(path) for path in _template_path]
159 template_path = [os.path.expanduser(path) for path in _template_path]
159
160
160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 jenv_opt = jinja_env_options if jinja_env_options else {}
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162
163
163 sys_info = get_sys_info()
164 sys_info = get_sys_info()
164 if sys_info['commit_source'] == 'repository':
165 if sys_info['commit_source'] == 'repository':
165 # don't cache (rely on 304) when working from master
166 # don't cache (rely on 304) when working from master
166 version_hash = ''
167 version_hash = ''
167 else:
168 else:
168 # reset the cache on server restart
169 # reset the cache on server restart
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170
171
171 settings = dict(
172 settings = dict(
172 # basics
173 # basics
173 log_function=log_request,
174 log_function=log_request,
174 base_url=base_url,
175 base_url=base_url,
175 default_url=default_url,
176 default_url=default_url,
176 template_path=template_path,
177 template_path=template_path,
177 static_path=ipython_app.static_file_path,
178 static_path=ipython_app.static_file_path,
178 static_handler_class = FileFindHandler,
179 static_handler_class = FileFindHandler,
179 static_url_prefix = url_path_join(base_url,'/static/'),
180 static_url_prefix = url_path_join(base_url,'/static/'),
180 static_handler_args = {
181 static_handler_args = {
181 # don't cache custom.js
182 # don't cache custom.js
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 },
184 },
184 version_hash=version_hash,
185 version_hash=version_hash,
185
186
186 # authentication
187 # authentication
187 cookie_secret=ipython_app.cookie_secret,
188 cookie_secret=ipython_app.cookie_secret,
188 login_url=url_path_join(base_url,'/login'),
189 login_url=url_path_join(base_url,'/login'),
189 login_handler_class=ipython_app.login_handler_class,
190 login_handler_class=ipython_app.login_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
191 logout_handler_class=ipython_app.logout_handler_class,
191 password=ipython_app.password,
192 password=ipython_app.password,
192
193
193 # managers
194 # managers
194 kernel_manager=kernel_manager,
195 kernel_manager=kernel_manager,
195 contents_manager=contents_manager,
196 contents_manager=contents_manager,
196 cluster_manager=cluster_manager,
197 cluster_manager=cluster_manager,
197 session_manager=session_manager,
198 session_manager=session_manager,
198 kernel_spec_manager=kernel_spec_manager,
199 kernel_spec_manager=kernel_spec_manager,
199 config_manager=config_manager,
200 config_manager=config_manager,
200
201
201 # IPython stuff
202 # IPython stuff
202 nbextensions_path=ipython_app.nbextensions_path,
203 nbextensions_path=ipython_app.nbextensions_path,
203 websocket_url=ipython_app.websocket_url,
204 websocket_url=ipython_app.websocket_url,
204 mathjax_url=ipython_app.mathjax_url,
205 mathjax_url=ipython_app.mathjax_url,
205 config=ipython_app.config,
206 config=ipython_app.config,
206 jinja2_env=env,
207 jinja2_env=env,
207 terminals_available=False, # Set later if terminals are available
208 terminals_available=False, # Set later if terminals are available
208 )
209 )
209
210
210 # allow custom overrides for the tornado web app.
211 # allow custom overrides for the tornado web app.
211 settings.update(settings_overrides)
212 settings.update(settings_overrides)
212 return settings
213 return settings
213
214
214 def init_handlers(self, settings):
215 def init_handlers(self, settings):
215 """Load the (URL pattern, handler) tuples for each component."""
216 """Load the (URL pattern, handler) tuples for each component."""
216
217
217 # Order matters. The first handler to match the URL will handle the request.
218 # Order matters. The first handler to match the URL will handle the request.
218 handlers = []
219 handlers = []
219 handlers.extend(load_handlers('tree.handlers'))
220 handlers.extend(load_handlers('tree.handlers'))
220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 handlers.extend([(r"/login", settings['login_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 handlers.extend(load_handlers('files.handlers'))
223 handlers.extend(load_handlers('files.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
224 handlers.extend(load_handlers('notebook.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
225 handlers.extend(load_handlers('nbconvert.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 handlers.extend(load_handlers('kernelspecs.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
227 handlers.extend(load_handlers('edit.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
228 handlers.extend(load_handlers('services.config.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
229 handlers.extend(load_handlers('services.kernels.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
230 handlers.extend(load_handlers('services.contents.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
231 handlers.extend(load_handlers('services.clusters.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
232 handlers.extend(load_handlers('services.sessions.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
235 handlers.extend(load_handlers('services.security.handlers'))
235 handlers.append(
236 handlers.append(
236 (r"/nbextensions/(.*)", FileFindHandler, {
237 (r"/nbextensions/(.*)", FileFindHandler, {
237 'path': settings['nbextensions_path'],
238 'path': settings['nbextensions_path'],
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 }),
240 }),
240 )
241 )
241 # register base handlers last
242 # register base handlers last
242 handlers.extend(load_handlers('base.handlers'))
243 handlers.extend(load_handlers('base.handlers'))
243 # set the URL that will be redirected from `/`
244 # set the URL that will be redirected from `/`
244 handlers.append(
245 handlers.append(
245 (r'/?', web.RedirectHandler, {
246 (r'/?', web.RedirectHandler, {
246 'url' : settings['default_url'],
247 'url' : settings['default_url'],
247 'permanent': False, # want 302, not 301
248 'permanent': False, # want 302, not 301
248 })
249 })
249 )
250 )
250 # prepend base_url onto the patterns that we match
251 # prepend base_url onto the patterns that we match
251 new_handlers = []
252 new_handlers = []
252 for handler in handlers:
253 for handler in handlers:
253 pattern = url_path_join(settings['base_url'], handler[0])
254 pattern = url_path_join(settings['base_url'], handler[0])
254 new_handler = tuple([pattern] + list(handler[1:]))
255 new_handler = tuple([pattern] + list(handler[1:]))
255 new_handlers.append(new_handler)
256 new_handlers.append(new_handler)
256 # add 404 on the end, which will catch everything that falls through
257 # add 404 on the end, which will catch everything that falls through
257 new_handlers.append((r'(.*)', Template404))
258 new_handlers.append((r'(.*)', Template404))
258 return new_handlers
259 return new_handlers
259
260
260
261
261 class NbserverListApp(BaseIPythonApplication):
262 class NbserverListApp(BaseIPythonApplication):
262
263
263 description="List currently running notebook servers in this profile."
264 description="List currently running notebook servers in this profile."
264
265
265 flags = dict(
266 flags = dict(
266 json=({'NbserverListApp': {'json': True}},
267 json=({'NbserverListApp': {'json': True}},
267 "Produce machine-readable JSON output."),
268 "Produce machine-readable JSON output."),
268 )
269 )
269
270
270 json = Bool(False, config=True,
271 json = Bool(False, config=True,
271 help="If True, each line of output will be a JSON object with the "
272 help="If True, each line of output will be a JSON object with the "
272 "details from the server info file.")
273 "details from the server info file.")
273
274
274 def start(self):
275 def start(self):
275 if not self.json:
276 if not self.json:
276 print("Currently running servers:")
277 print("Currently running servers:")
277 for serverinfo in list_running_servers(self.profile):
278 for serverinfo in list_running_servers(self.profile):
278 if self.json:
279 if self.json:
279 print(json.dumps(serverinfo))
280 print(json.dumps(serverinfo))
280 else:
281 else:
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282
283
283 #-----------------------------------------------------------------------------
284 #-----------------------------------------------------------------------------
284 # Aliases and Flags
285 # Aliases and Flags
285 #-----------------------------------------------------------------------------
286 #-----------------------------------------------------------------------------
286
287
287 flags = dict(base_flags)
288 flags = dict(base_flags)
288 flags['no-browser']=(
289 flags['no-browser']=(
289 {'NotebookApp' : {'open_browser' : False}},
290 {'NotebookApp' : {'open_browser' : False}},
290 "Don't open the notebook in a browser after startup."
291 "Don't open the notebook in a browser after startup."
291 )
292 )
292 flags['pylab']=(
293 flags['pylab']=(
293 {'NotebookApp' : {'pylab' : 'warn'}},
294 {'NotebookApp' : {'pylab' : 'warn'}},
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 )
296 )
296 flags['no-mathjax']=(
297 flags['no-mathjax']=(
297 {'NotebookApp' : {'enable_mathjax' : False}},
298 {'NotebookApp' : {'enable_mathjax' : False}},
298 """Disable MathJax
299 """Disable MathJax
299
300
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 very large, so you may want to disable it if you have a slow internet
302 very large, so you may want to disable it if you have a slow internet
302 connection, or for offline use of the notebook.
303 connection, or for offline use of the notebook.
303
304
304 When disabled, equations etc. will appear as their untransformed TeX source.
305 When disabled, equations etc. will appear as their untransformed TeX source.
305 """
306 """
306 )
307 )
307
308
308 # Add notebook manager flags
309 # Add notebook manager flags
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 'DEPRECATED, IGNORED',
311 'DEPRECATED, IGNORED',
311 'DEPRECATED, IGNORED'))
312 'DEPRECATED, IGNORED'))
312
313
313 aliases = dict(base_aliases)
314 aliases = dict(base_aliases)
314
315
315 aliases.update({
316 aliases.update({
316 'ip': 'NotebookApp.ip',
317 'ip': 'NotebookApp.ip',
317 'port': 'NotebookApp.port',
318 'port': 'NotebookApp.port',
318 'port-retries': 'NotebookApp.port_retries',
319 'port-retries': 'NotebookApp.port_retries',
319 'transport': 'KernelManager.transport',
320 'transport': 'KernelManager.transport',
320 'keyfile': 'NotebookApp.keyfile',
321 'keyfile': 'NotebookApp.keyfile',
321 'certfile': 'NotebookApp.certfile',
322 'certfile': 'NotebookApp.certfile',
322 'notebook-dir': 'NotebookApp.notebook_dir',
323 'notebook-dir': 'NotebookApp.notebook_dir',
323 'browser': 'NotebookApp.browser',
324 'browser': 'NotebookApp.browser',
324 'pylab': 'NotebookApp.pylab',
325 'pylab': 'NotebookApp.pylab',
325 })
326 })
326
327
327 #-----------------------------------------------------------------------------
328 #-----------------------------------------------------------------------------
328 # NotebookApp
329 # NotebookApp
329 #-----------------------------------------------------------------------------
330 #-----------------------------------------------------------------------------
330
331
331 class NotebookApp(BaseIPythonApplication):
332 class NotebookApp(BaseIPythonApplication):
332
333
333 name = 'ipython-notebook'
334 name = 'ipython-notebook'
334
335
335 description = """
336 description = """
336 The IPython HTML Notebook.
337 The IPython HTML Notebook.
337
338
338 This launches a Tornado based HTML Notebook Server that serves up an
339 This launches a Tornado based HTML Notebook Server that serves up an
339 HTML5/Javascript Notebook client.
340 HTML5/Javascript Notebook client.
340 """
341 """
341 examples = _examples
342 examples = _examples
342 aliases = aliases
343 aliases = aliases
343 flags = flags
344 flags = flags
344
345
345 classes = [
346 classes = [
346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 KernelManager, ProfileDir, Session, MappingKernelManager,
347 ContentsManager, FileContentsManager, NotebookNotary,
348 ContentsManager, FileContentsManager, NotebookNotary,
348 KernelSpecManager,
349 KernelSpecManager,
349 ]
350 ]
350 flags = Dict(flags)
351 flags = Dict(flags)
351 aliases = Dict(aliases)
352 aliases = Dict(aliases)
352
353
353 subcommands = dict(
354 subcommands = dict(
354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 )
356 )
356
357
357 _log_formatter_cls = LogFormatter
358 _log_formatter_cls = LogFormatter
358
359
359 def _log_level_default(self):
360 def _log_level_default(self):
360 return logging.INFO
361 return logging.INFO
361
362
362 def _log_datefmt_default(self):
363 def _log_datefmt_default(self):
363 """Exclude date from default date format"""
364 """Exclude date from default date format"""
364 return "%H:%M:%S"
365 return "%H:%M:%S"
365
366
366 def _log_format_default(self):
367 def _log_format_default(self):
367 """override default log format to include time"""
368 """override default log format to include time"""
368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369
370
370 # create requested profiles by default, if they don't exist:
371 # create requested profiles by default, if they don't exist:
371 auto_create = Bool(True)
372 auto_create = Bool(True)
372
373
373 # file to be opened in the notebook server
374 # file to be opened in the notebook server
374 file_to_run = Unicode('', config=True)
375 file_to_run = Unicode('', config=True)
375
376
376 # Network related information
377 # Network related information
377
378
378 allow_origin = Unicode('', config=True,
379 allow_origin = Unicode('', config=True,
379 help="""Set the Access-Control-Allow-Origin header
380 help="""Set the Access-Control-Allow-Origin header
380
381
381 Use '*' to allow any origin to access your server.
382 Use '*' to allow any origin to access your server.
382
383
383 Takes precedence over allow_origin_pat.
384 Takes precedence over allow_origin_pat.
384 """
385 """
385 )
386 )
386
387
387 allow_origin_pat = Unicode('', config=True,
388 allow_origin_pat = Unicode('', config=True,
388 help="""Use a regular expression for the Access-Control-Allow-Origin header
389 help="""Use a regular expression for the Access-Control-Allow-Origin header
389
390
390 Requests from an origin matching the expression will get replies with:
391 Requests from an origin matching the expression will get replies with:
391
392
392 Access-Control-Allow-Origin: origin
393 Access-Control-Allow-Origin: origin
393
394
394 where `origin` is the origin of the request.
395 where `origin` is the origin of the request.
395
396
396 Ignored if allow_origin is set.
397 Ignored if allow_origin is set.
397 """
398 """
398 )
399 )
399
400
400 allow_credentials = Bool(False, config=True,
401 allow_credentials = Bool(False, config=True,
401 help="Set the Access-Control-Allow-Credentials: true header"
402 help="Set the Access-Control-Allow-Credentials: true header"
402 )
403 )
403
404
404 default_url = Unicode('/tree', config=True,
405 default_url = Unicode('/tree', config=True,
405 help="The default URL to redirect to from `/`"
406 help="The default URL to redirect to from `/`"
406 )
407 )
407
408
408 ip = Unicode('localhost', config=True,
409 ip = Unicode('localhost', config=True,
409 help="The IP address the notebook server will listen on."
410 help="The IP address the notebook server will listen on."
410 )
411 )
411 def _ip_default(self):
412 def _ip_default(self):
412 """Return localhost if available, 127.0.0.1 otherwise.
413 """Return localhost if available, 127.0.0.1 otherwise.
413
414
414 On some (horribly broken) systems, localhost cannot be bound.
415 On some (horribly broken) systems, localhost cannot be bound.
415 """
416 """
416 s = socket.socket()
417 s = socket.socket()
417 try:
418 try:
418 s.bind(('localhost', 0))
419 s.bind(('localhost', 0))
419 except socket.error as e:
420 except socket.error as e:
420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
421 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
421 return '127.0.0.1'
422 return '127.0.0.1'
422 else:
423 else:
423 s.close()
424 s.close()
424 return 'localhost'
425 return 'localhost'
425
426
426 def _ip_changed(self, name, old, new):
427 def _ip_changed(self, name, old, new):
427 if new == u'*': self.ip = u''
428 if new == u'*': self.ip = u''
428
429
429 port = Integer(8888, config=True,
430 port = Integer(8888, config=True,
430 help="The port the notebook server will listen on."
431 help="The port the notebook server will listen on."
431 )
432 )
432 port_retries = Integer(50, config=True,
433 port_retries = Integer(50, config=True,
433 help="The number of additional ports to try if the specified port is not available."
434 help="The number of additional ports to try if the specified port is not available."
434 )
435 )
435
436
436 certfile = Unicode(u'', config=True,
437 certfile = Unicode(u'', config=True,
437 help="""The full path to an SSL/TLS certificate file."""
438 help="""The full path to an SSL/TLS certificate file."""
438 )
439 )
439
440
440 keyfile = Unicode(u'', config=True,
441 keyfile = Unicode(u'', config=True,
441 help="""The full path to a private key file for usage with SSL/TLS."""
442 help="""The full path to a private key file for usage with SSL/TLS."""
442 )
443 )
443
444
444 cookie_secret_file = Unicode(config=True,
445 cookie_secret_file = Unicode(config=True,
445 help="""The file where the cookie secret is stored."""
446 help="""The file where the cookie secret is stored."""
446 )
447 )
447 def _cookie_secret_file_default(self):
448 def _cookie_secret_file_default(self):
448 if self.profile_dir is None:
449 if self.profile_dir is None:
449 return ''
450 return ''
450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
451 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
451
452
452 cookie_secret = Bytes(b'', config=True,
453 cookie_secret = Bytes(b'', config=True,
453 help="""The random bytes used to secure cookies.
454 help="""The random bytes used to secure cookies.
454 By default this is a new random number every time you start the Notebook.
455 By default this is a new random number every time you start the Notebook.
455 Set it to a value in a config file to enable logins to persist across server sessions.
456 Set it to a value in a config file to enable logins to persist across server sessions.
456
457
457 Note: Cookie secrets should be kept private, do not share config files with
458 Note: Cookie secrets should be kept private, do not share config files with
458 cookie_secret stored in plaintext (you can read the value from a file).
459 cookie_secret stored in plaintext (you can read the value from a file).
459 """
460 """
460 )
461 )
461 def _cookie_secret_default(self):
462 def _cookie_secret_default(self):
462 if os.path.exists(self.cookie_secret_file):
463 if os.path.exists(self.cookie_secret_file):
463 with io.open(self.cookie_secret_file, 'rb') as f:
464 with io.open(self.cookie_secret_file, 'rb') as f:
464 return f.read()
465 return f.read()
465 else:
466 else:
466 secret = base64.encodestring(os.urandom(1024))
467 secret = base64.encodestring(os.urandom(1024))
467 self._write_cookie_secret_file(secret)
468 self._write_cookie_secret_file(secret)
468 return secret
469 return secret
469
470
470 def _write_cookie_secret_file(self, secret):
471 def _write_cookie_secret_file(self, secret):
471 """write my secret to my secret_file"""
472 """write my secret to my secret_file"""
472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
473 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
473 with io.open(self.cookie_secret_file, 'wb') as f:
474 with io.open(self.cookie_secret_file, 'wb') as f:
474 f.write(secret)
475 f.write(secret)
475 try:
476 try:
476 os.chmod(self.cookie_secret_file, 0o600)
477 os.chmod(self.cookie_secret_file, 0o600)
477 except OSError:
478 except OSError:
478 self.log.warn(
479 self.log.warn(
479 "Could not set permissions on %s",
480 "Could not set permissions on %s",
480 self.cookie_secret_file
481 self.cookie_secret_file
481 )
482 )
482
483
483 password = Unicode(u'', config=True,
484 password = Unicode(u'', config=True,
484 help="""Hashed password to use for web authentication.
485 help="""Hashed password to use for web authentication.
485
486
486 To generate, type in a python/IPython shell:
487 To generate, type in a python/IPython shell:
487
488
488 from IPython.lib import passwd; passwd()
489 from IPython.lib import passwd; passwd()
489
490
490 The string should be of the form type:salt:hashed-password.
491 The string should be of the form type:salt:hashed-password.
491 """
492 """
492 )
493 )
493
494
494 open_browser = Bool(True, config=True,
495 open_browser = Bool(True, config=True,
495 help="""Whether to open in a browser after starting.
496 help="""Whether to open in a browser after starting.
496 The specific browser used is platform dependent and
497 The specific browser used is platform dependent and
497 determined by the python standard library `webbrowser`
498 determined by the python standard library `webbrowser`
498 module, unless it is overridden using the --browser
499 module, unless it is overridden using the --browser
499 (NotebookApp.browser) configuration option.
500 (NotebookApp.browser) configuration option.
500 """)
501 """)
501
502
502 browser = Unicode(u'', config=True,
503 browser = Unicode(u'', config=True,
503 help="""Specify what command to use to invoke a web
504 help="""Specify what command to use to invoke a web
504 browser when opening the notebook. If not specified, the
505 browser when opening the notebook. If not specified, the
505 default browser will be determined by the `webbrowser`
506 default browser will be determined by the `webbrowser`
506 standard library module, which allows setting of the
507 standard library module, which allows setting of the
507 BROWSER environment variable to override it.
508 BROWSER environment variable to override it.
508 """)
509 """)
509
510
510 webapp_settings = Dict(config=True,
511 webapp_settings = Dict(config=True,
511 help="DEPRECATED, use tornado_settings"
512 help="DEPRECATED, use tornado_settings"
512 )
513 )
513 def _webapp_settings_changed(self, name, old, new):
514 def _webapp_settings_changed(self, name, old, new):
514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
515 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
515 self.tornado_settings = new
516 self.tornado_settings = new
516
517
517 tornado_settings = Dict(config=True,
518 tornado_settings = Dict(config=True,
518 help="Supply overrides for the tornado.web.Application that the "
519 help="Supply overrides for the tornado.web.Application that the "
519 "IPython notebook uses.")
520 "IPython notebook uses.")
520
521
521 ssl_options = Dict(config=True,
522 ssl_options = Dict(config=True,
522 help="""Supply SSL options for the tornado HTTPServer.
523 help="""Supply SSL options for the tornado HTTPServer.
523 See the tornado docs for details.""")
524 See the tornado docs for details.""")
524
525
525 jinja_environment_options = Dict(config=True,
526 jinja_environment_options = Dict(config=True,
526 help="Supply extra arguments that will be passed to Jinja environment.")
527 help="Supply extra arguments that will be passed to Jinja environment.")
527
528
528 enable_mathjax = Bool(True, config=True,
529 enable_mathjax = Bool(True, config=True,
529 help="""Whether to enable MathJax for typesetting math/TeX
530 help="""Whether to enable MathJax for typesetting math/TeX
530
531
531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
532 MathJax is the javascript library IPython uses to render math/LaTeX. It is
532 very large, so you may want to disable it if you have a slow internet
533 very large, so you may want to disable it if you have a slow internet
533 connection, or for offline use of the notebook.
534 connection, or for offline use of the notebook.
534
535
535 When disabled, equations etc. will appear as their untransformed TeX source.
536 When disabled, equations etc. will appear as their untransformed TeX source.
536 """
537 """
537 )
538 )
538 def _enable_mathjax_changed(self, name, old, new):
539 def _enable_mathjax_changed(self, name, old, new):
539 """set mathjax url to empty if mathjax is disabled"""
540 """set mathjax url to empty if mathjax is disabled"""
540 if not new:
541 if not new:
541 self.mathjax_url = u''
542 self.mathjax_url = u''
542
543
543 base_url = Unicode('/', config=True,
544 base_url = Unicode('/', config=True,
544 help='''The base URL for the notebook server.
545 help='''The base URL for the notebook server.
545
546
546 Leading and trailing slashes can be omitted,
547 Leading and trailing slashes can be omitted,
547 and will automatically be added.
548 and will automatically be added.
548 ''')
549 ''')
549 def _base_url_changed(self, name, old, new):
550 def _base_url_changed(self, name, old, new):
550 if not new.startswith('/'):
551 if not new.startswith('/'):
551 self.base_url = '/'+new
552 self.base_url = '/'+new
552 elif not new.endswith('/'):
553 elif not new.endswith('/'):
553 self.base_url = new+'/'
554 self.base_url = new+'/'
554
555
555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
556 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
556 def _base_project_url_changed(self, name, old, new):
557 def _base_project_url_changed(self, name, old, new):
557 self.log.warn("base_project_url is deprecated, use base_url")
558 self.log.warn("base_project_url is deprecated, use base_url")
558 self.base_url = new
559 self.base_url = new
559
560
560 extra_static_paths = List(Unicode, config=True,
561 extra_static_paths = List(Unicode, config=True,
561 help="""Extra paths to search for serving static files.
562 help="""Extra paths to search for serving static files.
562
563
563 This allows adding javascript/css to be available from the notebook server machine,
564 This allows adding javascript/css to be available from the notebook server machine,
564 or overriding individual files in the IPython"""
565 or overriding individual files in the IPython"""
565 )
566 )
566 def _extra_static_paths_default(self):
567 def _extra_static_paths_default(self):
567 return [os.path.join(self.profile_dir.location, 'static')]
568 return [os.path.join(self.profile_dir.location, 'static')]
568
569
569 @property
570 @property
570 def static_file_path(self):
571 def static_file_path(self):
571 """return extra paths + the default location"""
572 """return extra paths + the default location"""
572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
573 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
573
574
574 extra_template_paths = List(Unicode, config=True,
575 extra_template_paths = List(Unicode, config=True,
575 help="""Extra paths to search for serving jinja templates.
576 help="""Extra paths to search for serving jinja templates.
576
577
577 Can be used to override templates from IPython.html.templates."""
578 Can be used to override templates from IPython.html.templates."""
578 )
579 )
579 def _extra_template_paths_default(self):
580 def _extra_template_paths_default(self):
580 return []
581 return []
581
582
582 @property
583 @property
583 def template_file_path(self):
584 def template_file_path(self):
584 """return extra paths + the default locations"""
585 """return extra paths + the default locations"""
585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
586 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
586
587
587 extra_nbextensions_path = List(Unicode, config=True,
588 extra_nbextensions_path = List(Unicode, config=True,
588 help="""extra paths to look for Javascript notebook extensions"""
589 help="""extra paths to look for Javascript notebook extensions"""
589 )
590 )
590
591
591 @property
592 @property
592 def nbextensions_path(self):
593 def nbextensions_path(self):
593 """The path to look for Javascript notebook extensions"""
594 """The path to look for Javascript notebook extensions"""
594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
595 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
595
596
596 websocket_url = Unicode("", config=True,
597 websocket_url = Unicode("", config=True,
597 help="""The base URL for websockets,
598 help="""The base URL for websockets,
598 if it differs from the HTTP server (hint: it almost certainly doesn't).
599 if it differs from the HTTP server (hint: it almost certainly doesn't).
599
600
600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
601 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
601 """
602 """
602 )
603 )
603 mathjax_url = Unicode("", config=True,
604 mathjax_url = Unicode("", config=True,
604 help="""The url for MathJax.js."""
605 help="""The url for MathJax.js."""
605 )
606 )
606 def _mathjax_url_default(self):
607 def _mathjax_url_default(self):
607 if not self.enable_mathjax:
608 if not self.enable_mathjax:
608 return u''
609 return u''
609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
610 static_url_prefix = self.tornado_settings.get("static_url_prefix",
610 url_path_join(self.base_url, "static")
611 url_path_join(self.base_url, "static")
611 )
612 )
612
613
613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
614 # try local mathjax, either in nbextensions/mathjax or static/mathjax
614 for (url_prefix, search_path) in [
615 for (url_prefix, search_path) in [
615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
616 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
616 (static_url_prefix, self.static_file_path),
617 (static_url_prefix, self.static_file_path),
617 ]:
618 ]:
618 self.log.debug("searching for local mathjax in %s", search_path)
619 self.log.debug("searching for local mathjax in %s", search_path)
619 try:
620 try:
620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
621 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
621 except IOError:
622 except IOError:
622 continue
623 continue
623 else:
624 else:
624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
625 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
626 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
626 return url
627 return url
627
628
628 # no local mathjax, serve from CDN
629 # no local mathjax, serve from CDN
629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
630 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
630 self.log.info("Using MathJax from CDN: %s", url)
631 self.log.info("Using MathJax from CDN: %s", url)
631 return url
632 return url
632
633
633 def _mathjax_url_changed(self, name, old, new):
634 def _mathjax_url_changed(self, name, old, new):
634 if new and not self.enable_mathjax:
635 if new and not self.enable_mathjax:
635 # enable_mathjax=False overrides mathjax_url
636 # enable_mathjax=False overrides mathjax_url
636 self.mathjax_url = u''
637 self.mathjax_url = u''
637 else:
638 else:
638 self.log.info("Using MathJax: %s", new)
639 self.log.info("Using MathJax: %s", new)
639
640
640 contents_manager_class = Type(
641 contents_manager_class = Type(
641 default_value=FileContentsManager,
642 default_value=FileContentsManager,
642 klass=ContentsManager,
643 klass=ContentsManager,
643 config=True,
644 config=True,
644 help='The notebook manager class to use.'
645 help='The notebook manager class to use.'
645 )
646 )
646 kernel_manager_class = Type(
647 kernel_manager_class = Type(
647 default_value=MappingKernelManager,
648 default_value=MappingKernelManager,
648 config=True,
649 config=True,
649 help='The kernel manager class to use.'
650 help='The kernel manager class to use.'
650 )
651 )
651 session_manager_class = Type(
652 session_manager_class = Type(
652 default_value=SessionManager,
653 default_value=SessionManager,
653 config=True,
654 config=True,
654 help='The session manager class to use.'
655 help='The session manager class to use.'
655 )
656 )
656 cluster_manager_class = Type(
657 cluster_manager_class = Type(
657 default_value=ClusterManager,
658 default_value=ClusterManager,
658 config=True,
659 config=True,
659 help='The cluster manager class to use.'
660 help='The cluster manager class to use.'
660 )
661 )
661
662
662 config_manager_class = Type(
663 config_manager_class = Type(
663 default_value=ConfigManager,
664 default_value=ConfigManager,
664 config = True,
665 config = True,
665 help='The config manager class to use'
666 help='The config manager class to use'
666 )
667 )
667
668
668 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
669 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
669
670
670 kernel_spec_manager_class = Type(
671 kernel_spec_manager_class = Type(
671 default_value=KernelSpecManager,
672 default_value=KernelSpecManager,
672 config=True,
673 config=True,
673 help="""
674 help="""
674 The kernel spec manager class to use. Should be a subclass
675 The kernel spec manager class to use. Should be a subclass
675 of `IPython.kernel.kernelspec.KernelSpecManager`.
676 of `IPython.kernel.kernelspec.KernelSpecManager`.
676
677
677 The Api of KernelSpecManager is provisional and might change
678 The Api of KernelSpecManager is provisional and might change
678 without warning between this version of IPython and the next stable one.
679 without warning between this version of IPython and the next stable one.
679 """
680 """
680 )
681 )
681
682
682 login_handler_class = Type(
683 login_handler_class = Type(
683 default_value=LoginHandler,
684 default_value=LoginHandler,
684 klass=web.RequestHandler,
685 klass=web.RequestHandler,
685 config=True,
686 config=True,
686 help='The login handler class to use.',
687 help='The login handler class to use.',
687 )
688 )
688
689
689 logout_handler_class = Type(
690 logout_handler_class = Type(
690 default_value=LogoutHandler,
691 default_value=LogoutHandler,
691 klass=web.RequestHandler,
692 klass=web.RequestHandler,
692 config=True,
693 config=True,
693 help='The logout handler class to use.',
694 help='The logout handler class to use.',
694 )
695 )
695
696
696 trust_xheaders = Bool(False, config=True,
697 trust_xheaders = Bool(False, config=True,
697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
698 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
699 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
699 )
700 )
700
701
701 info_file = Unicode()
702 info_file = Unicode()
702
703
703 def _info_file_default(self):
704 def _info_file_default(self):
704 info_file = "nbserver-%s.json"%os.getpid()
705 info_file = "nbserver-%s.json"%os.getpid()
705 return os.path.join(self.profile_dir.security_dir, info_file)
706 return os.path.join(self.profile_dir.security_dir, info_file)
706
707
707 pylab = Unicode('disabled', config=True,
708 pylab = Unicode('disabled', config=True,
708 help="""
709 help="""
709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
710 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
710 """
711 """
711 )
712 )
712 def _pylab_changed(self, name, old, new):
713 def _pylab_changed(self, name, old, new):
713 """when --pylab is specified, display a warning and exit"""
714 """when --pylab is specified, display a warning and exit"""
714 if new != 'warn':
715 if new != 'warn':
715 backend = ' %s' % new
716 backend = ' %s' % new
716 else:
717 else:
717 backend = ''
718 backend = ''
718 self.log.error("Support for specifying --pylab on the command line has been removed.")
719 self.log.error("Support for specifying --pylab on the command line has been removed.")
719 self.log.error(
720 self.log.error(
720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
721 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
721 )
722 )
722 self.exit(1)
723 self.exit(1)
723
724
724 notebook_dir = Unicode(config=True,
725 notebook_dir = Unicode(config=True,
725 help="The directory to use for notebooks and kernels."
726 help="The directory to use for notebooks and kernels."
726 )
727 )
727
728
728 def _notebook_dir_default(self):
729 def _notebook_dir_default(self):
729 if self.file_to_run:
730 if self.file_to_run:
730 return os.path.dirname(os.path.abspath(self.file_to_run))
731 return os.path.dirname(os.path.abspath(self.file_to_run))
731 else:
732 else:
732 return py3compat.getcwd()
733 return py3compat.getcwd()
733
734
734 def _notebook_dir_changed(self, name, old, new):
735 def _notebook_dir_changed(self, name, old, new):
735 """Do a bit of validation of the notebook dir."""
736 """Do a bit of validation of the notebook dir."""
736 if not os.path.isabs(new):
737 if not os.path.isabs(new):
737 # If we receive a non-absolute path, make it absolute.
738 # If we receive a non-absolute path, make it absolute.
738 self.notebook_dir = os.path.abspath(new)
739 self.notebook_dir = os.path.abspath(new)
739 return
740 return
740 if not os.path.isdir(new):
741 if not os.path.isdir(new):
741 raise TraitError("No such notebook dir: %r" % new)
742 raise TraitError("No such notebook dir: %r" % new)
742
743
743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
744 # setting App.notebook_dir implies setting notebook and kernel dirs as well
744 self.config.FileContentsManager.root_dir = new
745 self.config.FileContentsManager.root_dir = new
745 self.config.MappingKernelManager.root_dir = new
746 self.config.MappingKernelManager.root_dir = new
746
747
747 server_extensions = List(Unicode(), config=True,
748 server_extensions = List(Unicode(), config=True,
748 help=("Python modules to load as notebook server extensions. "
749 help=("Python modules to load as notebook server extensions. "
749 "This is an experimental API, and may change in future releases.")
750 "This is an experimental API, and may change in future releases.")
750 )
751 )
751
752
752 reraise_server_extension_failures = Bool(
753 reraise_server_extension_failures = Bool(
753 False,
754 False,
754 config=True,
755 config=True,
755 help="Reraise exceptions encountered loading server extensions?",
756 help="Reraise exceptions encountered loading server extensions?",
756 )
757 )
757
758
758 def parse_command_line(self, argv=None):
759 def parse_command_line(self, argv=None):
759 super(NotebookApp, self).parse_command_line(argv)
760 super(NotebookApp, self).parse_command_line(argv)
760
761
761 if self.extra_args:
762 if self.extra_args:
762 arg0 = self.extra_args[0]
763 arg0 = self.extra_args[0]
763 f = os.path.abspath(arg0)
764 f = os.path.abspath(arg0)
764 self.argv.remove(arg0)
765 self.argv.remove(arg0)
765 if not os.path.exists(f):
766 if not os.path.exists(f):
766 self.log.critical("No such file or directory: %s", f)
767 self.log.critical("No such file or directory: %s", f)
767 self.exit(1)
768 self.exit(1)
768
769
769 # Use config here, to ensure that it takes higher priority than
770 # Use config here, to ensure that it takes higher priority than
770 # anything that comes from the profile.
771 # anything that comes from the profile.
771 c = Config()
772 c = Config()
772 if os.path.isdir(f):
773 if os.path.isdir(f):
773 c.NotebookApp.notebook_dir = f
774 c.NotebookApp.notebook_dir = f
774 elif os.path.isfile(f):
775 elif os.path.isfile(f):
775 c.NotebookApp.file_to_run = f
776 c.NotebookApp.file_to_run = f
776 self.update_config(c)
777 self.update_config(c)
777
778
778 def init_configurables(self):
779 def init_configurables(self):
779 self.kernel_spec_manager = self.kernel_spec_manager_class(
780 self.kernel_spec_manager = self.kernel_spec_manager_class(
780 parent=self,
781 parent=self,
781 ipython_dir=self.ipython_dir,
782 ipython_dir=self.ipython_dir,
782 )
783 )
783 self.kernel_manager = self.kernel_manager_class(
784 self.kernel_manager = self.kernel_manager_class(
784 parent=self,
785 parent=self,
785 log=self.log,
786 log=self.log,
786 connection_dir=self.profile_dir.security_dir,
787 connection_dir=self.profile_dir.security_dir,
787 )
788 )
788 self.contents_manager = self.contents_manager_class(
789 self.contents_manager = self.contents_manager_class(
789 parent=self,
790 parent=self,
790 log=self.log,
791 log=self.log,
791 )
792 )
792 self.session_manager = self.session_manager_class(
793 self.session_manager = self.session_manager_class(
793 parent=self,
794 parent=self,
794 log=self.log,
795 log=self.log,
795 kernel_manager=self.kernel_manager,
796 kernel_manager=self.kernel_manager,
796 contents_manager=self.contents_manager,
797 contents_manager=self.contents_manager,
797 )
798 )
798 self.cluster_manager = self.cluster_manager_class(
799 self.cluster_manager = self.cluster_manager_class(
799 parent=self,
800 parent=self,
800 log=self.log,
801 log=self.log,
801 )
802 )
802
803
803 self.config_manager = self.config_manager_class(
804 self.config_manager = self.config_manager_class(
804 parent=self,
805 parent=self,
805 log=self.log,
806 log=self.log,
806 profile_dir=self.profile_dir.location,
807 profile_dir=self.profile_dir.location,
807 )
808 )
808
809
809 def init_logging(self):
810 def init_logging(self):
810 # This prevents double log messages because tornado use a root logger that
811 # This prevents double log messages because tornado use a root logger that
811 # self.log is a child of. The logging module dipatches log messages to a log
812 # self.log is a child of. The logging module dipatches log messages to a log
812 # and all of its ancenstors until propagate is set to False.
813 # and all of its ancenstors until propagate is set to False.
813 self.log.propagate = False
814 self.log.propagate = False
814
815
815 for log in app_log, access_log, gen_log:
816 for log in app_log, access_log, gen_log:
816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
817 # consistent log output name (NotebookApp instead of tornado.access, etc.)
817 log.name = self.log.name
818 log.name = self.log.name
818 # hook up tornado 3's loggers to our app handlers
819 # hook up tornado 3's loggers to our app handlers
819 logger = logging.getLogger('tornado')
820 logger = logging.getLogger('tornado')
820 logger.propagate = True
821 logger.propagate = True
821 logger.parent = self.log
822 logger.parent = self.log
822 logger.setLevel(self.log.level)
823 logger.setLevel(self.log.level)
823
824
824 def init_webapp(self):
825 def init_webapp(self):
825 """initialize tornado webapp and httpserver"""
826 """initialize tornado webapp and httpserver"""
826 self.tornado_settings['allow_origin'] = self.allow_origin
827 self.tornado_settings['allow_origin'] = self.allow_origin
827 if self.allow_origin_pat:
828 if self.allow_origin_pat:
828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
829 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
829 self.tornado_settings['allow_credentials'] = self.allow_credentials
830 self.tornado_settings['allow_credentials'] = self.allow_credentials
830 # ensure default_url starts with base_url
831 # ensure default_url starts with base_url
831 if not self.default_url.startswith(self.base_url):
832 if not self.default_url.startswith(self.base_url):
832 self.default_url = url_path_join(self.base_url, self.default_url)
833 self.default_url = url_path_join(self.base_url, self.default_url)
833
834
834 self.web_app = NotebookWebApplication(
835 self.web_app = NotebookWebApplication(
835 self, self.kernel_manager, self.contents_manager,
836 self, self.kernel_manager, self.contents_manager,
836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
837 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
837 self.config_manager,
838 self.config_manager,
838 self.log, self.base_url, self.default_url, self.tornado_settings,
839 self.log, self.base_url, self.default_url, self.tornado_settings,
839 self.jinja_environment_options
840 self.jinja_environment_options
840 )
841 )
841 ssl_options = self.ssl_options
842 ssl_options = self.ssl_options
842 if self.certfile:
843 if self.certfile:
843 ssl_options['certfile'] = self.certfile
844 ssl_options['certfile'] = self.certfile
844 if self.keyfile:
845 if self.keyfile:
845 ssl_options['keyfile'] = self.keyfile
846 ssl_options['keyfile'] = self.keyfile
846 if not ssl_options:
847 if not ssl_options:
847 # None indicates no SSL config
848 # None indicates no SSL config
848 ssl_options = None
849 ssl_options = None
850 else:
851 # Disable SSLv3, since its use is discouraged.
852 ssl_options['ssl_version']=ssl.PROTOCOL_TLSv1
849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
853 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
854 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
851 xheaders=self.trust_xheaders)
855 xheaders=self.trust_xheaders)
852
856
853 success = None
857 success = None
854 for port in random_ports(self.port, self.port_retries+1):
858 for port in random_ports(self.port, self.port_retries+1):
855 try:
859 try:
856 self.http_server.listen(port, self.ip)
860 self.http_server.listen(port, self.ip)
857 except socket.error as e:
861 except socket.error as e:
858 if e.errno == errno.EADDRINUSE:
862 if e.errno == errno.EADDRINUSE:
859 self.log.info('The port %i is already in use, trying another random port.' % port)
863 self.log.info('The port %i is already in use, trying another random port.' % port)
860 continue
864 continue
861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
865 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
862 self.log.warn("Permission to listen on port %i denied" % port)
866 self.log.warn("Permission to listen on port %i denied" % port)
863 continue
867 continue
864 else:
868 else:
865 raise
869 raise
866 else:
870 else:
867 self.port = port
871 self.port = port
868 success = True
872 success = True
869 break
873 break
870 if not success:
874 if not success:
871 self.log.critical('ERROR: the notebook server could not be started because '
875 self.log.critical('ERROR: the notebook server could not be started because '
872 'no available port could be found.')
876 'no available port could be found.')
873 self.exit(1)
877 self.exit(1)
874
878
875 @property
879 @property
876 def display_url(self):
880 def display_url(self):
877 ip = self.ip if self.ip else '[all ip addresses on your system]'
881 ip = self.ip if self.ip else '[all ip addresses on your system]'
878 return self._url(ip)
882 return self._url(ip)
879
883
880 @property
884 @property
881 def connection_url(self):
885 def connection_url(self):
882 ip = self.ip if self.ip else 'localhost'
886 ip = self.ip if self.ip else 'localhost'
883 return self._url(ip)
887 return self._url(ip)
884
888
885 def _url(self, ip):
889 def _url(self, ip):
886 proto = 'https' if self.certfile else 'http'
890 proto = 'https' if self.certfile else 'http'
887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
891 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
888
892
889 def init_terminals(self):
893 def init_terminals(self):
890 try:
894 try:
891 from .terminal import initialize
895 from .terminal import initialize
892 initialize(self.web_app)
896 initialize(self.web_app)
893 self.web_app.settings['terminals_available'] = True
897 self.web_app.settings['terminals_available'] = True
894 except ImportError as e:
898 except ImportError as e:
895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
899 log = self.log.debug if sys.platform == 'win32' else self.log.warn
896 log("Terminals not available (error was %s)", e)
900 log("Terminals not available (error was %s)", e)
897
901
898 def init_signal(self):
902 def init_signal(self):
899 if not sys.platform.startswith('win'):
903 if not sys.platform.startswith('win'):
900 signal.signal(signal.SIGINT, self._handle_sigint)
904 signal.signal(signal.SIGINT, self._handle_sigint)
901 signal.signal(signal.SIGTERM, self._signal_stop)
905 signal.signal(signal.SIGTERM, self._signal_stop)
902 if hasattr(signal, 'SIGUSR1'):
906 if hasattr(signal, 'SIGUSR1'):
903 # Windows doesn't support SIGUSR1
907 # Windows doesn't support SIGUSR1
904 signal.signal(signal.SIGUSR1, self._signal_info)
908 signal.signal(signal.SIGUSR1, self._signal_info)
905 if hasattr(signal, 'SIGINFO'):
909 if hasattr(signal, 'SIGINFO'):
906 # only on BSD-based systems
910 # only on BSD-based systems
907 signal.signal(signal.SIGINFO, self._signal_info)
911 signal.signal(signal.SIGINFO, self._signal_info)
908
912
909 def _handle_sigint(self, sig, frame):
913 def _handle_sigint(self, sig, frame):
910 """SIGINT handler spawns confirmation dialog"""
914 """SIGINT handler spawns confirmation dialog"""
911 # register more forceful signal handler for ^C^C case
915 # register more forceful signal handler for ^C^C case
912 signal.signal(signal.SIGINT, self._signal_stop)
916 signal.signal(signal.SIGINT, self._signal_stop)
913 # request confirmation dialog in bg thread, to avoid
917 # request confirmation dialog in bg thread, to avoid
914 # blocking the App
918 # blocking the App
915 thread = threading.Thread(target=self._confirm_exit)
919 thread = threading.Thread(target=self._confirm_exit)
916 thread.daemon = True
920 thread.daemon = True
917 thread.start()
921 thread.start()
918
922
919 def _restore_sigint_handler(self):
923 def _restore_sigint_handler(self):
920 """callback for restoring original SIGINT handler"""
924 """callback for restoring original SIGINT handler"""
921 signal.signal(signal.SIGINT, self._handle_sigint)
925 signal.signal(signal.SIGINT, self._handle_sigint)
922
926
923 def _confirm_exit(self):
927 def _confirm_exit(self):
924 """confirm shutdown on ^C
928 """confirm shutdown on ^C
925
929
926 A second ^C, or answering 'y' within 5s will cause shutdown,
930 A second ^C, or answering 'y' within 5s will cause shutdown,
927 otherwise original SIGINT handler will be restored.
931 otherwise original SIGINT handler will be restored.
928
932
929 This doesn't work on Windows.
933 This doesn't work on Windows.
930 """
934 """
931 info = self.log.info
935 info = self.log.info
932 info('interrupted')
936 info('interrupted')
933 print(self.notebook_info())
937 print(self.notebook_info())
934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
938 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
935 sys.stdout.flush()
939 sys.stdout.flush()
936 r,w,x = select.select([sys.stdin], [], [], 5)
940 r,w,x = select.select([sys.stdin], [], [], 5)
937 if r:
941 if r:
938 line = sys.stdin.readline()
942 line = sys.stdin.readline()
939 if line.lower().startswith('y') and 'n' not in line.lower():
943 if line.lower().startswith('y') and 'n' not in line.lower():
940 self.log.critical("Shutdown confirmed")
944 self.log.critical("Shutdown confirmed")
941 ioloop.IOLoop.current().stop()
945 ioloop.IOLoop.current().stop()
942 return
946 return
943 else:
947 else:
944 print("No answer for 5s:", end=' ')
948 print("No answer for 5s:", end=' ')
945 print("resuming operation...")
949 print("resuming operation...")
946 # no answer, or answer is no:
950 # no answer, or answer is no:
947 # set it back to original SIGINT handler
951 # set it back to original SIGINT handler
948 # use IOLoop.add_callback because signal.signal must be called
952 # use IOLoop.add_callback because signal.signal must be called
949 # from main thread
953 # from main thread
950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
954 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
951
955
952 def _signal_stop(self, sig, frame):
956 def _signal_stop(self, sig, frame):
953 self.log.critical("received signal %s, stopping", sig)
957 self.log.critical("received signal %s, stopping", sig)
954 ioloop.IOLoop.current().stop()
958 ioloop.IOLoop.current().stop()
955
959
956 def _signal_info(self, sig, frame):
960 def _signal_info(self, sig, frame):
957 print(self.notebook_info())
961 print(self.notebook_info())
958
962
959 def init_components(self):
963 def init_components(self):
960 """Check the components submodule, and warn if it's unclean"""
964 """Check the components submodule, and warn if it's unclean"""
961 status = submodule.check_submodule_status()
965 status = submodule.check_submodule_status()
962 if status == 'missing':
966 if status == 'missing':
963 self.log.warn("components submodule missing, running `git submodule update`")
967 self.log.warn("components submodule missing, running `git submodule update`")
964 submodule.update_submodules(submodule.ipython_parent())
968 submodule.update_submodules(submodule.ipython_parent())
965 elif status == 'unclean':
969 elif status == 'unclean':
966 self.log.warn("components submodule unclean, you may see 404s on static/components")
970 self.log.warn("components submodule unclean, you may see 404s on static/components")
967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
971 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
968
972
969 def init_server_extensions(self):
973 def init_server_extensions(self):
970 """Load any extensions specified by config.
974 """Load any extensions specified by config.
971
975
972 Import the module, then call the load_jupyter_server_extension function,
976 Import the module, then call the load_jupyter_server_extension function,
973 if one exists.
977 if one exists.
974
978
975 The extension API is experimental, and may change in future releases.
979 The extension API is experimental, and may change in future releases.
976 """
980 """
977 for modulename in self.server_extensions:
981 for modulename in self.server_extensions:
978 try:
982 try:
979 mod = importlib.import_module(modulename)
983 mod = importlib.import_module(modulename)
980 func = getattr(mod, 'load_jupyter_server_extension', None)
984 func = getattr(mod, 'load_jupyter_server_extension', None)
981 if func is not None:
985 if func is not None:
982 func(self)
986 func(self)
983 except Exception:
987 except Exception:
984 if self.reraise_server_extension_failures:
988 if self.reraise_server_extension_failures:
985 raise
989 raise
986 self.log.warn("Error loading server extension %s", modulename,
990 self.log.warn("Error loading server extension %s", modulename,
987 exc_info=True)
991 exc_info=True)
988
992
989 @catch_config_error
993 @catch_config_error
990 def initialize(self, argv=None):
994 def initialize(self, argv=None):
991 super(NotebookApp, self).initialize(argv)
995 super(NotebookApp, self).initialize(argv)
992 self.init_logging()
996 self.init_logging()
993 self.init_configurables()
997 self.init_configurables()
994 self.init_components()
998 self.init_components()
995 self.init_webapp()
999 self.init_webapp()
996 self.init_terminals()
1000 self.init_terminals()
997 self.init_signal()
1001 self.init_signal()
998 self.init_server_extensions()
1002 self.init_server_extensions()
999
1003
1000 def cleanup_kernels(self):
1004 def cleanup_kernels(self):
1001 """Shutdown all kernels.
1005 """Shutdown all kernels.
1002
1006
1003 The kernels will shutdown themselves when this process no longer exists,
1007 The kernels will shutdown themselves when this process no longer exists,
1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1008 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1005 """
1009 """
1006 self.log.info('Shutting down kernels')
1010 self.log.info('Shutting down kernels')
1007 self.kernel_manager.shutdown_all()
1011 self.kernel_manager.shutdown_all()
1008
1012
1009 def notebook_info(self):
1013 def notebook_info(self):
1010 "Return the current working directory and the server url information"
1014 "Return the current working directory and the server url information"
1011 info = self.contents_manager.info_string() + "\n"
1015 info = self.contents_manager.info_string() + "\n"
1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1016 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1017 return info + "The IPython Notebook is running at: %s" % self.display_url
1014
1018
1015 def server_info(self):
1019 def server_info(self):
1016 """Return a JSONable dict of information about this server."""
1020 """Return a JSONable dict of information about this server."""
1017 return {'url': self.connection_url,
1021 return {'url': self.connection_url,
1018 'hostname': self.ip if self.ip else 'localhost',
1022 'hostname': self.ip if self.ip else 'localhost',
1019 'port': self.port,
1023 'port': self.port,
1020 'secure': bool(self.certfile),
1024 'secure': bool(self.certfile),
1021 'base_url': self.base_url,
1025 'base_url': self.base_url,
1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1026 'notebook_dir': os.path.abspath(self.notebook_dir),
1023 'pid': os.getpid()
1027 'pid': os.getpid()
1024 }
1028 }
1025
1029
1026 def write_server_info_file(self):
1030 def write_server_info_file(self):
1027 """Write the result of server_info() to the JSON file info_file."""
1031 """Write the result of server_info() to the JSON file info_file."""
1028 with open(self.info_file, 'w') as f:
1032 with open(self.info_file, 'w') as f:
1029 json.dump(self.server_info(), f, indent=2)
1033 json.dump(self.server_info(), f, indent=2)
1030
1034
1031 def remove_server_info_file(self):
1035 def remove_server_info_file(self):
1032 """Remove the nbserver-<pid>.json file created for this server.
1036 """Remove the nbserver-<pid>.json file created for this server.
1033
1037
1034 Ignores the error raised when the file has already been removed.
1038 Ignores the error raised when the file has already been removed.
1035 """
1039 """
1036 try:
1040 try:
1037 os.unlink(self.info_file)
1041 os.unlink(self.info_file)
1038 except OSError as e:
1042 except OSError as e:
1039 if e.errno != errno.ENOENT:
1043 if e.errno != errno.ENOENT:
1040 raise
1044 raise
1041
1045
1042 def start(self):
1046 def start(self):
1043 """ Start the IPython Notebook server app, after initialization
1047 """ Start the IPython Notebook server app, after initialization
1044
1048
1045 This method takes no arguments so all configuration and initialization
1049 This method takes no arguments so all configuration and initialization
1046 must be done prior to calling this method."""
1050 must be done prior to calling this method."""
1047 if self.subapp is not None:
1051 if self.subapp is not None:
1048 return self.subapp.start()
1052 return self.subapp.start()
1049
1053
1050 info = self.log.info
1054 info = self.log.info
1051 for line in self.notebook_info().split("\n"):
1055 for line in self.notebook_info().split("\n"):
1052 info(line)
1056 info(line)
1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1057 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1054
1058
1055 self.write_server_info_file()
1059 self.write_server_info_file()
1056
1060
1057 if self.open_browser or self.file_to_run:
1061 if self.open_browser or self.file_to_run:
1058 try:
1062 try:
1059 browser = webbrowser.get(self.browser or None)
1063 browser = webbrowser.get(self.browser or None)
1060 except webbrowser.Error as e:
1064 except webbrowser.Error as e:
1061 self.log.warn('No web browser found: %s.' % e)
1065 self.log.warn('No web browser found: %s.' % e)
1062 browser = None
1066 browser = None
1063
1067
1064 if self.file_to_run:
1068 if self.file_to_run:
1065 if not os.path.exists(self.file_to_run):
1069 if not os.path.exists(self.file_to_run):
1066 self.log.critical("%s does not exist" % self.file_to_run)
1070 self.log.critical("%s does not exist" % self.file_to_run)
1067 self.exit(1)
1071 self.exit(1)
1068
1072
1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1073 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1074 uri = url_path_join('notebooks', *relpath.split(os.sep))
1071 else:
1075 else:
1072 uri = 'tree'
1076 uri = 'tree'
1073 if browser:
1077 if browser:
1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1078 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1075 new=2)
1079 new=2)
1076 threading.Thread(target=b).start()
1080 threading.Thread(target=b).start()
1077
1081
1078 self.io_loop = ioloop.IOLoop.current()
1082 self.io_loop = ioloop.IOLoop.current()
1079 if sys.platform.startswith('win'):
1083 if sys.platform.startswith('win'):
1080 # add no-op to wake every 5s
1084 # add no-op to wake every 5s
1081 # to handle signals that may be ignored by the inner loop
1085 # to handle signals that may be ignored by the inner loop
1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1086 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1083 pc.start()
1087 pc.start()
1084 try:
1088 try:
1085 self.io_loop.start()
1089 self.io_loop.start()
1086 except KeyboardInterrupt:
1090 except KeyboardInterrupt:
1087 info("Interrupted...")
1091 info("Interrupted...")
1088 finally:
1092 finally:
1089 self.cleanup_kernels()
1093 self.cleanup_kernels()
1090 self.remove_server_info_file()
1094 self.remove_server_info_file()
1091
1095
1092 def stop(self):
1096 def stop(self):
1093 def _stop():
1097 def _stop():
1094 self.http_server.stop()
1098 self.http_server.stop()
1095 self.io_loop.stop()
1099 self.io_loop.stop()
1096 self.io_loop.add_callback(_stop)
1100 self.io_loop.add_callback(_stop)
1097
1101
1098
1102
1099 def list_running_servers(profile='default'):
1103 def list_running_servers(profile='default'):
1100 """Iterate over the server info files of running notebook servers.
1104 """Iterate over the server info files of running notebook servers.
1101
1105
1102 Given a profile name, find nbserver-* files in the security directory of
1106 Given a profile name, find nbserver-* files in the security directory of
1103 that profile, and yield dicts of their information, each one pertaining to
1107 that profile, and yield dicts of their information, each one pertaining to
1104 a currently running notebook server instance.
1108 a currently running notebook server instance.
1105 """
1109 """
1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1110 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1107 for file in os.listdir(pd.security_dir):
1111 for file in os.listdir(pd.security_dir):
1108 if file.startswith('nbserver-'):
1112 if file.startswith('nbserver-'):
1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1113 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1110 info = json.load(f)
1114 info = json.load(f)
1111
1115
1112 # Simple check whether that process is really still running
1116 # Simple check whether that process is really still running
1113 # Also remove leftover files from IPython 2.x without a pid field
1117 # Also remove leftover files from IPython 2.x without a pid field
1114 if ('pid' in info) and check_pid(info['pid']):
1118 if ('pid' in info) and check_pid(info['pid']):
1115 yield info
1119 yield info
1116 else:
1120 else:
1117 # If the process has died, try to delete its info file
1121 # If the process has died, try to delete its info file
1118 try:
1122 try:
1119 os.unlink(file)
1123 os.unlink(file)
1120 except OSError:
1124 except OSError:
1121 pass # TODO: This should warn or log or something
1125 pass # TODO: This should warn or log or something
1122 #-----------------------------------------------------------------------------
1126 #-----------------------------------------------------------------------------
1123 # Main entry point
1127 # Main entry point
1124 #-----------------------------------------------------------------------------
1128 #-----------------------------------------------------------------------------
1125
1129
1126 launch_new_instance = NotebookApp.launch_instance
1130 launch_new_instance = NotebookApp.launch_instance
1127
1131
General Comments 0
You need to be logged in to leave comments. Login now