上週作了一個移動端表單提交的頁面,其中涉及到了跨域問題,想來也是慚愧,由於以前一直都沒有遇到過這個問題,所以都沒有深刻探索過,只是知道有哪幾種方式,此次終於借這個機會能夠把遺留的知識點補一補了。javascript
【基本思想】:使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功仍是失敗。html
【實現方式】:
瀏覽器在發送請求時,檢測到跨域時,會自動加上一個額外的 Origin 頭部,其中包含請求頁面的原信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。前端
Origin: http://www.nczonline.net
若是服務器認爲這個請求能夠接受,就在 Access-Control-Allow-Origin 頭部中回發相同的原信息(若是是公共資源,能夠回發「*」)。java
Access-Control-Allow-Origin: http://www.nczonline.net
若是沒有這個頭部,或者有着頭部但原信息不匹配,瀏覽器就會駁回請求。webpack
注意:默認狀況下,請求和響應都不包含 cookie 信息。git
爲了模擬跨域,在本身的本地起了2個服務,一個採用 webpack 充當靜態資源服務器(webpack 腳手架可參考:scaffoldsForFE),另外一個用 Node 搭建,充當接受請求的服務器,分別給兩個服務器分配了不一樣的端口號。github
Client:http://localhost:8000web
var oDiv = document.getElementById('content'); var xhr = new XMLHttpRequest(); xhr.onload = function() { // 響應接收完畢後將觸發 onload 事件 if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ oDiv.innerHTML = 'Request was success:' + xhr.responseText; console.log('Request was success:', xhr.responseText); } else { oDiv.innerHTML = "Request was unsuccessful: " + xhr.status; console.log("Request was unsuccessful: ", xhr.status); } } } xhr.open('get', 'http://localhost:8000', true); // 不跨域 // xhr.open('get', 'http://localhost:8888', true); // 跨域 xhr.send();
Server:http://localhost:8888跨域
var http = require('http'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('This is a server test page.'); res.end(); }).listen(8888);
當 Client 端向 http://localhost:8000 發請求時,不存在跨域,可以成功返回頁面信息,此時的頁面及其發請求的 Request Headers 以下:瀏覽器
當 Client 端向 http://localhost:8888 請求服務的時候,因爲存在跨域問題,沒法得到響應:
可是其請求的頭部自動帶上了 Origin 字段,並且因爲是默認狀況,沒有帶上 Cookie:
解決的方式是,在 Server 端響應的時候,在 Access-Control-Allow-Origin 頭部中回發相應的原信息:
var http = require('http'); http.createServer(function(req, res) { // 設置響應頭部 res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('This is a server test page.'); res.end(); }).listen(8888);
再從新請求,成功得到響應信息,且請求沒有帶上 Cookie:
因爲在跨域時,默認狀況下是不容許客戶端向服務器發送請求時帶上 Cookie 的,那怎樣才能帶上 Cookie 呢?須要同時在客戶端和服務端同時設置相應字段:
(1)客戶端在請求中打開 withCredentials
屬性,指定某個請求應該發送憑據:
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
(2)服務端在響應頭部中添加 Access-Control-Allow-Credentials
字段,且設爲 true,代表服務器接受帶憑據的請求:
res.setHeader('Access-Control-Allow-Credentials', true);
若是發送的是帶憑據的請求,但服務器的響應中沒有包含這個頭部,那麼瀏覽器就不會把響應交給 Javascript,則 responseText 是空字符串,status 的值爲0,並且會調用 onerror() 事件處理程序。
cookie 是能夠設置訪問域的,在設置 cookie 的時候,設定了 cookie 的訪問域名爲一個頂級域名,則能夠達到幾個子域名共享 cookie 的效果,如騰訊網 www.qq.com 與微信網頁版 wx.qq.com 共享了 pac_uid,關於前端存儲的相關內容,可參考我以前在博客園寫的博文:前端存儲調研總結。
Client 端:經過在URL後面加上查詢參數
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onload = function() { // 響應接收完畢後將觸發 onload 事件 // 處理 xhr.responseText } xhr.open('get', 'http://localhost:8888?method=GET&name=Ruth', true); xhr.send();
Server 端:處理 GET 請求
var http = require('http'); var url = require('url'); http.createServer(function(req, res) { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.setHeader('Access-Control-Allow-Credentials', true); res.writeHead(200, {'Content-Type': 'text/plain'}); // 解析 url 參數 var params = url.parse(req.url, true).query; res.write('請求類型:' + params.method); res.write('<br />'); res.write('姓名:' + params.name); res.end(); }).listen(8888);
不一樣於 JSONP,CORS 的好處就是可讓咱們實現 POST 請求。
Client 端發送信息:
var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onload = function() { // 響應接收完畢後將觸發 onload 事件 // 處理 xhr.responseText } xhr.open('post', 'http://localhost:8888', true); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); // 設置請求頭 xhr.send('method=POST&name=Ruth');
Server 端處理請求:
var http = require('http'); var querystring = require('querystring'); http.createServer(function(req, res) { res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000'); res.setHeader('Access-Control-Allow-Credentials', true); res.writeHead(200, {'Content-Type': 'text/plain'}); var post = ''; // 經過req的data事件監聽函數,每當接受到請求體的數據,就累加到post變量中 req.on('data', function(chunk) { post += chunk; }); // 在end事件觸發後,經過querystring.parse將post解析爲真正的POST請求格式,而後向客戶端返回 req.on('end', function() { post = querystring.parse(post); res.write('請求類型:' + post.method); res.write('<br/>') res.write('姓名:' + post.name); res.end(); }); }).listent(8888);