從零開始的Koa實戰(2) 路由
路由將 URL 解析到對應的處理程式。這裡我們將使用 Koa 的中介軟體koa-router 來處理請求,將請求解析到對應的控制器(controller)上,實現訪問不同地址獲得不同的結果。不過在此之前,我們先了解使用原生koa實現路由的方式。
Koa原生路由實現
檢視路由資訊
首先,通過ctx.request.url
獲取到當前請求的地址:
// app.js const Koa = require('koa'); const app = new Koa(); // 響應 app.use(ctx => { const {url} = ctx.request; ctx.response.body = url; }); app.listen(3000);
啟動服務之後我們在瀏覽器訪問http://localhost:3000 的任何一個地址,可在頁面中看到返回的 url 資訊。
瞭解內容協商
Koa使用accepts
作為內容協商(content negotiation),accept
能告訴伺服器瀏覽器接受什麼樣的資料型別,預設的返回型別是text/plain
,若要返回其他型別的內容則用ctx.request.accepts
在請求頭部附帶資訊,然後用ctx.response.type
指定返回型別,示例:
const main = ctx => { if (ctx.request.accepts('xml')) { ctx.response.type = 'xml'; ctx.response.body = '<data>Hello World</data>'; } else if (ctx.request.accepts('json')) { ctx.response.type = 'json'; ctx.response.body = { data: 'Hello World' }; } else if (ctx.request.accepts('html')) { ctx.response.type = 'html'; ctx.response.body = '<p>Hello World</p>'; } else { ctx.response.type = 'text'; ctx.response.body = 'Hello World'; } };
可以使用acceptsEncodings
設定接收返回的編碼,acceptsCharsets
設定接收返回的字符集,acceptsLanguages
設定接收返回的語言。
通過路由控制頁面訪問
接下來我們通過對路由地址進行判斷,並取到對應的HTML頁面返回到瀏覽器顯示。
首先,新建 HTML 檔案如下:
<!--app/view/index.html--> <h1>Index Page</h1> <a href="home">home</a> <!--app/view/home.html--> <h1>Home Page</h1> <a href="index">index</a> <!--app/view/404.html--> <h1>404 訪問的頁面不存在</h1> <a href="home">home</a>
接著,我們需要讀取 HTML 檔案,約定 HTML 都在app/view/
目錄,然後從這個目錄讀取頁面,並且返回到瀏覽器顯示:
// app.js const Koa = require('koa'); const app = new Koa(); const fs = require('fs'); // 引入fs /** * 從app/view目錄讀取HTML檔案 * @param {string} page 路由指向的頁面 * @returns {Promise<any>} */ function readPage( page ) { return new Promise(( resolve, reject ) => { const viewUrl = `./app/view/${page}`; fs.readFile(viewUrl, "binary", ( err, data ) => { if ( err ) { reject( err ) } else { resolve( data ) } }) }) } // 路由 app.use(async ctx => { const {url} = ctx.request; let page; switch ( url ) { case '/': page = 'index.html'; break; case '/index': page = 'index.html'; break; case '/home': page = 'home.html'; break; default: page = '404.html'; break } ctx.response.type = 'html'; // 這裡設定返回的型別是html ctx.response.body = await readPage(page); }); app.listen(3000, () => { console.log('App started on http://localhost:3000') });
然後我們啟動服務,在瀏覽器訪問http://localhost:3000/index ,我們可以看到頁面已經顯示出來,點選裡面的連線,就能夠切換不同的頁面。
使用koa-router
從上面的實戰可以看到,要針對不同的訪問返回不同的內容,我們需要先獲取請求的 url,以及請求的型別,再進行相應的處理,最後返回結果,考慮到使用原生路由處理請求會很繁瑣,我們使用koa-router 中介軟體來管理專案路由。
安裝和使用
執行命令安裝 koa-router:
$ npm install koa-router --save
接著修改之前的路由程式碼,使用koa-router
:
// app.js const Koa = require('koa'); const app = new Koa(); const fs = require('fs'); // 引入fs // 引入koa-router const Router = require('koa-router'); const router = new Router(); /** * 從app/view目錄讀取HTML檔案 * @param {string} page 路由指向的頁面 * @returns {Promise<any>} */ function readPage( page ) { return new Promise(( resolve, reject ) => { const viewUrl = `./app/view/${page}`; fs.readFile(viewUrl, "binary", ( err, data ) => { if ( err ) { reject( err ) } else { resolve( data ) } }) }) } // 原生路由 // app.use(async ctx => { //const {url} = ctx.request; //let page; //switch ( url ) { //case '/': //page = 'index.html'; //break; //case '/index': //page = 'index.html'; //break; //case '/home': //page = 'home.html'; //break; //default: //page = '404.html'; //break //} //ctx.response.type = 'html'; // 這裡設定返回的型別是html //ctx.response.body = await readPage(page); // }); // koa-router const index = async (ctx, next) => { ctx.response.type = 'html'; ctx.response.body = await readPage('./index.html'); }; const home = async (ctx, next) => { ctx.response.type = 'html'; ctx.response.body = await readPage('./home.html'); }; router.get('/', index); router.get('/index', index); router.get('/home', home); // 使用中介軟體 處理404 router.use(async (ctx, next) => { await next(); // 呼叫next執行下一個中介軟體 if(ctx.status === 404) { ctx.response.type = 'html'; ctx.response.body = await readPage('./404.html'); } }); // 使用koa-router中介軟體 app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log('App started on http://localhost:3000') });
在以上程式碼中,我們使用ctx.response.type
來設定響應的型別,響應內容為 HTML 標籤,並且通過新增路由中介軟體,執行npm start
啟動服務即可預覽。
單獨管理路由
考慮到以後專案會複雜很多,我們把路由獨立出來,新增檔案管理路由:
// app/router/index.js const fs = require('fs'); // 引入fs const Router = require('koa-router'); const router = new Router(); /** * 從app/view目錄讀取HTML檔案 * @param {string} page 路由指向的頁面 * @returns {Promise<any>} */ function readPage( page ) { return new Promise(( resolve, reject ) => { const viewUrl = `./app/view/${page}`; fs.readFile(viewUrl, "binary", ( err, data ) => { if ( err ) { reject( err ) } else { resolve( data ) } }) }) } // koa-router const index = async (ctx, next) => { ctx.response.type = 'html'; ctx.response.body = await readPage('./index.html'); }; const home = async (ctx, next) => { ctx.response.type = 'html'; ctx.response.body = await readPage('./home.html'); }; router.get('/', index); router.get('/index', index); router.get('/home', home); module.exports = router; // app.js const Koa = require('koa'); const app = new Koa(); // 引入路由檔案 const router = require('./app/router'); // 使用中介軟體 處理404 app.use(async (ctx, next) => { await next(); // 呼叫next執行下一個中介軟體 if(ctx.status === 404) { ctx.response.type = 'html'; ctx.response.body = '404'; // 移除了讀取頁面的方法 } }); // 使用koa-router中介軟體 app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log('App started on http://localhost:3000') });
重新啟動服務,訪問http://localhost:3000/ 看看是否生效。
使用模板引擎
為了便於讀取模板和渲染頁面,我們將使用中介軟體koa-nunjucks-2 來作為模板引擎。
首先安裝 koa-nunjucks-2 :
$ npm i koa-nunjucks-2 --save
在使用路由之前應用 koa-nunjucks-2:
// app.js const Koa = require('koa'); const app = new Koa(); // 引入模板引擎 const koaNunjucks = require('koa-nunjucks-2'); const path = require('path'); // 引入路由檔案 const router = require('./app/router'); // 使用模板引擎 app.use(koaNunjucks({ ext: 'html', path: path.join(__dirname, 'app/view'), nunjucksConfig: { trimBlocks: true // 開啟轉義 防止Xss } })); // 使用中介軟體 處理404 app.use(async (ctx, next) => { await next(); // 呼叫next執行下一個中介軟體 if(ctx.status === 404) { await ctx.render('404'); } }); // 使用koa-router中介軟體 app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log('App started on http://localhost:3000') }); // app/router/index.js const Router = require('koa-router'); const router = new Router(); // koa-router const index = async (ctx, next) => { await ctx.render('index', {title: 'Index', link: 'home'}); }; const home = async (ctx, next) => { await ctx.render('index', {title: 'Home', link: 'index'}); }; router.get('/', index); router.get('/index', index); router.get('/home', home); module.exports = router;
分模組管理路由
為了細化路由,我們將根據業務分開管理路由:
// app/router/home.js const router = require('koa-router')(); router.get('/', async (ctx, next) => { await ctx.render('index', {title: 'Home', link: 'index'}); }); module.exports = router; // app/router/index.js const Router = require('koa-router'); const router = new Router(); const home = require('./home'); const index = async (ctx, next) => { await ctx.render('index', {title: 'Index', link: 'home'}); }; router.get('/', index); router.get('/index', index); router.use('/home', home.routes(), home.allowedMethods()); // 設定home的路由 module.exports = router;
我們將首頁相關的業務放到了app/router/home.js
檔案中進行管理,並且在app/router/index.js
中引入了,為home
分配了一個路徑/home
,以後就通過這個路由增加首頁相關的功能,重啟服務,訪問http://localhost:3000/home
即可看到配置的路由。
為路由增加字首
// config.js const CONFIG = { "API_PREFIX": "/api" // 配置了路由字首 }; module.exports = CONFIG; // app/router/index.js const config = require('../../config/config'); const Router = require('koa-router'); const router = new Router(); router.prefix(config.API_PREFIX); // 設定路由字首 const home = require('./home'); const index = async (ctx, next) => { await ctx.render('index', {title: 'Index', link: 'home'}); }; router.get('/', index); router.get('/index', index); router.use('/home', home.routes(), home.allowedMethods()); // 設定home的路由 module.exports = router; // app.js const Koa = require('koa'); const app = new Koa(); //... app.listen(3000, () => { console.log('App started on http://localhost:3000/api') });
重新啟動服務,訪問http://localhost:3000/api 和http://localhost:3000/api/home 即可看到新配置的路由。
刪除沒有用到的檔案app/view/home.html
之後,整個檔案目錄如下:
koa-blog ├── package.json ├── app.js ├── app │├── router │|├── homde.js │|└── index.js │└── view │├── 404.html │└── index.html └── config └── config.js