跨站請求 CORS


XMLHttpRequest 未標準化之前,受限於同源策略,XMLHttpRequest 不能進行跨站請求,因而開發者想出了〈使用 JSONP 跨站請求〉中的方式。

在 XMLHttpRequest Level 1 的規範中,XMLHttpRequest 可以進行跨站請求,至於是否能接受回應,要看伺服端是否支援〈CORS 協議〉,以最簡單的 GET 為例,伺服端若在回應標頭中,包含了 Access-Control-Allow-Origin,而值是發出請求的來源網站,那麼 XMLHttpRequest 就可以接受回應。

例如,若在我的網站上有個 PHP 如下:

<?php    
    header('Access-Control-Allow-Origin: http://output.jsbin.com');
    header('Content-Type: application/json');
    switch($_GET['id']) {
        case '1':
            $result = '{"name":"Justin","age":35}';
            break;
        case '2':
            $result = '{"name":"momor","age":32}';
            break;
        case '3':
            $result = '{"name":"Hamimi","age":3}';
            break;
        default:
            $result = '{"name":"NOBODY","age":0}';
    }
    echo $result;
?>

其中 Access-Control-Allow-Originhttp://output.jsbin.com,因此當底下的網頁是處於該網域下時:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <body>
        ID:<input id="id">
        <button id="test">JSONP 測試</button>
        <span id="result"></span>
    </body>

<script type="text/javascript">

    document.getElementById('test').onclick = function() {
        let id = document.getElementById('id').value;

        let request = new XMLHttpRequest();

        request.onload = function(evt) {
            let req = evt.target;
            if(req.status === 200) {
                let person = req.response;
                document.getElementById('result').innerHTML 
                    = `${person.name}, ${person.age}`;
            }
        };

        request.responseType = 'json';
        request.open('GET', 
            `https://openhome.cc/Gossip/ECMAScript/samples/CORS-1.php?id=${id}`);
        request.send(null);
    };

</script>    

</body>
</html>

按我觀看執行結果

那麼 XMLHttpRequest 就可以直接取得回應,而程式流程不用任何改變,上面的範例可以輸入 ID(1、2、3 是有資料的)來取得我網站上提供的資料。

跨域請求實際上會發出,只是伺服端若沒有支援跨域的回應標頭,瀏覽器就不會讀取回應,然而,因為伺服端確實收到了請求,也就有可能在非預期的情況下,改變了伺服端的狀態。

為了避免這類問題,在某些情況下,像是設定了某些請求標頭、使用了 PUTDELETE 等方法(參考〈What requests use CORS?〉,瀏覽器在發出跨域請求前會用 OPTION 做預請求(preflight request),確定伺服器真的接受跨域請求,才真的發出程式中指定的請求。

在〈Server-Side Access Control (CORS)〉中提供有一些 CORS 協議的範例可供參考。