Понимание алгоритмов выбора сервера Nginx и блока местоположения

Вступление

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

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

Конфигурации блоков Nginx

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

Основными блоками, которые мы будем обсуждать, являются блок * server * и блок * location *.

Блок сервера - это подмножество конфигурации Nginx, которое определяет виртуальный сервер, используемый для обработки запросов определенного типа. Администраторы часто конфигурируют несколько блоков серверов и решают, какой блок должен обрабатывать какое соединение, на основании запрошенного имени домена, порта и IP-адреса.

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

Как 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-адреса / порта.

  • Одинокий IP-адрес, который будет прослушивать порт 80 по умолчанию.

  • Одинокий порт, который будет прослушивать каждый интерфейс этого порта.

  • Путь к сокету 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 +.

Разбор директивы «имя_сервера» для выбора соответствия

Затем, чтобы дополнительно оценить запросы, которые имеют одинаково специфические директивы + listen +, Nginx проверяет заголовок запроса «Host». Это значение содержит домен или IP-адрес, который клиент фактически пытался получить.

Nginx пытается найти наилучшее соответствие для найденного значения, просматривая директиву + server_name + в каждом из блоков сервера, которые все еще являются кандидатами на выбор. Nginx оценивает их, используя следующую формулу:

  • Nginx сначала попытается найти блок сервера с + server_name +, который соответствует значению в заголовке «Host» запроса exactly. Если это найдено, связанный блок будет использоваться для обслуживания запроса. Если найдено несколько точных совпадений, используется * first *.

  • Если точное совпадение не найдено, Nginx попытается найти серверный блок с + server_name +, который совпадает с использованием начального подстановочного знака (обозначенного + * + в начале имени в конфигурации). Если он найден, этот блок будет использоваться для обслуживания запроса. Если найдено несколько совпадений, * самое длинное * совпадение будет использовано для обслуживания запроса.

  • Если не найдено совпадений с использованием начального подстановочного знака, Nginx затем ищет блок сервера с + server_name +, который совпадает с использованием конечного подстановочного знака (обозначенного именем сервера, оканчивающимся на + * + в конфигурации). Если он найден, этот блок используется для обслуживания запроса. Если найдено несколько совпадений, * самое длинное * совпадение будет использовано для обслуживания запроса.

  • Если не найдено совпадений с использованием конечного подстановочного знака, Nginx затем оценивает серверные блоки, которые определяют + server_name +, используя регулярные выражения (обозначенные + ~ + перед именем). * First * + server_name + с регулярным выражением, соответствующим заголовку «Host», будет использоваться для обслуживания запроса.

  • Если совпадения с регулярным выражением не найдено, Nginx выбирает блок сервера по умолчанию для этого IP-адреса и порта.

Каждая комбинация IP-адреса / порта имеет серверный блок по умолчанию, который будет использоваться, когда ход действий не может быть определен вышеупомянутыми методами. Для комбинированного IP-адреса / порта это будет либо первый блок в конфигурации, либо блок, который содержит опцию + default_server + как часть директивы + listen + (которая переопределяет алгоритм первого найденного). Может быть только одно объявление + default_server + для каждой комбинации IP-адрес / порт.

Примеры

Если определено + 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» «http://www.example.org [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» установлен как «http://www.example.com [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 +, использующих регулярные выражения. Соответствующее регулярное выражение first будет выбрано для ответа на запрос.

Например, если заголовок «Host» запроса установлен на «http://www.example.com [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$;

   . . .

}

Если ни один из вышеперечисленных шагов не может удовлетворить запрос, запрос будет передан на сервер default для сопоставления IP-адреса и порта.

Соответствующие блоки местоположения

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

Синтаксис блока местоположения

Прежде чем мы рассмотрим, как Nginx решает, какой блок местоположения использовать для обработки запросов, давайте рассмотрим синтаксис, который вы можете увидеть в определениях блока местоположения. Блоки местоположения находятся в пределах блоков сервера (или других блоков местоположения) и используются для принятия решения о том, как обрабатывать URI запроса (часть запроса, которая идет после имени домена или IP-адреса / порта).

Блоки местоположения обычно принимают следующую форму:

location   {

   . . .

}

«++» в вышеприведенном коде определяет, против чего Nginx должен проверять URI запроса. Наличие или отсутствие модификатора в приведенном выше примере влияет на то, как Nginx пытается сопоставить блок местоположения. Приведенные ниже модификаторы приведут к тому, что соответствующий блок местоположения будет интерпретирован следующим образом:

  • * (нет) *: Если модификаторы отсутствуют, местоположение интерпретируется как prefix match. Это означает, что указанное местоположение будет сопоставлено с началом URI запроса для определения соответствия.

  • * + = + *: Если используется знак равенства, этот блок будет считаться совпадающим, если URI запроса точно соответствует указанному местоположению.

  • * + ~ + *: Если присутствует модификатор тильды, это местоположение будет интерпретироваться как регистрозависимое совпадение регулярного выражения.

  • * + ~ * + *: Если используется модификатор тильды и звездочки, блок местоположения будет интерпретироваться как нечувствительное к регистру совпадение регулярного выражения.

  • * + ^ ~ + *: Если присутствуют модификаторы карата и тильды, и если этот блок выбран в качестве лучшего соответствия нерегулярного выражения, сопоставление регулярного выражения не будет иметь места.

Примеры, демонстрирующие синтаксис блока местоположения

В качестве примера сопоставления префиксов, следующий блок местоположения может быть выбран для ответа на URI запроса, которые выглядят как + / site +, + / site / page1 / index.html + или + / site / index.html + :

location /site {

   . . .

}

Для демонстрации точного соответствия URI запроса этот блок всегда будет использоваться для ответа на URI запроса, который выглядит как + / page1 +. Он * не * будет использоваться для ответа на URI запроса + / page1 / index.html +. Имейте в виду, что если этот блок выбран и запрос выполняется с использованием страницы индекса, внутреннее перенаправление будет выполнено в другое место, которое будет фактическим обработчиком запроса:

location = /page1 {

   . . .

}

В качестве примера местоположения, которое следует интерпретировать как регистрозависимое регулярное выражение, этот блок можно использовать для обработки запросов на + / tortoise.jpg +, но * not * для + / FLOWER.PNG +:

location ~ \.(jpe?g|png|gif|ico)$ {

   . . .

}

Блок, который допускает сопоставление без учета регистра, аналогичное приведенному выше, показан ниже. Здесь оба + / tortoise.jpg + and + / FLOWER.PNG + могут быть обработаны этим блоком:

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, объясняет в этой части алгоритма выбора в th post эту часть алгоритма выбора.

Когда оценка блока местоположения переходит в другие местоположения?

Вообще говоря, когда блок местоположения выбран для обслуживания запроса, запрос обрабатывается полностью в этом контексте с этого момента. Только выбранное местоположение и унаследованные директивы определяют, как обрабатывается запрос, без помех от соседних блоков местоположения.

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

Некоторые директивы, которые могут привести к этому типу внутреннего перенаправления:

  • индекс

  • * try_files *

  • * Переписать *

  • * Error_page *

Давайте кратко рассмотрим это.

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

В этом примере первое местоположение соответствует URI запроса «+ / точный », но для обработки запроса директива « 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 +, первое местоположение первоначально получит запрос. Он попытается найти файл с именем + blahblah + в каталоге + / var / www / main +. Если он не может его найти, он будет искать файл с именем + blahblah.html +. Затем он попытается определить, существует ли каталог с именем + blahblah / + в каталоге + / var / www / main +. В случае неудачи все эти попытки будут перенаправлены в + / fallback / index.html. Это вызовет другой поиск местоположения, который будет перехвачен вторым блоком местоположения. Это послужит файлу + / var / www / another / fallback / index.html.

Другая директива, которая может привести к пропуску блока местоположения, - это директива + rewrite +. При использовании параметра + last + с директивой + rewrite + или при отсутствии параметров вообще Nginx будет искать новое совпадающее местоположение на основе результатов перезаписи.

Например, если мы изменим последний пример, включив в него перезапись, мы увидим, что запрос иногда передается напрямую во второе местоположение, не полагаясь на директиву + 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 + будет первоначально обрабатываться первым блоком местоположения. Он будет переписан в + / hello +, и будет найден каталог. В этом случае он снова будет соответствовать первому местоположению и будет обработан + try_files +, как обычно, возможно, возвращаясь к + / fallback / index.html +, если ничего не найдено (используя внутренний перенаправление + try_files +, мы обсуждалось выше).

Однако, если сделан запрос для + / rewriteme / fallback / hello +, первый блок снова будет совпадать. Перезапись будет применена снова, на этот раз в результате получится + / fallback / hello +. Затем запрос будет отправлен из второго блока местоположения.

Ситуация связана с директивой + return + при отправке кодов состояния + 301 + или + 302 +. Разница в этом случае заключается в том, что он приводит к совершенно новому запросу в форме видимого снаружи перенаправления. Такая же ситуация может возникнуть с директивой + rewrite + при использовании флагов + redirect + или + constant +. Однако эти поиски местоположения не должны быть неожиданными, поскольку внешне видимые перенаправления всегда приводят к новому запросу.

Директива + 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, основываясь на каждом запросе клиента. Вы также сможете сказать, как будет выбран блок местоположения на основе URI запроса. В целом, зная, как Nginx выбирает различные блоки, вы сможете отслеживать контексты, которые Nginx будет применять для обработки каждого запроса.

Related