Пользователь
–0,6
рейтинг
4 апреля 2012 в 23:34

Разработка → Мой вариант .htaccess

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

Вашему вниманию мой вгляд на правила обработки URL с объяснениями и коментариями «почему так?».

Сперва логика


Объясню сперва логику:
1) все страницы имеют .html окончания.
2) все языки для страниц имеют вид pagename.en.html или pagename.html для языка по умолчанию. Никто, конечно, не запрещает иметь ссылки, где язык идёт вначале как /en/
3) «входной» скрипт только один в docroot.
4) Разрешены запросы на другие скрипты только в docroot
5) Соглашение по определению окончаний в url:
# site.com/
# site.com/index -> site.com/
# site.com -> site.com/
# site.com/file/ -> site.com/file.html
# site.com/file -> site.com/file.html
# site.com/dir/file ->site.com/dir/file.html
# site.com/dir/file/ -> site.com/dir/file.html
Но это можно менять.


Структура .htaccess


Теперь перейдём к самой структуре .htaccess. Замечу ещё, что будет работать только для апачей версий 2.x и старше.

Сперва полностью код:
DirectoryIndex index index.html
DirectorySlash off
Options -Indexes -MultiViews
# Rules
# site.com/
# site.com/index -> site.com
# site.com	-> site.com/
# site.com/file/ -> site.com/file.html
# site.com/file  -> site.com/file.html
# site.com/dir/file ->site.com/dir/file.html
# site.com/dir/file/ -> site.com/dir/file.html
# no ending slashes

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_URI} \.(css|jpg|gif|png|zip|rar|doc|xls|js|tif|tiff|docx|xlsx|ico)$|test\.php$
	RewriteRule ^(.*)$ $1 [L,QSA]

# nothing to do there in subrequests
RewriteCond %{ENV:NS}	!=1
RewriteCond %{IS_SUBREQ} =true
	RewriteRule (.*) $1 [L,QSA]
#do NS=0?


RewriteCond %{REQUEST_URI} ^/index$ [OR]
RewriteCond %{REQUEST_URI} ^/index[.]+(\w+)$
	RewriteRule . / [R=301,L]

# remove trailing slashes
# if want external redirect use correct external redir [R=301,L] or [R=301] for correct internal or simple redir [L]
RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} (.*)/$
	RewriteRule . %1.html [R=301,L,E=NS:1,QSA]

# if whants .html endings
RewriteCond %{REQUEST_URI} !^(.+)\.(html|php)$
	RewriteRule . %{REQUEST_URI}.html [R=301,L]

# fix multidots in endings (missed language) index..html instead of index.en.html
RewriteCond %{REQUEST_URI} ^(.+)\.\.+(\w+)$
	RewriteRule . %1.%2 [R=301,L]
# otherways
#RewriteCond %{REQUEST_URI} (.+)\.(html|php)$
#	RewriteRule . %1 [R=301,L]

# any php filename in root dir
# this makes secure loses
RewriteCond %{REQUEST_URI} ^[\w\-.]+$
RewriteCond %{REQUEST_FILENAME} (.*)\.(html|php)$
RewriteCond %1.php -s [OR]
RewriteCond %1.html -s
	RewriteRule . %1.%2 [L,QSA]

RewriteRule (.*) entry.php?URI=$1 [L,QSA]
#


Разбор полёта


Теперь, разберём построчно.

DirectoryIndex index index.html
DirectorySlash off
Options -Indexes -MultiViews

Сразу важный момент: выключена автоматическая подстановка слеша в конец и выключен MultiViews (с ним работать не будет).

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_URI} \.(css|jpg|gif|png|zip|rar|doc|xls|js|tif|tiff|docx|xlsx|ico)$|test\.php$
    RewriteRule ^(.*)$ $1 [L,QSA]


Третья строчка проверяет на статические файлы — их пропускаем не меняя запрос. Возможно, стоило бы сделать проверку на наличие файла, но оставим это дело механизму 404. Последний |test\.php$ сделан для различных тестовых файлов, но на продакшене это дело надо убирать.

# nothing to do there in subrequests
RewriteCond %{ENV:NS}	!=1
RewriteCond %{IS_SUBREQ} =true
    RewriteRule (.*) $1 [L,QSA]
#do NS=0?

Самая важная часть — так как идёт преобразование расширений (далее по коду), то скрипт будет уходить всегда в подзапрос и может уйти в бесконечный цикл. Для того, чтобы этого не произошло, ловим начало подзапросов и отправляем на уже исправленный «входной» скрипт текущий запрос по URL. Это можно посмотреть включив rewrite_log в апаче.

RewriteCond %{REQUEST_URI} ^/index$ [OR]
RewriteCond %{REQUEST_URI} ^/index[.]+(\w+)$
    RewriteRule . / [R=301,L]

Все попытки попасть на `/index' или `index.html' будут перенаправлены на URL `/'.

# remove trailing slashes
# if want external redirect use correct external redir [R=301,L] or [R=301] for correct internal or simple redir [L]
RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} (.*)/$
    RewriteRule . %1.html [R=301,L,E=NS:1,QSA]

Решает одну из частей «соглашения»: убирает завершающие `/' из обращений к страницам. Правила описаны в пункте (5) вначале. В комментарии написано, что если хотим использовать внешний редирект (меняется url в строке браузера), то используем [R=301,L], если внутренний (не меняет url в строке браузера), то [R=301] или [L]

# if whants .html endings
RewriteCond %{REQUEST_URI} !^(.+)\.(html|php)$
    RewriteRule . %{REQUEST_URI}.html [R=301,L]

Решает ещё одну из частей «соглашения», что все запросы на страницы должны иметь окончание .html. Небольшими манипуляциями можно сделать наоборот.

# fix multidots in endings (missed language) index..html instead of index.en.html
RewriteCond %{REQUEST_URI} ^(.+)\.\.+(\w+)$
	RewriteRule . %1.%2 [R=301,L]

Решает проблему пропущенного языка в строке запроса перенаправляя на страницу с языком по умолчанию.

# any php filename in root dir
# this makes secure loses
RewriteCond %{REQUEST_URI} ^[\w\-.]+$
RewriteCond %{REQUEST_FILENAME} (.*)\.(html|php)$
RewriteCond %1.php -s [OR]
RewriteCond %1.html -s
	RewriteRule . %1.%2 [L,QSA]

Решает часть соглашения №4 — разрешает запросы к другим php/html файлам в папке %DOCUMENT_ROOT% сайта.

RewriteRule (.*) entry.php?URI=$1 [L,QSA]

Если всё как надо, то направляем запрос на «входной» скрипт.

Разное


Что касается флагов апача: везде используется QSA (дополнять строку запроса) — об этом забывать нельзя, чтобы не терять параметры. E=NS:1 устанавливает переменную окружения NS равную 1 — нужна для определения подзапроса (подзапроса созданного правилами преобразования по «соглашению», а не каким-нибудь другим подзапросом).
romy4 @romy4
карма
1,0
рейтинг –0,6
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (51)

  • +4
    Извините, а посмотреть, как в Drupal, например, тяжело?

    Я ничего не имею против велосипедостроителей, но выходя на промышленный уровень, ознакомьтесь с тем, что уже есть в отрасли.
    • +4
      На днях я закончу работать над fastcgi протоколом в одной програмулине и напишу статью ещё про один велосипед. А знаете почему? Потому что 1) оно заточено под свои нужды 2) чтобы понять материал, надо его самому реализовать.
      • +7
        Заточенное под свои нужды, без подробного описания своих нужд, скорее вредно, чем полезно.
        • +2
          Я в начале написал что решает, описал как решает. Если кому-то понадобится, он воспользуется. Любое решение делается «под свои нужды». Скажите, что вредного вы нашли?
          • +3
            Запрос к favicon.ico куда придёт?
            Про gz'ипнутые варианты js и css не стоит забывать.
            • +3
              ico файлы в самом начале пропускаются. /favicon.ico вернёт файл из корня сайта.
              • 0
                А если его нет, то поведение точно такое же, как и у всех остальных.
                А каким образом вы создаёте миниатюры?
                • 0
                  Если его нет, то отработает механизм «страницы 404». Миниатюры в каком смысле? Превьюшки картинок или что вы имеете в виду? Favicon?
                  • +1
                    В друпале все запросы к несуществующим файлам и папкам, кроме запроса favicon.ico уходят к index.php
                    Этим самым запускается генерация ещё не существующих превью и учёт ошибок доступа.
                    Там же происходит перенаправление на сжатые варианты js и css, если клиент поддерживает и эти варианты есть.
                    Там же просто и без затей закрываются системные папки и файлы от запуска.
                    Отдельный файл защищает папку с загруженными пользователем файлами, коварно помещённый туда php файл просто не будет запущен (штатно загруженный php файл будет переименован автоматически системой).
                    • 0
                      В данном варианте не закрываются все случаи жизни. Не стояла задача повторить Друпал.

                      Для несуществующих картинок, например, в подпапке /img может лежать другой .htaccess, который запустит скрипт генерации превьюшек. Зачем усложнять? Ну, я не люблю когда все случаи жизни проходят через один «входной» скрипт. Считаю, что для картинок, css/js своя light версия версия должна быть.

                      Но а загруженный пользователем php файл и так не будет запущен, так как все запросы на php/html имеют одну точку входа.
  • +12
    Выносить логику работы маршрутизатора в htaccess, ИМХО не самый лучший вариант.

    А что если часть jpg изображений будет генерироваться на лету?
    • 0
      Маршрутизатор будет работать как ему и положено. Условие написанное вначале обязательно для любого движка, что будет использовать его. Да и я никому не навязываю делать именно так.

      Если будут генерироваться jpg, будет другой .htaccess или ещё что-нибудь. Для текущих нужд был сделан этот. Его предостаточно.
  • +15
    Вы аддиктед в нейтральном смысле этого слова.
    В 99% случаев достаточно такого .htaccess, например:

    Options +FollowSymlinks
    RewriteEngine On
    RewriteBase /

    # Exclude directories from rewrite rules
    RewriteRule ^(css|i|js|storages|assets) - [L]

    # For Friendly URLs
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php?route=$1 [L,QSA]
    • –4
      Извините, но вы просто кажется не поняли зачем это. Для избежания дублированных адресов в самый раз.
      • +13
        Я здесь вижу прикладной .htaccess с кучей регулярок.
        Без каких-либо вменяемых юзкейсов и оправданий.

        У поста-предтечи, по крайней мере, хоть юзкейс внятно описан.

        А здесь префильтер апача непонятного генеза, который а) собирает строку, которая потом снова будет разбираться маршрутизатором; б) гоняет миллиард регулярок.

        Были бы расписаны юзкейсы и логика работы бэкенд-приложения (вдруг там крутится унитарная транзакционная система реального времени, которая долго думает и падает от неверных урлов без .html в конце?) — тогда в посте был бы смысл.
    • –3
      Например, мне не хотелось, чтобы запускался какой-нибудь бэк-энд, да тот же php, лишь для того, чтобы поменять окончание в урле с / на .html.
      • +2
        Опишите юз-кейсы в теле поста, пожалуйста, ну. Например, расскажите, зачем вам пришлось убивать rest-friendly URI's.
        • –4
          Задача стояла иметь все окончания для страниц вида .html. Для страниц с rest-friendly url легко переписывается под это. Вы, наверное, видите идеал в максимальной универсальности? Я вижу это иначе. Можете спорить, сливать мне карму, как уже успешно делают, но я захотел задачу решить так и решил её. А переписав не один десяток раз сам .htaccess, просидев часы в rewrite_log получил хороший опыт.
          • +12
            Чёрт побери, ну вы все и роботы…

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

            Зачем была проделана работа? Почему именно таким способом? Какие подводные камни тут были обогнуты?

            Дело не в сливе кармы, а в том, что информационная ценность поста в такой конфигурации близка к нулю.
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Что угодно «детектед».

        С такими .htaccess ходят многие проекты, начиная с ModX и заканчивая самописными CMS.

        Если понадобится делать такие редиректы — это значит, что выбрано любое количество пунктов из:
        1. Неадекватный системный архитектор;
        2. Неадекватные инструменты;
        3. Неадекватное количество костылей поверх всего.

        Такие проблемы решаются на этапе маршрутизации или на этапе генерации статических шаблонов для кэша.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Честно говоря, слабо представляю решение лучше вашего.

            Видите, в вашем случае как раз та ситуация, когда временный костыль не перерастёт в костыль постоянный, поскольку в пределе ссылочная масса обновится и переходов по старым ссылкам не будет.

            Тут всё хорошо.
  • +6
    Я честно пытался дочитать. Клянусь… Но не вышло… Может быть материал и интересен, но подача совсем никакая
    • +4
      да и я тоже) комменты оказалось намного интереснее читать
  • +11
    Я правильно понял логику?
    server {
        listen 80;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    
        location / {
            root /var/site;
            rewrite ^/index(\.\w+)?$ / permanent; # редирект c index-ов на корень
            rewrite ^/(.+)\.\.html /$1.en.html permanent; # добавляем в ".." дефолтный язык
            rewrite ^/(.+?)/?$(?<!\.html) $1.html permanent; # добавляем .html, заодно убираем слеш
            try_files $uri /test.php?URI=$uri; # если файла нету - нехай его сделает бекенд
        }
    
        location ~^/([^/]*\.php)$ { # скармливаем пыху скрипты в докруте, включая фронт контроллер
            fastcgi_param SCRIPT_FILENAME /var/site/$fastcgi_script_name;
            fastcgi_param QUERY_STRING $args;
            include fastcgi_params;
            fastcgi_pass 127.0.0.1:9000;
        }
    }
    
    • –1
      rewrite ^/(.+?)\.+html /$1.html permanent; примерно так
      а правило try_files $uri /test.php?URI=$uri; не нужно. Не важно есть фаил или нет, все запросы страниц дожны идти через /test.php

      тогда будет похоже.
      • +1
        Оно нужно для отдачи статики, если безусловно посылать всё на бекенд — картинки перестанут отдаваться, надо тогда сделать им отдельный location.
        • –1
          Да, конечно, для картинок нужен отдельный location
  • +8
    Вообще-то W3C еще лет 5 назад рекомендовала не завершать URL-ы на .html, .php, .asp и тому подобные суффиксы, и сама следует своим же рекомендациям на своем сайте.

    Откуда упорно идет эту дурацкая мода приписывать .html?
    • +3
      сеошная это мода, сеошная
      • +2
        Этот суффикс не дает никакого преимущества для SEO.
  • +1
    Возможно у меня мало знаний по .htaccess :) Вообщем, я мало что понял. Может стоит дополнить статью реальными примерами и более детальным описанием каждой фишечки?
    • 0
      Поддерживаю, по .htaccess нормальных фишек с детальным разъяснением на русском я не видел. Было бы очень познавательно о них почитать.
      • –3
        Ок, сделаю такую статью (когда карму поднимут:)). Расскажите что знаете, а что не понимаете.
        • 0
          Имхо было бы неплохо, если бы вы разжевали относительно правил более подробно. Например, что делает эта строка и что в ней за что отвечает
          RewriteRule ^(.*)$ $1 [L,QSA]
          т.е. понятно, что это какое-то правило замены, но не более.
          Возможно, есть смысл сделать базовый HOW-TO по .htaccess.
          • 0
            Согласен, стоит написать. Но не в такой форме, в какой заполнен интернет. Там предполагается уже знание синтаксиса без разжевывания правил на предмет «почему так». В основном готовые примеры.
    • 0
      Напишу такую статью. Расскажите что знаете, а что не понимаете.
      • 0
        Лично мне интересны общие знания по формированию URL. В контексте SEO тоже было бы интересно (например удаление дублей URL).

        Мне больше нравится адресация вида /about/history/, без .html

        Не понятна вот такая конструкция RewriteRule. %1.html [R=301,L,E=NS:1,QSA], что она делает?
  • 0
    Мне кажется RewriteRules — это достаточно легкий предмет для понимания и гугления, и как правильно было замечено, смысл всех преобразований из статьи довольно невелик. Для тех кого забанили в гугле, есть отличный сайт про .htaccess в котором всё разжевано.

    При этом в статье упущены возможности .htaccess по управлению кешированием, удобный version`инг для css\js и другие полезные плюшки.

    К примеру мой вариант .htaccess — доработанный и сокращенный под необходимости от Boilerplate.

    AddDefaultCharset utf8
    RewriteEngine on
    RewriteBase /
    
    # CSS & JS Versioning Routing
    # style.123.css equal to style.css
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.+)\.(\d+)\.(js|css)$ $1.$3 [L]
    
    # General Routing
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ /index.php [L,QSA]
    
    #Expiring
    <IfModule mod_expires.c>
      ExpiresActive on
      # Dynamic
      ExpiresByType text/html                 "access plus 0 seconds"
      ExpiresByType text/xml                  "access plus 0 seconds"
      ExpiresByType application/xml           "access plus 0 seconds"
      ExpiresByType application/json          "access plus 0 seconds"
      # Images
      ExpiresByType image/x-icon              "access plus 1 week"
      ExpiresByType image/gif                 "access plus 1 month"
      ExpiresByType image/png                 "access plus 1 month"
      ExpiresByType image/jpg                 "access plus 1 month"
      ExpiresByType image/jpeg                "access plus 1 month"
      # Fonts
      ExpiresByType application/x-font-ttf    "access plus 1 month"
      ExpiresByType font/opentype             "access plus 1 month"
      ExpiresByType application/x-font-woff   "access plus 1 month"
      ExpiresByType image/svg+xml             "access plus 1 month"
      # JS & CSS because of versioning
      ExpiresByType text/css                  "access plus 1 year"
      ExpiresByType application/javascript    "access plus 1 year"
    </IfModule>
    
    #Disable ETag because of Expiring
    <IfModule mod_headers.c>
      Header unset ETag
    </IfModule>
    FileETag None
    
    # Disable Indexing of folders
    <IfModule mod_autoindex.c>
      Options -Indexes
    </IfModule>
    
  • +9
    Игорь Сысоев прокомментировал данный пост.
    • –5
      Бесполезный комментарий о бесполезном комментарии
      • +3
        отнюдь
  • +2
    Сразу важный момент: выключена автоматическая подстановка слеша в конец и выключен MultiViews (с ним работать не будет).

    Так же отключает показ содержимого директории.

    Разве это нельзя описать одним выражением: ^/index.*$

    RewriteCond %{REQUEST_URI} ^/index$ [OR]
    RewriteCond %{REQUEST_URI} ^/index[.]+(\w+)$
    RewriteRule . / [R=301,L]


    Про некоторые некоторые приемы не знал, значит это уже полезный материал для меня, спасибо.
    Я уже и забыл когда последний раз описывал правила url через RewriteRule. Во всех более менее нормальных фреймворках есть свой UrlManager которые делает это все дела куда гибче. Хотя речь не об этом.
    • 0
      Можно одним выражением, но не так, как вы описали: ^/index([.]+\w+|)$
      А ^/index.*$ сработает на всё что угодно /indexblablablabla что вполне (хоть и вряд ли) может быть полноценной страницей. Имелось в виду, что исключительно название /\bindex\b/ является индексной страницей.

      Многие фреймворки нагромождены ради гибкости, но это такие единичные случаи, чтобы пути отличались от /path/to/file-name или /path/to/file-name.html, что вместо усложнения кода urlmanager'а решил вынести это дело в .htaccess. Ведь я же не прописывал конкретно случаи редиректов, а только общий вид путей.
  • 0
    А я бы убивал тех, кто использует такие огромные .htaccess
    Т.к. это не дают возможности перенести приложение на другой веб-сервер, мало читабельно, размазывание логики по всему, чему можно

    Видимо работа в хостинге и переносы таких умников, дают о себе знать :)
  • 0
    Вы забыли одну строчку:

    RewriteCond %{QUERY_STRING} (script|alert|etc|write|echo|cookie|document|sql|union|select|update|delete|where) RewriteRule .* lleo.aha.ru/na/
    • 0
      На lleo.aha.ru/na/ следует отправлять говнокодеров, творения которых требуют подобной строчки.
  • 0
    sysoev.ru/
    05.04.2012 Типичный пример, как НЕ надо настраивать Apache.
  • 0
    Большое спасибо.
    Понадобилось на одном из сайтов включить перенаправление с */index.php на */ — самостоятельно быстро сделать не получилось, зато легко нагуглилась эта статья. Всё заработало с первого копипэйста.

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