內建模組
http 模組
要使用 HTTP Server 和 Client,則必須
require('http')
。
const http = require('http');
// 建立 server
http.createServer((req, res) => {
// req 接收瀏覽器傳的參數
// res 回傳渲染的內容
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
res.write(`
<html>
<b>hello world</b>
<div>你好 世界</div>
</html>
`);
res.end();
}).listen(3000, () => {
console.log('server start');
});
const http = require('http');
const server = http.createServer();
// 監聽請求事件
server.on('request', (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
data: 'Hello World!',
})
);
});
server.listen(3000);
url 模組
parse
const url = require('url')
const urlString = 'http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110';
const parsedStr = url.parse(urlString);
console.log(parsedStr);
/*
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'localhost:3000',
port: '3000',
hostname: 'localhost',
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',
href: 'http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110'
}
*/
format
const url = require('url')
const urlObject = {
protocol: 'http:',
slashes: true,
auth: null,
host: 'localhost:3000',
port: '3000',
hostname: 'localhost',
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',
href: 'http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110',
};
const parsedObj = url.format(urlObject);
console.log(parsedObj);
/*
http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110
*/
resolve
const url = require('url')
const a = url.resolve('/one/two/three', 'four'); // ( 注意最後加 / ,不加 / 的區別 )
const b = url.resolve('http://example.com/', '/one');
const c = url.resolve('http://example.com/one', '/two');
console.log(a + ',' + b + ',' + c);
/*
/one/two/four,http://example.com/one,http://example.com/two
*/
新版寫法
使用 new URL
取代 url.parse
const urlString = 'http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110';
const myURL = new URL(urlString);
console.log(myURL);
/*
URL {
href: 'http://localhost:3000/ad/index.html?id=8&name=mouse#tag=110',
origin: 'http://localhost:3000',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:3000',
hostname: 'localhost',
port: '3000',
pathname: '/ad/index.html',
search: '?id=8&name=mouse',
searchParams: URLSearchParams { 'id' => '8', 'name' => 'mouse' },
hash: '#tag=110'
}
*/
使用 new URL
取代 url.resolve
const newUrl = new URL('/one', 'http://example.com/aaa/bbb/');
console.log(newUrl.href);
// prints: http://example.com/one
新的 url.format
寫法
const myURL = new URL('https://a:b@測試?abc#foo');
console.log(url.format(myURL, { unicode: true, auth: false, fragment: false, search: false }));
// prints: https://測試/
fileURLToPath & urlToHttpOptions
const { fileURLToPath, urlToHttpOptions } = require('node:url');
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)
const myURL = new URL('https://a:b@測試?abc#foo');
console.log(myURL);
/*
URL {
href: 'https://a:b@xn--g6w251d/?abc#foo',
origin: 'https://xn--g6w251d',
protocol: 'https:',
username: 'a',
password: 'b',
host: 'xn--g6w251d',
hostname: 'xn--g6w251d',
port: '',
pathname: '/',
search: '?abc',
searchParams: URLSearchParams { 'abc' => '' },
hash: '#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'
}
*/
querystring 模組
parse
const querystring = require('node:querystring');
const str = 'name=sheep&age=25&job=f2e';
const obj = querystring.parse(str);
console.log(obj);
// prints: [Object: null prototype] { name: 'sheep', age: '25', job: 'f2e' }
stringify
const myobj = {
a: 1,
b: 2,
c: 3,
};
const mystr = querystring.stringify(myobj);
console.log(mystr);
// prints: a=1&b=2&c=3
escape/unescape
const str1 = 'id=3&city=台北&url=https://www.google.com';
const escaped = querystring.escape(str1);
console.log(escaped);
// prints: id%3D3%26city%3D%E5%8F%B0%E5%8C%97%26url%3Dhttps%3A%2F%2www.google.com
const escape1 = 'id%3D3%26city%3D%E5%8F%B0%E5%8C%97%26url%3Dhttps%3A%2F%2www.google.com';
const str2 = querystring.unescape(escape1);
console.log(str2);
// prints: id=3&city=台北&url=https://www.google.com
http 模組補充
JSONP
const http = require('http');
const app = http.createServer((req, res) => {
const myURL = new URL(req.url, 'http://localhost:3000');
// console.log(myURL.searchParams.get('callback'));
const cb = myURL.searchParams.get('callback');
switch (myURL.pathname) {
case '/api/user':
res.end(
`${cb}(${JSON.stringify({
name: 'sheep',
age: 18,
})})`
);
break;
default:
res.end('404');
}
});
app.listen(3000, () => {
console.log('localhost:3000');
});
<body>
<!-- 呼叫 JSONP -->
<script>
var oscript = document.createElement('script');
oscript.src = 'http://localhost:3000/api/user?callback=test';
document.body.appendChild(oscript);
function test(obj) {
console.log(obj);
}
</script>
</body>
跨域:CORS
const http = require('http');
const app = http.createServer((req, res) => {
const myURL = new URL(req.url, 'http://localhost:3000');
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'access-control-allow-origin': '*',
});
switch (myURL.pathname) {
case '/api/user':
res.end(
JSON.stringify({
name: 'sheep',
age: 18,
})
);
break;
default:
res.end('404');
}
});
app.listen(3000, () => {
console.log('localhost:3000');
});
get
const http = require('http');
const https = require('https');
const app = http.createServer((req, res) => {
const myURL = new URL(req.url, 'http://localhost:3000');
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'access-control-allow-origin': '*',
});
switch (myURL.pathname) {
case '/api/films':
httpGet((data) => {
res.end(data);
});
break;
default:
res.end('404');
}
});
app.listen(3000, () => {
console.log('localhost:3000');
});
function httpGet(cb) {
let data = '';
https.get('somethingURL', (res) => {
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
// console.log(data);
cb(data);
});
});
}
post
const http = require('http');
const https = require('https');
const app = http.createServer((req, res) => {
const myURL = new URL(req.url, 'http://localhost:3000');
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'access-control-allow-origin': '*',
});
switch (myURL.pathname) {
case '/api/post':
httpPost((data) => {
res.end(data);
});
break;
default:
res.end('404');
}
});
app.listen(3000, () => {
console.log('localhost:3000');
});
function httpPost(cb) {
let data = '';
const options = {
hostname: 'm.xiaomiyoupin.com',
port: '443',
path: '/mtop/market/search/placeHolder',
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 'Content-Type': 'x-www-form-urlencoded'
},
};
const req = https.request(options, (res) => {
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => cb(data));
});
// req.write("name=sheep&age=100")
req.write(JSON.stringify([{}, { baseParam: { ypClient: 1 } }]));
req.end();
}
爬蟲
const http = require('http');
const https = require('https');
const cheerio = require('cheerio');
const app = http.createServer((req, res) => {
const myURL = new URL(req.url, 'http://localhost:3000');
res.writeHead(200, {
'Content-Type': 'application/json;charset=utf-8',
'access-control-allow-origin': '*',
});
switch (myURL.pathname) {
case '/api/films':
httpGet((data) => {
res.end(spider(data));
});
break;
default:
res.end('404');
}
});
app.listen(3000, () => {
console.log('localhost:3000');
});
function httpGet(cb) {
let data = '';
https.get('https://i.maoyan.com/', (res) => {
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
cb(data);
});
});
}
function spider(data) {
// npm i cheerio
const $ = cheerio.load(data);
const $movielist = $('.column.content');
const movies = [];
$movielist.each((index, value) => {
movies.push({
title: $(value).find('.title').text(),
grade: $(value).find('.grade').text(),
actor: $(value).find('.actor').text(),
});
});
console.log(movies);
return JSON.stringify(movies);
}
events 模組
const EventEmitter = require('events');
const event = new EventEmitter();
event.on('play', (data) => {
console.log('事件觸發了-play', data);
});
event.on('run', (data) => {
console.log('事件觸發了-run', data);
});
setTimeout(() => {
event.emit('play', '111111');
}, 2000);
fs 模組
const fs = require('fs');
// 新增資料夾
fs.mkdir('./avatar', (err) => {
// console.log(err);
if (err?.code === 'EEXIST') {
console.log('資料夾已存在');
}
});
// 重新命名資料夾
fs.rename('./avatar', './avatar2', (err) => {
if (err?.code === 'ENOENT') {
console.log('該資料夾並不存在');
}
});
// 刪除資料夾
fs.rmdir('./avatar', (err) => {
if (err?.code === 'ENOENT') {
console.log('資料夾不存在');
}
});
// 寫內容到檔案裡
fs.writeFile('./avatar/a.txt', 'hello', (err) => {
console.log(err);
});
// 新增內容到檔案裡
fs.appendFile('./avatar/a.txt', '\nworld', (err) => {
console.log(err);
});
// 讀取檔案內容
fs.readFile('./avatar/a.txt', (err, data) => {
if (!err) {
console.log(data.toString('utf-8'));
}
});
fs.readFile('./avatar/a.txt', 'utf-8', (err, data) => {
if (!err) {
console.log(data);
}
});
// 刪除檔案
fs.unlink('./avatar/a.txt', (err) => {
console.log(err);
});
// 讀取資料夾內容
fs.readdir('./avatar', (err, data) => {
if (!err) {
console.log(data);
}
});
// 讀取檔案狀態
fs.stat('./avatar/text.txt', (err, data) => {
console.log(data.isFile());
console.log(data.isDirectory());
});
// 同步讀取檔案
try {
const content = fs.readFileSync('./avatar/text.txt', 'utf-8')
console.log(content)
console.log(0)
} catch (e) {
console.log(e.message)
}
// 非同步讀取檔案:方法一
fs.readFile('./avatar/text.txt', 'utf-8', (err, content) => {
console.log(content)
console.log(0)
})
console.log(1)
// 非同步讀取檔案:方法二
const fs = require("fs").promises
fs.readFile('./avatar/text.txt', 'utf-8').then(result => {
console.log(result)
})
stream 模組
stream
是 Node.js 提供的又一個僅在 server 端可用的模組,目的是支援“資料流”這種資料結構。
什麼是資料流?流是一種抽象的資料結構。想像水流,當在水管中流動時,就可以從某個地方(例如自來水廠)源源不斷地到達另一個地方(比如你家的洗手池)。我們也可以把資料看成是資料流,比如你敲鍵盤的時候,就可以把每個字符依次連起來,看成字符流。這個流是從鍵盤輸入到應用程式,實際上它還對應著一個名字:標準輸入流(stdin)。
如果應用程式把字符一個一個輸出到顯示器上,這也可以看成是一個資料流,這個流也有名字:標準輸出流(stdout)。資料流的特點是資料是有序的,而且必須依次讀取,或者依次寫入,不能像Array那樣隨機定位。
有些流用來讀取資料,比如從檔案讀取資料時,可以打開一個檔案流,然後從檔案流中不斷地讀取資料。有些流用來寫入資料,比如向檔案寫入資料時,只需要把資料不斷地往檔案流中寫進去就可以了。
在 Node.js 中,資料流也是一個物件,我們只需要回應資料流的事件就可以了:data
事件表示資料流的資料已經可以讀取了,end
事件表示這個資料流已經到末尾了,沒有資料可以讀取了,error
事件表示出錯了。
const fs = require('fs');
const rs = fs.createReadStream('./1.txt', 'utf-8');
rs.on('data', (chunk) => {
console.log('chunk-', chunk);
});
rs.on('end', () => {
console.log('end');
});
rs.on('error', (err) => {
console.log(err);
});
要注意,data
事件可能會有多次,每次傳遞的 chunk
是資料流的一部分資料。
要以資料流的形式寫入檔案,只需要不斷呼叫 write()
方法,最後以 end()
結束:
const fs = require('fs');
const ws = fs.createWriteStream('./2.txt', 'utf-8');
ws.write('1111111');
ws.write('2222222');
ws.write('3333333');
ws.end();
pipe
就像可以把兩個水管串成一個更長的水管一樣,兩個流也可以串起來。一個 Readable
流和一個 Writable
流串起來後,所有的資料自動從 Readable
流進入 Writable
流,這種操作叫 pipe
。
在 Node.js 中,Readable
流有一個 pipe()
方法,就是用來幹這件事的。
讓我們用 pipe()
把一個檔案流和另一個檔案流串起來,這樣源檔案的所有資料就自動寫入到目標檔案裡了,所以,這實際上是一個複製檔案的程式:
const fs = require('fs');
const readStream = fs.createReadStream('./1.txt');
const writeStream = fs.createWriteStream('./2.txt');
readStream.pipe(writeStream);
zlib 模組
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();
http
.createServer((req, res) => {
// res 可寫流
const readStream = fs.createReadStream('./index.js');
res.writeHead(200, {
'Content-Type': 'application/x-javascript;charset=utf-8',
'Content-Encoding': 'gzip',
});
readStream.pipe(gzip).pipe(res);
})
.listen(3000, () => {
console.log('server start!');
});
crypto 模組
crypto 模組的目的是為了提供通用的加密和雜湊(Hash)演算法。用純 JavaScript 程式碼實現這些功能不是不可能,但速度會非常慢。Nodejs 用 C/C++ 實現這些演算法後,透過 crypto 這個模組輸出為 JavaScript 介面,這樣用起來方便,執行速度也快。
MD5 是一種常用的雜湊演算法,用於給任意資料一個“簽章”。這個簽章通常用一個十六進位的字串表示:
const crypto = require('crypto');
const hash = crypto.createHash('md5');
// 可任意多次呼叫update():
hash.update('Hello, world!');
hash.update('Hello, nodejs!');
console.log(hash.digest('hex'));
update()
方法預設字串編碼為 UTF-8
,也可以傳入 Buffer。
如果要計算 SHA1,只需要把 'md5'
改成 'sha1'
,就可以得到 SHA1 的結果 1f32b9c9932c02227819a4151feed43e131aca40
。
Hmac 演算法也是一種雜湊演算法,它可以利用 MD5 或 SHA1 等雜湊演算法。不同的是,Hmac 還需要一個密鑰:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'secret-key');
hmac.update('Hello, world!');
hmac.update('Hello, nodejs!');
console.log(hmac.digest('hex')); // 80f7e22570...
只要密鑰發生了變化,那麼同樣的輸入資料也會得到不同的簽章,因此,可以把 Hmac 理解為用隨機數“增強”的雜湊演算法。
AES是一種常用的對稱加密演算法,加解密都用同一個密鑰。crypto 模組提供了 AES,但是需要自己封裝好函式,便於使用:
const crypto = require('crypto');
function encrypt(key, iv, data) {
let dep = crypto.createCipheriv('aes-128-cbc', key, iv);
return dep.update(data, 'binary', 'hex') + dep.final('hex');
}
function decrypt(key, iv, crypted) {
crypted = Buffer.from(crypted, 'hex').toString('binary');
let dep = crypto.createDecipheriv('aes-128-cbc', key, iv);
return dep.update(crypted, 'binary', 'utf8') + dep.final('utf8');
}
//16*8 = 128
let key = 'abcdef1234567890';
let iv = 'tbcdey1234567890';
let data = 'sheep';
let crypted = encrypt(key, iv, data);
console.log('加密結果-', crypted);
let decrypted = decrypt(key, iv, crypted);
console.log('解密結果-', decrypted);