Tornado server: enable CORS requests

The answer by kwarunek led me to the solution for my trouble with the PUT and the DELETE request. The only thing is, that the solution is over-appropriate for the example with GET and POST. In this case the line

self.set_header("Access-Control-Allow-Origin", "*")

is actually sufficient (if the browser doesn't block CORS before all). It is though most relevant for the PUT and DELETE requests. What happens here on the network level can be slightly more complex than in the GET/POST case.

"If the request is a "non-simple" request, the browser first sends a data-less "preflight" OPTIONS request, to verify that the server will accept the request. A request is non-simple when using an HTTP verb other than GET or POST (e.g. PUT, DELETE)." cf. non-simple requests

class BaseHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        print("setting headers!!!")
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', ' PUT, DELETE, OPTIONS')

    def options(self):
        # no body
        self.set_status(204)
        self.finish()

Now all handlers that inherit from BaseHandler are fully CORS-capable:

class MyHandler(BaseHandler):

    def put(self):
        self.write('some post')

    def delete(self):
        self.write('some get')

Your code is missing preflight, the OPTIONS request.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS:

The Cross-Origin Resource Sharing standard works by adding new HTTP headers that allow servers to describe the set of origins that are permitted to read that information using a web browser. Additionally, for HTTP request methods that can cause side-effects on user data (in particular, for HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method. Servers can also notify clients whether "credentials" (including Cookies and HTTP Authentication data) should be sent with requests.

To implement preflight handler simply add options handler with the same headers and no body.

class BaseHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        print "setting headers!!!"
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')

    def post(self):
        self.write('some post')

    def get(self):
        self.write('some get')

    def options(self, *args):
        # no body
        # `*args` is for route with `path arguments` supports
        self.set_status(204)
        self.finish()

edit

I've added x-requested-with header to allowed list. And here is simple jquery sample:

 $.ajax({
   url: "http://some_tornado/api",
   type: "POST",
   crossDomain: true,
   data: 'some_data',
   success: function (response) {
     alert(response);
   },
   error: function (xhr, status) {
     alert("error");
   }
 });

And some really good article about cors - http://dev.housetrip.com/2014/04/17/unleash-your-ajax-requests-with-cors/


Even with the previous answers I still got the following CORS error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:9999/home?message=Input%20to%20API.. (Reason: missing token ‘access-control-allow-origin’ in CORS header ‘Access-Control-Allow-Headers’ from CORS preflight channel).

and the solution is to also allow the headers:

class BaseHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        print("setting headers!!!")
        self.set_header("access-control-allow-origin", "*")
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS')
        # HEADERS!
        self.set_header("Access-Control-Allow-Headers", "access-control-allow-origin,authorization,content-type") 

    def options(self):
        # no body
        self.set_status(204)
        self.finish()

This worked for me.

def set_default_headers(self):
    self.set_header("Content-Type", "application/json")
    self.set_header("Access-Control-Allow-Origin", "*")
    self.set_header("Access-Control-Allow-Headers", "content-type")
    self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PATCH, PUT')

Tags:

Cors

Tornado