---
title: "OpenResty — это улучшенный NGINX с поддержкой динамических запросов и ответов - API7.ai"
description: "OpenResty — это расширенная версия NGINX с поддержкой динамических запросов и ответов. В этой статье объясняются API для управления запросами и ответами в OpenResty, такие как \"ngx.req.set_method\", \"ngx.req.set_uri_args\", \"ngx.req.set_header\" и другие."
url: "https://api7.ai/ru/learning-center/openresty/handling-requests-responses-dynamically"
---

# OpenResty — это улучшенный NGINX с поддержкой динамических запросов и ответов

API7.ai

October 23, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

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

Хотя OpenResty является веб-сервером на основе NGINX, он принципиально отличается от NGINX: NGINX управляется статическими конфигурационными файлами, в то время как OpenResty управляется Lua API, что обеспечивает большую гибкость и программируемость.

Позвольте мне рассказать о преимуществах Lua API.

## Категории API

Во-первых, нам нужно знать, что API OpenResty разделены на следующие основные категории.

- Обработка запросов и ответов.
- Связанные с SSL.
- Общий словарь (shared dict).
- Cosocket.
- Обработка трафика на четырёх уровнях.
- Процессы и воркеры.
- Доступ к переменным и конфигурации NGINX.
- Строки, время, кодирование и другие общие функции и т.д.

Здесь я предлагаю вам также открыть документацию Lua API OpenResty и сверить её с [списком API](https://github.com/openresty/lua-nginx-module/#nginx-api-for-lua), чтобы увидеть, насколько вы можете связать это с данной категорией.

API OpenResty существуют не только в проекте lua-nginx-module, но и в проекте lua-resty-core, такие как `ngx.ssl`, `ngx.base64`, `ngx.errlog`, `ngx.process`, `ngx.re.split`, `ngx.resp.add_header`, `ngx.balancer`, `ngx.semaphore`, `ngx.ocsp` и другие API.

Для API, которые не находятся в проекте lua-nginx-module, вам нужно отдельно их подключать для использования. Например, если вы хотите использовать функцию split, вам нужно вызвать её следующим образом.

```
$ resty -e 'local ngx_re = require "ngx.re"
 local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5})
 print(res)
 '
```

Конечно, это может вас запутать: в проекте `lua-nginx-module` есть несколько API, начинающихся с `ngx.re.sub`, `ngx.re.find` и т.д. Почему только API `ngx.re.split` требует предварительного подключения перед использованием?

Как мы упоминали в предыдущей главе о `lua-resty-core`, новые API OpenResty реализованы в репозитории `lua-rety-core` с использованием FFI, поэтому неизбежно возникает ощущение фрагментации. Я надеюсь, что в будущем эта проблема будет решена путём объединения проектов `lua-nginx-module` и `lua-resty-core`.

## Запрос

Теперь давайте рассмотрим, как OpenResty обрабатывает запросы и ответы клиентов. Сначала рассмотрим API для обработки запросов, но существует более 20 API, начинающихся с `ngx.req`, так с чего же начать?

Мы знаем, что HTTP-запросы состоят из трёх частей: строка запроса, заголовки запроса и тело запроса, поэтому я представлю API в этих трёх частях.

### Строка запроса

Первая часть — это строка запроса, которая содержит метод запроса, URI и версию HTTP-протокола. В NGINX вы можете получить это значение с помощью встроенной переменной, а в OpenResty это соответствует API `ngx.var.*`. Давайте рассмотрим два примера.

- Встроенная переменная `$scheme`, которая представляет название протокола в NGINX, это либо `http`, либо `https`; в OpenResty вы можете использовать `ngx.var.scheme` для получения того же значения.
- `$request_method` представляет метод запроса, например `GET`, `POST` и т.д.; в OpenResty вы можете получить то же значение через `ngx.var.request_method`.

Вы можете посетить официальную документацию NGINX, чтобы получить полный список встроенных переменных NGINX: <http://nginx.org/en/docs/http/ngx_http_core_module.html#variables>.

Возникает вопрос: зачем OpenResty предоставляет отдельный API для строки запроса, если вы можете получить данные в строке запроса, возвращая значение переменной, такой как `ngx.var.*`?

Результат содержит множество факторов:

- Во-первых, не рекомендуется многократно читать `ngx.var` из-за низкой производительности.
- Во-вторых, из соображений удобства для программы, `ngx.var` возвращает строку, а не объект Lua. Это затрудняет обработку, когда вы получаете `args`, которые могут возвращать несколько значений.
- В-третьих, с точки зрения гибкости, большинство `ngx.var` доступны только для чтения, и только несколько переменных доступны для записи, такие как `$args` и `limit_rate`. Однако нам часто нужно изменять метод, URI и args.

Поэтому OpenResty предоставляет несколько API, специально предназначенных для манипуляции строкой запроса, которые могут перезаписывать строку запроса для последующих операций, таких как перенаправление.

Давайте рассмотрим, как получить номер версии HTTP-протокола через API. API OpenResty `ngx.req.http_version` делает то же самое, что и переменная NGINX `$server_protocol`: возвращает номер версии HTTP-протокола. Однако возвращаемое значение этого API — не строка, а числовой формат, возможные значения которого — `2.0`, `1.0`, `1.1` и `0.9`. Если результат выходит за пределы этих значений, возвращается `nil`.

Теперь рассмотрим метод получения запроса в строке запроса. Как упоминалось, роль и возвращаемое значение `ngx.req.get_method` и переменной NGINX `$request_method` одинаковы: в строковом формате.

Однако формат параметра текущего метода HTTP-запроса `ngx.req.set_method` — не строка, а встроенные числовые константы. Например, следующий код перезаписывает метод запроса на POST.

```
ngx.req.set_method(ngx.HTTP_POST)
```

Чтобы убедиться, что встроенная константа `ngx.HTTP_POST` действительно является числом, а не строкой, вы можете вывести её значение и увидеть, что вывод будет 8.

```
resty -e 'print(ngx.HTTP_POST)'
```

Таким образом, возвращаемое значение метода `get` — строка, а входное значение метода `set` — число. Это нормально, когда метод `set` передаёт запутанное значение, так как API может завершиться с ошибкой и выдать ошибку `500`. Однако в следующей логике проверки:

```
if (ngx.req.get_method() == ngx.HTTP_POST) then
    -- do something
 end
```

Такой код работает нормально, не выдаёт ошибок, и его трудно обнаружить даже во время проверки кода. Я сам однажды допустил подобную ошибку и до сих пор помню её: я уже прошёл два раунда проверки кода и неполные тестовые случаи, чтобы попытаться её покрыть. В конечном итоге аномалия в рабочей среде привела меня к этой проблеме.

Нет практического способа решить такую проблему, кроме как быть более внимательным или добавить ещё один уровень инкапсуляции. Когда вы проектируете свои бизнес-API, вы также можете учитывать и поддерживать согласованный формат параметров методов `get` и `set`, даже если это потребует некоторого снижения производительности.

Кроме того, среди методов для перезаписи строки запроса есть два API, `ngx.req.set_uri` и `ngx.req.set_uri_args`, которые можно использовать для перезаписи URI и args. Давайте рассмотрим эту конфигурацию NGINX.

```
rewrite ^ /foo?a=3? break;
```

Итак, как мы можем решить это с помощью эквивалентного Lua API? Ответ — следующие две строки кода.

```
ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo")
```

Если вы читали официальную документацию, вы обнаружите, что `ngx.req.set_uri` имеет второй параметр: `jump`, который по умолчанию равен "false". Если вы установите его как "true", это будет эквивалентно установке флага команды `rewrite` на `last`, а не на `break`, как в примере выше.

Однако мне не очень нравится конфигурация флагов команды `rewrite`, так как она нечитаема и не распознаваема, и гораздо менее интуитивна и поддерживаема, чем код.

### Заголовки запроса

Как мы знаем, заголовки HTTP-запросов имеют формат `ключ : значение`, например:

```
  Accept: text/css,*/*;q=0.1
  Accept-Encoding: gzip, deflate, br
```

В OpenResty вы можете использовать `ngx.req.get_headers` для разбора и получения заголовков запроса, а тип возвращаемого значения — таблица.

```
local h, err = ngx.req.get_headers()
  if err == "truncated" then
      -- можно выбрать игнорирование или отклонение текущего запроса
  end
  for k, v in pairs(h) do
      ...
  end
```

По умолчанию возвращаются первые 100 заголовков. Если количество превышает 100, будет выдана ошибка `truncated`, и разработчик должен решить, как с этим справиться. Вам может быть интересно, почему это сделано именно так, и я упомяну об этом позже в разделе о уязвимостях безопасности.

Однако следует отметить, что OpenResty не предоставляет специального API для получения определённого заголовка запроса, то есть нет формы `ngx.req.header['host']`. Если у вас есть такая потребность, вам нужно полагаться на переменную NGINX `$http_xxx`, чтобы достичь этого. Затем в OpenResty вы можете получить её через `ngx.var.http_xxx`.

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

```
ngx.req.set_header("Content-Type", "text/css")
ngx.req.clear_header("Content-Type")
```

Конечно, официальная документация также упоминает другие способы удаления заголовка запроса, такие как установка значения заголовка на `nil` и т.д. Однако я всё же рекомендую использовать `clear_header` для единообразия и ясности кода.

### Тело запроса

Наконец, рассмотрим тело запроса. По соображениям производительности OpenResty не читает тело запроса активно, если вы не включили директиву `lua_need_request_body` в `nginx.conf`. Кроме того, для больших тел запросов OpenResty сохраняет содержимое во временный файл на диске, поэтому весь процесс чтения тела запроса выглядит следующим образом.

```
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then
    local tmp_file = ngx.req.get_body_file()
     -- io.open(tmp_file)
     -- ...
 end
```

Этот код содержит блокирующую операцию ввода-вывода для чтения файла с диска. Вы должны настроить параметр `client_body_buffer_size` (по умолчанию 16 КБ на 64-битных системах), чтобы минимизировать блокирующие операции; вы также можете настроить `client_body_buffer_size` и `client_max_body_size` одинаковыми и обрабатывать всё полностью в памяти, в зависимости от размера вашей памяти и количества одновременных запросов.

Кроме того, тело запроса можно перезаписать. Два API `ngx.req.set_body_data` и `ngx.req.set_body_file` принимают строку и локальный файл на диске в качестве входных параметров для выполнения перезаписи тела запроса. Однако такие операции встречаются редко, и вы можете ознакомиться с документацией для получения более подробной информации.

## Ответ

После обработки запроса нам нужно отправить ответ клиенту. Как и сообщение запроса, сообщение ответа также состоит из нескольких частей: строка состояния, заголовки ответа и тело ответа. Я представлю соответствующие API в соответствии с этими тремя частями.

### Строка состояния

Основное, что нас интересует в строке состояния, — это код состояния. По умолчанию возвращаемый HTTP-код состояния — 200, что соответствует встроенной константе OpenResty `ngx.HTTP_OK`. Но в мире кода всегда обрабатываются самые исключительные случаи.

Если вы обнаружили, что запрос является злонамеренным, то вам нужно завершить запрос:

```
ngx.exit(ngx.HTTP_BAD_REQUEST)
```

Однако в кодах состояния HTTP OpenResty есть особая константа: `ngx.OK`. В случае `ngx.exit(ngx.OK)` запрос завершает текущую фазу обработки и переходит к следующей, а не возвращается непосредственно клиенту.

Конечно, вы также можете выбрать не завершать запрос, а просто перезаписать код состояния с помощью `ngx.status`, как написано ниже.

```
ngx.status = ngx.HTTP_FORBIDDEN
```

Вы можете посмотреть их в [документации](https://github.com/openresty/lua-nginx-module/#http-status-constants), если хотите узнать больше о константах кодов состояния.

### Заголовки ответа

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

```
ngx.header.content_type = 'text/plain'
ngx.header["X-My-Header"] = 'blah blah'
ngx.header["X-My-Header"] = nil -- удалить
```

Здесь `ngx.header` содержит информацию о заголовках ответа, которую можно читать, изменять и удалять.

Второй способ установки заголовков ответа — `ngx_resp.add_header` из репозитория `lua-resty-core`, который добавляет заголовок, вызывается следующим образом:

```
local ngx_resp = require "ngx.resp"
ngx_resp.add_header("Foo", "bar")
```

Разница с первым методом заключается в том, что `add_header` не перезаписывает существующее поле с тем же именем.

### Тело ответа

Наконец, рассмотрим тело ответа. В OpenResty вы можете использовать `ngx.say` и `ngx.print` для вывода тела ответа.

```
ngx.say('hello, world')
```

Функциональность этих двух API идентична, единственная разница в том, что `ngx.say` добавляет перевод строки в конце.

Чтобы избежать неэффективности конкатенации строк, `ngx.say / ngx.print` поддерживают строки и массивы в качестве параметров.

```
$ resty -e 'ngx.say({"hello", ", ", "world"})'
 hello, world
```

Этот метод пропускает конкатенацию строк на уровне Lua и оставляет её обработку функциям на C.

## Итог

Давайте подведём итог сегодняшнего материала. Мы представили API OpenResty, связанные с сообщениями запросов и ответов. Как вы можете видеть, API OpenResty более гибкие и мощные, чем директивы NGINX.

Следовательно, достаточно ли API Lua, предоставляемых OpenResty, для удовлетворения ваших потребностей при обработке HTTP-запросов? Пожалуйста, оставьте свои комментарии и поделитесь этой статьёй с коллегами и друзьями, чтобы мы могли общаться и улучшаться вместе.

Share article link

Share

Related Posts

[![OpenResty FAQ | Как OpenResty используется на практике](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | Как OpenResty используется на практике](https://api7.ai/learning-center/openresty/faq-5)

[![Часть 3: Как создать API-шлюз для микросервисов с использованием OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [Часть 3: Как создать API-шлюз для микросервисов с использованием OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [Категории API](#%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D0%B8-api)

- [Запрос](#%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81)

  - [Строка запроса](#%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0)
  - [Заголовки запроса](#%D0%B7%D0%B0%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%BA%D0%B8-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0)
  - [Тело запроса](#%D1%82%D0%B5%D0%BB%D0%BE-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0)

- [Ответ](#%D0%BE%D1%82%D0%B2%D0%B5%D1%82)

  - [Строка состояния](#%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0-%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F)
  - [Заголовки ответа](#%D0%B7%D0%B0%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%BA%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B0)
  - [Тело ответа](#%D1%82%D0%B5%D0%BB%D0%BE-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B0)

- [Итог](#%D0%B8%D1%82%D0%BE%D0%B3)
