Node.js?搭建后端服務(wù)器內(nèi)置模塊(?http+url+querystring?的使用)
前言
這一節(jié)我們?nèi)W(xué)習(xí)NodeJs的內(nèi)置模塊:http、url、querystring ,并使用它們來(lái)搭建我們的node后端服務(wù)器,正式邁入后端開(kāi)發(fā)!
一、創(chuàng)建服務(wù)器
http是node的內(nèi)置模塊,我們可以直接引入它進(jìn)行使用,http這個(gè)模塊內(nèi)有一個(gè)createServer方法,這個(gè)方法可以幫助我們快速創(chuàng)建一個(gè)服務(wù)器:
// 引入http模塊
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// req:接受瀏覽器傳的參數(shù)
// res:返回渲染的內(nèi)容
}).listen(3000, () => { // 服務(wù)器端口號(hào)為3000
console.log("服務(wù)器啟動(dòng)啦!");
});另一種寫(xiě)法(推薦寫(xiě)法):
const http = require("http");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req:接受瀏覽器傳的參數(shù)
// res:返回渲染的內(nèi)容
});
server.listen(3000,() => { // 服務(wù)器端口號(hào)為3000
console.log("服務(wù)器啟動(dòng)啦!");
});createServer方法返回一個(gè)服務(wù)器對(duì)象,對(duì)象內(nèi)有一個(gè)listen方法,可以設(shè)置服務(wù)器啟動(dòng)的端口號(hào)和啟動(dòng)成功的回調(diào)內(nèi)容

通過(guò)nodemon運(yùn)行這個(gè)文件(nodemon可以在我們修改文件后幫助我們自動(dòng)重啟服務(wù)器),控制臺(tái)打印出了我們?cè)?code>listen中設(shè)置的回調(diào)內(nèi)容,說(shuō)明服務(wù)器啟動(dòng)成功了
我們直接在瀏覽器訪問(wèn)http://localhost:3000/時(shí)會(huì)發(fā)現(xiàn)瀏覽器一直在轉(zhuǎn)圈圈加載
注意: 直接在瀏覽器地址欄里訪問(wèn)服務(wù)器接口,相當(dāng)于是使用
get請(qǐng)求訪問(wèn)服務(wù)器接口(并且沒(méi)有跨域限制)

這是因?yàn)槲覀儾](méi)有在我們創(chuàng)建的node服務(wù)器中返回任何內(nèi)容
二、返回響應(yīng)數(shù)據(jù)
我們可以通過(guò)我們定義的res(response對(duì)象)參數(shù)向客戶端發(fā)送響應(yīng)內(nèi)容:
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// req:接受瀏覽器傳的參數(shù)
// res:返回渲染的內(nèi)容
// 傳遞數(shù)據(jù)
res.write("hello world");
res.write("Ailjx");
res.end();
}).listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});write方法傳遞內(nèi)容,可以調(diào)用多次write傳遞多條內(nèi)容,內(nèi)容必須是字符串格式最后必須調(diào)用end方法告訴請(qǐng)求的調(diào)用者我們響應(yīng)結(jié)束了也可以直接在end方法內(nèi)傳遞內(nèi)容,效果與使用write方法相同,但end方法只能調(diào)用一次
運(yùn)行上述代碼,我們直接在瀏覽器調(diào)用服務(wù)器接口:

如果服務(wù)器中不調(diào)用end方法,瀏覽器會(huì)收不到服務(wù)器傳遞的響應(yīng)結(jié)束的信號(hào),便會(huì)一直加載請(qǐng)求:

返回復(fù)雜對(duì)象數(shù)據(jù)
可以傳遞復(fù)雜對(duì)象數(shù)據(jù),但必須是字符串(JSON)的格式:
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// end方法也可以傳遞內(nèi)容,效果與write相同
res.end("{name:{me:'Ailjx'}}");
// 或者res.end(JSON.stringify({ name: { me: "Ailjx" } }));
}).listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});
上面瀏覽器顯示的數(shù)據(jù)被格式化了,是因?yàn)槲以跒g覽器中安裝了FeHelper(前端助手)插件,能夠自動(dòng)格式化JSON數(shù)據(jù)
返回html文檔數(shù)據(jù)
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// 傳遞html內(nèi)容
res.end(`
<h1>我是Ailjx,你好!</h1>
`);
}).listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});
這時(shí)發(fā)現(xiàn)我們傳遞的中文亂碼了,我們可以在響應(yīng)頭的Content-Type中指定utf-8的字符集來(lái)解析中文,下面會(huì)講到
三、設(shè)置響應(yīng)頭和狀態(tài)碼
我們可以使用response對(duì)象的writeHead方法來(lái)同時(shí)設(shè)置狀態(tài)碼和響應(yīng)頭信息:
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// 設(shè)置相應(yīng)頭,第一參數(shù)為狀態(tài)碼,第二個(gè)參數(shù)為響應(yīng)頭配置,第二個(gè)參數(shù)可不填
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
// 傳遞html內(nèi)容
res.end(`
<h1>我是Ailjx,你好!</h1>
`); // 直接在end中傳遞,效果與write方法相同
}).listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});
我們也可以使用setHeader單獨(dú)設(shè)置響應(yīng)頭信息,statusCode單獨(dú)設(shè)置狀態(tài)碼:
const http = require("http");
// 創(chuàng)建服務(wù)器
http.createServer((req, res) => {
// 設(shè)置相應(yīng)頭信息
res.setHeader("Content-Type", "text/html;charset=utf-8");
// 設(shè)置狀態(tài)碼
res.statusCode = 200;
// 傳遞html內(nèi)容
res.end(`
<h1>我是Ailjx,你好!</h1>
`); // 直接在end中傳遞,效果與write方法相同
}).listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});四、實(shí)現(xiàn)路由接口
上面我們已經(jīng)成功創(chuàng)建了一個(gè)服務(wù)器,并能夠使用res參數(shù)中的write或end方法向調(diào)用者發(fā)送內(nèi)容,但發(fā)送的這些內(nèi)容是不會(huì)隨著我們請(qǐng)求的路徑而變化的:
const http = require("http");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
res.end("Ailjx");
});
server.listen(3000);

可以看到,我們請(qǐng)求(訪問(wèn))不同路徑,服務(wù)器返回的數(shù)據(jù)都是一樣的,而我們?cè)趯?shí)際開(kāi)發(fā)中往往是需要不同的路徑有不同的數(shù)據(jù)
這時(shí)我們就可以利用我們創(chuàng)建服務(wù)器時(shí)定義的req參數(shù)(request對(duì)象)來(lái)獲取用戶請(qǐng)求的路徑從而判斷需要返回哪些數(shù)據(jù):
const http = require("http");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req.url拿到用戶請(qǐng)求的路徑
console.log(req.url);
res.end();
});
server.listen(3000);運(yùn)行代碼,之后在瀏覽器訪問(wèn)調(diào)用一下http://localhost:3000/list,控制臺(tái)會(huì)打印出:

可以看到我們?cè)L問(wèn)的/list路徑確實(shí)被打印出來(lái)了,但怎么還打印了一個(gè)/favicon.ico呢?
這其實(shí)是瀏覽器在訪問(wèn)一個(gè)域名時(shí)會(huì)自動(dòng)訪問(wèn)該域名下的/favicon.ico靜態(tài)文件,來(lái)作為網(wǎng)頁(yè)標(biāo)簽欄的小圖標(biāo),所以我們的服務(wù)器才會(huì)打印出/favicon.ico
如果是普通的ajax調(diào)用接口是不會(huì)出現(xiàn)這種情況的,這里我們是為了方便,直接使用瀏覽器訪問(wèn)接口來(lái)進(jìn)行演示,所以才出現(xiàn)這種請(qǐng)求,我們可以簡(jiǎn)單做一下處理:
const http = require("http");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req.url獲取用戶請(qǐng)求的路徑
if (req.url === "/favicon.ico") {
// 讀取本地圖標(biāo)
return;
}
console.log(req.url);
res.end("Ailjx");
});
server.listen(3000);這樣當(dāng)服務(wù)器收到/favicon.ico請(qǐng)求時(shí)就能直接跳過(guò)不對(duì)其進(jìn)行處理
創(chuàng)建簡(jiǎn)易路由應(yīng)用
現(xiàn)在,我們開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的路由應(yīng)用,我們先創(chuàng)建兩個(gè)模塊:
renderContent.js用來(lái)根據(jù)用戶請(qǐng)求路徑來(lái)返回對(duì)應(yīng)的內(nèi)容:
function renderContent(url) {
switch (url) {
case "/api/home":
return `
{
page:'首頁(yè)'
}
`;
case "/api/about":
return `
{
page:'關(guān)于頁(yè)'
}
`;
default:
return "404";
}
}
exports.renderContent = renderContent;renderStatus.js用來(lái)根據(jù)用戶請(qǐng)求的路徑來(lái)返回對(duì)應(yīng)的響應(yīng)狀態(tài)碼:
function renderStatus(url) {
const arr = ["/api/home", "/api/about"];
return arr.includes(url) ? 200 : 404;
}
module.exports = {
renderStatus,
};之后在我們的服務(wù)器文件server.js中調(diào)用這兩個(gè)模塊:
const http = require("http");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req.url獲取用戶請(qǐng)求的路徑
if (req.url === "/favicon.ico") {
return;
}
// 響應(yīng)頭
res.writeHead(renderStatus(req.url), {
// 標(biāo)志返回JSON數(shù)據(jù)
"Content-Type": "application/json",
});
// 返回的內(nèi)容
res.end(renderContent(req.url));
});
server.listen(3000);之后啟動(dòng)服務(wù)器,在瀏覽器調(diào)用接口查看效果:


五、處理URL
在上面我們通過(guò)判斷req.url來(lái)實(shí)現(xiàn)了簡(jiǎn)易的路由接口應(yīng)用,但當(dāng)用戶調(diào)用帶有url參數(shù)的接口時(shí),這就會(huì)出現(xiàn)問(wèn)題:

這是因?yàn)檫@時(shí)req.url為/api/about?name=ailj而并不是/api/about,我們可以手動(dòng)的對(duì)這個(gè)字符串進(jìn)行處理來(lái)獲得正確的路由路徑,也可以使用node.js的內(nèi)置模塊url來(lái)處理
URL格式轉(zhuǎn)換
修改上面的server.js文件:
const http = require("http");
const url = require("url");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req.url獲取用戶請(qǐng)求的路徑
if (req.url === "/favicon.ico") {
return;
}
// 新版使用全局的URL構(gòu)造函數(shù)
// 傳兩個(gè)參數(shù)用法
const myUrl = new URL(req.url, "http://127.0.0.1:3000").pathname;
// 傳一個(gè)參數(shù)用法
// const myUrl = new URL("http://127.0.0.1:3000" + req.url).pathname;
console.log(new URL(req.url, "http://127.0.0.1:3000"));
res.writeHead(renderStatus(myUrl), {
"Content-Type": "application/json",
});
res.end(renderContent(myUrl));
});
server.listen(3000, () => {
// 服務(wù)器端口號(hào)為3000
console.log("服務(wù)器啟動(dòng)啦!");
});全局的構(gòu)造函數(shù)UR可以將完整的 url地址轉(zhuǎn)換成url對(duì)象(WHATWG URL標(biāo)準(zhǔn)的對(duì)象)
我們可以對(duì)其傳遞兩個(gè)參數(shù),第一個(gè)是用戶請(qǐng)求的路徑(路由),第二個(gè)參數(shù)是地址的根域名(我們這里是本地啟動(dòng)的服務(wù)器,根域名為
http://127.0.0.1:3000)
也可以直接傳遞一個(gè)參數(shù),該參數(shù)是帶有域名的完整
url地址
當(dāng)我們?cè)L問(wèn)http://localhost:3000/api/about?name=ailjx時(shí)server.js會(huì)打印出:
URL {
href: 'http://127.0.0.1:3000/api/about?name=ailjx',
origin: 'http://127.0.0.1:3000',
protocol: 'http:',
username: '',
password: '',
host: '127.0.0.1:3000',
hostname: '127.0.0.1',
port: '3000',
pathname: '/api/about',
search: '?name=ailjx',
searchParams: URLSearchParams { 'name' => 'ailjx' },
hash: ''
}上面Url對(duì)象里 searchParams是url的參數(shù)部分,是一個(gè)迭代器對(duì)象URLSearchParams對(duì)象
// searchParams對(duì)象是一個(gè)迭代器對(duì)象
const query = new URL(req.url, "http://127.0.0.1:3000").searchParams;
// 使用get方法獲取指定的值
console.log(query.get("name")); // ailjx我們還可以從組成部分構(gòu)造 URL 并獲取構(gòu)造的字符串:
const myURL = new URL("https://www.baidu.com");
myURL.port = "443";
myURL.pathname = "/ad/index.html";
myURL.search = "?id=8&name=mouse";
myURL.hash = "#tag=110";
// 獲取構(gòu)造的 URL 字符串,請(qǐng)使用href屬性訪問(wèn)器
console.log(myURL.href); // https://www.baidu.com/ad/index.html?id=8&name=mouse#tag=110或者:
const pathname = '/a/b/c';
const search = '?d=e';
const hash = '#fgh';
const myURL = new URL(`https://example.org${pathname}${search}${hash}`);
console.log(myURL.href);使用url.format方法可以自定義序列化url字符串,format方法接收兩個(gè)參數(shù):
new URL返回的一個(gè)WHATWG URL格式的對(duì)象- 配置對(duì)象:
fragment:序列化的網(wǎng)址字符串是否包含片段,默認(rèn)為trueauth:序列化的網(wǎng)址字符串是否包含用戶名和密碼,默認(rèn)為trueunicode:是否將出現(xiàn)在URL字符串的主機(jī)組件中的Unicode字符直接編碼而不是Punycode編碼,默認(rèn)是falsesearch:序列化的網(wǎng)址字符串是否包含搜索查詢(xún)(參數(shù)),默認(rèn)為true
const myURL = new URL(
"https://username:password@URL路徑序列化測(cè)試?name=ailjx#foo"
);
console.log(
url.format(myURL, {
fragment: false, // 不顯示片段(#foo)
unicode: true, // 不轉(zhuǎn)化Unicode字符(中文字符)
auth: false, // 不包含用戶名和密碼(username:password)
search: false, // 不顯示參數(shù)(?name=ailjx)
})
);
// 打印結(jié)果: 'https://url路徑序列化測(cè)試/'舊版Node使用parse和format處理URL:
注意:舊版的
parse方法官方表示已棄用,format方法在新版中使用方式有所更改
const http = require("http");
const url = require("url");
const { renderContent } = require("./module/renderContent");
const { renderStatus } = require("./module/renderStatus");
// 創(chuàng)建服務(wù)器
const server = http.createServer();
server.on("request", (req, res) => {
// req.url獲取用戶請(qǐng)求的路徑
if (req.url === "/favicon.ico") {
return;
}
console.log(url.parse(req.url));
const myUrl = url.parse(req.url).pathname;
res.writeHead(renderStatus(myUrl), {
"Content-Type": "application/json",
});
res.end(renderContent(myUrl));
});
server.listen(3000, () => {
// 服務(wù)器端口號(hào)為3000
console.log("服務(wù)器啟動(dòng)啦!");
});url模塊的parse方法可以將完整的url地址轉(zhuǎn)換成url對(duì)象,如當(dāng)我們?cè)L問(wèn)http://localhost:3000/api/about?name=ailjx時(shí)server.js會(huì)打印出:
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?name=ailjx',
query: 'name=ailjx',
pathname: '/api/about',
path: '/api/about?name=ailjx',
href: '/api/about?name=ailjx'
}上面Url對(duì)象里 query 是url的參數(shù)部分,默認(rèn)是字符串的格式,可以給parse方法傳遞第二個(gè)參數(shù),將其轉(zhuǎn)換成對(duì)象格式:
url.parse(req.url,true)
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?name=ailjx',
query: [Object: null prototype] { name: 'ailjx' },
pathname: '/api/about',
path: '/api/about?name=ailjx',
href: '/api/about?name=ailjx'
}這時(shí)通過(guò)url.parse(req.url, true).query.name就可以拿到ailjx這個(gè)參數(shù)
與parse方法相反的有一個(gè)format方法,它能將一個(gè) url對(duì)象轉(zhuǎn)換成url地址 :
const url = require("url");
const urlObject = {
protocol: "https:",
slashes: true,
auth: null,
host: "www.baidu.com:443",
port: "443",
hostname: "www.baidu.com",
hash: "#tag=110",
search: "?id=8&name=mouse",
query: { id: "8", name: "mouse" },
pathname: "/ad/index.html",
path: "/ad/index.html?id=8&name=mouse",
};
const parsedObj = url.format(urlObject);
console.log(parsedObj); // https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110URL路徑拼接
URL構(gòu)造函數(shù),傳遞兩個(gè)字符串路徑時(shí)能夠?qū)崿F(xiàn)路徑的拼接:
let myURL = new URL('http://Example.com/', 'https://example.org/');
// http://example.com/
myURL = new URL('https://Example.com/', 'https://example.org/');
// https://example.com/
myURL = new URL('foo://Example.com/', 'https://example.org/');
// foo://Example.com/
myURL = new URL('http:Example.com/', 'https://example.org/');
// http://example.com/
myURL = new URL('https:Example.com/', 'https://example.org/');
// https://example.org/Example.com/
myURL = new URL('foo:Example.com/', 'https://example.org/');
// foo:Example.com/舊版Node使用resolve方法拼接路徑:
注意:舊版node的
resolve方法官方表示已棄用
const url = require('url')
var a = url.resolve("/one/two/three", "four"); // /one/two/four
// var a = url.resolve("/one/two/three/", "four"); // /one/two/three/four
// var a = url.resolve("/one/two/three", "/four"); // /four
// var a = url.resolve("/one/two/three/", "/four"); // /four
var b = url.resolve("http://example.com/", "/one");
// var b = url.resolve("http://example.com/", "one");
// var b = url.resolve("http://example.com", "one");
// var b = url.resolve("http://example.com", "/one");
// 以上b的結(jié)果都是:http://example.com/one
var c = url.resolve("http://example.com/one", "two");
// var c = url.resolve("http://example.com/one", "/two");
// var c = url.resolve("http://example.com/one/", "/two");
// var c = url.resolve("http://example.com/one/a/b", "/two");
// 以上c的結(jié)果都是:http://example.com/two
var d = url.resolve("http://example.com/one/", "two"); // http://example.com/one/two
var e = url.resolve("http://example.com/one/aaa", "http://example.com/one/two");
// var e = url.resolve("/one/aaa", "http://example.com/one/two");
// 以上e的結(jié)果都是:http://example.com/one/tworesolve方法并不是簡(jiǎn)單的將兩個(gè)路徑直接拼接在一起,而是具有它自己的一些拼接規(guī)則:
- 如果第二個(gè)路徑(接收的第二個(gè)參數(shù))開(kāi)頭前帶
/,則將直接用第二個(gè)路徑替代第一個(gè)路徑的路由部分(不會(huì)替代第一個(gè)路徑的根域名,如上面的最后一個(gè)變量c所示:/two替代了/one/a/b) - 如果第二個(gè)路徑開(kāi)頭前不帶
/,第一個(gè)路徑結(jié)尾處不帶/,則第二個(gè)路徑將會(huì)替代第一個(gè)路徑的最后一個(gè)路由,如上邊的第一個(gè)變量a和第一個(gè)變量c - 第二個(gè)路徑開(kāi)頭前不帶
/,第一個(gè)路徑結(jié)尾處帶/,則直接將第二個(gè)路徑拼接在第一個(gè)路徑后面,如上邊的第變量d和第二個(gè)變量a - 如果第二個(gè)路徑包含根域名(
http://xxx),則直接以第二個(gè)路徑為主(第一個(gè)路徑失效) 處理URL路徑參數(shù)
注意:
querystring方法官方表示已棄用
NodeJS有一個(gè)內(nèi)置模塊querystring,它里面的parse和stringify方法可以幫助我們快速處理URL上的形如id=8&name=mouse的參數(shù):
const querystring = require("querystring");
var qs = "id=8&name=Ailjx";
// parse:路徑將參數(shù)轉(zhuǎn)化為對(duì)象
var parsed = querystring.parse(qs);
console.log(parsed.id, parsed.name); // 8 Ailjx
var qo = {
x: 3,
y: 4,
};
//stringify:將對(duì)象轉(zhuǎn)化為路徑參數(shù)
var parsed = querystring.stringify(qo);
console.log(parsed); // x=3&y=4querystring中還有一對(duì)能夠轉(zhuǎn)義特殊字符的方法: escape/unescape:
const querystring = require("querystring");
var str = 'ns"--';
// escape:將特殊字符轉(zhuǎn)義
var escaped = querystring.escape(str);
console.log(escaped); //ns%22--
// unescape:恢復(fù)轉(zhuǎn)義的特殊字符
console.log(querystring.unescape(escaped)); // ns"--對(duì)于特殊字符的轉(zhuǎn)義在一些特殊情況下特別重要,例如我們通過(guò)用戶傳遞的參數(shù)來(lái)向mysql數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù),我們?yōu)榱朔乐褂脩魝鬟f的參數(shù)與sql語(yǔ)句發(fā)送沖突,就可以對(duì)該參數(shù)進(jìn)行轉(zhuǎn)義,以防止sql注入
例如有一條含有用戶傳遞的param參數(shù)的sql語(yǔ)句:
let sql = `select * from users where name = "${param}" and del_status=1`上面的sql語(yǔ)句在正常情況下是只能查詢(xún)到一條數(shù)據(jù),如:
// let param = 'Ailjx' ,對(duì)應(yīng)的sql語(yǔ)句如下: select * from users where name = "Ailjx" and del_status=1
但當(dāng)param與sql語(yǔ)句沖突時(shí):
// let param = 'ns"--',對(duì)應(yīng)的sql語(yǔ)句如下: select * from tb_nature where nature = "ns"-- " and del_status=1
可以看到del_status被參數(shù)中的--注釋掉了,失去了作用,這時(shí)這條sql能查詢(xún)到多條數(shù)據(jù),這就是sql注入的危害,也就是我們需要轉(zhuǎn)義特殊字符的原因
正確轉(zhuǎn)換文件路徑
url模塊針對(duì)轉(zhuǎn)換文件路徑提供了單獨(dú)的fileURLToPath和pathToFileURL方法,它們不僅能正確進(jìn)行文件路徑的轉(zhuǎn)換而且能自動(dòng)適配不同的操作系統(tǒng)
fileURLToPath該方法能夠正確將文件網(wǎng)址url轉(zhuǎn)換成文件路徑:
const { fileURLToPath } = require("url");
console.log(new URL("file:///C:/path/").pathname); // 獲得錯(cuò)誤路徑:/C:/path/
console.log(fileURLToPath("file:///C:/path/")); // 獲得正確路徑:C:\path\ (Windows)
console.log(new URL("file://nas/foo.txt").pathname); // 獲得錯(cuò)誤路徑:/foo.txt
console.log(fileURLToPath("file://nas/foo.txt")); // 獲得正確路徑: \\nas\foo.txt (Windows)
console.log(new URL("file://c://你好.txt").pathname); // 獲得錯(cuò)誤路徑:/c://%E4%BD%A0%E5%A5%BD.txt
console.log(fileURLToPath("file://c://你好.txt")); // 獲得正確路徑: c:\\你好.txt (Windows)pathToFileURL方法,能夠?qū)⑽募窂睫D(zhuǎn)換成文件網(wǎng)址url對(duì)象:
const { pathToFileURL } = require("url");
console.log(new URL("/foo#1", "file:").href); // 錯(cuò)誤: file:///foo#1
console.log(pathToFileURL("/foo#1").href); // 正確: file:///D:/foo%231
console.log(new URL("/some/path%.c", "file:").href); // 錯(cuò)誤: file:///some/path%.c
console.log(pathToFileURL("/some/path%.c").href); // 正確: file:///D:/some/path%25.c 轉(zhuǎn)換為Options Url對(duì)象
urlToHttpOptions方法可以將new URL返回的WHATWG URL對(duì)象轉(zhuǎn)換成http.request()或https.request()需要的Options Url對(duì)象
http.request()和https.request()在下面會(huì)講到
const { urlToHttpOptions } = require("url");
const myURL = new URL('https://a:b@測(cè)試?abc#foo');
console.log(urlToHttpOptions(myURL));
/*
{
protocol: 'https:',
hostname: 'xn--g6w251d',
hash: '#foo',
search: '?abc',
pathname: '/',
path: '/?abc',
href: 'https://a:b@xn--g6w251d/?abc#foo',
auth: 'a:b'
}
*/六、跨域處理
在不做處理的情況下,前后端交互會(huì)出現(xiàn)CORS跨域的問(wèn)題,如:
定義一個(gè)簡(jiǎn)單的服務(wù)器:
server.js
const http = require("http");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
const urlObj = url.parse(req.url, true);
switch (urlObj.pathname) {
case "/api/user":
// 模擬數(shù)據(jù)
const userinfo = {
name: "Ailjx",
};
res.end(JSON.stringify(userinfo));
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});可以看到上面我們定義的服務(wù)器并沒(méi)有設(shè)置跨域,我們直接在html文件內(nèi)請(qǐng)求該服務(wù)器的接口:
index.html
<body>
<div>jsonp接口測(cè)試</div>
<script>
fetch('http://localhost:3000/api/user')
.then(res => res.json())
.then(res => console.log(res))
</script>
</body>打開(kāi)該html文件,可以看到果然出現(xiàn)了CORS跨域的報(bào)錯(cuò)

這種問(wèn)題有多種解決方案:
- 后端設(shè)置響應(yīng)頭來(lái)允許前端訪問(wèn)服務(wù)器(只需要后端進(jìn)行修改)
- 前后端使用
jsonp方式進(jìn)行交互(前后端都需要進(jìn)行相應(yīng)修改) - 前端配置代理(只需要前端進(jìn)行修改,這在像
vue,react等這些框架中經(jīng)常使用)
后端設(shè)置跨域
后端可以直接在響應(yīng)頭里設(shè)置跨域,而前端不需要做額外的操作
我們下載一個(gè)vscode的Live Server插件,用于在線運(yùn)行html文件:

右鍵html文件選擇Open with Live Server:


打開(kāi)后發(fā)現(xiàn)前端html的在線運(yùn)行地址為http://127.0.0.1:5500,我們?cè)诤蠖朔祷財(cái)?shù)據(jù)的響應(yīng)頭里允許該地址訪問(wèn)即可:
修改一下上邊的server.js
const http = require("http");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
const urlObj = url.parse(req.url, true);
res.writeHead(200, {
"content-type": "application/json;charset=utf-8",
// 設(shè)置跨域允許http://127.0.0.1:5500訪問(wèn)
"Access-Control-Allow-Origin": "http://127.0.0.1:5500",
// "Access-Control-Allow-Origin": "*", 也可以使用'*',代表允許所有地址訪問(wèn)
});
switch (urlObj.pathname) {
case "/api/user":
// 模擬數(shù)據(jù)
const userinfo = {
name: "Ailjx",
};
res.end(JSON.stringify(userinfo));
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});這時(shí)前端就能正常調(diào)用該接口了:

jsonp接口
我們知道html的script標(biāo)簽可以引入js文件,并且重要的是使用script標(biāo)簽引入js沒(méi)有跨域的要求,所以我們可以在script標(biāo)簽的src屬性中調(diào)用我們的接口來(lái)獲取后端返回的數(shù)據(jù)
前端處理:
<body>
<div>jsonp接口測(cè)試</div>
<div>
name: <b id="myName"></b>
</div>
<script>
// 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
function getUser(params) {
const myName = document.getElementById('myName')
// 可以做一些操作
myName.innerText = params.name
}
// 創(chuàng)建一個(gè)script標(biāo)簽
const myScript = document.createElement('script')
// script標(biāo)簽的src內(nèi)調(diào)用接口,需要將我們定義的接收數(shù)據(jù)的函數(shù)名傳遞給后端
myScript.src = 'http://localhost:3000/api/user?cb=getUser'
// 向文檔內(nèi)插入該script標(biāo)簽
document.body.appendChild(myScript)
</script>
</body>或者直接用script調(diào)用后端接口:
<body>
<div>jsonp接口測(cè)試</div>
<div>
name: <b id="myName"></b>
</div>
<script>
// // 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
function getUser(params) {
const myName = document.getElementById('myName')
myName.innerText = params.name
}
</script>
<!-- 調(diào)用后端接口 -->
<script src="http://localhost:3000/api/user?cb=getUser"></script>
</body>后端處理:
const http = require("http");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
const urlObj = url.parse(req.url, true);
switch (urlObj.pathname) {
case "/api/user":
// 模擬數(shù)據(jù)
const userinfo = {
name: "Ailjx",
};
// urlObj.query.cb是前端傳遞的接收數(shù)據(jù)的函數(shù)名稱(chēng)
// 返回給前端一個(gè)函數(shù)調(diào)用
res.end(`${urlObj.query.cb}(${JSON.stringify(userinfo)})`);
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});可以看到使用jsonp的原理就是在script標(biāo)簽中調(diào)用后端接口,因?yàn)?code>script標(biāo)簽內(nèi)的js代碼是立即執(zhí)行的
所以我們需要提前定義一個(gè)接收后端參數(shù)的處理函數(shù),然后將該函數(shù)名傳遞給后端,后端根據(jù)這個(gè)函數(shù)名返回給前端一個(gè)該函數(shù)的調(diào)用并將需要給前端的數(shù)據(jù)作為該函數(shù)的參數(shù)
上述代碼的最終效果如下:
<script>
// 定義一個(gè)接收后端返回?cái)?shù)據(jù)的函數(shù)
function getUser(params) {
const myName = document.getElementById('myName')
myName.innerText = params.name
}
</script>
<!-- <script src="http://localhost:3000/api/user?cb=getUser"></script>的效果如下 -->
<script>
getUser({
name: "Ailjx",
})
</script>
七、Node作為中間層使用
上面我們使用node搭建了后端服務(wù)器,使其作為服務(wù)端運(yùn)行,但其實(shí)node還能當(dāng)作客戶端反過(guò)來(lái)去調(diào)用其它服務(wù)端的接口,這使得node成為了一個(gè)中間層

因?yàn)榭缬蛑皇菫g覽器的限制,服務(wù)端之間的通信并不存在跨域的問(wèn)題,這樣我們就能借助node去調(diào)用第三方具有跨域限制的接口,這是將node作為中間層的一個(gè)非常實(shí)用的功能
模擬get請(qǐng)求(轉(zhuǎn)發(fā)跨域數(shù)據(jù))
在貓眼電影網(wǎng)上隨便找了一個(gè)帶有跨域的接口,我們直接調(diào)用時(shí)會(huì)報(bào)CORS跨域問(wèn)題:
<h1>使用node模擬get請(qǐng)求</h1>
<script>
fetch('https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4')
.then(res => res.json())
.then(res => {
console.log(res);
})
</script>
這時(shí)我們可以利用node幫我們?nèi)フ?qǐng)求這個(gè)接口的數(shù)據(jù):
const http = require("http");
const https = require("https");
// http和https的區(qū)別僅在于一個(gè)是http協(xié)議一個(gè)是https協(xié)議
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
const urlObj = url.parse(req.url, true);
res.writeHead(200, {
"content-type": "application/json;charset=utf-8",
"Access-Control-Allow-Origin": "http://127.0.0.1:5500",
});
switch (urlObj.pathname) {
case "/api/maoyan":
// 我們定義的httpget方法:使node充當(dāng)客戶端去貓眼的接口獲取數(shù)據(jù)
httpget((data) => res.end(data));
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});
function httpget(cb) {
// 定義一個(gè)存放數(shù)據(jù)的變量
let data = "";
// 因?yàn)樨堁鄣慕涌谑莌ttps協(xié)議的,所以我們需要引入https
// http和https都具有一個(gè)get方法能夠發(fā)起get請(qǐng)求,區(qū)別是一個(gè)是http協(xié)議,一個(gè)是https協(xié)議
// http get方法第一個(gè)參數(shù)為接口地址,第二個(gè)參數(shù)為回調(diào)函數(shù)
https.get(
"https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E8%A5%BF%E5%8D%8E&ci=936&channelId=4",
(res) => {
// http get方法獲取的數(shù)據(jù)是一點(diǎn)點(diǎn)返回的,并不是直接返回全部
// 監(jiān)聽(tīng)data,當(dāng)有數(shù)據(jù)返回時(shí)就會(huì)被調(diào)用
res.on("data", (chunk) => {
// 收集數(shù)據(jù)
data += chunk;
});
// 監(jiān)聽(tīng)end,數(shù)據(jù)返回完畢后調(diào)用
res.on("end", () => {
cb(data);
});
}
);
}之后我們調(diào)用我們node的接口即可:
<h1>使用node模擬get請(qǐng)求</h1>
<script>
fetch('http://localhost:3000/api/maoyan')
.then(res => res.json())
.then(res => {
console.log(res);
})
</script>
這里node即作為服務(wù)端給我們提供接口/api/maoyan,又充當(dāng)了一下客戶端去調(diào)用貓眼的接口,這樣我們就繞過(guò)了貓眼的跨域限制獲取了它的數(shù)據(jù)
模擬post請(qǐng)求(服務(wù)器提交)
使用node模擬post請(qǐng)求需要使用http或https的request方法來(lái)進(jìn)行請(qǐng)求的配置,稍微有點(diǎn)麻煩:
http和https模塊的區(qū)別僅在于一個(gè)是http協(xié)議一個(gè)是https協(xié)議
const http = require("http");
const https = require("https");
const url = require("url");
const server = http.createServer();
server.on("request", (req, res) => {
const urlObj = url.parse(req.url, true);
res.writeHead(200, {
"content-type": "application/json;charset=utf-8",
"Access-Control-Allow-Origin": "http://127.0.0.1:5500",
});
switch (urlObj.pathname) {
case "/api/xiaomiyoumin":
// httpPost方法:使node充當(dāng)客戶端去小米有品的post接口獲取數(shù)據(jù)
httpPost((data) => res.end(data));
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務(wù)器啟動(dòng)啦!");
});
function httpPost(cb) {
// 定義一個(gè)存放數(shù)據(jù)的變量
let data = "";
// 這是小米有品的一個(gè)post接口:"https://m.xiaomiyoupin.com/mtop/market/search/placeHolder"
// 這個(gè)接口調(diào)用時(shí)需要傳“[{}, { baseParam: { ypClient: 1 } }]”這樣一個(gè)參數(shù)才能返回?cái)?shù)據(jù)
// 配置Options Url請(qǐng)求對(duì)象
const options = {
// 域名
hostname: "m.xiaomiyoupin.com",
// 接口端口號(hào),443代表https,80代表http
port: "443",
// 路徑
path: "/mtop/market/search/placeHolder",
// 請(qǐng)求方式
method: "POST",
// 請(qǐng)求頭
Headers: {
// 表示接收json數(shù)據(jù)
"Content-Type": "application/json",
},
};
// http request方法第一個(gè)參數(shù)為請(qǐng)求對(duì)象,第二個(gè)參數(shù)為回調(diào)函數(shù),request方法返回一個(gè)值(),在該值內(nèi)通過(guò)調(diào)用write向post請(qǐng)求傳遞數(shù)據(jù)
const req = https.request(options, (res) => {
// 監(jiān)聽(tīng)data,當(dāng)有數(shù)據(jù)返回時(shí)就會(huì)被調(diào)用
res.on("data", (chunk) => {
// 收集數(shù)據(jù)
data += chunk;
});
// 監(jiān)聽(tīng)end,數(shù)據(jù)返回完畢后調(diào)用
res.on("end", () => {
cb(data);
});
});
// 發(fā)送post的參數(shù)
// req.write(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
// 這里的使用與我們server服務(wù)器中的req參數(shù)使用方式差不多,不要忘記最后調(diào)用end方法,并且也可以直接在end方法內(nèi)傳遞數(shù)據(jù)
req.end(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
}<body>
<h1>使用node模擬post請(qǐng)求</h1>
<script>
fetch('http://localhost:3000/api/xiaomiyoumin')
.then(res => res.json())
.then(res => {
console.log(res);
})
</script>
</body>
八、使用Node實(shí)現(xiàn)爬蟲(chóng)
我們使用node的一個(gè)cheerio包也可以實(shí)現(xiàn)爬蟲(chóng)功能,如我們爬取貓眼移動(dòng)端https://i.maoyan.com首頁(yè)的一些數(shù)據(jù):

我們?cè)谝粋€(gè)文件夾內(nèi)打開(kāi)終端,先生成package.json文件:
npm init
安裝cheerio:
npm i cheerio
文件夾內(nèi)創(chuàng)建我們的服務(wù)器文件server.js:
const https = require("https");
const http = require("http");
const cheerio = require("cheerio");
http.createServer((request, response) => {
response.writeHead(200, {
"content-type": "application/json;charset=utf-8",
});
const options = {
hostname: "i.maoyan.com",
port: 443,
path: "/",
method: "GET",
};
// 獲取頁(yè)面數(shù)據(jù)
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
response.end(data);
});
});
req.end();
}).listen(3000);上面演示了使用
https.request方法配置get請(qǐng)求(接口為:https://i.maoyan.com)的寫(xiě)法,你也可以直接使用https.get進(jìn)行調(diào)用
通過(guò)瀏覽器調(diào)用我們的服務(wù)器:

可以看到我們成功獲取了貓眼移動(dòng)端首頁(yè)的html文檔,我們需要的數(shù)據(jù)都在文檔中了,之后我們只需將我們需要的數(shù)據(jù)提取出來(lái),這里就將用到cheerio:
const https = require("https");
const http = require("http");
const cheerio = require("cheerio");
http.createServer((request, response) => {
response.writeHead(200, {
"content-type": "application/json;charset=utf-8",
});
const options = {
hostname: "i.maoyan.com",
port: 443,
path: "/",
method: "GET",
};
// 獲取頁(yè)面數(shù)據(jù)
const req = https.request(options, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
// 處理頁(yè)面數(shù)據(jù)
filterData(data);
});
});
function filterData(data) {
let $ = cheerio.load(data);
// 獲取class="column content"的元素
let $movieList = $(".column.content");
// console.log($movieList);
let movies = [];
$movieList.each((index, value) => {
movies.push({
// 獲取class為movie-title下的class為title的元素的文本值
title: $(value).find(".movie-title .title").text(),
detail: $(value).find(".detail .actor").text(),
});
});
response.end(JSON.stringify(movies));
}
req.end();
}).listen(3000);
cheerio.load接收html文檔字符串,它返回一個(gè)對(duì)象,該對(duì)象與jQuery相似,我們可以對(duì)其使用jQuery的語(yǔ)法進(jìn)行操作
上面使用的class類(lèi)名都是在貓眼首頁(yè)文檔的對(duì)應(yīng)類(lèi)名,如:

重新訪問(wèn)一下我們的服務(wù)器:

可以看到我們已經(jīng)爬蟲(chóng)成功!
如果在請(qǐng)求https://i.maoyan.com接口時(shí)獲取不到數(shù)據(jù),可能是我們?yōu)g覽器上的貓眼網(wǎng)進(jìn)入了驗(yàn)證操作,我們?cè)谠L問(wèn)我們服務(wù)器的這個(gè)瀏覽器上打開(kāi)https://i.maoyan.com進(jìn)行驗(yàn)證一下,之后我們就能請(qǐng)求到https://i.maoyan.com的html文檔內(nèi)容了
到此這篇關(guān)于Node.js 搭建后端服務(wù)器內(nèi)置模塊( http+url+querystring 的使用)的文章就介紹到這了,更多相關(guān)Node.js 后端搭建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Node.js使用http模塊實(shí)現(xiàn)后臺(tái)服務(wù)器流程解析
- Node.js基礎(chǔ)入門(mén)之path模塊,url模塊,http模塊使用詳解
- 基于Node.js的http模塊搭建HTTP服務(wù)器
- node.js使用http模塊創(chuàng)建服務(wù)器和客戶端完整示例
- Node.js進(jìn)階之核心模塊https入門(mén)
- node.js中http模塊和url模塊的簡(jiǎn)單介紹
- node.js 核心http模塊,起一個(gè)服務(wù)器,返回一個(gè)頁(yè)面的實(shí)例
- Node.js中Request模塊處理HTTP協(xié)議請(qǐng)求的基本使用教程
- Node.js 中 http 模塊的深度剖析與實(shí)戰(zhàn)應(yīng)用小結(jié)
相關(guān)文章
使用nvm進(jìn)行多個(gè)nodejs版本的統(tǒng)一管理
隨著前端項(xiàng)目的越來(lái)越多,不同項(xiàng)目使用的nodejs版本可能不一樣,導(dǎo)致在切換不同項(xiàng)目時(shí)需要更換不同的nodejs版本,非常麻煩,本次推薦使用nvm進(jìn)行多個(gè)nodejs版本的統(tǒng)一管理,文中有詳細(xì)的圖文介紹,需要的朋友可以參考下2023-12-12
nodejs中使用HTTP分塊響應(yīng)和定時(shí)器示例代碼
本文通過(guò)示例將要?jiǎng)?chuàng)建一個(gè)輸出純文本的HTTP服務(wù)器,輸出的純文本每隔一秒會(huì)新增100個(gè)用換行符分隔的時(shí)間戳。實(shí)例代碼非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-03-03
Node.js中的http請(qǐng)求客戶端示例(request client)
本篇文章主要介紹了Node.js中的http請(qǐng)求客戶端示例(request client),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
編譯打包nodejs服務(wù)代碼如何部署到服務(wù)器
這篇文章主要介紹了編譯打包nodejs服務(wù)代碼如何部署到服務(wù)器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10

