以express和koa框架爲例,說明nodejs服務器CORS跨域怎麼設置

CORS即Cross-Origin Resource Sharing,跨域資源共享

CORS分爲兩種

一:簡單的跨域請求,流程如下

網頁:當HTTP請求同時滿足以下兩種情況時,瀏覽器認爲是簡單跨請求

1),請求的方法是get,head或者post,同時Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一個值,或者不設置也可以,一般默認就是application/x-www-form-urlencoded。

2),請求中沒有自定義的HTTP頭部,如x-token。(應該是這幾種頭部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)

瀏覽器:把客戶端腳本所在的域填充到Origin header裏,向其他域的服務器請求資源。

服務器:根據資源權限配置,在響應頭中添加Access-Control-Allow-Origin Header,返回結果

瀏覽器:比較服務器返回的Access-Control-Allow-Origin Header和請求域的Origin,如果當前域已經得到授權,則將結果返回給頁面。否則瀏覽器忽略此次響應。

網頁:收到返回結果或者瀏覽器的錯誤提示。

總結:對於簡單的跨域請求,只要服務器設置的Access-Control-Allow-Origin Header和請求來源匹配,瀏覽器就允許跨域。服務器端設置的Access-Control-Allow-Methods和Access-Control-Allow-Headers對簡單跨域沒有作用。

 

二:帶預檢(Preflighted)的跨域請求,流程如下

網頁:當HTTP請求出現以下兩種情況時之一,瀏覽器認爲是帶預檢(Preflighted)的跨域請求:

1),請求的方法不是 GET, HEAD或者POST三種,或者是這三種,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一種。

2),請求中有自定義HTTP頭部。

瀏覽器:發現請求屬於以上兩種情況,向服務器發送一個OPTIONS預檢請求,檢測服務器端是否支持真實請求進行跨域資源訪問,瀏覽器會在發送OPTIONS請求時會自動添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。

服務器:響應OPTIONS請求,會在responseHead裏添加Access-Control-Allow-Methods head。這其中的method的值是服務器給的默認值,可能不同的服務器添加的值不一樣。服務器還會添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。這些取決於服務器對OPTIONS請求具體如何做出響應。如果服務器對OPTIONS響應不合你的要求,你可以手動在服務器配置OPTIONS響應,以應對帶預檢的跨域請求。在配置服務器OPTIONS的響應時,可以添加Access-Control-Max-Age head告訴瀏覽器在一定時間內無需再次發送預檢請求,但是如果瀏覽器禁用緩存則無效。

瀏覽器:接到OPTIONS的響應,比較真實請求的method是否屬於返回的Access-Control-Allow-Methods head的值之一,還有origin, head也會進行比較是否匹配。如果通過,瀏覽器就繼續向服務器發送真實請求。 否則就會報預檢錯誤,如下幾種:

請求來源不被options響應允許:Failed to load...Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8080' is therefore not allowed access.

請求方法不被options響應允許:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

請求中有自定義header不被options響應允許: Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.

服務器:響應真實請求,在響應頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請求的域、請求方法和請求頭,並返回數據。(如果服務器對真實請求的響應另外設置有Access-Control-Allow-Methods,它的值不會生效,個人理解是因爲剛剛在服務器響應OPTIONS響應時,就已經驗證過真實請求的method是屬於Access-Control-Allow-Methods head的值之一)。也可以在響應真實請求時添加Access-Control-Max-Age head。

瀏覽器:接受服務器對真實請求的返回結果,返回給網頁

網頁:收到返回結果或者瀏覽器的錯誤提示。

總結:也就是說Access-Control-Allow-MethodsAccess-Control-Allow-Headers只在響應options請求時有作用,Access-Control-Allow-Origin在響應options請求和響應真實請求時都是有作用的,兩者必須同時包含要跨域的源。

服務器設置OPTIONS響應一般要同時滿足這些條件,一是跨域,二是有帶預檢的請求,三是服務器對OPTIONS響應默認值不符合要求,如果是不存在跨域情況,就不需要在服務器手動設置OPTIONS響應。

 

 

 

 

XMLHttpRequest支持通過withCredentials屬性跨域請求攜帶身份信息(Credential,例如Cookie或者HTTP認證信息)。瀏覽器將攜帶Cookie Header的請求發送到服務器端後,如果服務器沒有響應Access-Control-Allow-Credentials Header,那麼瀏覽器會忽略掉這次響應。如果服務器設置Access-Control-Allow-Credentials爲true,那麼就不能再設置Access-Control-Allow-Origin爲*,必須用具體的域名。

express框架跨域設置:

 

// 以node-express框架爲例
const app = require('express')();
app.options('/', (req, res) => {
//express框架有res.set()和res.header()兩種方式設置header,沒有setHeader方法。
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
        'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST',
        'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type',
        'Access-Control-Max-Age':10000,
        'Access-Control-Allow-Credentials':true
    });
    const obj = {
        "msg": "options請求"
    }
    res.send(obj)
})

app.post('/', (req, res) => {
    console.log('post請求')
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
  // 'Access-Control-Allow-Methods': 'POST',//無需設置。因爲如果是帶預檢的跨域請求時,是否是允許的該請求方法取決於options請求響應時的response head裏的access-control-allow-methods head.如果是簡單的跨域請求,只有Access-Control-Allow-Origin會參與匹配,此設置依然沒有作用。
// 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需設置,原因同上。
    });
    const obj = {
        "msg": "post請求"
    }
    res.send(obj)
})

app.get('/', (req, res, next) => {
    console.log('get請求')
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
    });
    const obj = {
        msg: 'get請求'
    }
    res.send(obj)
})

app.put('/', (req, res) => {
    res.set({
        "Access-Control-Allow-Origin": "http://127.0.0.1:8080"
    });
    const obj = {
        "msg": "put請求"
    }
    res.send(obj)
})
app.listen(3333, function () {
    console.log('express start at port 3333')
})

 

 

 

 

koa框架跨域設置: 

//以node-koa框架爲例
const Koa = require('koa');
const app = new Koa();
const _cors=(ctx,next)=>{
    //指定服務器端允許進行跨域資源訪問的來源域。可以用通配符*表示允許任何域的JavaScript訪問資源,但是在響應一個攜帶身份信息(Credential)的HTTP請求時,必需指定具體的域,不能用通配符
    ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");

    //指定服務器允許進行跨域資源訪問的請求方法列表,一般用在響應預檢請求上
    ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");
    
    //必需。指定服務器允許進行跨域資源訪問的請求頭列表,一般用在響應預檢請求上
    ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
    
    //告訴客戶端返回數據的MIME的類型,這只是一個標識信息,並不是真正的數據文件的一部分
    ctx.set("Content-Type", "application/json;charset=utf-8");
    
    //可選,單位爲秒,指定瀏覽器在本次預檢請求的有效期內,無需再發送預檢請求進行協商,直接用本次協商結果即可。當請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時,服務器會提前發送一次請求進行驗證
    ctx.set("Access-Control-Max-Age", 300);

    //可選。它的值是一個布爾值,表示是否允許客戶端跨域請求時攜帶身份信息(Cookie或者HTTP認證信息)。默認情況下,Cookie不包括在CORS請求之中。當設置成允許請求攜帶cookie時,需要保證"Access-Control-Allow-Origin"是服務器有的域名,而不能是"*";如果沒有設置這個值,瀏覽器會忽略此次響應。
    ctx.set("Access-Control-Allow-Credentials", true);

    //可選。跨域請求時,客戶端xhr對象的getResponseHeader()方法只能拿到6個基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要獲取其他字段時,使用Access-Control-Expose-Headers,xhr.getResponseHeader('myData')可以返回我們所需的值
    ctx.set("Access-Control-Expose-Headers", "myData");

    next()
}
const main = function (ctx) {
    const _method=ctx.request.method;
    ctx.response.body={"請求方式":_method};
};

app.use(_cors)
app.use(main)

app.listen(5000, function () {
    console.log('koa start at port 5000')
})

 

// 前臺代碼,用jqAjax和axios兩種請求方式對比
<!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>
</head>
<body>
  <table>
      <tr>
          <th col="2">Server</th>
      </tr>
    <tr>
      <th><button id="express" class="server" οnclick='changeServer("http://localhost:3333",this)'>express_3333</button></th>
      <th><button id="koa" class="server" οnclick='changeServer("http://localhost:5000",this)'>koa_5000</button></th>
    </tr>
  </table>

  <table>
    <tr>
      <th col="4">content-Type</th>
    </tr>
    <td><button id="x-www-form-urlencoded" class="ContentType" οnclick='changeContentType("application/x-www-form-urlencoded",this)'>application/x-www-form-urlencoded</button></td>
    <td><button class="ContentType" οnclick='changeContentType("multipart/form-data",this)'>multipart/form-data</button></td>
    <td><button class="ContentType" οnclick='changeContentType("text/plain",this)'>text/plain</button></td>
    <td><button class="ContentType" οnclick='changeContentType("application/json",this)'>application/json</button></td>
  </table>

  <table>

    <tr>
        <tr>
            <th col="2">Method</th>
        </tr>
      <td>JQuery</td>
      <td>axios</td>
    </tr>
    <tr>
      <td><button οnclick='jq_request("GET")'>jq_get</button></td>
      <td><button οnclick='axios_request("GET")'>axios_get</button></td>
    </tr>
    <tr>
        <td><button οnclick='jq_request("HEAD")'>jq_head</button></td>
        <td><button οnclick='axios_request("HEAD")'>axios_head</button></td>
      </tr>
    <tr>
      <td><button οnclick='jq_request("POST")'>jq_post</button></td>
      <td><button οnclick='axios_request("POST")'>axios_post</button></td>
    </tr>
    <tr>
      <td><button οnclick='jq_request("PUT")'>jq_put</button></td>
      <td><button οnclick='axios_request("PUT")'>axios_put</button></td>
    </tr>
    <tr>
      <td><button οnclick='jq_request("DELETE")'>jq_delete</button></td>
      <td><button οnclick='axios_request("DELETE")'>axios_delete</button></td>
    </tr>
    <tr>
        <td><button οnclick='jq_request("OPTIONS")'>jq_options</button></td>
        <td><button οnclick='axios_request("OPTIONS")'>axios_options</button></td>
      </tr>
  </table>

</body>
<script src="https://cdn.bootcss.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script>

  let url='http://localhost:3333';
  let contentType="application/x-www-form-urlencoded";

  const changeServer=(a,self)=>{
    url=a;
    $('.server').css('background','#eee')
    $(self).css('background','gray')
  }

  const changeContentType=(e,self)=>{
    contentType=e;
    $('.ContentType').css('background','#eee')
    $(self).css('background','gray')
  }

  $('#koa').click();
  $('#x-www-form-urlencoded').click();

  const jq_request = (method) => {
    $.ajax({
      url: url,
      type: method,
      contentType:contentType,
      dataType: "json",
      success: function (data) {
        console.log(data)
      },
      error: function (err) {
        console.log(err)
      }
    })
  }

  const axios_request=(method)=>{
    axios({
      method: method,
      url: url,
      headers: {
        'Content-Type':contentType,
      },

      responseType: 'json',
    }).then(res => {
      console.log(res.data)
    }).catch(error => {
      console.log(error);
    });
  }
</script>
</html>

 參考了以下幾篇博客:

https://blog.csdn.net/enter89/article/details/51205752

https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md

https://www.cnblogs.com/MrZouJian/p/8568414.html

https://www.jianshu.com/p/5b3acded5182

 

歡迎補充,討論。轉載或引用請註明出處。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章