Cross-domain HTTP with Python
For security and other reasons, browsers put limitations on what sites you can access from JavaScript. The basic rule is: if the web page containing the script originates from mysite.com, then the script is only allowed to access mysite.com.
New Firefox and Chrome versions allow the server owner to bypass these limitations. When JavaScript tries to make a cross-domain (or cross-site, whatever you call it) request, the browser does not outright deny it. Instead, the browser will reach out to the server and ask, “I would like to make this kind of request, is it allowed?” The browser is using the OPTIONS method to place this question. If the server gives permission, the browser sends the actual request.
This is all documented for example in Mozilla Developer Network, so I’m not going to duplicate the instructions for setting appropriate headers here.
One thing that might not be obvious is that at least Firefox seems to require that you also set these Access-Control headers for the actual response. Remember, the browser is making two requests. First with method OPTIONS, then with GET or POST. As Carsten has also noticed, Firefox may not give the actual response to JavaScript if the server does not set the access-control headers also to the response. There is also a Stackoverflow question that partly covers this.
Now, fast forward to the Python part. Below is a simple Python decorator that can be used, for example, with Django to allow cross-domain requests to your application. Before you use this for anything real, take a look at the access-control headers it is setting and compare those with the documentation.
The decorator is meant to be used on your view function that takes a request in and returns a response. For the OPTIONS method, the decorator does not call the actual function at all. Instead, it just sets the access control headers to the response and returns. For the actual GET/POST, the decorator first calls the function and then adds the access-control headers.
@HttpOptionsDecorator
def retrieve_rate(request, currency):
# do something
return HttpResponse(....)
def set_access_control_headers(response):
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
response['Access-Control-Max-Age'] = 1000
response['Access-Control-Allow-Headers'] = '*'
class HttpOptionsDecorator(object):
def __init__(self, f):
self.f = f;
def __call__(self, *args):
logging.info("Call decorator")
request = args[0]
if request.method == "OPTIONS":
response = HttpResponse()
set_access_control_headers(response)
return response
else:
response = self.f(*args)
set_access_control_headers(response)
return response