Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Middleware

Middleware — промежуточный обработчик: код, через который проходит запрос до контроллера и ответ после него. В нём удобно делать общие проверки — «вошёл ли пользователь», «не заблокирован ли он» — и вести журнал запросов, не повторяя код в каждом контроллере.

Создание

node ace make:middleware log_requests

Команда спросит, в какой стек добавить middleware, — выберите named. Появится файл app/middleware/log_requests_middleware.ts:

// app/middleware/log_requests_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
 
export default class LogRequestsMiddleware {
  async handle(ctx: HttpContext, next: NextFn) {
    const start = Date.now()
    const output = await next()
    console.log(`${ctx.request.method()} ${ctx.request.url()} — ${Date.now() - start} мс`)
    return output
  }
}

Код до await next() выполняется перед контроллером, код после — когда ответ уже готов. Если не вызвать next(), запрос дальше не пойдёт.

Регистрация

Все middleware перечислены в start/kernel.ts. Именованные — в объекте middleware:

// start/kernel.ts
export const middleware = router.named({
  guest: () => import('#middleware/guest_middleware'),
  auth: () => import('#middleware/auth_middleware'),
  logRequests: () => import('#middleware/log_requests_middleware'),
})

Команда make:middleware уже дописала последнюю строку. Подключите middleware к маршруту:

// start/routes.ts
import { middleware } from '#start/kernel'
 
router.get('/posts', [PostsController, 'index']).use(middleware.logRequests())

После каждого запроса к /posts в терминале сервера появится строка вроде GET /posts — 12 мс.

Три стека

СтекГде регистрируетсяКогда работает
Серверserver.use([...])На каждый запрос, даже без маршрута
Роутерrouter.use([...])На каждый найденный маршрут
Именованныеrouter.named({...})Только там, где подключены через .use()

Защита группы маршрутов

Чаще всего именованные middleware подключают к группе. Все страницы управления статьями доступны только вошедшим:

// start/routes.ts
router
  .group(() => {
    router.get('/posts/create', [PostsController, 'create']).as('posts.create')
    router.post('/posts', [PostsController, 'store']).as('posts.store')
  })
  .use(middleware.auth())

Гость, открывший /posts/create, попадёт на страницу входа — адрес для перенаправления задан в app/middleware/auth_middleware.ts свойством redirectTo.

Пользователь на каждой странице

Чтобы auth.user был известен и на публичных страницах, нужен middleware, который просто проверяет сессию. Создайте его командой node ace make:middleware silent_auth и выберите стек router:

// app/middleware/silent_auth_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
 
export default class SilentAuthMiddleware {
  async handle(ctx: HttpContext, next: NextFn) {
    await ctx.auth.check()
    return next()
  }
}

check не выбрасывает ошибку для гостей, а только заполняет auth.user, если пользователь вошёл. Команда добавит middleware в стек роутера после инициализации auth:

// start/kernel.ts
router.use([
  () => import('@adonisjs/core/bodyparser_middleware'),
  () => import('@adonisjs/session/session_middleware'),
  () => import('@adonisjs/shield/shield_middleware'),
  () => import('@adonisjs/auth/initialize_auth_middleware'),
  () => import('#middleware/silent_auth_middleware'),
])

Теперь шапка блога показывает имя пользователя на любой странице.

Частая ошибка

Перед next() забыли await. Тогда код «после» выполняется раньше, чем контроллер закончил работу: замер времени показывает 0 мс, а ошибки контроллера проходят мимо middleware. Всегда пишите const output = await next() и возвращайте output, как в примере.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.