示例#1
0
文件: app.py 项目: roadsync/falcon
    def add_sink(self, sink, prefix=r'/'):
        if not iscoroutinefunction(sink) and is_python_func(sink):
            if _should_wrap_non_coroutines():
                sink = wrap_sync_to_async(sink)
            else:
                raise CompatibilityError(
                    'The sink method must be an awaitable coroutine function '
                    'in order to be used safely with an ASGI app.')

        super().add_sink(sink, prefix=prefix)
示例#2
0
文件: app.py 项目: wsz/falcon
    def add_sink(self, sink, prefix=r'/'):
        """Register a sink method for the App.

        If no route matches a request, but the path in the requested URI
        matches a sink prefix, Falcon will pass control to the
        associated sink, regardless of the HTTP method requested.

        Using sinks, you can drain and dynamically handle a large number
        of routes, when creating static resources and responders would be
        impractical. For example, you might use a sink to create a smart
        proxy that forwards requests to one or more backend services.

        Args:
            sink (callable): A callable taking the form ``func(req, resp, **kwargs)``.

                Note:
                    When using an async version of the ``App``, this must be a
                    coroutine.

            prefix (str): A regex string, typically starting with '/', which
                will trigger the sink if it matches the path portion of the
                request's URI. Both strings and precompiled regex objects
                may be specified. Characters are matched starting at the
                beginning of the URI path.

                Note:
                    Named groups are converted to kwargs and passed to
                    the sink as such.

                Warning:
                    If the prefix overlaps a registered route template,
                    the route will take precedence and mask the sink.

                    (See also: :meth:`~.add_route`)

        """

        if not self._ASGI and iscoroutinefunction(sink):
            raise CompatibilityError(
                'The sink method must be a regular synchronous function '
                'in order to be used with a WSGI app.'
            )

        if not hasattr(prefix, 'match'):
            # Assume it is a string
            prefix = re.compile(prefix)

        # NOTE(kgriffs): Insert at the head of the list such that
        # in the case of a duplicate prefix, the last one added
        # is preferred.
        self._sinks.insert(0, (prefix, sink, True))
        self._update_sink_and_static_routes()
示例#3
0
def prepare_middleware_ws(middleware):
    """Check middleware interfaces and prepare WebSocket methods for request handling.

    Note:
        This method is only applicable to ASGI apps.

    Arguments:
        middleware (iterable): An iterable of middleware objects.

    Returns:
        tuple: A two-item ``(request_mw, resource_mw)`` tuple, where
        *request_mw* is an ordered list of ``process_request_ws()`` methods,
        and *resource_mw* is an ordered list of ``process_resource_ws()``
        methods.
    """

    # PERF(kgriffs): do getattr calls once, in advance, so we don't
    # have to do them every time in the request path.
    request_mw = []
    resource_mw = []

    for component in middleware:
        process_request_ws = util.get_bound_method(component,
                                                   'process_request_ws')
        process_resource_ws = util.get_bound_method(component,
                                                    'process_resource_ws')

        for m in (process_request_ws, process_resource_ws):
            if not m:
                continue

            # NOTE(kgriffs): iscoroutinefunction() always returns False
            #   for cythonized functions.
            #
            #   https://github.com/cython/cython/issues/2273
            #   https://bugs.python.org/issue38225
            #
            if not iscoroutinefunction(m) and util.is_python_func(m):
                msg = '{} must be implemented as an awaitable coroutine.'
                raise CompatibilityError(msg.format(m))

        if process_request_ws:
            request_mw.append(process_request_ws)

        if process_resource_ws:
            resource_mw.append(process_resource_ws)

    return request_mw, resource_mw
示例#4
0
    async def __aenter__(self):
        if not _is_asgi_app(self.app):
            raise CompatibilityError(
                "a conductor context manager may only be used with a Falcon ASGI app"
            )

        # NOTE(kgriffs): We normally do not expect someone to try to nest
        #   contexts, so this is just a sanity-check.
        assert not self._conductor

        # Let us custom the conductor
        self._conductor = self._conductor_class(self.app,
                                                headers=self._default_headers)
        await self._conductor.__aenter__()

        return self._conductor
示例#5
0
文件: app.py 项目: nZac/falcon
    def add_error_handler(self, exception, handler=None):
        if not handler:
            try:
                handler = exception.handle
            except AttributeError:
                # NOTE(kgriffs): Delegate to the parent method for error handling.
                pass

        handler = _wrap_non_coroutine_unsafe(handler)

        if handler and not iscoroutinefunction(handler):
            raise CompatibilityError(
                'The handler must be an awaitable coroutine function in order '
                'to be used safely with an ASGI app.')

        super().add_error_handler(exception, handler=handler)
示例#6
0
    def add_error_handler(self, exception, handler=None):
        """Register a handler for one or more exception types.

        Error handlers may be registered for any exception type, including
        :class:`~.HTTPError` or :class:`~.HTTPStatus`. This feature
        provides a central location for logging and otherwise handling
        exceptions raised by responders, hooks, and middleware components.

        A handler can raise an instance of :class:`~.HTTPError` or
        :class:`~.HTTPStatus` to communicate information about the issue to
        the client.  Alternatively, a handler may modify `resp`
        directly.

        An error handler "matches" a raised exception if the exception is an
        instance of the corresponding exception type. If more than one error
        handler matches the raised exception, the framework will choose the
        most specific one, as determined by the method resolution order of the
        raised exception type. If multiple error handlers are registered for the
        *same* exception class, then the most recently-registered handler is
        used.

        For example, suppose we register error handlers as follows::

            app = App()
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_not_found)
            app.add_error_handler(falcon.HTTPError, custom_handle_http_error)
            app.add_error_handler(Exception, custom_handle_uncaught_exception)
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_404)

        If an instance of ``falcon.HTTPForbidden`` is raised, it will be
        handled by ``custom_handle_http_error()``. ``falcon.HTTPError`` is a
        superclass of ``falcon.HTTPForbidden`` and a subclass of ``Exception``,
        so it is the most specific exception type with a registered handler.

        If an instance of ``falcon.HTTPNotFound`` is raised, it will be handled
        by ``custom_handle_404()``, not by ``custom_handle_not_found()``, because
        ``custom_handle_404()`` was registered more recently.

        .. Note::

            By default, the framework installs three handlers, one for
            :class:`~.HTTPError`, one for :class:`~.HTTPStatus`, and one for
            the standard ``Exception`` type, which prevents passing uncaught
            exceptions to the WSGI server. These can be overridden by adding a
            custom error handler method for the exception type in question.

        Args:
            exception (type or iterable of types): When handling a request,
                whenever an error occurs that is an instance of the specified
                type(s), the associated handler will be called. Either a single
                type or an iterable of types may be specified.
            handler (callable): A coroutine function taking the form
                ``async func(req, resp, ex, params)``.

                If not specified explicitly, the handler will default to
                ``exception.handle``, where ``exception`` is the error
                type specified above, and ``handle`` is a static method
                (i.e., decorated with ``@staticmethod``) that accepts
                the same params just described. For example::

                    class CustomException(CustomBaseException):

                        @staticmethod
                        async def handle(req, resp, ex, params):
                            # TODO: Log the error
                            # Convert to an instance of falcon.HTTPError
                            raise falcon.HTTPError(falcon.HTTP_792)

                If an iterable of exception types is specified instead of
                a single type, the handler must be explicitly specified.
        """

        if handler is None:
            try:
                handler = exception.handle
            except AttributeError:
                raise AttributeError('handler must either be specified '
                                     'explicitly or defined as a static'
                                     'method named "handle" that is a '
                                     'member of the given exception class.')

        handler = _wrap_non_coroutine_unsafe(handler)

        # NOTE(kgriffs): iscoroutinefunction() always returns False
        #   for cythonized functions.
        #
        #   https://github.com/cython/cython/issues/2273
        #   https://bugs.python.org/issue38225
        #
        if not iscoroutinefunction(handler) and is_python_func(handler):
            raise CompatibilityError(
                'The handler must be an awaitable coroutine function in order '
                'to be used safely with an ASGI app.')

        try:
            exception_tuple = tuple(exception)
        except TypeError:
            exception_tuple = (exception, )

        for exc in exception_tuple:
            if not issubclass(exc, BaseException):
                raise TypeError('"exception" must be an exception type.')

            self._error_handlers[exc] = handler
示例#7
0
文件: app.py 项目: roadsync/falcon
    def add_error_handler(self, exception, handler=None):
        """Register a handler for one or more exception types.

        Error handlers may be registered for any exception type, including
        :class:`~.HTTPError` or :class:`~.HTTPStatus`. This feature
        provides a central location for logging and otherwise handling
        exceptions raised by responders, hooks, and middleware components.

        A handler can raise an instance of :class:`~.HTTPError` or
        :class:`~.HTTPStatus` to communicate information about the issue to
        the client.  Alternatively, a handler may modify `resp`
        directly.

        An error handler "matches" a raised exception if the exception is an
        instance of the corresponding exception type. If more than one error
        handler matches the raised exception, the framework will choose the
        most specific one, as determined by the method resolution order of the
        raised exception type. If multiple error handlers are registered for the
        *same* exception class, then the most recently-registered handler is
        used.

        For example, suppose we register error handlers as follows::

            app = App()
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_not_found)
            app.add_error_handler(falcon.HTTPError, custom_handle_http_error)
            app.add_error_handler(Exception, custom_handle_uncaught_exception)
            app.add_error_handler(falcon.HTTPNotFound, custom_handle_404)

        If an instance of ``falcon.HTTPForbidden`` is raised, it will be
        handled by ``custom_handle_http_error()``. ``falcon.HTTPError`` is a
        superclass of ``falcon.HTTPForbidden`` and a subclass of ``Exception``,
        so it is the most specific exception type with a registered handler.

        If an instance of ``falcon.HTTPNotFound`` is raised, it will be handled
        by ``custom_handle_404()``, not by ``custom_handle_not_found()``, because
        ``custom_handle_404()`` was registered more recently.

        Note:

            By default, the framework installs three handlers, one for
            :class:`~.HTTPError`, one for :class:`~.HTTPStatus`, and one for
            the standard ``Exception`` type, which prevents passing uncaught
            exceptions to the WSGI server. These can be overridden by adding a
            custom error handler method for the exception type in question.

            When a generic unhandled exception is raised while
            handling a :ref:`WebSocket <ws>` connection, the default handler will
            close the connection with the standard close code ``1011`` (Internal
            Error). If your ASGI server does not support this code, the
            framework will use code ``3011`` instead; or you can customize
            it via the
            :attr:`~falcon.asgi.WebSocketOptions.error_close_code`
            property of :attr:`~.ws_options`.

            On the other hand, if an ``on_websocket()`` responder raises an
            instance of :class:`~falcon.HTTPError`, the default error handler
            will close the :ref:`WebSocket <ws>` connection with a framework
            close code derived by adding ``3000`` to the HTTP status code (e.g.,
            ``3404``)

        Args:
            exception (type or iterable of types): When handling a request,
                whenever an error occurs that is an instance of the specified
                type(s), the associated handler will be called. Either a single
                type or an iterable of types may be specified.

        Keyword Args:
            handler (callable): A coroutine function taking the
                form::

                    async def func(req, resp, ex, params, ws=None):
                        pass

                In the case of a WebSocket connection, the `resp` argument
                will be ``None``, while the `ws` keyword argument
                will receive the :class:`~falcon.asgi.WebSocket` object
                representing the connection.

                If the `handler` keyword argument is not provided to
                :meth:`~.add_error_handler`, the handler will default to
                ``exception.handle``, where ``exception`` is the error type
                specified above, and ``handle`` is a static method (i.e.,
                decorated with ``@staticmethod``) that accepts the params
                just described. For example::

                    class CustomException(CustomBaseException):

                        @staticmethod
                        async def handle(req, resp, ex, params):
                            # TODO: Log the error
                            # Convert to an instance of falcon.HTTPError
                            raise falcon.HTTPError(falcon.HTTP_792)

                Note, however, that if an iterable of exception types is
                specified instead of a single type, the handler must be
                explicitly specified using the `handler` keyword argument.
        """

        if handler is None:
            try:
                handler = exception.handle
            except AttributeError:
                raise AttributeError('handler must either be specified '
                                     'explicitly or defined as a static'
                                     'method named "handle" that is a '
                                     'member of the given exception class.')

        # NOTE(vytas): Do not shoot ourselves in the foot in case error
        #   handlers are our own cythonized code.
        if handler not in (
                self._http_status_handler,
                self._http_error_handler,
                self._python_error_handler,
        ):
            handler = _wrap_non_coroutine_unsafe(handler)

        # NOTE(kgriffs): iscoroutinefunction() always returns False
        #   for cythonized functions.
        #
        #   https://github.com/cython/cython/issues/2273
        #   https://bugs.python.org/issue38225
        #
        if not iscoroutinefunction(handler) and is_python_func(handler):
            raise CompatibilityError(
                'The handler must be an awaitable coroutine function in order '
                'to be used safely with an ASGI app.')

        try:
            exception_tuple = tuple(exception)
        except TypeError:
            exception_tuple = (exception, )

        for exc in exception_tuple:
            if not issubclass(exc, BaseException):
                raise TypeError('"exception" must be an exception type.')

            self._error_handlers[exc] = handler
示例#8
0
def prepare_middleware(middleware, independent_middleware=False, asgi=False):
    """Check middleware interfaces and prepare the methods for request handling.

    Arguments:
        middleware (iterable): An iterable of middleware objects.

    Keyword Args:
        independent_middleware (bool): ``True`` if the request and
            response middleware methods should be treated independently
            (default ``False``)
        asgi (bool): ``True`` if an ASGI app, ``False`` otherwise
            (default ``False``)

    Returns:
        tuple: A tuple of prepared middleware method tuples
    """

    # PERF(kgriffs): do getattr calls once, in advance, so we don't
    # have to do them every time in the request path.
    request_mw = []
    resource_mw = []
    response_mw = []

    for component in middleware:
        # NOTE(kgriffs): Middleware that uses parts of the Request and Response
        #   interfaces that are the same between ASGI and WSGI (most of it is,
        #   and we should probably define this via ABC) can just implement
        #   the method names without the *_async postfix. If a middleware
        #   component wants to provide an alternative implementation that
        #   does some work that requires async def, or something specific about
        #   the ASGI Request/Response classes, the component can implement the
        #   *_async method in that case.
        #
        #   Middleware that is WSGI-only or ASGI-only can simply implement all
        #   methods without the *_async postfix. Regardless, components should
        #   clearly document their compatibility with WSGI vs. ASGI.

        if asgi:
            process_request = (
                util.get_bound_method(component, 'process_request_async') or
                _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_request')
                )
            )

            process_resource = (
                util.get_bound_method(component, 'process_resource_async') or
                _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_resource')
                )
            )

            process_response = (
                util.get_bound_method(component, 'process_response_async') or
                _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_response')
                )
            )

            for m in (process_request, process_resource, process_response):
                if m and not iscoroutinefunction(m):
                    msg = (
                        '{} must be implemented as an awaitable coroutine. If '
                        'you would like to retain compatibility '
                        'with WSGI apps, the coroutine versions of the '
                        'middleware methods may be implemented side-by-side '
                        'by applying an *_async postfix to the method names. '
                    )
                    raise CompatibilityError(msg.format(m))

        else:
            process_request = util.get_bound_method(component, 'process_request')
            process_resource = util.get_bound_method(component, 'process_resource')
            process_response = util.get_bound_method(component, 'process_response')

            for m in (process_request, process_resource, process_response):
                if m and iscoroutinefunction(m):
                    msg = (
                        '{} may not implement coroutine methods and '
                        'remain compatible with WSGI apps without '
                        'using the *_async postfix to explicitly identify '
                        'the coroutine version of a given middleware '
                        'method.'
                    )
                    raise CompatibilityError(msg.format(component))

        if not (process_request or process_resource or process_response):
            if asgi and (
                hasattr(component, 'process_startup') or hasattr(component, 'process_shutdown')
            ):
                # NOTE(kgriffs): This middleware only has ASGI lifespan
                #   event handlers
                continue

            msg = '{0} must implement at least one middleware method'
            raise TypeError(msg.format(component))

        # NOTE: depending on whether we want to execute middleware
        # independently, we group response and request middleware either
        # together or separately.
        if independent_middleware:
            if process_request:
                request_mw.append(process_request)
            if process_response:
                response_mw.insert(0, process_response)
        else:
            if process_request or process_response:
                request_mw.append((process_request, process_response))

        if process_resource:
            resource_mw.append(process_resource)

    return (tuple(request_mw), tuple(resource_mw), tuple(response_mw))
示例#9
0
def prepare_middleware(middleware, independent_middleware=False, asgi=False):
    """Check middleware interfaces and prepare the methods for request handling.

    Arguments:
        middleware (iterable): An iterable of middleware objects.

    Keyword Args:
        independent_middleware (bool): ``True`` if the request and
            response middleware methods should be treated independently
            (default ``False``)
        asgi (bool): ``True`` if an ASGI app, ``False`` otherwise
            (default ``False``)

    Returns:
        tuple: A tuple of prepared middleware method tuples
    """

    # PERF(kgriffs): do getattr calls once, in advance, so we don't
    # have to do them every time in the request path.
    request_mw = []
    resource_mw = []
    response_mw = []

    for component in middleware:
        # NOTE(kgriffs): Middleware that supports both WSGI and ASGI can
        #   append an *_async postfix to the ASGI version of the method
        #   to distinguish the two. Otherwise, the prefix is unnecessary.

        if asgi:
            process_request = (
                util.get_bound_method(component, 'process_request_async')
                or _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_request')))

            process_resource = (
                util.get_bound_method(component, 'process_resource_async')
                or _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_resource')))

            process_response = (
                util.get_bound_method(component, 'process_response_async')
                or _wrap_non_coroutine_unsafe(
                    util.get_bound_method(component, 'process_response')))

            for m in (process_request, process_resource, process_response):
                # NOTE(kgriffs): iscoroutinefunction() always returns False
                #   for cythonized functions.
                #
                #   https://github.com/cython/cython/issues/2273
                #   https://bugs.python.org/issue38225
                #
                if m and not iscoroutinefunction(m) and util.is_python_func(m):
                    msg = (
                        '{} must be implemented as an awaitable coroutine. If '
                        'you would like to retain compatibility '
                        'with WSGI apps, the coroutine versions of the '
                        'middleware methods may be implemented side-by-side '
                        'by applying an *_async postfix to the method names. ')
                    raise CompatibilityError(msg.format(m))

        else:
            process_request = util.get_bound_method(component,
                                                    'process_request')
            process_resource = util.get_bound_method(component,
                                                     'process_resource')
            process_response = util.get_bound_method(component,
                                                     'process_response')

            for m in (process_request, process_resource, process_response):
                if m and iscoroutinefunction(m):
                    msg = ('{} may not implement coroutine methods and '
                           'remain compatible with WSGI apps without '
                           'using the *_async postfix to explicitly identify '
                           'the coroutine version of a given middleware '
                           'method.')
                    raise CompatibilityError(msg.format(component))

        if not (process_request or process_resource or process_response):
            if asgi and (hasattr(component, 'process_startup')
                         or hasattr(component, 'process_shutdown')):
                # NOTE(kgriffs): This middleware only has ASGI lifespan
                #   event handlers
                continue

            msg = '{0} must implement at least one middleware method'
            raise TypeError(msg.format(component))

        # NOTE: depending on whether we want to execute middleware
        # independently, we group response and request middleware either
        # together or separately.
        if independent_middleware:
            if process_request:
                request_mw.append(process_request)
            if process_response:
                response_mw.insert(0, process_response)
        else:
            if process_request or process_response:
                request_mw.append((process_request, process_response))

        if process_resource:
            resource_mw.append(process_resource)

    return (tuple(request_mw), tuple(resource_mw), tuple(response_mw))