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)為true
auth
:序列化的網(wǎng)址字符串是否包含用戶名和密碼,默認(rèn)為true
unicode
:是否將出現(xiàn)在URL
字符串的主機(jī)組件中的Unicode
字符直接編碼而不是Punycode
編碼,默認(rèn)是false
search
:序列化的網(wǎng)址字符串是否包含搜索查詢(參數(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=110
URL路徑拼接
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/two
resolve
方法并不是簡(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=4
querystring
中還有一對(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ù)查詢數(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ǔ)句在正常情況下是只能查詢到一條數(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
能查詢到多條數(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ù)名稱 // 返回給前端一個(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
類名都是在貓眼首頁(yè)文檔的對(duì)應(yīng)類名,如:
重新訪問(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ǔ)入門之path模塊,url模塊,http模塊使用詳解
- 基于Node.js的http模塊搭建HTTP服務(wù)器
- node.js使用http模塊創(chuàng)建服務(wù)器和客戶端完整示例
- Node.js進(jìn)階之核心模塊https入門
- 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-12nodejs中使用HTTP分塊響應(yīng)和定時(shí)器示例代碼
本文通過(guò)示例將要?jiǎng)?chuàng)建一個(gè)輸出純文本的HTTP服務(wù)器,輸出的純文本每隔一秒會(huì)新增100個(gè)用換行符分隔的時(shí)間戳。實(shí)例代碼非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-03-03Node.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