Esempio n. 1
0
File: mole.py Progetto: ruter/Mole
 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)
Esempio n. 2
0
File: mole.py Progetto: ruter/Mole
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)
Esempio n. 3
0
 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))])
Esempio n. 4
0
File: mole.py Progetto: ruter/Mole
 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
Esempio n. 5
0
 def __init__(self, message):
     HTTPError.__init__(self, 500, message)
Esempio n. 6
0
File: mole.py Progetto: ruter/Mole
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))
Esempio n. 7
0
File: mole.py Progetto: ruter/Mole
def abort(code=500, text='Unknown Error: Application stopped.'):
    """ Aborts execution and causes a HTTP error. """
    raise HTTPError(code, text)
Esempio n. 8
0
File: mole.py Progetto: ruter/Mole
                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):
Esempio n. 9
0
 def __init__(self, message):
     HTTPError.__init__(self, 500, message)