koa 概述
node的一個(gè)輕量級(jí)框架,內(nèi)置類似connect用來處理中間件,但是并沒有捆綁任何中間件,采用的是洋蔥模型進(jìn)行級(jí)聯(lián)
koa 安裝
依賴
koa依賴node v7.6.0 或者 ES2015(ES6)及更高版本和async方法支持
安裝
$ node -v
# 大于等于 7.6.0
$ yarn add koa
要在 node < 7.6.0的版本中使用koa的async
require('babel-register');
// 應(yīng)用的其他require 需要放在這個(gè)后面
// eg:
const app = require('./app');
要解析和編譯 async 方法, 需要 transform-async-to-generator 插件
在.babelrc 中 增加
{
"plugins": ["transform-async-to-generator"]
}
也可以用 env preset 的 target 參數(shù) "node": "current" 替代.
使用
起一個(gè)基本服務(wù)
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(9800);
console.log('server is started in : \n http://127.0.0.1:9800');
級(jí)聯(lián)模式(洋蔥模型)
使用async,實(shí)現(xiàn)從哪里出發(fā)最后回到哪里去
當(dāng)一個(gè)請(qǐng)求開始,先按順序執(zhí)行中間件,當(dāng)一個(gè)中間件調(diào)用next()時(shí),該中間件會(huì)暫停并將控制傳遞到下一個(gè)中間件。最后當(dāng)沒有中間件執(zhí)行的時(shí)候,會(huì)從最后一個(gè)中間件的next()后開始執(zhí)行,執(zhí)行完后去找上一個(gè)中間件的next()執(zhí)行,一直到第一個(gè)中間件
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(4);
// 這時(shí)候因?yàn)橐呀?jīng)設(shè)置好了,所以可以獲取到我們想要的
const responseTime = ctx.response.get('X-Response-Time');
console.log(`【${ctx.method}】 ${ctx.url} - ${responseTime}`);
});
// set x-response-time
app.use(async (ctx, next) => {
console.log(2);
// 請(qǐng)求到這的時(shí)間
const start = Date.now();
await next();
console.log(3);
// 處理完的時(shí)間
// 處理的時(shí)間
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
app.use(async ctx => {
ctx.body = 'connect execution order';
})
const port = 9800;
app.listen(port);
console.log(`server is started in : \n http://127.0.0.1:${port}`);
koa-router
使用
require('koa-router')返回的是一個(gè)函數(shù)
const Koa = require('koa');
// 函數(shù),別忘了
const router = require('koa-router')();
/**
或者
const KoaRouter = require('koa-router');
const router = new KoaRouter();
*/
const app = new Koa();
// 增加路由
router.get('/', async(ctx, next) => {
ctx.body = '<h1>index page</h1>'
});
router.get('/home', async(ctx, next) => {
ctx.body = '<h1>home page</h1>'
});
router.get('not-found', '/404', async(ctx, next) => {
ctx.body = '<h1>Not Found page</h1>'
});
router.redirect('/*', 'not-found', 301);
/**
等價(jià)于
router.all('/*', async(ctx, next) => {
ctx.redirect('/404');
ctx.status = 301;
})
*/
// 使用路由中間件
app.use(router.routes());
app.listen(9800, () => {
console.log('server is running at http://127.0.0.1:9800');
})
命名路由
router.get('user', '/users/:id'. (ctx, next) => {
});
// 使用路由生成器
router.url('user', 3);
// 生成路由 '/users/3'
router.url('user', { id: 3 });
// 生成路由 '/users/3'
router.use((ctx, next) =>{
ctx.redirect(ctx.router.url('user', 3));
})
router.url根據(jù)路由名稱和可選的參數(shù)生成具體的URL,而不用采用字符串拼接的方式生成URL
單個(gè)路由多中間件
router.get('/users/:id', async(ctx, next) => {
// 中間件
const user = await User.findOne(ctx.params.id);
ctx.user = user;
next();
}, async(ctx) => {
console.log(ctx.user);
})
URL參數(shù)
router.get('/:id/:title', async(ctx, next) => {
console.log(ctx.params);
const { id, title } = ctx.params;
});
路由前綴
為一組路由添加一個(gè)統(tǒng)一的前綴,是一個(gè)固定的字符串
const router = new KoaRouter({
prefix: '/api'
});
router.get('/', ...)
// /api
router.get('/:id', ...)
// /api/:id
方法和參數(shù)
app
app.env默認(rèn)是NODE_ENV或者'development'
app.proxy 當(dāng)真正的代理頭字段將被信任時(shí)
app.subdomainOffset 對(duì)于要忽略的 .subdomains 偏移[2]
這句不太懂
app.listen(port)
koa可以將一個(gè)或者多個(gè)koa應(yīng)用安裝在一起,打包成單個(gè)服務(wù)
- 無作用koa應(yīng)用
const Koa = require('koa');
const app = new Koa();
app.listen(9800);
- app.listen()是語法糖,最后執(zhí)行的還是下面這個(gè)
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(9800);
- 一個(gè)koa應(yīng)用多個(gè)地址/服務(wù)
const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(9800);
https.createServer(app.callback()).listen(9801);
- 多個(gè)koa應(yīng)用一個(gè)地址/服務(wù)
怎么將多個(gè) Koa 應(yīng)用程序安裝在一起以形成具有單個(gè)HTTP服務(wù)器的更大應(yīng)用程序?
app.callback()
這個(gè)回調(diào)函數(shù)返回值可以用http.createServer()方法處理請(qǐng)求,也可以使用這個(gè)回調(diào)函數(shù)將koa應(yīng)用掛載到Connect/Express應(yīng)用中
app.use(function)
將需要的中間件方法添加到應(yīng)用里
這里還有很多需要學(xué)習(xí),比如開發(fā)中間件
app.keys
設(shè)置Cookie密鑰
// 使用koa的KeyGrip
app.keys = ['new secret', 'demo'];
// 使用別的
app.keys = new KeyGrip(['new secret', 'demo'], 'sha256');
這些簽名可以倒換,并在使用{ signed: true }參數(shù)簽名Cookie時(shí)使用
倒換????
ctx.cookies.set('name', 'company', { signed: true });
app.context
ctx 是 基于app.context原型創(chuàng)建的,可以編輯app.context為ctx添加其他屬性
app.context.db = db();
app.use(async ctx => {
console.log(ctx.db);
});
注意: ctx上屬性都是使用
getter,setter和Object.defineProperty()定義的。只能通過在app.context上使用Object.defineProperty()來編輯這些屬性,但是不推薦修改
錯(cuò)誤處理
app.silent = true
錯(cuò)誤日志不輸出,這時(shí)候需要手動(dòng)記錄錯(cuò)誤和拋出
app.on('error', (err, ctx) => {
// 這里對(duì)錯(cuò)誤做處理
console.log('server error >>> ', err);
});
context
ctx.req ctx.request
- ctx.req:Node的request對(duì)象
- ctx.request:koa的request對(duì)象
ctx.res ctx.response
- ctx.res: Node
- ctx.response:koa
- 繞過koa的response處理是不被支持的,不能使用node的一些屬性:
- res.statusCode
- res.writeHead()
- res.write()
- res.end()
所以建議使用 ctx.response
ctx.state
命名空間,用于通過中間件傳遞信息,
ctx.state.user = await User.find(id);
ctx.app
應(yīng)用程序的實(shí)例引用
ctx.cookies
cookie的相關(guān)操作,使用的是 cookies模塊
-
ctx.cookies.get(name, [options])
通過options獲取 cookie name
signed所請(qǐng)求的cookie應(yīng)該被簽名
-
ctx.cookies.set(name, value, [options])
通過options設(shè)置cookie name 的值:
- maxAge 一個(gè)數(shù)字表示從Date.now()得到的毫秒數(shù)
- signed cookie簽名值
- expires cookie過期的Date
- path cookie路徑,默認(rèn)是
'/' - domain cookie域名
- secure 安全cookie
- httpOnly 服務(wù)器可訪問cookie, 默認(rèn)是
true - overwrite 布爾值, 表示是否覆蓋以前設(shè)置的同名的cookie, 默認(rèn)是
false
cookie這方面還要再看下,好多不懂
ctx.throw([statusCode], [msg], [properties])
Helper方法拋出一個(gè).status屬性默認(rèn)的 500 的錯(cuò)誤,這允許koa做出適當(dāng)響應(yīng)
使用 http-errors 來創(chuàng)建的錯(cuò)誤
ctx.throw(400);
ctx.throw(400, 'name is required');
ctx.throw(400, 'name is required', { user: user });
等效于:
const err = new Error('name is required');
err.status = 400;
err.expose = true;
throw err;
一般用于用戶級(jí)錯(cuò)誤,消息適用于客戶端響應(yīng),這通常不是錯(cuò)誤信息的內(nèi)容,因?yàn)橐恍┬畔⒉荒芊祷亟o客戶端
err.expose 是什么
ctx.assert(value, [status], [msg], [properties])
斷言,一般測試用的
判斷value是否為真值,具體用法同node的assert()
ctx.respond
ctx.respond = false;
繞過koa對(duì)response的處理
koa 不支持使用此功能,主要是為了能在koa中使用傳統(tǒng)道德fn(req, res)功能和中間件
中間件使用
中間件的使用順序要注意,不同順序產(chǎn)生的結(jié)果可能不同,洋蔥模型引起的
koa-bodyparser
解析post/表單發(fā)送的信息
- 使用
const Koa = require('koa');
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
router.post('/user/login', async(ctx, next) => {
const { name, password } = ctx.request.body;
if (name === 'abc' && password === '123') {
ctx.response.body = `Hello, ${name}`;
} else {
ctx.response.body = '賬號(hào)信息錯(cuò)誤'
}
});
koa-static
處理靜態(tài)文件
const path = require('path');
const koaStatic = require('koa-static');
app.use(koaStatic(path.resolve(__dirname, './static')));
log4js
日志記錄模塊
日志等級(jí)
有7個(gè)等級(jí)的日志級(jí)別:
- ALL:輸出所有的日志
- TRACE
- DEBUG
- INFO
- WARN
- ERROR
- FATAL
- MARK
- OFF:所有的日志都不輸出
基本使用
const log4js = require('log4js');
log4js.configure({
/**
* 指定要記錄的日志分類 cheese
* 展示方式為文件類型 file
* 日志輸出的文件名 cheese.log
*/
appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
/**
* 指定日志的默認(rèn)配置項(xiàng)
* 如果 log4js.getLogger 中沒有指定,默認(rèn)為 cheese 日志的配置項(xiàng)
* 指定 cheese 日志的記錄內(nèi)容為 error和error以上級(jí)別的信息
*/
categories: { default: { appenders: ['cheese'], level: 'error' } }
});
const logger = log4js.getLogger('cheese');
logger.trace('Entering cheese testing');
logger.debug('Got cheese.');
logger.info('Cheese is Gouda.');
logger.warn('Cheese is quite smelly.');
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');
按日期分割日志
const log4js = require('log4js');
log4js.configure({
appenders: {
cheese: {
type: 'dateFile', // 日志類型
filename: `logs/task`, // 輸出的文件名
pattern: '-yyyy-MM-dd.log', // 文件名增加后綴
alwaysIncludePattern: true // 是否總是有后綴名
layout: { // 設(shè)置輸出格式, 如果默認(rèn)輸出日志格式不滿意
/**
* 有好多模式,默認(rèn)是 default
* pattern 是自己寫格式
*/
type: 'pattern',
/**
* 下面這個(gè)是默認(rèn)的,自己改動(dòng)的不展示
* 我不喜歡日期中的T, 所以把T手動(dòng)去掉了
*/
pattern: '[%d{yyyy-MM-ddThh:mm:ss}] [%p] %c %m%n'
}
}
},
categories: {
default: {
appenders: ['cheese'],
level:'info'
}
}
});
const logger = log4js.getLogger('cheese');
logger.info('日期分割');