Введение
Nginx – один из самых популярных веб-серверов в мире. Он успешно справляется с высокой нагрузкой при множестве одновременных клиентских подключений и может функционировать как веб-сервер, почтовый сервер или обратный прокси-сервер.
В этом руководстве мы обсудим некоторые детали, касающиеся того, как Nginx обрабатывает клиентские запросы. Понимание этих идей может помочь избежать угадываний при проектировании блоков сервера и местоположений, а также сделать обработку запросов менее предсказуемой.
Конфигурации блоков Nginx
Nginx логически делит конфигурации, предназначенные для обслуживания различного контента, на блоки, которые находятся в иерархической структуре. Каждый раз, когда поступает запрос клиента, Nginx начинает процесс определения того, какие блоки конфигурации следует использовать для обработки запроса. Этот процесс принятия решения мы и будем обсуждать в данном руководстве.
Основные блоки, о которых мы будем говорить, – это блок server и блок location.
A server block is a subset of Nginx’s configuration that defines a virtual server used to handle requests of a defined type. Administrators often configure multiple server blocks and decide which block should handle which connection based on the requested domain name, port, and IP address.
A location block lives within a server block and is used to define how Nginx should handle requests for different resources and URIs for the parent server. The URI space can be subdivided in whatever way the administrator likes using these blocks. It is an extremely flexible model.
Как Nginx определяет, какой блок сервера будет обрабатывать запрос
Поскольку Nginx позволяет администратору определить несколько блоков сервера, которые функционируют как отдельные виртуальные веб-серверы, ему необходима процедура определения того, какой из этих блоков сервера будет использоваться для удовлетворения запроса.
Он делает это через определенную систему проверок, которые используются для поиска наилучшего совпадения. Основные директивы блока сервера, которые волнуют Nginx во время этого процесса, – это директива listen
и директива server_name
.
Разбор директивы listen
для поиска возможных совпадений
Сначала Nginx рассматривает IP-адрес и порт запроса. Он сопоставляет это с директивой listen
каждого сервера, чтобы составить список блоков сервера, которые могут потенциально разрешить запрос.
Директива listen
обычно определяет IP-адрес и порт, на который будет отвечать блок сервера. По умолчанию любому блоку сервера, который не включает директиву listen
, присваиваются параметры прослушивания 0.0.0.0:80
(или 0.0.0.0:8080
, если Nginx запущен обычным, не-root пользователем). Это позволяет этим блокам отвечать на запросы с любого интерфейса на порту 80, но это значение по умолчанию не имеет большого значения в процессе выбора сервера.
Директива listen
может быть установлена на:
- Комбинацию IP-адреса/порта.
- A lone IP address which will then listen on the default port 80.
- A lone port which will listen to every interface on that port.
- Путь к сокету Unix.
Последний вариант обычно имеет значение только при передаче запросов между различными серверами.
При попытке определить, какому блоку сервера отправить запрос, Nginx сначала попытается принять решение на основе специфичности директивы listen
с использованием следующих правил:
- Nginx переводит все «неполные» директивы
listen
, заменяя отсутствующие значения их значениями по умолчанию, чтобы каждый блок мог быть оценен по его IP-адресу и порту. Некоторые примеры таких переводов:- Блок без директивы
listen
использует значение0.0.0.0:80
. - Блок, установленный на IP-адрес
111.111.111.111
без порта, становится111.111.111.111:80
- Блок, установленный на порт
8888
без IP-адреса, становится0.0.0.0:8888
- Блок без директивы
- Затем Nginx пытается собрать список блоков сервера, которые наиболее точно соответствуют запросу на основе IP-адреса и порта. Это означает, что любой блок, который функционально использует
0.0.0.0
в качестве своего IP-адреса (для сопоставления с любым интерфейсом), не будет выбран, если есть совпадающие блоки, которые перечисляют определенный IP-адрес. В любом случае порт должен соответствовать точно. - Если есть только одно самое точное совпадение, этот блок сервера будет использоваться для обслуживания запроса. Если есть несколько блоков сервера с тем же уровнем точности совпадения, Nginx начинает оценивать директиву
server_name
каждого блока сервера.
Важно понимать, что Nginx будет оценивать директиву server_name
только тогда, когда ему нужно различать блоки сервера, которые совпадают с тем же уровнем точности в директиве listen
. Например, если example.com
размещен на порту 80
у 192.168.1.10
, запрос на example.com
всегда будет обслуживаться первым блоком в этом примере, несмотря на директиву server_name
во втором блоке.
server {
listen 192.168.1.10;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}
В случае если более одного блока сервера совпадают с одинаковой точностью, следующим шагом является проверка директивы server_name
.
Анализ директивы server_name
для выбора совпадения
Затем, для дальнейшей оценки запросов с одинаково специфичными директивами listen
, Nginx проверяет заголовок Host
запроса. В этом значении содержится доменное имя или IP-адрес, который клиент действительно пытался достичь.
Nginx пытается найти наилучшее соответствие значению, найденному путем просмотра директивы server_name
в каждом из блоков сервера, которые все еще являются кандидатами на выбор. Nginx оценивает их, используя следующую формулу:
- Сначала Nginx попытается найти блок сервера с
server_name
, который точно соответствует значению в заголовкеHost
запроса полностью. Если такой блок найден, он будет использован для обслуживания запроса. Если найдено несколько точных совпадений, будет использовано первое. - Если точное совпадение не найдено, Nginx затем попытается найти блок сервера с
server_name
, совпадающим с использованием ведущего символа подстановки (указанного символом*
в начале имени в конфигурации). Если такой блок найден, он будет использован для обслуживания запроса. Если найдено несколько совпадений, будет использовано самое длинное совпадение для обслуживания запроса. - Если с использованием ведущего символа подстановки совпадение не найдено, Nginx затем ищет блок сервера с
server_name
, совпадающим с использованием завершающего символа подстановки (указанного символом*
в конце имени сервера в конфигурации). Если такой блок найден, он используется для обслуживания запроса. Если найдено несколько совпадений, будет использовано самое длинное совпадение для обслуживания запроса. - Если с использованием завершающего шаблона не найдено совпадений, то Nginx затем оценивает блоки сервера, которые определяют
server_name
с использованием регулярных выражений (указанных символом~
перед именем). Первыйserver_name
с регулярным выражением, которое совпадает с заголовком “Host”, будет использоваться для обслуживания запроса. - Если совпадений с регулярным выражением не найдено, то Nginx затем выбирает блок сервера по умолчанию для этого IP-адреса и порта.
У каждой комбинации IP-адреса и порта есть блок сервера по умолчанию, который будет использоваться, когда не удается определить дальнейшее действие с помощью вышеуказанных методов. Для комбинации IP-адреса и порта это либо первый блок в конфигурации, либо блок, который содержит опцию default_server
как часть директивы listen
(что переопределит алгоритм первого найденного). Для каждой комбинации IP-адреса и порта может быть только одно объявление default_server
.
Примеры
Если определено server_name
, который точно соответствует значению заголовка Host
, этот блок сервера выбирается для обработки запроса.
В этом примере, если заголовок Host
запроса установлен в host1.example.com
, будет выбран второй сервер:
server {
listen 80;
server_name *.example.com;
. . .
}
server {
listen 80;
server_name host1.example.com;
. . .
}
Если точное совпадение не найдено, то Nginx проверяет, есть ли server_name
с начальным подстановочным символом, который подходит. Будет выбрано самое длинное совпадение, начинающееся с подстановочного символа, чтобы выполнить запрос.
В этом примере, если запрос имел заголовок Host
www.example.org
, то будет выбран второй блок сервера:
server {
listen 80;
server_name www.example.*;
. . .
}
server {
listen 80;
server_name *.example.org;
. . .
}
server {
listen 80;
server_name *.org;
. . .
}
Если с начальным подстановочным символом совпадение не найдено, Nginx затем проверяет, существует ли совпадение, используя подстановочный символ в конце выражения. На этом этапе будет выбрано самое длинное совпадение, заканчивающееся подстановочным символом, чтобы обслужить запрос.
Например, если заголовок Host
запроса установлен на www.example.com
, то будет выбран третий блок сервера:
server {
listen 80;
server_name host1.example.com;
. . .
}
server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name www.example.*;
. . .
}
Если не найдены подстановочные символы, то Nginx затем переходит к попытке сопоставления директивы server_name
, использующей регулярные выражения. Будет выбрано первое совпадающее регулярное выражение для ответа на запрос.
Например, если заголовок Host
запроса установлен на www.example.com
, то будет выбран второй блок сервера для удовлетворения запроса:
server {
listen 80;
server_name example.com;
. . .
}
server {
listen 80;
server_name ~^(www|host1).*\.example\.com$;
. . .
}
server {
listen 80;
server_name ~^(subdomain|set|www|host1).*\.example\.com$;
. . .
}
Если ни один из вышеперечисленных шагов не может удовлетворить запрос, то запрос будет передан основному серверу для соответствующего IP-адреса и порта.
Совпадение блоков Location
Подобно процессу, который использует Nginx для выбора блока сервера, который будет обрабатывать запрос, у Nginx также есть установленный алгоритм для определения того, какой блок расположения внутри сервера использовать для обработки запросов.
Синтаксис блока расположения
Прежде чем мы рассмотрим, как Nginx определяет, какой блок расположения использовать для обработки запросов, давайте рассмотрим некоторый синтаксис, который вы можете увидеть в определениях блоков расположения. Блоки расположения находятся внутри блоков сервера (или других блоков расположения) и используются для принятия решения о том, как обрабатывать URI запроса (часть запроса, которая следует после имени домена или IP-адреса/порта).
Блоки расположения обычно имеют следующую форму:
location optional_modifier location_match {
. . .
}
Слово location_match
в приведенном выше примере определяет, что Nginx должен проверить URI запроса. Наличие или отсутствие модификатора в приведенном выше примере влияет на способ, которым Nginx пытается сопоставить блок расположения. Ниже приведены модификаторы, которые вызовут следующие интерпретации соответствующего блока расположения:
- (none): Если модификаторы отсутствуют, расположение интерпретируется как префиксное совпадение. Это означает, что указанное расположение будет сопоставляться с началом URI запроса для определения совпадения.
=
: Если используется знак равенства, этот блок будет считаться совпадением, если URI запроса точно соответствует указанному расположению.~
: Если присутствует модификатор тильды, это местоположение будет интерпретироваться как совпадение регулярного выражения с учетом регистра символов.~*
: Если используется модификатор тильды и звездочки, блок location будет интерпретироваться как сопоставление регулярного выражения без учета регистра символов.^~
: Если присутствует модификатор каретки и тильды, и если этот блок выбран как лучшее несоответствие регулярному выражению, то регулярное выражение не будет применяться.
Примеры демонстрирующие синтаксис блока location
В качестве примера префиксного сопоставления, следующий блок location может быть выбран для ответа на запросы URI, которые выглядят как /site
, /site/page1/index.html
или /site/index.html
:
location /site {
. . .
}
Для демонстрации точного совпадения с URI запросом, этот блок всегда будет использоваться для ответа на URI запроса, который выглядит как /page1
. Он не будет использоваться для ответа на URI запроса /page1/index.html
. Имейте в виду, что если этот блок выбран и запрос удовлетворен с использованием индексной страницы, будет выполнено внутреннее перенаправление на другое местоположение, которое будет фактическим обработчиком запроса:
location = /page1 {
. . .
}
В качестве примера местоположения, которое следует интерпретировать как регистрозависимое регулярное выражение, этот блок может использоваться для обработки запросов на /tortoise.jpg
, но не для /FLOWER.PNG
:
location ~ \.(jpe?g|png|gif|ico)$ {
. . .
}
A block that would allow for case-insensitive matching similar to the above is shown below. Here, both /tortoise.jpg
and /FLOWER.PNG
could be handled by this block:
location ~* \.(jpe?g|png|gif|ico)$ {
. . .
}
Наконец, этот блок предотвратит сопоставление с регулярным выражением, если будет определено, что это лучший вариант не регулярного сопоставления. Он может обрабатывать запросы на /costumes/ninja.html
:
location ^~ /costumes {
. . .
}
Как видите, модификаторы указывают, как должен интерпретироваться блок местоположения. Однако это не сообщает нам алгоритм, который использует Nginx для определения, какому блоку местоположения отправить запрос. Мы рассмотрим это далее.
Как Nginx выбирает, какое местоположение использовать для обработки запросов
Nginx выбирает местоположение, которое будет использоваться для обслуживания запроса аналогичным образом, как он выбирает блок сервера. Он проходит через процесс, который определяет лучший блок местоположения для любого данного запроса. Понимание этого процесса является ключевым требованием для возможности настройки Nginx надежно и точно.
Имея в виду типы объявлений местоположений, описанные выше, Nginx оценивает возможные контексты местоположения, сравнивая URI запроса с каждым из местоположений. Он делает это с использованием следующего алгоритма:
- Nginx начинает с проверки всех совпадений на основе префиксов (все типы местоположений, не включающие регулярное выражение). Он проверяет каждое местоположение по полному URI запроса.
- Сначала Nginx ищет точное совпадение. Если блок местоположения с использованием модификатора
=
найден точно соответствующим URI запроса, этот блок местоположения немедленно выбирается для обслуживания запроса. - Если нет точных (с модификатором
=
) совпадений блоков местоположения, Nginx переходит к оценке не точных префиксов. Он находит самый длинный совпадающий префикс для данного URI запроса, который затем оценивается следующим образом:- Если самый длинный совпадающий префикс имеет модификатор
^~
, то Nginx немедленно завершает свой поиск и выбирает это местоположение для обслуживания запроса. - Если самый длинный совпадающий префикс не использует модификатор
^~
, совпадение сохраняется Nginx на некоторое время, чтобы можно было сместить фокус поиска.
- Если самый длинный совпадающий префикс имеет модификатор
- После определения и сохранения местоположения с наибольшим совпадением префикса Nginx переходит к оценке местоположений с регулярными выражениями (как чувствительными к регистру, так и нечувствительными к регистру). Если есть какие-либо местоположения с регулярными выражениями внутри местоположения с наибольшим совпадением префикса, Nginx перемещает их вверх в свой список проверки местоположений с регулярными выражениями. Затем Nginx пытается последовательно сопоставить с местоположениями с регулярными выражениями. Первое местоположение с регулярным выражением, которое соответствует запрашиваемому URI, сразу выбирается для обслуживания запроса.
- Если не найдены местоположения с регулярными выражениями, которые соответствуют запрашиваемому URI, выбирается ранее сохраненное местоположение префикса для обслуживания запроса.
Важно понимать, что по умолчанию Nginx будет обслуживать совпадения с регулярными выражениями в приоритете перед совпадениями с префиксами. Однако он оценивает местоположения префиксов первыми, позволяя администратору переопределить эту тенденцию, указывая местоположения с использованием модификаторов =
и ^~
.
Также важно отметить, что, в то время как местоположения префиксов обычно выбираются на основе наиболее длинного и наиболее конкретного совпадения, оценка регулярного выражения прекращается, когда найдено первое совпадение местоположения. Это означает, что позиционирование в конфигурации имеет огромное значение для местоположений с регулярными выражениями.
Наконец, важно понимать, что совпадения регулярных выражений в пределах самого длинного префиксного совпадения “перепрыгивают строку”, когда Nginx оценивает регулярные выражения местоположений. Эти будут оценены, в порядке, прежде чем будут рассмотрены любые другие совпадения регулярных выражений. Максим Дунин, чрезвычайно полезный разработчик Nginx, объясняет в этом посте эту часть алгоритма выбора.
Когда Оценка Блока Местоположения Переходит к Другим Местоположениям?
В общем, когда выбирается блок местоположения для обслуживания запроса, запрос обрабатывается полностью в этом контексте с этой точки далее. Только выбранное местоположение и унаследованные директивы определяют, как обрабатывается запрос, без вмешательства из параллельных блоков местоположений.
Хотя это общее правило, которое позволит вам проектировать ваши блоки местоположений предсказуемым способом, важно понимать, что есть моменты, когда новый поиск местоположения запускается определенными директивами в выбранном местоположении. Исключения из правила “только один блок местоположения” могут иметь последствия для того, как фактически обрабатывается запрос и могут не соответствовать ожиданиям, которые вы имели при проектировании ваших блоков местоположений.
Некоторые директивы, которые могут привести к этому типу внутреннего перенаправления, включают:
- index
- try_files
- rewrite
- error_page
Давайте кратко рассмотрим это.
Директива index
всегда приводит к внутреннему перенаправлению, если она используется для обработки запроса. Точные совпадения местоположений часто используются для ускорения процесса выбора, заканчивая выполнение алгоритма немедленно. Однако, если вы создаете точное совпадение местоположения, которое является каталогом, есть большая вероятность того, что запрос будет перенаправлен на другое местоположение для фактической обработки.
В этом примере первое местоположение совпадает с URI запроса /exact
, но для обработки запроса директива index
, унаследованная блоком, инициирует внутреннее перенаправление ко второму блоку:
index index.html;
location = /exact {
. . .
}
location / {
. . .
}
В случае выше, если вам действительно нужно, чтобы выполнение оставалось в первом блоке, вам придется придумать другой метод удовлетворения запроса к каталогу. Например, вы можете установить недопустимый index
для этого блока и включить autoindex
:
location = /exact {
index nothing_will_match;
autoindex on;
}
location / {
. . .
}
Это один из способов предотвращения переключения контекста index
, но, вероятно, это не полезно для большинства конфигураций. Главным образом, точное совпадение по каталогам может быть полезным для таких вещей, как переписывание запроса (что также приводит к новому поиску местоположения).
Еще один случай, когда местоположение обработки может быть переоценено, это директива try_files
. Эта директива говорит Nginx проверить существование набора именованных файлов или каталогов. Последний параметр может быть URI, на который Nginx сделает внутреннее перенаправление.
Рассмотрим следующую конфигурацию:
root /var/www/main;
location / {
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
В приведенном выше примере, если будет сделан запрос на /blahblah
, сначала запрос попадет в первый блок location. Он попытается найти файл с именем blahblah
в каталоге /var/www/main
. Если такого файла не существует, он попытается найти файл с именем blahblah.html
. Затем он попытается проверить, существует ли каталог с именем blahblah/
внутри каталога /var/www/main
. Если все эти попытки не увенчаются успехом, произойдет перенаправление на /fallback/index.html
. Это вызовет еще один поиск блока location, который будет перехвачен вторым блоком location. Он обслужит файл /var/www/another/fallback/index.html
.
Другая директива, которая может привести к передаче выполнения блока location, – это директива rewrite
. При использовании параметра last
с директивой rewrite
, или когда не используется никакого параметра, Nginx будет искать новый соответствующий блок location на основе результатов перезаписи.
Например, если мы модифицируем последний пример, чтобы добавить перезапись, мы можем увидеть, что запрос иногда передается непосредственно во второй блок location без использования директивы try_files
:
root /var/www/main;
location / {
rewrite ^/rewriteme/(.*)$ /$1 last;
try_files $uri $uri.html $uri/ /fallback/index.html;
}
location /fallback {
root /var/www/another;
}
В приведенном выше примере запрос на /rewriteme/hello
сначала будет обработан первым блоком location. Он будет перезаписан как /hello
и будет выполнен поиск блока location. В этом случае снова будет найден первый блок location и запрос будет обработан директивой try_files
как обычно, возможно, снова перенаправляя на /fallback/index.html
, если ничего не будет найдено (используя внутреннее перенаправление try_files
, о котором мы говорили выше).
Однако, если будет сделан запрос на /rewriteme/fallback/hello
, первый блок снова будет соответствовать. Перенаправление будет применено снова, на этот раз получится /fallback/hello
. Затем запрос будет обработан из второго блока.
A related situation happens with the return
directive when sending the 301
or 302
status codes. The difference in this case is that it results in an entirely new request in the form of an externally visible redirect. This same situation can occur with the rewrite
directive when using the redirect
or permanent
flags. However, these location searches shouldn’t be unexpected, since externally visible redirects always result in a new request.
Директива error_page
может привести к внутреннему перенаправлению, аналогичному создаваемому с помощью try_files
. Эта директива используется для определения того, что должно произойти при обнаружении определенных кодов состояния. Это, вероятно, никогда не будет выполнено, если установлен try_files
, поскольку эта директива обрабатывает весь жизненный цикл запроса.
Рассмотрим этот пример:
root /var/www/main;
location / {
error_page 404 /another/whoops.html;
}
location /another {
root /var/www;
}
Каждый запрос (кроме тех, которые начинаются с /another
) будет обрабатываться первым блоком, который будет обслуживать файлы из /var/www/main
. Однако, если файл не найден (код состояния 404), произойдет внутреннее перенаправление на /another/whoops.html
, что приведет к новому поиску расположения, который в конечном итоге приведет к второму блоку. Этот файл будет обслуживаться из /var/www/another/whoops.html
.
Как видите, понимание обстоятельств, при которых Nginx инициирует новый поиск расположения, может помочь предсказать поведение при выполнении запросов.
Заключение
Понимание способов обработки клиентских запросов Nginx может значительно облегчить вашу работу в качестве администратора. Вы сможете узнать, какой серверный блок выберет Nginx на основе каждого клиентского запроса. Вы также сможете определить, как будет выбран блок location на основе URI запроса. В общем, знание способа выбора различных блоков Nginx даст вам возможность отслеживать контексты, которые Nginx применит для обслуживания каждого запроса.