Node.js+Webpack开发实战
上QQ阅读APP看书,第一时间看更新

5.5 Cookie操作

Cookie是Web应用维持少量数据的一种手段,通过Cookie,服务端可以表示用户以及用户身份。本节我们将学习一下Koa如何操作Cookie。

5.5.1 Cookie签名

由于Cookie存放在浏览器端,存在篡改风险,因此Web应用一般会在存放数据的时候同时存放一个签名cookie,以保证Cookie内容不被篡改。

Koa中需要配置Cookie签名密钥才能使用Cookie功能,否则将报错。

const Koa = require('koa');
const app = new Koa();
app.keys = ['signedKey'];  // 推荐使用随机字符串

5.5.2 写入Cookie

访问http://localhost:10000,查看浏览器的Cookie列表,结果如图5-1所示。

图5-1

可以看到除了我们设置的“logged”之外,多了个“logged.sig”,这就是用来签名的Cookie。服务端读取“logged”时,还会同时读取“logged.sig”,一旦发现签名不匹配,则读取到的cookie值为“undefined”。

5.5.3 读取Cookie

// 导入模块
const Koa = require('koa');
const app = new Koa();
app.keys = ['signedKey'];
app.use(async (ctx) => {
   const logged = ctx.cookies.get('logged', { signed: true });
   ctx.body = logged;
});
// 监听
app.listen(10000, () => {
   console.log('listen on 10000');
});

访问http://localhost:10000,浏览器将显示“1”。

ctx.cookies.get()建议传递signed选项来验证签名,否则cookie将有篡改风险。

5.5.4 中间件

与Express类似,Koa的中间件也能访问请求对象、响应对象和next函数,通常用来执行以下任务:

· 执行逻辑代码。

· 更改请求和响应对象。

· 结束请求-响应周期。

· 调用下一个中间件。

· 错误处理。

如果一个请求流程中,任何中间件都没有输出响应,Koa中此次请求将返回404状态码(Express会将请求挂起)。

造成这种差别的原因是Express需要手动执行输出函数才可以结束请求流程,而Koa使用了async/await来进行异步编程,不需要执行回调函数,直接对ctx.body赋值即可。

Koa的中间件是一个标准的异步函数,函数签名如下:

async function middleware(ctx, next)

· ctx:上下文对象。

· next:下一个中间件。

运行完逻辑代码,将需要传递的数据挂载到ctx.state,并且调用await next()才能将请求交给下一个中间件处理。

为了更好地理解中间件的执行流程,下面使用图5-2所示的模型来说明。

图5-2

Koa的中间件模型称为“洋葱圈模型”,请求从左边进入,有序地经过中间件处理,最终从右边输出响应。

最先use的中间件在最外层,最后use的中间件在最内层。

一般的中间件会执行两次(下面会给出示例),调用next之前为第一次,也就是“洋葱左半边”这一部分,从外层向内层依次执行。当后续没有中间件时,就进入响应流程,也就是“洋葱右半边”这一部分,从内层向外层依次执行,这是第二次执行。

下面举一个例子以方便大家理解该模型。

// 导入模块
const Koa = require('koa');

// 实例化应用
const app = new Koa();

async function middleware1(ctx, next) {
    console.log('middleware1 start');
    await next();
    console.log('middlware1 end')
}
async function middleware2(ctx, next) {
    console.log('middleware2 start');
    await next();
    console.log('middlware2 end')
}
// 中间件
app.use(middleware1);
app.use(middleware2);
// 路由
app.use(async (ctx) => {
    console.log('router');
    ctx.body = 'Hello World';
});
// 监听
app.listen(10000, () => {
    console.log('listen on 10000');
});

访问http://localhost:10000时,终端输出如下:

middleware1 start
middleware2 start
router
middlware2 end
middlware1 end

也就是说,执行流程如下:

请求→中间件1开始→中间件2开始→路由处理→中间件2结束→中间件1结束→响应。

5.5.5 请求日志中间件

由于每个中间件有两次执行机会,因此相比于Express监听finish事件的方式,Koa的中间件显得更加简单。

下面我们来开发一个日志中间件,该中间件会输出请求方法、请求路径、User-Agent和处理时长。

访问http://localhost:10000,输出如下:

GET / "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML,
    like Gecko) Chrome/78.0.3904.87 Safari/537.36" 2ms

在logger中,我们在调用next()之前记录当前请求时间,调用next()后logger将不再执行,直接执行下一个中间件。本例中下一个中间件就是我们的路由了,路由执行完毕后,再来执行logger中间件next()之后的代码。

5.5.6 可配置的中间件

与Express类似,Koa也可以通过“函数返回一个新函数”来编写可配置的中间件。

下面通过配置选项来控制日志输出的内容:

访问http://localhost:10000,由于未开启user-agent来显示网页,因此终端输出如下:

GET / 2 ms

5.5.7 Cookie解析中间件

与上面的中间件不同,本节编写的中间件解析完请求Cookie之后,需要将其挂载到ctx.state中,然后在路由中间件使用。

访问http://localhost:10000,输出如下:

{
 "Idea-26226ea6": "760672c4-a540-4189-a6d4-8d71c8b48fdd",
 "grafana_session": "20e77e60cad3c900ad12cf8d6dc2c1cb"
}

有些第三方中间件处理完数据并未挂载到state下,这不影响使用,但不是Koa推荐的做法,笔者建议各位读者还是将自定义的数据挂载到ctx.state下。

5.5.8 路由函数

Koa核心并没有提供路由功能,但是可以使用一个默认的路由函数来提供响应。所有的请求都会执行该默认的路由函数。

路由函数的定义如下:

async function(ctx, next)

· ctx:请求上下文。

· next:下一个路由函数。

如果路由函数内部未使用异步逻辑,async是可以省略的。

如下示例代码是合法且能执行的:

app.use((ctx) => {
 ctx.body = 'Hello World';
});

5.5.9 多个路由函数

一般来说,路由函数只有一个,设置响应数据到ctx.body,执行完中间件后,请求终止。但是Koa支持同一个路由来使用多个路由函数。

// 导入模块
const Koa = require('koa');
const Router = require('koa-router');
// 实例化应用
const app = new Koa();
app.use(async (ctx, next) => { //路由函数1
    ctx.body = '1';
    await next();
});
app.use((ctx) => { // 路由函数2
    ctx.body = '2';
});

// 监听
app.listen(10000, () => {
    console.log('listen on 10000');
});

访问http://localhost:10000,将“2”输出到浏览器。

如果将路由函数1中的代码修改一下,将“1”输出到浏览器:

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

因为Koa的洋葱圈模型,所以先执行路由函数1,执行到next()后,进入路由函数2的执行,路由函数2设置了ctx.body为“2”,此时没有后续路由函数,执行流程将回到路由函数1,而此时路由函数1将ctx.body设置为“1”,所以浏览器最终显示“1”。

5.5.10 错误处理

Koa有着简单且优雅的中间件机制,因此编写错误处理中间件变得很简单。

和Express错误处理中间件需要放置在应用末尾不同,Koa采用了洋葱圈模型,所以Koa的错误处理中间件需要在应用的开始处挂载,这样才能将整个请求-响应周期涵盖,捕获其发生的错误。

访问http://localhost:1000时,将输出“System Error: Forbidden”。

如果调换一下路由与错误处理中间件之间的位置呢?

访问http://localhost:10000,将输出“Forbidden”,也就是说错误处理器放在后面是不生效的,最终采用了Koa自带的错误处理机制。

5.5.11 多个错误处理器

在生产环境的应用中,发生错误除了要显示错误信息给客户端之外,还需要上报错误、记录日志等操作,因此为了项目的可维护性,一般需要将错误处理中间件进行拆分,拆分为错误响应中间件、日志记录中间件等,每个中间件只负责一项工作。

访问http://localhost:10000,终端将显示“GET / Error: Forbidden”,之后客户端将收到“System Error: Forbidden”。