Bottle Py: включение CORS для запросов JQuery AJAX

Я работаю над RESTful API веб-службы на платформе Bottle Web Framework и хочу получить доступ к ресурсам с помощью вызовов jQuery AJAX.

Используя клиента REST, интерфейсы ресурсов работают по назначению и корректно обрабатывают запросы GET, POST,.... Но при отправке запроса POQ JQuery AJAX, в результате запроса предварительной предварочной процедуры OPTIONS просто отказано как "405: метод не разрешен".

Я попытался включить CORS на сервере Bottle - как описано здесь: http://bottlepy.org/docs/dev/recipes.html#using-the-hooks-plugin Но after_request hook никогда не вызывается для запроса OPTIONS.

Вот отрывок моего сервера:

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?

Ответ 1

Установите обработчик вместо крюка.

Есть два дополнительных способа, которые я делал в прошлом: декоратор или плагин для бутылок. Я покажу вам обоим, и вы можете решить, подходит ли один (или оба) к вашим потребностям. В обоих случаях общая идея заключается в следующем: обработчик перехватывает ответ до его отправки клиенту, вставляет заголовки CORS и затем возвращает ответ.

Способ 1: установить в маршрут (декоратор)

Этот метод предпочтительнее, если вы хотите только запустить обработчик на некоторых ваших маршрутах. Просто украсьте каждый маршрут, который вы хотите выполнить. Вот пример:

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)

Способ 2: установить глобально (плагин для бутылок)

Этот метод предпочтительнее, если вы хотите, чтобы обработчик выполнялся на всех или большинстве ваших маршрутов. Вы просто определите плагин Bottle один раз, и Bottle автоматически вызовет его для вас на каждом маршруте; не нужно указывать декоратор на каждом из них. (Обратите внимание, что вы можете использовать параметр route 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)

Ответ 2

Здесь небольшое усовершенствование метода @ron.rothman №2 для установки обработчика 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'

`` `

Ответ 3

Подумайте о том, чтобы позволить вашему веб-серверу, а не бутылке, устанавливать заголовки.

Не уверен, что это применимо в вашей ситуации, но я решил проблему в прошлых проектах, установив заголовки CORS для моего приложения Bottle в Apache. Он легко настраивается, сохраняет мой код Python красивым и чистым и эффективен.

Информация доступна из многих источников, но если вы используете Apache, то вот как выглядит мой config (более или менее):

<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>

Ответ 4

И разве вы не должны использовать это?

response.set_header('Access-Control-Allow-Origin', '*')
response.add_header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')