一篇搞懂跨域之CORS

date: 2019-12-31 18:09:11javascript

跨年是web開發中。。奧,錯了。跨域是web開發中常見的一個問題,其中CORS是比較經常使用的解決跨域的方式。如今經過new XMLHttpRequest()和簡單實現一個node server,完全搞懂CORS(Cross-Origin Resource Sharing)。html

幾個相關響應頭頭信息

  • Access-Control-Allow-Origin:
    容許哪些地址訪問。能夠指定某些URL訪問。也能夠經過設置通配符*,容許因此地址訪問,不過在設置Credentials時,不容許設置通配符,解決方法下面說。java

  • Access-Control-Allow-Headers:
    容許客戶端設置約定好的請求頭參數。node

  • Access-Control-Allow-Credentials:
    響應頭是否能夠將請求的響應暴露給頁面。主要是指的是Cookieweb

  • Access-Control-Allow-Methods:
    容許哪些方法訪問我。ajax

  • Access-Control-Max-Age:
    設置多久內不須要再進行預檢請求json

幾個實際的錯誤

實際開發中會瀏覽器會之間提示響應的錯誤信息,根據信息內容,能夠很容易的推斷出是什麼緣由跨域,解決方案是什麼。api

先寫一個簡單的xhr客戶端:跨域

// 用http-server靜態服務器端口 8080 啓動
  <button id='btn'>發送ajax</button>
  <script> btn.addEventListener('click',()=>{ doRequest() }) // 發送請求 function doRequest(){ let xhr = new XMLHttpRequest(); // 。。。請求處理  let xhr = new XMLHttpRequest(); xhr.open('GET','http://localhost:3000/user',true) // todo請求設置 xhr.responseType = 'json'; // 設置服務器的響應類型 xhr.onload = function(){console.log(xhr.response)} xhr.send() } </script>
複製代碼

一個簡單的node服務端:瀏覽器

// nodemon server.js啓動 
http.createServer((req,res)=>{
  let {pathname} = url.parse(req.url)
  let method = req.method
  if(req.headers.origin){ // 若是跨域了 才走跨域邏輯
    // TODO CORS處理 
  }
  // 接口邏輯處理
  if (pathname == '/user') {
  }
}).listen('3000')
複製代碼

一、 第一個請求,被拒絕了。

  • 客戶端請求:

    function doRequest(){
      let xhr = new XMLHttpRequest();
      xhr.open('GET','http://localhost:3000/user',true)
      xhr.responseType = 'json'; // 設置服務器的響應類型
      xhr.onload = function(){console.log(xhr.response)}
      xhr.send('a=1&b=2')
    }
    複製代碼
  • 出現錯誤:

    當咱們經過一個簡單的get請求'http://localhost:3000/user'時,錯誤提示很明確,從http://127.0.0.1:8080地址請求http://localhost:3000/user的接口被拒絕了。要設置Access-Control-Allow-Originheader響應頭。

  • 解決方法:
    服務端設置響應頭(node http模塊寫法):

    // ...
      res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080') 
      // or 容許全局訪問 設置通配符 * 
      res.setHeader('Access-Control-Allow-Origin', '*') 
      // or 容許全局訪問 動態獲取請求源地址。( * 在某些狀況下不能用,下面會說)
      res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
      // ...
    複製代碼

2. 請求頭內攜帶token信息被拒絕了

  • 客戶端請求:

    // ...
      // 設置請求頭token
      xhr.setRequestHeader('token','xxxx');
      // ...
    }
    複製代碼
  • 出現錯誤:

    設置請求頭信息token。服務器應該要容許,如需其餘參數另行添加

  • 解決方法: 服務端設置

    // ...
      res.setHeader('Access-Control-Allow-Headers','token');
      // ...
    複製代碼

    其實如今再去請求又會出現另外一個錯誤,看下面。

3. 非簡單請求的預檢請求 OPTIONS

客戶端請求時(包括GET、POST請求)增長自定義請求頭信息時或者非GET、POST請求,都是非簡單請求。非簡單請求觸發時,會先發送一次OPTIONS預檢請求,

  • 客戶端:設置自定義請求頭字段,或者PUT請求等非GET和POST請求。

    function doRequest(){
      // ...
      xhr.open('PUT','http://localhost:3000/user',true)
      // or
      xhr.open('GET','http://localhost:3000/user',true)
      xhr.setRequestHeader('token','xxxx');
      // ...
    }
    複製代碼
  • 出現錯誤:
    OPTIONS請求未處理,因此404

    同時出現跨域錯誤

  • 解決方法:
    服務端設置:同時考慮到不須要每次都進行預檢請求,響應成功一次後,服務器能夠告訴客戶端多少秒內不須要繼續OPTIONS請求了。

    // 設置options請求的間隔事件
        res.setHeader('Access-Control-Max-Age','10');
        if(method === 'OPTIONS'){
            res.end(); // 瀏覽器就知道了 我能夠繼續訪問你
            return;
        }
    
    複製代碼

4. 設置Cookie時出現到問題

有時候服務端須要給客戶端返回Cookie用於身份憑證,客戶端收到Cookie後,再次請求能夠攜帶該Cookie代表身份。

  • 客戶端:
function doRequest(){
    // ...
    xhr.withCredentials = true; // 強制攜帶服務器設置的cookie
    // ...
  }
複製代碼
  • 出現錯誤:

    服務端未容許cookie設置

  • 解決方法: 服務器端設置,容許設置憑證(此處做者有疑問🤔️)

    // ...
      res.setHeader('Access-Control-Allow-Credentials',true)
      // ...
    複製代碼

下面給出完整把端客戶端和服務端的代碼

客戶端: 能夠用http-server指定8080端口啓動訪問,模擬 http://localhost:8080/index.html訪問端口 3000

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <button id='btn'>發送ajax</button>
  <script> btn.addEventListener('click',()=>{ let xhr = new XMLHttpRequest(); xhr.open('POST','http://localhost:3000/user',true) xhr.setRequestHeader('token','xxxx'); // 設置token 服務器須要贊成設置token xhr.withCredentials = true // 請求攜帶cookie,服務器須要同步設置容許攜帶 xhr.responseType = 'json'; // 設置服務器的響應類型 xhr.onload = function(){ // xhr.readyState == 4 + xhr.status == 200 // xhr.response 對象等數據 console.log(xhr.response) } xhr.send('a=1&b=2') }) </script>
</body>
</html>
複製代碼

node服務器:

const http = require('http');  
const fs = require('fs');  
const url = require('url');  
const path = require('path');  
http.createServer((req,res)=>{
    // 動態服務
    let {pathname,query} = url.parse(req.url);
    // pathname 有多是客戶端發起的接口請求
    let method = req.method;
    // 容許那個域 來訪問我
    if(req.headers.origin){ // 若是跨域了 才走跨域邏輯
        res.setHeader('Access-Control-Allow-Origin',req.headers.origin); // 和 * 是同樣
        // 容許哪些方法訪問我
        res.setHeader('Access-Control-Allow-Methods','GET,PUT,DELETE,POST,OPTIONS');
        // 容許攜帶哪些頭
        res.setHeader('Access-Control-Allow-Headers','token');
        // 設置options請求的間隔事件
        res.setHeader('Access-Control-Max-Age','10');
        res.setHeader('Access-Control-Allow-Credentials',true)
        // 跨域 cookie 憑證 若是跨域是不容許攜帶cookie
        if(method === 'OPTIONS'){
            res.end(); // 瀏覽器就知道了 我能夠繼續訪問你
            return;
        }
    }
    if(pathname === '/user'){ // 你發送的是api接口
        switch(method){
            case 'GET':  
                // res.setHeader('Set-Cookie','name=zf'); 
                res.end(JSON.stringify({name:'zf'}))
                break;
            case 'POST':
                let arr = [];
                console.log('log')
                req.on('data',function(chunk){
                    arr.push(chunk);
                })
                req.on('end',function(){
                    console.log(Buffer.concat(arr).toString());
                    res.end('{"a":1}')
                })
                
        }
        return 
    }

    // 靜態服務
    let filePath = path.join(__dirname,pathname);
    fs.stat(filePath,function(err,statObj){
        if(err){
            res.statusCode = 404;
            res.end();
            return
        }
        if(statObj.isFile()){
            // mime header
            fs.createReadStream(filePath).pipe(res);
            return
        }else{
            res.statusCode = 404;
            res.end();
            return
        }
    })
}).listen(3000);
複製代碼

原地址

相關文章
相關標籤/搜索