什么是Koa2?
koa-generator
Koa2提供了一个生成器koa-generator,用于Koa2开发者生成一个项目骨架,方便开发者开箱即用。
生成器主要功能包括:
- 约定目录结构
- 集成一些基础的、必要的中间件
- app.js作为入口文件
- bin/www作为启动入口
- 支持静态服务器
- 支持routes路由目录
- 支持views视图目录,默认pug作为模板引
package.json
{
"name": "koa-demo",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "node bin/www", // 代码变动需要重启node进程
"dev": "./node_modules/.bin/nodemon bin/www", // 代码变动,通过nodemon自动重启node进程
"prd": "pm2 start bin/www",// 生产环境 pm2启动
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"debug": "^4.1.1", // 根据Debug环境变量输出调试日志
"koa": "^2.7.0",
"koa-bodyparser": "^4.2.1",// 解析body,主要针对post请求
"koa-convert": "^1.2.0", // 兼容Koa2中间件写法
"koa-json": "^2.0.2", // 对json更好对的支持
"koa-logger": "^3.2.0", // 开发阶段的日志模块
"koa-onerror": "^4.1.0", // 错误处理模块
"koa-router": "^7.4.0", // 路由
"koa-static": "^5.0.0", // HTTP静态服务器
"koa-views": "^6.2.0", // 视图渲染
"pug": "^2.0.3" // 模板引擎
},
"devDependencies": {
"nodemon": "^1.19.1" // 自动重启node进程
}
}
bin/www
bin/www是应用的启动入口。
#!/usr/bin/env node
/**
* Module dependencies.启动文件
*/
var app = require('../app');
var debug = require('debug')('demo:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
// app.set('port', port);
/**
* Create HTTP server.
* http://nodejs.cn/api/http.html#http_class_http_server
* 返回的是一个http.Server实例,继承自: <net.Server>
* http://nodejs.cn/api/net.html#net_class_net_server
* 而<net.Server>继承自: <EventEmitter>
* http://nodejs.cn/api/events.html#events_class_eventemitter
*/
var server = http.createServer(app.callback());
/**
* Listen on provided port, on all network interfaces.
*/
// 启动 HTTP 服务器用于监听连接
server.listen(port);
// on方法来自于<EventEmitter>
// error、listening事件来自于<net.Server>
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
// 关于失败的系统调用描述:http://nodejs.cn/api/errors.html#errors_error_syscall
// https://man7.org/linux/man-pages/man2/syscalls.2.html
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
// 拒绝访问)
console.error(bind + ' requires elevated privileges');
// http://nodejs.cn/api/process.html#process_process_exit_code
// 调用 process.exit() 将强制进程尽快退出,即使还有尚未完全完成的异步操作,包括对 process.stdout 和 process.stderr 的 I/O 操作。
process.exit(1);
break;
case 'EADDRINUSE':
// 地址已经被使用
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
// http://nodejs.cn/api/net.html#net_server_address
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const index = require('./routes/index')
const users = require('./routes/users')
// 错误处理
onerror(app)
// 挂载中间件
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'pug'
}))
// 日志中间件
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// 路由
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
Koa2推荐的中间件写法是:
app.use(async(ctx, next) => {
// ...
})
路由
路由本身也是一种中间件,不过我们由于职责拆分,将其独立到routes目录下。
const router = require('koa-router')()
// 视图渲染
router.get('/', async (ctx, next) => {
await ctx.render('index', {
title: 'Hello Koa 2!'
})
})
// ctx.body是一个字符串
router.get('/string', async (ctx, next) => {
ctx.body = 'koa2 string'
})
// ctx.body是一个json
router.get('/json', async (ctx, next) => {
ctx.body = {
title: 'koa2 json'
}
})
module.exports = router
ctx.render方法不是Koa2自身的,而是koa-views中间件绑定到ctx上下文的。可见很多时候,我们可以通过中间件在ctx挂载一些业务方法,比如统一处理接口响应的json约定格式方法。
静态服务器
// app.js
app.use(require('koa-static')(__dirname + '/public'))
静态服务器主要存放一些静态资源,这里是通过koa-static中间件实现,它是基于koa-send模块的封装。
koa-static推荐与koa-router结合使用,这样就不需要每个请求都经过koa-static。
// koa-router支持多个中间件
router.get('/public', async(ctx,netx) => {
}, staticServer(resolve('./public)))
视图
关于模板引擎的配置
app.use(views(__dirname + '/views', {
extension: 'pug'
}))
单纯的html不够强大,不能提供很好的复用。而模板引擎就是基于复用思想的一种定义模板的语法,使用时候它把数据与模板一起编译,生成html。
// 视图渲染
router.get('/', async (ctx, next) => {
// ctx.render方法第一个参数是模板相对路径,相对于views目录下
// 第二个参数就是传入到模板的数据
await ctx.render('index', {
title: 'Hello Koa 2!'
})
})
模板引擎工作原理:
- ctx.render()方法指定需要渲染的模板路径
- 根据路径读取该文件模板
- 通过模板引擎编译器把数据与模板内容一起编译为HTML字符串
- 将响应头的Content-Type设置text/html; charset=utf-8
- 通过http模块底层的res.write以及res.end方法把html字符串写到浏览器
ps: 推荐我的公众号:xyz编程日记