Koa讲解

web应用开发框架

第一章 koa入门与使用

image-20190620190151695.png

简介

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

  • 下一代, 超前
  • 新的语法, es7
  • 优雅

安装

Koa 依赖 node v7.6.0 或 ES2015及更高版本和 async 方法支持.

你可以使用自己喜欢的版本管理器快速安装支持的 node 版本:

$ nvm install 7
$ npm i koa

基本用法

1.架设http服务

只要三行代码,就可以用 Koa 架设一个 HTTP 服务。

const Koa = require('koa');
const app = new Koa();

app.listen(3000);

2. context对象

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

上面代码中,main函数用来设置ctx.response.body。然后,使用app.use方法加载main函数。

你可能已经猜到了,ctx.response代表 HTTP Response。同样地,ctx.request代表 HTTP Request。

3. http response类型

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

// demos/03.js
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';
  }
};

4. 网页模板

实际开发中,返回给用户的网页往往都写成模板文件。我们可以让 Koa 先读取模板文件,然后将这个模板返回给用户。

const Koa = require('koa');
const app = new Koa();
const fs = require('fs');
const path = require('path');

const main = (ctx) => {
    ctx.response.type = 'html';
    ctx.response.body = fs.createReadStream(path.resolve(path.join(__dirname, './demo.html')));
}

app.use(main);
app.listen(3000);

路由

1.原生路由

网站一般都有多个页面。通过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';
  }
};

2. 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));

3. 静态资源

如果网站提供静态资源(图片、字体、样式表、脚本......),为它们一个个写路由就很麻烦,也没必要。koa-static模块封装了这部分的请求。

const path = require('path');
const serve = require('koa-static');

const main = serve(path.join(__dirname));
app.use(main);

4.重定向

有些场合,服务器需要重定向(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));

中间件

1.logger

Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware)。为了理解中间件,我们先看一下 Logger (打印日志)功能的实现。

最简单的写法就是在main函数里面增加一行。

const main = ctx => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  ctx.response.body = 'Hello World';
};

2. 中间件的概念

上一个例子里面的 Logger 功能,可以拆分成一个独立函数。

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

3.中间件执行顺序

var koa = require('koa');
var app = new koa();
// next前面的代码,按照use的顺序执行,next后面的代码反着执行。
// koa结论: next方法其实就是指的, 当前执行函数的下一个use里面的方法。
const one = (ctx, next) => {
    console.log('>> one');
    next();  // tow
    console.log('<< one');
  }
  
  const two = (ctx, next) => {
    console.log('>> two');
    next();  // three
    console.log('<< two');
  }
  
  const three = (ctx, next) => {
    console.log('>> three');
    next();  //  空
    console.log('<< three');
  }
  
  // 他们3个都是中间件, next
  app.use(one);
  app.use(two);
  app.use(three);
  app.listen(3000);

结果如下:

>> one
>> two
>> three
<< three
<< two
<< one

洋葱模型

其实就是想向我们表达,调用next的时候,中间件的代码执行顺序是什么。

image-20190623131006853.png

4.异步中间件

迄今为止,所有例子的中间件都是同步的,不包含异步操作。如果有异步操作(比如读取数据库),中间件就必须写成 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);

5. 中间件的合成

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

const compose = require('koa-compose');

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

const main = ctx => {
  ctx.response.body = 'Hello World';
};

const middlewares = compose([logger, main]);
app.use(middlewares);
compose简单介绍
var greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastName
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack', 'smith'))   // Hello Jack Smith
  • compose的参数是函数,返回的也是一个函数
  • 因为除了第一个函数的接受参数,其他函数的接受参数都是上一个函数的返回值,所以初始函数的参数是多元的,而其他函数的接受值是一元
  • compsoe函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面

参考链接: https://segmentfault.com/a/1190000008394749

  • compose不是koa专有,例如react的redux中都有类似的函数式编程组件,其共通之处就是合并函数输出结果,而且都是根据compose参数从右向左执行

错误处理

下面是常见的HTTP状态码:

  • 200 - 请求成功
  • 304 - 资源(网页等)被转移到其它URL,缓存(协商缓存,如果是强缓存则是200),所谓协商缓存就是还是有网络请求的,只是结果是没变化然后读取本地。强缓存则是直接读取硬盘数据
  • 404 - 请求的资源(网页等)不存在。客户端错误
  • 500 - 内部服务器错误
image-20190620193933587.png

1. 500错误

如果代码运行过程中发生错误,我们需要把错误信息返回给用户。HTTP 协定约定这时要返回500状态码。Koa 提供了ctx.throw()方法,用来抛出错误,ctx.throw(500)就是抛出500错误。

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

2.404错误

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

3.处理错误的中间件

为了方便处理错误,最好使用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);
};
//一般错误处理的中间件都是use在最上面,可以从洋葱图分析原因
app.use(handler);
app.use(main);

4. error事件监听

运行过程中一旦出错,Koa 会触发一个error事件。监听这个事件,也可以处理错误。

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

app.on('error', (err, ctx) =>
  console.error('server error', err);
);

5. 释放error事件

  • 上面的错误处理中间件如果不写,则node也会兜底错误一般不会崩溃,只是报错,其实这个错误就是在error事件内部触发的。

需要注意的是,如果错误被try...catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。

const handler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.type = 'html';
    ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
    ctx.app.emit('error', err, ctx);
  }
};

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

app.on('error', function(err) {
  console.log('logging error ', err.message);
  console.log(err);
});

说明app是继承自nodejs的EventEmitter对象。

参考链接:https://www.runoob.com/nodejs/nodejs-event.html

web app

1. request参数处理

Web 应用离不开处理表单。本质上,表单就是 POST 方法发送到服务器的键值对。koa-body模块可以用来从 POST 请求的数据体里面提取键值对。

var koa = require('koa');
var app = new koa();
// var route = require('koa-route');

const main = (ctx) => {
    var dataArr = [];
    ctx.req.addListener('data', (data) => {
        dataArr.push(data);
    });
    ctx.req.addListener('end', () => {
        // console.log(jsonBodyparser(str));
        let data = Buffer.concat(dataArr).toString();
        console.log(data)
    });
    ctx.response.body = 'hello world';
}

app.use(route.post('/', main));  //  1. 路径 2. ctx函数
app.listen(3000);  // 起服务 , 监听3000端口

2. 文件上传

koa-body模块还可以用来处理文件上传。

const os = require('os');
const path = require('path');
const koaBody = require('koa-body');
var route = require('koa-route');
var koa = require('koa');
var app = new koa();
var fs = require('fs');

const main = async function(ctx) {
  const tmpdir = os.tmpdir(); 
  const filePaths = [];  
  const files = ctx.request.files || {};

  for (let key in files) {
    const file = files[key];
    const filePath = path.join(tmpdir, file.name);
    const reader = fs.createReadStream(file.path);
    const writer = fs.createWriteStream(filePath);
    reader.pipe(writer);
    filePaths.push(filePath);
  }
//   console.log('xxxxxxxx', filePaths)
  ctx.body = filePaths;
};

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

推荐阅读更多精彩内容

  • 一、基本用法 1.1 架设 HTTP 服务 // demos/01.jsconst Koa = require('...
    majun00阅读 1,312评论 0 5
  • 1.简书 koa是由Express原班人马打造,致力于成为一个更小、更富有表现力、更健壮的Web框架。使用koa编...
    不去解释阅读 2,621评论 0 11
  • koa介绍 Koa是由Express背后的团队创建的新流行的Web应用框架。它旨在成为Express的现代和极简主...
    初漾流影阅读 1,506评论 0 2
  • Koa 必须使用 7.6 以上的版本。如果你的版本低于这个要求,就要先升级 Node。 基本用法 Koa 提供一个...
    Gukson666阅读 2,406评论 0 1
  • Koa 学习 历史 Express Express是第一代最流行的web框架,它对Node.js的http进行了封...
    Junting阅读 2,751评论 0 0