?
本文檔使用 php中文網(wǎng)手冊(cè) 發(fā)布
瀏覽器發(fā)送特定的HTTP標(biāo)頭,用于從內(nèi)部XMLHttpRequest
或獲取API發(fā)起的跨站點(diǎn)請(qǐng)求。它還希望看到具有跨站點(diǎn)響應(yīng)的特定HTTP標(biāo)頭。HTTP訪問(wèn)控制(CORS)文章中提供了這些頭文件的概述,包括啟動(dòng)請(qǐng)求和處理來(lái)自服務(wù)器的響應(yīng)的示例JavaScript代碼以及對(duì)每個(gè)頭文件的討論,應(yīng)該將其作為配套文章這個(gè)。本文介紹處理訪問(wèn)控制請(qǐng)求和制定訪問(wèn)控制響應(yīng)在PHP中。本文的目標(biāo)受眾是服務(wù)器程序員或管理員。盡管這里顯示的代碼示例是用PHP編寫(xiě)的,但類(lèi)似的概念適用于ASP.net,Perl,Python,Java等。通常,這些概念可以應(yīng)用于處理HTTP請(qǐng)求并動(dòng)態(tài)地制定HTTP響應(yīng)的任何服務(wù)器端編程環(huán)境。
這篇文章涵蓋了客戶端和服務(wù)器使用的HTTP標(biāo)頭,并且應(yīng)該被視為讀取前提條件。
后續(xù)章節(jié)中的PHP片段(以及對(duì)服務(wù)器的JavaScript調(diào)用)取自此處發(fā)布的工作代碼示例。這些將在實(shí)現(xiàn)跨站點(diǎn)的瀏覽器中工作XMLHttpRequest
。
簡(jiǎn)單訪問(wèn)控制請(qǐng)求在以下情況下啟動(dòng):
一個(gè)HTTP/1.1 GET
或者一個(gè)POST
被用作請(qǐng)求方法。在POST的情況下,Content-Type
請(qǐng)求體的是一種application/x-www-form-urlencoded
,multipart/form-data
或text/plain.
沒(méi)有自定義標(biāo)頭與HTTP請(qǐng)求一起發(fā)送(例如X-Modified
等)
在這種情況下,可以根據(jù)一些考慮將回復(fù)發(fā)送回去。
如果有問(wèn)題的資源被廣泛訪問(wèn)(就像GET訪問(wèn)的任何HTTP資源一樣),那么發(fā)送回頭Access-Control-Allow-Origin: *
就足夠了,除非資源需要諸如Cookie和HTTP認(rèn)證信息之類(lèi)的憑證。
如果資源應(yīng)該基于請(qǐng)求者域保持限制,或者如果資源需要使用憑據(jù)訪問(wèn)(或設(shè)置憑證),則Origin
可能需要按請(qǐng)求頭進(jìn)行過(guò)濾,或者至少回應(yīng)請(qǐng)求者Origin
(例如Access-Control-Allow-Origin:
http://arunranga.com
)。此外,Access-Control-Allow-Credentials: true
標(biāo)題將不得不發(fā)送。這將在后面的章節(jié)中討論。
簡(jiǎn)單訪問(wèn)控制請(qǐng)求部分顯示客戶端和服務(wù)器之間的頭部交換。這是處理簡(jiǎn)單請(qǐng)求的PHP代碼段:
<?php// We'll be granting access to only the arunranga.com domain // which we think is safe to access this resource as application/xmlif($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://arunranga.com'); header('Content-type: application/xml'); readfile('arunerDotNetResource.xml');} else { header('Content-Type: text/html'); echo "<html>"; echo "<head>"; echo " <title>Another Resource</title>"; echo "</head>"; echo "<body>", "<p>This resource behaves two-fold:"; echo "<ul>", "<li>If accessed from <code>http://arunranga.com</code> it returns an XML document</li>"; echo "<li>If accessed from any other origin including from simply typing in the URL into the browser's address bar,"; echo "you get this HTML document</li>", "</ul>", "</body>", "</html>";}?>
上面的內(nèi)容檢查Origin
瀏覽器發(fā)送的頭文件(通過(guò)$ _SERVER'HTTP_ORIGIN'獲得)是否匹配' http://arunranga.com '。如果是,它會(huì)返回Access-Control-Allow-Origin:
http://arunranga.com
。
預(yù)沖的訪問(wèn)控制請(qǐng)求在下列情況下發(fā)生:
以外的方法,GET
或POST
使用,或如果POST
使用具有Content-Type
比其它的一個(gè)application/x-www-form-urlencoded
,multipart/form-data
或text/plain
。舉例來(lái)說(shuō),如果Content-Type
所述的POST
體是application/xml
,請(qǐng)求預(yù)檢。
自定義標(biāo)題(例如X-PINGARUNER
)與請(qǐng)求一起發(fā)送。
Preflighted訪問(wèn)控制請(qǐng)求部分顯示客戶端和服務(wù)器之間的頭部交換。響應(yīng)預(yù)檢請(qǐng)求的服務(wù)器資源需要能夠做出以下決定:
基于Origin
(如果有的話)過(guò)濾。
響應(yīng)于OPTIONS
請(qǐng)求(這是預(yù)檢請(qǐng)求),包括與發(fā)送必要的值Access-Control-Allow-Methods
,Access-Control-Allow-Headers
(如果需要,為了任何附加頭的應(yīng)用程序的工作),并且,如果證書(shū)是必要的這一資源,Access-Control-Allow-Credentials
。
對(duì)實(shí)際請(qǐng)求的回應(yīng),包括處理POST
數(shù)據(jù)等。
以下是處理預(yù)發(fā)光請(qǐng)求的PHP示例:
<?php if($_SERVER['REQUEST_METHOD'] == "GET") { header('Content-Type: text/plain'); echo "This HTTP resource is designed to handle POSTed XML input"; echo "from arunranga.com and not be retrieved with GET"; } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // Tell the Client we support invocations from arunranga.com and // that this preflight holds good for only 20 days if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://arunranga.com'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Headers: X-PINGARUNER'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); //exit(0); } else { header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; }} elseif($_SERVER['REQUEST_METHOD'] == "POST") { // Handle POST by first getting the XML POST blob, // and then doing something to it, and then sending results to the client if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { $postData = file_get_contents('php://input'); $document = simplexml_load_string($postData); // do something with POST data $ping = $_SERVER['HTTP_X_PINGARUNER']; header('Access-Control-Allow-Origin: http://arunranga.com'); header('Content-Type: text/plain'); echo // some string response after processing } else { die("POSTing Only Allowed from arunranga.com"); }} else { die("No Other Methods Allowed");}?>
請(qǐng)注意,為了響應(yīng)OPTIONS
預(yù)檢和POST
數(shù)據(jù),將返回相應(yīng)的標(biāo)題。因此一個(gè)資源處理預(yù)檢和實(shí)際請(qǐng)求。在對(duì)OPTIONS
請(qǐng)求的響應(yīng)中,服務(wù)器通知客戶端實(shí)際的請(qǐng)求確實(shí)可以用該POST
方法進(jìn)行,并且諸如頭部字段X-PINGARUNER
可以與實(shí)際請(qǐng)求一起發(fā)送。這個(gè)例子可以在這里看到。
憑證訪問(wèn)控制請(qǐng)求 - 即伴隨有Cookie或HTTP身份驗(yàn)證信息的請(qǐng)求(并且期望Cookie與響應(yīng)一起發(fā)送) - 可以是簡(jiǎn)單或預(yù)檢,具體取決于所使用的請(qǐng)求方法。
在簡(jiǎn)單請(qǐng)求方案中,請(qǐng)求將以Cookie發(fā)送(例如,如果withCredentials
標(biāo)志設(shè)置為開(kāi)啟XMLHttpRequest
)。如果服務(wù)器響應(yīng)Access-Control-Allow-Credentials: true
附加的憑證響應(yīng),則響應(yīng)被客戶端接受并暴露給Web內(nèi)容。在預(yù)檢請(qǐng)求,服務(wù)器可以響應(yīng)Access-Control-Allow-Credentials: true
的OPTIONS
請(qǐng)求。
以下是一些處理憑證請(qǐng)求的PHP:
<?phpif($_SERVER['REQUEST_METHOD'] == "GET") { header('Access-Control-Allow-Origin: http://arunranga.com'); header('Access-Control-Allow-Credentials: true'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Content-Type: text/plain'); // First See if There Is a Cookie if (!isset($_COOKIE["pageAccess"])) { setcookie("pageAccess", 1, time()+2592000); echo 'I do not know you or anyone like you so I am going to'; echo 'mark you with a Cookie :-)'; } else { $accesses = $_COOKIE['pageAccess']; setcookie('pageAccess', ++$accesses, time()+2592000); echo 'Hello -- I know you or something a lot like you!'; echo 'You have been to ', $_SERVER['SERVER_NAME'], '; echo 'at least ', $accesses-1, ' time(s) before!'; } } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // Tell the Client this preflight holds good for only 20 days if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://arunranga.com'); header('Access-Control-Allow-Methods: GET, OPTIONS'); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); } else { header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; }} else { die("This HTTP Resource can ONLY be accessed with GET or OPTIONS");}?>
請(qǐng)注意,對(duì)于有證書(shū)請(qǐng)求的情況,Access-Control-Allow-Origin:
標(biāo)頭不得有通配符值“*”。它必須提到一個(gè)有效的原始域。上面的例子可以看到在這里運(yùn)行。
一個(gè)有用的技巧是使用Apache重寫(xiě),環(huán)境變量和標(biāo)頭來(lái)應(yīng)用于Access-Control-Allow-*
某些URI。例如,這對(duì)于將GET /api(.*).json
請(qǐng)求的跨請(qǐng)求限制為沒(méi)有憑證的請(qǐng)求很有用:
RewriteRule ^/api(.*)\.json$ /api$1.json [CORS=True]Header set Access-Control-Allow-Origin "*" env=CORS Header set Access-Control-Allow-Methods "GET" env=CORS Header set Access-Control-Allow-Credentials "false" env=CORS
Examples of Access Control in Action
HTTP Access Control covering the HTTP headers
XMLHttpRequest
Fetch API