Модуль Apache mod_proxy_ajp
| Описание: | Модуль поддержки AJP для mod_proxy
|
|---|---|
| Статус: | Расширение |
| Идентификатор модуля: | proxy_ajp_module |
| Исходный файл: | mod_proxy_ajp.c |
| Совместимость: | Доступен в версии 2.1 и более поздних версиях |
Краткое описание
Этот модуль требует службы mod_proxy. Он обеспечивает поддержку Apache JServ Protocol version 1.3 (далее — AJP13).
Таким образом, чтобы получить возможность обрабатывать протокол AJP13, на сервере должны присутствовать mod_proxy и mod_proxy_ajp.
Предупреждение
Не включайте проксирование, пока не обеспечите безопасность сервера. Открытые прокси-серверы опасны как для вашей сети, так и для Интернета в целом.
Использование
Этот модуль используется для обратного проксирования к внутреннему серверу приложений (например, Apache Tomcat) с помощью протокола AJP13. Использование аналогично обратному прокси для HTTP, но применяется префикс ajp://:
Простой обратный прокси
ProxyPass "/app" "ajp://backend.example.com:8009/app"
Такие параметры, как параметр secret Tomcat (обязательный по умолчанию начиная с Tomcat 8.5.51 и 9.0.31), можно просто добавить в качестве отдельного параметра в конце ProxyPass или BalancerMember. Этот параметр доступен в Apache HTTP Server 2.4.42 и более поздних версиях:
Простой обратный прокси с параметром secret
ProxyPass "/app" "ajp://backend.example.com:8009/app" secret=YOUR_AJP_SECRET
Также можно использовать балансировщики:
Обратный прокси с балансировщиком
<Proxy "balancer://cluster">
BalancerMember "ajp://app1.example.com:8009" loadfactor=1
BalancerMember "ajp://app2.example.com:8009" loadfactor=2
ProxySet lbmethod=bytraffic
</Proxy>
ProxyPass "/app" "balancer://cluster/app" Обратите внимание, что обычно директива ProxyPassReverse не требуется. Запрос AJP содержит исходный заголовок host, переданный прокси, и можно ожидать, что сервер приложений будет генерировать самоссылочные заголовки относительно этого узла, поэтому переписывание не требуется.
Основное исключение возникает, когда путь URL на прокси отличается от пути на внутреннем сервере. В этом случае заголовок перенаправления можно переписать относительно URL исходного узла (а не URL внутреннего сервера ajp://), например:
Перезапись проксируемого пути
ProxyPass "/apps/foo" "ajp://backend.example.com:8009/foo" ProxyPassReverse "/apps/foo" "http://www.example.com/foo"
Однако обычно лучше развернуть приложение на внутреннем сервере по тому же пути, что и на прокси, а не использовать этот подход.
Переменные окружения
Переменные окружения, имена которых имеют префикс AJP_, пересылаются исходному серверу как атрибуты запроса AJP (при этом префикс AJP_ удаляется из имени ключа).
Обзор протокола
Протокол AJP13 ориентирован на передачу пакетов. Предположительно, двоичный формат был выбран вместо более удобного для чтения простого текста из соображений производительности. Веб-сервер взаимодействует с контейнером сервлетов через TCP-соединения. Чтобы сократить ресурсоёмкий процесс создания сокетов, веб-сервер будет пытаться поддерживать постоянные TCP-соединения с контейнером сервлетов и повторно использовать соединение для нескольких циклов запроса и ответа.
После назначения соединения конкретному запросу оно не будет использоваться для других запросов, пока не завершится цикл обработки запроса. Иными словами, запросы не мультиплексируются по соединениям. Это значительно упрощает код на обоих концах соединения, хотя приводит к увеличению числа одновременно открытых соединений.
После того как веб-сервер открыл соединение с контейнером сервлетов, оно может находиться в одном из следующих состояний:
- Свободное
По этому соединению не обрабатывается ни один запрос. - Назначенное
Соединение обрабатывает конкретный запрос.
После назначения соединения для обработки конкретного запроса по нему передаётся основная информация о запросе (например, заголовки HTTP и т. д.) в сильно сжатом виде (например, распространённые строки кодируются как целые числа). Подробное описание формата приведено ниже в разделе «Структура пакета запроса». Если у запроса есть тело (content-length > 0), оно передаётся в отдельном пакете сразу после этого.
На этом этапе контейнер сервлетов, предположительно, готов начать обработку запроса. Во время обработки он может отправлять веб-серверу следующие сообщения:
- SEND_HEADERS
Отправить браузеру набор заголовков. - SEND_BODY_CHUNK
Отправить браузеру фрагмент данных тела. - GET_BODY_CHUNK
Получить дополнительные данные запроса, если они ещё не переданы полностью. Это необходимо, поскольку пакеты имеют фиксированный максимальный размер, а тело запроса может содержать произвольное количество данных (например, загруженные файлы). (Примечание: это не связано с передачей HTTP с разбиением на фрагменты.) - END_RESPONSE
Завершить цикл обработки запроса.
Каждому сообщению соответствует пакет данных с собственным форматом. Подробности см. ниже в разделе «Структуры пакетов ответа».
Базовая структура пакета
Этот протокол отчасти унаследовал особенности XDR, но во многом отличается от него (например, отсутствует выравнивание по 4 байтам).
В AJP13 для всех типов данных используется сетевой порядок байтов.
В протоколе используются четыре типа данных: байты, логические значения, целые числа и строки.
- Байт
- Один байт.
- Логическое значение
- Один байт:
1 = true,0 = false. Использование других ненулевых значений как true (то есть в стиле C) в некоторых местах может работать, но в других — нет. - Целое число
- Число в диапазоне
0 to 2^16 (32768). Хранится в 2 байтах, сначала старший байт. - Строка
- Строка переменной длины (длина ограничена значением 2^16). Сначала записывается длина в виде двух байтов, а затем строка (включая завершающий '\0'). Обратите внимание, что закодированная длина не включает завершающий '\0' — как в
strlen. В коде на Java это несколько сбивает с толку: он изобилует необычными операторами автоинкремента для пропуска этих завершающих символов. Полагаю, это было сделано, чтобы код на C мог особенно эффективно читать строки, отправляемые контейнером сервлетов: при наличии завершающего символа \0 код на C может передавать ссылки на участки одного буфера без копирования. Если бы символа \0 не было, коду на C пришлось бы копировать данные, чтобы получить строку в нужном ему представлении.
Размер пакета
Согласно большей части кода, максимальный размер пакета составляет 8 * 1024 bytes (8K). Фактическая длина пакета указывается в заголовке.
Заголовки пакетов
Пакеты, отправляемые сервером контейнеру, начинаются с 0x1234. Пакеты, отправляемые контейнером серверу, начинаются с AB (это код ASCII символа A, за которым следует код ASCII символа B). После этих первых двух байтов идёт целое число (закодированное, как описано выше), содержащее длину полезных данных. Хотя это может навести на мысль, что максимальный размер полезных данных может достигать 2^16, фактически в коде установлен максимум 8K.
| Формат пакета (сервер->контейнер) | |||||
|---|---|---|---|---|---|
| Байт | 0 | 1 | 2 | 3 | 4...(n+3) |
| Содержимое | 0x12 | 0x34 | Длина данных (n) | Данные | |
| Формат пакета (контейнер->сервер) | |||||
|---|---|---|---|---|---|
| Байт | 0 | 1 | 2 | 3 | 4...(n+3) |
| Содержимое | A | B | Длина данных (n) | Данные | |
В большинстве пакетов тип сообщения кодируется первым байтом полезных данных. Исключение составляют пакеты тела запроса, отправляемые сервером контейнеру: они передаются со стандартным заголовком пакета (0x1234 и затем длина пакета), но без кода префикса после него.
Веб-сервер может отправлять контейнеру сервлетов следующие сообщения:
| Код | Тип пакета | Значение |
| 2 | Переслать запрос | Начать цикл обработки запроса со следующими данными |
| 7 | Завершение работы | Веб-сервер просит контейнер завершить работу. |
| 8 | Проверка связи | Веб-сервер просит контейнер взять управление на себя (этап защищённого входа). |
| 10 | CPing | Веб-сервер просит контейнер быстро ответить сообщением CPong. |
| нет | Данные | Размер (2 байта) и соответствующие данные тела. |
Для обеспечения базового уровня безопасности контейнер фактически выполнит Shutdown, только если запрос поступил с той же машины, на которой он размещён.
Первый пакет Data отправляется веб-сервером сразу после Forward Request.
Контейнер сервлетов может отправлять веб-серверу следующие типы сообщений:
| Код | Тип пакета | Значение |
| 3 | Отправить фрагмент тела | Отправить фрагмент тела из контейнера сервлетов веб-серверу (а предположительно, затем и браузеру). |
| 4 | Отправить заголовки | Отправить заголовки ответа из контейнера сервлетов веб-серверу (а предположительно, затем и браузеру). |
| 5 | Завершить ответ | Обозначает конец ответа (и, следовательно, цикла обработки запроса). |
| 6 | Получить фрагмент тела | Получить дополнительные данные запроса, если они ещё не переданы полностью. |
| 9 | Ответ CPong | Ответ на запрос CPing |
Каждое из перечисленных выше сообщений имеет собственную внутреннюю структуру, описанную ниже.
Структура пакета запроса
Для сообщений типа Переслать запрос, отправляемых сервером контейнеру:
AJP13_FORWARD_REQUEST :=
prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST
method (byte)
protocol (string)
req_uri (string)
remote_addr (string)
remote_host (string)
server_name (string)
server_port (integer)
is_ssl (boolean)
num_headers (integer)
request_headers *(req_header_name req_header_value)
attributes *(attribut_name attribute_value)
request_terminator (byte) OxFFrequest_headers имеют следующую структуру:
req_header_name :=
sc_req_header_name | (string) [see below for how this is parsed]
sc_req_header_name := 0xA0xx (integer)
req_header_value := (string)attributes являются необязательными и имеют следующую структуру:
attribute_name := sc_a_name | (sc_a_req_attribute string) attribute_value := (string)
Обратите внимание, что важный заголовок — content-length, поскольку он определяет, будет ли контейнер сразу же ожидать следующий пакет.
Подробное описание элементов запроса Forward Request
Префикс запроса
Для всех запросов это значение равно 2. Подробную информацию о других кодах префиксов см. выше.
Метод
Метод HTTP, закодированный одним байтом:
| Название команды | Код |
| OPTIONS | 1 |
| GET | 2 |
| HEAD | 3 |
| POST | 4 |
| PUT | 5 |
| DELETE | 6 |
| TRACE | 7 |
| PROPFIND | 8 |
| PROPPATCH | 9 |
| MKCOL | 10 |
| COPY | 11 |
| MOVE | 12 |
| LOCK | 13 |
| UNLOCK | 14 |
| ACL | 15 |
| REPORT | 16 |
| VERSION-CONTROL | 17 |
| CHECKIN | 18 |
| CHECKOUT | 19 |
| UNCHECKOUT | 20 |
| SEARCH | 21 |
| MKWORKSPACE | 22 |
| UPDATE | 23 |
| LABEL | 24 |
| MERGE | 25 |
| BASELINE_CONTROL | 26 |
| MKACTIVITY | 27 |
Более поздние версии ajp13 передают дополнительные методы, даже если их нет в этом списке.
protocol, req_uri, remote_addr, remote_host, server_name, server_port, is_ssl
Назначение всех этих параметров достаточно очевидно. Каждый из них обязателен и передаётся для каждого запроса.
Заголовки
Структура request_headers следующая: сначала кодируется количество заголовков num_headers. Затем следуют пары имя заголовка req_header_name / значение req_header_value. Для экономии места распространённые имена заголовков кодируются как целые числа. Если имени заголовка нет в списке основных заголовков, оно кодируется обычным способом (как строка с указанием длины перед ней). Список распространённых заголовков sc_req_header_nameи соответствующих им кодов (с учётом регистра):
| Имя | Значение кода | Название кода |
| accept | 0xA001 | SC_REQ_ACCEPT |
| accept-charset | 0xA002 | SC_REQ_ACCEPT_CHARSET |
| accept-encoding | 0xA003 | SC_REQ_ACCEPT_ENCODING |
| accept-language | 0xA004 | SC_REQ_ACCEPT_LANGUAGE |
| authorization | 0xA005 | SC_REQ_AUTHORIZATION |
| connection | 0xA006 | SC_REQ_CONNECTION |
| content-type | 0xA007 | SC_REQ_CONTENT_TYPE |
| content-length | 0xA008 | SC_REQ_CONTENT_LENGTH |
| cookie | 0xA009 | SC_REQ_COOKIE |
| cookie2 | 0xA00A | SC_REQ_COOKIE2 |
| host | 0xA00B | SC_REQ_HOST |
| pragma | 0xA00C | SC_REQ_PRAGMA |
| referer | 0xA00D | SC_REQ_REFERER |
| user-agent | 0xA00E | SC_REQ_USER_AGENT |
Код Java, который считывает эти данные, получает первое двухбайтовое целое число. Если в его старшем байте обнаруживается '0xA0', целое число из второго байта используется как индекс в массиве имён заголовков. Если первый байт не равен 0xA0, предполагается, что двухбайтовое целое число задаёт длину строки, которую затем считывают.
Этот подход основан на предположении, что длина имени заголовка не превышает 0x9FFF (==0xA000 - 1), что вполне разумно, хотя и несколько произвольно.
Примечание:
Заголовокcontent-length чрезвычайно важен. Если он присутствует и имеет ненулевое значение, контейнер предполагает, что у запроса есть тело (например, запрос POST), и сразу же считывает из входного потока отдельный пакет с этим телом. Атрибуты
Атрибуты с префиксом ? (например, ?context) являются необязательными. Для каждого атрибута предусмотрен однобайтовый код типа, за которым следует его значение (строка или целое число). Их можно передавать в любом порядке (хотя код на C всегда отправляет их в порядке, указанном ниже). Для обозначения конца списка необязательных атрибутов отправляется специальный завершающий код. Список однобайтовых кодов:
| Сведения | Значение кода | Тип значения | Примечание |
| ?context | 0x01 | - | В настоящее время не реализовано |
| ?servlet_path | 0x02 | - | В настоящее время не реализовано |
| ?remote_user | 0x03 | Строка | |
| ?auth_type | 0x04 | Строка | |
| ?query_string | 0x05 | Строка | |
| ?jvm_route | 0x06 | Строка | |
| ?ssl_cert | 0x07 | Строка | |
| ?ssl_cipher | 0x08 | Строка | |
| ?ssl_session | 0x09 | Строка | |
| ?req_attribute | 0x0A | Строка | Имя (далее следует имя атрибута) |
| ?ssl_key_size | 0x0B | Целое число | |
| ?secret | 0x0C | Строка | Поддерживается начиная с версии 2.4.42 |
| are_done | 0xFF | - | request_terminator |
Код на C в настоящее время не задаёт context и servlet_path, а большая часть кода на Java полностью игнорирует передаваемые значения этих полей (и некоторые его части фактически перестанут работать, если после одного из этих кодов будет передана строка). Неизвестно, ошибка ли это, нереализованная функция или просто устаревший код, но эти поля отсутствуют с обеих сторон соединения.
Предположительно, remote_user и auth_type относятся к аутентификации на уровне HTTP и передают имя удалённого пользователя и тип аутентификации, использованной для подтверждения его личности (например, Basic или Digest).
query_string, ssl_cert, ssl_cipher, ssl_session и ssl_key_size относятся к соответствующим компонентам HTTP и HTTPS.
jvm_route используется для поддержки привязки сеансов — привязки сеанса пользователя к конкретному экземпляру Tomcat при наличии нескольких серверов с балансировкой нагрузки.
secret передаётся, когда параметр secret=secret_keyword используется в директивах ProxyPass или BalancerMember. Внутренний сервер должен поддерживать secret, и значения должны совпадать. Описание request.secret или requiredSecret приведено в документации по настройке AJP в Apache Tomcat.
Помимо этого списка основных атрибутов, с помощью кода req_attribute 0x0A можно передать любое количество других атрибутов. Сразу после каждого экземпляра этого кода передаётся пара строк, обозначающих имя и значение атрибута. Этим способом передаются значения переменных окружения.
После передачи всех атрибутов отправляется завершающий код атрибута 0xFF. Он обозначает конец списка атрибутов и, таким образом, конец пакета запроса.
Структура пакета ответа
для сообщений, которые контейнер может отправлять серверу.
AJP13_SEND_BODY_CHUNK :=
prefix_code 3
chunk_length (integer)
chunk *(byte)
chunk_terminator (byte) Ox00
AJP13_SEND_HEADERS :=
prefix_code 4
http_status_code (integer)
http_status_msg (string)
num_headers (integer)
response_headers *(res_header_name header_value)
res_header_name :=
sc_res_header_name | (string) [see below for how this is parsed]
sc_res_header_name := 0xA0 (byte)
header_value := (string)
AJP13_END_RESPONSE :=
prefix_code 5
reuse (boolean)
AJP13_GET_BODY_CHUNK :=
prefix_code 6
requested_length (integer)Подробности:
Отправка фрагмента тела
Фрагмент представляет собой двоичные данные, которые напрямую отправляются браузеру.
Отправка заголовков
Код состояния и сообщение — это обычные элементы HTTP (например, 200 и OK). Имена заголовков ответа кодируются так же, как имена заголовков запроса. Подробности о различении кодов и строк см. выше в описании header_encoding.
Коды распространённых заголовков:
| Имя | Значение кода |
| Content-Type | 0xA001 |
| Content-Language | 0xA002 |
| Content-Length | 0xA003 |
| Date | 0xA004 |
| Last-Modified | 0xA005 |
| Location | 0xA006 |
| Set-Cookie | 0xA007 |
| Set-Cookie2 | 0xA008 |
| Servlet-Engine | 0xA009 |
| Status | 0xA00A |
| WWW-Authenticate | 0xA00B |
После кода или строкового имени заголовка сразу кодируется значение заголовка.
Завершение ответа
Обозначает конец текущего цикла обработки запроса. Если флаг reuse имеет значение true (anything other than 0 in the actual C code), это TCP-соединение можно использовать для обработки новых входящих запросов. Если reuse имеет значение false (==0), соединение следует закрыть.
Получение фрагмента тела
Контейнер запрашивает дополнительные данные запроса (если тело слишком велико, чтобы поместиться в первом отправленном пакете, или если запрос передаётся фрагментами). Сервер отправит пакет тела с объёмом данных, равным наименьшему из значений request_length, максимального размера отправляемого тела (8186 (8 Kbytes - 6)) и количества байтов тела запроса, оставшихся для отправки.
Если в теле больше нет данных (то есть контейнер сервлетов пытается читать за его концом), сервер отправит пустой пакет — пакет тела с длиной полезных данных, равной 0. (0x12,0x34,0x00,0x00)
© 2018 The Apache Software Foundation
Licensed under the Apache License, Version 2.0.
https://httpd.apache.org/docs/2.4/en/mod/mod_proxy_ajp.html