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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM