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

通過nodemon運行這個文件(nodemon可以在我們修改文件后幫助我們自動重啟服務器),控制臺打印出了我們在listen中設置的回調內容,說明服務器啟動成功了
我們直接在瀏覽器訪問http://localhost:3000/時會發(fā)現(xiàn)瀏覽器一直在轉圈圈加載
注意: 直接在瀏覽器地址欄里訪問服務器接口,相當于是使用
get請求訪問服務器接口(并且沒有跨域限制)

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

如果服務器中不調用end方法,瀏覽器會收不到服務器傳遞的響應結束的信號,便會一直加載請求:

返回復雜對象數(shù)據(jù)
可以傳遞復雜對象數(shù)據(jù),但必須是字符串(JSON)的格式:
const http = require("http");
// 創(chuàng)建服務器
http.createServer((req, res) => {
// end方法也可以傳遞內容,效果與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內容
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內容
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內容
res.end(`
<h1>我是Ailjx,你好!</h1>
`); // 直接在end中傳遞,效果與write方法相同
}).listen(3000, () => {
console.log("服務器啟動啦!");
});四、實現(xiàn)路由接口
上面我們已經成功創(chuàng)建了一個服務器,并能夠使用res參數(shù)中的write或end方法向調用者發(fā)送內容,但發(fā)送的這些內容是不會隨著我們請求的路徑而變化的:
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);運行代碼,之后在瀏覽器訪問調用一下http://localhost:3000/list,控制臺會打印出:

可以看到我們訪問的/list路徑確實被打印出來了,但怎么還打印了一個/favicon.ico呢?
這其實是瀏覽器在訪問一個域名時會自動訪問該域名下的/favicon.ico靜態(tài)文件,來作為網(wǎng)頁標簽欄的小圖標,所以我們的服務器才會打印出/favicon.ico
如果是普通的ajax調用接口是不會出現(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ù)用戶請求路徑來返回對應的內容:
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中調用這兩個模塊:
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",
});
// 返回的內容
res.end(renderContent(req.url));
});
server.listen(3000);之后啟動服務器,在瀏覽器調用接口查看效果:


五、處理URL
在上面我們通過判斷req.url來實現(xiàn)了簡易的路由接口應用,但當用戶調用帶有url參數(shù)的接口時,這就會出現(xiàn)問題:

這是因為這時req.url為/api/about?name=ailj而并不是/api/about,我們可以手動的對這個字符串進行處理來獲得正確的路由路徑,也可以使用node.js的內置模塊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)址字符串是否包含片段,默認為trueauth:序列化的網(wǎng)址字符串是否包含用戶名和密碼,默認為trueunicode:是否將出現(xiàn)在URL字符串的主機組件中的Unicode字符直接編碼而不是Punycode編碼,默認是falsesearch:序列化的網(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=110URL路徑拼接
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/tworesolve方法并不是簡單的將兩個路徑直接拼接在一起,而是具有它自己的一些拼接規(guī)則:
- 如果第二個路徑(接收的第二個參數(shù))開頭前帶
/,則將直接用第二個路徑替代第一個路徑的路由部分(不會替代第一個路徑的根域名,如上面的最后一個變量c所示:/two替代了/one/a/b) - 如果第二個路徑開頭前不帶
/,第一個路徑結尾處不帶/,則第二個路徑將會替代第一個路徑的最后一個路由,如上邊的第一個變量a和第一個變量c - 第二個路徑開頭前不帶
/,第一個路徑結尾處帶/,則直接將第二個路徑拼接在第一個路徑后面,如上邊的第變量d和第二個變量a - 如果第二個路徑包含根域名(
http://xxx),則直接以第二個路徑為主(第一個路徑失效) 處理URL路徑參數(shù)
注意:
querystring方法官方表示已棄用
NodeJS有一個內置模塊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=4querystring中還有一對能夠轉義特殊字符的方法: 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文件內請求該服務器的接口:
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等這些框架中經常使用)
后端設置跨域
后端可以直接在響應頭里設置跨域,而前端不需要做額外的操作
我們下載一個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("服務器啟動啦!");
});這時前端就能正常調用該接口了:

jsonp接口
我們知道html的script標簽可以引入js文件,并且重要的是使用script標簽引入js沒有跨域的要求,所以我們可以在script標簽的src屬性中調用我們的接口來獲取后端返回的數(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內調用接口,需要將我們定義的接收數(shù)據(jù)的函數(shù)名傳遞給后端
myScript.src = 'http://localhost:3000/api/user?cb=getUser'
// 向文檔內插入該script標簽
document.body.appendChild(myScript)
</script>
</body>或者直接用script調用后端接口:
<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>
<!-- 調用后端接口 -->
<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ù)調用
res.end(`${urlObj.query.cb}(${JSON.stringify(userinfo)})`);
break;
default:
res.end("404");
break;
}
});
server.listen(3000, () => {
console.log("服務器啟動啦!");
});可以看到使用jsonp的原理就是在script標簽中調用后端接口,因為script標簽內的js代碼是立即執(zhí)行的
所以我們需要提前定義一個接收后端參數(shù)的處理函數(shù),然后將該函數(shù)名傳遞給后端,后端根據(jù)這個函數(shù)名返回給前端一個該函數(shù)的調用并將需要給前端的數(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還能當作客戶端反過來去調用其它服務端的接口,這使得node成為了一個中間層

因為跨域只是瀏覽器的限制,服務端之間的通信并不存在跨域的問題,這樣我們就能借助node去調用第三方具有跨域限制的接口,這是將node作為中間層的一個非常實用的功能
模擬get請求(轉發(fā)跨域數(shù)據(jù))
在貓眼電影網(wǎng)上隨便找了一個帶有跨域的接口,我們直接調用時會報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幫我們去請求這個接口的數(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ù)為回調函數(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ù)返回時就會被調用
res.on("data", (chunk) => {
// 收集數(shù)據(jù)
data += chunk;
});
// 監(jiān)聽end,數(shù)據(jù)返回完畢后調用
res.on("end", () => {
cb(data);
});
}
);
}之后我們調用我們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,又充當了一下客戶端去調用貓眼的接口,這樣我們就繞過了貓眼的跨域限制獲取了它的數(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"
// 這個接口調用時需要傳“[{}, { 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ù)為回調函數(shù),request方法返回一個值(),在該值內通過調用write向post請求傳遞數(shù)據(jù)
const req = https.request(options, (res) => {
// 監(jiān)聽data,當有數(shù)據(jù)返回時就會被調用
res.on("data", (chunk) => {
// 收集數(shù)據(jù)
data += chunk;
});
// 監(jiān)聽end,數(shù)據(jù)返回完畢后調用
res.on("end", () => {
cb(data);
});
});
// 發(fā)送post的參數(shù)
// req.write(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
// 這里的使用與我們server服務器中的req參數(shù)使用方式差不多,不要忘記最后調用end方法,并且也可以直接在end方法內傳遞數(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ù):

我們在一個文件夾內打開終端,先生成package.json文件:
npm init
安裝cheerio:
npm i cheerio
文件夾內創(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進行調用
通過瀏覽器調用我們的服務器:

可以看到我們成功獲取了貓眼移動端首頁的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類名都是在貓眼首頁文檔的對應類名,如:

重新訪問一下我們的服務器:

可以看到我們已經爬蟲成功!
如果在請求https://i.maoyan.com接口時獲取不到數(shù)據(jù),可能是我們?yōu)g覽器上的貓眼網(wǎng)進入了驗證操作,我們在訪問我們服務器的這個瀏覽器上打開https://i.maoyan.com進行驗證一下,之后我們就能請求到https://i.maoyan.com的html文檔內容了
到此這篇關于Node.js 搭建后端服務器內置模塊( http+url+querystring 的使用)的文章就介紹到這了,更多相關Node.js 后端搭建內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Nodejs?Socket連接池及TCP?HTTP網(wǎng)絡模型詳解
這篇文章主要為大家介紹了Nodejs?Socket連接池及TCP?HTTP網(wǎng)絡模型,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
Node.js中文件操作模塊File System的詳細介紹
FileSystem模塊是類似UNIX(POSIX)標準的文件操作API,用于操作文件系統(tǒng)——讀寫目錄、讀寫文件——Node.js底層使用C程序來實現(xiàn),這些功能是客戶端JS所不具備的。下面這篇文章就給大家詳細介紹了Node.js中的文件操作模塊File System,有需要的朋友們可以參考借鑒。2017-01-01
Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程
本文主要介紹了Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-05-05
npm?install?-g?@vue/cli常見問題解決匯總
這篇文章主要給大家介紹了關于npm?install?-g?@vue/cli常見問題解決的相關資料,文中通過實例代碼將解決的方式介紹的非常詳細,對遇到這個問題的朋友具有一定的參考學習價值,需要的朋友可以參考下2022-08-08
node?NPM庫qs?iconv-lite字符串編碼轉換及解析URL查詢學習
這篇文章主要為大家介紹了node?NPM庫之qs解析URL查詢字符串及iconv-lite字符串編碼轉換學習,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07
Visual?Studio?Code中npm腳本找不到圖文解決辦法
這篇文章主要給大家介紹了關于Visual?Studio?Code中npm腳本找不到的圖文解決辦法,做前端開發(fā)如果項目達到了一定的規(guī)模就離不開npm了,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-07-07

