1. 程式人生 > 其它 >跨域的解決辦法

跨域的解決辦法

技術標籤: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.comhttp://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反向代理還能實現負載均衡