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是一个高度专注于以最少开销和强大的插件架构,为开发人员提供最佳体验的Web框架. 它受到了 Hapi 和 Express 的启发,是目前最快的 Node 框架之一. 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. Zookeeper之Curator(1)客户端基本的创建,删除,更新,查找操作api

    Curator Framework提供了简化使用zookeeper更高级的API接口.它包涵很多优秀的特性,主要包括以下三点: 自动连接管理:自动处理zookeeper的连接和重试存在一些潜在的问题: ...

  2. Storm集群参数调整

    Supervisor 参数调整 修改${STORM_HOME}conf/storm.yaml文件内容 supervisor变更参数 slots 配置: 若storm host仅仅执行superviso ...

  3. Python初体验(一)—【配置环境变量】【变量】【input】【条件语句】【循环语句】

    写在前面的: 作为一个控制专业的女研究生,不知道每天在研究什么,但总归逃脱不了码代码的命运.之前也学习过一些C语言.C++,基础嘛,稍稍微有一些.本不想走上码农的道路,天真烂漫的过此生(白日梦过程中. ...

  4. CPU 分支预测

    去年在安宁庄的时候, 有个同事阐述了一个观点:php中的if else  在执行时考虑到效率的原因,不会按我们的代码的顺序一条一条去试,而是随机找出一个分支,执行,如果不对,再随机找到一个分支 当时由 ...

  5. 2019年北航OO第一次博客总结

    一.基于度量对程序结构的分析 1. 第一次作业 1.1 基于类的分析的度量 首先,基于类的属性个数,方法个数,每个方法的规模,每个方法的控制分支数目,类总代码规模等特征对本次作业的结构进行分析. 1. ...

  6. 剑指offer四十五之扑克牌顺子(序列是否连续)

    一.题目 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决 ...

  7. php 页面 不显示任何错误提示

    error_reporting(0); ini_set('html_errors',false); ini_set('display_errors',false);

  8. 暴力攻击 PHP 脚本 初探

    考虑下面的HTML表单: CODE: <form action="http://example.org/login.php" method="POST"& ...

  9. CentOS7.4安装Java8

    官网下载Jdk8Linux64位版本: 使用MobaXterm工具连接远程Linux系统: 上传刚才下载好的文件到远程系统下 /usr/local/ 文件夹: 先进入压缩包所在目录再输入命令解压jdk ...

  10. Gen代码解读

    源代码为: package com.test19; public class Test04 { void spin(){ int i; for(i=0;i<100;i++){ ; } i = 0 ...