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

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

posted @ 2017-10-25 14:15  lavyun  阅读(7788)  评论(0编辑  收藏  举报