在规则映射到端点之前如何在 Flask 中处理 URL?

How to process URLs in Flask before Rules are mapped to endpoints?

我正在尝试在 API 中实现一项功能,其中 URL 的一部分是可选的。如果提供,我想处理它并在 g 中粘贴一些数据。如果没有,我会在 g 中放置一些默认信息。无论哪种方式,我都会在规则映射到端点之前将其从 URL 中删除。所以我希望以下两个 URLs 最终调用相同的端点:

/bar/1   (I would fill in a default value for foo here)
/foo/32/bar/1

我想要在我拥有的每个端点中使用相同的 URL 可选部分。我想我可以通过装饰每个端点来蛮力做到这一点,但我有超过 250 个端点,所以我想要更优雅的东西。

我正在使用多个蓝图,如果可能的话,我想让每个端点尽可能简单(蓝图已经有自己的前缀):

@blueprint1.route('/bar/<int:id>', methods=['GET'])
@blueprint2.route('/bar/<int:id>', methods=['GET'])
def get_foo():

我已经尝试了@url_defaults、@url_value_preprocessor 和@before_request 装饰器,但似乎规则已经映射到端点。在映射完成之前是否有访问 URL 的挂钩?

通常在应用程序初始化期间,URL 仅映射到端点一次。更准确地说 - 每次解释器第一次遇到代码 @app.route('/some_route') ... 时。重要的是要了解每个请求都不会将 URL 映射到端点(例如 PHP)。

向端点添加一些默认值的一种方法是覆盖

Flask.route()
Flask.add_url_rule()
Blueprint.route()

您的继任者 app/blueprint class。只需将其放入 **options dict.

我通过子classing Flask class 并覆盖 create_url_adapter() 函数来完成这项工作,如下所示:

class MyFlask(Flask):
    """
    MyFlask subclasses the standard Flask app class so that I can hook the URL parsing before the URL
    is mapped to a Route. We do this so we can extract an optional "/foo/<int:foo_id>" prefix. If we
    see this prefix, we store the int value for later processing and delete the prefix from the URL before
    Flask maps it to a route.
    """

def _extract_optional_foo_id(self, path):
    ....
    return path, foo_id  # path has the optional part removed

def create_url_adapter(self, request):
        """
        Augment the base class's implementation of create_url_adapter() by extracting an optional foo_id and modifying
        the URL. The Flask function name is misleading: we aren't creating anything like an object. The "adapter" is
        just this function call. We add our own behavior then call the super class's version of this function.
        :param request: Flask's Request object
        :return: the results of the Flask super class's create_url_adapter()
        """
        # Must test for request. It is None when calling this for the app-wide scenario (instead of request-wide).
        if request and request.environ:
            (new_path, foo_id) = self._extract_optional_foo_id(request.environ['PATH_INFO'])
            if foo_id is not None:
                request.environ['PATH_INFO'] = new_path
                request.foo_id_in_url = foo_id

        return super(MyFlask, self).create_url_adapter(request)

然后在我的应用初始化代码中,我没有实例化 Flask 的实例,而是:

app = MyFlask( ... )