Tornado server: enable CORS requests

2019-01-24 09:35发布

问题:

I have a simple tornado server which has the class:

class BaseHandler(tornado.web.RequestHandler):
    def set_default_headers(self):
        print "setting headers!!!"
        self.set_header("Access-Control-Allow-Origin", "*")

When a regular (no CORS) request is made, the server answers as expected, including the Access-Control-Allow-Origin header. But when I make a post request coming from different domain (using jQuery.post), the response is 404 and an error is displayed: "XMLHttpRequest cannot load http://dev-machine:8090/handshake. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8090' is therefore not allowed access. The response had HTTP status code 404."

Can you tell if I miss something? (another header/other configuration/anything else)

回答1:

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):
        # no body
        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/



回答2:

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


回答3:

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


标签: cors tornado