Skip to content

Commit 618b99e

Browse files
committed
register contents_manager.files_handler_class directly
rather than trying to call one handler from another, which is unreliable and can cause misbehavior.
1 parent 2ee51ab commit 618b99e

File tree

4 files changed

+55
-16
lines changed

4 files changed

+55
-16
lines changed

notebook/files/handlers.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@
1212
from base64 import decodestring as decodebytes
1313

1414

15-
from tornado import web, escape
15+
from tornado import web
1616

1717
from notebook.base.handlers import IPythonHandler
1818

19+
1920
class FilesHandler(IPythonHandler):
20-
"""serve files via ContentsManager"""
21+
"""serve files via ContentsManager
22+
23+
Normally used when ContentsManager is not a FileContentsManager.
24+
25+
FileContentsManager subclasses use AuthenticatedFilesManager by default,
26+
a subclass of StaticFileManager.
27+
"""
2128

2229
@web.authenticated
2330
def head(self, path):
@@ -27,16 +34,10 @@ def head(self, path):
2734
def get(self, path, include_body=True):
2835
cm = self.contents_manager
2936

30-
if cm.files_handler_class:
31-
return cm.files_handler_class(self.application, self.request, path=cm.root_dir)._execute(
32-
[t(self.request) for t in self.application.transforms],
33-
path
34-
)
35-
3637
if cm.is_hidden(path):
3738
self.log.info("Refusing to serve hidden file, via 404 Error")
3839
raise web.HTTPError(404)
39-
40+
4041
path = path.strip('/')
4142
if '/' in path:
4243
_, name = path.rsplit('/', 1)
@@ -73,6 +74,5 @@ def get(self, path, include_body=True):
7374
self.write(model['content'])
7475
self.flush()
7576

76-
default_handlers = [
77-
(r"/files/(.*)", FilesHandler),
78-
]
77+
78+
default_handlers = []

notebook/notebookapp.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager,
277277

278278
def init_handlers(self, settings):
279279
"""Load the (URL pattern, handler) tuples for each component."""
280-
280+
281281
# Order matters. The first handler to match the URL will handle the request.
282282
handlers = []
283283
handlers.extend(load_handlers('tree.handlers'))
@@ -299,7 +299,8 @@ def init_handlers(self, settings):
299299
handlers.extend(load_handlers('services.kernelspecs.handlers'))
300300
handlers.extend(load_handlers('services.security.handlers'))
301301
handlers.extend(load_handlers('services.shutdown'))
302-
302+
handlers.extend(settings['contents_manager'].get_extra_handlers())
303+
303304
handlers.append(
304305
(r"/nbextensions/(.*)", FileFindHandler, {
305306
'path': settings['nbextensions_path'],

notebook/services/contents/filemanager.py

+4
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ def _checkpoints_class_default(self):
153153
def _files_handler_class_default(self):
154154
return AuthenticatedFileHandler
155155

156+
@default('files_handler_params')
157+
def _files_handler_params_default(self):
158+
return {'path': self.root_dir}
159+
156160
def is_hidden(self, path):
157161
"""Does the API style path correspond to a hidden directory or file?
158162

notebook/services/contents/manager.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import os
1111
import re
1212

13-
from tornado.web import HTTPError
13+
from tornado.web import HTTPError, RequestHandler
1414

15+
from ...files.handlers import FilesHandler
1516
from .checkpoints import Checkpoints
1617
from traitlets.config.configurable import LoggingConfigurable
1718
from nbformat import sign, validate as validate_nb, ValidationError
@@ -131,7 +132,40 @@ def _default_checkpoints_kwargs(self):
131132
log=self.log,
132133
)
133134

134-
files_handler_class = Type(IPythonHandler, allow_none=True, config=True)
135+
files_handler_class = Type(
136+
FilesHandler, klass=RequestHandler, allow_none=True, config=True,
137+
help="""handler class to use when serving raw file requests.
138+
139+
Default is a fallback that talks to the ContentsManager API,
140+
which may be inefficient, especially for large files.
141+
142+
Local files-based ContentsManagers can use a StaticFileHandler subclass,
143+
which will be much more efficient.
144+
145+
Access to these files should be Authenticated.
146+
"""
147+
)
148+
149+
files_handler_params = Dict(
150+
config=True,
151+
help="""Extra parameters to pass to files_handler_class.
152+
153+
For example, StaticFileHandlers generally expect a `path` argument
154+
specifying the root directory from which to serve files.
155+
"""
156+
)
157+
158+
def get_extra_handlers(self):
159+
"""Return additional handlers
160+
161+
Default: self.files_handler_class on /files/.*
162+
"""
163+
handlers = []
164+
if self.files_handler_class:
165+
handlers.append(
166+
(r"/files/(.*)", self.files_handler_class, self.files_handler_params)
167+
)
168+
return handlers
135169

136170
# ContentsManager API part 1: methods that must be
137171
# implemented in subclasses.

0 commit comments

Comments
 (0)