Глубокое понимание принципа промежуточного программного обеспечения

О чем вы думаете, когда мы говорим о промежуточном программном обеспечении? Некоторые люди сказали бы, что промежуточное ПО — это перехватчик/фильтр, который можно использовать для предварительной и постобработки.

Вы правы, промежуточное ПО, по сути, предназначено для обеспечения общего контроля процесса.

Что контролировать? То есть предварительная обработка и постобработка, упомянутые выше.

Как правило, для этого мы помещаем общий код управления процессом в промежуточное ПО, например:

  • Проверка разрешения
  • Отслеживание запросов
  • Предварительная проверка параметров
  • Ведение журнала

Если бы мы реализовали промежуточное ПО, что нам нужно было бы сделать?

Во-первых, давайте проанализируем, какие функции должны быть у промежуточного программного обеспечения.

  • Может перехватывать запросы, отказ от перехвата запросов не имеет смысла.
  • Может связывать несколько общих функций управления процессом.
  • Обязательный порядок выполнения функций управления технологическим процессом.

После анализа мы можем сделать вывод, что промежуточное ПО используется для обработки отношений вызовов универсальных функций.

Его цель — обернуть бизнес-функцию и вернуть метод того же типа.

Помещая бизнес-функцию во внутреннюю часть, а затем слой за слоем оборачивая общую функцию управления процессом снаружи, все выглядит немного похоже на луковицу, поэтому ее также называют >「модель лука」.

В каждом промежуточном программном обеспечении будет три части:

  • Предварительный перехват бизнес-логики.
  • Следующая обработка.
  • Пост-перехват после завершения обработки бизнес-логики.

На следующем рисунке показан поток обработки с 3 промежуточными программами.

Здесь мы реализуем рудиментарную версию промежуточного программного обеспечения с помощью пакета http.

После запуска программы мы используем postman для доступа к http://localhost:8099, а затем устанавливаем параметр Authorizationthe в заголовке со значением foo.

После доступа к нему мы получаем следующий результат.

Просмотр клиентской печати:

[AuthHandle] token is: foo
[LogHandle] start at: 1645004016
cost 0.000103 second

Конечно, наше промежуточное ПО здесь — просто пустая оболочка, но оно ничего не делает.

Здесь мы просто моделируем эффект простой версии мидлвара, которая реализована в виде вложенных оберток, оставляя огромную проблему, что каждый раз, когда нам нужно добавить новый мидлвар, нам нужно вкладывать его вот так, что в конечном итоге привести к раздуванию кода.

Окончательный код будет выглядеть следующим образом.

middlewareA(middlewareB(middlewareC(middlewareD(business()))))

Когда промежуточного программного обеспечения становится все больше и больше, его вообще невозможно поддерживать.

Давайте проанализируем, как промежуточное ПО реализовано в фреймворке Gin.

По сравнению с глупым методом, который мы использовали выше, реализация Gin намного лучше, она использует массив функций.

Всякий раз, когда приходит запрос, Ginвыделяет Contextдля запроса, который содержит массив HandlerFuncдля обработки вместе с индексом index, который отслеживает, какие HandlerFuncобрабатываются в данный момент.

Следует отметить, что начальное значение index по умолчанию равно -1. Для индекса, равного -1`, вы можете увидеть функцию ofreset().

Почему он использует значение по умолчанию -1 здесь? Потому что в методе handleHTTPRequest() Ginвызывает функцию Next() напрямую.

Чтобы c.index++ можно было получить значение 0, то есть начать выполнение первого подстрочного мидлвара.

Конечно, вы также можете вручную выполнить 0-е промежуточное программное обеспечение в массиве промежуточного программного обеспечения по умолчанию, поэтому вам не нужно устанавливать для него значение -1, вы можете установить значение по умолчанию 0, но это не кажется таким элегантным.

Здесь следует отметить, что тип атрибута index равен int8, мы знаем, что диапазон длины int8 равен -128 ~ 127 .

Таким образом, теоретически Gin может обработать только до 127 HandlerFunc.

Но реальная ситуация такова, что можно обрабатывать только 63, давайте посмотрим на код дальше.

Gin устанавливает вложенность HandlerFunc с помощью метода use().

Обратите внимание, что в приведенном выше коде значение переменной abortIndex составляет всего 63.

При вызове функции Group() HandlerFunc будут объединены, и здесь будет определено максимальное число, и если оно превысит abortIndex, будет сообщено об ошибке.

Функция Use() очень проста, просто добавьте HandlerFunc к HandlersChain.

Как реализовать вызов операции HandlerFunc в контексте?

Ответ прост, нам достаточно вызвать функцию Next(), внутри этого метода индекс выполнения текущего контекста будет добавлен на единицу, а затем будет выполнен следующий метод `HandlerFunc`.

Преимущество этого в том, что когда стек вызовов возвращается с индексом, превышающим размер HandlersChain, он не будет выполняться повторно.

В то же время добавление каждый раз также гарантирует выполнение ордера HandlerFunc, потому что нижний слой вызывается по индексу массива.

Конкретный код выглядит следующим образом:

Здесь нам нужно объяснить, почему нам нужно выполнить c.index++ дважды.

Если есть массив из 3 элементов и функция Next() выполняется последовательно при выполнении последнего элемента, то есть когда индекс равен 2, то при отсутствии второй редакции это приведет к мертвая петля.

Потому что, когда индекс 2 является последним элементом, у него нет следующего handler(), который может быть выполнен.

Условие цикла: индекс меньше длины массива, где всегда устанавливается 2 < 3, входя в бесконечный цикл.

Итак, здесь добавьте второй c.index++, когда последний элемент будет выполнен, индекс продолжит добавлять 1, окончательное значение будет 3, и цикл завершится.

После разговора о добавлении промежуточного ПО и выполнении промежуточного ПО, следующим шагом будет реализация функции, которая должна заранее прерывать промежуточное ПО.

Например, если мне не удается выполнить регистрацию промежуточного слоя и я хочу вернуться непосредственно к промежуточному программному обеспечению, а не продолжать, мне нужно заранее прервать всю обработку промежуточного слоя.

Давайте сначала посмотрим на реализацию Gin.

Здесь вам нужно только установить индекс на максимальное значение, потому что каждый HandlerFunc будет судить об индексе при вызове метода Next(), и он не будет выполняться после того, как индекс превысит длину среза обрабатываемой функции.

Что ж, теперь, когда мы поняли концепцию дизайна Gin, давайте реализуем простую версию промежуточного программного обеспечения.

Определить структуру контекста.

Внутри него два ключевых элемента handlersи index.

handlers используется для хранения нашей общей логики оценки для каждого промежуточного программного обеспечения, что также эквивалентно послойной луковице.

index используется для хранения количества промежуточного программного обеспечения, которое мы выполняем в данный момент.

Если мы используем модель лука, это количество слоев, которые мы очищаем в данный момент.

Определить метод добавления промежуточного ПО.

Здесь все очень просто, просто добавьте функцию ожидания handlers в наше свойство контекста MyContext.

Определить метод выполнения.

После того, как у меня есть метод add, мне также нужен метод execute, который соответствует операции очистки следующего слоя лука в модели лука.

В Gin этот метод называется Next(), и здесь мы используем то же имя.

Начать использовать

Что ж, после подготовки всего вышеперечисленного приступим к тестированию.

Вывод после выполнения:

[AuthMiddleware Start]
[LogHandle] start at: 1645605404
GET handler func
cost 0.000006 second

Спасибо, что прочитали эту статью. Если вы найдете какие-либо ошибки в этой статье, пожалуйста, сообщите мне.