說到跨域咱們先來講說同源,同源是指"協議+域名+端口"三者相同,所謂的跨域就是跨協議、跨域名、跨端口。javascript
因爲安全緣由,跨域訪問是被各大瀏覽器所禁止的。html
Cookie、LocalStorage 和 IndexDB 沒法跨域讀取前端
假如你登陸了某個銀行網站那麼該網站會發送cookie做爲登陸憑證。假如此時你訪問了惡意網站,對其發送請求若是能夠攜帶cookie跨域,那麼cookie會發送到惡意網站,此時就能夠模擬用戶去銀行網站發送請求。java
DOM同源策略也同樣,不能經過iframe引入其餘域下的頁面直接操做其dom元素webpack
在本身的網站嵌入別人的網站,若是嵌入的網站有登陸功能,若是能夠跨域得到嵌入頁面的元素,那在咱們的網站上能夠輕鬆獲取到用戶的帳號密碼。nginx
ajax請求不能跨域發送web
若是不限制ajax跨域發送數據,就至關於網站的全部接口對全部人都是公開的ajax
jsonp主要是利用標籤的src屬性沒有受同源策略限制來獲取來自其餘域名的數據。咱們一般使用 img/script 標籤。express
先舉個例子~ (bd搜索框) 在訪問bd進行搜索時咱們能夠很輕鬆的獲取搜索接口npm
https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list
接口返回的結果
// 不難發現cb傳什麼返回的就是什麼
list({q:"hello",p:false,s:["hello kitty","hello kitty樂園","hello 樹先生","hello world","hellobike","hello tv","hello女神","hellotalk","hello語音","hello venus"]});
複製代碼
開始來寫咱們的例子
<script>
function list(data){
console.log(data);
}
</script>
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list"></script>
複製代碼
好了這就已經實現了跨域了,咱們訪問了bd的接口!
加深點印象咱們本身來寫下服務端吧!
// client
<script>
function list(data){
console.log(data);
}
</script>
<script src="http://localhost:3000/getData?cb=list"></script>
// server
let express = require('express');
let app = express();
app.get('/getData', (req,res) => {
let cbName = req.query.cb
res.end(`${cbName}({name:'jw'})`);
});
app.listen(3000);
複製代碼
缺點:它只支持GET請求而不支持POST等其它類型的HTTP請求,可能會致使xss攻擊
CORS(Cross-Origin Resource Sharing, 跨源資源共享)是W3C出的一個標準,想要實現跨域主要靠服務器進行一些設置。客戶端不用作任何更改!
先列點雜七雜八的東西,後面咱們會用到!
Access-Control-Allow-Origin 容許的域
Access-Control-Allow-Credentials 容許攜帶cookie
Access-Control-Expose-Headers 容許客戶端獲取哪一個頭
Access-Control-Allow-Methods 容許的方法
Access-Control-Allow-Headers 容許哪些頭
Access-Control-Max-Age 預檢請求的結果緩存
廢話很少說先上例子~啓動兩個服務
// 經過3000端口來啓動index.html
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// 4000端口有getData接口
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.get('/getData', (req,res) => {
console.log(req.headers);
res.end('{name:"jw"}')
});
app.listen(4000);
複製代碼
在html發送ajax請求
let xhr = new XMLHttpRequest();
xhr.open('get','/getData',true);
xhr.onreadystatechange = function () {
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){
console.log(xhr.response)
}
}
}
xhr.send();
複製代碼
咱們能夠發現雖然沒有獲取到數據可是4000端口實際上是接收到了響應的,只是瀏覽器默認屏蔽了響應結果
第一次改造服務端:
當請求發送過來時,要根據當前客戶端的origin屬性判斷是否容許跨域
let express = require('express');
let app = express();
app.use(express.static(__dirname));
let whiteList = ['http://localhost:3000'];
app.use(function (req,res,next) {
if (whiteList.includes(req.headers.origin)) {
// 設置某個域能夠容許訪問,也可使用* 可是不建議這樣作
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
}
next();
})
app.get('/getData', (req,res) => {
console.log(req.headers);
res.end('{name:"jw"}');
});
app.listen(4000);
複製代碼
默認跨域是不攜帶cookie的,但是我想帶!強制設置攜帶cookie
// client
document.cookie = 'a=1'
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
複製代碼
第二次改造服務端:
app.use(function (req,res,next) {
if (whiteList.includes(req.headers.origin)) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
+ res.setHeader('Access-Control-Allow-Credentials',true)
}
next();
})
複製代碼
客戶端仍是不滿意想在傳遞些自定義的頭!
document.cookie = 'a=1'
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('get','http://localhost:4000/getData',true);
xhr.setRequestHeader('name', 'jw');
複製代碼
第三次改造服務端:
app.use(function (req,res,next) {
if (whiteList.includes(req.headers.origin)) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials',true);
+ res.setHeader('Access-Control-Allow-Headers',"name");
}
next();
});
複製代碼
客戶端又發現發送put請求時。又出現了噁心的問題......
xhr.open('put','http://localhost:4000/getData',true);
複製代碼
這回我一眼就看到了問題,確定是沒有設置容許接收的方法
第四次改造服務端:
app.use(function (req,res,next) {
if (whiteList.includes(req.headers.origin)) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials',true);
res.setHeader('Access-Control-Allow-Headers',"name");
+ res.setHeader('Access-Control-Allow-Methods','PUT');
}
next();
});
app.put('/getData', (req,res) => {
console.log(req.headers);
res.end('{name:"jw"}');
});
複製代碼
等等怎麼多了個請求?
原來是這樣對於非簡單請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。類型爲 options。
第五次改造服務端:
app.use(function (req,res,next) {
if (whiteList.includes(req.headers.origin)) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials',true);
res.setHeader('Access-Control-Allow-Headers',"name");
res.setHeader('Access-Control-Allow-Methods','PUT');
// 設置預檢查發送的時間
+ res.setHeader('Access-Control-Max-Age',6000);
if (req.method === 'OPTIONS'){
res.end();
}else{
next();
}
}
});
複製代碼
到此客戶端終於不搞事了。服務端爲了表示友好給他寫了個頭
app.put('/getData', (req,res) => {
res.setHeader('info','ok');
res.end('{name:"jw"}');
});
複製代碼
xhr.onreadystatechange = function () {
if(xhr.readyState === 4){
if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){
+ console.log(xhr.getResponseHeader('info'));
console.log(xhr.response);
}
}
}
複製代碼
這才發現客戶端原來是收不到的,好吧!還須要告訴客戶端能夠拿到這個值
app.put('/getData', (req,res) => {
res.setHeader('info', 'ok');
res.setHeader('Access-Control-Expose-Headers', 'info');
res.end('{name:"jw"}');
});
複製代碼
到此! 終於知道cors是個什麼鬼了,就是設置各類約定好的頭
CORS 不只使用方便,支持全部類型請求,具備權限控制,並且瀏覽器原生支持,咱們能夠輕易的處理請求異常。利於排查錯誤。因此咱們大多數狀況下會首選該方式。在低版本瀏覽器可使用jsonp配合其餘方式來兼容。
在來看看兩個窗口是如何實現跨域的,先看看H5的postMessage! postMessage()方法能夠安全地實現跨源通訊
如今的需求是這樣的 http://127.0.0.1:3000/a.html 想對http://127.0.0.1:4000/b.html 說我愛你,以後b頁面收到消息後對其說我不愛你~,這事多麼悲傷的一個故事#_#。
// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()" frameborder="0"></iframe>
<script>
function load() {
let frame = document.getElementById('frame');
frame.contentWindow.postMessage('我愛你','http://localhost:4000');
window.onmessage = function (e) {
console.log(e.data);
}
}
</script>
// b.html
<script>
window.onmessage = function (e) {
console.log(e.data);
e.source.postMessage('我不愛你',e.origin);
}
</script>
複製代碼
domain方式跨域要先保證兩個頁面屬於同一個域下的 咱們在hosts文件增長兩個映射關係,位置在 C:\Windows\System32\drivers\etc
127.0.0.1 a.fullstackjavascript.cn
127.0.0.1 b.fullstackjavascript.cn
複製代碼
// a.html
<iframe src="http://b.fullstackjavascript.cn:4000/b.html"
frameborder="0" id="frame"
onload="load()"
></iframe>
<script>
function load() {
document.domain = 'fullstackjavascript.cn';
console.log(frame.contentWindow.name);
}
</script>
// b.html
<script>
document.domain = 'fullstackjavascript.cn';
var name = 'jw';
</script>
複製代碼
訪問http://a.fullstackjavascript.cn:3000/a.html,發現是能夠拿到另外一個頁面中的值
用hash傳遞數據就比較有意思了,須要藉助第三個頁面實現。
先用粗俗的話介紹一下,先有三個頁面hash1,hash2,hash3他們之間的關係是hash1和hash2是同域下的hash3是獨立域下的,咱們經過hash1頁面用iframe引入hash3頁面,能夠在引入時給hash3頁面傳遞hash值,hash3接到hash值後算出須要返回結果,在建立iframe引入hash2把結果經過hash的方式傳遞給hash2,hash2和hash1是同域的,hash2能夠直接操控hash1的值,此時hash1頁面能夠監控hash值的變化來獲取hash2的數據
// hash1.html
<iframe src="http://a.fullstackjavascript.cn:3000/hash3.html#iloveyou" frameborder="0"></iframe>
<script>
window.onhashchange = function () {
console.log(location.hash);
}
</script>
// hash2.html
<script>
let hash = location.hash;
let data;
if(hash === '#iloveyoue'){
data = 'idontloveyou'
}
let frame = document.createElement('iframe');
frame.src = `http://a.fullstackjavascript.cn:3000/hash3.html#${data}`;
</script>
// hash3.html
<script>
let hash = location.hash;
window.parent.parent.location.hash = hash
</script>
複製代碼
效果是沒問題的,不過感受有點麻煩~
使用window.name跨域是利用切換路徑後window上的name屬性是會保留的。不懂?不要緊,舉個例子,仍是有三個頁面a,b,c。a和b是同域下的,c本身一個域。a先引用c,c將想表達的內容放到name,屬性上以後,a改變引用路徑,改爲引用b,此時name屬性不會被刪除,由於a,b是同域的,因此能夠直接獲取。
// a.html
<iframe src="http://a.fullstackjavascript.cn:4000/c.html" id="myFrame" onload="load()" frameborder="0"></iframe>
<script>
let first = true
function load() {
if(first){
myFrame.src = 'http://b.fullstackjavascript.cn:3000/c.html';
first = false
}else{
let name = myFrame.contentWindow.name;
console.log(name);
}
}
</script>
// c.html
<script>
window.name = '我愛你'
</script>
複製代碼
直接訪問http://b.fullstackjavascript.cn:3000/a.html發現能夠拿到咱們想要的結果啦~
WebSocket最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話。這個不是我們考慮的問題。我們如今的話題是跨域,偏偏WebSocket是沒有同源限制的!
能夠直接建立socket對象和服務器通訊發消息
// socket.html
<script>
let socket = new WebSocket('ws://localhost:4000');
socket.onopen= function () {
socket.send('我愛你');
}
socket.onmessage = function (e) {
console.log(e.data);
}
</script>
複製代碼
服務端須要ws模塊
$ npm install ws
複製代碼
let express = require('express');
let app = express();
let WebSockect = require('ws');
let wss = new WebSockect.Server({port:4000});
wss.on('connection',function (ws) {
ws.on('message',function (data) {
console.log(data);
ws.send('我不愛你');
});
})
複製代碼
咱們都知道好東西每每不兼容,咱們可使用socket.io模塊保證兼容性問題。這裏我就不過多去介紹websocket的使用了
咱們來看看服務端是如何實現代理的,也就是咱們常說的反向代理!你們可能都對webpack比較熟悉了,webpack-dev-server內置了http-proxy-middleware。那好吧我們就用express配合這個中間件插件來試試這東西怎麼玩。
// 3000服務器
let express = require('express');
let app = express();
var proxy = require('http-proxy-middleware');
let proxyOptions = {
target: 'http://localhost:4000',
changeOrigin: true
};
app.use('/api', proxy(proxyOptions));
app.listen(3000);
複製代碼
// 4000服務器
let express = require('express');
let app = express();
app.get('/api', (req,res) => {
res.end('ok');
})
app.listen(4000);
複製代碼
當咱們訪問http://localhost:3000/api路徑時請求會被轉發到4000服務器上,將4000服務器上的結果響應給客戶端
我相信你們對Nginx都並不陌生,他能夠實現代理咱們的接口。這裏我就簡單的使用一下展現下效果! 這裏以windows系統爲例:
先要安裝nginx 下載地址
更改conf/nginx.conf增長訪問代理
location /api/ {
proxy_pass http://localhost:4000;
}
複製代碼
雙擊nginx.exe運行,訪問localhost/api發現返回了4000服務器上的結果。
廢了這麼多口水,終於把前端經常使用的跨域手段介紹了一遍!有什麼疑問歡迎聯繫我!
喜歡的點個贊吧^_^!
支持個人能夠給我打賞哈!