Fetch API與POST請求那些事

簡述

相信不少前端開發童鞋與後端聯調介面時,都會碰到前端明明已經傳了參數,後端童鞋卻說沒有收到,尤其是post請求,遇到的非常多。本文以node.js作為服務端語言,借用express框架,簡要分析客戶端發送post請求的四種方式以及服務端如何接收。本文客戶端請求沒有藉助第三方ajax庫,採用的是Fetch API,雖然瀏覽器兼容性有點問題,但是用法簡潔靈活,以後可能會是一個趨勢。在說post請求之前,先簡要概述下Fetch API

Fetch API

Fetch API提供了一個獲取資源的介面(包括跨域請求),提供了更強大和靈活的功能集。未來可能是XMLHttpRequest的一種替代方案。去年GitHub程式碼去jQuery重構時,就使用Fetch API替代jQueryajax,畢竟目前JavaScript很多原生語法都進行了大量精簡,比如DOM操作APIhttp請求fetches6+等。今天的axios可能就是明日的jQuery

簡單的實例

Fetch API主要暴露了三個介面一個方法。


  • 三個介面
    • Request(資源請求)
    • Response(請求的響應)
    • Headers(Request/Response頭部資訊)
  • 一個方法
    • fetch()(獲取資源調用的方法)
// 實例化一個Request實例  // 第一個參數一般指資源路徑  // 第二個參數可以理解為請求的配置項,包含頭部資訊和http請求一些關鍵配置(請求類型、參數...)  let requestInstance = new Request('/hello', {      method: 'post',      headers: {          'Content-Type': 'application/json;charset=utf-8'      },      body: '{"hello": "world"}'  })  // fetch方法參數同Request實例  // 第一個參數為url或者Request實例  // 第二個參數為請求配置項  fetch(requestInstance).then(response => {      // 返回的是一個Response的實例      // 調用Response實例的序列化方法,序列化成json,返回值是一個promise      // 序列化方法有 json,text,formData,blob,arrayBuffer,redirct      let result = response.json()      result.then(res => {          consolee.log(res)      })  })  

有意思的特性

Fetch API添加了一個實驗性的功能,支援客戶端手動取消http請求了,這個比較有意思,因為之前的ajax貌似都不支援手動取消。藉助AbortSignal介面,可以通過AbortController實例化一個控制器,將實例的siginal當做請求的配置項,傳遞到服務端,客戶端可以通過AbortController實例的abort方法,來終止當前的http請求,示例程式碼如下:

<template>      <button id='btn'>中止請求</button>  </template>  <script>      // 實例化controller      var controller = new AbortController()      // 獲取實例的signal介面      var signal = controller.signal      let btn = document.getElementById('btn')      // 點擊按鈕,中止請求      btn.addEventListener('click', e => {          controller.abort()      })      // json方式提交數據      const url = 'http://192.168.43.216:3000'      // 將signal介面放到請求配置項中      let testRequest = new Request(url + '/test', {          method: 'post',          headers: {              'Content-Type': 'application/json;charset=utf-8;'          },          body: '{"foo":"bar"}',          signal      })      fetch(testRequest).then(response => {          let result = response.text()          result.then(res => {              console.log(res)          })      })  </script>

post請求四種傳參方式

本文所說的前端傳遞數據格式相對於主流的ajax函數庫有一定區別,一般的ajax函數庫為了方便用戶使用,都會對數據進行二次封裝。本文主要說原始的數據格式交互,具體ajax庫的使用,還是以官方文檔為準。
請求頭(Request Headers)的實體Content-Type用於指示資源的MIME類型,即客戶端傳遞消息的格式;響應頭中Content-Type用於指示服務端返回消息的格式。所以在http請求中,我們可以從報文中的Content-Type屬性來判斷客戶端-服務端消息傳遞的格式。

JSON提交

JSON是常用的一種前後端數據接收格式。前端傳遞的是鍵值對數據,即對象(Object)。採用JSON傳遞參數,請求頭Content-Typeapplication/json;charset=utf-8,其中charset為採用的字符集。

注意點:

  1. 既然為JSON提交,就要對參數進行序列化,即JSON.stringify(params),否則傳遞到服務端的參數可能是[Object object]
  2. 服務端(node.js)是以流的方式進行接收,接收完是一個JSON字元串,調用JSON.parse(params)可以對參數進行序列化

示例程式碼


客戶端:

const url = 'http://192.168.43.216:3000'  let testRequest = new Request(url + '/test', {      method: 'post',      headers: {          'Content-Type': 'application/json;charset=utf-8;'      },      body: JSON.stringify({a: 1})  })  fetch(testRequest).then(response => {      let result = response.text()      result.then(res => {          console.log(res)      })  })

服務端:

router.post('/test', (req, res, next) => {      let data = ''      req.on('data', chunk => {          data += chunk      })      req.on('end', () => {          // 將JSON字元串解析成對象          data = JSON.parse(data)          res.send(data)      })  })

請求資訊:

jsonParams

請求頭提交

在實際開發中,遇到過不少後端開發,喜歡吧請求參數放在請求頭,類似於get請求,即請求的參數是拼接在請求地址後面。個人覺得這種傳參方式並不好,一般瀏覽器對URL長度是有限制的,以Chrome為例,URL最大長度正在7700個字元左右,對於post請求來說,最好參數還是放在body中。

注意點

  1. 客戶端請求參數拼接在url後,在?後,鍵值對寫法a=1,多個鍵值對之間通過連接符&連接
  2. 服務端能夠在request對象中,通過request.query直接進行接收
  3. 由於參數是拼接在url後面,所以請求頭Content-Type無需設置

示例程式碼


客戶端:

let queryStringRequest = new Request(`${url}/querystring?a=1&b=2`, {      method: 'post'  })  fetch(queryStringRequest).then(response => {      let result = response.json()      result.then(res => {          console.log(res)      })  })

服務端:

router.post('/querystring', (req, res, next) => {      res.send(req.query)  })

請求資訊:

querystring

普通表單提交

表單提交的方式有兩種,一種是普通的表單提交,另外一種是通過FormData進行提交(主要應用在文件上傳)。單純的表單提交,與上述兩種參數格式上還是存在一定的差別的,主要體現在以下幾個方面。

  1. Content-Type
    表單提交Request HeadersContent-Typeapplication/x-www-form-urlencoded;charset=utf-8
  2. 參數
    表單提交參數是放在body中,感覺是JSON和請求頭提交的合體。參數位置與JSON提交相同,參數格式與請求頭提交一致

示例程式碼


客戶端:

 let formRequest = new Request(url + '/form', {      method: 'post',      headers: {          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'      },      body: 'a=1&b=2'  })  fetch(formRequest).then(response => {      let result = response.json()      result.then(res => {          console.log(res)      })  })

服務端:

const fs = require('fs')  router.post('/form', (req, res, next) => {      let data = ''      req.on('data', chunk => {          data += chunk      })        req.on('end', () => {          data = decodeURI(data)          // 將a=1&b=2解析成{a: 1, b: 2}          let dataObj = querystring.parse(data)          res.send(dataObj)      })  })

請求資訊:

formdata

FormData提交 (文件上傳)

通常我們在進行文件上傳時,都會採用表單提交。參數放在body中,只不過格式與普通的有差別,具體如下:

  1. 參數需要放在FormData的實例中,通過append進行參數的添加
  2. 請求頭Content-Typemultipart/formdata

示例程式碼


客戶端:

<template>      <input type="file" id="uploadFile">  </template>  <script>  let $input = document.getElementById('uploadFile')  // 監聽文件上傳  $input.addEventListener('change', e => {      let file = e.target.files[0]      handleUploadFile(file)  })    function handleUploadFile (file) {      let bean = new FormData()      bean.append('file', file)      bean.append('hello', 'world')      let uploadFileRequest = new Request(`${url}/upload`, {          method: 'post',          headers: {              'Content-Type': 'multipart/formdata'          },          body: bean      })      fetch(uploadFileRequest).then(response => {          let result = response.text()          result.then(res => {              console.log(res)          })      })  }  </script>

服務端:

router.post('/upload', (req, res, next) => {      let data = []      let size = 0      req.on('data', chunk => {          data.push(chunk)          size += chunk.length      })      let rems = []      req.on('end', () => {          let buffer = Buffer.concat(data, size)          for (let i = 0; i < buffer.length; i++) {              var v = buffer[i];              var v2 = buffer[i+1];              if(v==13 && v2==10){                  rems.push(i);              }          }          // 圖片資訊          var picmsg_1 = buffer.slice(rems[0]+2,rems[1]).toString();          var filename = picmsg_1.match(/filename=".*"/g)[0].split('"')[1];            // 圖片數據          var nbuf = buffer.slice(rems[3]+2,rems[rems.length-2]);          var path = './static/'+filename;          fs.writeFileSync(path , nbuf);          res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8'});          res.end('<div id="path">'+path+'</div>');      })  })

請求資訊:

upload

小結

post請求向服務端提交參數,一般情況下都是放在body中,但是從上文列舉的幾種傳參方式,仍然可以放在請求頭中傳遞,服務端對於在請求頭中傳遞的參數的處理和get請求保持一致。此外,從node.js接收的參數來看,除了放在請求頭中能夠直接獲取外,其餘三種請求方式都是以位元組流的方式傳遞到服務端的。熟悉post請求的幾種傳參方式,有助於我們和後端同學進行介面聯調。

參考資料