Web全棧開發學習筆記—Part3 用NodeJS和Express寫服務端程式—a.Node.js 與 Express

旭日東歌發表於2021-01-03

目錄

Simple web server

Express

Web and express

nodemon

REST

Fetching a single resource

Deleting resources

Postman

Receiving data

About HTTP request types

Middleware


現在重點轉向後端,也就是轉向伺服器端的功能實現。

NodeJS基礎上構建的後端,是基於 Google 的 Chrome V8 引擎的 JavaScript 執行時環境。

確保 Node 版本不低於v10.18.0 (可以通過在命令列中執行 node -v 來檢查版本)。

瀏覽器還不支援 JavaScript 的最新特性,在瀏覽器中執行的程式碼必須是babel轉譯過的。而在後端執行 JavaScript 的情況不同, 最新版本的 Node 支援大部分最新的 JavaScript 特性,因此可以使用最新的特性而不必轉譯程式碼。

我們的目標是實現一個後端,與 notes 應用一起工作。

從實現經典的“ hello world”應用的基礎開始。

之前已經提到了npm ,這是一個用於管理 JavaScript 包的工具,來源於 Node 生態系統。

進入一個合適的目錄,並使用npm init命令為應用建立一個新模板。 設定的結果會在專案根目錄下自動生成的package.json 檔案中,其中包含有關專案的資訊。

{
  "name": "backend",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Matti Luukkainen",
  "license": "MIT"
}

例如,該檔案定義應用的入口點是index.js 檔案。

讓我們對scripts 物件做一個小小的修改:

{
  // ...
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
}

接下來,建立應用的第一個版本,在專案的根目錄中新增一個index.js 檔案,程式碼如下:

console.log('hello world')

可以通過命令列直接用 Node 執行程式:

node index.js

或者將它作為一個 npm 指令碼執行:

npm start

start 這個npm 指令碼之所以有效,是因為 package.json 檔案中定義了它:

{
  // ...
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
}

儘管通過從命令列呼叫 node index.js 來啟動專案是可以工作的,但 npm 專案通常執行 npm 指令碼之類的任務。

預設情況下,package.json 檔案還定義了另一個常用的 npm 指令碼,稱為npm test。 由於我們的專案還沒有測試庫,npm test 命令只是執行如下命令:

echo "Error: no test specified" && exit 1

 

Simple web server

【簡單的 web 伺服器】

把這個應用改成一個 web 伺服器:

const http = require('http')

const app = http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('Hello World')
})

const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)

一旦執行應用,控制檯中就會輸出如下訊息:

Server running on port 3001

我們可以在瀏覽器中通過訪問地址 http://localhost:3001 開啟我們的應用:

fullstack content

事實上,無論 URL 的後半部分是什麼,伺服器的工作方式都是相同的。 地址http://localhost:3001/foo/bar 也會顯示相同的內容。

注意:如果埠3001已經被其他應用使用,那麼啟動伺服器將產生如下錯誤訊息:

➜  hello npm start

> hello@1.0.0 start /Users/mluukkai/opetus/_2019fullstack-code/part3/hello
> node index.js

Server running on port 3001
events.js:167
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE :::3001
    at Server.setupListenHandle [as _listen2] (net.js:1330:14)
    at listenInCluster (net.js:1378:12)

 要麼關閉使用3001埠應用,要麼為此應用使用不同的埠。

程式碼的第一行:

const http = require('http')

匯入 Node 的內建 web server模組。 這實際上是我們在瀏覽器端程式碼中已經做過的事情,只是語法稍有不同:

import http from 'http'

在瀏覽器中執行的程式碼使用 ES6模組。 模組定義為export ,並與import一起使用。

而Node.js 使用 CommonJS。 原因在於,早在 JavaScript 在語言規範中支援模組之前,Node 生態系統就有對模組需求,Node 還不支援 ES6模組 。

Commonjs 模組的功能幾乎完全類似於 ES6模組。

程式碼中的下一塊:

const app = http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('Hello World')
})

該程式碼使用 http 模組的 createServer 方法建立一個新的 web 伺服器。 一個事件處理 被註冊到伺服器,每次 向伺服器的地址http://localhost:3001 發出 HTTP 請求時,它就被呼叫。

響應請求的狀態程式碼為200,Content-Type 標頭檔案設定為 text/plain,將返回站點的內容設定為Hello World

最後一行將繫結的HTTP 伺服器分配給 app 變數 ,並監聽傳送到埠3001的 HTTP 請求:

const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)

需要後端伺服器的主要用途是向前端提供 JSON 格式的原始資料。 更改伺服器,返回 JSON 格式的“硬編碼”便箋列表:

const http = require('http')

let notes = [
  {
    id: 1,
    content: "HTML is easy",
    date: "2019-05-30T17:30:31.098Z",
    important: true
  },
  {
    id: 2,
    content: "Browser can execute only Javascript",
    date: "2019-05-30T18:39:34.091Z",
    important: false
  },
  {
    id: 3,
    content: "GET and POST are the most important methods of HTTP protocol",
    date: "2019-05-30T19:20:14.298Z",
    important: true
  }
]

const app = http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'application/json' })  
  response.end(JSON.stringify(notes))
})

const PORT = 3001
app.listen(PORT)
console.log(`Server running on port ${PORT}`)

重新啟動伺服器(可以通過在控制檯中按 Ctrl + c 關閉伺服器) ,並重新整理瀏覽器。

Content-Type 頭中的 application/json 值通知接收方資料為 JSON 格式。 使用 JSON.stringify(notes) 方法將 notes 陣列轉換為 JSON。

當開啟瀏覽器的時候,顯示的格式:

fullstack content

 

Express

直接使用 Node 內建的http web 伺服器實現我們的伺服器程式碼是可行的。 但是當應用規模變大時會很麻煩。

許多庫提供比內建的 http 模組更友好的介面,以簡化使用 Node 作為伺服器端開發。express為構建後臺伺服器的一般的用例提供一個很好的抽象。

通過下面的命令將它定義為一個專案依賴,來開始使用 express:

npm install express

該依賴項也被新增到了我們的package.json 檔案中:

{
  // ...
  "dependencies": {
    "express": "^4.17.1"
  }
}

依賴的原始碼安裝在專案根目錄中的 node_modules 目錄中。 

 

這些實際上是express的依賴項,以及它所有依賴項的依賴項,等等。 這些被稱為專案的 傳遞依賴transitive dependencies 。

使用如下命令更新專案的依賴:

npm update

同樣,如果在另一臺計算機上開始工作,可以使用如下命令安裝package.json 中定義的專案的所有最新依賴項:

npm install

如果依賴項的major值沒有改變,那麼新版本應該是向後相容backwards compatible。 這意味著,如果我們的應用在將來碰巧使用了 express 的版本4.99.175,那麼在這個部分中實現的所有程式碼仍然可以在不對程式碼進行更改的情況下正常工作。 相比之下,未來的5.0.0。 Express版本 可能包含may contain更改,將導致應用不能正常工作。

 

Web and express

回到應用,並進行如下更改:

const express = require('express')
const app = express()

let notes = [
  ...
]

app.get('/', (req, res) => {
  res.send('<h1>Hello World!</h1>')
})

app.get('/api/notes', (req, res) => {
  res.json(notes)
})

const PORT = 3001
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

必須重新啟動應用來更新伺服器狀態。

程式碼的開頭匯入了 express,這次是一個function ,用於建立一個儲存在 app 變數中的 express 應用:

const express = require('express')
const app = express()

接下來,定義應用的兩個路由。 第一個定義了一個事件處理,用於處理對應用的 / root 發出的 HTTP GET 請求:

app.get('/', (request, response) => {
  response.send('<h1>Hello World!</h1>')
})

事件處理接受兩個引數。 第一個request 引數包含 HTTP 請求的所有資訊,第二個 response 引數用於定義請求的響應方式。

程式碼中,請求是通過使用 response 物件的send 方法來應答的。 呼叫該方法,使伺服器通過傳送 <h1>Hello World!</h1>字串,以response響應 HTTP 請求。 由於引數是一個字串,所以 express 會自動將Content-Type 頭的值設定為 text/html.。 響應的狀態程式碼預設為200。

可以通過開發工具中的Network 選項卡來驗證這一點:

fullstack content

第二個路由定義了一個事件處理,它處理對應用的notes 路徑發出的 HTTP GET 請求:

app.get('/api/notes', (request, response) => {
  response.json(notes)
})

請求用response物件的json方法進行響應。 呼叫該方法會將notes 陣列作為 JSON 格式的字串進行傳遞。 Express 自動設定Content-Type 標頭檔案,其值為 application/json

fullstack content

Node 的早期版本必須使用 JSON.stringify 方法將資料轉換為 JSON 格式:

response.end(JSON.stringify(notes))

而 express自動轉換,不再需要這樣做。

JSON是一個字串,而不是像分配給 notes 的值那樣的 JavaScript 物件。

下面的實驗可以說明這一點:

fullstack content

上面的實驗是在互動式的node-repl中完成的。 可以通過在命令列中鍵入 node 來啟動互動式 node-repl。 在編寫應用程式碼時,對於測試命令的工作方式,repl 特別有用,強烈推薦!

 

nodemon

對應用的程式碼進行更改後,必須重新啟動應用以更新。 鍵入 ⌃+C 首先關閉應用,然後重新啟動應用。 與 React 中方便的工作流程相比,Node就有點麻煩,在 React 中,瀏覽器會在進行更改後自動重新載入。

解決這個問題的方法是使用nodemon :

nodemon 將監視啟動 nodemon 的目錄中的檔案,如果任何檔案發生更改,nodemon 將自動重啟node應用。

nodemon 定義為開發依賴development dependency:

npm install --save-dev nodemon

package.json 的內容也發生了變化:

{
  //...
  "dependencies": {
    "express": "^4.17.1",
  },
  "devDependencies": {
    "nodemon": "^2.0.2"
  }
}

如果不小心敲錯了命令,並且 nodemon 依賴項被新增到“ dependencies”而不是“ devDependencies” ,那麼手動更改package.json 的內容以匹配上面顯示的內容也是可以的。

開發依賴會指向僅在應用開發過程中需要的工具,例如用於測試或自動重啟應用的工具,如nodemon

當應用在生產伺服器(例如 Heroku)的生產模式下執行時,並不需要這些開發依賴項。

可以用nodemon 這樣來啟動我們的應用:

node_modules/.bin/nodemon index.js

對應用程式碼的更改會導致伺服器自動重新啟動。 但即使後端伺服器自動重啟,瀏覽器仍然需要手動重新整理。 

package.json 檔案中將這個命令定義一個專用的npm 指令碼:

{
  // ..
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ..
}

在指令碼中,不需要指定node_modules/.bin/nodemon 到 nodemon ,因為 npm 自己知道從該目錄搜尋檔案。

現在可以在開發模式下使用如下命令啟動伺服器:

npm run dev

start 和test 指令碼不同,還必須將run 新增到命令中。

 

REST

擴充套件應用,使它提供像json-server那樣的 RESTful HTTP API 。

Representational State Transfer,又名REST,是一種架構風格,用於構建可伸縮的 web 應用。

我們只關注web應用對 RESTful API 的典型理解, narrow view。 Rest 的最初定義實際上並不侷限於 web 應用。

應用中像便箋這樣的單數實體,在 RESTful thinking 中稱為resource。 每個resource都有一個相關聯的 URL,這個 URL 是資源的唯一地址。

一個約定是結合resource 型別名稱和resource的唯一識別符號來建立resource唯一的地址。

假設服務的根 URL 是 www.example.com/api 

如果我們將便箋的資源型別定義為note,那麼標識為10的便箋資源的地址就是唯一的地址www.example.com/api/notes/10

所有便箋資源的整個集合的 URL 是 www.example.com/api/notes 

我們可以對資源執行不同的操作。要執行的操作由 HTTP動詞 verb 定義:

URLverbfunctionality
notes/10GETfetches a single resource
notesGETfetches all resources in the collection
notesPOSTcreates a new resource based on the request data
notes/10DELETEremoves the identified resource
notes/10PUTreplaces the entire identified resource with the request data
notes/10PATCHreplaces a part of the identified resource with the request data
   

以上粗略地定義 REST 所指的 統一介面 uniform interface ,一種一致的定義介面的方式,使系統能夠進行合作。

 

Fetching a single resource

【獲取一個單一資源】

擴充套件應用,以提供一個 REST 介面,用於操作單個便箋。 首先建立一個路由來獲取單個資源。

我們為單個便箋使用的唯一地址是 notes/10,其中末尾的數字指的是便箋的唯一 id 號。

可以使用冒號語法為express路由定義引數 :

app.get('/api/notes/:id', (request, response) => {
  const id = request.params.id
  const note = notes.find(note => note.id === id)
  response.json(note)
})

現在, app.get('/api/notes/:id', ...)將處理所有的 HTTP GET 請求,這些請求的格式是/api/notes/SOMETHING,其中SOMETHING 是任意的字串。

請求路由中的id 引數可以通過request物件訪問:

const id = request.params.id

使用 find 方法查詢與 id 引數匹配的的便箋,返回給request的傳送者。

當在瀏覽器中鍵入 http://localhost:3001/api/notes/1 測試應用時,不能正常工作,瀏覽器顯示一個空白頁面。 

在程式碼中新增 console.log 命令是一個久經驗證的技巧:

app.get('/api/notes/:id', (request, response) => {
  const id = request.params.id
  console.log(id)
  const note = notes.find(note => note.id === id)
  console.log(note)
  response.json(note)
})

在瀏覽器中再次訪問 http://localhost:3001/api/notes/1 時,終端控制檯將顯示如下內容:

fullstack content

來自 route 的 id 引數被傳遞給應用,但是 find 方法沒有找到匹配的便箋。

在傳遞給 find 方法的比較函式中新增console log。

app.get('/api/notes/:id', (request, response) => {
  const id = request.params.id
  const note = notes.find(note => {
    console.log(note.id, typeof note.id, id, typeof id, note.id === id)
    return note.id === id
  })
  console.log(note)
  response.json(note)
})

當我們在瀏覽器中再次訪問 URL 時,對比較函式的每次呼叫都會向控制檯列印一些不同的內容。 控制檯輸出如下:

1 'number' '1' 'string' false
1‘ number’’1’‘ string’ false
2 'number' '1' 'string' false
2‘ number’’1’‘ string’ false
3 'number' '1' 'string' false
3‘ number’’1’‘ string’ false

這個錯誤的原因很清楚了。 id 變數包含一個字串“1” ,而便箋的 id 是整數。 在 JavaScript 中,“三個等號 triple equals”比較預設認為不同型別的所有值都不相等,這意味著1不等於“1”。

將 id 引數從一個字串更改為一個number來解決這個問題:

app.get('/api/notes/:id', (request, response) => {
  const id = Number(request.params.id)
  const note = notes.find(note => note.id === id)
  response.json(note)
})

現在可以正常獲取單個資源了。

fullstack content

然而應用還有另一個問題。如果我們搜尋一個 id 不存在的便箋,伺服器會響應:

fullstack content

返回的 HTTP狀態碼還是200,這意味著響應成功了。 content-length 標頭的值為0,因為沒有將資料與響應一起傳送回來。

出現此行為的原因是,如果沒有找到匹配的便箋,則將note變數設定為了undefined。 伺服器應該用狀態碼404 not found響應,而不是200。

對程式碼進行如下更改:

app.get('/api/notes/:id', (request, response) => {
  const id = Number(request.params.id)
  const note = notes.find(note => note.id === id)
  
  if (note) {
    response.json(note)
  } else {
    response.status(404).end()
  }
})

由於響應沒有附加任何資料,我們使用status方法來設定狀態,並使用end方法來響應request而不傳送任何資料。

現在應用正常工作,如果沒有找到便箋,則傳送錯誤狀態程式碼。 然而,應用不會返回任何東西顯示給使用者,就像我們 在web 應用訪問一個不存在的頁面時所做的那樣。 不需要在瀏覽器中顯示任何內容,因為 REST API 是用於程式設計使用的介面,只需要錯誤狀態程式碼就行了。

 

Deleting resources

【刪除資源】

接下來實現一個刪除資源的路由。 通過向資源的 url 發出 HTTP DELETE 請求來刪除:

app.delete('/api/notes/:id', (request, response) => {
  const id = Number(request.params.id)
  notes = notes.filter(note => note.id !== id)

  response.status(204).end()
})

刪除資源成功意味著便箋存在並被刪除,用狀態碼204 no content響應請求,並返回沒有資料的響應。

如果資源不存在,對於應該向 DELETE 請求返回什麼狀態程式碼並沒有共識。 實際上,只有204和404兩個可選項。 為了簡單起見,我們的應用在這兩種情況下都將響應204。

 

Postman

可以通過瀏覽器進行 HTTP GET 請求很容易測試刪除操作。 

使用工具讓後端的測試變得更加容易。 其中之一就是命令列程式curl 。這裡我們將使用 Postman 來測試應用。

安裝 Postman 並嘗試一下:

fullstack content

使用Postman在這種情況下很容易。 定義 url 然後選擇正確的請求型別就足夠了。

後端伺服器響應正確。 通過向http://localhost:3001/api/notes 發出 HTTP GET 請求,可以看到 id 為2的便箋已經不在列表中,表明刪除成功。

因為應用的便箋只儲存到了記憶體中,所以當重新啟動應用時,便箋列表將返回到原始狀態。

 

Receiving data

【接受資料】

接下來向伺服器新增新便箋。 通過向地址 HTTP://localhost:3001/api/notes 傳送一個 HTTP POST 請求,並以 JSON 格式在請求body中傳送新便箋的所有資訊,就可以新增一個便箋。

express json-parser幫助我們方便地訪問資料,它與命令app.use(express.json())一起使用。

啟用 json-parser 並實現一個處理 HTTP POST 請求的初始處理程式:

const express = require('express')
const app = express()

app.use(express.json())

//...

app.post('/api/notes', (request, response) => {
  const note = request.body
  console.log(note)

  response.json(note)
})

事件處理函式可以從request 物件的body 屬性訪問資料。

如果沒有 json-parser,body 屬性將是undefined的。 Json-parser 的功能是獲取請求的 JSON 資料,將其轉換為 JavaScript 物件,然後在呼叫路由處理程式之前將其附加到請求物件的 body 屬性。

目前,除了將接收到的資料列印到控制檯並在響應中將其傳送回來之外,應用並不對其執行任何操作。

在實現應用邏輯的剩餘部分之前,讓我們先用 Postman 驗證伺服器實際接收到的資料。 除了在 Postman 中定義 URL 和請求型別外,還必須定義body 中傳送的資料:

fullstack content

該應用將我們在請求中傳送到控制檯的資料列印出來:

fullstack content

注意:在後端工作時,應該讓執行應用的終端始終可見。 Nodemon允許我們對程式碼所做的任何更改都將重新啟動應用。注意控制檯,會立即發現應用中出現的錯誤:

fullstack content

類似地,檢查控制檯以確保後端在不同情況下的行為與我們期望的一樣,比如在使用 HTTP POST 請求傳送資料時。 當然,在開發應用時向程式碼中新增一些 console.log 命令是一個不錯的主意。

導致問題的一個潛在原因是在請求中錯誤地設定了Content-Type 頭。 如果body型別沒有正確定義,這種情況可能發生在 Postman 身上:

fullstack content

Content-Type 的header設定為了 text/plain

fullstack content

伺服器似乎只接收到一個空物件:

fullstack content

如果頭部沒有設定正確的值,伺服器將無法正確解析資料。 

如果在程式碼中的某個位置使用 console.log(request.headers) 命令列印所有請求頭,那麼您將能夠發現缺少了Content-Type 頭。

回到應用。 一旦我們知道應用正確地接收了資料,就可以處理最終請求了:

app.post('/api/notes', (request, response) => {
  const maxId = notes.length > 0
    ? Math.max(...notes.map(n => n.id)) 
    : 0

  const note = request.body
  note.id = maxId + 1

  notes = notes.concat(note)

  response.json(note)
})

我們需要唯一的 id。 首先,找出當前列表中最大的 id 號,並將其賦值給 maxId 變數。 然後將新通知的 id 定義為 maxId + 1。 

現在仍然存在 HTTP POST 請求可新增任意屬性的問題。 可以通過定義content 屬性不能為空來改進應用。important 和date 屬性將被賦予預設值。 所有其他屬性都被丟棄:

const generateId = () => {
  const maxId = notes.length > 0
    ? Math.max(...notes.map(n => n.id))
    : 0
  return maxId + 1
}

app.post('/api/notes', (request, response) => {
  const body = request.body

  if (!body.content) {
    return response.status(400).json({ 
      error: 'content missing' 
    })
  }

  const note = {
    content: body.content,
    important: body.important || false,
    date: new Date(),
    id: generateId(),
  }

  notes = notes.concat(note)

  response.json(note)
})

為便箋生成新 id 號的邏輯已經提取到一個單獨的 generateId 函式中。

如果接收到的資料缺少content 屬性的值,伺服器將使用狀態碼400 bad request響應請求:

if (!body.content) {
  return response.status(400).json({ 
    error: 'content missing' 
  })
}

呼叫 return 是至關重要的,否則程式碼將執行到最後才能將格式不正確的通知儲存到應用中。

如果 content 屬性具有值,則說明便箋內容將基於接收到的資料。 前面提到,在伺服器上生成時間戳比在瀏覽器上生成更好,因為我們不能確保執行瀏覽器的主機的時鐘設定是正確的。 現在由伺服器生成date 屬性。

如果缺少important 屬性,則將該值預設為false。 當前生成預設值的方式相當奇怪:

important: body.important || false,

如果儲存在 body 變數中的資料具有important 屬性,則表示式將計算它作為值。 如果該屬性不存在,那麼表示式將預設為 false,該表示式在雙豎線的右側定義。

還有一件事,生成 id 的函式現在是這樣的:

const generateId = () => {
  const maxId = notes.length > 0
    ? Math.max(...notes.map(n => n.id))
    : 0
  return maxId + 1
}

函式體包含一行內容:

Math.max(...notes.map(n => n.id))

notes.map(n => n.id) 建立一個包含所有便箋 id 的新陣列。 Math.max返回傳遞給它的數的最大值。 然而,notes.map(n => n.id) 是一個陣列,因此它不能直接作為 Math.max 的引數。 陣列可以通過使用“ 三個點...展開語法 轉換為單獨的數字。

 

About HTTP request types

【關於 HTTP 請求型別】

HTTP 標準討論了與請求型別相關的兩個屬性,安全 和 冪等性 。

Http GET 請求應該滿足安全性:

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". 

特別是,已經建立了一個約定,即 GET 和 HEAD 方法除了檢索之外不應該有其他行動的含義。 這些方法應該被認為是“安全的”。

安全性意味著執行請求不能在伺服器中引起任何副作用。 副作用是指資料庫的狀態不能因請求而改變,響應只能返回伺服器上已經存在的資料。

沒有什麼能夠保證 GET 請求實際上是安全的,這實際上只是 HTTP 標準中定義的一個建議。 通過遵守我們的 API 中的 RESTful 原則,GET 請求實際上總是以一種安全safe 的方式使用。

Http 標準還定義了應該是安全的請求型別HEAD。 HEAD 應該像 GET 一樣工作,但是它只返回狀態碼和響應頭。 發出 HEAD 請求不會返回響應主體。

除了 POST 之外的所有 HTTP 請求都應該是冪等:

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property 

方法也可以具有“冪等”屬性,即(除了錯誤或過期問題) N > 0 相同請求的副作用與單個請求相同。 方法 GET、 HEAD、 PUT 和 DELETE 都具有此屬性

如果一個請求有副作用,那麼無論傳送多少次請求,結果都應該是相同的。

如果我們對 /api/notes/10 發出 HTTP PUT 請求,並且在發出請求時傳送資料{ content: "no side effects!", important: true },結果是相同的,不管請求被髮送多少次。

就像 GET 請求的安全性 一樣,冪等也只是 HTTP 標準中的一個推薦,而不是僅僅基於請求型別就可以保證的東西。 但是,當我們的 API 遵循 RESTful 原則時,GET、 HEAD、 PUT 和 DELETE 請求的使用方式是等冪的。

Post 是唯一既不是安全性 也不是冪等 的 HTTP 請求型別。 如果我們向 /api/notes 傳送5個不同的 HTTP POST 請求,其中包含 {content: "many same", important: true},那麼伺服器上得到的5個便箋將具有相同的內容。

 

Middleware

【中介軟體】

之前使用的 express json-parser是所謂的中介軟體

中介軟體是可用於處理請求和響應物件的函式。

json-parser 從請求物件中儲存的請求中獲取原始資料,將其解析為一個 JavaScript 物件,並將其作為一個新的屬性、body 分配給請求物件。

在實踐中,可以同時使用多箇中介軟體。 有多個的時候,將按照順序,一個接一個地執行。

實現中介軟體,列印有關傳送到伺服器的每個請求的資訊。

中介軟體是一個接收三個引數的函式:

const requestLogger = (request, response, next) => {
  console.log('Method:', request.method)
  console.log('Path:  ', request.path)
  console.log('Body:  ', request.body)
  console.log('---')
  next()
}

在函式體的末尾,呼叫作為引數傳遞的下一個函式。 函式將控制權交給下一個中介軟體。

中介軟體是這樣使用的:

app.use(requestLogger)

中介軟體函式按照與express伺服器物件的使用方法一起使用的順序呼叫。 注意,json-parser 是在 requestLogger 中介軟體之前使用的,否則在執行日誌記錄器時,不會初始化 request.body !

如果希望在呼叫路由事件處理程式之前執行中介軟體函式,則必須在路由之前使用中介軟體函式。 還有一些情況,我們希望在路由之後定義中介軟體函式,這意味著我們定義的中介軟體函式只有在沒有路由處理 HTTP 請求的情況下才被呼叫。

在路由之後新增如下中介軟體,用於捕獲對不存在的路由發出的請求。 對於這些請求,中介軟體將返回 JSON 格式的錯誤訊息。

const unknownEndpoint = (request, response) => {
  response.status(404).send({ error: 'unknown endpoint' })
}

app.use(unknownEndpoint)

 

相關文章