koa-node.js

总结一下koa

koa2开始

async/await 使用
快速上手理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()

从代码中可以找到
异步->同步 底层返回一个promise 可以替代callback

koa 结构

https://github.com/koajs/koa/
开源中的源码结构

1
2
3
4
5
6
├── lib
│ ├── application.js
│ ├── context.js
│ ├── request.js
│ └── response.js
└── package.json

application 核心中间件处理流程。入口文件
context 上下文
request http 请求
response 处理http响应

koa2特性

只提供封装好http上下文,请求,响应,以及基于async/await的中间件容器

koa中间件开发

async 中间件开发

1
2
3
4
5
6
7
8
9
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return async function ( ctx, next ) {
log(ctx);
await next()
}
}

koa原生路由实现

1
2
3
4
5
6
7
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
let url = ctx.request.url
ctx.body = url
})
app.listen(3000)

koa-router

https://github.com/ChenShenhai/koa2-note/tree/master/demo/route-use-middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
const Router = require('koa-router')
let home = new Router()
// 子路由1
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('[demo] route-use-middleware is starting at port 3000')
})

请求数据获取

  • get请求
    https://github.com/ChenShenhai/koa2-note/blob/master/demo/request/get.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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('[demo] request get is starting at port 3000')
    })
  • post请求
    https://github.com/ChenShenhai/koa2-note/blob/master/demo/request/post.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 解析上下文里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
    }
  • koa-bodyparser中间件
    https://github.com/ChenShenhai/koa2-note/blob/master/demo/request/post-middleware.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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/>
    <p>email</p>
    <input name="email" /><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!!! o(╯□╰)o</h1>'
    }
    })
    app.listen(3000, () => {
    console.log('[demo] request post is starting at port 3000')
    })

原生koa2实现静态资源服务器

https://github.com/ChenShenhai/koa2-note/blob/master/demo/static-server/

1
2
3
4
5
6
7
8
9
10
11
12
├── static # 静态资源目录
│ ├── css/
│ ├── image/
│ ├── js/
│ └── index.html
├── util # 工具代码
│ ├── content.js # 读取请求内容
│ ├── dir.js # 读取目录内容
│ ├── file.js # 读取文件内容
│ ├── mimes.js # 文件类型列表
│ └── walk.js # 遍历目录内容
└── index.js # 启动入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const Koa = require('koa')
const path = require('path')
const content = require('./util/content')
const mimes = require('./util/mimes')
const app = new Koa()
// 静态资源目录对于相对入口文件index.js的路径
const staticPath = './static'
// 解析资源类型
function parseMime( url ) {
let extName = path.extname( url )
extName = extName ? extName.slice(1) : 'unknown'
return mimes[ extName ]
}
app.use( async ( ctx ) => {
// 静态资源目录在本地的绝对路径
let fullStaticPath = path.join(__dirname, staticPath)
// 获取静态资源内容,有可能是文件内容,目录,或404
let _content = await content( ctx, fullStaticPath )
// 解析请求内容的类型
let _mime = parseMime( ctx.url )
// 如果有对应的文件类型,就配置上下文的类型
if ( _mime ) {
ctx.type = _mime
}
// 输出静态资源内容
if ( _mime && _mime.indexOf('image/') >= 0 ) {
// 如果是图片,则用node原生res,输出二进制数据
ctx.res.writeHead(200)
ctx.res.write(_content, 'binary')
ctx.res.end()
} else {
// 其他则输出文本
ctx.body = _content
}
})
app.listen(3000)
console.log('[demo] static-server is starting at port 3000')

koa-static 中间件

https://github.com/ChenShenhai/koa2-note/blob/master/demo/static-use-middleware/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Koa = require('koa')
const path = require('path')
const static = require('koa-static')
const app = new Koa()
// 静态资源目录对于相对入口文件index.js的路径
const staticPath = './static'
app.use(static(
path.join( __dirname, staticPath)
))
app.use( async ( ctx ) => {
ctx.body = 'hello world'
})
app.listen(3000, () => {
console.log('[demo] static-use-middleware is starting at port 3000')
})

koa2使用cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
if ( ctx.url === '/index' ) {
ctx.cookies.set(
'cid',
'hello world',
{
domain: 'localhost', // 写cookie所在的域名
path: '/index', // 写cookie所在的路径
maxAge: 10 * 60 * 1000, // cookie有效时长
expires: new Date('2017-02-15'), // cookie失效时间
httpOnly: false, // 是否只用于http请求中获取
overwrite: false // 是否允许重写
}
)
ctx.body = 'cookie is ok'
} else {
ctx.body = 'hello world'
}
})
app.listen(3000, () => {
console.log('[demo] cookie is starting at port 3000')
})

koa2实现session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const Koa = require('koa')
const session = require('koa-session-minimal')
const MysqlSession = require('koa-mysql-session')
const app = new Koa()
// 配置存储session信息的mysql
let store = new MysqlSession({
user: 'root',
password: 'abc123',
database: 'koa_demo',
host: '127.0.0.1',
})
// 存放sessionId的cookie配置
let cookie = {
maxAge: '', // cookie有效时长
expires: '', // cookie失效时间
path: '', // 写cookie所在的路径
domain: '', // 写cookie所在的域名
httpOnly: '', // 是否只用于http请求中获取
overwrite: '', // 是否允许重写
secure: '',
sameSite: '',
signed: '',
}
// 使用session中间件
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: cookie
}))
app.use( async ( ctx ) => {
// 设置session
if ( ctx.url === '/set' ) {
ctx.session = {
user_id: Math.random().toString(36).substr(2),
count: 0
}
ctx.body = ctx.session
} else if ( ctx.url === '/' ) {

// 读取session信息
ctx.session.count = ctx.session.count + 1
ctx.body = ctx.session
}
})
app.listen(3000)
console.log('[demo] session is starting at port 3000')

koa2加载模板引擎

https://github.com/ChenShenhai/koa2-note/blob/master/demo/ejs/

1
2
3
4
├── package.json
├── index.js
└── view
└── index.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

文件上传 busboy模块、异步上传图片

https://github.com/ChenShenhai/koa2-note/tree/master/demo/upload-async

1
2
3
4
5
6
7
8
9
10
11
12
.
├── index.js # 后端启动文件
├── node_modules
├── package.json
├── static # 静态资源目录
│ ├── image # 异步上传图片存储目录
│ └── js
│ └── index.js # 上传图片前端js操作
├── util
│ └── upload.js # 后端处理图片流操作
└── view
└── index.ejs # ejs后端渲染模板

创建数据库会话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const mysql      = require('mysql')
const connection = mysql.createConnection({
host : '127.0.0.1', // 数据库地址
user : 'root', // 数据库用户
password : '123456' // 数据库密码
database : 'my_database' // 选中数据库
})
// 执行sql脚本对数据库进行读写
connection.query('SELECT * FROM my_table', (error, results, fields) => {
if (error) throw error
// connected!
// 结束会话
connection.release()
});

async/await封装使用mysql

  • promise 封装./async-db
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    const mysql = require('mysql')
    const pool = mysql.createPool({
    host : '127.0.0.1',
    user : 'root',
    password : '123456',
    database : 'my_database'
    })
    let query = function( sql, values ) {
    return new Promise(( resolve, reject ) => {
    pool.getConnection(function(err, connection) {
    if (err) {
    reject( err )
    } else {
    connection.query(sql, values, ( err, rows) => {

    if ( err ) {
    reject( err )
    } else {
    resolve( rows )
    }
    connection.release()
    })
    }
    })
    })
    }
    module.exports = { query }

async/await 使用

1
2
3
4
5
6
7
8
9
10
11
const { query } = require('./async-db')
async function selectAllData( ) {
let sql = 'SELECT * FROM my_table'
let dataList = await query( sql )
return dataList
}
async function getData() {
let dataList = await selectAllData()
console.log( dataList )
}
getData()

跨域

https://github.com/ChenShenhai/koa2-note/blob/master/demo/jsonp/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 判断是否为JSONP的请求 
if ( ctx.method === 'GET' && ctx.url.split('?')[0] === '/getData.jsonp') {
// 获取jsonp的callback
let callbackName = ctx.query.callback || 'callback'
let returnData = {
success: true,
data: {
text: 'this is a jsonp api',
time: new Date().getTime(),
}
}
// jsonp的script字符串
let jsonpStr = `;${callbackName}(${JSON.stringify(returnData)})`
// 用text/javascript,让请求支持跨域获取
ctx.type = 'text/javascript'
// 输出jsonp字符串
ctx.body = jsonpStr
}

单元测试

比较出名的有mocha,karma,jasmine等
https://github.com/ChenShenhai/koa2-note/blob/master/demo/test-unit/

开发debug

https://github.com/ChenShenhai/koa2-note/blob/master/demo/start-quick/

坚持原创技术分享,您的支持将鼓励我继续创作!