Доброе утро!
В этой заметке я опишу один из способов скрытия .xhtml в адресной строке.
Дано приложение на JSF+Spring, проксируемое при помощи nginx. По просьбе заказчика возникла необходимость спрятать из адресной строки .xhtml, стандартное расширение страниц JSF-приложений. Один из пришедших в голову подходов — а пусть nginx поправит адрес, который видит пользователь, а в приложение отдаст «правильный». Одно из преимуществ такого подхода — нет нужды править приложение. Попробуем это реализовать.
Будем работать со следующим кусочком конфига:
server {
listen 80 default_server;
server_name kk.free;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
В данном виде всё, что делает nginx — передаёт запросы приложению на другой порт на этой же машине. Давайте порежем .xhtml и отправим пользователя на обычную обработку:
server {
listen 80 default_server;
server_name kk.free;
location ~ "\.xhtml" {
rewrite "(.*)\.xhtml(.*)" $1$2 redirect;
}
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
Это, естественно, приводит к тому, что пользователь хоть и видит в браузере строки вида example.com/index
вместо example.com/index.xhtml
, но и приложение видит их такими же, а значит отдаёт 404 и ничего не работает. Исправим это:
server {
listen 80 default_server;
server_name kk.free;
location ~ "\.xhtml" {
rewrite "(.*)\.xhtml(.*)" $1$2 redirect;
}
location / {
rewrite "^(.*/[^.]+)(\?|$)" $1.xhtml$2 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
Уже лучше. Что сейчас происходит? Пользователь приходит на страницу, в которой есть .xhtml, его перенаправляют на такую же, но без него (rewrite … redirect
), а локейшен для «всего остального» подставляет страницам без расширения его обратно, продолжая обрабатывать запрос (rewrite … brak
). Но это всё равно не работает — отваливаются скрипты и стили. Если посмотреть, какие запросы делает браузер, то можно увидеть, что среди них есть что-то вроде /javax.faces.resource/…/style.css.xhml?…
, расширение в которых обрубается, но не возвращается назад, потому что есть ещё одно. Решение — запретить в именах xhtml-файлов, созданных самостоятельно использовать более одной точки (что, обычно, в проектах выполняется и так) и слегка поправить конфиг:
server {
listen 80 default_server;
server_name kk.free;
location ~ "^[^.]*\.xhtml" {
rewrite "(.*)\.xhtml(.*)" $1$2 redirect;
}
location / {
rewrite "^(.*/[^.]+)(\?|$)" $1.xhtml$2 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
Таким образом, мы подменяем расширения только в путях, где нет лишних точек, а остальные пользователь и не видит. Теперь почти всё работает, за исключением POST-запросов. Браузер, делая POST-запрос, видит редирект, переходит, но тело уже не повторяет. В нашем случае, POST-запросы генерируются лишь JSF-ом, внутри скриптов, и в адресной строке нет путей, по которым они происходят. Так что это лечится достаточно просто:
server {
listen 80 default_server;
server_name kk.free;
location ~ "^[^.]*\.xhtml" {
if ($request_method != POST) {
rewrite "(.*)\.xhtml(.*)" $1$2 redirect;
}
rewrite "(.*)\.xhtml(.*)" $1$2 last;
}
location / {
rewrite "^(.*/[^.]+)(\?|$)" $1.xhtml$2 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
С этого момента POST-запросы будут обрезаться и обрабатываться nginx-ом заново (rewrite … last
), без перенаправления, тогда как для остальных всё осталось по-прежнему. Ну и если вдруг происходили какие-то запросы к страницам без расширений, для них я не нашёл лучшего способа, кроме как сделать отдельную проверку:
server {
listen 80 default_server;
server_name kk.free;
location ~ "^[^.]*\.xhtml" {
if ($request_method != POST) {
rewrite "(.*)\.xhtml(.*)" $1$2 permanent;
}
rewrite "(.*)\.xhtml(.*)" $1$2 last;
}
location / {
if ($request_uri !~ "^(/path1/)|(/other/prefix/to/pgs/wo/ext/)") {
rewrite "^(.*/[^.]+)(\?|$)" $1.xhtml$2 break;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Proxy-Forwarded-SSL $ssl_client_verify;
proxy_set_header X-Proxy-Forwarded-CN $ssl_client_s_dn;
proxy_set_header Host 127.0.0.1;
proxy_pass http://localhost:8080;
proxy_redirect default;
}
}
Когда всё проверено и отлажено, можно заменить redirect на permanent, как в последнем примере, дабы браузеры не делали лишних запросов.
Теперь у нас есть способ убрать расширение страниц из адресной строки без (или почти без) правок приложения, на этом можно закончить.