nginx, пользовательские поддомены и rewrite

    В самых разнообразных веб-проектах возникает задача организации пользовательских поддоменов «на лету». При использовании nginx приходит на помощь следующая конструкция:
    server {
        listen 80;
        server_name example.com *.example.com;
        location / {
            root   /var/www/example.com/$subdomain;
            index  index.html index.php;
        }
        set $subdomain "";
        if ($host ~* ^([a-z0-9-\.]+)\.example.com$) {
            set $subdomain $1;
        }
        if ($host ~* ^www.example.com$) {
            set $subdomain "";
        }
    }
    
    Сама по себе она не нова и упоминалась уже на хабре. Тем не менее остался не рассмотренным вопрос организации rewrite для пользовательских поддоменов.Сразу обращаю ваше внимание, что решаемая задача — организация различных rewrite для небольшого количества пользовательских поддоменов.Основная проблема применения правил rewrite для конкретного поддомена заключается в том что часто мы вынуждены использовать два if. Первый — для явного задания поддомена, второй — является частью самого правила. Например:
    if (!-e $request_filename) {
      rewrite ^(.+)$ /index.php?q=$1 last;
    }
    
    К сожалению, на даннный момент nginx не позволяет использовать ни вложенные конструкции if ни оператор «И» в них. Таким образом прийдется пойти на небольшую хитрость.Создадим в папке с конфигами nginx (у меня во FreeBSD это /usr/local/etc/nginx, в Linux, как я подозреваю, /etc/nginx) файлик checks.conf следующего содержания:
    set $f "|f";
    set $e "|e";
    set $d "|d";
    
    if (!-f $request_filename) {
        set $check $check$f;
    }
    if (!-e $request_filename) {
        set $check $check$e;
    }
    if (!-d $request_filename) {
        set $check $check$d;
    }
    set $check $subdomain$check;
    
    Что он делает? фактически — помещает в переменную $check имя текущего поддомена, дописав в конец “флаги” относительно нашего запроса (т.е. если -f истинно — допишется “|f”, если -e истинно то “|e” и т.д.)Теперь организуем использование этого файла для поддоменовДобавим в наш конфигурационный файл следующую строку:
    location / {
       root   /var/www/example.com/$subdomain;
       index  index.html index.php;
       include example.com/*.conf;
    }Создадим в папке с конфигами nginx каталог example.com. В нем будут хранится правила rewrite для каждого поддомена (по одному на файл, отсутствие файла означает что rewrite для данного поддомена не нуженВ качестве примера приведу содержимое подобного файла для поддомена docs на котором запущена DokuWiki:
    if ($subdomain = "docs") {
        rewrite ^(/)_media/(.*) $1lib/exe/fetch.php?media=$2 last;
        rewrite ^(/)_detail/(.*) $1lib/exe/detail.php?media=$2 last;
        rewrite ^(/)_export/([^/]+)/(.*) $1doku.php?do=export_$2&id=$3 last;
    }
    
    include service/checks.conf;
    
    if ($check ~* ^docs.*[f].*$) {
        rewrite ^(/)(.*)?(.*)  $1doku.php?id=$2&$3 last;
        rewrite ^(/)$ $1doku.php last;
    }
    
    Сам файл состоит из двух частей. Первую часть (до include) составляют безусловные rewrite. Мы могли бы их вынести в сам конфиг пользовательских доменов, но хранить все яйца в одной корзине в нашем случе удобнееВторая часть (после include) в работе идентична тому, что в нормалных условиях было бы записано в виде if (!-f ....). Очевидно что если бы нам необходима была проверка вида -e достаточно заменить один символ в нашем regexp.К сожалению, данный метод не позволяет решить еще одну проблему при использовании автоматических пользовательских поддоменов — а именно ограничения доступа к тем или иным папкам сайта путем базовой аутентификации (аналог .htaccess). Это связано с тем, что директива location в которой осуществляется задание подобных правил недопустима внутри оператора if.P.S. Это моя первая статья на Хабре, если есть замечания — я с радостью постараюсь их учесть

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 27

      +2
      Когда же появится пост об организации пользовательских доменов на лету? :)
        0
        Гм. Немного не понял о чем Вы. В приведенной конфигурации вам достаточно прописать одну строчку в вашем ДНС сервере и все - как только создается новая папка - новый поддомен уже доступен. Даже не надо перезагружать nginx
          0
          Да. Но что насчёт именно доменов (не сабдоменов)?
            0
            не понял немного. Т.е. я например регистрирую домен president.org.ua у Вас на сайте и он на лету становится доступным? ИМХО малореально - как минимум из-за задержек обновления DNS по всему миру+регистрация не моментальный процесс.
            Если я Вас не понял - поясните на примере :)
              0
              Задержки обновления ДНСа - это другое. Я говорю о возможности добавления домена к nginx без его перезагрузки. Создал папку "president.org.ua" и сервер уже может отвечать на запросы к этому домену.
                +1
                а в чем проблема-то. Только что проверил вот-так - работает: http://paste.org.ru/?9br60m . Т.е. пишу xanf.org.ua - сервер лезет в папку /usr/www/xanf.org.ua/
                  +1
                  тока выше добавь default сервер, ато если у тебя ненайдеться папка - будет писать ошибку
                    0
                    потерялся ваш кусок кода на paste.org.ru. Можно его как-то повторить, это именно то что я ищу.
                      0
                      server {
                          listen 80;
                          server_name *.*;
                          location / {
                              root   /var/www/$host;
                              index  index.html index.php;
                          }
                      }
                      
                        0
                        Спасибо!
                          0
                          Не заработает — попробуйте заменить server_name *.* на server_name *;

                          Писал по памяти
                • UFO just landed and posted this here
                    0
                    речь шла о создании "сторонних" доменов. Т.е. типа president.org.ua в случае если вы не владелец "org.ua" :)
                    • UFO just landed and posted this here
                • UFO just landed and posted this here
              0
              По-моему, проще парсить SERVER_NAME в php на лету, выцепляя поддомен, а в nginx'е только лишь alias * указать.
                +1
                ИМХО это два подхода к решению одной проблемы. Хотя мне больше нравится метод с nginx - т.к. в поддоменах могут обитать не только PHP-скрипты
                • UFO just landed and posted this here
                    0
                    да и почему на один и тот же скрипт. У нас на подобной системе обитают совершенно разные поддомены. Всего-то делов:
                    fastcgi_param SCRIPT_FILENAME /usr/www/example.com/$subdomain/$fastcgi_script_name;
                  +2
                  Итак, товарищи! Как все Вы прекрасно понимаете подобный способ накладывает некоторые ограничения!

                  Например, особенно неприятно, что невозможно, без квадратоколёсных велосипедов, создавать свои поддомены. Типа forum.mysite.com.

                  И что же делать, спросите Вы?

                  Так всё просто!

                  * Apache уже давно научился перечитывать конфигурационные файлы, без перезагрузки.
                  * У Apache есть отличная директива Include (http://httpd.apache.org/docs/2.0/mod/core.html#include).

                  Выносим с помощью Include место с виртуальными хостами куда нам надо. Обрабатываем скриптово. После чего выполняем команду apachectl graceful.

                  Но, можем пойти ещё дальше! Как? Да просто, дать пользователю возможность прикручивать свои домены к своему блогу (или что там у Вас?).

                  Например можно DNS записи хранить в какой-нибудь базе (MySQL, PostgreSQL etc). А туда их загонять скриптом, дописав чуток веб-интерфейс. При этом демон named можно так же заставлять перечитывать конфиги после отработки MySQL запроса, либо по расписанию в crontab.

                  Для этого надо исполнить следующие команды:
                  $ps aux | grep имя_процесса_вашего_named_демона
                  $kill -HUP

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

                  как раз собирался статью написать на эту тему но я чёто в слишкомглубоком минусе, надеюсь, из комментария тоже суть понятна.
                    0
                    Гм. Ну метод с include понятен и прост как апельсин. Про ДНС записи - зачем? Ведь давно есть формат * IN A 1.2.3.4 позволяющий завернуть все поддомены неявно описанные на один IP. Да и в предложенной мною конфигурации для создания нового поддомена автоматически ничего не надо - просто добавь воды создай папку
                      0
                      Про DNS записи я говорил для доменов пользователей (допустим хочу я к используемому сервису прикрутить свой домен второго уровня) - тут без правки DNS никак.

                      Ваш способ прост и понятен, но имеет недостаток, о котором я писал выше — геморрой с зарезервированными именами (forum, static etc). Раньше так же как и вы заворачивал все поддомены на основной а там уже разбирался. Меня такой способ больше не устраивает.
                        0
                        Да, если вы даете юзеру доменное имя вида user.example.org - то с форумом уже не выйдет (вспомним хабр с его ns1). Но я не понимаю как предложенный Вами метод помогает решать эту проблему.

                        З.Ы. Внес посильный вклад в Вашу карму
                          0
                          *тупо* а почему не выйдет? Достаточно исключить возможность создания юзера forum к примеру, и проблема решена. Нет юзера - нет проблемы. :)
                            0
                            Мы скриптом прописываем виртуальные хосты пользователей в конфиг веб-сервера.

                            pratevara, не в этом дело! дело в том, что все поддомены форвардятся в одно и тоже место (на какую-нибудь папку с блоговским движком, где уже по имени домена происходит определение того, чей это блог).

                            наилучший способ, имхо, записывать ручками поддомены пользователей в virtualhosts апачевского конфига и затем перечитывать конфиг. Только я не знаю одного, насколько долго будут перечитываться конфиги апача в случае с 10 тысячами поддоменов...
                      +3
                      На боевом шареде использую технологию подобную virtualdocroot для апача только в нгинкс для отдачи с статичного контента и проксирования динамических страниц в апач2. Данная схема работает уже около года без нареканий и переписываний логики в конфигах веб серверов.

                      Апач занимается обработкой динамических страниц а nginx статикой и проксированием динамики с возможностью распределения нагрузки (балансировки) среди апачей. (в представленных ниже конфигах балансировка не используется)

                      Рассмотрим некоторые принципы работы nginx для шаред хостинга.
                      Конфигурация -> http://trash.pronskiy.ru/shared/nginx.conf

                      Клиентские директории выглядят таким образом:
                      /home/namedomain/www.namedomain.ru/www
                      /home/namedomain/www.namedomain.ru/logs

                      Поддомены:
                      /home/namedomain/pets.namedomain.ru/www
                      /home/namedomain/pets.namedomain.ru/logs

                      Также существуют служебные поддомены для каждого домена у клиента: mysql.namedomain.ru, mail... и тд. для них в апаче прописаны виртуалхосты с директивами serveralias mysql.* и соответствующим докрутом.

                      Виртуалхост клиента -> http://trash.pronskiy.ru/shared/namedomain-apache2.conf
                      Я нарочно не стал менять домен внутри виртуалхоста. Вы можете зайти по адресу указанному в servername и протестировать работу некоторых локейшнов описаных в nginx ;-)

                      Думаю с остальным сами разберётесь.

                      P.S Задонатте кармой если помог ;-]
                        0
                        зачётный номер статьи - 47777!
                        а вообще, нжикс - прикольный, но перед сном уже не воспринимается.

                        Only users with full accounts can post comments. Log in, please.