Enabling CORS support for server
tutorial python bottlepy cors backendso i am working on this project
Project brief: there is this webapp that I am working on right now. It consist of a single-page application (SPA) serving the user interface, connected to a data API serving the content.
On my developement environment, I am running the two services on different ports (the SPA on port 8000
, the API on port 8080
).
On the SPA, we wrote an API object that will make XHR requests to the content server. In theory, this should work just fine. The page send a request to the server then load the response into the interface, what is so hard about it?
This is what is so hard about it.
going into CORS
CORS, cross orgin request sharing. All I knew was that browsers are careful when different hosts share resources because of cross-site scripting. Beyond that, I am clueless as to how the whole CORS process works and how to implement it on a server.
Looking at the error message, it seems like I am missing a Access-Control-Allow-Origin
header. So I just added it in, in hope that this simple solution will do. The sample below is a very simplified version of what I did.
I am using bottle.py here, but the idea is the similar for whatever framework you use (flask, web.py, webapp2, etc)
from bottle import route, app
@route('/')
def index():
bottle.response.set_header("Acess-Control-Allow-Origin", "*")
return 'ok'
if __name__=='__main':
app.run()
it doesn’t work?!?!
I tried accessing the resource from the terminal: curl -i localhost:8080
. Seems to return the right value. Should work by now right?
didn’t i send out a get request?
500 Internal Error? My server doesn’t accept OPTIONS
request? Why does my GET
request keeps changing into an OPTIONS
request? What keeps changing the XHR’s method?
I went about searching and rewriting my XHR function, but there doesn’t seem to be anything wrong. There shouldn’t be an reason why the method is changed. So, what’s up with the OPTIONS
request?
As I found out after hours of Googling later, the OPTIONS
request is part of the CORS checking process. What else does this CORS include exactly? I decided I should read up more on the protocol before continuing.
CORS basics
This is what I learnt. The CORS protocol verifies if a request is legitimate. And how does the browser check the legitimacy of a request? By sending pre-flight OPTIONS
request to check.
The OPTIONS
request is kind of like the browser asking the server: “So… what kind of requests do you accept”. And if the response value fits the XHR request, the request is sent and everything goes well, else, you will trigger the Cross-Origin Request Blocked error.
The XHR request will be matched with the headers returned in response to the OPTIONS
request. These headers will describe what kind of requests this server is allowed to respond to (the requests’ methods, their origin, etc)
so let’s set it up
First of all, we will need to allow our server to accept OPTIONS
request; no use having the framework drop all your requests.
We will then add in all the appropriate headers that CORS requires, which includes Access-Control-Allow-Origin
, 'Access-Control-Allow-Methods
and Access-Control-Allow-Headers
. (there are more of course, but these are sufficient)
So what does the 3 headers represent? The names are pretty intuitive.
Access-Control-Allow-Origin
will determine if your request origin (host of the frontend SPA) is on the whitelist of the server. To allow access from all pages,set this value to *
.
The second header, Access-Control-Allow-Methods
determines what kind of methods are allowed. These will include the usual GET
, POST
methods. Your list should include OPTIONS
too. (Since the browser would have to make a request to know what kind of requests are valid)
Access-Control-Allow-Headers
is a list of headers that your requests are allowed to attached (Origin
is one that is used in CORS) on the request going to the server.
this is how you do it
Putting all of that into code
from bottle import route, app
@route('/', methods='GET OPTIONS'.split())
def index():
bottle.response.set_header("Access-Control-Allow-Origin", "*")
bottle.response.set_header("Access-Control-Allow-Origin", "GET, POST, OPTIONS")
bottle.response.set_header("Access-Control-Allow-Headers", "Origin, Content-Type")
return 'ok'
if __name__=='__main':
app.run()
I tested it out and tried the reach the API server through my frontend API. It works!!!
a better way to do this
This code here stinks, there has to be a much better way of writing this…
I took out the headers and place them in a decorator. Since we will be using it all the time, makes no sense to have to write them over and over again.
from bottle import route, app
def cors(func):
def wrapper(*args, **kwargs):
bottle.response.set_header("Access-Control-Allow-Origin", "*")
bottle.response.set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
bottle.response.set_header("Access-Control-Allow-Headers", "Origin, Content-Type")
# skip the function if it is not needed
if bottle.request.method == 'OPTIONS':
return
return func(*arg, **kwargs)
return wrapper
@route('/', methods='GET OPTIONS'.split())
@cors
def index():
return 'ok'
if __name__=='__main':
app.run()
that’s all?
Yes, that is all to setting up your python API server to handle CORS pre-flight requests. Now your API server will work as you expect it to.
The idea here is transferable to different frameworks, so as long as you know how to set the response headers you are good to go.
On the frontend side, all you have to do is to create a XHR that opens to your API endpoint, nothing fancy. If you need some help, here is some javascript to get you started.
function fetch(url){
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.addEventListener("load", function(){
console.log(this.responseText);
});
xhr.send();
return xhr;
}
fin
If you have any questions, or some part of it doesn’t seem to work, just leave a comment below, and let me see if I can help you with it.
Keep coding :)