Вашему вниманию мой вгляд на правила обработки 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 и старше.
Сперва полностью код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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] # |
Разбор полёта
Теперь, разберём построчно.
1 2 3 |
DirectoryIndex index index.html DirectorySlash off Options -Indexes -MultiViews |
Сразу важный момент: выключена автоматическая подстановка слеша в конец и выключен MultiViews (с ним работать не будет).
1 2 3 4 5 |
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$
сделан для различных тестовых файлов, но на продакшене это дело надо убирать.
вариант без test
1 |
RewriteCond %{REQUEST_URI} \.(css|jpg|gif|png|zip|rar|doc|xls|js|tif|tiff|docx|xlsx|ico)\.html$ |
1 2 3 4 5 |
# nothing to do there in subrequests RewriteCond %{ENV:NS} !=1 RewriteCond %{IS_SUBREQ} =true RewriteRule (.*) $1 [L,QSA] #do NS=0? |
Самая важная часть — так как идёт преобразование расширений (далее по коду), то скрипт будет уходить всегда в подзапрос и может уйти в бесконечный цикл. Для того, чтобы этого не произошло, ловим начало подзапросов и отправляем на уже исправленный «входной» скрипт текущий запрос по URL. Это можно посмотреть включив rewrite_log в апаче.
1 2 3 |
RewriteCond %{REQUEST_URI} ^/index$ [OR] RewriteCond %{REQUEST_URI} ^/index[.]+(\w+)$ RewriteRule . / [R=301,L] |
Все попытки попасть на /index' или
index.html’ будут перенаправлены на URL /'.
1 2 3 4 5 |
# 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]
1 2 3 |
# if whants .html endings RewriteCond %{REQUEST_URI} !^(.+)\.(html|php)$ RewriteRule . %{REQUEST_URI}.html [R=301,L] |
Решает ещё одну из частей «соглашения», что все запросы на страницы должны иметь окончание .html. Небольшими манипуляциями можно сделать наоборот.
1 2 3 |
# fix multidots in endings (missed language) index..html instead of index.en.html RewriteCond %{REQUEST_URI} ^(.+)\.\.+(\w+)$ RewriteRule . %1.%2 [R=301,L] |
Решает проблему пропущенного языка в строке запроса перенаправляя на страницу с языком по умолчанию.
1 2 3 4 5 6 7 |
# 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% сайта.
1 |
RewriteRule (.*) entry.php?URI=$1 [L,QSA] |
Если всё как надо, то направляем запрос на «входной» скрипт.
Разное
Что касается флагов апача: везде используется QSA (дополнять строку запроса) — об этом забывать нельзя, чтобы не терять параметры. E=NS:1
устанавливает переменную окружения NS равную 1 — нужна для определения подзапроса (подзапроса созданного правилами преобразования по «соглашению», а не каким-нибудь другим подзапросом).