Fastify 路由

2020-02-06 15:38 更新

路由

完整定義

fastify.route(options)

路由選項

  • method:支持的 HTTP 請求方法。目前支持 'DELETE'、'GET'、'HEAD'、'PATCH'、'POST'、'PUT' 以及 'OPTIONS'。它還可以是一個 HTTP 方法的數(shù)組。
  • url:路由匹配的 url 路徑 (別名:path)。
  • schema:用于驗證請求與回復的 schema 對象。 必須符合 JSON Schema 格式。請看這里了解更多信息。body:當為 POST 或 PUT 方法時,校驗請求主體。querystring 或 query:校驗 querystring??梢允且粋€完整的 JSON Schema 對象,它包括了值為 object 的 type 屬性以及包含參數(shù)的 properties 對象,也可以僅僅是 properties 對象中的值 (見下文示例)。params:校驗 url 參數(shù)。response:過濾并生成用于響應的 schema,能幫助提升 10-20% 的吞吐量。
  • attachValidation:當 schema 校驗出錯時,將一個 validationError 對象添加到請求中,否則錯誤將被發(fā)送給錯誤處理函數(shù)。
  • onRequest(request, reply, done): 每當接收到一個請求時觸發(fā)的函數(shù)??梢允且粋€函數(shù)數(shù)組。
  • preParsing(request, reply, done): 解析請求前調(diào)用的函數(shù)。可以是一個函數(shù)數(shù)組。
  • preValidation(request, reply, done):在共享的 preValidation 鉤子之后執(zhí)行的函數(shù),在路由層進行認證等場景中會有用處??梢允且粋€函數(shù)數(shù)組。
  • preHandler(request, reply, done):處理請求之前調(diào)用的函數(shù)。可以是一個函數(shù)數(shù)組。
  • preSerialization(request, reply, payload, done):序列化之前調(diào)用的函數(shù)。可以是一個函數(shù)數(shù)組。
  • onSend(request, reply, payload, done): 響應即將發(fā)送前調(diào)用的函數(shù)??梢允且粋€函數(shù)數(shù)組。
  • onResponse(request, reply, done): 當響應發(fā)送后調(diào)用的函數(shù)。因此,在這個函數(shù)內(nèi)部,不允許再向客戶端發(fā)送數(shù)據(jù)。可以是一個函數(shù)數(shù)組。
  • handler(request, reply):處理請求的函數(shù)。
  • schemaCompiler(schema):生成校驗 schema 的函數(shù)。請看這里
  • bodyLimit:一個以字節(jié)為單位的整形數(shù),默認值為 1048576 (1 MiB),防止默認的 JSON 解析器解析超過此大小的請求主體。你也可以通過 fastify(options),在首次創(chuàng)建 Fastify 實例時全局設置該值。
  • logLevel:設置日志級別。詳見下文。
  • logSerializers:設置當前路由的日志序列化器。
  • config:存放自定義配置的對象。
  • version:一個符合語義化版本控制規(guī)范 (semver) 的字符串。示例。 prefixTrailingSlash:一個字符串,決定如何處理帶前綴的 / 路由。both (默認值):同時注冊 /prefix 與 /prefix/。slash:只會注冊 /prefix/。no-slash:只會注冊 /prefix。request 的相關內(nèi)容請看 請求一文。reply 請看回復一文。

示例:

fastify.route({
  method: 'GET',
  url: '/',
  schema: {
    querystring: {
      name: { type: 'string' },
      excitement: { type: 'integer' }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

簡寫定義

上文的路由定義帶有 Hapi 的風格。要是偏好 Express/Restify 的寫法,F(xiàn)astify 也是支持的: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)

示例:

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }
}
fastify.get('/', opts, (request, reply) => {
  reply.send({ hello: 'world' })
})

fastify.all(path, [options], handler) 會給所有支持的 HTTP 方法添加相同的處理函數(shù)。

處理函數(shù)還可以寫到 options 對象里:

const opts = {
  schema: {
    response: {
      200: {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  },
  handler (request, reply) {
    reply.send({ hello: 'world' })
  }
}
fastify.get('/', opts)
注:假如同時在 options 和簡寫方法的第三個參數(shù)里指明了處理函數(shù),將會拋出重復的 handler 錯誤。

Url 構(gòu)建

Fastify 同時支持靜態(tài)與動態(tài)的 url。要注冊一個參數(shù)命名的路徑,請在參數(shù)名前加上冒號。星號表示*通配符**。 *注意,靜態(tài)路由總是在參數(shù)路由和通配符之前進行匹配。

// 參數(shù)路由
fastify.get('/example/:userId', (request, reply) => {}))
fastify.get('/example/:userId/:secretToken', (request, reply) => {}))

// 通配符
fastify.get('/example/*', (request, reply) => {}))

正則表達式路由亦被支持。但要注意,正則表達式會嚴重拖累性能!

// 正則表達的參數(shù)路由
fastify.get('/example/:file(^\\d+).png', (request, reply) => {}))

你還可以在同一組斜杠 ("/") 里定義多個參數(shù)。就像這樣:

fastify.get('/example/near/:lat-:lng/radius/:r', (request, reply) => {}))

使用短橫線 ("-") 來分隔參數(shù)。

最后,同時使用多參數(shù)和正則表達式也是允許的。

fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', (request, reply) => {}))

在這個例子里,任何未被正則匹配的符號均可作為參數(shù)的分隔符。

多參數(shù)的路由會影響性能,所以應該盡量使用單參數(shù),對于高頻訪問的路由來說更是如此。 如果你對路由的底層感興趣,可以查看find-my-way。

Async Await

你是 async/await 的使用者嗎?我們?yōu)槟憧紤]了一切!

fastify.get('/', options, async function (request, reply) {
  var data = await getData()
  var processed = await processData(data)
  return processed
})

如你所見,我們不再使用 reply.send 向用戶發(fā)送數(shù)據(jù),只需返回消息主體就可以了!

當然,需要的話你還是可以使用 reply.send 發(fā)送數(shù)據(jù)。

fastify.get('/', options, async function (request, reply) {
  var data = await getData()
  var processed = await processData(data)
  reply.send(processed)
})

假如在路由中,reply.send() 脫離了 promise 鏈,在一個基于回調(diào)的 API 中被調(diào)用,你可以使用 await reply:

fastify.get('/', options, async function (request, reply) {
  setImmediate(() => {
    reply.send({ hello: 'world' })
  })
  await reply
})

返回回復也是可行的:

fastify.get('/', options, async function (request, reply) {
  setImmediate(() => {
    reply.send({ hello: 'world' })
  })
  return reply
})

警告:

  • 如果你同時使用 return value 與 reply.send(value),那么只會發(fā)送第一次,同時還會觸發(fā)警告日志,因為你試圖發(fā)送兩次響應。
  • 不能返回 undefined。更多細節(jié)請看 promise 取舍。

Promise 取舍

假如你的處理函數(shù)是一個 async 函數(shù),或返回了一個 promise,請注意一種必須支持回調(diào)函數(shù)和 promise 控制流的特殊情況:如果 promise 被 resolve 為 undefined,請求會被掛起,并觸發(fā)一個錯誤日志。

  1. 如果你想使用 async/await 或 promise,但通過 reply.send 返回值:別 return 任何值。別忘了 reply.send。
  2. 如果你想使用 async/await 或 promise:別使用 reply.send。別返回 undefined。

通過這一方法,我們便可以最小代價同時支持 回調(diào)函數(shù)風格 以及 async-await。盡管這么做十分自由,我們還是強烈建議僅使用其中的一種,因為應用的錯誤處理方式應當保持一致。

注意:每個 async 函數(shù)各自返回一個 promise 對象。

路由前綴

有時你需要維護同一 api 的多個不同版本。一般的做法是在所有的路由之前加上版本號,例如 /v1/user。 Fastify 提供了一個快捷且智能的方法來解決上述問題,無需手動更改全部路由。這就是路由前綴。讓我們來看下吧:

// 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, done) {
  fastify.get('/user', handler_v1)
  done()
}
// routes/v2/users.js
module.exports = function (fastify, opts, done) {
  fastify.get('/user', handler_v2)
  done()
}

在編譯時 Fastify 自動處理了前綴,因此兩個不同路由使用相同的路徑名并不會產(chǎn)生問題。(這也意味著性能一點兒也不受影響!)。

現(xiàn)在,你的客戶端就可以訪問下列路由了:

  • /v1/user
  • /v2/user

根據(jù)需要,你可以多次設置路由前綴,它也支持嵌套的 register 以及路由參數(shù)。 請注意,當使用了 fastify-plugin 時,這一選項是無效的。

處理帶前綴的 / 路由

根據(jù)前綴是否以 / 結(jié)束,路徑為 / 的路由的匹配模式有所不同。舉例來說,前綴為 /something/ 的 / 路由只會匹配 something,而前綴為 /something 則會匹配 /something 和 /something/。

要改變這一行為,請見上文 prefixTrailingSlash 選項。

自定義日志級別

在 Fastify 中為路由里設置不同的日志級別是十分容易的。你只需在插件或路由的選項里設置 logLevel 為相應的即可。

要注意的是,如果在插件層面上設置了 logLevel,那么 setNotFoundHandler 和 setErrorHandler 也會受到影響。

// server.js
const fastify = require('fastify')({ logger: true })

fastify.register(require('./routes/user'), { logLevel: 'warn' })
fastify.register(require('./routes/events'), { logLevel: 'debug' })

fastify.listen(3000)

你也可以直接將其傳給路由:

fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
  reply.send({ hello: 'world' })
})

自定義的日志級別僅對路由生效,通過 fastify.log 訪問的全局日志并不會受到影響。

自定義日志序列化器

在某些上下文里,你也許需要記錄一個大型對象,但這在其他路由中是個負擔。這時,你可以定義一些序列化器 (serializer),并將它們設置在正確的上下文之上!

const fastify = require('fastify')({ logger: true })
fastify.register(require('./routes/user'), { 
  logSerializers: {
    user: (value) => `My serializer one - ${value.name}`
  } 
})
fastify.register(require('./routes/events'), {
  logSerializers: {
    user: (value) => `My serializer two - ${value.name} ${value.surname}`
  }
})
fastify.listen(3000)

你可以通過上下文來繼承序列化器:

const fastify = Fastify({ 
  logger: {
    level: 'info',
    serializers: {
      user (req) {
        return {
          method: req.method,
          url: req.url,
          headers: req.headers,
          hostname: req.hostname,
          remoteAddress: req.ip,
          remotePort: req.connection.remotePort
        }
      }
    }
  } 
})
fastify.register(context1, { 
  logSerializers: {
    user: value => `My serializer father - ${value}`
  } 
})
async function context1 (fastify, opts) {
  fastify.get('/', (req, reply) => {
    req.log.info({ user: 'call father serializer', key: 'another key' })
    // 打印結(jié)果: { user: 'My serializer father - call father  serializer', key: 'another key' }
    reply.send({})
  })
}
fastify.listen(3000)

配置

注冊一個新的處理函數(shù),你可以向其傳遞一個配置對象,并在其中使用它。

// server.js
const fastify = require('fastify')()

function handler (req, reply) {
  reply.send(reply.context.config.output)
}

fastify.get('/en', { config: { output: 'hello world!' } }, handler)
fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)

fastify.listen(3000)

版本

默認

需要的話,你可以提供一個版本選項,它允許你為同一個路由聲明不同的版本。版本號請遵循 semver 規(guī)范。Fastify 會自動檢測 Accept-Version header,并將請求分配給相應的路由 (當前尚不支持 semver 規(guī)范中的 advanced ranges 與 pre-releases 語法)。請注意,這一特性會降低路由的性能。

fastify.route({
  method: 'GET',
  url: '/',
  version: '1.2.0',
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

fastify.inject({
  method: 'GET',
  url: '/',
  headers: {
    'Accept-Version': '1.x' // 也可以是 '1.2.0' 或 '1.2.x'
  }
}, (err, res) => {
  // { hello: 'world' }
})

如果你聲明了多個擁有相同主版本或次版本號的版本,F(xiàn)astify 總是會根據(jù) Accept-Version header 的值選擇最兼容的版本。假如請求未帶有 Accept-Version header,那么將返回一個 404 錯誤。

自定義

新建實例時,可以通過設置 versioning 來自定義版本號邏輯。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號