Fastify 路由

2020-02-06 15:38 更新

路由

完整定義

fastify.route(options)

路由選項(xiàng)

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

示例:

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' })
  }
})

簡(jiǎn)寫(xiě)定義

上文的路由定義帶有 Hapi 的風(fēng)格。要是偏好 Express/Restify 的寫(xiě)法,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) 會(huì)給所有支持的 HTTP 方法添加相同的處理函數(shù)。

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

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

Url 構(gòu)建

Fastify 同時(shí)支持靜態(tài)與動(dòng)態(tài)的 url。要注冊(cè)一個(gè)參數(shù)命名的路徑,請(qǐng)?jiān)趨?shù)名前加上冒號(hào)。星號(hào)表示*通配符**。 *注意,靜態(tài)路由總是在參數(shù)路由和通配符之前進(jìn)行匹配。

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

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

正則表達(dá)式路由亦被支持。但要注意,正則表達(dá)式會(huì)嚴(yán)重拖累性能!

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

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

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

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

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

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

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

多參數(shù)的路由會(huì)影響性能,所以應(yīng)該盡量使用單參數(shù),對(duì)于高頻訪問(wèn)的路由來(lái)說(shuō)更是如此。 如果你對(duì)路由的底層感興趣,可以查看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
})

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

當(dāng)然,需要的話你還是可以使用 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 鏈,在一個(gè)基于回調(diào)的 API 中被調(diào)用,你可以使用 await reply:

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

返回回復(fù)也是可行的:

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

警告:

  • 如果你同時(shí)使用 return value 與 reply.send(value),那么只會(huì)發(fā)送第一次,同時(shí)還會(huì)觸發(fā)警告日志,因?yàn)槟阍噲D發(fā)送兩次響應(yīng)。
  • 不能返回 undefined。更多細(xì)節(jié)請(qǐng)看 promise 取舍。

Promise 取舍

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

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

通過(guò)這一方法,我們便可以最小代價(jià)同時(shí)支持 回調(diào)函數(shù)風(fēng)格 以及 async-await。盡管這么做十分自由,我們還是強(qiáng)烈建議僅使用其中的一種,因?yàn)閼?yīng)用的錯(cuò)誤處理方式應(yīng)當(dāng)保持一致。

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

路由前綴

有時(shí)你需要維護(hù)同一 api 的多個(gè)不同版本。一般的做法是在所有的路由之前加上版本號(hào),例如 /v1/user。 Fastify 提供了一個(gè)快捷且智能的方法來(lái)解決上述問(wèn)題,無(wú)需手動(dòng)更改全部路由。這就是路由前綴。讓我們來(lái)看下吧:

// 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()
}

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

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

  • /v1/user
  • /v2/user

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

處理帶前綴的 / 路由

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

要改變這一行為,請(qǐng)見(jiàn)上文 prefixTrailingSlash 選項(xiàng)。

自定義日志級(jí)別

在 Fastify 中為路由里設(shè)置不同的日志級(jí)別是十分容易的。你只需在插件或路由的選項(xiàng)里設(shè)置 logLevel 為相應(yīng)的即可。

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

// 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' })
})

自定義的日志級(jí)別僅對(duì)路由生效,通過(guò) fastify.log 訪問(wèn)的全局日志并不會(huì)受到影響。

自定義日志序列化器

在某些上下文里,你也許需要記錄一個(gè)大型對(duì)象,但這在其他路由中是個(gè)負(fù)擔(dān)。這時(shí),你可以定義一些序列化器 (serializer),并將它們?cè)O(shè)置在正確的上下文之上!

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)

你可以通過(guò)上下文來(lái)繼承序列化器:

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)

配置

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

// 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)

版本

默認(rèn)

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

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' }
})

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

自定義

新建實(shí)例時(shí),可以通過(guò)設(shè)置 versioning 來(lái)自定義版本號(hào)邏輯。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)