路由將 URL 解析到對應的處理程序。
經過上一節的實戰,我們已經有了下面的目錄結構:
koa-blog
├── package.json
└── app.js
我們將使用 Koa 的中間件 koa-router 來處理請求,把請求解析到對應的控制器(controller)上,實現訪問不同地址獲得不同的結果。在這之前,我們先了解使用原生 Koa 實現路由的方式。
Koa原生路由實現
查看url信息
首先,通過 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/
目錄里面。
首先,新建 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>
接著使用 Node.js 的文件系統實現HTML的讀取,具體代碼如下:
/**
* 從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, 'utf8', ( err, data ) => {
if ( err ) {
reject( err )
} else {
resolve( data )
}
})
})
}
我們讀取了創建在 app/view/
目錄的 HTML 之后,通過判斷請求的 url
,選擇對應的頁面進行返回到瀏覽器顯示,完整代碼:
// app.js
const Koa = require('koa');
const fs = require('fs'); // 引入fs
const app = new Koa();
/**
* 從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, 'utf8', ( 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
中間件使用指南: Koa中間件使用之koa-router 。
安裝和使用
執行命令安裝 koa-router:
$ npm install koa-router --save
接著修改之前的路由代碼,使用 koa-router
:
// app.js
const Koa = require('koa');
const fs = require('fs'); // 引入fs
const Router = require('koa-router'); // 引入koa-router
const app = new Koa();
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, 'utf8', ( 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
app.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.js
的代碼顯得非常臃腫,考慮到以后項目會復雜很多,那樣我們的代碼將變得難以維護,因此下面來把路由獨立出來,放在單獨的文件夾管理:
// 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, 'utf8', ( 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')
});
通過上面的代碼,我們已經實現路由獨拿在一個目錄維護,以后我們都將在 app/router/
里面進行路由的創建,重新啟動服務,訪問 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: { // 這里是nunjucks的配置
/*
* autoescape (默認值: true) 控制輸出是否被轉義,查看 Autoescaping
* throwOnUndefined (default: false) 當輸出為 null 或 undefined 會拋出異常
* trimBlocks (default: false) 自動去除 block/tag 后面的換行符
* lstripBlocks (default: false) 自動去除 block/tag 簽名的空格
* watch (默認值: false) 當模板變化時重新加載。使用前請確保已安裝可選依賴 chokidar。
* noCache (default: false) 不使用緩存,每次都重新編譯
* web 瀏覽器模塊的配置項
* useCache (default: false) 是否使用緩存,否則會重新請求下載模板
* async (default: false) 是否使用 ajax 異步下載模板
* express 傳入 express 實例初始化模板設置
* tags: (默認值: see nunjucks syntax) 定義模板語法,查看 Customizing Syntax
*/
trimBlocks: true
}
}));
// 使用中間件 處理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/view/index.html
作為模板文件,因此刪除沒有用到的文件 app/view/home.html
,在 app/view/index.html
中我們接收了傳遞的參數:
<!--app/view/index.html-->
<h1>{{title}} Page</h1>
<a href="/{{link}}">home</a>
分模塊管理路由
為了細化路由,我們將根據業務分開管理路由:
// 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/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')
});
<!--app/view/404.html-->
<h1>404</h1>
<a href="/api/index">index</a>
<!--app/view/index.html-->
<h1>{{title}} Page</h1>
<a href="/api/{{link}}">home</a>
重新啟動服務,訪問 http://localhost:3000/api 和 http://localhost:3000/api/home 即可看到新配置的路由。
完成這一節實戰之后,整個文件目錄如下:
koa-blog
├── package.json
├── app.js
├── app
│ ├── router
│ | ├── homde.js
│ | └── index.js
│ └── view
│ ├── 404.html
│ └── index.html
└── config
└── config.js
下一步,我們來實現log…
本實戰代碼已經創建了GitHub倉庫:https://github.com/YuQian2015/koa-blog ,歡迎關注。