koa相關(guān)介紹和操作

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ù)

  1. 無作用koa應(yīng)用
const Koa = require('koa');
const app = new Koa();
app.listen(9800);
  1. app.listen()是語法糖,最后執(zhí)行的還是下面這個(gè)
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(9800);
  1. 一個(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);
  1. 多個(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,setterObject.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的一些屬性:
    1. res.statusCode
    2. res.writeHead()
    3. res.write()
    4. 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í)別:

  1. ALL:輸出所有的日志
  2. TRACE
  3. DEBUG
  4. INFO
  5. WARN
  6. ERROR
  7. FATAL
  8. MARK
  9. 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('日期分割');
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容