在 Node.js 中發(fā)出 HTTP 請求的五種方法
原文來源:https://blog.logrocket.com/5-ways-make-http-requests-node-js/
原文作者:Geshan Manandhar
譯者:一川
在 Node.js 中發(fā)出 HTTP 請求的方法有多種??梢酝ㄟ^使用 Node.js 提供的標準內置 HTTP/HTTPS 模塊、利用 Node 環(huán)境中包含的 Fetch API 或選擇第三方 npm 包來簡化流程來實現(xiàn)此目的。
在本文中,將探索本機 HTTPS 模塊和 Fetch API,并研究流行的 npm 包,例如 Axios、Got、superagent 和 node-fetch,以促進高效地發(fā)出 HTTP 請求。
將使用每個 HTTP 客戶端向 JSONPlaceholder API 發(fā)出 GET 請求。它將向我們發(fā)送 10 個用戶的數(shù)據(jù)。將在控制臺上記錄每個用戶名和 ID。
Let’s get started!
標準 Node.js HTTP(S) 模塊
Node.js 帶有內置的 HTTP 和 HTTPS 模塊。在下面的示例中,使用 HTTPS 模塊對占位符 API 執(zhí)行 GET 請求:
const https = require('https');
https.get('https://jsonplaceholder.typicode.com/users', res => {
let data = [];
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.statusCode);
console.log('Date in Response header:', headerDate);
res.on('data', chunk => {
data.push(chunk);
});
res.on('end', () => {
console.log('Response ended: ');
const users = JSON.parse(Buffer.concat(data).toString());
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
});
}).on('error', err => {
console.log('Error: ', err.message);
});
讓我們看一下代碼,需要使用nodejs內置的 https 模塊,該模塊在任何標準 Node.js 安裝中都可用。無需 package.json 文件或 npm install 即可開始使用它。
接下來,將 data 初始化為空數(shù)組,并記錄響應標頭中的狀態(tài)代碼和日期。每當獲得一塊數(shù)據(jù)時,就把它推入 data 數(shù)組中。收到所有響應后,連接數(shù)據(jù)數(shù)組,將其轉換為字符串,并解析 JSON 以獲取用戶列表。循環(huán)訪問用戶并將用戶 ID 和名稱記錄到控制臺。
這里需要注意一件事:如果請求出現(xiàn)錯誤,錯誤消息將記錄在控制臺上。上述代碼可作為拉取請求使用。
您可以使用 node native-https.js 命令執(zhí)行上面的代碼,前提是您將文件命名為 native-https.js 。它應該顯示如下輸出:
圖片
可以使用相同的方法來運行本文中的所有其他示例;他們將顯示類似的輸出。打印狀態(tài)代碼、響應標頭中的日期以及響應正文中的用戶 ID 和名稱。
內置Fetch API
Node.js 在 v16.15.0 中提供了 Fetch API 的瀏覽器兼容實現(xiàn)的實驗版本,并在 Node v21 中變得穩(wěn)定。
Fetch 在環(huán)境中本身可用,無需導入或單獨需要。這個內置 API 具有多種優(yōu)勢:無需持續(xù)維護,最大限度地減少安全問題,并且不會影響捆綁包大小或通常與第三方軟件包相關的許可問題。
您可以將 Fetch API 與 async/await 或 Promise 鏈結合使用:
(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const headerDate = res.headers && res.headers.get('date') ? res.headers.get('date') : 'no response date';
console.log('Status Code:', res.status);
console.log('Date in Response header:', headerDate);
const users = await res.json();
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
} catch (err) {
console.log(err.message); //can be console.error
}
})();
由于 Fetch API 與瀏覽器兼容,因此您為使用 Fetch API 在瀏覽器中獲取數(shù)據(jù)而編寫的代碼也可以在 Node.js 中使用而無需修改,反之亦然。
Axios
Axios 是一個非常流行的基于 Promise 的請求庫。它是一個適用于瀏覽器和 Node.js 的 HTTP 客戶端。它還包括一些方便的功能,例如攔截請求和響應數(shù)據(jù),以及自動將請求和響應數(shù)據(jù)轉換為 JSON。
const axios = require('axios');
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.status);
console.log('Date in Response header:', headerDate);
const users = res.data;
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
})
.catch(err => {
console.log('Error: ', err.message);
});
上面的示例中的代碼比前一個示例中的代碼少,因為它使用了 Promise 鏈。但是,您可以將其變成 async/await。
解釋一下上面的例子做了什么。需要使用 axios 庫,然后使用 axios.get 方法向 JSONPlaceholder API 發(fā)出 GET 請求。使用承諾鏈來處理響應。在 then 方法回調中,將狀態(tài)代碼和日期記錄到控制臺。
Axios 將響應數(shù)據(jù)轉換為開箱即用的 JSON。上例中的響應數(shù)據(jù)是用戶數(shù)組。循環(huán)遍歷它并將用戶 ID 和名稱記錄到控制臺。
Got
Got 是 Node.js 的另一個流行的 HTTP 請求庫。 Got 具有基于承諾的 API,其 HTTP/2 支持和分頁 API 是其獨特的特點。
const got = require('got');
got.get('https://jsonplaceholder.typicode.com/users', {responseType: 'json'})
.then(res => {
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.statusCode);
console.log('Date in Response header:', headerDate);
const users = res.body;
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
})
.catch(err => {
console.log('Error: ', err.message);
});
上面的代碼示例與 Axios 類似,但有兩個主要區(qū)別:
- 我們需要將 {responseType: 'json'} 作為第二個參數(shù)傳遞給 get 方法,以指示響應為 JSON 格式
- 狀態(tài)代碼標頭稱為 statusCode ,而不是 status
其他的與之前對 axios 的要求保持一致。您可以在此拉取請求中看到上面的示例。
superagent
superagent 于 2011 年 4 月由 VisionMedia 首次發(fā)布,是最古老的 Node.js 請求包之一。 superagent 將自己定位為“小型、漸進式客戶端 HTTP 請求庫和 Node.js 模塊,具有相同的 API,支持許多高級 HTTP 客戶端功能?!彼峁┗诨卣{和基于承諾的 API。 superagent 有幾個插件,您可以使用它來擴展其功能。
const superagent = require('superagent');
(async () => {
try {
const res = await superagent.get('https://jsonplaceholder.typicode.com/users');
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.statusCode);
console.log('Date in Response header:', headerDate);
const users = res.body;
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
} catch (err) {
console.log(err.message); //can be console.error
}
})();
Superagent 已經(jīng)成熟且經(jīng)過實戰(zhàn)考驗,因此非??煽?。我們還可以使用 SuperTest 庫測試超級代理調用。與前面的示例一樣,上面的超級代理示例可作為拉取請求使用。
node-fetch
node-fetch 是 Node.js 的另一個非常流行的 HTTP 請求庫 - 根據(jù) npm 趨勢,在 2024 年 2 月的第一周,它的下載量超過 5000 萬次。
用他們自己的話來說,“node-fetch 是一個輕量級模塊,它將 Fetch API ( window.fetch ) 引入 Node.js?!逼涔δ馨ㄅc基于瀏覽器的 window.fetch 以及本機 Promise 和異步函數(shù)的一致性。
const fetch = require('node-fetch');
(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const headerDate = res.headers && res.headers.get('date') ? res.headers.get('date') : 'no response date';
console.log('Status Code:', res.status);
console.log('Date in Response header:', headerDate);
const users = await res.json();
for(user of users) {
console.log(`Got user with id: ${user.id}, name: ${user.name}`);
}
} catch (err) {
console.log(err.message); //can be console.error
}
})();
讓我們回顧一下與使用 superagent 和 async/await 的示例相比的一些差異:
- node-fetch 不需要顯式的 GET 方法; HTTP 動詞可以作為第二個參數(shù)中的 method 鍵發(fā)送,該參數(shù)是一個對象。例如: {method: 'GET'}
- 另一個區(qū)別是標頭是一個對象,具有 get 方法來獲取標頭值。我們調用 res.headers.get('date') 來獲取日期響應頭的值
Node HTTP請求方式對比
除了內置的 HTTP/HTTPS 模塊和內置的 fetch API 之外,所有其他四個 HTTP 客戶端庫都可以作為 npm 包提供。以下是根據(jù) npm 趨勢顯示的過去六個月每周下載統(tǒng)計數(shù)據(jù)的快速概覽:
從每月下載量來看,過去六個月中,node-fetch 最受歡迎,而 superagent 則最不受歡迎。為了更全面地了解它們的受歡迎程度,讓我們檢查其他指標,從 Got GitHub 存儲庫上提供的比較表中獲取見解:
從上表來看,node-fetch 是下載次數(shù)最多的軟件包,最大安裝大小為 7.45MB。 Axios 擁有最多的 GitHub 星數(shù),達到 10.3 萬——比其他所有三個庫的總和還多。
使用 Express.js 實現(xiàn) HTTP 服務
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.send("Hello world!");
});
app.listen(PORT, () => {
console.log(`Your app is listening on port ${PORT}`);
});
這就是使用 Express.js 實現(xiàn)基本 HTTP 服務的方式。
處理Node HTTPS POST 請求
在本節(jié)中,我們將探討如何在 Node.js 服務器中處理 POST 請求。當用戶提交 HTML 表單或發(fā)出 AJAX POST 請求時,會發(fā)生典型的 POST 請求。
當 POST 請求到達其預期端點時,您將訪問 POST 數(shù)據(jù),在回調函數(shù)中解析它,驗證和清理數(shù)據(jù),并可能發(fā)回響應。但是,您應該意識到,在使用普通 Node.js 服務器時,解析 HTTP 請求正文可能會很乏味。
下面的代碼是普通 Node.js HTTP 服務器的基本實現(xiàn)。它有一個基本的 HTML 表單,您可以使用它來發(fā)出 POST 請求。請求正文的結構取決于編碼類型。這些編碼類型包括 application/x-www-form-urlencoded 、 multipart/form-data 和 text/plain :
const http = require("http");
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" cnotallow="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<form actinotallow="/submit-form" enctype="application/x-www-form-urlencoded" method="POST">
<label> Enter Name:
<input type="text" autocomplete="name" name="name" required />
</label>
<input type="submit" />
</form>
</body>
</html>
`;
const server = http.createServer((req, res) => {
switch (req.method) {
case "GET":
if (req.url === "/") {
res.writeHead(200, { "Content-Type": "text/html" });
res.end(html);
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Page not found");
}
break;
case "POST":
if (req.url === "/submit-form") {
let body = "";
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
console.log("Request body: " + body);
// Parse, validate, and sanitize
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ body }));
});
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Page not found");
}
break;
default:
res.writeHead(405, { "Content-Type": "text/plain" });
res.end("Method not supported");
}
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Your app is listening on PORT ${PORT}`);
});
解析 POST 請求正文后,您需要驗證和清理數(shù)據(jù)。然后,您可以將數(shù)據(jù)保存在數(shù)據(jù)庫中、對用戶進行身份驗證或重定向到適當?shù)捻撁妗?/p>
大多數(shù)后端框架都具有用于解析 HTTP 請求正文的內置功能。使用 Express.js,當請求正文具有 application/x-www-form-urlencoded 編碼時,您可以使用內置的 express.urlencoded() 中間件。中間件將使用請求數(shù)據(jù)的鍵值對填充 req.body :
const express = require("express");
const path = require("path");
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.static("public"));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post("/submit-form", (req, res) => {
console.log(req.body);
res.json(req.body);
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "index.html"));
});
app.listen(PORT, () => {
console.log(`Your app is listening on port ${PORT}`);
});
對于 multipart/form-data 編碼,需要使用第三方包,例如busboy、Multer或formidable。
下面的代碼說明了如何使用 Multer。由于它不是內置中間件,因此請務必首先從 npm 包注冊表安裝它:
const express = require("express");
const path = require("path");
const multer = require("multer");
const app = express();
const upload = multer();
const PORT = process.env.PORT || 3000;
app.use(express.static("public"));
app.post("/submit-form", upload.none(), (req, res) => {
console.log("req.body: ", req.body);
console.log("Content-Type: ", req.get("Content-Type"));
res.json(req.body);
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "index.html"));
});
app.listen(PORT, () => {
console.log(`Your app is listening on port ${PORT}`);
});
最后,Express還有一個內置的中間件,用于解析具有 text/plain 編碼的請求體。它的用法與我們之前看過的中間件類似。你可以像這樣安裝它:
app.use(express.text());
總結
axios比superagent的功能列表很長,盡管 node-fetch 看起來很有前途并且安裝大小很小,但我不確定該 API 是否足夠用戶友好——至少對我來說是這樣。
您可能會注意到我的討論中省略了 Request npm 包。盡管 Request 持續(xù)受歡迎,每周下載量達到 1142 萬次,但截至 2024 年 2 月已被棄用,這使其成為一個不切實際的選擇。
所有這些庫主要做同樣的事情——就像你喜歡哪個品牌的咖啡一樣,最終你仍然在喝咖啡。根據(jù)您的用例明智地選擇,并做出正確的權衡以獲得最大利益。