Node.js?搭建后端服務器內(nèi)置模塊(?http+url+querystring?的使用)
前言
這一節(jié)我們?nèi)W習NodeJs
的內(nèi)置模塊:http
、url
、querystring
,并使用它們來搭建我們的node
后端服務器,正式邁入后端開發(fā)!
一、創(chuàng)建服務器
http
是node
的內(nèi)置模塊,我們可以直接引入它進行使用,http
這個模塊內(nèi)有一個createServer
方法,這個方法可以幫助我們快速創(chuàng)建一個服務器:
// 引入http模塊 const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // req:接受瀏覽器傳的參數(shù) // res:返回渲染的內(nèi)容 }).listen(3000, () => { // 服務器端口號為3000 console.log("服務器啟動啦!"); });
另一種寫法(推薦寫法):
const http = require("http"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req:接受瀏覽器傳的參數(shù) // res:返回渲染的內(nèi)容 }); server.listen(3000,() => { // 服務器端口號為3000 console.log("服務器啟動啦!"); });
createServer
方法返回一個服務器對象,對象內(nèi)有一個listen
方法,可以設置服務器啟動的端口號和啟動成功的回調(diào)內(nèi)容
通過nodemon
運行這個文件(nodemon
可以在我們修改文件后幫助我們自動重啟服務器),控制臺打印出了我們在listen
中設置的回調(diào)內(nèi)容,說明服務器啟動成功了
我們直接在瀏覽器訪問http://localhost:3000/
時會發(fā)現(xiàn)瀏覽器一直在轉圈圈加載
注意: 直接在瀏覽器地址欄里訪問服務器接口,相當于是使用
get請求
訪問服務器接口(并且沒有跨域限制)
這是因為我們并沒有在我們創(chuàng)建的node
服務器中返回任何內(nèi)容
二、返回響應數(shù)據(jù)
我們可以通過我們定義的res
(response
對象)參數(shù)向客戶端發(fā)送響應內(nèi)容:
const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // req:接受瀏覽器傳的參數(shù) // res:返回渲染的內(nèi)容 // 傳遞數(shù)據(jù) res.write("hello world"); res.write("Ailjx"); res.end(); }).listen(3000, () => { console.log("服務器啟動啦!"); });
write
方法傳遞內(nèi)容,可以調(diào)用多次write
傳遞多條內(nèi)容,內(nèi)容必須是字符串格式最后必須調(diào)用end
方法告訴請求的調(diào)用者我們響應結束了也可以直接在end
方法內(nèi)傳遞內(nèi)容,效果與使用write
方法相同,但end
方法只能調(diào)用一次
運行上述代碼,我們直接在瀏覽器調(diào)用服務器接口:
如果服務器中不調(diào)用end
方法,瀏覽器會收不到服務器傳遞的響應結束的信號,便會一直加載請求:
返回復雜對象數(shù)據(jù)
可以傳遞復雜對象數(shù)據(jù),但必須是字符串(JSON
)的格式:
const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // end方法也可以傳遞內(nèi)容,效果與write相同 res.end("{name:{me:'Ailjx'}}"); // 或者res.end(JSON.stringify({ name: { me: "Ailjx" } })); }).listen(3000, () => { console.log("服務器啟動啦!"); });
上面瀏覽器顯示的數(shù)據(jù)被格式化了,是因為我在瀏覽器中安裝了FeHelper(前端助手)插件,能夠自動格式化JSON數(shù)據(jù)
返回html文檔數(shù)據(jù)
const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // 傳遞html內(nèi)容 res.end(` <h1>我是Ailjx,你好!</h1> `); }).listen(3000, () => { console.log("服務器啟動啦!"); });
這時發(fā)現(xiàn)我們傳遞的中文亂碼了,我們可以在響應頭的Content-Type
中指定utf-8
的字符集來解析中文,下面會講到
三、設置響應頭和狀態(tài)碼
我們可以使用response
對象的writeHead
方法來同時設置狀態(tài)碼和響應頭信息:
const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // 設置相應頭,第一參數(shù)為狀態(tài)碼,第二個參數(shù)為響應頭配置,第二個參數(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("服務器啟動啦!"); });
我們也可以使用setHeader
單獨設置響應頭信息,statusCode
單獨設置狀態(tài)碼:
const http = require("http"); // 創(chuàng)建服務器 http.createServer((req, res) => { // 設置相應頭信息 res.setHeader("Content-Type", "text/html;charset=utf-8"); // 設置狀態(tài)碼 res.statusCode = 200; // 傳遞html內(nèi)容 res.end(` <h1>我是Ailjx,你好!</h1> `); // 直接在end中傳遞,效果與write方法相同 }).listen(3000, () => { console.log("服務器啟動啦!"); });
四、實現(xiàn)路由接口
上面我們已經(jīng)成功創(chuàng)建了一個服務器,并能夠使用res
參數(shù)中的write
或end
方法向調(diào)用者發(fā)送內(nèi)容,但發(fā)送的這些內(nèi)容是不會隨著我們請求的路徑而變化的:
const http = require("http"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { res.end("Ailjx"); }); server.listen(3000);
可以看到,我們請求(訪問)不同路徑,服務器返回的數(shù)據(jù)都是一樣的,而我們在實際開發(fā)中往往是需要不同的路徑有不同的數(shù)據(jù)
這時我們就可以利用我們創(chuàng)建服務器時定義的req
參數(shù)(request
對象)來獲取用戶請求的路徑從而判斷需要返回哪些數(shù)據(jù):
const http = require("http"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req.url拿到用戶請求的路徑 console.log(req.url); res.end(); }); server.listen(3000);
運行代碼,之后在瀏覽器訪問調(diào)用一下http://localhost:3000/list,控制臺會打印出:
可以看到我們訪問的/list
路徑確實被打印出來了,但怎么還打印了一個/favicon.ico
呢?
這其實是瀏覽器在訪問一個域名時會自動訪問該域名下的/favicon.ico
靜態(tài)文件,來作為網(wǎng)頁標簽欄的小圖標,所以我們的服務器才會打印出/favicon.ico
如果是普通的ajax
調(diào)用接口是不會出現(xiàn)這種情況的,這里我們是為了方便,直接使用瀏覽器訪問接口來進行演示,所以才出現(xiàn)這種請求,我們可以簡單做一下處理:
const http = require("http"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req.url獲取用戶請求的路徑 if (req.url === "/favicon.ico") { // 讀取本地圖標 return; } console.log(req.url); res.end("Ailjx"); }); server.listen(3000);
這樣當服務器收到/favicon.ico
請求時就能直接跳過不對其進行處理
創(chuàng)建簡易路由應用
現(xiàn)在,我們開始實現(xiàn)一個簡易的路由應用,我們先創(chuàng)建兩個模塊:
renderContent.js
用來根據(jù)用戶請求路徑來返回對應的內(nèi)容:
function renderContent(url) { switch (url) { case "/api/home": return ` { page:'首頁' } `; case "/api/about": return ` { page:'關于頁' } `; default: return "404"; } } exports.renderContent = renderContent;
renderStatus.js
用來根據(jù)用戶請求的路徑來返回對應的響應狀態(tài)碼:
function renderStatus(url) { const arr = ["/api/home", "/api/about"]; return arr.includes(url) ? 200 : 404; } module.exports = { renderStatus, };
之后在我們的服務器文件server.js
中調(diào)用這兩個模塊:
const http = require("http"); const { renderContent } = require("./module/renderContent"); const { renderStatus } = require("./module/renderStatus"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req.url獲取用戶請求的路徑 if (req.url === "/favicon.ico") { return; } // 響應頭 res.writeHead(renderStatus(req.url), { // 標志返回JSON數(shù)據(jù) "Content-Type": "application/json", }); // 返回的內(nèi)容 res.end(renderContent(req.url)); }); server.listen(3000);
之后啟動服務器,在瀏覽器調(diào)用接口查看效果:
五、處理URL
在上面我們通過判斷req.url
來實現(xiàn)了簡易的路由接口應用,但當用戶調(diào)用帶有url參數(shù)的接口時,這就會出現(xiàn)問題:
這是因為這時req.url
為/api/about?name=ailj
而并不是/api/about
,我們可以手動的對這個字符串進行處理來獲得正確的路由路徑,也可以使用node.js
的內(nèi)置模塊url
來處理
URL格式轉換
修改上面的server.js
文件:
const http = require("http"); const url = require("url"); const { renderContent } = require("./module/renderContent"); const { renderStatus } = require("./module/renderStatus"); // 創(chuàng)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req.url獲取用戶請求的路徑 if (req.url === "/favicon.ico") { return; } // 新版使用全局的URL構造函數(shù) // 傳兩個參數(shù)用法 const myUrl = new URL(req.url, "http://127.0.0.1:3000").pathname; // 傳一個參數(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, () => { // 服務器端口號為3000 console.log("服務器啟動啦!"); });
全局的構造函數(shù)UR可以將完整的 url
地址轉換成url
對象(WHATWG URL標準的對象)
我們可以對其傳遞兩個參數(shù),第一個是用戶請求的路徑(路由),第二個參數(shù)是地址的根域名(我們這里是本地啟動的服務器,根域名為
http://127.0.0.1:3000
)
也可以直接傳遞一個參數(shù),該參數(shù)是帶有域名的完整
url
地址
當我們訪問http://localhost:3000/api/about?name=ailjx
時server.js
會打印出:
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
對象里 searchParams
是url
的參數(shù)部分,是一個迭代器對象URLSearchParams對象
// searchParams對象是一個迭代器對象 const query = new URL(req.url, "http://127.0.0.1:3000").searchParams; // 使用get方法獲取指定的值 console.log(query.get("name")); // ailjx
我們還可以從組成部分構造 URL
并獲取構造的字符串:
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"; // 獲取構造的 URL 字符串,請使用href屬性訪問器 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方法接收兩個參數(shù):
new URL
返回的一個WHATWG URL
格式的對象- 配置對象:
fragment
:序列化的網(wǎng)址字符串是否包含片段,默認為true
auth
:序列化的網(wǎng)址字符串是否包含用戶名和密碼,默認為true
unicode
:是否將出現(xiàn)在URL
字符串的主機組件中的Unicode
字符直接編碼而不是Punycode
編碼,默認是false
search
:序列化的網(wǎng)址字符串是否包含搜索查詢(參數(shù)),默認為true
const myURL = new URL( "https://username:password@URL路徑序列化測試?name=ailjx#foo" ); console.log( url.format(myURL, { fragment: false, // 不顯示片段(#foo) unicode: true, // 不轉化Unicode字符(中文字符) auth: false, // 不包含用戶名和密碼(username:password) search: false, // 不顯示參數(shù)(?name=ailjx) }) ); // 打印結果: 'https://url路徑序列化測試/'
舊版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)建服務器 const server = http.createServer(); server.on("request", (req, res) => { // req.url獲取用戶請求的路徑 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, () => { // 服務器端口號為3000 console.log("服務器啟動啦!"); });
url
模塊的parse
方法可以將完整的url
地址轉換成url
對象,如當我們訪問http://localhost:3000/api/about?name=ailjx
時server.js
會打印出:
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
對象里 query
是url
的參數(shù)部分,默認是字符串的格式,可以給parse
方法傳遞第二個參數(shù),將其轉換成對象格式:
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' }
這時通過url.parse(req.url, true).query.name
就可以拿到ailjx
這個參數(shù)
與parse
方法相反的有一個format
方法,它能將一個 url
對象轉換成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
構造函數(shù),傳遞兩個字符串路徑時能夠實現(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的結果都是: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的結果都是: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的結果都是:http://example.com/one/two
resolve
方法并不是簡單的將兩個路徑直接拼接在一起,而是具有它自己的一些拼接規(guī)則:
- 如果第二個路徑(接收的第二個參數(shù))開頭前帶
/
,則將直接用第二個路徑替代第一個路徑的路由部分(不會替代第一個路徑的根域名,如上面的最后一個變量c
所示:/two
替代了/one/a/b
) - 如果第二個路徑開頭前不帶
/
,第一個路徑結尾處不帶/
,則第二個路徑將會替代第一個路徑的最后一個路由,如上邊的第一個變量a
和第一個變量c
- 第二個路徑開頭前不帶
/
,第一個路徑結尾處帶/
,則直接將第二個路徑拼接在第一個路徑后面,如上邊的第變量d
和第二個變量a
- 如果第二個路徑包含根域名(
http://xxx
),則直接以第二個路徑為主(第一個路徑失效) 處理URL路徑參數(shù)
注意:
querystring
方法官方表示已棄用
NodeJS
有一個內(nèi)置模塊querystring
,它里面的parse
和stringify
方法可以幫助我們快速處理URL
上的形如id=8&name=mouse
的參數(shù):
const querystring = require("querystring"); var qs = "id=8&name=Ailjx"; // parse:路徑將參數(shù)轉化為對象 var parsed = querystring.parse(qs); console.log(parsed.id, parsed.name); // 8 Ailjx var qo = { x: 3, y: 4, }; //stringify:將對象轉化為路徑參數(shù) var parsed = querystring.stringify(qo); console.log(parsed); // x=3&y=4
querystring
中還有一對能夠轉義特殊字符的方法: escape
/unescape
:
const querystring = require("querystring"); var str = 'ns"--'; // escape:將特殊字符轉義 var escaped = querystring.escape(str); console.log(escaped); //ns%22-- // unescape:恢復轉義的特殊字符 console.log(querystring.unescape(escaped)); // ns"--
對于特殊字符的轉義在一些特殊情況下特別重要,例如我們通過用戶傳遞的參數(shù)來向mysql
數(shù)據(jù)庫查詢數(shù)據(jù),我們?yōu)榱朔乐褂脩魝鬟f的參數(shù)與sql
語句發(fā)送沖突,就可以對該參數(shù)進行轉義,以防止sql
注入
例如有一條含有用戶傳遞的param
參數(shù)的sql
語句:
let sql = `select * from users where name = "${param}" and del_status=1`
上面的sql
語句在正常情況下是只能查詢到一條數(shù)據(jù),如:
// let param = 'Ailjx' ,對應的sql語句如下: select * from users where name = "Ailjx" and del_status=1
但當param
與sql
語句沖突時:
// let param = 'ns"--',對應的sql語句如下: select * from tb_nature where nature = "ns"-- " and del_status=1
可以看到del_status
被參數(shù)中的--
注釋掉了,失去了作用,這時這條sql
能查詢到多條數(shù)據(jù),這就是sql
注入的危害,也就是我們需要轉義特殊字符的原因
正確轉換文件路徑
url
模塊針對轉換文件路徑提供了單獨的fileURLToPath
和pathToFileURL
方法,它們不僅能正確進行文件路徑的轉換而且能自動適配不同的操作系統(tǒng)
fileURLToPath
該方法能夠正確將文件網(wǎng)址url
轉換成文件路徑:
const { fileURLToPath } = require("url"); console.log(new URL("file:///C:/path/").pathname); // 獲得錯誤路徑:/C:/path/ console.log(fileURLToPath("file:///C:/path/")); // 獲得正確路徑:C:\path\ (Windows) console.log(new URL("file://nas/foo.txt").pathname); // 獲得錯誤路徑:/foo.txt console.log(fileURLToPath("file://nas/foo.txt")); // 獲得正確路徑: \\nas\foo.txt (Windows) console.log(new URL("file://c://你好.txt").pathname); // 獲得錯誤路徑:/c://%E4%BD%A0%E5%A5%BD.txt console.log(fileURLToPath("file://c://你好.txt")); // 獲得正確路徑: c:\\你好.txt (Windows)
pathToFileURL
方法,能夠將文件路徑轉換成文件網(wǎng)址url
對象:
const { pathToFileURL } = require("url"); console.log(new URL("/foo#1", "file:").href); // 錯誤: file:///foo#1 console.log(pathToFileURL("/foo#1").href); // 正確: file:///D:/foo%231 console.log(new URL("/some/path%.c", "file:").href); // 錯誤: file:///some/path%.c console.log(pathToFileURL("/some/path%.c").href); // 正確: file:///D:/some/path%25.c
轉換為Options Url對象
urlToHttpOptions
方法可以將new URL
返回的WHATWG URL
對象轉換成http.request()
或https.request()
需要的Options Url
對象
http.request()
和https.request()
在下面會講到
const { urlToHttpOptions } = require("url"); const myURL = new URL('https://a:b@測試?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' } */
六、跨域處理
在不做處理的情況下,前后端交互會出現(xiàn)CORS
跨域的問題,如:
定義一個簡單的服務器:
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("服務器啟動啦!"); });
可以看到上面我們定義的服務器并沒有設置跨域,我們直接在html
文件內(nèi)請求該服務器的接口:
index.html
<body> <div>jsonp接口測試</div> <script> fetch('http://localhost:3000/api/user') .then(res => res.json()) .then(res => console.log(res)) </script> </body>
打開該html
文件,可以看到果然出現(xiàn)了CORS
跨域的報錯
這種問題有多種解決方案:
- 后端設置響應頭來允許前端訪問服務器(只需要后端進行修改)
- 前后端使用
jsonp
方式進行交互(前后端都需要進行相應修改) - 前端配置代理(只需要前端進行修改,這在像
vue
,react
等這些框架中經(jīng)常使用)
后端設置跨域
后端可以直接在響應頭里設置跨域,而前端不需要做額外的操作
我們下載一個vscode
的Live Server
插件,用于在線運行html
文件:
右鍵html
文件選擇Open with Live Server
:
打開后發(fā)現(xiàn)前端html
的在線運行地址為http://127.0.0.1:5500
,我們在后端返回數(shù)據(jù)的響應頭里允許該地址訪問即可:
修改一下上邊的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", // 設置跨域允許http://127.0.0.1:5500訪問 "Access-Control-Allow-Origin": "http://127.0.0.1:5500", // "Access-Control-Allow-Origin": "*", 也可以使用'*',代表允許所有地址訪問 }); 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("服務器啟動啦!"); });
這時前端就能正常調(diào)用該接口了:
jsonp接口
我們知道html
的script
標簽可以引入js
文件,并且重要的是使用script
標簽引入js
沒有跨域的要求,所以我們可以在script
標簽的src
屬性中調(diào)用我們的接口來獲取后端返回的數(shù)據(jù)
前端處理:
<body> <div>jsonp接口測試</div> <div> name: <b id="myName"></b> </div> <script> // 定義一個接收后端返回數(shù)據(jù)的函數(shù) function getUser(params) { const myName = document.getElementById('myName') // 可以做一些操作 myName.innerText = params.name } // 創(chuàng)建一個script標簽 const myScript = document.createElement('script') // script標簽的src內(nèi)調(diào)用接口,需要將我們定義的接收數(shù)據(jù)的函數(shù)名傳遞給后端 myScript.src = 'http://localhost:3000/api/user?cb=getUser' // 向文檔內(nèi)插入該script標簽 document.body.appendChild(myScript) </script> </body>
或者直接用script
調(diào)用后端接口:
<body> <div>jsonp接口測試</div> <div> name: <b id="myName"></b> </div> <script> // // 定義一個接收后端返回數(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ù)名稱 // 返回給前端一個函數(shù)調(diào)用 res.end(`${urlObj.query.cb}(${JSON.stringify(userinfo)})`); break; default: res.end("404"); break; } }); server.listen(3000, () => { console.log("服務器啟動啦!"); });
可以看到使用jsonp
的原理就是在script
標簽中調(diào)用后端接口,因為script
標簽內(nèi)的js
代碼是立即執(zhí)行的
所以我們需要提前定義一個接收后端參數(shù)的處理函數(shù),然后將該函數(shù)名傳遞給后端,后端根據(jù)這個函數(shù)名返回給前端一個該函數(shù)的調(diào)用并將需要給前端的數(shù)據(jù)作為該函數(shù)的參數(shù)
上述代碼的最終效果如下:
<script> // 定義一個接收后端返回數(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
搭建了后端服務器,使其作為服務端運行,但其實node
還能當作客戶端反過來去調(diào)用其它服務端的接口,這使得node
成為了一個中間層
因為跨域只是瀏覽器的限制,服務端之間的通信并不存在跨域的問題,這樣我們就能借助node
去調(diào)用第三方具有跨域限制的接口,這是將node
作為中間層的一個非常實用的功能
模擬get請求(轉發(fā)跨域數(shù)據(jù))
在貓眼電影網(wǎng)上隨便找了一個帶有跨域的接口,我們直接調(diào)用時會報CORS
跨域問題:
<h1>使用node模擬get請求</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>
這時我們可以利用node
幫我們?nèi)フ埱筮@個接口的數(shù)據(jù):
const http = require("http"); const https = require("https"); // http和https的區(qū)別僅在于一個是http協(xié)議一個是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充當客戶端去貓眼的接口獲取數(shù)據(jù) httpget((data) => res.end(data)); break; default: res.end("404"); break; } }); server.listen(3000, () => { console.log("服務器啟動啦!"); }); function httpget(cb) { // 定義一個存放數(shù)據(jù)的變量 let data = ""; // 因為貓眼的接口是https協(xié)議的,所以我們需要引入https // http和https都具有一個get方法能夠發(fā)起get請求,區(qū)別是一個是http協(xié)議,一個是https協(xié)議 // http get方法第一個參數(shù)為接口地址,第二個參數(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ù)是一點點返回的,并不是直接返回全部 // 監(jiān)聽data,當有數(shù)據(jù)返回時就會被調(diào)用 res.on("data", (chunk) => { // 收集數(shù)據(jù) data += chunk; }); // 監(jiān)聽end,數(shù)據(jù)返回完畢后調(diào)用 res.on("end", () => { cb(data); }); } ); }
之后我們調(diào)用我們node
的接口即可:
<h1>使用node模擬get請求</h1> <script> fetch('http://localhost:3000/api/maoyan') .then(res => res.json()) .then(res => { console.log(res); }) </script>
這里node
即作為服務端給我們提供接口/api/maoyan
,又充當了一下客戶端去調(diào)用貓眼的接口,這樣我們就繞過了貓眼的跨域限制獲取了它的數(shù)據(jù)
模擬post請求(服務器提交)
使用node
模擬post
請求需要使用http
或https
的request
方法來進行請求的配置,稍微有點麻煩:
http
和https
模塊的區(qū)別僅在于一個是http
協(xié)議一個是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充當客戶端去小米有品的post接口獲取數(shù)據(jù) httpPost((data) => res.end(data)); break; default: res.end("404"); break; } }); server.listen(3000, () => { console.log("服務器啟動啦!"); }); function httpPost(cb) { // 定義一個存放數(shù)據(jù)的變量 let data = ""; // 這是小米有品的一個post接口:"https://m.xiaomiyoupin.com/mtop/market/search/placeHolder" // 這個接口調(diào)用時需要傳“[{}, { baseParam: { ypClient: 1 } }]”這樣一個參數(shù)才能返回數(shù)據(jù) // 配置Options Url請求對象 const options = { // 域名 hostname: "m.xiaomiyoupin.com", // 接口端口號,443代表https,80代表http port: "443", // 路徑 path: "/mtop/market/search/placeHolder", // 請求方式 method: "POST", // 請求頭 Headers: { // 表示接收json數(shù)據(jù) "Content-Type": "application/json", }, }; // http request方法第一個參數(shù)為請求對象,第二個參數(shù)為回調(diào)函數(shù),request方法返回一個值(),在該值內(nèi)通過調(diào)用write向post請求傳遞數(shù)據(jù) const req = https.request(options, (res) => { // 監(jiān)聽data,當有數(shù)據(jù)返回時就會被調(diào)用 res.on("data", (chunk) => { // 收集數(shù)據(jù) data += chunk; }); // 監(jiān)聽end,數(shù)據(jù)返回完畢后調(diào)用 res.on("end", () => { cb(data); }); }); // 發(fā)送post的參數(shù) // req.write(JSON.stringify([{}, { baseParam: { ypClient: 1 } }])); // 這里的使用與我們server服務器中的req參數(shù)使用方式差不多,不要忘記最后調(diào)用end方法,并且也可以直接在end方法內(nèi)傳遞數(shù)據(jù) req.end(JSON.stringify([{}, { baseParam: { ypClient: 1 } }])); }
<body> <h1>使用node模擬post請求</h1> <script> fetch('http://localhost:3000/api/xiaomiyoumin') .then(res => res.json()) .then(res => { console.log(res); }) </script> </body>
八、使用Node實現(xiàn)爬蟲
我們使用node
的一個cheerio
包也可以實現(xiàn)爬蟲功能,如我們爬取貓眼移動端https://i.maoyan.com
首頁的一些數(shù)據(jù):
我們在一個文件夾內(nèi)打開終端,先生成package.json
文件:
npm init
安裝cheerio
:
npm i cheerio
文件夾內(nèi)創(chuàng)建我們的服務器文件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", }; // 獲取頁面數(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
請求(接口為:https://i.maoyan.com)的寫法,你也可以直接使用https.get
進行調(diào)用
通過瀏覽器調(diào)用我們的服務器:
可以看到我們成功獲取了貓眼移動端首頁的html
文檔,我們需要的數(shù)據(jù)都在文檔中了,之后我們只需將我們需要的數(shù)據(jù)提取出來,這里就將用到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", }; // 獲取頁面數(shù)據(jù) const req = https.request(options, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { // 處理頁面數(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
文檔字符串,它返回一個對象,該對象與jQuery
相似,我們可以對其使用jQuery
的語法進行操作
上面使用的class
類名都是在貓眼首頁文檔的對應類名,如:
重新訪問一下我們的服務器:
可以看到我們已經(jīng)爬蟲成功!
如果在請求https://i.maoyan.com接口時獲取不到數(shù)據(jù),可能是我們?yōu)g覽器上的貓眼網(wǎng)進入了驗證操作,我們在訪問我們服務器的這個瀏覽器上打開https://i.maoyan.com進行驗證一下,之后我們就能請求到https://i.maoyan.com的html文檔內(nèi)容了
到此這篇關于Node.js 搭建后端服務器內(nèi)置模塊( http+url+querystring 的使用)的文章就介紹到這了,更多相關Node.js 后端搭建內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Node.js中的http請求客戶端示例(request client)
本篇文章主要介紹了Node.js中的http請求客戶端示例(request client),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05