Bottle Py: Enabling CORS for jQuery AJAX requests
Solution 1
Install a handler instead of a hook.
There are two complementary ways I've done this in the past: decorator, or Bottle plugin. I'll show you both and you can decide whether one (or both) of them suit your needs. In both cases, the general idea is: a handler intercepts the response before it's sent back to the client, inserts the CORS headers, and then proceeds to return the response.
Method 1: Install Per-route (Decorator)
This method is preferable when you only want to run the handler on some of your routes. Just decorate each route that you want it to execute on. Here's an example:
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)
Method 2: Install Globally (Bottle Plugin)
This method is preferable if you want the handler to execute on all or most of your routes. You'll just define a Bottle plugin once, and Bottle will automatically call it for you on every route; no need to specify a decorator on each one. (Note that you can use a route's skip
parameter to avoid this handler on a per-route basis.) Here's an example that corresponds to the one above:
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)
Solution 2
Here's a minor improvement on @ron.rothman's method #2 for installing the CORS handler globally. His method requires you to specify that the OPTIONS
method is accepted on every route you declare. This solution installs a global handler for all OPTIONS
requests.
@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'
```
Solution 3
Also shouldn't you actually be using this?
response.set_header('Access-Control-Allow-Origin', '*')
response.add_header('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')
Solution 4
Consider letting your webserver, not Bottle, set the headers.
Not sure whether this applies in your situation, but I've solved the problem in past projects by setting CORS headers for my Bottle application in Apache. It's easy to configure, keeps my Python code nice and clean, and is efficient.
Information is available from many sources, but if you're using Apache, here's what my config looks like (more or less):
<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>
Related videos on Youtube
Joern
Updated on February 22, 2020Comments
-
Joern over 4 years
I'm working on a RESTful API of a web service on the Bottle Web Framework and want to access the resources with jQuery AJAX calls.
Using a REST client, the resource interfaces work as intended and properly handle GET, POST, ... requests. But when sending a jQuery AJAX POST request, the resulting OPTIONS preflight request is simply denied as '405: Method not allowed'.
I tried to enable CORS on the Bottle server - as described here: http://bottlepy.org/docs/dev/recipes.html#using-the-hooks-plugin But the after_request hook is never called for the OPTIONS request.
Here is an excerpt of my server:
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]" [...]
The jQuery AJAX call:
$.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); } });
The server only logs a 405 error:
192.168.169.3 - - [23/Jun/2013 17:10:53] "OPTIONS /cors HTTP/1.1" 405 741
$.post does work, but not being able to send PUT requests would defeat the purpose of a RESTful service. So how can I allow the OPTIONS preflight request to be handled?
-
Joern about 11 yearsHi Ron, thanks a lot for your answers. I've tried both your methods but unfortunately neither worked for me. The issue persists that the OPTIONS request gets outright denied with Error 405. Also, is there a significant difference between Method 2 and calling enable_cors via a hook? To me they look virtually the same (though I'm no python nor bottle expert). What seems to work is this cors_plugin ( github.com/myGengo/bottle-cors ). It seems to add a dedicated @options routing in the RequestPreflightPlugin class. This appears to do the trick. Any idea why your code doesn't work?
-
ron rothman about 11 yearsOops, sorry, I forgot to include the
method
param in theroute
call, to tell Bottle to acceptOPTIONS
requests. (Otherwise the default method is justGET
.) I've updated the code snippets; looks like it's working now; hopefully works for you too. -
Joern about 11 yearsOk, it works now! But with your solution, I'd need to create a new route just for method OPTIONS for each request I want to allow preflights (i.e. all in my REST API)?It can't be piggybacked by existing routes as the defined methods expect request.json to be either correctly populated or incorrect (i.e. an error).
-
ron rothman about 11 yearsCool! Hmm, wouldn't your original hook-based idea have had the same property? (I.e., conflict with methods that expect request.json.) In any case, I think it's solvable; if you want help, I can modify my code, no problem.
-
ron rothman about 11 yearsDone! No new routes needed. Actually, I'm glad you made that last point, because I think my new code is better than it was originally. So, thanks. :)
-
ron rothman about 11 yearsJoern, did this do the trick? Let me know if I can be of more help. (And if you see fit to accept the answer as correct, I'd appreciate it!)
-
Joern about 11 yearsHi, sorry for the wait, I'm hardly finding time for programming. Your code is still faulty (apart from the indentation error in Method 2). Only OPTIONS requests are allowed via CORS as the if-check on request method makes sure that for any other method, the response header is not set to ...-Allow-Origin: *. I removed the if-line and put a
if bottle.request.method != 'OPTIONS':
instead of theelse:
. Now everything works great :) If you can fix that, I can mark your answer as the solution! You were really helpful thanks. -
Joern about 11 yearsalso: the cors_plugin linked in the very first comment doesn't seem to work if the very first request the server receives is preflighted. A non-preflighted request seems to trigger the registration of the OPTIONS method, only then are OPTIONS requests handled. Very awkward.
-
ron rothman about 11 yearsSorry, didn't mean to rush you--happy to hear you got it working with that change. I'm not sure I totally understood the fix you suggested--so when you have a minute, take a look at the modifications I made and let me know if that's what you meant.
-
Joern about 11 yearsYup, just like that, although the if block in Method 2 should be 1 indentation level to the right (like in Method 1).
-
ron rothman about 11 yearsYikes, thought I fixed the indentation. Should be good now. Thanks for the collaboration!
-
user1071182 almost 9 yearsThis worked for me on all browsers except firefox. Chrome and Safari working splendidly, but Firefox won't even execute the request. Anyone have any idea why? No headers show up in "Live HTTP Headers" plugin or the "Network" tab when clicking button in firefox, but everything show up fine in chrome.
-
Checo R about 5 yearsThis worked for me with Python 3.6 and bottle 0.12.16. The accepted answer throws and error when trying to write headers
-
eatmeimadanish about 5 yearsI use async GEVENT pywsgi and an AWS load balancer for mine. I really do not see the point of running apache with the load balancer options available in AWS.
-
ron rothman about 5 yearsThanks. OP didn't mention AWS so not sure how that's relevant to this question.
-
73k05 about 4 yearsThis method works for a cheroot cherrypy server wrapped in bottle, thanks
-
Giulio almost 4 yearsI am using pypi.org/project/bottle-cors, works ok.
-
user3072843 almost 4 yearsCould you add a route? I don't know how to use this.