首先明白一件事:
跨域是瀏覽器的限制html
也就是說不是你真的不能訪問,而是瀏覽器出於他本身的種種擔憂攔截了你的訪問。前端
至於瀏覽器在擔憂什麼(什麼是跨域,爲何會有跨域),參考:百度node
其實cors的原理就是,告訴瀏覽器:"你別擔憂啦,這個請求我ok的,讓他訪問吧"。
而jsonp的原理是,使用瀏覽器不擔憂的方式去請求:script標籤中的src屬性(其餘帶有src屬性的標籤也能夠,好比img)。ajax
先來模擬出跨域的場景,在本地啓動一個最簡單的node服務,返回查詢參數。json
// 新建一個server.js文件,固然前提要安裝node const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
啓動服務後端
node server.js
複製代碼
先在瀏覽器裏直接訪問一下 http://127.0.0.1:9999/?name=無用書生&age=25 看到返回沒有問題跨域
而後就在當前頁面(你如今正在閱讀文章的掘金頁面)f12打開控制檯,在console裏建立一個ajax請求瀏覽器
var xhr = new XMLHttpRequest(); xhr.open('get', 'http://127.0.0.1:9999/?name=無用書生&age=25'); xhr.send(); 複製代碼
執行之後就出現跨域報錯了bash
首先來看一下cors的跨域原理,其實報錯裏就寫的很明白,咱們訪問的資源沒有設置對掘金這個訪問源的頭。markdown
在cors的規則中,請求分爲簡單請求和非簡單請求,咱們上面發送的就是一個簡單請求。
關於簡單請求和非簡單請求,參考:CORS跨域原理解析
只要在響應頭中(response header)指明容許哪些訪問源訪問就能夠了。
在server.js中給響應頭添加 Access-Control-Allow-Origin
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*" // * 表明容許全部的源訪問 }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
重啓node服務,再試一次
報錯沒有了,打開network能夠看到 Access-Control-Allow-Origin
已經生效,數據也成功獲取到
將上面請求的方法從get改爲put,再次請求 (put方法就屬於非簡單請求)
var xhr = new XMLHttpRequest(); xhr.open('put', 'http://127.0.0.1:9999/?name=無用書生&age=25'); xhr.send(); 複製代碼
能夠看到跨域報錯又出現了
剛纔設置的響應頭依然存在,卻不起做用了。
另外這裏能夠看到咱們原本發送的是put請求,請求方法那裏寫的倒是options。緣由就是對於非簡單請求瀏覽器會先發送一次預檢,預檢經過纔會發送真正的請求,這個options就是預檢請求,由於沒有經過,因此也就沒有發送真正的請求
其實報錯中也寫的很明白,咱們訪問的資源沒有設置容許對PUT這個方法的訪問
在server.js中給響應頭添加 Access-Control-Allow-Methods
,設置容許put方法的請求
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT", }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
重啓服務之後再次請求,能夠看到跨域報錯就消失了
還能夠看到依然先進行了一次預檢請求,此次預檢請求經過了,繼續發送了put請求
非簡單請求還對請求頭的信息有所限制,原理仍是同樣的,經過Access-Control-Allow-Headers
在返回頭中設置容許的訪問頭就ok了,好比
var xhr = new XMLHttpRequest(); xhr.open('put', 'http://127.0.0.1:9999/?name=無用書生&age=25'); xhr.setRequestHeader("X-Corx-Test", "aabbcc"); xhr.send(); 複製代碼
設置容許 X-Corx-Test這個請求頭
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT", "Access-Control-Allow-Headers": "X-Corx-Test" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
因爲掘金作了csp處理,沒法測試jsonp,用百度進行演示。
什麼是csp,參考:阮一峯博客
去掉node服務中對cors的配置
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
在百度首頁f12打開控制檯,這時候若是再使用ajax請求咱們的服務又會報跨域的錯誤
上面說過jsonp的原理就是使用script標籤的src不受瀏覽器跨域限制的原理
在console中建立一個jsonp請求
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25'; document.head.appendChild(script); 複製代碼
執行之後看到這時候就沒有報錯了
在network中能夠看到這是一個成功的get請求,注意jsonp只能發送get請求這時候請求雖然成功了,還沒拿到返回的數據
獲取數據的方法就是在前端定義一個接收數據的函數,而後後端返回的js中執行這個函數,並把要返回的數據做爲參數傳入
好比在前端定義一個叫作 getData
的函數
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25'; document.head.appendChild(script); function getData(res) { console.log(res); } 複製代碼
在後端返回的內容中調用這個函數,把數據傳進去
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", }); let jsonpStr = `getData(${queryStr})`; res.end(jsonpStr); }); server.listen(9999); console.log('server run at 9999'); 複製代碼
重啓服務之後,執行前端代碼,數據就能夠取到了
這個函數名字要先後端約定一致,另外獲取完數據之後最好移除一下script標籤
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=無用書生&age=25'; document.head.appendChild(script); function getData(res) { console.log(res); document.head.removeChild(script); } 複製代碼