Node.js服務端編程
第一章:服務端基礎概念
1.1-網站的組成
網站應用程序主要分為兩大部分:客戶端和服務器端。
- 客戶端:在瀏覽器中運行的部分,就是用戶看到並與之交互的界面程序。使用HTML、CSS、JavaScript構建。
- 服務器端:在服務器中運行的部分,負責存儲數據和處理應用邏輯。
1.2-Node網站服務器
能夠提供網站訪問服務的機器就是網站服務器,它能夠接收客戶端的請求,能夠對請求做出響應。
1.3-IP地址
互聯網中設備的唯一標識。
IP是Internet Protocol Address的簡寫,代表互聯網協議地址。
1.4-域名
由於IP地址難於記憶,所以產生了域名的概念,所謂域名就是平時上網所使用的網址。
http://www.baidu.com => http://39.156.66.14/
雖然在地址欄中輸入的是網址, 但是最終還是會將域名轉換為ip才能訪問到指定的網站服務器。
1.5-端口
端口是計算機與外界通訊交流的出口,用來區分服務器電腦中提供的不同的服務。
1.6-URL
統一資源定位符,又叫URL(Uniform Resource Locator),是專為標識Internet網上資源位置而設的一種編址方式,我們平時所說的網頁地址指的即是URL。
URL的組成:傳輸協議://服務器IP或域名:端口/資源所在位置標識
https://www.baidu.com/index.html
1.7-開發過程中客戶端和服務器端
在開發階段,客戶端和服務器端使用同一台電腦,即開發人員電腦。
本機域名:localhost
本地IP :127.0.0.1
第二章:創建web服務器
我們要開發后端程序(服務端),必須要有web服務器,才能提供服務。
那Node.js創建服務器的步驟如下:
// 1. 導入Node.js系統模塊http模塊
const http = require("http");
// 2. 創建web服務器對象
const app = http.createServer();
// 3. 給服務器注冊request事件,監聽用戶請求,並作出響應
app.on("request", (req, res) => {
// 響應客戶端
res.end("Hello!")
});
// 4. 監聽端口4000
app.listen(4000);
在本地瀏覽器總:輸入地址→localhost:4000
第三章:HTTP協議
3.1-概念
超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)規定了如何從網站服務器傳輸超文本到本地瀏覽器,它基於客戶端服務器架構工作,是客戶端(用戶)和服務器端(網站)請求和應答的標准。
3.2-報文
在HTTP請求和響應的過程中傳遞的數據塊就叫報文,包括要傳送的數據和一些附加信息,並且要遵守規定好的格式。
3.3-請求報文
請求方式
- get
- post
請求地址
const http = require("http");
const app = http.createServer();
app.on("request", (req, res) => {
// 獲取請求報文
console.log(req.headers);
// 獲取請求的路徑
console.log(req.url);
// 獲取請求的方式
console.log(req.method);
});
app.listen(4000);
3.4-響應報文
HTTP狀態碼
200 請求成功
404 請求的資源沒有被找到
500 服務器端錯誤
400 客戶端請求有語法錯誤
內容類型
text/html
text/css
application/javascript
image/jpeg
application/json
代碼演示
const http = require("http");
const app = http.createServer();
app.on("request", (req, res) => {
// 設置響應報文(狀態碼和響應的內容類型與編碼)
res.writeHead(200, {
"Content-Type":"text/html;charset=utf-8"
})
res.end("<h1>你好!</h1>");
});
app.listen(4000);
第四章:HTTP請求與響應處理
4.1-請求參數
客戶端向服務器端發送請求時,有時需要攜帶一些客戶信息,客戶信息需要通過請求參數的形式傳遞到服務器端,比如查詢操作、登錄操作等。
https://www.baidu.com/s?wd=程序員是猴子嗎?
4.2-GET請求參數
參數被放置在瀏覽器地址欄中,例如:http://localhost:3000/?id=1&type=2
參數獲取需要借助系統模塊url
,url模塊用來處理url地址
const http = require("http");
// 【導入url模塊】
const url = require("url");
const app = http.createServer();
app.on("request", (req, res) => {
// 【將req.url轉換為對象,並解構出 [請求路徑] 和 [對象格式的請求參數]】
let {query,pathname} = url.parse(req.url, true);
// 請求路徑
console.log(pathname);
// 請求參數對象
console.log(query);
});
app.listen(3000);
4.3-POST請求參數
參數被放置在請求體中進行傳輸
獲取POST參數需要使用data事件
和end事件
使用querystring系統模塊
將參數轉換為對象格式
表單:
<form action="http://localhost:4000" method="POST">
用戶名:<input type="text" name="username">
密碼:<input type="password" name="pwd">
<input type="submit">
</form>
node服務
const http = require("http");
// 導入querystring模塊
const querystring = require("querystring");
const app = http.createServer();
app.on("request", (req, res) => {
// 定義變量,接收客戶端post請求參數
let paramsStr = "";
// 注冊data事件接收參數數據
req.on("data", (chunk) => { paramsStr += chunk });
// 注冊end事件,接收完畢后的處理
req.on("end", () => {
let paramsObj = querystring.parse(paramsStr);
console.log(paramsObj);
});
});
app.listen(4000);
4.4-路由
路由是指客戶端請求地址與服務器端程序代碼的對應關系。簡單的說,就是請求什么響應什么。
const http = require("http");
const url = require("url");
const app = http.createServer();
app.on("request", (req, res) => {
// 獲取請求的路徑
let { pathname } = url.parse(req.url, true);
// 設置響應頭
res.writeHead(200, { "content-Type": "text/html;charset=utf-8" });
// 處理路由
if (pathname == "/index.html" || pathname == "/") {
res.end("<h1>首頁</h1>");
} else if (pathname == "/list.html") {
res.end("<h1>列表頁面</h1>");
} else {
res.writeHead(404, { "content-Type": "text/html;charset=utf-8" });
res.end("<h1>頁面不存在</h1>");
}
});
app.listen(4000);
4.5-靜態資源
服務器端不需要處理,可以直接響應給客戶端的資源就是靜態資源,例如CSS、JavaScript、image文件。
https://m.360buyimg.com/babel/jfs/t1/36002/35/9106/3311/5cd4f1bdE06ff07ed/9570fdd46ecd3a76.png
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
// 導入第三方模塊mime
const mime = require("mime");
const app = http.createServer();
app.on("request", (req, res) => {
// 獲取請求的路徑
let { pathname } = url.parse(req.url, true);
// 拼接服務器上文件的物理路徑
let realPath = path.join(__dirname, "public", pathname);
// 獲取請求的資源類型
let type = mime.getType(realPath);
// 讀取服務器本地文件
fs.readFile(realPath, (err, data) => {
if (err) {
res.writeHead(404,{"Content-type":type+";charset=utf-8"});
res.end("訪問資源不存在");
return;
}
res.writeHead(200);
res.end(data);
});
});
app.listen(4000);
4.6-動態資源
相同的請求地址不同的響應資源,這種資源就是動態資源。
4.7-客戶端請求途徑
- GET方式
- 瀏覽器地址欄
- link標簽的href屬性
- script標簽的src屬性
- img標簽的src屬性
- Form表單提交
- POST方式
- Form表單提交
第五章:Node異步編程
5.1-同步API, 異步API
同步API:只有當前API執行完成后,才能繼續執行下一個API
console.log('before');
console.log('after');
異步API:當前API的執行不會阻塞后續代碼的執行
console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');
區別1:同步API, 異步API的區別(返回值):
// 【同步】
function sum (n1, n2) {
return n1 + n2;
}
const result = sum (10, 20);
// 結果:30
// 【異步】
function getMsg () {
setTimeout(function () {
return { msg: 'Hello Node.js' }
}, 2000);
}
const msg = getMsg ();
// 結果:undefind
區別2:同步API, 異步API的區別(代碼執行順序)
- 同步API從上到下依次執行,前面代碼會阻塞后面代碼的執行
- 異步API不會等待API執行完成后再向下執行代碼
5.2-回調函數
回調函數的基本定義和使用
// getData函數定義
function getData (callback) {}
// getData函數調用
getData (() => {});
使用回調函數獲取異步API執行結果
function getMsg (callback) {
setTimeout(function () {
callback ({ msg: 'Hello Node.js' })
}, 2000);
}
getMsg (function (msg) {
console.log(msg); // 結果:Hello Node.js
});
5.3-異步代碼執行順序分析
console.log('代碼開始執行');
setTimeout(() => {
console.log('2秒后執行的代碼');
}, 2000);
setTimeout(() => {
console.log('"0秒"后執行的代碼');
}, 0);
console.log('代碼結束執行');
5.4-Node.js中的異步API
需求:依次讀取A文件、B文件、C文件
const fs = require("fs");
// 讀取文件1
fs.readFile("./public/a.txt", "utf-8", function (err, data) {
console.log(data);
fs.readFile("./public/b.txt", "utf-8", function (err, data) {
console.log(data);
fs.readFile("./public/c.txt", "utf-8", function (err, data) {
console.log(data);
})
})
});
問題:回調嵌套太多,代碼不易於維護
解決方案:Promise對象
5.5-Promise對象
基本使用
let p1 = new Promise((resolve, reject) => {
// resolve表示執行成功后的函數
// reject表示異常時的函數
fs.readFile("./public/1.txt", "utf-8", function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
p1
// 執行成功后的操作
.then((data) => { console.log(data) })
// 發生異常時的操作
.catch((err) => { console.log(err) });
完成需求
const fs = require("fs");
function p1() {
return new Promise((resolve, reject) => {
fs.readFile("./public/a.txt", "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function p2() {
return new Promise((resolve, reject) => {
fs.readFile("./public/b.txt", "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function p3() {
return new Promise((resolve, reject) => {
fs.readFile("./public/c.txt", "utf-8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
p1()
.then((data) => {
console.log(data);
return p2();
})
.then((data) => {
console.log(data);
return p3();
})
.then((data) => {
console.log(data);
})
5.6-異步函數
異步函數是異步編程語法的終極解決方案,它可以讓我們將異步代碼寫成同步的形式,讓代碼不再有回調函數嵌套,使代碼變得清晰明了。
關鍵字:async
語法:async function fn () {}
或 const fn = async () => {};
- 普通函數定義前加async關鍵字 普通函數變成異步函數
- 異步函數默認返回promise對象
- 在異步函數內部使用return關鍵字進行結果返回 結果會被包裹的promise對象中 return關鍵字代替了resolve方法
- 在異步函數內部使用throw關鍵字拋出程序異常
- 調用異步函數再鏈式調用then方法獲取異步函數執行結果
- 調用異步函數再鏈式調用catch方法獲取異步函數執行的錯誤信息
let fn1 = async () => {
// throw "異常";
return "success";
};
fn1()
.then((data) => { console.log(data) })
.catch((err)=>{console.log(err)})
關鍵字:await
- await關鍵字只能出現在異步函數中
- await promise await后面只能寫promise對象 寫其他類型的API是不不可以的
- await關鍵字可是暫停異步函數向下執行 直到promise返回結果
完成需求
const fs = require("fs");
// 用於改造現有的異步函數api,讓其返回promise對象,從而支持異步函數的語法
const promisify = require("util").promisify;
let readFile = promisify(fs.readFile);
async function run() {
let f1 = await readFile("./public/1.txt", "utf-8");
let f2 = await readFile("./public/2.txt", "utf-8");
let f3 = await readFile("./public/3.txt", "utf-8");
console.log(f1);
console.log(f2);
console.log(f3);
}
run();
第六章:擴展【可選-了解】
6.1-CommonJS
6.1.1-什么是CommonJs
JavaScript 是一個強大面向對象語言,它有很多快速高效的解釋器。然而, JavaScript
標准定義的 API 是為了構建基於瀏覽器的應用程序。並沒有制定一個用於更廣泛的應用程序
的標准庫。CommonJS 規范的提出,主要是為了彌補當前 JavaScript 沒有標准庫的缺陷。它的
終極目標就是:提供一個類似 Python,Ruby 和 Java 語言的標准庫,而不只是讓 JavaScript 停
留在小腳本程序的階段。用 CommonJS API 編寫出的應用,不僅可以利用 JavaScript 開發客
戶端應用,而且還可以編寫以下應用。
-
服務器端 JavaScript 應用程序。(nodejs)
-
命令行工具。
-
桌面圖形界面應用程序。
CommonJS 就是模塊化的標准,nodejs 就是 CommonJS(模塊化)的實現
6.1.2-CommonJS規范
- require('模塊名稱') 導入模塊
- module.exports 或 exports 導出模塊內容
6.2-npm相關
6.2.1-npm介紹
npm 是世界上最大的開放源代碼的生態系統。我們可以通過 npm 下載各種各樣的包,
這些源代碼(包)我們可以在 https://www.npmjs.com 找到。
npm 是隨同 NodeJS 一起安裝的包管理工具,能解決 NodeJS 代碼部署上的很多問題,
常見的使用場景有以下幾種:
-
允許用戶從 NPM 服務器下載別人編寫的第三方包到本地使用。(silly-datetime)
-
允許用戶從 NPM 服務器下載並安裝別人編寫的命令行程序(工具)到本地使用。 (supervisor)
-
允許用戶將自己編寫的包或命令行程序上傳到 NPM 服務器供別人使用
6.2.2-npm命令
npm -v 查看 npm 版本
npm -v
使用 npm 命令安裝模塊
npm install Module Name
如安裝 jq 模塊:npm install jquery
指定版本安裝 npm install jquery@1.8.0
npm uninstall moudleName 卸載模塊
如卸載jq模塊:npm uninstall jquery
npm list 查看當前目錄下已安裝的 node 包
npm list
npm info moduleName 查看模塊系信息
如:npm info jquery 查看 jquery 的版本
6.3-fs模塊常用API
6.3.1-fs.stat 獲取文件信息
const fs = require('fs')
// 檢測一個本地文件
fs.stat('./data/hello.js', (err, stats) => {
if (err) {
// 若讀取本地文件有異常,則打印異常信息
console.log(err.message)
} else {
console.log(stats)
console.log('是否是文件:' + stats.isFile())
console.log('是否是文件夾:' + stats.isDirectory())
// 單位是b-位
console.log('大小:' + Math.ceil(stats.size/1024) + 'kb')
}
})
輸出結果:
是否是文件:true
是否是文件夾:false
大小:85kb
6.3.2-fs.mkdir 創建目錄
// 在data目錄下創建一個logs目錄
fs.mkdir('./data/logs', (error) => {
if (error) {
console.log(error)
} else {
console.log('成功創建目錄:logs')
}
})
6.3.3-fs.writeFile 寫入文件
fs.writeFile('./data/logs/hello.log', '您好 ~ \n', (error) => {
if (error) {
console.log(error)
} else {
console.log('成功寫入文件')
}
})
6.3.4-fs.appendFile 追加文件
fs.appendFile('./data/logs/hello.log', '您好 ~ \n', (error) => {
if (error) {
console.log(error)
} else {
console.log('追加寫入文件')
}
})
6.3.5-fs.readFile讀取文件
fs.readFile('./data/logs/hello.log', 'utf8', (error, data) => {
if (error) {
console.log(error)
} else {
console.log(data)
}
})
6.3.6-讀取目錄
fs.readdir('./data', (error, files) => {
if (error) {
console.log(error)
} else {
console.log(files)
// [ 'hello.js', 'logs', 'test01.js' ]
}
})
6.3.7-重命名
const fs = require('fs')
// fs.rename(oldName,newName,callback)
fs.rename('./data/test01.js', './data/test02.js', (err)=>{
if (err) {
return err.message
} else {
console.log('重命名成功!')
}
})
6.3.8-刪除文件
const fs = require('fs')
fs.unlink('./data/logs/log.js', (err) => {
if (err) return err
console.log('刪除成功!')
})
6.3.9-從文件流中讀取數據
const fs = require('fs')
const fileStream = fs.createReadStream('./data/hello.js')
var count = 0
var str = ''
fileStream.on('data', (chunk) => {
console.log(chunk.toString())
str += chunk
count++
})
fileStream.on('end', (chunk) => {
cosnole.log('讀取結束')
console.log(str)
})
fileStream.on('error', (err) => {
console.log(err)
})
6.3.10-把數據寫入流中
var fs = require("fs");
var data = '我是從數據庫獲取的數據,我要保存起來';
//創建一個可以寫入的流,寫入到文件output.txt中
var writerStream = fs.createWriteStream('output.txt');
//使用utf8編碼寫入數據
writerStream.write(data, 'UTF8');
//標記文件末尾
writerStream.end();
// 處理流事件-- > finish事件 finish-所有數據已被寫入到底層系統時觸發。
writerStream.on('finish', function () {
console.log('寫入完成')
})
writerStream.on('error', function (err) {
console.log(err)
})
6.3.11-管道流
管道提供了一個輸出流到輸入流的機制。通常我們用於從一個流中獲取數據並將數據傳遞到另外一個流中。
如上面的圖片所示,我們把文件比作裝水的桶,而水就是文件里的內容,我們用一根管子(pipe)連接兩個桶使得水從一個桶流入另一個桶,這樣就慢慢的實現了大文件的復制過程。
以下實例我們通過讀取一個文件內容並將內容寫入到另外一個文件中。
var fs = require("fs");
// 創建一個可讀流
var readerStream = fs.createReadStream('./data/hello.js');
// 創建一個可寫流
var writerStream = fs.createWriteStream('output.txt');
// 管道讀寫操作
// 讀取input.txt文件內容,並將內容寫入到output.txt文件中
readerStream.pipe(writerStream);
console.log("程序執行完畢");