原文首發(fā)于 同源策略那些事兒锁荔。
Tips
在本文中锌畸,域
和源
指代的都是 origin泌辫,換著講純粹是出于通用的習(xí)慣随夸。另外,出于學(xué)習(xí)的目的且為了避免瀏覽器的差異導(dǎo)致的麻煩震放,請(qǐng)?jiān)?Chrome 下運(yùn)行以下所有客戶端的代碼宾毒。
重現(xiàn)
我們先來(lái)寫個(gè)用 ajax 提交表單的小小小的 demo,這畢竟太常見(jiàn)了殿遂。
/Test/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>index</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
xhr.open('post', 'http://127.0.0.1/Test/index.php', true);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
document.write(xhr.responseText);
}
};
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('username=yang');
</script>
</body>
</html>
/Test/index.php
<?php
echo $_POST['username'];
?>
Tips
請(qǐng)將上述代碼放在本地服務(wù)器中運(yùn)行诈铛。
不出意外,你會(huì)得到以下大禮包:
XMLHttpRequest cannot load http://127.0.0.1/Test/index.php. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
此刻墨礁,有的人微微一笑幢竹,有的人一臉懵逼。
怎么回事恩静?
之所以會(huì)出現(xiàn)這個(gè)問(wèn)題焕毫,是因?yàn)闉g覽器有同源策略 (Same-origin policy) 的限制,一個(gè)域 (origin) 的腳本驶乾,在未經(jīng)允許的情況下邑飒,不得通過(guò) DOM 讀取另一個(gè)域的文檔 (document) 的內(nèi)容或?qū)傩浴?/p>
同源策略在 Web 應(yīng)用安全中扮演著重要的角色,它能保護(hù)一個(gè)網(wǎng)站的敏感信息轻掩,防止惡意腳本的竊取幸乒。同源策略中的同源
,指的是協(xié)議
唇牧、host
罕扎、端口
相同。同源下的文檔內(nèi)容及屬性可以共享崖蜜,不同源下的文檔內(nèi)容及屬性在未經(jīng)允許時(shí)不可以直接獲取洲劣。
Tips
同源中的host
可以為主機(jī)名 (hostname) 或 IP 地址战惊。
如果有一個(gè)地址為:http://example.com
茎刚,則:
http://example.com/test/a.html # 同源
https://example.com # 不同源互例,協(xié)議不同
http://www.example.com # 不同源武福,host 不同
http://example.com:8080 # 不同源伍纫,端口不同
規(guī)避同源策略的方法
然而我們有些時(shí)候是需要在不同源的地址間進(jìn)行通信的腻扇,有以下的方法可以用來(lái)規(guī)避同源策略债热。
document.domain
- 適用情況:不同的窗口 (window) 或 內(nèi)斂框架元素 <iframe> 之間互相訪問(wèn)文檔內(nèi)容,包括 cookie
- 實(shí)現(xiàn)前提:
- 協(xié)議幼苛、端口相同
- 當(dāng)前域名 (domain name) 的父域 (superdomain) 相同
- 其中一個(gè)窗口或 iframe 能得到另一個(gè)窗口或 iframe 的引用
Tips
如果兩個(gè)地址為one.example.com
和two.example.com
窒篱,則它們的父域?yàn)?code>example.com。注意上面所述父域不能為頂級(jí)域名 (top-level domain)舶沿,或者說(shuō)不能為一級(jí)域名 (first-level domain)墙杯。關(guān)于域名分級(jí),詳見(jiàn)domain name space括荡。
無(wú)碼言*高镐。我們來(lái)試試。
/Test/main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main window</title>
</head>
<body>
<p>This is the main window</p>
<iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
<script>
document.domain = 'w1.localhost';
</script>
</body>
</html>
/Test/iframe.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe window</title>
</head>
<body>
<p>This is the iframe window</p>
<button id="btn">Close this iframe</button>
<script>
document.domain = 'w1.localhost';
const btn = document.querySelector('#btn');
const parent = window.parent.document;
const frame = parent.querySelector('#child-iframe');
btn.onclick = () => {
parent.body.removeChild(frame);
};
</script>
</body>
</html>
Tips
跨域通信中畸冲,有些窗口屬性是允許跨域訪問(wèn)的嫉髓,詳見(jiàn)這里。
Tips
注意localhost
是預(yù)留的頂級(jí)域名邑闲,不可以把它設(shè)置為document.domain
算行。
訪問(wèn)http://w2.w1.localhost/Test/main.html
,點(diǎn)擊按鈕苫耸,iframe 窗口消失了州邢,再注釋掉任何一個(gè) html 文件的 document.domain = 'w1.localhost'
看看,emmmm..
另外褪子,我們可以通過(guò)在服務(wù)器中如下設(shè)置Set-Cookie
頭來(lái)實(shí)現(xiàn)在多個(gè)擁有相同父域的子域名間共享 cookie量淌。
<?php
# 假設(shè)發(fā)起請(qǐng)求的域與此域是同域
# 指定了域名的話就相當(dāng)于包含了所有子域名,所有子域名和此父域名都可以共享 cookie
setcookie('username', 'Sam Yang', time() + 24 * 3600, '/', "w1.localhost");
# setcookie('user', 'Sam Yang', time() + 24 * 3600, '/'); // 不指定則默認(rèn)當(dāng)前域名褐筛,cookie 不可被子域名共享
?>
然后你在客戶端的所有子域名下的頁(yè)面都可以通過(guò)document.cookie
獲得共享的 cookie类少。
跨文檔消息傳遞 (cross-document messaging)
- 適用情況:不同的窗口 (window) 或 內(nèi)斂框架元素 <iframe> 之間跨域傳遞數(shù)據(jù)
- 實(shí)現(xiàn)前提:瀏覽器支持 HTML5 的
window.postMessage
通過(guò) HTML5 提供的window.postMessage
方法,可以讓一個(gè)頁(yè)面的腳本渔扎,傳遞數(shù)據(jù)給另一個(gè)頁(yè)面的腳本硫狞,而無(wú)需理會(huì)腳本所在的頁(yè)面是否跨域。
這個(gè)方法的主要語(yǔ)法是這樣的:
otherWindow.postMessage(message, targetOrigin)
- otherWindow - 另一個(gè)窗口的引用晃痴,換言之残吩,是接收數(shù)據(jù)的窗口的引用
- message - 發(fā)送給其他窗口的數(shù)據(jù),數(shù)據(jù)在發(fā)送前會(huì)被自動(dòng)序列化倘核,而且數(shù)據(jù)類型不受限制
-
targetOrigin -
otherWindow
的源 (origin)泣侮,可以是字符串*
(可以發(fā)送給任何源)或一個(gè) URI,注意只有當(dāng)這里指定的targetOrigin
的值和想要接收數(shù)據(jù)的窗口的源 (origin) 完全匹配紧唱,window.postMessage
觸發(fā)的事件才會(huì)被發(fā)送
接收數(shù)據(jù)的窗口可以監(jiān)聽(tīng)message
事件活尊,這個(gè)事件接收到的數(shù)據(jù)參數(shù)包含三個(gè)重要屬性:
- data - 發(fā)送數(shù)據(jù)的窗口傳來(lái)的數(shù)據(jù)對(duì)象
- origin - 發(fā)送數(shù)據(jù)的窗口的源 (origin)
- source - 發(fā)送數(shù)據(jù)的窗口的引用隶校,可以用這個(gè)屬性建立兩個(gè)非同源窗口間的雙向通信
我們來(lái)用一下:
/Test/main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main window</title>
</head>
<body>
<p>This is the main window</p>
<iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
<script>
const iframe = document.querySelector('#child-iframe');
const iframeWin = iframe.contentWindow; // 獲得 iframe 元素的窗口
iframe.onload = () => { // 等待 iframe 窗口完全加載完再發(fā)送消息
iframeWin.postMessage('hello, my friend', 'http://w3.w1.localhost');
};
</script>
</body>
</html>
/Test/iframe.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe window</title>
</head>
<body>
<p>This is the iframe window</p>
<script>
window.onmessage = (event) => {
if(event.origin !== 'http://w2.w1.localhost') return;
document.write(event.data);
};
</script>
</body>
</html>
訪問(wèn)http://w2.w1.localhost/Test/main.html
,可以看到子窗口已經(jīng)接收到了父窗口傳來(lái)的數(shù)據(jù)蛹锰,你還可以嘗試傳輸其他更復(fù)雜的數(shù)據(jù)形式深胳。
Tips
這個(gè)方法雖然很棒,但是需要注意以下幾點(diǎn)安全問(wèn)題铜犬,否則你的站點(diǎn)可能被爆得體無(wú)完膚舞终。
- 不希望從其他站點(diǎn)接收數(shù)據(jù)時(shí),不要設(shè)置任何
message
事件的監(jiān)聽(tīng)器- 希望從其他站點(diǎn)接收數(shù)據(jù)時(shí)癣猾,接收方務(wù)必使用
origin
屬性 (有必要的話再加上source
屬性) 驗(yàn)證發(fā)送者的身份敛劝,避免惡意代碼的攻擊,可保平安- 發(fā)送方應(yīng)總是指定精確的接收方源纷宇,即
targetOrigin
屬性夸盟,而不是*
,因?yàn)楹笳呖梢宰屸嵉恼军c(diǎn)惡意改變你發(fā)送的數(shù)據(jù)的相關(guān)屬性進(jìn)而攔截你的數(shù)據(jù)
JSONP (JSON with padding)
- 適用情況:客戶端與服務(wù)器端的跨域通信
- 實(shí)現(xiàn)前提:無(wú)
Tips
由于內(nèi)在的風(fēng)險(xiǎn)呐粘,JSONP 正逐漸被 CORS (見(jiàn)下文) 取代满俗,此處僅出于了解的目的講解此技巧,你可以選擇跳過(guò)這個(gè)部分作岖。
直譯這個(gè)東東唆垃,就是“填充的 JSON”,這是跟 Ajax 一樣的老爺爺了痘儡,不同的是辕万,前者要退休了。
這項(xiàng)技術(shù)之所以出現(xiàn)沉删,是因?yàn)?code><script src="..."></script>標(biāo)簽不受同源策略的限制渐尿。利用<script>
元素,頁(yè)面可以向服務(wù)器端請(qǐng)求 JSON 數(shù)據(jù)矾瑰,服務(wù)器端收到請(qǐng)求后砖茸,將 JSON 數(shù)據(jù)傳傳入一個(gè)指定名字的回調(diào)函數(shù)里再傳回給客戶端,這種使用模式就是所謂的 JSONP殴穴。
/Test/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>index</title>
</head>
<body>
<script>
function addScriptTag(src) {
const script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
}
window.onload = () => {
addScriptTag('http://127.0.0.1/Test/index.php?callback=sayHello'); // 這里的 `callback` 換成其他名字也可以
};
function sayHello(data) { // 瀏覽器會(huì)自動(dòng)解析得到的 JSON 數(shù)據(jù)凉夯,無(wú)需手動(dòng)解析
document.body.append(`Hello, ${data.username}`);
}
</script>
</body>
</html>
/Test/index.php
<?php
$callback = $_GET['callback'];
$data = array(
'username' => 'samyang'
);
$result = json_encode($data);
echo "{$callback}({$result})";
?>
訪問(wèn)http://localhost/Test/index.html
。
當(dāng)然啦采幌,JSONP 是很容易遭到跨站請(qǐng)求偽造攻擊的劲够,所以你懂的。
WebSocket
- 適用情況:實(shí)時(shí)通信 (無(wú)論是否跨域)
- 實(shí)現(xiàn)前提:瀏覽器和服務(wù)器支持 HTML5 的 WebSocket
WebSocket 是一種基于 ws(非加密) / wss(加密)
協(xié)議的技術(shù)休傍,使用這種技術(shù)可以建立客戶端和服務(wù)器端雙向且持續(xù)的通信連接征绎,并且這不受同源策略限制。但是磨取,一旦你使用了 WebSocket 的 URI人柿,請(qǐng)求頭中就會(huì)加入`Origin字段柴墩,指明請(qǐng)求連接的腳本所在的源 (origin),服務(wù)器用這個(gè)字段來(lái)檢驗(yàn)跨站請(qǐng)求是否安全凫岖。
這項(xiàng)技術(shù)仍然不穩(wěn)定拐邪,但有一些成熟的相應(yīng)實(shí)現(xiàn),如 Socket.IO隘截,有興趣可以參見(jiàn)我寫的 Socket.IO 的教程。
這項(xiàng)技術(shù)主要用于實(shí)時(shí)通信汹胃,此處不做進(jìn)一步詳述婶芭,想進(jìn)一步探索,見(jiàn) WebSocket着饥。
跨域資源共享 (Cross-Origin Resource Sharing, CORS)
好了犀农,請(qǐng)出我們的大 boss。與 JSONP 的只支持 GET 請(qǐng)求相比宰掉,CORS 支持所有類型的 HTTP 請(qǐng)求呵哨。
- 適用情況:客戶端與服務(wù)器端的跨域通信
- 實(shí)現(xiàn)前提:客戶端和服務(wù)器端同時(shí)支持 CORS
ajax 受到同源策略的限制,使用 ajax 技術(shù)時(shí)轨奄,在未經(jīng)允許的情況下孟害,如果跨域請(qǐng)求發(fā)出給了服務(wù)器端并返回了數(shù)據(jù) (視瀏覽器情況而定,有的在發(fā)出時(shí)即攔截)挪拟,則客戶端無(wú)法讀取服務(wù)器端返回的數(shù)據(jù)挨务。CORS 允許服務(wù)器端進(jìn)行跨域訪問(wèn)控制,從而使跨域數(shù)據(jù)傳輸?shù)靡园踩M(jìn)行玉组。
我們首先來(lái)了解下為了支持 CORS谎柄,增加了哪些 HTTP 首部字段。
HTTP 請(qǐng)求首部字段
Tips
這些請(qǐng)求字段無(wú)需手動(dòng)設(shè)置惯雳,當(dāng)你使用 XMLHttpRequest 發(fā)起跨域請(qǐng)求時(shí)朝巫,瀏覽器已自動(dòng)幫你設(shè)置好了它們。
Origin
Origin: <origin>
表明發(fā)起請(qǐng)求的源 (origin)石景,這是一個(gè) URI劈猿,不包含任何路徑信息,只是服務(wù)器的名字鸵钝。
Access-Control-Request-Method
Access-Control-Request-Method: <method>
這個(gè)字段用于預(yù)檢請(qǐng)求 (下文會(huì)講)糙臼,其作用是將實(shí)際請(qǐng)求所使用的 HTTP 方法告訴服務(wù)器。
Access-Control-Request-Headers
Access-Control-Request-Headers: <field-name>[, <field-name>]*
這個(gè)字段用于預(yù)檢請(qǐng)求恩商,其作用是將實(shí)際請(qǐng)求所攜帶的自定義首部字段告訴服務(wù)器变逃。
HTTP 響應(yīng)首部字段
Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin> | *
指定可以訪問(wèn)當(dāng)前資源的外源 (origin),可以為不含路徑信息的一個(gè) URI怠堪,也可以是通配符*
揽乱。后者表示當(dāng)前資源可以被任何外源訪問(wèn)名眉,即允許來(lái)自所有域的請(qǐng)求。注意凰棉,當(dāng)請(qǐng)求攜帶有身份憑證時(shí) (下文講)损拢,服務(wù)器端不可以指定該值為通配符。
Access-Control-Expose-Headers
用 XMLHttpRequest 對(duì)象的 getResponseHeader() 方法可以獲取一些基本的響應(yīng)頭撒犀,當(dāng)想要獲取一些額外的響應(yīng)頭時(shí)福压,可以用這個(gè)字段指定。
Access-Control-Max-Age
Access-Control-Max-Age: <delta-seconds>
指定預(yù)檢請(qǐng)求的結(jié)果能被緩存多少秒或舞。在這個(gè)緩存時(shí)間內(nèi)荆姆,瀏覽器無(wú)須為同一請(qǐng)求再次發(fā)起預(yù)檢請(qǐng)求。
Access-Control-Allow-Methods
Access-Control-Allow-Methods: <method>[, <method>]*
這個(gè)字段用于預(yù)檢請(qǐng)求響應(yīng)映凳,其指明了實(shí)際請(qǐng)求時(shí)所允許使用的 HTTP 方法胆筒。
Access-Control-Allow-Headers
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
這個(gè)字段用于預(yù)檢請(qǐng)求響應(yīng),其指明了實(shí)際請(qǐng)求時(shí)允許攜帶的自定義首部字段诈豌。
Access-Control-Allow-Credentials
講這個(gè)字段前仆救,我們先講下 XMLHttpRequest 對(duì)象的 withCredentials 屬性來(lái)用,withCredentials 設(shè)置為true
時(shí)矫渔,身份憑證 (cookies彤蔽、HTTP 認(rèn)證信息等) 就會(huì)被瀏覽器包含在跨域請(qǐng)求中,設(shè)置為false
則排除在跨域請(qǐng)求之外并且瀏覽器忽略響應(yīng)中設(shè)置 cookie 的字段蚌斩,這個(gè)屬性默認(rèn)為false
铆惑,也就是說(shuō)一般而言,在跨域請(qǐng)求中送膳,瀏覽器不會(huì)發(fā)送身份憑證信息员魏。注意,在同源請(qǐng)求中叠聋,設(shè)置這個(gè)屬性沒(méi)有任何影響撕阎。
Access-Control-Allow-Credentials 字段指定了當(dāng)客戶端設(shè)置了 withCredentials 為true
時(shí)是否允許瀏覽器讀取響應(yīng)體 (response) 的內(nèi)容,為true
時(shí)表示允許碌补。當(dāng)此字段作為預(yù)檢請(qǐng)求中的響應(yīng)頭的一部分時(shí)虏束,它指示的是實(shí)際請(qǐng)求是否可以使用身份憑證 (credentials)。
當(dāng)允許攜帶身份憑證時(shí)厦章,請(qǐng)求頭中包含了可能含有隱私信息的 cookie 數(shù)據(jù)镇匀,所以服務(wù)器端不得設(shè)置 Access-Control-Allow-Origin 的值為*
,只能設(shè)置為準(zhǔn)確的域 (origin)袜啃。
Tips
雖然 CORS 允許跨域請(qǐng)求汗侵,但是 cookie 仍然受限于瀏覽器的同源策略,這意味著除了使用前文所講的document.domain
方法外,只有來(lái)自同一個(gè)源的頁(yè)面可以讀寫這個(gè)源的 cookie晰韵,你無(wú)法通過(guò) JavaScript 讀寫跨域的 cookie发乔。設(shè)置withCredentials
為true
只能讓你把跨域請(qǐng)求的那個(gè)服務(wù)器端設(shè)置在客戶端的 cookie 發(fā)送回給服務(wù)器端,不能讓你把客戶端設(shè)置的 cookie 發(fā)送給服務(wù)器端雪猪。詳情見(jiàn)這里栏尚。
Tips
當(dāng)服務(wù)器端設(shè)置Set-Cookie
字段時(shí),如果設(shè)置的域名 (domain) 不包含服務(wù)器的地址只恨,那么設(shè)置的這個(gè) cookie 就會(huì)被用戶代理拒絕保存译仗。比如說(shuō)你服務(wù)器端的地址為http://w1.localhost
,下面設(shè)置的 cookie 就會(huì)被用戶代理拒絕保存:setcookie('user', 'Sam Yang', time() + 24 * 3600, '/', "w2.localhost");
詳情見(jiàn) Invalid domains官觅。
簡(jiǎn)單請(qǐng)求 (simple request)
不會(huì)觸發(fā) CORS 預(yù)檢請(qǐng)求 (下文會(huì)提到) 的請(qǐng)求即為簡(jiǎn)單請(qǐng)求古劲,具體來(lái)說(shuō),就是同時(shí)滿足下列條件的請(qǐng)求:
- 使用下列請(qǐng)求方法之一:
- GET
- HEAD
- POST
- 除了用戶代理自動(dòng)設(shè)置的頭部字段缰猴,只允許手動(dòng)設(shè)置以下集合中的首部字段:
- Accept
- Accept-Language
- Content-Language
- Content-Type (值為下面三種之一)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- 請(qǐng)求時(shí)沒(méi)有在任何 XMLHttpRequestUpload 對(duì)象上注冊(cè)事件監(jiān)聽(tīng)器
- 沒(méi)有在請(qǐng)求中使用 ReadableStream 對(duì)象
/Test/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>index</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
xhr.open('get', 'http://w1.localhost/Test/index.php', true);
xhr.send();
</script>
</body>
</html>
/Test/index.php
<?php
header('Access-Control-Allow-Origin: *');
?>
訪問(wèn)http://w2.localhost/Test/main.html
,下面分別是請(qǐng)求頭和響應(yīng)頭:
# 請(qǐng)求頭
GET /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Origin: http://w2.localhost # 注意這個(gè)字段
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J
# 響應(yīng)頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: * # 注意這個(gè)字段
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
根據(jù)上述條件疤剑,這是一個(gè)簡(jiǎn)單請(qǐng)求滑绒,我們使用 Origin
和 Access-Control-Allow-Origin
就能完成最簡(jiǎn)單的訪問(wèn)控制芽狗。
預(yù)檢請(qǐng)求 (preflight request) 和 實(shí)際請(qǐng)求 (actual request)
為了避免那些可能對(duì)服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請(qǐng)求方法晤揣,瀏覽器必須首先先使用 OPTIONS 方法發(fā)起一個(gè)預(yù)檢請(qǐng)求,從而獲知服務(wù)器端是否允許該跨域請(qǐng)求俏讹,獲得允許之后才發(fā)起實(shí)際請(qǐng)求弯菊。滿足下述任一條件時(shí)纵势,即應(yīng)首先發(fā)送預(yù)檢請(qǐng)求:
- 使用下列請(qǐng)求方法之一
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 除了用戶代理自動(dòng)設(shè)置的頭部字段,手動(dòng)設(shè)置了以下集合 之外 的首部字段:
- Accept
- Accept-Language
- Content-Language
- Content-Type (值為下面三種之一)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- 請(qǐng)求時(shí)在一個(gè) XMLHttpRequestUpload 對(duì)象上注冊(cè)了一個(gè)至多個(gè)事件監(jiān)聽(tīng)器
- 在請(qǐng)求中使用 ReadableStream 對(duì)象
/Test/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>index</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
const body = '<?xml version="1.0"?><person><name>Arun</name></person>';
xhr.open('post', 'http://w1.localhost/Test/index.php', true);
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.send(body);
</script>
</body>
</html>
/Test/index.php
<?php
header('Access-Control-Allow-Origin: http://w2.localhost');
header('Access-Control-Allow-Headers: X-PINGOTHER, Content-Type');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 86400');
?>
訪問(wèn)http://w2.localhost/Test/main.html
管钳,下面分別是預(yù)檢請(qǐng)求頭和響應(yīng)頭:
# 請(qǐng)求頭
OPTIONS /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST # 關(guān)注它
Origin: http://w2.localhost # 關(guān)注它
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Access-Control-Request-Headers: content-type,x-pingother # 關(guān)注它
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J
# 響應(yīng)頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: http://w2.localhost # 關(guān)注它
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type # 關(guān)注它
Access-Control-Allow-Methods: POST, GET, OPTIONS # 關(guān)注它
Access-Control-Max-Age: 86400 # 關(guān)注它
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
預(yù)檢請(qǐng)求之后的實(shí)際請(qǐng)求頭和響應(yīng)頭:
# 請(qǐng)求頭
POST /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Content-Length: 55
Pragma: no-cache
Cache-Control: no-cache
X-PINGOTHER: pingpong # 關(guān)注它
Origin: http://w2.localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type: application/xml # 關(guān)注它
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J
# 響應(yīng)頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: http://w2.localhost
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 86400
Content-Length: 0
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
一些小伎倆
之所以叫小伎倆钦铁,是因?yàn)檫@里所講的方法都只是在小數(shù)據(jù)量的跨域通信中比較方便,大數(shù)據(jù)量則不宜使用才漆。
- 適用情況:小數(shù)據(jù)量的跨域通信
Tips
如果對(duì) URL 的結(jié)構(gòu)不甚清晰牛曹,可以參見(jiàn) URL。
片段標(biāo)識(shí)符 (fragment identifier)
- 實(shí)現(xiàn)前提:無(wú)醇滥。
片段標(biāo)識(shí)符是網(wǎng)址 URL 中#
符后面的部分黎比。我們可以這樣來(lái)使用之。
/Test/main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main window</title>
</head>
<body>
<p>This is the main window</p>
<iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
</body>
</html>
/Test/iframe.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe window</title>
</head>
<body>
<p>This is the iframe window</p>
<script>
window.onhashchange = () => {
document.write(`hello, ${window.location.hash.substr(1)}`);
};
</script>
</body>
</html>
訪問(wèn)http://w2.w1.localhost/Test/main.html
鸳玩,打開(kāi)控制臺(tái)阅虫,輸入:
document.querySelector('#child-iframe').src += '#samyang'
emmmm... 小窗口內(nèi)容變了!
但是呢不跟,這種方法是比較雞肋的颓帝,雖然#
有其他的用途 (見(jiàn) 這里),但它一般是用于定位頁(yè)面中某個(gè)部分的,如果頁(yè)面中有一些標(biāo)簽含有相同的片段標(biāo)識(shí)符躲履,便會(huì)產(chǎn)生一些意料之外的行為见间。再者,這種方法傳輸?shù)臄?shù)據(jù)量太少了工猜。
查詢字符串 (search string, aka, query string)
- 實(shí)現(xiàn)前提:無(wú)米诉。
這是另一種用于小數(shù)據(jù)量跨域通信的方法,也是我所常用的方法篷帅,也是相比于前者更推薦的做法史侣。查詢字符串是 URL 中?
及其后面但不包括#
及片段標(biāo)識(shí)符的部分。
/Test/main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main window</title>
</head>
<body>
<p>This is the main window</p>
<button>Click me</button>
<script>
document.querySelector('button').onclick = () => {
location.href = `http://w3.w1.localhost/Test/iframe.html?username=samyang`;
};
</script>
</body>
</html>
/Test/iframe.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Iframe window</title>
</head>
<body>
<p>This is the iframe window</p>
<button id="btn">Close this iframe</button>
<script>
window.onload = () => {
document.write(location.search.substr(1))
};
</script>
</body>
</html>
訪問(wèn)http://w2.w1.localhost/Test/main.html
魏身,點(diǎn)擊按鈕惊橱。
window.name
- 實(shí)現(xiàn)前提:跨域頁(yè)面在同一個(gè)窗口中。
這個(gè)屬性是屬于窗口的箭昵,只要窗口不變税朴,即使頁(yè)面變?yōu)椴煌颍@個(gè)屬性的值也不變家制。
/Test/main.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Main window</title>
</head>
<body>
<p>This is the main window</p>
<script>
window.name = 'samyang';
location.;
</script>
</body>
</html>
訪問(wèn)http://w2.w1.localhost/Test/main.html
正林,窗口跳到新頁(yè)面后打開(kāi)控制臺(tái)輸入window.name
并回車可以看到原頁(yè)面設(shè)置的值。
結(jié)尾
好了颤殴,大概是這么多了(其實(shí)還有很多)觅廓,如有疏漏請(qǐng)指出。