I'm trying to do an AJAX request to https://developers.zomato.com/api/v2.1/search referring to Zomato API
The server has headers:
"access-control-allow-methods": "GET, POST, DELETE, PUT, PATCH, OPTIONS",
"access-control-allow-origin": "*"
The problem is that the API requires additional headers set for user-key. But whenever I set custom headers then chrome would do a pre-flight request by sending an OPTIONS request to the above URL which is failing, and thus the AJAX request is failing as well.
If I don't set the headers, then I don't get a CORS error, but rather a forbidden error from server since I'm not setting user-key header.
Any way to go about this catch-22 situation?
Both Jquery and JavaScript way are failing:
$(document).ready(function () {
$.ajax({
url: 'https://developers.zomato.com/api/v2.1/search',
headers: {
'Accept': 'application/json',
'user_key': 'XXXXX'
},
success: function (data) {
console.log(data);
}
});
});
var xhr = new XMLHttpRequest();
var url = 'https://developers.zomato.com/api/v2.1/search';
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('user_key', 'XXXXXX');
xhr.send(null);
if (xhr.status == 200) {
console.log(xhr.responseText);
}
Error I'm getting:
OPTIONS https://developers.zomato.com/api/v2.1/search
XMLHttpRequest cannot load https://developers.zomato.com/api/v2.1/search. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 501.
If somebody wants to reproduce you can get a free user-key here:
https://developers.zomato.com/api
There does not appear to be a work-around for this issue from a browser. The CORS specification requires a browser to preflight the request with the OPTIONS request if any custom headers are required. And, when it does the OPTIONS preflight, it does not include your custom headers because part of what the OPTIONS request is for is to find out what custom headers are allowed to be sent on the request. So, the server is not supposed to require custom headers on the OPTIONS request if it wants this to work from a browser.
So, if the server is requiring the custom headers to be on the OPTIONS request, then the server is just expecting something that will not happen from a browser.
See related answers that describe more about this here:
jQuery CORS Content-type OPTIONS
Cross Domain AJAX preflighting failing Origin check
How do you send a custom header in a cross-domain (CORS) XMLHttpRequest?
Using CORS for Cross-Domain Ajax Requests
And, another user with the same issue here:
Zomato api with angular
It appears the Zomato is not browser friendly, but requires access from a server where you don't have CORS restrictions.
FYI, the error coming back from Zomato is 501 which means NOT IMPLEMENTED for the OPTIONS command. So, it looks like it's not only that the key is not being sent with the OPTIONS command, but that Zomato does not support the OPTIONS command, but that is required for the use of custom headers on a cross-origin request from a browser.
You can't bypass Access-Control-Allow-Headers in preflight response.
However as mentioned by #Jaromanda X in comments, Zomato sends:
Access-Control-Allow-Headers:X-Zomato-API-Key
...meaning you can only send this non-standard header from browser. Also don't go too low-level in request definition when jQuery has pretty and prepared shorthands ...
TL;DR Working example:
$.ajax({
type: "GET", //it's a GET request API
headers: {
'X-Zomato-API-Key': 'YOUR_API_KEY' //only allowed non-standard header
},
url: 'https://developers.zomato.com/api/v2.1/dailymenu', //what do you want
dataType: 'json', //wanted response data type - let jQuery handle the rest...
data: {
//could be directly in URL, but this is more pretty, clear and easier to edit
res_id: 'YOUR_RESTAURANT_OR_PLACE_ID',
},
processData: true, //data is an object => tells jQuery to construct URL params from it
success: function(data) {
console.log(data); //what to do with response data on success
}
});
Related
I created an AWS API Gateway endpoint that needs to be called from the browser. This is a simple jquery ajax post:
$.post(
'https://jhntqqm19l.execute-api.us-east-1.amazonaws.com/dev',
{}
)
(fiddle here: https://jsfiddle.net/7cfyr1mL/)
The browser says that the endpoint does not return appropriate CORS headers:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
however when I request this endpoint from python I see that the headers do exist:
# testing OPTIONS request
>>> res = requests.options('https://jhntqqm19l.execute-api.us-east-1.amazonaws.com/dev')
>>> print(res.headers)
200
>>> print(res.status_code)
{'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true', ...}
# testing POST request
>>> res = requests.post('https://jhntqqm19l.execute-api.us-east-1.amazonaws.com/dev', json={})
>>> print(res.headers)
{'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true', ...}
>>> print(res.status_code)
400
what is wrong with my headers? How do I change them so that the browser is happy?
Your server-side code throws a 500 error and doesn't set the Access-Control-Allow-Origin header if it gets a POST request which isn't JSON encoded.
jQuery POST uses standard form URL encoding by default, so you have to override that.
$.ajax(
'https://jhntqqm19l.execute-api.us-east-1.amazonaws.com/dev',
{ contentType: 'application/json', data: JSON.stringify({}), method: "post"}
);
You'll also need to change the server-side code to allow a preflight request as currently, you don't allow JSON formatted requests with CORS.
In server side code (python in your case) you will have to enable few things along with header
'Access-Control-Allow-Origin': '*'
You can refer to https://www.codecademy.com/articles/what-is-cors
and search how to apply cors based on your framework.
The browser makes an OPTIONS call before it actually makes any call to your application. Make sure it is enabled depending on your application.
For example allowedHttpHeaders, allowedHttpMethods, allowedOrigins.
This is pretty much a known standard error but unable to fix the same using existing Stackoverflow posts.
XMLHttpRequest cannot load https://myserver.com/context/
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested
resource.
Origin 'https://www.otherwebsite.com' is therefore not allowed access.
The response had HTTP status code 405.
Following is the code I have -
function setHeader(xhr) {
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
}
var url = "https://myserver.com/context/";
$.ajax({
url: url,
type: 'GET', dataType:'json',
success: function(data) {
_this.question(data.question);
_this.questionId(data.questionId);
_this.choices(data.choices);
}, error: function() {
console.log("ERROR in getting microquestion");
},
beforeSend: setHeader
});
The “Response to preflight request… had HTTP status code 405.” message indicates, to start, the https://myserver.com/context/ endpoint needs to be configured to handle OPTIONS requests correctly—even if it may already be set to send the right CORS headers back for “normal” requests.
For more details about what’s happening, you can read the How to avoid the CORS preflight section of the answer at No 'Access-Control-Allow-Origin' header is present on the requested resource—when trying to get data from a REST API — but the gist of it is, minimally, the server must respond to OPTIONS requests with a 2xx success status code.
I am working on an internal web application at work. In IE10 the requests work fine, but in Chrome all the AJAX requests (which there are many) are sent using OPTIONS instead of whatever defined method I give it. Technically my requests are "cross domain." The site is served on localhost:6120 and the service I'm making AJAX requests to is on 57124. This closed jquery bug defines the issue, but not a real fix.
What can I do to use the proper http method in ajax requests?
Edit:
This is in the document load of every page:
jQuery.support.cors = true;
And every AJAX is built similarly:
var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
url: url,
dataType: "json",
data: json,
async: true,
cache: false,
timeout: 30000,
headers: { "x-li-format": "json", "X-UserName": userName },
success: function (data) {
// my success stuff
},
error: function (request, status, error) {
// my error stuff
},
type: "POST"
});
Chrome is preflighting the request to look for CORS headers. If the request is acceptable, it will then send the real request. If you're doing this cross-domain, you will simply have to deal with it or else find a way to make the request non-cross-domain. This is why the jQuery bug was closed as won't-fix. This is by design.
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method to the resource on the
other domain, in order to determine whether the actual request is safe
to send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
Based on the fact that the request isn't sent on the default port 80/443 this Ajax call is automatically considered a cross-origin resource (CORS) request, which in other words means that the request automatically issues an OPTIONS request which checks for CORS headers on the server's/servlet's side.
This happens even if you set
crossOrigin: false;
or even if you ommit it.
The reason is simply that localhost != localhost:57124. Try sending it only to localhost without the port - it will fail, because the requested target won't be reachable, however notice that if the domain names are equal the request is sent without the OPTIONS request before POST.
I agree with Kevin B, the bug report says it all. It sounds like you are trying to make cross-domain ajax calls. If you're not familiar with the same origin policy you can start here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript.
If this is not intended to be a cross-domain ajax call, try making your target url relative and see if the problem goes away. If you're really desperate look into the JSONP, but beware, mayhem lurks. There really isn't much more we can do to help you.
If it is possible pass the params through regular GET/POST with a different name and let your server side code handles it.
I had a similar issue with my own proxy to bypass CORS and I got the same error of POST->OPTION in Chrome. It was the Authorization header in my case ("x-li-format" and "X-UserName" here in your case.) I ended up passing it in a dummy format (e.g. AuthorizatinJack in GET) and I changed the code for my proxy to turn that into a header when making the call to the destination. Here it is in PHP:
if (isset($_GET['AuthorizationJack'])) {
$request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}
In my case I'm calling an API hosted by AWS (API Gateway). The error happened when I tried to call the API from a domain other than the API own domain. Since I'm the API owner I enabled CORS for the test environment, as described in the Amazon Documentation.
In production this error will not happen, since the request and the api will be in the same domain.
I hope it helps!
As answered by #Dark Falcon, I simply dealt with it.
In my case, I am using node.js server, and creating a session if it does not exist. Since the OPTIONS method does not have the session details in it, it ended up creating a new session for every POST method request.
So in my app routine to create-session-if-not-exist, I just added a check to see if method is OPTIONS, and if so, just skip session creating part:
app.use(function(req, res, next) {
if (req.method !== "OPTIONS") {
if (req.session && req.session.id) {
// Session exists
next();
}else{
// Create session
next();
}
} else {
// If request method is OPTIONS, just skip this part and move to the next method.
next();
}
}
"preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Consider using axios
axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {
if(res.data.error) {
} else {
doAnything( res.data )
}
}).catch(function (error) {
doAnythingError(error)
});
I had this issue using fetch and axios worked perfectly.
I've encountered a very similar issue. I spent almost half a day to understand why everything works correctly in Firefox and fails in Chrome. In my case it was because of duplicated (or maybe mistyped) fields in my request header.
Use fetch instead of XHR,then the request will not be prelighted even it's cross-domained.
$.ajax({
url: '###',
contentType: 'text/plain; charset=utf-8',
async: false,
xhrFields: {
withCredentials: true,
crossDomain: true,
Authorization: "Bearer ...."
},
method: 'POST',
data: JSON.stringify( request ),
success: function (data) {
console.log(data);
}
});
the contentType: 'text/plain; charset=utf-8', or just contentType: 'text/plain', works for me!
regards!!
I'm trying to read the headers of the coming response upon Ext.ajax.request.
Here it is the code:
Ext.Ajax.request({ url: 'http://localhost:3000/v0.1/login' ,
method: 'POST',
scope:this,
jsonData: {"_username":username,"_userpwd":password},
success: function(responseObject){
var headers = responseObject.getAllResponseHeaders();
console.info(headers );
Ext.destroy(Ext.ComponentQuery.query('#loginWindow'));
this.application.getController('SiteViewController').showView();
},
failure: function(responseObject){
alert(responseObject.status);
}
});
But the only header that it is printed out in console is:
Object {content-type: "application/json; charset=utf-8"}
All the other headers are missing, but they are present in the chrome inspector!!!
What am I missing? Thanks
Because you're probably doing a cross-domain request, you will only have headers explicitly exposed by the server. Same domain requests expose all the headers.
On the server side you have to add the header "Access-Control-Expose-Headers" with the exhaustive list of headers you want to expose, separated by a coma. In php it would look like this:
header("Access-Control-Expose-Headers: Content-length, X-My-Own-Header");
The headers will indeed be available through responseObject.getAllResponseHeaders() or something like responseObject.getResponseHeader('content-type').
More information about cross-domain requests and headers: http://www.html5rocks.com/en/tutorials/cors/
PS: Ace.Yin had the right answer, but I don't have enough reputation to simply comment.
i ran into the same issue and finally i found the solution here: http://www.html5rocks.com/en/tutorials/cors/
here is the part about the headers:
Access-Control-Expose-Headers (optional) -
The XMLHttpRequest 2 object has a getResponseHeader() method that returns the value of
a particular response header. During a CORS request, the getResponseHeader() method
can only access simple response headers.
Simple response headers are defined as follows:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
If you want clients to be able to access other headers, you have to use the
Access-Control-Expose-Headers header. The value of this header is a comma-delimited
list of response headers you want to expose to the client.
i have not verify it yet, but it seems on the right track :)
I have an API hosted on one domain that has CORS enabled with the following headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 1728000
I am able to make a GET or POST request from hackst.com and it works fine. Link: http://hackst.com/#w3SbV
From my backbone app hosted on another domain, GET requests work fine. But when I try to create and save a new model (i.e. make a POST request), it fails with the following error:
Failed to load resource: the server responded with a status of 501 (Not Implemented) http://projectwhatup.us:5000/api/posts
XMLHttpRequest cannot load http://projectwhatup.us:5000/api/posts. Origin http://ayush.projectwhatup.us is not allowed by Access-Control-Allow-Origin.
My relevant backbone code:
var newPostData = {
topic : "New Post",
body : "new body",
user_id : 1,
};
var newPostModel = new Post(newPostData);
this.model.create(newPostModel);
I even tried over-riding the create method and making a POST request manually like this:
create : function(data) {
console.log('overriden create');
$.ajax({
"url" : this.url,
"async" : true,
"beforeSend" : function(obj){
console.log(obj);
},
"contentType" : 'application/json',
//"crossDomain" : true, // uncommenting this doesnt help either
"headers" : {
},
"dataType" : 'json',
"type" : 'POST',
"data" : JSON.stringify(data),
"error" : function(err){
console.log('new post creation failed');
console.log(err);
},
"success" : function(resp){
console.log('new post created');
console.log(resp);
}
});
}
Same error.
I tried a stand-alone GET request on JSFiddle as well (http://jsfiddle.net/X9cqh/5/), but that fails even though my backbone app can make the GET request fine.
I'm completely clueless at this point. Any hints, pointers, solutions?
The server should also reply to the preflight with the following header:
Access-Control-Allow-Headers: Content-Type
This is necessary because the content type is application/json, which is outside the acceptable values defined in the CORS spec (http://www.w3.org/TR/cors/).
Your sever setup works. JSFiddle apparently does not make the ajax requests, but you can quickly test that it works by entering these four lines into Chrome console or Safari developer console:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://projectwhatup.us:5000/api/posts', false);
xhr.send();
xhr.responseText;
If you try this with a domain that does not allow CORS, it will error out.
The reason that adding a 'Content-Type' header makes your CORS request fail is because your server is set up wrongly.
If the client wishes to specify particular headers or use an unusual http method verb (e.g. PUT), then the browser will first do a 'preflight' OPTIONS call to ensure that it is allowed to set those headers. Your server needs to respond to this OPTIONS call with the appropriate headers. You'll see that options call in the network tab of the Chrome developer tools or firebug if you want to confirm that this is what the problem is.
You may be interested in my more detailed answer here.