Why does JSONP work with JSON-formatted data? - ajax

There is a URL I am using in a project of mine, that is working just fine. I make a request from one web server, to a different IP address, invoking a page that outputs data in this format:
[{"PhoneNumber":"+123456789","Name":"Mark"},
{"PhoneNumber":"+123456789","Name":"Josh"},
{"PhoneNumber":"+123456789","Name":"Alex"},
{"PhoneNumber":"+123456789","Name":"John"},
{"PhoneNumber":"+123456789","Name":"Sean"}]
And I can get and process that data with a function call such as this:
$.ajax({
url: serverAddress + "/getpeople",
dataType: "jsonp",
timeout: 4000,
success: function(response) {
for(var i in response) {
alert(response[i].Name);
}
}
});
Here is what's confusing me. From what I've learned about JSONP so far, it isn't actually data, but is instead a function. So the response should be wrapped in a function call, such as callback(), and then I could implement a function callback(data) {} in my project to process the data.
But in this case, the data seems to be just JSON data, which I think should cause a cross-origin error to be generated? But it doesn't.
When I try to call another URL from the same server, fetching an ordinary plain text file, then I do get a cross-origin error, which complains in the console:
Reason: CORS header 'Access-Control-Allow-Origin' missing
But the original getpeople URL does not have that header either. When I examine the response headers in Firefox's document inspector, all of the headers are:
Connection: "close"
Content-Type: "text/html;charset=utf-8"
Date: "Mon, 5 Oct 2015 08:29:07 GMT"
Server: "ServerName/1.1.10011.2211"
So:
The data is not formatted as a JSONP callback
It is served from a different IP address than the web application
The response doesn't have a Access-Control-Allow-Origin header
Why does this work?

Related

"The length of the string exceeds the value set on the maxJsonLength property" isn't respecting the web.config value?

I asked this question the other day but was not able to come up with a solution:
What happens between clicking a button and the Javascript method actually executing?
Posting a new question with better information including a screenshot of the ajax request, the details from the request / response, as well as my own understanding of what was going on yesterday more clearly so I could debug it better.
My jQuery sends the following request to my server:
The RtfErrorList in data is north of 4 million characters and includes RTF encoding.
Despite adding a variety of XML nodes to my web.config to increase the the maxJsonLength, I'm still getting this exception.
If I look at the network tab in Chrome's dev tools, I get the following information (I stripped some unimportant bits out like origin: locahost and other things like that)
Request Method:POST
Status Code:500 Internal Server Error
Response Headers:
HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-UA-Compatible: IE=Edge
Request Headers:
Content-Length: 3799356
Content-Type: application/json;
Accept: text/html, /; q=0.01
X-Requested-With: XMLHttpRequest
Request Payload:
FileName : "someFileName"
RtfErrorList : "some4MillionCharacterStringIncludingRtfEncoding"
The last few entries from the stack trace:
[ArgumentException: Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.
Parameter name: input]
System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)
System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
I've tried adding the following keys to my web.config:
<add key="JSONMAXJSONLENGTH" value="2147483644" />
<add key="aspnet:MaxJsonDeserializerMembers" value="2147483644" />
I'm actually not sure what that first node is from; it was here before me.
As well as the snippet found here.
Per this SO answer it is my understanding these web.config values are used only by the internal JavaScriptSerializer class, not any "custom" code I might write in a controller myself. In that case, this should be fine as it is System.web.SCript.Serialization.javaScriptSerializer.Deserialize that is throwing the exception. This should be using the web.config value, right?
I cannot figure out what could possibly be causing this...
Are you expecting to receive html format as you set dataType:'html' in Ajax ?
Did you try this in your Ajax:
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
Moreover, according to the documentation, the value of the MaxJsonLength property applies only to the internal JavaScriptSerializer instance that is used by the asynchronous communication layer to invoke Web services methods.
In this case, you may need to serialize manually in the controller. For instance:
public ActionResult FOO()
{
var serializer = new JavaScriptSerializer { MaxJsonLength = Int32.MaxValue };
// You could also read MaxJsonLength from your config
//serializer.MaxJsonLength = Int32.MaxValue;
var myLargeData = new {
ID = "5",
Foo = "Bar",
Value = "foo"
};
var result = new ContentResult
{
Content = serializer.Serialize(myLargeData),
ContentType = "application/json"
};
return result;
}

Using ajax to GET a token from external site returns empty response

I have two sites right now. One that has a token and one that is supposed to allow a user to do stuff with the token.
When I visit the first site that has the token, mySite.local/services/session/token it shows it: OTV4Gu9VQfjIo2ioQ0thajdEJ6nEINoxsLuwgT_6S0w
When I am on the page that is supposed to GET this token, I get an empty response and the error for the ajax function is thrown.
The weird part is that when investigating the issue with firebug, I can see the response for the ajax request is 43B - the same size as the token. So for some reason the page with the token is being hit properly, but the response is not coming through.
Here is a screenshot of the firebug response:
And here is the JQuery with the ajax request:
var nid; //global node id variable
$('html').click(function(){
try {
$.ajax({
url:"http://mySite.local/services/session/token",
type:"get",
dataType:"text",
error:function (jqXHR, textStatus, errorThrown) {
alert('error thrown - ' + errorThrown);
console.log(JSON.stringify(jqXHR));
console.log(JSON.stringify(textStatus));
console.log(JSON.stringify(errorThrown));
},
success: function (token) {
//Do some stuff now that token is received
}
});
}
catch (error) {
alert("page_dashboard - " + error);
}
});
Your running into the Same Origin Policy which essentially states any request done by client side/browser language like Javascript must be on the same port, with the same domain name and the same protocol. In your case http://mysitemobile.local does not equal http://mysite.local so you're request is being blocked. Firebug's way of displaying that is no response with 43 bytes.
There are two ways to work around this, Cross-origin resource sharing (CORS) or JSONP. CORS is a HTTP header that is added to the server you are requesting to and provides a whitelist of acceptable domains that are allowed break the same origin policy. Most recent browsers support this header.
The other option is JSONP, wraps a JSON object into a Javascript function that is called using <script> tags normally. If the other server returns {status: 0} and you have a function called parseStatus() in your code that the remote server would wrap into parseStatus({status:0}); thus calling your function without having to worry about the same origin policy.

Invalid JSON GET Request Express.js

While writing an API, I have come across a very thorny error: when I try to do a res.send(INSERT JSON) with a Content-Type header application/json (a default for most AJAX), I get an invalid json error. When I set the content-type to anything else (eg. text/plain), I get the correct response, but in order to use some front-end frameworks, I need to support application/json. Here is the actual error message:
Error: invalid json
at Object.exports.error (/Users/Brad/node_modules/express/node_modules/connect/lib/utils.js:44:13)
at IncomingMessage.module.exports (/Users/Brad/node_modules/express/node_modules/connect/lib/middleware/json.js:68:73)
at IncomingMessage.EventEmitter.emit (events.js:85:17)
at IncomingMessage._emitEnd (http.js:366:10)
at HTTPParser.parserOnMessageComplete [as onMessageComplete] (http.js:149:23)
at Socket.socket.ondata (http.js:1680:22)
at TCP.onread (net.js:410:27)
My server code is below:
app.configure(function () {
app.use(express.bodyParser());
app.use(express.cookieParser('SALT'));
app.use(express.static(__dirname + '/static/'));
app.use(express.session());
});
app.get('/users', function(req, res) {
res.send({'test': 'test'});
});
Here is an picture of my Postman setup--I am using the Postman Chrome extension to test my API:
I believe the problem is that you want to be using Content-Type header in your servers response; not in your request Content-Type header.
When you use the Content-Type header in your request, Express will read the Content-Type and attempt to interpret the provided information as that Content-Type, in this case, as JSON. Because this is a GET request and thus has no body, Express is trying to interpret an empty string as JSON, which is giving you the error.

jQuery.ajax call to Twitter succeeds but returns null for Firefox

I've got code that makes a simple get request to Twitter (search) using jQuery's Ajax method. The code works fine on Safari, but fails on Firefox (3.6.3). In the Firefox case, my jQuery.ajax parameters 'success' method is invoked, but the supplied data is null. (In Safari, I receive a lot of JSON data.)
My Ajax call is:
$.ajax({
url: 'http://search.twitter.com/search.json?q='+searchTerm,
dataType: 'json',
async: true,
beforeSend: function(request) {
window.console.log('starting AJAX request to get Twitter data');
},
success: function(data, textStatus, request) {
window.console.log('AJAX request to get Twitter succeeded: status=' + textStatus);
callback(data);
},
error: function(request, status, error) {
window.console.log('Ajax request to get user data --> Error: ' + status);
errback(request, status, error);
}
});
Firebug shows Response headers:
Date Sun, 11 Apr 2010 22:30:26 GMT
Server hi
Status 200 OK
X-Served-From b021
X-Runtime 0.23841
Content-Type application/json; charset=utf-8
X-Served-By sjc1o024.prod.twitter.com
X-Timeline-Cache-Hit Miss
Cache-Control max-age=15, must-revalidate, max-age=300
Expires Sun, 11 Apr 2010 22:35:26 GMT
Vary Accept-Encoding
X-Varnish 1827846877
Age 0
Via 1.1 varnish
X-Cache-Svr sjc1o024.prod.twitter.com
X-Cache MISS
Content-Encoding gzip
Content-Length 2126
Connection close
The HTTP status is OK (200), the Content-Type is properly application/json, and the Content-Length of 2126 (gzip'd) implies data came back. Yet, Firebug shows the Response to be empty, and a test of the supplied data shows it to be 'null.'
I am aware of a similar post on Stack Overflow, jQuery $.get() function succeeds with 200 but returns no content in Firefox and from that would assume this problem is possibly related to cross-domain security, but... I know there are many JavaScript widgets and whatnot that Ajax get data from Twitter. Is there something I need to enable to allow this?
You are attempting to make a cross domain Ajax call. For this to happen you need to use JSONP.
JQuery understands JSONP and it will handle all the underlying tricks for you.
You only need to add the parameter &callback=? to your URL and JQuery will make the request as a Cross domain call. More important is, it will understand and handle the JSONP response from the server, so for you it will be transparent.

jQuery $.ajax(), $.post sending "OPTIONS" as REQUEST_METHOD in Firefox

Having trouble with what I thought was a relatively simple jQuery plugin...
The plugin should fetch data from a php script via ajax to add options to a <select>. The ajax request is pretty generic:
$.ajax({
url: o.url,
type: 'post',
contentType: "application/x-www-form-urlencoded",
data: '{"method":"getStates", "program":"EXPLORE"}',
success: function (data, status) {
console.log("Success!!");
console.log(data);
console.log(status);
},
error: function (xhr, desc, err) {
console.log(xhr);
console.log("Desc: " + desc + "\nErr:" + err);
}
});
This seems to work fine in Safari. In Firefox 3.5, the REQUEST_TYPE on the server is always 'OPTIONS', and the $_POST data does not appear. Apache logs the request as type 'OPTIONS':
::1 - - [08/Jul/2009:11:43:27 -0500] "OPTIONS sitecodes.php HTTP/1.1" 200 46
Why would this ajax call work in Safari, but not Firefox, and how do I fix it for Firefox?
Response Headers
Date: Wed, 08 Jul 2009 21:22:17 GMT
Server:Apache/2.0.59 (Unix) PHP/5.2.6 DAV/2
X-Powered-By: PHP/5.2.6
Content-Length 46
Keep-Alive timeout=15, max=100
Connection Keep-Alive
Content-Type text/html
Request Headers
Host orderform:8888
User-Agent Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
Connection keep-alive
Origin http://ux.inetu.act.org
Access-Control-Request-Method POST
Access-Control-Request-Headers x-requested-with
Here is a picture of the Firebug output:
The reason for the error is the same origin policy. It only allows you to do XMLHTTPRequests to your own domain. See if you can use a JSONP callback instead:
$.getJSON( 'http://<url>/api.php?callback=?', function ( data ) { alert ( data ); } );
I used the following code on Django side to interpret the OPTIONS request and to set the required Access-Control headers. After this my cross domain requests from Firefox started working. As said before, the browser first sends the OPTIONS request and then immediately after that the POST/GET
def send_data(request):
if request.method == "OPTIONS":
response = HttpResponse()
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
response['Access-Control-Max-Age'] = 1000
# note that '*' is not valid for Access-Control-Allow-Headers
response['Access-Control-Allow-Headers'] = 'origin, x-csrftoken, content-type, accept'
return response
if request.method == "POST":
# ...
Edit: it seems to be that at least in some cases you also need to add the same Access-Control headers to the actual response. This can be a little bit confusing, since the request seems to succeed, but Firefox does not pass the contents of the response to the Javascript.
This mozilla developer center article describes various cross-domain request scenarios. The article seems to indicate that a POST request with content type of 'application/x-www-form-urlencoded' should be sent as a 'simple request' (with no 'preflight' OPTIONS request). I found , however, that Firefox sent the OPTIONS request, even though my POST was sent with that content type.
I was able to make this work by creating an options request handler on the server, that set the 'Access-Control-Allow-Origin' response header to '*'. You can be more restrictive by setting it to something specific, like 'http://someurl.com'. Also, I have read that, supposedly, you can specify a comma-separated list of multiple origins, but I couldn't get this to work.
Once Firefox receives the response to the OPTIONS request with an acceptable 'Access-Control-Allow-Origin' value, it sends the POST request.
I've fixed this issue using an entirely-Apache based solution. In my vhost / htaccess I put the following block:
# enable cross domain access control
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
# force apache to return 200 without executing my scripts
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule .* / [R=200,L]
You may not need the latter part, depending on what happens when Apache executes your target script. Credit goes to the friendly ServerFault folk for the latter part.
This PHP at the top of the responding script seems to work. (With Firefox 3.6.11. I have not yet done a lot of testing.)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 1000');
if(array_key_exists('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', $_SERVER)) {
header('Access-Control-Allow-Headers: '
. $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
} else {
header('Access-Control-Allow-Headers: *');
}
if("OPTIONS" == $_SERVER['REQUEST_METHOD']) {
exit(0);
}
I had same problem with sending requests to google maps, and solution is quite simple with jQuery 1.5 - for dataType use dataType: "jsonp"
Culprit is preflight request using OPTIONS method
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.
Web specification refer to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
I resolved the problem by adding following lines in Nginx conf.
location / {
if ($request_method = OPTIONS ) {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "POST, GET, PUT, UPDATE, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization";
add_header Access-Control-Allow-Credentials "true";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
location ~ ^/(xxxx)$ {
if ($request_method = OPTIONS) {
rewrite ^(.*)$ / last;
}
}
I was looking through source 1.3.2, when using JSONP, the request is made by building a SCRIPT element dynamically, which gets past the browsers Same-domain policy. Naturally, you can't make a POST request using a SCRIPT element, the browser would fetch the result using GET.
As you are requesting a JSONP call, the SCRIPT element is not generated, because it only does this when the Type of AJAX call is set to GET.
http://dev.jquery.com/ticket/4690
We had a problem like this with ASP.Net. Our IIS was returning an Internal Server Error when trying to execute a jQuery $.post to get some html content due to PageHandlerFactory was restricted to respond only GET,HEAD,POST,DEBUG Verbs. So you can change that restriction adding the verb "OPTIONS" to the list or selecting "All Verbs"
You can modify that in your IIS Manager, selecting your website, then selecting Handler Mappings, double click in your PageHandlerFactory for *.apx files as you need (We use Integrated application pool with framework 4.0). Click on Request Restrictions, then go to Verbs Tabn and apply your modification.
Now our $.post request is working as expected :)
Check if your form's action URL includes the www part of the domain, while the original page you have opened is viewed without www.
Typically done for Canonical Urls..
I struggled for hours before stumbling upon this article and found the hint of Cross Domain.
I seems that if o.url = 'index.php' and this file exists is ok and returning a success message in the console. It returns an error if I use url:http://www.google.com
If doing a post request why not using directly the $.post method:
$.post("test.php", { func: "getNameAndTime" },
function(data){
alert(data.name); // John
console.log(data.time); // 2pm
}, "json");
It is so much simpler.
I have posted a clear example of how to solve this if control the server code of the domain you are POSTing to. This answer is touched on in this thread, but this more clearly explains it IMO.
How do I send a cross-domain POST request via JavaScript?
Solution to this is:
use dataType: json
add &callback=? to your url
this worked on calling Facebook API and with Firefox. Firebug is using GET instead of OPTIONS with the above conditions (both of them).
Another possibility to circumvent the problem is to use a proxy script. That method is described for example here
Can you try this without
contentType:application/x-www-form-urlencoded
Try adding the option:
dataType: "json"
function test_success(page,name,id,divname,str)
{
var dropdownIndex = document.getElementById(name).selectedIndex;
var dropdownValue = document.getElementById(name)[dropdownIndex].value;
var params='&'+id+'='+dropdownValue+'&'+str;
//makerequest_sp(url, params, divid1);
$.ajax({
url: page,
type: "post",
data: params,
// callback handler that will be called on success
success: function(response, textStatus, jqXHR){
// log a message to the console
document.getElementById(divname).innerHTML = response;
var retname = 'n_district';
var dropdownIndex = document.getElementById(retname).selectedIndex;
var dropdownValue = document.getElementById(retname)[dropdownIndex].value;
if(dropdownValue >0)
{
//alert(dropdownValue);
document.getElementById('inputname').value = dropdownValue;
}
else
{
document.getElementById('inputname').value = "00";
}
return;
url2=page2;
var params2 = parrams2+'&';
makerequest_sp(url2, params2, divid2);
}
});
}
I had a similar problem with trying to use the Facebook API.
The only contentType which didn't send the Preflighted request seemed to be just text/plain... not the rest of the parameters mentioned at mozilla here
Why is this the only browser which does this?
Why doesn't Facebook know and accept the preflight request?
FYI: The aforementioned Moz doc suggests X-Lori headers should trigger a Preflighted request ... it doesn't.
You need to do some work on server side. I see you are using PHP on server side, but solution for .NET web application is here:
Cannot set content-type to 'application/json' in jQuery.ajax
Do the same in PHP script and it will work. Simply: At first request browser is asking server if is allowed to send such data with such type and second request is the proper/allowed.
Try to add the following:
dataType: "json",
ContentType: "application/json",
data: JSON.stringify({"method":"getStates", "program":"EXPLORE"}),
I used a proxy url to solve a similar problem when I want to post data to my apache solr hosted in another server. (This may not be the perfect answer but it solves my problem.)
Follow this URL: Using Mode-Rewrite for proxying, I add this line to my httpd.conf:
RewriteRule ^solr/(.*)$ http://ip:8983/solr$1 [P]
Therefore, I can just post data to /solr instead of posting data to http://ip:8983/solr/*. Then it will be posting data in the same origin.
I already have this code handling well my cors situation in php:
header( 'Access-Control-Allow-Origin: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Headers: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Credentials: true' );
And it was working fine locally and remotely, but not for uploads when remote.
Something happen with apache/php OR my code, I didn't bother to search it, when you request OPTIONS it returns my header with cors rules but with 302 result. Therefore my browser doesn't recognise as an acceptable situation.
What I did, based on #Mark McDonald answer, is just put this code after my header:
if( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' )
{
header("HTTP/1.1 202 Accepted");
exit;
}
Now, when requesting OPTIONS it will just send the header and 202 result.
Please be advised:
JSONP supports only the GET request method.
*Send request by firefox:*
$.ajax({
type: 'POST',//<<===
contentType: 'application/json',
url: url,
dataType: "json"//<<=============
...
});
Above request send by OPTIONS(while ==>type: 'POST')!!!!
$.ajax({
type: 'POST',//<<===
contentType: 'application/json',
url: url,
dataType: "jsonp"//<<==============
...
});
But above request send by GET(while ==>type: 'POST')!!!!
When you are in "cross-domain communication" , pay attention and be careful.

Resources