Поиск по сайту:

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


Введение

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

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

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

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

Основные блоки, которые мы будем обсуждать, — это блок сервера и блок местоположения.

Блок сервера — это подмножество конфигурации 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 будет оценивать директиву 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 с одинаковой специфичностью следующим шагом будет проверка директивы 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-адреса и порта.

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

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

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

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

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

    location optional_modifier location_match {
    
        . . .
    
    }
    

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

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

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

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

    location /site {
    
        . . .
    
    }
    

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

    location = /page1 {
    
        . . .
    
    }
    

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

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

    Ниже показан блок, который позволил бы выполнять сопоставление без учета регистра, подобное приведенному выше. Здесь оба /tortoise.jpg и /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, объясняет в этом посте эту часть алгоритма выбора.

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

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

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

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

      • индекс
      • пробные_файлы
      • переписать
      • страница_ошибки

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

      Директива 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, первое местоположение изначально получит запрос. Он попытается найти файл с именем 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 или permanent. Однако эти поиски местоположения не должны быть неожиданными, поскольку видимые извне перенаправления всегда приводят к новому запросу.

      Директива 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 будет применять для обслуживания каждого запроса.