基于 Flask 中的 Accept header 路由请求

Route requests based on the Accept header in Flask

我想根据接受 HTTP header 路由到不同的 Flask 视图,例如:

@api.route('/test', accept='text/html')
def test_html():
    return "<html><body>Test</body></html>"

@api.route('/test', accept='text/json')
def test_json():
    return jsonify(test="Test")

我没有在Werkzeug Rule constructor中找到相关的选项,这是Flask使用的。它是缺少的功能还是有可能以不同的方式实现相同的效果,例如通过在路由之前拦截和修改 URL 路径?

我不想将视图合并为一个视图,因为这会使代码显着复杂化,它们有很多,而且它们位于不同的蓝图中。

我知道 similar question has been asked, but nobody answered it using Flask. It's possible to do it in different web frameworks, for example in Pyramid using predicates - sample code can be found in this answer

我写了一个 decorator 来做到这一点(为后代复制这里)。这只是一个可以进一步改进的粗略想法(例如,当没有与给定 MIME 类型匹配的处理程序时,返回 406 Not Acceptable 响应而不是使用默认处理程序)。更多解释在评论中。

import functools
from flask import Flask, request, jsonify

app = Flask(__name__)

def accept(func_or_mimetype=None):
    """Decorator which allows to use multiple MIME type handlers for a single
    endpoint.
    """

    # Default MIME type.
    mimetype = 'text/html'

    class Accept(object):
        def __init__(self, func):
            self.default_mimetype = mimetype
            self.accept_handlers = {mimetype: func}
            functools.update_wrapper(self, func)

        def __call__(self, *args, **kwargs):
            default = self.default_mimetype
            mimetypes = request.accept_mimetypes
            best = mimetypes.best_match(self.accept_handlers.keys(), default)
            # In case of Accept: */*, choose default handler.
            if best != default and mimetypes[best] == mimetypes[default]:
                best = default
            return self.accept_handlers[best](*args, **kwargs)

        def accept(self, mimetype):
            """Register a MIME type handler."""

            def decorator(func):
                self.accept_handlers[mimetype] = func
                return func
            return decorator

    # If decorator is called without argument list, return Accept instance.
    if callable(func_or_mimetype):
        return Accept(func_or_mimetype)

    # Otherwise set new MIME type (if provided) and let Accept act as a
    # decorator.
    if func_or_mimetype is not None:
        mimetype = func_or_mimetype
    return Accept

@app.route('/')
@accept     # Or: @accept('text/html')
def index():
    return '<strong>foobar</strong>'

@index.accept('application/json')
def index_json():
    return jsonify(foobar=True)

@index.accept('text/plain')
def index_text():
    return 'foobar\n', 200, {'Content-Type': 'text/plain'}

我知道这是一个老问题,但我最终在这里寻找类似的东西,所以我希望它能帮助其他人。

flask_accept具有通过不同路由处理不同Accept类型的功能。

from flask import Flask, jsonify
from flask_accept import accept
app = Flask(__name__)

@app.route('/')
@accept('text/html')
def hello_world():
    return 'Hello World!'

@hello_world.support('application/json')
def hello_world_json():
    return jsonify(result="Hello World!")

if __name__ == '__main__':
    app.run()

如果您只想根据请求是否为特定数据类型来拒绝请求,您也可以使用 Flask-Negotiate

from flask import Flask
from flask_negotiate import consumes, produces

app = Flask(__name__)

@app.route('/consumes_json_only')
@consumes('application/json')
def consumes_json_only():
    return 'consumes json only'

When one tries to access the endpoint without a valid Accept header:

$ curl localhost:5000 -I
HTTP 415 (Unsupported Media Type)

您可以 return 基于接受 header 使用 request 的不同响应类型。例子.

if request.accept_mimetypes['application/json']:
           return jsonify(<object>), '200 OK'