路由 (Routing)

Hono 的路由非常灵活和直观。让我们来看看。

基本用法 (Basic)

  1. // HTTP 方法
  2. app.get('/', (c) => c.text('GET /'))
  3. app.post('/', (c) => c.text('POST /'))
  4. app.put('/', (c) => c.text('PUT /'))
  5. app.delete('/', (c) => c.text('DELETE /'))
  6. // 通配符
  7. app.get('/wild/*/card', (c) => {
  8. return c.text('GET /wild/*/card')
  9. })
  10. // 匹配所有 HTTP 方法
  11. app.all('/hello', (c) => c.text('Any Method /hello'))
  12. // 自定义 HTTP 方法
  13. app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
  14. // 多个方法
  15. app.on(['PUT', 'DELETE'], '/post', (c) =>
  16. c.text('PUT or DELETE /post')
  17. )
  18. // 多个路径
  19. app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
  20. c.text('Hello')
  21. )

路径参数 (Path Parameter)

  1. app.get('/user/:name', async (c) => {
  2. const name = c.req.param('name')
  3. // ...
  4. })

或者一次性获取所有参数:

  1. app.get('/posts/:id/comment/:comment_id', async (c) => {
  2. const { id, comment_id } = c.req.param()
  3. // ...
  4. })

可选参数 (Optional Parameter)

  1. // 会同时匹配 `/api/animal` 和 `/api/animal/:type`
  2. app.get('/api/animal/:type?', (c) => c.text('Animal!'))

正则匹配 (Regexp)

  1. app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => {
  2. const { date, title } = c.req.param()
  3. // ...
  4. })

包含斜杠 (Including slashes)

  1. app.get('/posts/:filename{.+\\.png}', async (c) => {
  2. //...
  3. })

链式路由 (Chained route)

  1. app
  2. .get('/endpoint', (c) => {
  3. return c.text('GET /endpoint')
  4. })
  5. .post((c) => {
  6. return c.text('POST /endpoint')
  7. })
  8. .delete((c) => {
  9. return c.text('DELETE /endpoint')
  10. })

路由分组 (Grouping)

你可以使用 Hono 实例对路由进行分组,然后通过 route 方法添加到主应用。

  1. const book = new Hono()
  2. book.get('/', (c) => c.text('List Books')) // GET /book
  3. book.get('/:id', (c) => {
  4. // GET /book/:id
  5. const id = c.req.param('id')
  6. return c.text('Get Book: ' + id)
  7. })
  8. book.post('/', (c) => c.text('Create Book')) // POST /book
  9. const app = new Hono()
  10. app.route('/book', book)

不改变 base 的分组 (Grouping without changing base)

你也可以在不改变 base 路径的情况下分组多个实例。

  1. const book = new Hono()
  2. book.get('/book', (c) => c.text('List Books')) // GET /book
  3. book.post('/book', (c) => c.text('Create Book')) // POST /book
  4. const user = new Hono().basePath('/user')
  5. user.get('/', (c) => c.text('List Users')) // GET /user
  6. user.post('/', (c) => c.text('Create User')) // POST /user
  7. const app = new Hono()
  8. app.route('/', book) // 处理 /book
  9. app.route('/', user) // 处理 /user

基础路径 (Base path)

你可以指定一个基础路径。

  1. const api = new Hono().basePath('/api')
  2. api.get('/book', (c) => c.text('List Books')) // GET /api/book

按主机名路由 (Routing with hostname)

当包含主机名时,它也能正常工作。

  1. const app = new Hono({
  2. getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'),
  3. })
  4. app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
  5. app.get('/www2.example.com/hello', (c) => c.text('hello www2'))

根据 host Header 路由 (Routing with host Header value)

如果你在 Hono 构造函数中设置了 getPath(),Hono 可以根据 host Header 进行路由。

  1. const app = new Hono({
  2. getPath: (req) =>
  3. '/' +
  4. req.headers.get('host') +
  5. req.url.replace(/^https?:\/\/[^/]+(\/[^?]*).*/, '$1'),
  6. })
  7. app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
  8. // 以下请求将匹配上述路由:
  9. // new Request('http://www1.example.com/hello', {
  10. // headers: { host: 'www1.example.com' },
  11. // })

通过这种方式,你还可以根据 User-Agent 等 Header 来改变路由。

路由优先级 (Routing priority)

处理器或中间件会按照注册顺序执行。

  1. app.get('/book/a', (c) => c.text('a')) // a
  2. app.get('/book/:slug', (c) => c.text('common')) // common
  1. GET /book/a ---> `a`
  2. GET /book/b ---> `common`

当一个处理器被执行后,流程将会停止。

  1. app.get('*', (c) => c.text('common')) // common
  2. app.get('/foo', (c) => c.text('foo')) // foo
  1. GET /foo ---> `common` // foo 不会被触发

如果你有必须执行的中间件,请将其写在处理器之前。

  1. app.use(logger())
  2. app.get('/foo', (c) => c.text('foo'))

如果你想设置一个 “fallback” 处理器,将其写在其他处理器之后。

  1. app.get('/bar', (c) => c.text('bar')) // bar
  2. app.get('*', (c) => c.text('fallback')) // fallback
  1. GET /bar ---> `bar`
  2. GET /foo ---> `fallback`

分组顺序 (Grouping ordering)

请注意,路由分组顺序错误可能难以发现。route() 函数会将第二个参数(如 three 或 two)的路由添加到自身(two 或 app)的路由中。

  1. three.get('/hi', (c) => c.text('hi'))
  2. two.route('/three', three)
  3. app.route('/two', two)
  4. export default app

这将返回 200 响应:

  1. GET /two/three/hi ---> `hi`

然而,如果顺序错误,就会返回 404。

  1. three.get('/hi', (c) => c.text('hi'))
  2. app.route('/two', two) // `two` 没有任何路由
  3. two.route('/three', three)
  4. export default app
  1. GET /two/three/hi ---> 404 Not Found