web前端面試題對答篇:HTTP fetch發送2次請求的原因?

HTTP fetch發送2次請求的原因?面對這道出現頻率較高的面試題,我想說的是:發送兩次請求的情況確實存在,但這與你所使用的是不是http協議,所採用的是不是fetch真的沒有一毛錢關係!接下來,咱們可以通過代碼一一去驗證……

一、準備工作
1、創建一個文件夾zhangpeiyue
2、在zhangpeiyue文件夾內創建兩個文件:server.jsindex.html
  • server.js:搭建一個express服務器,用於提供接口
  • index.html:通過fetch調用接口。
二、前後端符合同源策略的場景
1、通過server.js創建服務:
const express = require("express");
// 通過 body-parser 接收 post 過來的數據
const bodyParser = require("body-parser");
const app = express();
// 接收 post 的數據爲 application/json 格式
app.use(bodyParser.json());
// 將當前文件夾設置爲靜態資源
app.use(express.static(__dirname));
app.post("/my",(req,res)=>{
    res.json({
        ok:1,
        body:req.body// 將接收到的數據返回給前端
    })
});
app.listen(80,(err)=>{
    console.log("success");
})
2、啓動服務
node server
3、index.html嵌入js:
// 爲避免出現緩存,增加 t 參數
fetch("http://127.0.0.1/my?t="+Date.now(),{
    method:"post",
    body:JSON.stringify({
        a:1,
        b:2
    }),
    headers:{
        "content-type":"application/json"
    }
}).then(res=>res.json())
    .then(response=>console.log("Success:",response))
    .catch(error=>console.log("Error",error))
4、瀏覽器打開http://127.0.0.1測試
5、結論
  • 在同源的情況下並未出現請求兩次的情況
三、fetch在跨域的情況下
1、server.js修改如下:
const express = require("express");
// 通過 body-parser 接收 post 過來的數據
const bodyParser = require("body-parser");
const app = express();
// 接收 post 的數據爲 application/json 格式
app.use(bodyParser.json());
// 將當前文件夾設置爲靜態資源
app.use(express.static(__dirname));
app.all("*",(req,res,next)=>{
    console.log(req.method);
    res.set({
        "Access-Control-Allow-Origin":"*",
        "Access-Control-Allow-Headers":"content-type"
    })
    next();
})
app.post("/my",(req,res)=>{
    res.json({
        ok:1,
        body:req.body// 將接收到的數據返回給前端
    })
});
app.listen(80,(err)=>{
    console.log("success");
})
2、將前端content-type設置爲application/json,然後通過開發工具的http方式在瀏覽器打開index.html,或自己重新創建一個服務,在瀏覽器打開index.html。你會發現其果然請求了兩次,分別爲OPTIONS請求與POST請求:
// 爲避免出現緩存,增加 t 參數
fetch("http://127.0.0.1/my?t="+Date.now(),{
    method:"post",
    body:JSON.stringify({
        a:1,
        b:2
    }),
    headers:{
        "content-type":"application/json"
    }
}).then(res=>res.json())
    .then(response=>console.log("Success:",response))
    .catch(error=>console.log("Error",error))
  • 請求方式:OPTIONS
  • 請求方式:POST
3、將js代碼中的content-type註釋掉,然後在非同源的場景下再次訪問,你會發現只發送了一次post請求。
// 爲避免出現緩存,增加 t 參數
fetch("http://127.0.0.1/my?t="+Date.now(),{
    method:"post",
    body:JSON.stringify({
        a:1,
        b:2
    }),
    headers:{
        // "content-type":"application/json"
    }
}).then(res=>res.json())
    .then(response=>console.log("Success:",response))
    .catch(error=>console.log("Error",error))
  • 只發送了post請求:
4、將content-type更改爲application/x-www-form-urlencoded,再次訪問,依然只發送了一次POST請求:
// 爲避免出現緩存,增加 t 參數
fetch("http://127.0.0.1/my?t="+Date.now(),{
    method:"post",
    body:JSON.stringify({
        a:1,
        b:2
    }),
    headers:{
        "content-type":"application/x-www-form-urlencoded"
    }
}).then(res=>res.json())
    .then(response=>console.log("Success:",response))
    .catch(error=>console.log("Error",error))
  • 只發送了post請求:
5、將fetch改爲XMLHttpRequest。將content-type設置爲application/json。打開index.html,此時會請求兩次,分別爲OPTIONS請求與POST請求:
const xhr = new XMLHttpRequest();
xhr.open("post","http://127.0.0.1/my?t="+Date.now());
xhr.setRequestHeader("content-type","application/json")
xhr.send(JSON.stringify({a:1,b:2}));
xhr.onload = function () {
    console.log(xhr.responseText)
}
  • 請求方式:OPTIONS
  • 請求方式:POST
6、將配置content-type的代碼註釋掉,結果只發送了一次POST請求:
const xhr = new XMLHttpRequest();
xhr.open("post","http://127.0.0.1/my?t="+Date.now());
// xhr.setRequestHeader("content-type","application/json")
xhr.send(JSON.stringify({a:1,b:2}));
xhr.onload = function () {
    console.log(xhr.responseText)
}
  • 請求方式:POST
四、接口的協議爲https:
1、server.js
const express = require("express");
// 通過 body-parser 接收 post 過來的數據
const bodyParser = require("body-parser");
const https = require("https");
const fs = require("fs");
const app = express();
// 創建 https 服務,需要證書與密鑰(需要有自己的域名)
const httpsServer = https.createServer({
    key:fs.readFileSync(__dirname+"/key/weixin.key"),
    cert:fs.readFileSync(__dirname+"/key/weixin.crt")
},app)
// 接收 post 的數據爲 application/json 格式
app.use(bodyParser.json());
app.all("*",(req,res,next)=>{
    console.log(req.method);
    res.set({
        "Access-Control-Allow-Origin":"*"
    })
    next();
})
app.post("/my",(req,res)=>{
    res.json({
        ok:1,
        body:req.body// 將接收到的數據返回給前端
    })
});
httpsServer.listen(443,()=>{
    console.log("httpsServer->success")
})
2、content-type設置爲application/json,然後以http的形式打開頁面。結果會請求兩次,分別爲OPTIONS請求與POST請求:
// 爲避免出現緩存,增加 t 參數
fetch("https://weixin.zhangpeiyue.com/my?t="+Date.now(),{
    method:"post",
    body:JSON.stringify({
        a:1,
        b:2
    }),
    headers:{
        "content-type":"application/json"
    }
}).then(res=>res.json())
    .then(response=>console.log("Success:",response))
    .catch(error=>console.log("Error",error))
  • 請求方式:OPTIONS
  • 請求方式:POST
五、總結

發送2次請求需要滿足以下2個條件:

  • 必須要在跨域的情況下。
  • 除GET、HEAD和POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的跨域請求(我們可以稱爲預檢(Preflighted)的跨域請求)。
    最後,建議大家可以這樣回覆面試官:之所以會發送2次請求,那是因爲我們使用了帶預檢(Preflighted)的跨域請求。該請求會在發送真實的請求之前發送一個類型爲OPTIONS的預檢請求。預檢請求會檢測服務器是否支持我們的真實請求所需要的跨域資源,唯有資源滿足條件纔會發送真實的請求。比如我們在請求頭部增加了authorization項,那麼在服務器響應頭中需要放入Access-Control-Allow-Headers,並且其值中必須要包含authorization,否則OPTIONS預檢會失敗,從而導致不會發送真實的請求。

—————END—————
分享結束!喜歡本文的朋友們,歡迎關注公衆號 張培躍,收看更多精彩內容,謝過!!

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