Fastify 系列教程:

介绍

Fastify是一个高度专注于以最少开销和强大的插件架构,为开发人员提供最佳体验的Web框架。

它受到了 Hapi 和 Express 的启发,是目前最快的 Node 框架之一。

Fastify 独特的将 JSON Schema 应用到请求时的 validation 和响应时的 serialization,作者写的 fast-json-stringify 包更是达到了 2x faster than JSON.stringify() 的神奇效果。

起步

安装

输入如下指令安装 fastify :

npm i fastify --save

第一个 fastify 应用

app.js:

const fastify = require('fastify')()

// 注册匹配到 '/' 的路由
fastify.get('/', function (request, reply) {
reply
.send({ text: 'hello fastify' })
}) // 监听 3030 端口
fastify.listen(3030, err => {
if (err) {
console.log(err)
}
})

在命令行输入 node app.js 启动服务,在浏览器访问 localhost:3030 就可以访问到了:

路由

使用 fastify.route(option) 注册一个路由:

fastify.route({
method: 'post',
url: '/home',
handler: function(request, reply){
reply.send({text: 'hello fastify'})
}
})

上述代码将匹配到访问 '/home' 的 post 请求。

详细的 option 配置如下:

method:

目前只支持 'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT''OPTIONS'。也可以是一个数组:

fastify.route({
method: ['POST', 'PUT'],
url: '/put_and_post',
handler: function(request, reply){
reply.send({hello: 'world'})
}
})

此时使用 POST 或者 PUT 方式访问 /put_and_post 都能成功响应。

注意:Fastify 只支持 application/json 内容类型,如果想解析其他类型可以查看社区插件,fastify-formbody 可以解析 x-www-form-urlencoded 请求类型,使用 fastify-multipart 处理 form-data 类型请求。或者使用 ContentTypeParser 方法自定义内容类型解析器。

url:

路由匹配的路径,也可以使用 path 代替。

所支持的 url 格式可以查看:https://github.com/delvedor/find-my-way#supported-path-formats

schema:

一个包含请求和响应模式的对象。

需要符合 JSON Schema 的格式。

  • body: 验证请求体, 必须是'POST' 或者 'PUT' 请求。
  • querystring: 验证查询字符串。可以是一个完成的 JSON Schema 对象(符合 {type: "object", properties: {...}})或者没有 typeproperties 属性,而只有查询字符串列表。
  • params: 验证 params
  • response: 过滤并生成响应的 schema ,可以提升 10-20% 的吞吐量。
  • headers: 验证请求头。

使用 schema 是 Fastify 比其他框架都快的原因所在,如何理解这个 schema 呢?大家都知道,在 JSON 里面有 string, number, object, array, null 共五中类型,不同的类型格式是不一样的,比如字符串两头需要加引号,数组两头需要加括号等等,如果不用 schema 指明 json 是何种模式的话,原生的 JSON.stringify() 会先判断字段的类型后再做相应字符串化方法。而 JSON Schema 就是来定义 json 的数据格式的,通过 JSON Schema 可以达到验证 JSON 是否符合规定的格式。而框架作者独创的使用 JSON Schema 并手写 stringify (fast-json-stringify) ,达到了 2x faster than JSON.stringify() ,也是令人敬佩不已,也是正如 贺师俊 老师评论的那样:

好了,言归正传,使用 schema 示例:

fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'number' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
console.log(request.query)
reply.send({ hello: 'world' })
}
})

当访问 http://localhost:3030/put_and_post?name=2 时可以正常得到 {"hello": "world"},同时控制台也会输入 {name: 2},注意,name=2 这条查询字符串也被自动转成了 number 类型了。

如果访问 http://localhost:3030/put_and_post?name=true 会发生什么呢?对不起,Fastify 会返回 400 错误,因为 true 不是 number 类型,而是一个字符串:

{
"error": "Bad Request",
"message": [{
"keyword":"type",
"dataPath":".name",
"schemaPath":"#/properties/name/type",
"params":{"type":"number"},
"message":"should be number"
}],
"statusCode": 400
}

如果将 reply.send({hello: 'world'}),改成 reply.send({text: 'world'}) 会发生什么呢?

客户端将不会得到 text 这个字段,因为 {text: 'world'} 不符合定义的 responese 规则,最终只会得到 {}

beforeHandler(request, reply, done):

在处理请求之前调用的函数,可以在这里处理用户认证:

fastify.route({
method: 'POST',
path: '/authentication',
beforeHandler: function(request, reply, done){
if(request.body.username === 'lavyun') {
done()
} else {
reply.send({
meg: 'Authentication failed!',
code: '1'
})
}
},
handler: function(request, reply){
reply.send({
code: '0',
msg: 'Authentication success!'
})
}
})

handler(request, reply):

用来处理请求的函数。

schemaCompiler(schema):

schemaCompiler 是一个指定 schema 编译器的方法。(用来验证 body, params, headers, querystring)。默认的 schemaCompiler 返回一个实现 ajv 接口的编译器。

比如使用 Joi:

const Joi = require('joi')

fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
},
schemaCompiler: schema => data => Joi.validate(data, schema)
})

fastify 支持类似 Express/Restify 的路由语法:

  • fastify.get(path, [options], handler)
  • fastify.head(path, [options], handler)
  • fastify.post(path, [options], handler)
  • fastify.put(path, [options], handler)
  • fastify.delete(path, [options], handler)
  • fastify.options(path, [options], handler)
  • fastify.patch(path, [options], handler)
  • fastify.all(path, [options], handler)

示例:

const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, (req, reply) => {
reply.send({ hello: 'world' })
})

Async Await

使用 async/await

fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
return processed
})

可以看到,我们没有调用 reply.send 来返回数据给用户,而是使用 return,当然你也可以使用 replay.send

fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
reply.send(processed)
})

警告:如果同时使用 returnreply.send,则后者将会被舍弃。

路由前缀

有时我们会维护两套不同版本的api,一般我们都会手动的给所有的路由加上版本号前缀,例如:v1/user

Fastify 提供了快速而智能的方式来创建不同版本的相同的api,而无需手动更改所有路由名称:

// server.js
const fastify = require('fastify')() fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
fastify.register(require('./routes/v2/users'), { prefix: '/v2' }) fastify.listen(3000)
// routes/v1/users.js
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v1)
next()
}
// routes/v2/users.js
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v2)
next()
}

因为在编译时会自动处理前缀(这也意味着性能不会受到影响),所以Fastify不会抱怨两个不同的路由使用相同的名称。

现在客户端将可以访问以下路由:

  • /v1/user
  • /v2/user

日志

日志功能默认是关闭的,如果需要启用,可以这样做:

const fastify = require('fastify')({
logger: true
}) fastify.get('/', function (req, reply) {
reply.send({ hello: 'world' })
}) // 监听 3030 端口
fastify.listen(3030, err => {
if (err) {
console.log(err)
}
})

此时,当收到请求后,会在控制台打印日志:

{
"pid":78817,
"hostname":"localhost",
"level":30,
"time":1508910898897,
"msg":"incoming request",
"reqId":1,
"req":{
"id":1,
"method":"GET",
"url":"/",
"remoteAddress":"::1",
"remotePort":57731
},
"v":1
} {
"pid":78817,
"hostname":"localhost",
"level":30,
"time":1508910898907,
"msg":"request completed",
"reqId":1,
"res":{
"statusCode":200
},
"responseTime":8.21057403087616,
"v":1
}

Fastify 的更多使用将在接下来的博客中说明。

Tips:

访问 https://lavyun.gitbooks.io/fastify/content/ 查看我翻译的 Fastify 中文文档。

访问我的个人博客查看我的最新动态 lavyun.cn

Fastify 系列教程一(路由和日志)的更多相关文章

  1. Fastify 系列教程一 (路由和日志)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) Fastify ...

  2. Fastify 系列教程二 (中间件、钩子函数和装饰器)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) 中间件 Fastify 提供了与 Express 和 Restify ...

  3. Fastify 系列教程三 (验证、序列化和生命周期)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) 验证 Fast ...

  4. Fastify 系列教程四 (求对象、响应对象和插件)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) Fastify ...

  5. Fastify 系列教程二 (中间件、钩子函数和装饰器)

    Fastify 系列教程: Fastify 系列教程一 (路由和日志) Fastify 系列教程二 (中间件.钩子函数和装饰器) Fastify 系列教程三 (验证.序列化和生命周期) Fastify ...

  6. RabbitMQ系列教程之四:路由(Routing)(转载)

    RabbitMQ系列教程之四:路由(Routing) (使用Net客户端) 在上一个教程中,我们构建了一个简单的日志系统,我们能够向许多消息接受者广播发送日志消息. 在本教程中,我们将为其添加一项功能 ...

  7. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  8. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  9. angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用

    今天我们要讲的是ng2的路由系统. 例子

随机推荐

  1. 学号:201521123116 《java程序设计》第一周学习总结

    #1. 本章学习总结了解了班级的纪律和期末成绩组成.本周重点学习了java语言的特点具有简单性和结构中立.明白了jdk是java开发人员使用的一种工具,以及编写一个java程序的所需步骤,懂得了jdk ...

  2. 201521123075 《Java程序设计》第14周学习总结

    1. 本周学习总结 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) 在自己建立的数据库上执行常见SQL语句(截图) 参 ...

  3. 数据库复用代码【c3p0配置文件、数据库连接池】

    前言 为了复用,记载一些以前写过的工具类.方法 c3p0配置文件[c3p0-config.xml] <?xml version="1.0" encoding="UT ...

  4. Markdown编辑器 简单使用

    [TOC] 一号标题 二号标题 三号标题 1 列表 2 列表 3 列表 引用 引用就是实在前面加上> 分割线 分割线就是三个* 目录 目录是[TOC],加在一个文章的最前面 粗体 和斜体 一个之 ...

  5. JAVA数据流再传递

    有一个filter类,在请求进入的时候读取了URL信息,并且读取了requestBod中的参数信息,那么在请求到达实际的控制层时,入参信息是拿不到的,对这种情况就需要数据流做再传递处理. 处理原理:使 ...

  6. [转]Xcode的快捷键及代码格式化

    Xcode比较常用的快捷键,特别是红色标注的,很常用.1. 文件CMD + N: 新文件CMD + SHIFT + N: 新项目CMD + O: 打开CMD + S: 保存CMD+OPt+S:保存所有 ...

  7. linux环境下根据文件的某一列进行去重

    momo@ubuntu:~/user/txt$ cat test.txt1 chen nihao2 chen hello3 li nimenhao4 he dajiahao5 li nimenhaom ...

  8. php字符的替换,截取,指定查找

    <?php/** * Created by 郭鹏. * User: msi * Date: 2017/9/27 * Time: 14:17 *///随机数生成器echo rand();echo ...

  9. 洗礼灵魂,修炼python(4)--从简单案列中揭示常用内置函数以及数据类型

    上一篇说到print语句,print是可以打印任何类型到屏幕上,都有哪些类型呢? 整形(int) 长整型(long) 浮点型(float) 字符型(str) 布尔型(bool) 最常见的就这几种. 在 ...

  10. 《深入浅出设计模式》读书笔记 C#版(第一章)

    原始需求和设计 事情是这样开始的,公司需要做一套程序,鸭子,设计如下: 一个鸭子父类,多个派生类,三个可override的方法. 第一次需求变更 我们要会飞的鸭子!!!!! 所以我们做了如下的更改: ...