Koa2

Koa 必须使用 7.6 以上的版本。如果你的版本低于这个要求,就要先升级 Node。

基本用法

Koa 提供一个 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)。通过加工这个对象,就可以控制返回给用户的内容。
Context.response.body属性就是发送给用户的内容。如下:

const Koa = require('koa');
const app = new Koa();
const main = ctx => {
  ctx.response.body = 'Hello World';
};
app.use(main);
app.listen(3000);    //架设http服务器

app.listen()就是http.createServer(app.callback()).listen(...)的缩写。

main函数用来设置ctx.response.body。然后,使用app.use方法加载main函数。
ctx.response代表 HTTP Response。同样地,ctx.request代表 HTTP Request。

Koa 默认的返回类型是text/plain,如果想返回其他类型内容,可以用ctx.request.accepts判断一下,客户端希望接受什么数据(根据 HTTP Request 的Accept字段),然后使用ctx.response.type指定返回类型。

const main = ctx => {
  if (ctx.request.accepts('xml')) {
    ctx.response.type = 'xml';
    ctx.response.body = '<data>Hello World</data>';
  } else if (ctx.request.accepts('json')) {
    ctx.response.type = 'json';
    ctx.response.body = { data: 'Hello World' };
  } else if (ctx.request.accepts('html')) {
    ctx.response.type = 'html';
    ctx.response.body = '<p>Hello World</p>';
  } else {
    ctx.response.type = 'text';
    ctx.response.body = 'Hello World';
  }
};

路由

原生路由

通过ctx.request.path可以获取用户请求的路径,由此实现简单的路由。

const main = ctx => {
  if (ctx.request.path !== '/') {
    ctx.response.type = 'html';
    ctx.response.body = '<a href="/">Index Page</a>';
  } else {
    ctx.response.body = 'Hello World';
  }
};

koa-route 模块

原生路由用起来不太方便,我们可以使用封装好的[koa-route]模块。

const route = require('koa-route');
const about = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = '<a href="/">Index Page</a>';
};
const main = ctx => {
  ctx.response.body = 'Hello World';
};
app.use(route.get('/', main));
app.use(route.get('/about', about));

上面代码中,根路径/的处理函数是main,/about路径的处理函数是about。

静态资源

静态资源一个个写路由很麻烦,也没必要。[koa-static]模块封装了这部分的请求。

const path = require('path');
const serve = require('koa-static');
const main = serve(path.join(__dirname));
app.use(main);

重定向

服务器需要重定向(redirect)访问请求,ctx.response.redirect()方法可以发出一个302跳转,将用户导向另一个路由。

const redirect = ctx => {
  ctx.response.redirect('/');
  ctx.response.body = '<a href="/">Index Page</a>';
};
app.use(route.get('/redirect', redirect));

中间件

const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}
app.use(logger);

上面代码中的logger函数就叫做"中间件"(middleware),因为它处在 HTTP Request 和 HTTP Response 中间,用来实现某种中间功能。app.use()用来加载中间件。

Koa 所有的功能都是通过中间件实现的,默认接受两个参数,第一个参数是 Context 对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。
多个中间件会形成一个栈结构(middle stack),以"先进后出"(first-in-last-out)的顺序执行。如果中间件内部没有调用next函数,那么执行权就不会传递下去。

  • 最外层的中间件首先执行。
  • 调用next函数,把执行权交给下一个中间件。
    ...
  • 最内层的中间件最后执行。
  • 执行结束后,把执行权交回上一层的中间件。
    ...
  • 最外层的中间件收回执行权之后,执行next函数后面的代码。

如果有异步操作(比如读取数据库),中间件就必须写成 [async 函数]。

const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();
const main = async function (ctx, next) {
  ctx.response.type = 'html';
  ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};
app.use(main);
app.listen(3000);

中间件的合成[koa-compose]模块可以将多个中间件合成为一个。

const middlewares = compose([logger, main]);
app.use(middlewares);

Koa 提供了ctx.throw()方法,用来抛出错误。ctx.throw(500)就是抛出500错误。

const main = ctx => {
  ctx.throw(500);
};

如果将ctx.response.status设置成404,就相当于ctx.throw(404),返回404错误。

const main = ctx => {
  ctx.response.status = 404;
  ctx.response.body = 'Page Not Found';
};

为了方便处理错误,最好使用try...catch将其捕获。但是,为每个中间件都写try...catch太麻烦,我们可以让最外层的中间件,负责所有中间件的错误处理。

const handler = async (ctx, next) => {
  try {
    await next();  // 此处包住了后面内部所有中间件
  } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.body = {
      message: err.message
    };
  }
};
const main = ctx => {
  ctx.throw(500);
};
app.use(handler);
app.use(main);

Web App 的功能

Cookies

ctx.cookies用来读写 Cookie。

const main = function(ctx) {
  const n = Number(ctx.cookies.get('view') || 0) + 1;
  ctx.cookies.set('view', n);
  ctx.response.body = n + ' views';
}
// 访问 http://127.0.0.1:3000 ,你会看到1 views。刷新一次页面,就变成了2 views。

表单就是 POST 方法发送到服务器的键值对。[koa-body]模块可以用来从 POST 请求的数据体里面提取键值对。

const koaBody = require('koa-body');
const main = async function(ctx) {
  const body = ctx.request.body;
  if (!body.name) ctx.throw(400, '.name required');
  ctx.body = { name: body.name };
};
app.use(koaBody());

[koa-body]模块还可以用来处理文件上传。请看:
https://github.com/ruanyf/jstutorial/blob/gh-pages/nodejs/koa.md 阮一峰教程

级联式(Cascading)的结构,也就是说,属于是层层调用,第一个中间件调用第二个中间件,第二个调用第三个,以此类推。上游的中间件必须等到下游的中间件返回结果,才会继续执行,这点很像递归。

Koa2与Koa1的区别

简单来说就是async取代了星号* await取代了yield;
async/await 的特点:
可以让异步逻辑用同步写法实现
最底层的await返回需要是Promise对象
可以通过多层 async function 的同步写法代替传统的callback嵌套

Koa2特性:

  • 只提供封装好http上下文、请求、响应,以及基于async/await的中间件容器。
  • 利用ES7的async/await的来处理传统回调嵌套问题和代替koa@1的generator,但是需要在node.js 7.x的harmony模式下才能支持async/await
  • 中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用。
  • generator 中间件开发在koa v1和v2中使用
  • async await 中间件开发和只能在koa v2中使用
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中间件

npm/cnpm install --save koa-router

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')
})

官网技术说明

Koa 依赖 node v7.6.0 或 ES2015及更高版本和 async 方法支持.
必修的 hello world 应用:

const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
  ctx.body = 'Hello World';
});
app.listen(3000);

级联通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。

app.use(function) 将给定的中间件方法添加到此应用程序。
app.keys= 设置签名的 Cookie 密钥。

上下文(Context)

Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用。

app.use(async ctx => {
  ctx; // 这是 Context
  ctx.request; // 这是 koa Request
  ctx.response; // 这是 koa Response
});

为方便起见许多上下文的访问器和方法直接委托给它们的 ctx.request或 ctx.response。ctx.type 和 ctx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。

ctx.req // Node 的 request 对象.
ctx.res // Node 的 response 对象.

绕过 Koa 的 response 处理是不被支持的.应避免使用以下 node 属性:
res.statusCode
res.writeHead()
res.write()
res.end()

ctx.request koa 的 Request 对象.
ctx.response koa 的 Response 对象.

ctx.cookies.get(name, [options]) 通过 options 获取 cookie name:
signed 所请求的cookie应该被签名
ctx.cookies.set(name, value, [options])通过 options 设置 cookie name 的 value :

  • maxAge 一个数字表示从 Date.now() 得到的毫秒数
  • signed cookie 签名值
  • expires cookie 过期的 Date
  • path cookie 路径, 默认是'/'

request.header 请求标头对象。
request.header = 设置请求标头对象。

request.method 请求方法。
request.method = 设置请求方法,对于实现诸如 methodOverride() 的中间件是有用的。

request.url 获取请求 URL.
request.url = 设置请求 URL, 对 url 重写有用。

request.origin 获取URL的来源,包括 protocol 和 host。
ctx.request.origin // => http://example.com

request.href 获取完整的请求URL,包括 protocol,host 和 url。
ctx.request.href;// => http://example.com/foo/bar?q=1

request.path 获取请求路径名。

request.querystring 根据 ? 获取原始查询字符串.

request.type 获取请求 Content-Type 不含参数 "charset"。
const ct = ctx.request.type;// => "image/png"

request.query 获取解析的查询字符串, 当没有查询字符串时,返回一个空对象。
例如 "color=blue&size=small":

{
  color: 'blue',
  size: 'small'
}

request.accepts(types)
检查给定的 type(s) 是否可以接受,如果 true,返回最佳匹配,否则为 false。

response.header 响应标头对象。

response.status
获取响应状态。默认情况下,response.status 设置为 404 而不是像 node 的 res.statusCode 那样默认为 200。

response.message 获取响应的状态消息.
response.body 获取响应主体。

response.redirect(url, [alt]) 执行 [302] 重定向到 url.

response.type 获取响应 Content-Type 不含参数 "charset"。
const ct = ctx.type;// => "image/png"

response.length
以数字返回响应的 Content-Length,或者从ctx.body推导出来,或者undefined。


起步填坑

项目生成器: npm install -g koa-generator
在你的工作目录下,输入:koa2 HelloKoa2
成功创建项目后,进入项目目录,并执行npm install命令cd HelloKoa2 npm install
启动项目:npm start

koa声明说要在v3版本中取消对generator中间件的支持,所以为了长久考虑还是用async语法的好。
如果想要继续使用function*语法,可以使用 koa-convert 这个中间件进行转换。

const convert = require('koa-convert');
app.use(convert(bodyparser));
app.use(convert(json()));
app.use(convert(logger()));

Context封装了node中的request和response。
koa@1.x使用this引用Context对象:

app.use(function *(){
  this.body = 'Hello World';
});

koa@2.x中使用ctx来访问Context对象:

app.use(async (ctx, next) => {
  await next();
  ctx.body = 'Hello World';
});

项目配置

这里的配置指的是运行环境的配置,比如我们在开发阶段使用本地的数据库,测试要使用测试库,发布上线时候使用线上的库,也会有不同的端口号。
npm start 就会运行package.json中scripts对象对应的start字段后面的内容。

在npm中,有四个常用的缩写
npm start是npm run start
npm stop是npm run stop的简写
npm test是npm run test的简写
npm restart是npm run stop && npm run restart && npm run start的简写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容