Frontend Fundamental - AJAX, JSONP and CORS

Sometimes front-end noob like me will run into Same-Origin-Policy when dealing with ajax things. This article is a note for myself to get clear of what is it and how to deal with it when encounter.

Same-Origin-Policy

Same-Origin-Policy is a concept of preventing malicious script from getting sensitive data through [CCRF](Cross-site request forgery) and XSS. Under this policy, browser can access resources only if current document and target server are under the same domain.

The question is, why do we need to access data from other domain? The reason is that in web development, sometimes we store resources like html, css and js on different server/domain in order to reduce the burden of server. Another reason is that today’s web is RESTful, which means client sends request to server to get operation on resources.

AJAX

One way to access cross domain resource without violating SOP is using current server that sends resources to us as a proxy.

By doing this, fetching cross domain resources is our server’s job now since SOP only works on browser, not server.

client’s request <—> our server as proxy <—> another server

JSONP

SOP restricts where we can request resources we want, however, there are still exceptions.

These exceptions are script, img, link tags.

According to the draft of origin (or this question from stackoverflow if you are lazy like me), it says that because not wanting to make web development a painful thing, they decided not to treat every page as an independent origin, and thus they made some exceptions.

The spirit of this approach is that you still need the server holding resources you want implements a way to provide this service. We will see how to do this with following code. Let’s code!

title:"server 1"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var http = require("http");
var fs = require("fs");

var html = null;

fs.readFile("jsonp.html", function(err, file){
if(err){
throw err;
}
html = file.toString();
});

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/html"
});
res.end(html);
}).listen(3000);

console.log("domain 1 is running...");
title:"server 2"
1
2
3
4
5
6
7
8
9
10
var http = require("http");

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/plain"
});
res.end("Hello JSONP!");
}).listen(3001);

console.log("domain 2 is running...");
title:"jsonp.html"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<title>jsonp test</title>
</head>
<body>
<div id="text"></div>
<button id="button">ajax!</button>
<script>
function contentLoaded(){
var text = document.getElementById("text");
var button = document.getElementById("button");
button.addEventListener("click", function(e){
var ajax = new XMLHttpRequest();
ajax.addEventListener("readystatechange", function(){
if(ajax.readyState === 4){
if(ajax.status === 200){
text.appendChild(document.createTextNode(ajax.responseText));
}
}
});
ajax.open("GET", "http://localhost:3001");
ajax.send();
});
}

window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>
title:"Result in Chrome console"
1
XMLHttpRequest cannot load http://localhost:3001/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

Well, of course it doesn’t work since it violates SOP. Let’s correct this.

title:"server 2"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require("http");
var url = require("url");
var querystring = require("querystring");

http.createServer(function(req, res){
var queryTemp = url.parse(req.url).query;
var query = querystring.parse(queryTemp);

res.writeHeader(200, {
"content-type": "text/plain"
});
res.end(query.callback + "('Hello JSONP!')");
}).listen(3001);

console.log("domain 2 is running...");
title:"jsonp.html"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<title>jsonp test</title>
<script id="jsonp"></script>
</head>
<body>
<div id="text"></div>
<button id="button">jsonp!</button>
<script>
var text = document.getElementById("text");
var button = document.getElementById("button");
var jsonp = document.getElementById("jsonp");

function contentLoaded(){
button.addEventListener("click", function(e){
jsonp.src= "http://localhost:3001/?callback=changeText";
});
}

function changeText(string){
text.appendChild(document.createTextNode(string));
}

window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>

When you click jsonp! button text div should be appened with Hello JSONP! text.

CORS

Cross-Origin-Resource-Sharing, CORS, is a standard mechanism that let you access cross-domain resources with standard ajax request. You don’t need those sorcery ways to do the magic anymore.

By simply enabling the following header in server 2:

Access-Control-Allow-Origin: http://localhost:3000

Visit here if you want more headers for customization.

title:"server 2"
1
2
3
4
5
6
7
8
9
10
11
var http = require("http");

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/plain",
"access-control-allow-origin": "http://localhost:3000"
});
res.end("Hello CORS!");
}).listen(3001);

console.log("domain 2 is running...");
title:"index"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<title>CORS test</title>
</head>
<body>
<div id="text"></div>
<button id="button">CORS rocks!</button>
<script>
function contentLoaded(){
var text = document.getElementById("text");
var button = document.getElementById("button");
button.addEventListener("click", function(e){
var ajax = new XMLHttpRequest();
ajax.addEventListener("readystatechange", function(){
if(ajax.readyState === 4){
if(ajax.status === 200){
text.appendChild(document.createTextNode(ajax.responseText));
}
}
});
ajax.open("GET", "http://localhost:3001");
ajax.send();
});
}
window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>

We are now be able to acess cross-domain resources in standard ajax way without any errors, weeeeeeeeee!

References