Bottle Py:为jQuery AJAX请求启用CORS

25

我正在使用Bottle Web框架开发RESTful API的Web服务,并希望使用jQuery AJAX调用来访问资源。

使用REST客户端,资源接口按预期工作并正确处理GET、POST等请求。但是,发送jQuery AJAX POST请求时,生成的OPTIONS预检请求被简单地拒绝为“405:方法不允许”。

我尝试在Bottle服务器上启用CORS-如此处所述:http://bottlepy.org/docs/dev/recipes.html#using-the-hooks-plugin 但是对于OPTIONS请求,after_request hook从未被调用。

以下是我的服务器摘录:

from bottle import Bottle, run, request, response
import simplejson as json

app = Bottle()

@app.hook('after_request')
def enable_cors():
    print "after_request hook"
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'

@app.post('/cors')
def lvambience():
    response.headers['Content-Type'] = 'application/json'
    return "[1]"

[...]

jQuery AJAX调用:

$.ajax({
    type: "POST",
    url: "http://192.168.169.9:8080/cors",
    data: JSON.stringify( data ),
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(data){
        alert(data);
    },
    failure: function(err) {
        alert(err);
    }
});

服务器仅记录 405 错误:

192.168.169.3 - - [23/Jun/2013 17:10:53] "OPTIONS /cors HTTP/1.1" 405 741

$.post可以使用,但无法发送PUT请求将违背RESTful服务的目的。那么如何允许处理OPTIONS预检请求呢?

4个回答

39

使用处理程序代替挂钩。

在过去,我用过两种互补的方法:装饰器或Bottle插件。我将展示这两种方法,并让您决定它们中的一种(或两种)是否适合您的需求。在这两种情况下,总体思路是:一个处理程序在响应发送回客户端之前拦截它,插入CORS头,然后继续返回响应。

方法1:安装Per-route(装饰器)

当您只想在某些路由上运行处理程序时,此方法是首选。只需为要执行它的每个路由添加修饰符即可。以下是一个示例:

import bottle
from bottle import response

# the decorator
def enable_cors(fn):
    def _enable_cors(*args, **kwargs):
        # set CORS headers
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'

        if bottle.request.method != 'OPTIONS':
            # actual request; reply with the actual response
            return fn(*args, **kwargs)

    return _enable_cors


app = bottle.app()

@app.route('/cors', method=['OPTIONS', 'GET'])
@enable_cors
def lvambience():
    response.headers['Content-type'] = 'application/json'
    return '[1]'

app.run(port=8001)

方法二:全局安装(Bottle插件)

如果您希望处理程序在所有或大多数路由上执行,则此方法更为理想。您只需 定义一个Bottle插件,Bottle将自动在每个路由上调用它,无需在每个路由上指定装饰器。(请注意,您可以使用路由的skip参数来避免每个路由上的此处理程序。)以下是与上面的示例相对应的示例:

import bottle
from bottle import response

class EnableCors(object):
    name = 'enable_cors'
    api = 2

    def apply(self, fn, context):
        def _enable_cors(*args, **kwargs):
            # set CORS headers
            response.headers['Access-Control-Allow-Origin'] = '*'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'

            if bottle.request.method != 'OPTIONS':
                # actual request; reply with the actual response
                return fn(*args, **kwargs)

        return _enable_cors


app = bottle.app()

@app.route('/cors', method=['OPTIONS', 'GET'])
def lvambience():
    response.headers['Content-type'] = 'application/json'
    return '[1]'

app.install(EnableCors())

app.run(port=8001)

1
好的,现在它可以工作了!但是使用您的解决方案,我需要为每个请求创建一个新路由,仅用于OPTIONS方法以允许预检(即我的REST API中的所有请求)?它不能通过现有路由进行附加,因为定义的方法期望request.json要么正确填充,要么不正确(即错误)。 - Joern
Joern,这个行得通了吗?如果还需要帮助,请告诉我。 (如果您认为答案正确,请接受它,我会很感激!) - ron rothman
嗨,抱歉让你久等了,我几乎没有时间编程。您的代码仍然有问题(除了 Method 2 中的缩进错误之外)。由于请求方法上的 if-检查确保对于任何其他方法,响应头未设置为 ...-Allow-Origin:*,因此仅允许通过CORS进行OPTIONS请求。我删除了if行并用 if bottle.request.method!= 'OPTIONS': 替换了 else:。现在一切都很好 :) 如果您能解决这个问题,我可以将您的答案标记为解决方案! 感谢您的帮助。 - Joern
1
另外:在第一条评论中提到的cors_plugin似乎无法在服务器接收到的第一个请求是preflighted的情况下正常工作。非preflighted请求似乎会触发OPTIONS方法的注册,只有在此之后才能处理OPTIONS请求。非常尴尬。 - Joern
这在除了Firefox之外的所有浏览器上都对我有效。 Chrome和Safari表现得很出色,但Firefox甚至不会执行请求。 有人知道为什么吗? 在firefox中单击按钮时,“Live HTTP Headers”插件或“Network”选项卡中没有标题显示,但在chrome中一切正常显示。 - user1071182
显示剩余8条评论

7
这是对@ron.rothman的第二种全局安装CORS处理程序方法的小改进。他的方法要求您在声明每个路由时指定接受OPTIONS方法。此解决方案为所有OPTIONS请求安装了全局处理程序。
@bottle.route('/<:re:.*>', method='OPTIONS')
def enable_cors_generic_route():
    """
    This route takes priority over all others. So any request with an OPTIONS
    method will be handled by this function.

    See: https://github.com/bottlepy/bottle/issues/402

    NOTE: This means we won't 404 any invalid path that is an OPTIONS request.
    """
    add_cors_headers()

@bottle.hook('after_request')
def enable_cors_after_request_hook():
    """
    This executes after every route. We use it to attach CORS headers when
    applicable.
    """
    add_cors_headers()

def add_cors_headers():
    if SOME_CONDITION:  # You don't have to gate this
        bottle.response.headers['Access-Control-Allow-Origin'] = '*'
        bottle.response.headers['Access-Control-Allow-Methods'] = \
            'GET, POST, PUT, OPTIONS'
        bottle.response.headers['Access-Control-Allow-Headers'] = \
            'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'

```


这种方法适用于在bottle中包装的cheroot cherrypy服务器,谢谢。 - 73k05
你能添加一条路由吗?我不知道如何使用它。 - user3072843

2
“也许你应该真正使用这个?”
response.set_header('Access-Control-Allow-Origin', '*')
response.add_header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')

这对我来说在Python 3.6和bottle 0.12.16上有效。被接受的答案在尝试编写标头时会抛出错误。 - Checo R

1

考虑让您的Web服务器而不是Bottle设置标头。

不确定这是否适用于您的情况,但我过去通过在Apache中为我的Bottle应用程序设置CORS标头来解决了该问题。 它易于配置,使我的Python代码保持整洁并且高效。

有关信息可以从许多来源获取,但如果您正在使用Apache,则我的配置如下(或多或少):

<Location "/cors">
    Header set Access-Control-Allow-Headers "Origin, Content-Type"
    Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Request-Headers "Origin, Content-Type"
</Location>

我使用异步GEVENT pywsgi和AWS负载均衡器。我真的不明白为什么要在AWS中运行apache,因为已经有负载均衡器选项了。 - eatmeimadanish
谢谢。OP没有提到AWS,所以不确定它与这个问题有什么关系。 - ron rothman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接