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