def wrapper(*a, **ka): user, password = request.auth or (None, None) if user is None or not check(user, password): response.headers[ 'WWW-Authenticate'] = 'Basic realm="%s"' % realm return HTTPError(401, text) return func(*a, **ka)
def static_file(filename, root, guessmime=True, mimetype=None, download=False): """ Opens a file in a safe way and returns a HTTPError object with status code 200, 305, 401 or 404. Sets Content-Type, Content-Length and Last-Modified header. Obeys If-Modified-Since header and HEAD requests. """ root = os.path.abspath(root) + os.sep filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) header = dict() if not filename.startswith(root): return HTTPError(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): return HTTPError(404, "File does not exist.") if not os.access(filename, os.R_OK): return HTTPError(403, "You do not have permission to access this file.") if not mimetype and guessmime: header['Content-Type'] = mimetypes.guess_type(filename)[0] else: header['Content-Type'] = mimetype if mimetype else 'text/plain' if download == True: download = os.path.basename(filename) if download: header['Content-Disposition'] = 'attachment; filename="%s"' % download stats = os.stat(filename) lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime)) header['Last-Modified'] = lm ims = request.environ.get('HTTP_IF_MODIFIED_SINCE') if ims: ims = ims.split(";")[0].strip() # IE sends "<date>; length=146" ims = parse_date(ims) if ims is not None and ims >= int(stats.st_mtime): header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) return HTTPResponse(status=304, header=header) header['Content-Length'] = stats.st_size if request.method == 'HEAD': return HTTPResponse('', header=header) else: return HTTPResponse(open(filename, 'rb'), header=header)
def match(self, environ): ''' Return a (target, url_agrs) tuple or raise HTTPError(404/405). ''' targets, urlargs = self._match_path(environ) if not targets: raise HTTPError(404, "Not found: " + environ.get('PATH_INFO', '')) environ['router.url_args'] = urlargs method = environ['REQUEST_METHOD'].upper() if method in targets: return targets[method], urlargs if method == 'HEAD' and 'GET' in targets: return targets['GET'], urlargs if 'ANY' in targets: return targets['ANY'], urlargs allowed = [verb for verb in targets if verb != 'ANY'] if 'GET' in allowed and 'HEAD' not in allowed: allowed.append('HEAD') raise HTTPError(405, "Method not allowed.", header=[('Allow', ",".join(allowed))])
def handle(self, environ): """ Execute the handler bound to the specified url and method and return its output. If catchall is true, exceptions are catched and returned as HTTPError(500) objects. """ if not self.serve: return HTTPError(503, "Server stopped") try: handler, args = self.match(environ) return handler(**args) except HTTPResponse, e: return e
def __init__(self, message): HTTPError.__init__(self, 500, message)
class Mole(object): """ WSGI application or Handler """ def __init__(self, catchall=True, autojson=True, config=None): """ Create a new mole instance. You usually don't do that. Use `mole.app.push()` instead. """ self.routes = [] # List of installed routes including metadata. self.callbacks = {} # Cache for wrapped callbacks. self.router = Router() # Maps to self.routes indices. self.mounts = {} self.error_handler = {} self.catchall = catchall self.config = config or {} self.serve = True self.castfilter = [] if autojson and json_dumps: self.add_filter(dict, dict2json) self.hooks = {'before_request': [], 'after_request': []} def optimize(self, *a, **ka): utils.depr("Mole.optimize() is obsolete.") def mount(self, app, script_path): ''' Mount a Mole application to a specific URL prefix ''' if not isinstance(app, Mole): raise TypeError('Only Mole instances are supported for now.') script_path = '/'.join(filter(None, script_path.split('/'))) path_depth = script_path.count('/') + 1 if not script_path: raise TypeError('Empty script_path. Perhaps you want a merge()?') for other in self.mounts: if other.startswith(script_path): raise TypeError('Conflict with existing mount: %s' % other) @self.route('/%s/:#.*#' % script_path, method="ANY") def mountpoint(): request.path_shift(path_depth) return app.handle(request.environ) self.mounts[script_path] = app def add_filter(self, ftype, func): ''' Register a new output filter. Whenever mole hits a handler output matching `ftype`, `func` is applied to it. ''' if not isinstance(ftype, type): raise TypeError("Expected type object, got %s" % type(ftype)) self.castfilter = [(t, f) for (t, f) in self.castfilter if t != ftype] self.castfilter.append((ftype, func)) self.castfilter.sort() def match_url(self, path, method='GET'): return self.match({'PATH_INFO': path, 'REQUEST_METHOD': method}) def match(self, environ): """ Return a (callback, url-args) tuple or raise HTTPError. """ target, args = self.router.match(environ) try: return self.callbacks[target], args except KeyError: callback, decorators = self.routes[target] wrapped = callback for wrapper in decorators[::-1]: wrapped = wrapper(wrapped) #for plugin in self.plugins or []: # wrapped = plugin.apply(wrapped, rule) functools.update_wrapper(wrapped, callback) self.callbacks[target] = wrapped return wrapped, args def get_url(self, routename, **kargs): """ Return a string that matches a named route """ scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/' location = self.router.build(routename, **kargs).lstrip('/') return urljoin(urljoin('/', scriptname), location) def route(self, path=None, method='GET', no_hooks=False, decorate=None, template=None, template_opts={}, callback=None, name=None, static=False): """ Decorator: Bind a callback function to a request path. :param path: The request path or a list of paths to listen to. See :class:`Router` for syntax details. If no path is specified, it is automatically generated from the callback signature. See :func:`yieldroutes` for details. :param method: The HTTP method (POST, GET, ...) or a list of methods to listen to. (default: GET) :param decorate: A decorator or a list of decorators. These are applied to the callback in reverse order (on demand only). :param no_hooks: If true, application hooks are not triggered by this route. (default: False) :param template: The template to use for this callback. (default: no template) :param template_opts: A dict with additional template parameters. :param name: The name for this route. (default: None) :param callback: If set, the route decorator is directly applied to the callback and the callback is returned instead. This equals ``Mole.route(...)(callback)``. """ # @route can be used without any parameters if callable(path): path, callback = None, path # Build up the list of decorators decorators = makelist(decorate) if template: decorators.insert(0, view(template, **template_opts)) if not no_hooks: decorators.append(self._add_hook_wrapper) #decorators.append(partial(self.apply_plugins, skiplist)) def wrapper(func): for rule in makelist(path) or yieldroutes(func): for verb in makelist(method): if static: rule = rule.replace(':', '\\:') utils.depr("Use backslash to escape ':' in routes.") #TODO: Prepare this for plugins self.router.add(rule, verb, len(self.routes), name=name) self.routes.append((func, decorators)) return func return wrapper(callback) if callback else wrapper def _add_hook_wrapper(self, func): ''' Add hooks to a callable. See #84 ''' @functools.wraps(func) def wrapper(*a, **ka): for hook in self.hooks['before_request']: hook() response.output = func(*a, **ka) for hook in self.hooks['after_request']: hook() return response.output return wrapper def get(self, path=None, method='GET', **kargs): """ Decorator: Bind a function to a GET request path. See :meth:'route' for details. """ return self.route(path, method, **kargs) def post(self, path=None, method='POST', **kargs): """ Decorator: Bind a function to a POST request path. See :meth:'route' for details. """ return self.route(path, method, **kargs) def put(self, path=None, method='PUT', **kargs): """ Decorator: Bind a function to a PUT request path. See :meth:'route' for details. """ return self.route(path, method, **kargs) def delete(self, path=None, method='DELETE', **kargs): """ Decorator: Bind a function to a DELETE request path. See :meth:'route' for details. """ return self.route(path, method, **kargs) def error(self, code=500): """ Decorator: Register an output handler for a HTTP error code""" def wrapper(handler): self.error_handler[int(code)] = handler return handler return wrapper def hook(self, name): """ Return a decorator that adds a callback to the specified hook. """ def wrapper(func): self.add_hook(name, func) return func return wrapper def add_hook(self, name, func): ''' Add a callback from a hook. ''' if name not in self.hooks: raise ValueError("Unknown hook name %s" % name) if name in ('after_request'): self.hooks[name].insert(0, func) else: self.hooks[name].append(func) def remove_hook(self, name, func): ''' Remove a callback from a hook. ''' if name not in self.hooks: raise ValueError("Unknown hook name %s" % name) self.hooks[name].remove(func) def handle(self, environ): """ Execute the handler bound to the specified url and method and return its output. If catchall is true, exceptions are catched and returned as HTTPError(500) objects. """ if not self.serve: return HTTPError(503, "Server stopped") try: handler, args = self.match(environ) return handler(**args) except HTTPResponse, e: return e except Exception, e: import traceback traceback.print_exc() if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\ or not self.catchall: raise return HTTPError(500, 'Unhandled exception', e, format_exc(10))
def abort(code=500, text='Unknown Error: Application stopped.'): """ Aborts execution and causes a HTTP error. """ raise HTTPError(code, text)
return request.environ['wsgi.file_wrapper'](out) elif hasattr(out, 'close') or not hasattr(out, '__iter__'): return WSGIFileWrapper(out) # Handle Iterables. We peek into them to detect their inner type. try: out = iter(out) first = out.next() while not first: first = out.next() except StopIteration: return self._cast('', request, response) except HTTPResponse, e: first = e except Exception, e: first = HTTPError(500, 'Unhandled exception', e, format_exc(10)) if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\ or not self.catchall: raise # These are the inner types allowed in iterator or generator objects. if isinstance(first, HTTPResponse): return self._cast(first, request, response) if isinstance(first, bytes): return itertools.chain([first], out) if isinstance(first, unicode): return itertools.imap(lambda x: x.encode(response.charset), itertools.chain([first], out)) return self._cast(HTTPError(500, 'Unsupported response type: %s'\ % type(first)), request, response) def wsgi(self, environ, start_response):