Why does this cross-domain request workaround work? - javascript

In this John Resig article, he's is dealing with a dictionary-sized list of words with javascript, and he's loading the content via ajax from a CDN.
The words are loaded in with newlines separating the words. Then he says cross domain fails:
There's a problem, though: We can't load our dictionary from a CDN!
Since the CDN is located on another server (or on another sub-domain,
as is the case here) we're at the mercy of the browser's cross-origin
policy prohibiting those types of requests. All is not lost though -
with a simple tweak to the dictionary file we can load it across
domains.
First, we replace all endlines in the dictionary file with a space.
Second, we wrap the entire line with a JSONP statement. Thus the final
result looks something like this:
dictLoaded('aah aahed aahing aahs aal... zyzzyvas zzz');
This allows us to do an Ajax request for the file and have it work as
would expected it to - while still benefiting from all the caching and
compression provided by the browser.
So, if I'm reading this correctly, simply adding his method dictLoaded('original content') around the original content alone causes the ajax request to not fail.
Is that (turning it into a function + param) really all it takes? and why does JSONP solve the problem of cross domain access restriction?

the <script> tags can load any JS file from anywhere (even cross domain). The nice thing that comes with it is that the code inside that script is also executed, therefore, a method of bypassing cross-domain restrictions.
The problem is, when the code gets executed, it's executed in the global scope. so having this code:
var test = 'foo'
will create a test variable in the global scope.
To mitigate this, you use enclose the reply in a function. This is the "P" in "JSONP" which means "padding". This encloses your reply in a function call.
So if your foreign script has:
myFunction({
test : 'foo'
});
It calls myFunction and passes an object with test key which has value foo. The receiving function would look like:
function myFunction(data){
//"data.test" is "foo"
}
Now we have successfully bypassed the cross-domain restriction. The essential parts needed are:
the receiving function (which can be dynamically created and discarded after use)
the "padded" JSON reply

Is that (turning it into a function + param) really all it takes?
Yes.
and why does that solve the problem of cross domain access restriction?
You should read about JSONP. The idea is that you can now include a <script> tag dynamically pointing to the resource instead of sending an AJAX request (which is prohibited). And since you have wrapped the contents with a function name, this function will be executed and passed as argument the JSON object. So all that's left for you is to define this function.

It is because of the JSONP statement that he added.
"JSON with padding" is a complement to the base JSON data format. It provides a method to request data from a server in a different domain, something prohibited by typical web browsers because of the Same origin policy.
This works via script element injection.
JSONP makes sense only when used with a script element. For each new JSONP request, the browser must add a new element, or reuse an existing one. The former option - adding a new script element - is done via dynamic DOM manipulation, and is known as script element injection. The element is injected into the HTML DOM, with the URL of the desired JSONP endpoint set as the "src" attribute. This dynamic script element injection is usually done by a javascript helper library. jQuery and other frameworks have jsonp helper functions; there are also standalone options.
Source: http://en.wikipedia.org/wiki/JSONP

Related

Autoloading invalid javascript files

I've created a script which autoloads javascript files based on the users location. Like any other loaders, I create a <script> element, set it's src attribute and add it to <head>. I then listen for onload and onreadystatechanged etc. events, then check for the presence of a value exposed by the file. This works.
The problem is, when supplied with an invalid cross origin url "http://fail_on_purpose", my ISP returns HTML (directing the browser to reload with a different address). I can catch this in the onload event, but it's too late. The HTML has already been injected on the page within the <script> tags. This then causes the browser (firefox at least) to issue a SyntaxError exception.
All I can do is remove the offending element from the DOM. But, I don't know if this is going to cause problems in other browsers, so I would prefer to not have the error at all.
What is the best way to check for a valid javascript file then inject it into the page?
Update:
Issuing a HEAD or GET request to check if the file exists/is valid, doesn't work due to the "Cross Origin" protection in the browser. I don't have access to the CDN, so that is an unfixable problem.
You could send a HEAD request and parse response headers to see if the page returns successfully, i.e. not with redirection, client error or server error response code. Any decently implemented server should set correct response code.
Ideally, you'd also check content-type to include application/javascript, if the server is set up to set it correctly.
If this matches, you know that the script exists, so you can then proceed with loading it the same as you do now.
Alternatively, you already get these headers with GET request, so you could do all that with a single request. But with incorrect request, you'll be waiting for all the payload before determining it's incorrect, which will take up more time and waste data transfer. The latter is something worth considering if you're expecting mobile users, you wouldn't want to waste data plans like that.
As Sumit suggests, you might load the script via an Ajax request, then check it somehow (e.g. because you know that it contains a particular string).
On success, just proceed as you did - append a <script> tag to the head or body element with the "src" attribute set. If caching is enabled for the URL, the browser won't do another HTTP request for that.
Using the Ajax-loaded JS code directly (by setting scriptElem.text instead of the "src" attribute) is another option, but not as good, since it is subject to CORS restrictions. See jQuery's DOMEval function, https://code.jquery.com/jquery-3.1.0.js (line 77)
Since you want to load the code from the Google servers, the "src"-attribute version is better.

js "scripts" folder name prepended to XMLHttpRequest calls

I have a javascript file named myscripts.js in the "scripts" folder of my webserver. It could be accessed with this:
http://www.example.com/scripts/myscripts.js
Within myscripts.js is a javascript function which makes a XMLHttpRequest call to somemethod.html of my website. Here is the calling code:
xmlhttp.open("GET","somemethod.html",false);
99% of the time everything works fine. But I am finding some browsers are prepending "scripts/" to the call. So the result is a call like this:
http://www.example.com/scripts/somemethod.html
when it should be this:
http://www.example.com/somemethod.html
This is a custom built webserver (i.e. I basically handle ALL requests).
Should my webserver be able to handle this? Or is this just some fluky browser that I should not worry about?
Should I not be using "relative" paths in the javascript? And instead use absolute calls in the java script? e.g.: instead of "somemethod.html" it should be coded like this:
xmlhttp.open("GET","http://www.example.com/somemethod.html",false);
It's absolutely fine (and overwhelmingly the standard of practice) to use relative paths in the JavaScript, just be aware of what they're relative to: The document in which you've included the JavaScript (not the JavaScript file.) You seem clear on this, but just emphasizing.
I've never seen a browser get this wrong. It's possible the requests you're seeing are from a poorly-written web crawler looking at the source of the JavaScript rather than doing something intelligent like figuring out where/how it's run.
Just for clarity, though, about the relative thing (more for lurkers than for you):
Given this structure:
foo.html
index.html
js/
script.js
In that structure, if you include script.js in index.html:
<script src="js/script.js"></script>
...then use code in that script file to do an XHR call, the call will be relative to index.html, not script.js, on a correctly-functioning browser.
I never use relative requests, I built my own url handing js code to build up and pass urls around with a 'toString' method to give me exactly the url I need.
Also as an aside, try not to use synchronous XHR calls anymore, ideally you should use async and call backs, it's a pain, but it's for the best.
client . open(method, url [, async = true [, username = null [, password = null]]])
Sets the request method, request URL, and synchronous flag.
Throws a "SyntaxError" exception if either method is not a valid HTTP method or url cannot be parsed.
Throws a "SecurityError" exception if method is a case-insensitive match for `CONNECT`, `TRACE` or `TRACK`.
Throws an "InvalidAccessError" exception if async is false, the JavaScript global environment is a document environment, and either the timeout attribute is not zero, the withCredentials attribute is true, or the responseType attribute is not the empty string.
source: http://xhr.spec.whatwg.org/#the-open%28%29-method

Using a javasscript closure that exist in an external site

I want to use a javascript file that exist in another host.
the file contain a closure on it
//http://ExternalHost.com/somefile.js
(function(){
//...........
//...........
return { x:1, y:2 };
})();
and I want to use the returned object of the closure.
My question is
1) how can I get that file?
2) how can I use the return value of closure?
I know we can use the returned value if the file likes below
//http://ExternalHost.com/somefile.js
window.returnedObject = (function(){
//...........
//...........
return { x:1, y:2 };
})();
but the problem is: window.returnedObject is global!
Well, let's analyze it.
somefile.js resides in another domain. So, you should request it from yourdomain.tld and it's on externalhost.com. You have many options:
Request it via a simple HTTP Get method, which is issued by putting a <script /> tag anywhere on your DOM. This way, browser gets the file, executes it on its arrival, and you have to way to hook into this process, unless you and external host both agree on some similar protocol like JSONP, which is of course for data, not for libraries. So, this option is not useful.
You use XMLHttpRequest to load the file either synchronously or asynchronously. But XMLHttpRequest is by design under strict control of same-origin policy. So, external host should let you use it via some HTTP Headers, like Access-Control-Allow-Origin. This of course is applicable to normal request too. However, when you use XMLHttpRequest, you have the chance to hook into the load of the content, and do something on it. However, in this case, browser compiles JavaScript and runs it before giving it to XMLHttpRequest (your ajax call). So, again no use.
Understand the true meaning and philosophy of closure in JavaScript. It's like private access modifier in object-oriented programming. Technically it's there to NOT ALLOW you to access it :D. This options works. But it only increases your knowledge, to not expect to use that closure.
So, I think you can't AMAIK.

JSONP and XMLHttpRequest question

Am trying to understand the same origin policy in browsers (and also Javascript newbie) and ran into the JSONP page on wikipedia. The How It Works section says -
Now, consider that it is possible to specify any URL, including a URL that returns JSON, as the src > attribute for a element. This means it is possible to retrieve JSON via a script element in > an HTML page.
However, a JSON document is not a JavaScript program. If it is to be evaluated by the browser in a element, the return value from the src URL must be executable JavaScript. In the JSONP usage pattern, the URL returns the dynamically-generated JSON, with a function call wrapped around it. This is the "padding" (or sometimes, "prefix") of JSONP.
My questions are -
So is XMLHTTPRequest() supposed to return only javascript or html? Can it not return a pure json document?
I thought the same origin policy does not apply to XMLHttpRequest() call. Why is there a need to inject a tag into the DOM to make a call to a third party server? Is that how all the advertising add-ons to sites call home to collect data?
At the end of it I did not understand JSONP at all. Can some one explain or refer me to a better explanation please?
Thanks,
- P
So is XMLHTTPRequest() supposed to return only javascript or html?
It can return any text you like (and maybe binary data, but I've never see that tried so I won't swear to it)
Can it not return a pure json document?
It can.
I thought the same origin policy does not apply to XMLHttpRequest() call.
The same origin policy most definitely does apply to XHR
Why is there a need to inject a tag into the DOM to make a call to a third party server?
The same origin policy is bypassed by loading a script (with embedded data) from another origin.
This is because you aren't reading a remote resource using JavaScript. You are executing some remote JavaScript which comes with embedded data.
At the end of it I did not understand JSONP at all. Can some one explain or refer me to a better explanation please?
JSON-P is just loading some JavaScript from another origin. That JavaScript consists of a single function call (to a function you define before adding the <script> element) with a single argument (a JS object or array literal).

Why wouldn't this jquery load work?

This is not loading the website that I wanted.
$('#example').load("http://www.example.com");
http://www.jsfiddle.net/JFdVv/
You can't load content from a domain other than the one you're on unless it's JSONP (JSON with a function wrapper)...you can't load plain HTML like you're trying, it's blocked for security reasons by the same origin policy.
As an aside, the reason you get an error with example_ajax_request inline in the page is that by default jsfiddle puts your JavaScript code in a wrapper...you need to have functions like that directly in the page (global functions, not scoped to a ready handler), notice the first drop down up top...it needs to be "no wrap" (either one), instead of "onDomReady".
If you really must load a page from different website, you can always use an <iframe> although this practice would be questionable to say the least.
Or, for a server-side solution, if you're using PHP, you can have a look at the PHP cURL library.

Categories

Resources