08【Express框架】詳細版 - Express框架簡介,中介軟體, Express請求處理,express-art-template模板引擎.
Express框架
目標
- 能夠使用Express建立web伺服器
- 能夠使用Express處理請求引數
- 能夠使用Express處理靜態資源
- 能夠使用中介軟體處理請求
- 能夠在Express中整合art-template模板引擎
- Express框架簡介及初體驗
- Express框架請求處理
- express-art-template模板引擎
- Express中介軟體
1. Express框架簡介及初體驗
1.1 Express框架是什麼?
1)Express是一個基於Node平臺的web應用開發框架,它提供了一系列的強大特性,幫助你建立各種Web網站應用。使用原生JS程式碼寫起來比較複雜,比較底層的;比如實現路由功能,需要對請求地址進行解析,還要進行各種判斷,程式碼亂,不易閱讀;再比如,實現靜態資源訪問功能
2)這個是nodejs的第三方模組-使用 npm install express 命令進行下載。企業中建立web應用的標準
1.2 Express框架特性
- 提供了方便簡潔的路由定義方式:router第三方模組 ,其實就是從express框架中抽取出來的
- 對獲取HTTP請求引數進行了簡化處理:不用再轉換格式,直接拿到物件型別
- 對模板引擎 支援程度高,方便渲染動態HTML頁面;
- 提供了中介軟體(對請求的攔截)機制有效控制HTTP請求
- 擁有大量第三方中介軟體 對功能進行擴充套件:非常少的程式碼,做同樣的事情
1.3 原生Node.js與Express框架對比 之 路由
app.on('request', (req, res) => { // 獲取客戶端的請求路徑 let { pathname } = url.parse(req.url); // 對請求路徑進行判斷 不同的路徑地址響應不同的內容 if (pathname == '/' || pathname == 'index') { res.end('歡迎來到首頁 } else if (pathname == '/list') { res.end('歡迎來到列表頁'); } else if (pathname == '/about') { res.end('歡迎來到關於我們頁面') } else { res.end('抱歉, 您訪問的頁面出遊了'); } }); |
// 當客戶端以get方式訪問/時 app.get('/', (req, res) => { // 對客戶端做出響應 res.send('Hello Express'); }); // 當客戶端以post方式訪問/add路由時 app.post('/add', (req, res) => { res.send('使用post方式請求了/add路由'); }); |
1.4 原生Node.js與Express框架對比 之 獲取請求引數
app.on('request', (req, res) => { // 獲取GET引數 let {query} = url.parse(req.url, true); // 獲取POST引數 let postData = ''; req.on('data', (chunk) => { postData += chunk; }); req.on('end', () => { console.log(querystring.parse(postData) })); }); |
app.get('/', (req, res) => { // 獲取GET引數req.query console.log(req.query); }); app.post('/', (req, res) => { // 獲取POST引數 req.body console.log(req.body); }) |
1.5 Express初體驗
使用Express框架 建立web伺服器及其簡單,呼叫express模組 返回的函式即可。
命令列下載: npm install express //1, 引入Express框架,返回值是一個方法,通過呼叫這個方法,就可以建立網站伺服器,就不用HTTP模組和呼叫createserver()方法 const express = require('express'); //2, 使用框架建立web伺服器 + 監聽埠 = 向外提供服務 const app = express(); // 4, 當客戶端以get方式訪問/路由時, 伺服器要建立路由來響應客戶端的請求;如何建立路由,和第三方模組 router是一樣的 app.get(‘預設訪問地址/’,請求處理函式,兩個引數分別為 請求物件和 響應物件) 用來接受 get 請求 app.get('/', (req, res) => { // 不再是res.end(), 對客戶端做出響應 send()方法會根據內容的型別自動設定請求頭 // send() 內部回檢測響應內容的型別;會自動設定HTTP狀態碼;會幫我們自動設定響應的內容型別以及 編碼 res.send('Hello Express'); // <h2>Hello Express</h2> {say: 'hello'} }); //在定義一個路由,當訪問 / list 的時候,響應一個其他內容;send 內部 還可以傳遞Json 物件 app.get('/list', (req, res) => { res.send({name: '張三', age: 20}) }) //3, 程式監聽3000埠 app.listen(3000); |
2. 中介軟體
2.1 什麼是中介軟體
1) 中介軟體就是 一堆方法,可以 接收客戶端發來的請求、可以對請求做出響應,也可以將請求繼續交給下一個中介軟體繼續處理。專門接受請求處理請求的
下圖:中介軟體處理請求的過程:
圖兩邊是當客戶端瀏覽器, 向中間的伺服器傳送請求
當瀏覽器傳送請求,伺服器可以使用中介軟體 接受這個請求進行處理,直接對客戶端做出響應,或者交給下一個中介軟體繼續處理,有下一個 中介軟體對客戶端瀏覽器做出相應;
中介軟體好處:可以對複雜的請求處理邏輯,進行分開處理,也可以再請求到到指定路由之前做一些驗證:比如檢視使用者是不是登入,如果登入,在向下繼續執行
2) 中介軟體主要由兩部分構成,中介軟體方法以及請求處理函式。 中介軟體方法 由Express框架提供,負責攔截請求,請求處理函式 由開發人員提供,負責處理請求。
app.get('請求路徑', '處理函式') // 接收並處理get請求 app.post('請求路徑', '處理函式') // 接收並處理post請求 |
3) 可以針對同一個請求設定 多箇中間件,對同一個請求進行多次處理。
預設情況下,請求從上到下依次匹配中介軟體,一旦匹配成功,終止匹配。
可以呼叫第三個引數next()方法將請求的控制權交給下一個中介軟體,直到遇到結束請求的中介軟體
app.get('/request', (req, res, next) => { req.name = "zhangsan"; next(); }); | app.get('/request', (req, res) => { res.send(req.name); }); |
2.2 app.use中介軟體用法
- app.use 匹配所有的請求方式,可以直接傳入請求處理函式,代表接收所有的請求,只要客戶端發來請求,就可以匹配到當前中介軟體
中介軟體是有順序的,所以,中介軟體必須定義在其他前邊;否則其他中介軟體匹配到了這個請求,有沒有將權力交給下一個中介軟體,也是匹配不到這個中介軟體的
app.use((req, res, next) => { console.log(req.url); next(); });
- app.use 第一個引數 也可以傳入請求地址,代表不論什麼請求方式,只要是這個請求地址就接收這個請求。
app.use('/admin', (req, res, next) => { console.log(req.url); next(); });
2.3 中介軟體應用
1. 路由保護,客戶端在訪問 需要登入的頁面時,可以先使用中介軟體判斷使用者登入狀態,使用者如果未登入,則攔截請求,直接響應, 禁止使用者進入需要登入的頁面。
// 定義一個路由:要訪問必須要先登入可以的 |
2. 網站維護公告,在所有路由的最上面定義接收所有請求的中介軟體,直接為客戶端做出響應,網站正在維護中。
// 網站公告,比如在凌晨 6:00 -12:00 要維護,不想要使用者訪問這個網站,要定義在所有的路由的前邊,沒有呼叫 next(), 請求到這裡就截止了 app.use((req, res, next) => { res.send('當前網站正在維護,請在其他時間段訪問...') }) |
3. 自定義404頁面,使用者訪問路徑不存在時,同時使用者訪問的頁面不存在,當所有使用者訪問的上邊所有的路由不存在,才會響應給使用者,所以定義在所有路由最後邊,不會呼叫next();status(404)更改狀態碼,為客戶端響應404狀態碼以及提示資訊
2.4 錯誤處理中介軟體
在程式執行的過程中,不可避免的會出現一些無法預料的錯誤,比如檔案讀取失敗,資料庫連線失敗,錯誤處理中介軟體是一個集中處理錯誤的地方。
想要出錯以後,還能繼續執行,需要捕獲錯誤,加入錯誤處理;程式錯誤分為兩種:
- 應用邏輯錯誤:=bug 開發階段解決
- 讀取硬碟檔案/資料庫連線錯誤:無法開發階段預料到,在執行過程中,需要被捕獲和妥善處理;
如何處理錯誤呢?在每一個會出錯的地方進行判斷,但是程式碼太多!所以提供了錯誤處理中介軟體;
只能捕獲到 同步程式碼錯誤!!!,非同步需要手動觸發,呼叫next(0方法 當非同步程式出現錯誤時,呼叫next()方法,並且將錯誤資訊通過引數的形式傳遞給next()方法,即可觸發錯誤處理中介軟體
有四個引數,發生錯誤,自動執行錯誤處理中介軟體 app.use((err, req, res, next) => { res.status(500).send('伺服器發生未知錯誤'); }) |
如果檔案讀取錯誤,系統會把錯誤資訊通過引數傳給我們,我們對錯誤物件進行判斷,如果它真的時錯誤物件,不是null,就呼叫next()方法,傳給他,他就會觸發錯誤處理中介軟體了 app.get("/", (req, res, next) => { fs.readFile("/file-does-not-exist", (err, data) => { if (err) { next(err); } }); }); |
//05,js 錯誤處理中介軟體 // 引入express框架 + 檔案讀取模組 const express = require('express'); const fs = require('fs'); // 建立網站伺服器 const app = express(); // 普通的路由中介軟體 app.get('/index', (req, res, next) => { // throw new Error('程式發生了未知錯誤') 丟擲錯誤 ,不報錯往下執行 fs.readFile('./01.js', 'utf8', (err, result) => { if (err != null) { next(err) // 傳引數,代表觸發中介軟體,不傳引數,代表控制權交給下一個 }else { res.send(result) } }) // res.send('程式正常執行') }) // 錯誤處理中間 app.use((err, req, res, next) => { res.status(500).send(err.message); }) // 監聽埠 app.listen(3000); console.log('網站伺服器啟動成功'); |
2.5 捕獲錯誤
在node.js中,非同步API的錯誤資訊都是通過 回撥函式 獲取的,支援Promise物件的非同步API 發生錯誤可以通過catch方法捕獲。 非同步函式執行如果發生錯誤要如何捕獲錯誤呢?
try catch 可以捕獲 非同步函式以及其他同步程式碼 在執行過程中發生的錯誤,但是 不能捕獲其他型別的API發生的錯誤
app.get("/", async (req, res, next) => { // 如果程式沒有錯誤,跳到try,catch外;如果程式有錯誤,會執行catch裡邊的程式碼,裡邊的引數就是錯誤資訊,可以呼叫next方法,手動觸發錯誤處理中介軟體;try()裡邊的程式碼,是從資料塊中查詢資料,如果查詢失敗,就跳轉catch,執行並將錯誤資訊傳給錯誤處理中介軟體; try { await User.find({name: '張三'}) }catch(ex) { next(ex); // 呼叫next() 觸發錯誤處理中介軟體 } }); |
命令列已經不報錯了,程式就可以繼續執行;增加了我們程式的健壯性 |
3. Express請求處理
3.1 構建模組化路由
雖然已經可以通過 app.get(), app.post()方法建立路由了,但是在一般情況下,路由的數量是非常多的,如果將所有的放在同一檔案中,非常可怕,所以express提供了模組化,進行分類,不同的型別路由放在不同的模組中,方便管理。
例如:部落格網站:使用者看的文章列表,詳情頁面;管理員看的文章釋出,管理頁面等。設定不同的路由進行分別管理
const express = require('express') // 引入框架,返回express方法,直接呼叫或者使用他下邊的其他方法;比如 express.Router用來建立路由 const home = express.Router();// 建立路由物件 app.use('/home', home); // 將路由和請求路徑進行匹配;當客戶端訪問什麼請求路徑時/home',才能使用當前路由來處理,用app.use()方法來匹配 home;程式碼中並沒有請求處理函式,請求來了以後,在哪裡處理呢?具體請求處理再二級路由中完成 home.get('/index', () => {// 在home路由下的get()方法繼續建立路由,訪問:/home/index 二級路由 res.send('歡迎來到部落格展示頁面'); // /home/index }); |
3.2 構建模組化路由
// home.js
| // admin,js
|
// app.js |
3.3 GET引數的獲取
Express框架中使用 req.query 即可獲取 GET引數, query 屬性下存的就是 get請求引數,不在需要引入url 模組,通過對請求地址進行解析來獲取get 請求引數了;框架內部會將GET引數 轉換為 物件並返回。
// 接收位址列中問號後面的引數,客戶端訪問時加了請求引數 name=zhangsan&age=30 // 例如: http://localhost:3000/?name=zhangsan&age=30 app.get('/', (req, res) => { console.log(req.query); // 直接通過 req.query 就可以拿到?號後的請求引數了,並且已經解析成物件型別{"name": "zhangsan", "age": "30"} }); |
3.4 POST引數的獲取
Express中接收 post請求引數 需要藉助第三方包 body-parser。Npm i body-parser下載
const bodyParser = require('body-parser'); // 引入body-parser模組 app.use(bodyParser.urlencoded({ extended: false }));// 配置body-parser模組;使用app.use()這個中介軟體攔截所有請求,呼叫'body-parser'模組下邊的 urlencoded() 方法,對請求進行處理,方法內部會檢測當前請求中是不是包含請求引數,如果包含,就接受並轉換為物件型別;然後在為req這個請求新增一個屬性,屬性的名字叫body;並且將引數作為值賦值給 req.body屬性,最後呼叫next()將請求控制權交給下一個中介軟體 app.post('/add', (req, res) => { // 接收請求 console.log(req.body); // 接收post請求引數 }) |
10.js 如何獲取post請求引數
|
如何傳送 post 請求,通過表單就可以:post.html 點選提交後,就提交到 /add這個路由地址去了: |
11.js app.use方法 |
3.5 Express 路由引數
傳遞和接受 get 請求引數,還有另一種方式; 路由引數;可以讓請求地址看起來美觀,路由程式碼容易閱讀;更容易看出傳了那些引數;:id 是一個佔位符,請求當前路由,要傳遞一個id 作為引數,不是實際的引數
app.get('/find/:id', (req, res) => { console.log(req.params); // {id: 123} req.params 獲取引數 }); localhost:3000/find/123 // 請求引數/?id = 123 |
3.6 靜態資源的處理
通過Express內建的 express.static可以方便地託管靜態檔案,例如img、CSS、JavaScript 檔案等。
app.use(express.static('public')); express.static(‘引數:靜態資源存放的目錄’);呼叫傳給app.use()中介軟體,攔截所有請求,將請求交給express.static()這個方法處理,並且將靜態資源目錄告訴express.static()方法;方法內部判斷客戶端發來的請求,如果是靜態資源請求,直接響應給客戶端,終止請求;如果不是再方法內部呼叫next()將請求控制權交給下一個中介軟體;開啟靜態資源訪問功能後,就可以,public 目錄下面的檔案就可以通過以下方式訪問了。
- http://localhost:3000/images/kitten.jpg
- http://localhost:3000/css/style.css
- http://localhost:3000/js/app.js
- http://localhost:3000/images/bg.png
- http://localhost:3000/hello.html
訪問:http://localhost:3000/static/images/1.jpg
4. express-art-template模板引擎
4.1 模板引擎
- 為了使 art-template模板引擎 能夠更好的和 Express框架配合,模板引擎官方在原art-template模板引擎的基礎上封裝了express-art-template。
- 使用 npm install art-template express-art-template 命令進行安裝。
// 告訴express 框架,使用的模板引擎是什麼? 當渲染字尾為art的模板時 使用express-art-template; app.engine('art', require('express-art-template')); // 設定模板存放目錄 app.set對express框架進行配置 app.set('views', path.join(__dirname, 'views')); // 渲染模板時不寫字尾 預設拼接art字尾 app.set('view engine', 'art'); app.get('/', (req, res) => { // 渲染模板 res.render('index'); }); |
4.2 app.locals 物件
不同的頁面中,總會有公共資料,程式碼中如何查詢公共資料呢?
- 在不同頁面路由中 都去查詢這個相同的資料,render 將資料填充到模板中,麻煩; 只寫一次,讓所有能用到都而已來拿到這個數據呢?
- 將變數設定到 app.locals 物件下面,這個資料在所有的模板中都可以獲取到。!!!
app.locals.users = [{ name: '張三', age: 20 },{ name: '李四', age: 20 }] |