1、jsonp
jsonp解決跨域問題的本質:<script>標籤可以請求不同域名下的資源,即<script>請求不受瀏覽器同源策略影響。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>jsonp</title>
</head>
<body>
</body>
</html>
<script>
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://127.0.0.1:8888/?callback=foo';
document.body.appendChild(script);
function foo(data) {
console.log(`methods: ${data.methods}, result: ${data.result}`);
// 請求完後刪除添加到頁面上的script標籤
var body = document.body
body.removeChild(script)
};
</script>
服務端:
const http = require('http')
const urlTool = require('url')
var server = http.createServer((req, res) => {
var params = urlTool.parse(req.url, true)
var data = {'methods': 'jsonp', 'result': 'success'}
res.writeHead(200, {
'Content-Type': 'text/plain'
})
if (params.query && params.query.callback) {
// 服務器端應當在JSON數據前加上回調函數名,以便完成一個有效的JSONP請求
var str = `${params.query.callback}(${JSON.stringify(data)})`
}
res.end(str);
})
server.listen(8888)
console.log('Server running at http://127.0.0.1:8888/')
2、CORS
跨域資源共享(CORS) 是一種機制,它使用額外的HTTP頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
在進行非簡單請求的時候,瀏覽器會先發送一次OPTION請求來“預檢”(preflight)該請求是否被允許,請求頭中會通過Access-Control-Request-Method,Access-Control-Request-Headers來告訴服務器我需要用到的方法和字段,服務器通過返回的頭部信息中的Access-Control-Allow-Origin,Access-Control-Allow-Method來告訴瀏覽器該跨域請求是否被允許。當確認允許跨域之後,以後再發送該請求,就會省去預檢處理,之間當作簡單請求來操作。
簡單請求與非簡單請求的區別
* 請求方式:HEAD,GET,POST
* 請求頭信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 對應的值是以下三個中的任意一個
application/x-www-form-urlencoded
multipart/form-data
text/plain
只有同時滿足以上兩個條件時,才是簡單請求,否則為非簡單請求
3、Nginx代理
3.1、正向代理和反向代理的概念
無論是正向代理,還是反向代理,説到底,就是代理模式的衍生版本罷了。我們都學習過代理設計模式,都知道代理模式中有代理角色和被代理角色,為什麼這麼説,因為這兩個角色對於我們理解正向和反向代理非常重要,下面會講到。
下面我將引入這樣一個場景,很多時候我們上網的網速特別慢,或者説由於翻牆問題導致我們無法訪問到國外的網站,通常這些情況我們會通過給瀏覽器配置一個網速快的、可以翻牆的代理ip及端口號來解決我們的問題,那麼配置好之後,大概的請求流程如下圖所示:
我們首先請求代理服務器,然後代理服務器幫我們去快速訪問國外的網站,對於這種代理方式,我們就稱之為正向代理。請記住,上面説到代理模式的兩個角色中,我們當前的角色為 被代理者,也就是瀏覽器這個角色。更重要的是,正向代理的本質是我們去請求外部的資源,如果以生產者、消費者模式來區分,我們屬於消費者。
總結:
- 1、正向代理,我們的角色是 被代理者
- 2、正向代理,我們不對外提供服務,反而是對外消費服務,屬於消費者
反向代理,很顯然,就是和正向代理相反,如果説正向代理是男,那麼反向代理就是女了,親,此處不再糾結其他情況!下面我用一副圖片解釋下反向代理:
看完上面的圖片,請你想象一下這麼一個場景,假設你現在是某公司技術總監,你們公司需要對外提供一套web服務,那麼你打算怎麼做呢?
答案是可以通過反向代理來完成。通常你們公司擁有自己的IDC機房,機房通訊通常採用局域網交換機,internet網用户請求是無法直接訪問到局域網內的web服務的,因此這個時候,你需要一台反向代理服務器來接收internet web請求,然後將請求分發到局域網中的不同主機上進行處理,處理完成之後再做出響應。因此,反向代理大概就是這麼一個場景。請記住,反向代理中,我們的角色是 局域網 web服務。
總結:
- 1、反向代理,我們的角色是 局域網 web服務
- 2、反向代理,我們對外提供服務,屬於服務提供者
3.2、nginx反向代理實現跨域
3.2.1、前端配置nginx方向代理實現跨域
場景:假如前端頁面url為http://127.0.0.1:5500需要向後端http://127.0.0.1:3000發起請求(http://127.0.0.1:3000/index),由於瀏覽器的同源策略,該請求跨域了。前端可以配置nginx方向代理,將前端服務http://127.0.0.1:5500代理到http://127.0.0.1:3003,前端請求由http://127.0.0.1:3000/xxx改成http://127.0.0.1:3003/xxx,後端服務http://127.0.0.1:3000代理到http://127.0.0.1:3003,這樣前後端交互就同源了。核心思想就是將前後端服務代理到同一域下
// http://127.0.0.1:5500頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<button id="getOK">發送請求OK(客户端解決跨域問題)</button>
<button id="getNO">發送請求NO(客户端解決跨域問題)</button>
<script>
$(document).ready(function () {
$('#getOK').click(function () {
$.ajax({
url:'http://127.0.0.1:3003/ok',
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
$('#getNO').click(function () {
$.ajax({
url:'http://127.0.0.1:3003/no',
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
})
</script>
</body>
</html>
// http://127.0.0.1:3000後端
const express = require('express')
const cookieParser = require('cookie-parser')
var app = express()
var router = express.Router()
router.get('/ok',function (req,res) {
res.json({
code:200,
msg:"isOK"
})
})
router.get('/ok/son',function (req,res) {
res.json({
code:200,
msg:"isOKSon"
})
})
router.get('/ok2',function (req,res) {
res.json({
code:200,
msg:"isOK2"
})
})
router.get('/no',function (req,res) {
res.json({
code:200,
msg:"isNO"
})
})
router.get('/no/son',function (req,res) {
res.json({
code:200,
msg:"isNOSON"
})
})
router.get('/no/son2',function (req,res) {
res.json({
code:200,
msg:"isNOSON2"
})
})
app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
console.log('listen in 3000')
})
// nginx反向代理
server {
listen 3003;
server_name 127.0.0.1; // 不能加上http:// ,否則會報server name "\*" has suspicious symbols
location / {
proxy_pass http://127.0.0.1:5500;
}
location /no {
proxy_pass http://127.0.0.1:3000;
}
location /ok/ {
proxy_pass http://127.0.0.1:3000;
}
}
效果:
3.2.2、後端配置nginx方向代理實現跨域
思想:跟前端配置nginx方向代理有所不同,後端nginx方向代理實現跨域只需要代理後端服務,並在代理服務器上配置響應頭屬性允許前端訪問代理服務器。
// http://127.0.0.1:5500 前端頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<button id="get">發送請求(服務端解決跨域問題)</button>
<script>
$(document).ready(function () {
$('#get').click(function () {
$.ajax({
url:'http://127.0.0.1:3002/ok',
// 帶cookies的請求
xhrFields:{
withCredentials:true
},
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
})
</script>
</body>
</html>
// http://127.0.0.1:3000 後端
const express = require('express')
const cookieParser = require('cookie-parser')
var app = express()
var router = express.Router()
router.get('/ok',function (req,res) {
res.json({
code:200,
msg:"isOK"
})
})
router.get('/ok/son',function (req,res) {
res.json({
code:200,
msg:"isOKSon"
})
})
router.get('/ok2',function (req,res) {
res.json({
code:200,
msg:"isOK2"
})
})
router.get('/no',function (req,res) {
res.json({
code:200,
msg:"isNO"
})
})
router.get('/no/son',function (req,res) {
res.json({
code:200,
msg:"isNOSON"
})
})
router.get('/no/son2',function (req,res) {
res.json({
code:200,
msg:"isNOSON2"
})
})
app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
console.log('listen in 3000')
})
// 後端nginx方向代理
server {
listen 3002;
server_name 127.0.0.1;
location /ok {
proxy_pass http://127.0.0.1:3000;
# 指定允許跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
# 預檢命令的緩存,如果不緩存每次會發送兩次請求
add_header Access-Control-Max-Age 3600;
# 帶cookie請求需要加上這個字段,並設置為true
add_header Access-Control-Allow-Credentials true;
# 表示允許這個域跨域調用(客户端發送請求的域名和端口)
# $http_origin動態獲取請求客户端請求的域 不用*的原因是帶cookie的請求不支持*號
add_header Access-Control-Allow-Origin $http_origin;
# 表示請求頭的字段 動態獲取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
# OPTIONS預檢命令,預檢命令通過時才發送請求
# 檢查請求的類型是不是預檢命令
if ($request_method = OPTIONS){
return 200;
}
}
}
實際上不是非簡單請求的且不帶cookie只需2個字段即可解決跨域
add\_header Access-Control-Allow-Methods \*;
add\_header Access-Control-Allow-Origin $http\_origin;
效果:
window下常用nginx命令:
查看nginx進程
tasklist /fi "imagename eq nginx.exe"
殺掉nginx進程
taskkill /f /pid 16900 /pid 19012
啓動nginx
start nginx
4、postMessage
場景1:在a頁面裏打開了另一個不同源的頁面b,你想要讓a和b兩個頁面互相通信。比如,a要訪問b的LocalStorage。
場景2:你的a頁面裏的iframe的src是不同源的b頁面,你想要讓a和b兩個頁面互相通信。比如,a要訪問b的LocalStorage。
解決方案:HTML5y引入了一個全新的API,跨文檔通信 API(Cross-document messaging)。這個API為window對象新增了一個window.postMessage方法,允許跨窗口通信,不論這兩個窗口是否同源。a就可以把它的LocalStorage,發送給b,b也可以把自己的LocalStorage發給a。
window.postMessage(message, targetOrigin, [transfer]),有三個參數:
message是向目標窗口發送的數據;
targetOrigin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個URI(或者説是發送消息的目標域名);
transfer可選參數,是一串和message 同時傳遞的 Transferable 對象. 這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權。
另外消息的接收方必須有監聽事件,否則發送消息時就會報錯。The target origin provided ('http://localhost:8080') does not match the recipient window's origin ('http://localhost:63343').
window.addEventListener("message",onmessage);onmessage接收到的message事件包含三個屬性:
data:從其他 window 中傳遞過來的數據。
origin:調用 postMessage 時消息發送方窗口的 origin 。請注意,這個origin不能保證是該窗口的當前或未來origin,因為postMessage被調用後可能被導航到不同的位置。
source:對發送消息的窗口對象的引用; 您可以使用此來在具有不同origin的兩個窗口之間建立雙向通信。
案例
使用http-server模擬不同源的兩個頁面
// http://127.0.0.1:8080/A.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="send()">向彈窗頁發送信息</button>
</body>
</html>
<script>
var popup;
window.onload = function () {
popup = window.open('http://127.0.0.1:8081/C.html', '', 'width=500,height=500')
}
function send() {
popup.postMessage('Hello World!', 'http://127.0.0.1:8081/')
}
</script>
// http://127.0.0.1:8081/C.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
window.onload = function () {
console.log('recieve')
window.addEventListener("message", onmessage);
}
function onmessage(event) {
if (event.origin == "http://127.0.0.1:8080") { // http://localhost:8080是發送方a的域名
document.getElementById('app').innerText = event.data
}
}
</script>
5、document.domain
6、window.name
場景1:現在瀏覽器的一個標籤頁裏打開http://www.damonare.cn/a.html頁面,你通過location.href=http://baidu.com/b.html,在同一個瀏覽器標籤頁裏打開了不同域名下的頁面。這時候這兩個頁面你可以使用window.name來傳遞參數。因為window.name指的是瀏覽器窗口的名字,只要瀏覽器窗口相同,那麼無論在哪個網頁裏訪問值都是一樣的。
場景2:你的http://www.damonare.cn/a.html頁面裏使用<iframe>調用另一個http://baidu.com/b.html頁面。這時候你想在a頁面裏獲取b頁面裏的dom,然後進行操作。然後你會發現你不能獲得b的dom。同樣會因為不同源而報錯,和上面提到的不同之處就是兩個頁面的一級域名也不相同。這時候document.domain就解決不了了。
解決方案:瀏覽器窗口有window.name屬性。這個屬性的最大特點是,無論是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁可以讀取它。。比如你在b頁面裏設定window.name="hello",你再返回到a頁面,在a頁面裏訪問window.name,可以得到hello。
這種方法的優點是,window.name容量很大,可以放置非常長的字符串;缺點是必須監聽子窗口window.name屬性的變化,影響網頁性能。
// http://127.0.0.1:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p></p>
<iframe src="http://127.0.0.1:8081/demo10.html" frameborder="0"></iframe>
</body>
</html>
<script>
var ifr = document.getElementsByTagName('iframe')[0]
ifr.onload = function() {
document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
}
</script>
// http://127.0.0.1:8081
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
window.name="hello, world!"
</script>
</body>
</html>
上面執行結果會報錯,因為不同源
解決方法:
<script>
var ifr = document.getElementsByTagName('iframe')[0]
ifr.onload = function() {
ifr.onload = function(){
document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
}
ifr.src = 'about:blank';
}
</script>
參考:
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.im/entry/5b3a2...
https://juejin.im/post/5b67b3...
https://juejin.im/post/5815f4...