koa是Express的下一代基于Node.js的web框架,由 Express 原班人马打造,利用 async 函数,Koa丢弃了回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件,给人一种干净利落的感觉,体积小、编程方式干净。 Koa官网链接:koa.bootcss.com/
历史
Express
Express是第一代最流行的web框架,它对Node.js的http进行了封装,用起来如下:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('server启动成功');
});
虽然Express的API很简单,但是它是基于ES5的语法,要实现异步代码,只有一个方法:回调。如果异步嵌套层次过多,代码写起来就非常难看:
app.get('/test', function (req, res) {
fs.readFile('/file1', function (err, data) {
if (err) {
res.status(500).send('read file1 error');
}
fs.readFile('/file2', function (err, data) {
if (err) {
res.status(500).send('read file2 error');
}
res.type('text/plain');
res.send(data);
});
});
});
虽然可以用async这样的库来组织异步代码,但是用回调写异步实在是太痛苦了!
koa1
随着新版Node.js开始支持ES6,Express的团队又基于ES6的generator重新编写了下一代web框架koa。和Express相比,koa 1.0使用generator实现异步,代码看起来像同步的:
var koa = require('koa');
var app = koa();
app.use('/test', function *() {
yield doReadFile1();
var data = yield doReadFile2();
this.body = data;
});
app.listen(3000, function () {
console.log('server启动成功');
});
koa2
koa团队并没有止步于koa 1.0,他们非常超前地基于ES7开发了koa2,和koa 1相比,koa2完全使用Promise并配合async来实现异步。
app.use(async (ctx, next) => {
await next();
var data = await doReadFile();
ctx.response.type = 'text/plain';
ctx.response.body = data;
});
Koa开发环境搭建
安装Node.js
因为node.js v7.6.0开始完全支持async/await,不需要加flag,所以node.js环境都要7.6.0以上。 node.js环境版本v7.6以上,npm 版本3.x以上。
安装Koa2
# 初始化package.json
npm init
# 安装koa2
npm install koa
如果安装错误,一般都是网速问题,可以使用cnpm来进行安装。
hello Koa2
// 在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa')
// 创建一个Koa对象表示web app本身:
const app = new Koa()
// 对于任何请求,app将调用该异步函数处理请求:
app.use( async ( ctx ) => {
ctx.body = 'hello koa2'
})
app.listen(3000)
console.log('server启动成功')
在命令行中输入:
node index.js
然后在浏览器中输入:http://127.0.0.1:3000 就可以看到结果了。
async/await使用
快速理解
先复制以下这段代码,在粘贴在chrome的控制台console中,按回车键执行
function getSyncTime() {
return new Promise((resolve, reject) => {
try {
let startTime = new Date().getTime()
setTimeout(() => {
let endTime = new Date().getTime()
let data = endTime - startTime
resolve( data )
}, 500)
} catch ( err ) {
reject( err )
}
})
}
async function getSyncData() {
let time = await getSyncTime()
let data = `endTime - startTime = ${time}`
return data
}
async function getData() {
let data = await getSyncData()
console.log( data )
}
getData()
结果:
从上述例子可以看出 async/await 的特点:
- 可以让异步逻辑用同步写法实现
- 最底层的await返回需要是Promise对象
- 可以通过多层 async function 的同步写法代替传统的callback嵌套
请求数据获取
GET请求数据获取
koa2中GET请求通过request接收,接收的方式有两种:query和querystring。
- query:返回的是格式化好的参数对象。
- querystring:返回的是请求字符串。 由于ctx对request的API有直接引用的方式,所以也可以直接在ctx中得到GET请求。ctx中也分为query和querystring。
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
let url = ctx.url
// 从上下文的request对象中获取
let request = ctx.request
let req_query = request.query
let req_querystring = request.querystring
// 从上下文中直接获取
let ctx_query = ctx.query
let ctx_querystring = ctx.querystring
ctx.body = {
url,
req_query,
req_querystring,
ctx_query,
ctx_querystring
}
})
app.listen(3000, () => {
console.log('server启动成功')
})
POST请求参数获取
对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req来获取。
获取Post请求的步骤:
- 解析上下文ctx中的原生nodex.js对象req。
- 将POST表单数据解析成query string-字符串.(例如:user=jspang&age=18)
- 将字符串转换成JSON格式。
注意:
ctx.request是context经过封装的请求对象,
ctx.req是context提供的node.js原生HTTP请求对象,可以得到更多的内容,适合我们深度编程;
ctx.response是context经过封装的响应对象,
ctx.res是context提供的node.js原生HTTP响应对象。 案例代码演示:
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
if ( ctx.url === '/' && ctx.method === 'GET' ) {
// 当GET请求时候返回表单页面
let html = `
<h1>koa2 request post demo</h1>
<form method="POST" action="/">
<p>userName</p>
<input name="userName" /><br/>
<p>nickName</p>
<input name="nickName" /><br/>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if ( ctx.url === '/' && ctx.method === 'POST' ) {
// 当POST请求的时候,解析POST表单里的数据,并显示出来
let postData = await parsePostData( ctx )
ctx.body = postData
} else {
// 其他请求显示404
ctx.body = '<h1>404!</h1>'
}
})
// 解析上下文里node原生请求的POST参数
function parsePostData( ctx ) {
return new Promise((resolve, reject) => {
try {
let postdata = "";
ctx.req.addListener('data', (data) => {
postdata += data
})
ctx.req.addListener("end",function(){
let parseData = parseQueryStr( postdata )
resolve( parseData )
})
} catch ( err ) {
reject(err)
}
})
}
// 将POST请求参数字符串解析成JSON
function parseQueryStr( queryStr ) {
let queryData = {}
let queryStrList = queryStr.split('&')
console.log( queryStrList )
for ( let [ index, queryStr ] of queryStrList.entries() ) {
let itemList = queryStr.split('=')
queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
}
return queryData
}
app.listen(3000, () => {
console.log('server启动成功')
})
koa-bodyparser中间件
对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
安装中间件
// 使用npm进行安装,需要注意的是我们这里要用–save,因为它在生产环境中需要使用。
npm install --save koa-bodyparser@3
引入使用
// 安装完成后,需要在代码中引入并使用。我们在代码顶部用require进行引入。
const bodyParser = require('koa-bodyparser');
// 由于middleware的顺序很重要,这个koa-bodyparser必须在router之前被注册到app对象上。
app.use(bodyParser());
在代码中使用后,直接可以用ctx.request.body进行获取POST请求参数,中间件自动给我们作了解析。
现在我们再优化刚刚案例代码:
const Koa = require('koa')
const app = new Koa()
const bodyParser = require('koa-bodyparser')
// 使用ctx.body解析中间件
app.use(bodyParser())
app.use( async ( ctx ) => {
if ( ctx.url === '/' && ctx.method === 'GET' ) {
// 当GET请求时候返回表单页面
let html = `
<h1>koa2 request post demo</h1>
<form method="POST" action="/">
<p>userName</p>
<input name="userName" /><br/>
<p>nickName</p>
<input name="nickName" /><br/>
<button type="submit">submit</button>
</form>
`
ctx.body = html
} else if ( ctx.url === '/' && ctx.method === 'POST' ) {
// 当POST请求的时候,中间件koa-bodyparser解析POST表单里的数据,并显示出来
let postData = ctx.request.body
ctx.body = postData
} else {
// 其他请求显示404
ctx.body = '<h1>404!</h1>'
}
})
app.listen(3000, () => {
console.log('server启动成功')
})
路由
Koa2原生路由实现
简单的例子
要想实现原生路由,需要得到地址栏输入的路径,然后根据路径的不同进行跳转。用ctx.request.url就可以实现。我们通过一个简单的例子了解一下如何获得访问路径。
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
let url = ctx.request.url
ctx.body = url
})
app.listen(3000)
这时候访问http://127.0.0.1:3000/hello/12 页面会输出/hello/12。也就是说上下文的请求request对象中url之就是当前访问的路径名称,可以根据ctx.request.url 通过一定的判断或者正则匹配就可以定制出所需要的路由。
定制化的路由
源码文件目录:
.
├── app.js
├── package.json
└── view
├── 404.html
├── index.html
└── todo.html
原生路由的实现需要引入fs模块来读取文件。然后再根据路由的路径去读取,最后返回给页面,进行渲染。我们看一个小例子进行学习。
/* app.js文件 */
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
/**
* 用Promise封装异步读取文件方法
* @param {string} page html文件名称
* @return {promise}
*/
function render( page ) {
return new Promise(( resolve, reject ) => {
let viewUrl = `./view/${page}`
fs.readFile(viewUrl, "binary", ( err, data ) => {
if ( err ) {
reject( err )
} else {
resolve( data )
}
})
})
}
/**
* 根据URL获取HTML内容
* @param {string} url koa2上下文的url,ctx.url
* @return {string} 获取HTML文件内容
*/
async function route( url ) {
let view = '404.html'
switch ( url ) {
case '/':
view = 'index.html'
break
case '/index':
view = 'index.html'
break
case '/todo':
view = 'todo.html'
break
case '/404':
view = '404.html'
break
default:
break
}
let html = await render( view )
return html
}
app.use( async ( ctx ) => {
let url = ctx.request.url
let html = await route( url )
ctx.body = html
})
app.listen(3000, () => {
console.log('server启动成功')
})
koa-router中间件
如果依靠ctx.request.url去手动处理路由,将会写很多处理代码,这时候就需要对应的路由的中间件对路由进行控制,这里介绍一个比较好用的路由中间件koa-router
安装koa-router中间件:
// koa2 对应的版本是 7.x
npm install --save koa-router@7
快速使用koa-router:
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
// 子路由1
let home = new Router()
home.get('/', async ( ctx )=>{
let html = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`
ctx.body = html
})
// 子路由2
let page = new Router()
page.get('/404', async ( ctx )=>{
ctx.body = '404 page!'
})
.get('/helloworld', async ( ctx )=>{
ctx.body = 'helloworld page!'
})
// 装载所有子路由
let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
// 加载路由中间件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000, () => {
console.log('server启动成功')
})
cookie的使用
koa的上下文(ctx)直接提供了读取和写入的方法。
- ctx.cookies.get(name,[optins]):读取上下文请求中的cookie。
- ctx.cookies.set(name,value,[options]):在上下文中写入cookie。
写入Cookie操作:
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx)=>{
if(ctx.url=== '/index'){
ctx.cookies.set(
'MyName','FangC'
);
ctx.body = 'cookie is ok';
}else{
ctx.body = 'hello world'
}
});
app.listen(3000, () => {
console.log('server启动成功')
})
Cookie选项
比如我们要存储用户名,保留用户登录状态时,你可以选择7天内不用登录,也可以选择30天内不用登录。这就需要在写入是配置一些选项:
- domain:写入cookie所在的域名
- path:写入cookie所在的路径
- maxAge:Cookie最大有效时长
- expires:cookie失效时间
- httpOnly:是否只用http请求中获得
- overwirte:是否允许重写
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx)=>{
if(ctx.url=== '/index'){
ctx.cookies.set(
'MyName','FangC',{
domain:'127.0.0.1', // 写cookie所在的域名
path:'/index', // 写cookie所在的路径
maxAge:1000*60*60*24, // cookie有效时长
expires:new Date('2021-12-31'), // cookie失效时间
httpOnly:false, // 是否只用于http请求中获取
overwrite:false // 是否允许重写
}
);
ctx.body = 'cookie is ok';
}else{
ctx.body = 'hello world'
}
});
app.listen(3000, () => {
console.log('server启动成功')
})
读取Cookie:
const Koa = require('koa');
const app = new Koa();
app.use(async(ctx)=>{
if(ctx.url=== '/index'){
ctx.cookies.set(
'MyName','FangC',{
domain:'127.0.0.1', // 写cookie所在的域名
path:'/index', // 写cookie所在的路径
maxAge:1000*60*60*24, // cookie有效时长
expires:new Date('2021-12-31'), // cookie失效时间
httpOnly:false, // 是否只用于http请求中获取
overwrite:false // 是否允许重写
}
);
ctx.body = 'cookie is ok';
}else{
if( ctx.cookies.get('MyName')){
ctx.body = ctx.cookies.get('MyName');
}else{
ctx.body = 'Cookie is none';
}
}
});
app.listen(3000, () => {
console.log('server启动成功')
})
koa-static静态资源中间件
安装
npm install --save koa-static
新建static文件夹 然后在static文件中放入图片,css和js文件。
使用
const Koa = require('koa')
// 导入path包 用于拼接路径
const path = require('path')
// 导入koa-static包
const static = require('koa-static')
const app = new Koa()
// 静态资源目录对于相对入口文件index.js的路径
const staticPath = './static'
// 开放的文件夹 __dirname为当前运行文件的目录 目前看来 只能开放文件夹 无法开放单独一个文件
app.use(static(
path.join( __dirname, staticPath)
))
app.use( async ( ctx ) => {
ctx.body = 'hello world'
})
app.listen(3000, () => {
console.log('server启动成功')
})
模板引擎
开发中不可能把所有的html代码全部写在JS里,这显然不现实,也没办法完成大型web开发。必须借用模板机制来帮助我们开发,koa2的模板机制要依靠中间件来完成开发。
安装中间件
npm install --save koa-views
安装ejs模板引擎
npm install --save ejs
编写模板
安装好ejs模板引擎后,就可以编写模板了,为了模板统一管理,我们新建一个view的文件夹,并在它下面新建index.ejs文件。
文章目录:
├── package.json
├── index.js
└── view
└── index.ejs
view/index.ejs文件
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>EJS Welcome to <%= title %></p>
</body>
</html>
./index.js文件
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()
// 加载模板引擎
app.use(views(path.join(__dirname, './view'), {
extension: 'ejs'
}))
app.use( async ( ctx ) => {
let title = 'hello koa2'
await ctx.render('index', {
title,
})
})
app.listen(3000,()=>{
console.log('server启动成功');
})
参考
千锋教育大前端小册 Koa2
技术胖-挑战全栈 Koa2教程
廖雪峰的官方网站 Koa
Node.js 系列 - 使用 EJS 模板引擎