跨域AJAX總結

跨域是指不一樣協議、域名、端口下訪問js腳本。而當遇到跨域時,因爲瀏覽器中同源策略的安全限制,致使不能正常執行,報相似如下的錯誤:javascript

image

同源策略

什麼是同源策略

同源策略是指限制了腳本與不一樣源的資源交互,而當中的是以協議、域名和端口區分,如下狀況爲不一樣源:html

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不一樣源(域名不一樣)
http://v2.www.example.com/dir/other.html:不一樣源(域名不一樣)
http://www.example.com:81/dir/other.html:不一樣源(端口不一樣)
複製代碼

限制範圍

(1) Cookie、LocalStorage 和 IndexDB 沒法讀取。

(2) DOM 沒法得到。

(3) AJAX 請求不能發送。
複製代碼

AJAX

這裏只總結關於AJAX方面的跨域問題。同源政策規定,AJAX請求只能發給同源的網址,不然就報錯。而解決方案通常有JSONPCORSWebSocket前端

JSONP

JSONP是服務器與客戶端跨源通訊的經常使用方法,這裏咱們寫一個例子去探究JSONP的原理。java

JSONP的實現原理

首先,網頁經過添加一個<script>元素,向服務器請求JSON數據,這種作法不受同源政策限制。jquery

render.js:

const express = require('express');
const app = express();

app.get('/jsonp', (req, res) => {
    const viewPath = `${__dirname}/jsonp.html`;
    res.setHeader('Content-Type', 'text/html');
    res.sendFile(viewPath);
});

app.listen(8081);
console.log(`listen 8081....`);
複製代碼
jsonp.html:

<html>
    <body>
        <h1>jsonp test</h1>    
    </body>
    <script>
        function addScriptsTag (src) {
            var script = document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            script.src = src;
            document.body.appendChild(script);
        }

        window.onload = function () {
            console.log('onload');
            addScriptsTag('http://127.0.0.1:8080/ip?callback=foo');
        }

        function foo (data) {
            console.log(`Your public IP address is:${data.ip}`);
        }

    </script>
</html>
複製代碼

服務器收到請求後,將數據放在一個指定名字的回調函數裏傳回來。ios

const express = require('express');
const app = express();

app.get('/ip', (req, res) => {
    const callback = req.query.callback;
    res.send(`/**/ typeof ${callback} === 'function' && ${callback} ({"ip":"127.0.0.2"});`);
    // res.jsonp({ ip: '127.0.0.1' });// express自帶jsonp更方便
});

app.listen(8080);
console.log(`listen 8080....`);
複製代碼

最後前端收到返回的數據以下所示,瀏覽器就會執行該腳本,調用前面定義好的回調函數fooexpress

/**/ typeof foo === 'function' && foo ({"ip":"127.0.0.2"});
複製代碼

使用

能夠自行封裝方法或者找第三方庫去實現JSONP請求,好比jquery的jsonp方法。json

CORS

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。axios

CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息有時還會多出一次附加的請求,但用戶不會有感受。跨域

兩種請求

瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

只要同時知足如下兩大條件,就屬於簡單請求。

(1) 請求方法是如下三種方法之一:

- HEAD
- GET
- POST

(2)HTTP的頭信息不超出如下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
複製代碼

凡是不一樣時知足上面兩個條件,就屬於非簡單請求

簡單請求

輸入127.0.0.1:8081/cors,瀏覽器正常進行一次簡單的AJAX請求(使用axios):

render.js

const express = require('express');
const app = express();

app.get('/cors', (req, res) => {
    const viewPath = `${__dirname}/cros.html`;
    res.setHeader('Content-Type', 'text/html');
    res.sendFile(viewPath);
});

app.listen(8081);
console.log(`listen 8081....`);
複製代碼
cros.html:

<html>

<body>
    <h1>cors test</h1>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        axios({
                method: 'post',
                baseURL: 'http://127.0.0.1:8080/',
                url: '/user',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },// 默認是applictaion/json,爲非簡單請求,因此爲了測試需自定義
                data: {
                    firstName: 'Fred',
                    lastName: 'Flintstone'
                },
            })
            .then(function (response) {
                console.log(response);
            })
            .catch(function (error) {
                console.log(error);
            });
    </script>
</body>

</html>
複製代碼

服務器經過中間件形式加上頭部信息:

const express = require('express');
const app = express();

app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "http://127.0.0.1:8081");
    next();
});

app.post('/user', (req, res) => {
    res.send('user ok');
});

app.listen(8080);
console.log(`listen 8080....`);
複製代碼

這時候CORS就成功了,通訊過程的頭部信息:

請求頭:

POST http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 44
Accept: application/json, text/plain, */*
Origin: http://127.0.0.1:8081
...

{"firstName":"Fred","lastName":"Flintstone"}
複製代碼

響應頭:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
...

user ok
複製代碼

其中起關鍵做用的是OriginAccess-Control-Allow-OriginOrigin瀏覽器請求時會帶上,指明請求的來源。而響應頭的Access-Control-Allow-Origin指明可以跨域請求資源的容許網址,多個用逗號隔開,若是寫*代表任意網址均可以。

非簡單請求

當請求爲非簡單請求時,一次請求會分兩次請求,分別是預檢請求和正常的請求。

咱們如今把上面AJAX請求方法變爲PUT

預檢請求

請求就會先發一次預檢請求。請求頭以下:

OPTIONS http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Access-Control-Request-Method: PUT
Origin: http://127.0.0.1:8081
...
複製代碼

預檢請求的方法爲OPTIONS,代表請求時用來詢問的。Origin依然代表請求來源。而新增的請求字段Access-Control-Request-Method代表接下來的CORS請求會到什麼方法,可否使用。

這時服務器經過設置Access-Control-Allow-Methods來規定服務器支持的CORS請求方法:

res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE");
複製代碼

正確返回後,響應頭以下:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
Access-Control-Allow-Methods: PUT,POST,GET,DELETE
Allow: PUT
...
複製代碼
正常請求

正常請求的請求頭:

PUT http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 44
Accept: application/json, text/plain, */*
Origin: http://127.0.0.1:8081
...

{"firstName":"Fred","lastName":"Flintstone"}
複製代碼

迴應:

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
Access-Control-Allow-Methods: PUT,POST,GET,DELETE
...

user ok
複製代碼

和簡單請求同樣,請求寫到Origin,回包攜帶Access-Control-Allow-Origin

請求時支持攜帶Cookie

客戶端,XMLHttpRequest對象開啓withCredentials屬性。

JS寫法:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
複製代碼

axios開啓選項便可:

withCredentials: true,
複製代碼

服務端贊成容許發送Cookie,經過設置頭部:

res.header("Access-Control-Allow-Credentials",true);
複製代碼

其餘服務器設置頭

  • Access-Control-Request-Headers:該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,好比但願請求時使用application/json格式時,你就須要在服務器設置res.header('Access-Control-Allow-Headers', 'Content-Type');來支持自定義內容格式。

  • Access-Control-Max-Age: 該字段可選,用來指定本次預檢請求的有效期,單位爲秒

WebSocket

參考下面阮一峯老師的總結。

總結

JSONP只支持GET請求方式,客戶端和服務器經過第三庫來書寫也很方便,服務器改動也不多。CORS支持幾乎全部的請求方式,服務器支持只需設置一些頭部信息,而客戶端支持發送Cookie須要開啓一下選項(不多改動)。WebSocket是更適用於實時性、頻繁的請求。因此CORS更通用,開發時也更多使用這種方式。

參考

瀏覽器同源政策及其規避方法

跨域資源共享 CORS 詳解

趙先生的博客例子

相關文章
相關標籤/搜索