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

    第一周学习总结 本周开始了对java的初次学习接触,Java是一门新的编程语言不同于C,由于有了c的基础,对于Java的理解和学习也相对从前学C更容易些. 也学习了Java的诞生发展以及运用包括JVN ...

  2. 存储过程重置SEQUENCE值从新开始。

    CREATE OR REPLACE PROCEDURE RESET_SEQUENCE( v_SeqName IN VARCHAR2, v_sqlcode OUT NUMBER, v_sqlerrm O ...

  3. 《Java从入门到放弃》JavaSE入门篇:文件操作

    Java中的文件操作还有点小复杂··· 不过没关系,我会把它讲得很简单,嘿嘿嘿!!! 在讲Java中的文件操作前,先了解一个概念--"流",比如我们把一个杯子的水倒到另一个同样大小 ...

  4. Python接口测试自动化说明及代码实例:含get、post、put、delete等方法

    一.接口说明文档 环境准备: 安装火狐 安装插件: httprequester https://addons.mozilla.org/en-US/firefox/addon/httprequester ...

  5. Linux第二篇【系统环境、常用命令、SSH连接、安装开发环境】

    系统环境 我们知道Windows的出色就在于它的图形界面那一块,而Linux对图形界面的支持并不是那么友好-其实我们在Windows下对图形界面进行的操作都是得装换成命令的方式的! 当然了,我们在Ub ...

  6. Spring配置属性文件

    在项目开发阶段和交付阶段数据库的连接信息往往是不同的,可以把这些信息写成属性文件,再在Spring中导入即可引用 jdbc.properties属性文件如下: jdbc.driverClassName ...

  7. javaSE(九)之泛型(Generics)

    前言 这几天分享了怎么搭建集群,这一篇给大家介绍的是泛型,在我们的很多java底层的源代码都是有很多复杂的泛型的!那什么是泛型呢? 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是 ...

  8. 自定义Django的中间件

    分析Django的生命周期,我们知道所有的http请求都要经过Django的中间件. 假如现在有一个需求,所有到达服务端的url请求都在系统中记录一条日志,该怎么做呢? Django的中间件的简介 D ...

  9. Sql Server——运用代码创建数据库及约束

    在没有学习运用代码创建数据库.表和约束之前,我们只能用鼠标点击操作,这样看起来就不那么直观(高大上)了. 在写代码前要知道在哪里写和怎么运行: 点击新建查询,然后中间的白色空白地方就是写代码的地方了. ...

  10. IIC-BUS INTERFACE

    I2C总线是由Philips公司开发的一种简单.双向二线制同步串行总线.它只需要两根线即可在连接于总线上的器件之间传送信息. To control multi-master IIC-bus opera ...