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. 201521123079 《Java程序设计》第1周学习总结

    1. 本周学习总结 了解学习了JAVA的开发环境的基础内容以及JDK,JRE等,学会用eclipse编写简单的代码 2. 书面作业 Q1.为什么java程序可以跨平台运行?执行java程序的步骤是什么 ...

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

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  3. eclipse: eclipse导入工程出现大红叹号

    总结: 问题原因:工程中classpath中指向的包路径错误 解决办法:到BUILDPATH CONFIG````中,liberaies中 出现红色叉号的包为路径错误的包.到classpath中修改相 ...

  4. mysql数据库-初始化sql建库建表-关联查询投影问题

    下面是一个简易商城的几张表的创建方式 drop database if exists shop ; create database shop CHARACTER SET 'utf8' COLLATE ...

  5. C#抓取数据、正则表达式+线程池初步运用

    去年底用 多线程+HtmlAgilityPack.dll 写了一个抓取“慧聪网” 公司信息的小程序,代码惨不忍赌.好在能抓到数据,速度也能让人忍受就很久没管了. 最近这段时间把这个小程序发给同事看着玩 ...

  6. SimpleRpc-系统边界以及整体架构

    系统边界 什么是系统边界?系统边界就是在系统设计之初,对系统所要实现的功能进行界定,不乱添加,不多添加.这么做的好处就是,系统简单明了,主旨明确,方便开发和用户使用.举个例子,一个自动售货机的本职工作 ...

  7. jz2440重新分区

    在购买开发板的时候,板子上已经烧写好了bootloader.内核和文件系统.但是在具体使用时,发现板子上划分的内核分区只有2M,但是我编译出来的内核大于2M,于是将内核烧写到nandflash上面时会 ...

  8. Java线程池带图详解

    线程池作为Java中一个重要的知识点,看了很多文章,在此以Java自带的线程池为例,记录分析一下.本文参考了Java并发编程:线程池的使用.Java线程池---addWorker方法解析.线程池.Th ...

  9. CentOS 7安装squid代理服务器

    Squid,一个高性能的代理缓存服务器,支持FTP.gopher.HTTP协议. Squid,一个缓存Internet 数据的软件,其接收用户的下载申请(作为代理服务器),并自动处理所下载的数据,并返 ...

  10. bzoj3713 [PA2014]Iloczyn|暴力(模拟)

    斐波那契数列的定义为:k=0或1时,F[k]=k:k>1时,F[k]=F[k-1]+F[k-2].数列的开头几项为0,1,1,2,3,5,8,13,21,34,55,-你的任务是判断给定的数字能 ...