From 2c9b9731786bf5516a24dc016510e4d78cdf3550 Mon Sep 17 00:00:00 2001 From: James Graham Date: Tue, 14 Mar 2017 18:59:20 +0000 Subject: [PATCH] Support handlers altering the set of files that appear in a directory listing. In cases like .any.js files for wpt we create some virtual resources for on-disk files. It would be nice if they appeared in directory listings. To allow that to happen, we add a mechanism so that when a handler is registered with the router we can also register a corresponding listing_fixup function that takes the request and the list of files in the directory and returns an amended list of files. --- wptserve/handlers.py | 38 +++++++++++++++++++++----------------- wptserve/request.py | 2 ++ wptserve/router.py | 32 +++++++++++++++++++++----------- wptserve/server.py | 3 ++- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/wptserve/handlers.py b/wptserve/handlers.py index f0ce3c0..1d9b981 100644 --- a/wptserve/handlers.py +++ b/wptserve/handlers.py @@ -73,30 +73,34 @@ def __call__(self, request, response): %(items)s """ % {"path": cgi.escape(url_path), - "items": "\n".join(self.list_items(url_path, path))} # flake8: noqa + "items": "\n".join(self.list_items(request, url_path, path))} # flake8: noqa - def list_items(self, base_path, path): + def list_items(self, request, base_path, path): assert base_path.endswith("/") - # TODO: this won't actually list all routes, only the - # ones that correspond to a real filesystem path. It's - # not possible to list every route that will match - # something, but it should be possible to at least list the - # statically defined ones - if base_path != "/": link = urljoin(base_path, "..") yield ("""
  • %(name)s
  • """ % {"link": link, "name": ".."}) - for item in sorted(os.listdir(path)): - link = cgi.escape(quote(item)) - if os.path.isdir(os.path.join(path, item)): - link += "/" - class_ = "dir" - else: - class_ = "file" - yield ("""
  • %(name)s
  • """ % - {"link": link, "name": cgi.escape(item), "class": class_}) + + dir_items = set((item, os.path.isdir(os.path.join(path, item))) for item in os.listdir(path)) + dirs = list(name for name, is_dir in dir_items if is_dir) + files = set(name for name, is_dir in dir_items if not is_dir) + + for fn in reversed(request.router.listing_fixups): + files = fn(request, files) + files = list(files) + + dirs.sort() + files.sort() + + template = """
  • %(name)s
  • """ + + for class_, item_list in [("dir", dirs), + ("file", files)]: + for item in item_list: + link = cgi.escape(quote(item)) + yield template % {"link": link, "name": cgi.escape(item), "class": class_} def wrap_pipeline(path, request, response): diff --git a/wptserve/request.py b/wptserve/request.py index 4476634..6679ad7 100644 --- a/wptserve/request.py +++ b/wptserve/request.py @@ -237,9 +237,11 @@ class Request(object): .. attribute:: server Server object containing information about the server environment. + """ def __init__(self, request_handler): + self.router = request_handler.server.router self.doc_root = request_handler.server.router.doc_root self.route_match = None # Set by the router diff --git a/wptserve/router.py b/wptserve/router.py index a35e098..01620db 100644 --- a/wptserve/router.py +++ b/wptserve/router.py @@ -97,11 +97,12 @@ class Router(object): def __init__(self, doc_root, routes): self.doc_root = doc_root self.routes = [] + self.listing_fixups = [] self.logger = get_logger() for route in reversed(routes): self.register(*route) - def register(self, methods, path, handler): + def register(self, methods, path, handler, listing_fixup=None): """Register a handler for a set of paths. :param methods: Set of methods this should match. "*" is a @@ -130,9 +131,18 @@ def register(self, methods, path, handler): {"resource": "test", "*": "data.json"} - :param handler: Function that will be called to process matching - requests. This must take two parameters, the request - object and the response object. + :param handler: Function that will be called to process + matching requests. This must take two + parameters, the request object and the + response object. + + :param listing_fixup: Optional function that takes a request + and a set of names under a particuar + directory and returns an updated set of + names under that directory. Used to fix + up directory listings when a route adds + resources under a path that don't map + directly to a file. """ if type(methods) in types.StringTypes or methods in (any_method, "*"): @@ -140,18 +150,20 @@ def register(self, methods, path, handler): for method in methods: self.routes.append((method, compile_path_match(path), handler)) self.logger.debug("Route pattern: %s" % self.routes[-1][1].pattern) + if listing_fixup is not None: + self.listing_fixups.append(listing_fixup) - def get_handler(self, request): + def get_handler(self, request_method, request_path): """Get a handler for a request or None if there is no handler. :param request: Request to get a handler for. :rtype: Callable or None """ for method, regexp, handler in reversed(self.routes): - if (request.method == method or + if (request_method == method or method in (any_method, "*") or - (request.method == "HEAD" and method == "GET")): - m = regexp.match(request.url_parts.path) + (request_method == "HEAD" and method == "GET")): + m = regexp.match(request_path) if m: if not hasattr(handler, "__class__"): name = handler.__name__ @@ -162,7 +174,5 @@ def get_handler(self, request): match_parts = m.groupdict().copy() if len(match_parts) < len(m.groups()): match_parts["*"] = m.groups()[-1] - request.route_match = match_parts - - return handler + return match_parts, handler return None diff --git a/wptserve/server.py b/wptserve/server.py index 0ab512c..0b48c09 100644 --- a/wptserve/server.py +++ b/wptserve/server.py @@ -230,7 +230,8 @@ def handle_one_request(self): return self.logger.debug("%s %s" % (request.method, request.request_path)) - handler = self.server.router.get_handler(request) + match_parts, handler = self.server.router.get_handler(request.method, request.url_parts.path) + request.match_parts = match_parts # If the handler we used for the request had a non-default base path # set update the doc_root of the request to reflect this