跨域的解決辦法
阿新 • • 發佈:2020-12-31
技術標籤:javascript
跨域
1. jsonp
- jsonp是一種跨域通訊手段,通過script標籤的src屬性實現跨域,由於瀏覽器同源策略,並不會截斷script的跨域響應
- 通過將前端方法名作為引數傳遞到伺服器端,然後由伺服器端注入引數之後再返回,實現伺服器端向客戶端通訊
- 由於使用script標籤的src屬性,因此只支援get方法
// 前端準備
// 定義回撥函式
function fn(arg) {
// arg為服務端傳來的資料
console.log(`客戶端獲取的資料:${arg}`)
}
// 建立script標籤
const s = document.createElement ('script')
// 給script標籤的src屬性賦值,值為請求url,查詢引數callback,需與後端對應
// fn為前端回撥函式名
s.src = `http://127.0.0.1:3000/test?callback=fn`
// 向html新增此標籤,新增完成之後瀏覽器自動請求script的src對應的網址
document.getElementsByTagName('head')[0].appendChild(s);
// 等待瀏覽器收到響應之後,將會自動執行響應內容的程式碼
----------------------------------------------
// 後端準備
// nestjs(ts)處理
@Controller('test') //api
export class TestController {
@Get() //get方式請求
//取url中的查詢引數,即?之後的鍵值對,鍵與值對應query物件引數的鍵與值
callback(@Query() query) {
// 返回的資料
const data = '我是服務端返回的資料';
// 取查詢引數,這裡的callback要與前端?之後的鍵名一致,fn即fn函式名
const fn = query.callback;
// 返回結果,格式:函式名(伺服器的資料),注意這裡需要序列化成字串,如果引數本身是字串那麼要加引號,前端並不知道data是字串
return `${fn}('${data}')`;
}
}
// express(js)處理,同上
router.get('/test', async (req, res) => {
const data = '我是伺服器返回的資料'
// req.query為查詢引數列表
const fn = req.query.callback
// 返回資料
res.send(`${fn}('${data}')`)
})
響應內容
2. CORS
- 跨域資源共享cors,它使用額外的 HTTP 頭來告訴瀏覽器,讓執行在一個 origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源
- 需要服務端與客戶端同時支援cors跨域方式才能進行跨域請求,服務端通過設定
Access-Control-Allow-Origin:
即可開啟cors允許跨域請求,使用萬用字元表示允許所有不同域的源訪問資源,也可單獨設定指定允許的源域名
-
使用cors跨域時,將會在發起請求時出現2種情況:
-
簡單請求,需滿足以下條件
- 使用get、head、post方式發起的請求
- Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded - 不滿足這些條件即為預檢請求
-
預檢請求
- 需預檢的請求要求必須首先使用OPTIONS方法發起一個預檢請求到伺服器,以獲知伺服器是否允許該實際請求
- 預檢請求的使用,可以避免跨域請求對伺服器的使用者資料產生未預期的影響
- 當滿足以下條件之一,將會發送預檢請求
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
-
人為設定了對 CORS 安全的首部欄位集合之外的其他首部欄位。該集合為:
Accept
Accept-Language
Content-Language
Content-Type (需要注意額外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width -
Content-Type 的值不屬於下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain -
滿足以上條件之一將會發起預檢請求,總共會發起2次請求,第一次為OPTIONS方式的請求,用來確定伺服器是否支援跨域,如果支援,再發起第二次實際請求,否則不傳送第二次請求
3. postMessage
- postMessage可用於不同頁面之間的跨域傳遞資料
postMessage(data,origin[, source])
data為傳送的資料只能傳送字串資訊,origin傳送目標源,指定哪些視窗能接收到訊息事件,如果origin設定為*則表示無限制,source為傳送訊息視窗的window物件引用,
<!-- test.html -->
<iframe src="http://127.0.0.1:5501/postMessage.html"
name="postIframe" onload="messageLoad()"></iframe>
<script>
// 定義載入之後執行的函式,給postMessage.html傳送資料
function messageLoad() {
const url = 'http://127.0.0.1:5501/postMessage.html'
window.postIframe.postMessage('給postMessage的資料', url)
}
// 用於監聽postMessage.html的回饋,執行回撥
window.addEventListener('message', (event) => {
console.log(event.data);
})
</script>
----------------------------------------------
<!-- postMessage.html -->
<script>
// 監聽test.html發來的資料,延遲1秒返回資料
window.addEventListener('message', (event) => {
setTimeout(() => {
event.source.postMessage('給test的資料', event.origin)
},1000)
})
</script>
- event物件的幾個重要屬性
1.data 指的是從其他視窗傳送過來的訊息物件
2.type 指的是傳送訊息的型別
3.source 指的是傳送訊息的視窗物件
4.origin 指的是傳送訊息的視窗的源
4. window.name
- 由於
window.name
屬於全域性屬性,在html中的iframe載入新頁面(可以是跨域),通過iframe設定的src指向的源中更改name的值,同時主頁面中的name也隨之更改,但是需要給iframe中的window設定為about:blank
或者同源頁面即可 - iframe使用之後應該刪除,name的值只能為string型別,且資料量最大支援2MB
<!-- test.html -->
// 封裝應該用於獲取資料的函式
function foo(url, func) {
let isFirst = true
const ifr = document.createElement('iframe')
loadFunc = () => {
if (isFirst) {
// 設定為同源
ifr.contentWindow.location = 'about:blank'
isFirst = false
} else {
func(ifr.contentWindow.name)
ifr.contentWindow.close()
document.body.removeChild(ifr)
}
}
ifr.src = url
ifr.style.display = 'none'
document.body.appendChild(ifr)
// 載入之後的回撥
ifr.onload = loadFunc
}
foo(`http://127.0.0.1:5501/name.html`, (data) => {
console.log(data) //
})
----------------------------------------------
<!-- name.html -->
const obj = { name: "iframe" }
// 修改name的值,必須為string型別
window.name = JSON.stringify(obj);
5.document.domain
document.domain
的值對應當前頁面的域名- 通過對domain設定當前域名來實現跨域,不過僅限於域名不同,但是又要屬於同一個基礎域名下,如
http://a.baidu.com
與http://b.baidu.com
這2個子域名之間才能使用domain跨域,一般用於子域名之間的跨域訪問 - domain只能賦值為當前域名或者其基礎域名,即上級域名
<!-- test.html -->
<script>
document.domain = 'baidu.com';
const ifr = document.createElement('iframe');
ifr.src = 'a.baidu.com/test.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 此處即可操作domain.html的document
ifr.onload = null;
};
</script>
----------------------------------------------
<!-- domain.html -->
<script>
// domain.html下設定為與test.html中的domain一致
document.domain = 'baidu.com';
</script>
- 主要就是通過設定為同源域名(只能為其基礎域名),通過iframe操作另一個頁面的內容
6.nginx反向代理
- nginx反向代理,代理從客戶端來的請求,轉發到其代理源
- 通過配置nginx的配置檔案實現代理到不同源
// nginx.conf配置
server {
listen 80; // 監聽埠
server_name www.baidu.com; // 匹配來源
location / { //匹配路徑
// 反向代理到http://127.0.0.1:3000
proxy_pass http://127.0.0.1:3000;
// 預設入口檔案
index index.html index.htm index.jsp;
}
- nginx反向代理還能實現負載均衡