Masowe przekierowania z RewriteMap (Apache)

tech • 745 słów • 4 minuty czytania

Przy migracji i poprawianiu starych wpisów zaktualizowałem niektóre tytuły, tym samym ich adresy URL uległy zmianie. Dla zachowania kompatybilności, szczególnie z zewnętrznymi odnośnikami, wypadałoby dodać jakieś przekierowania co do niektórych wpisów. I przypadkiem odkryłem ciekawy sposób na masowe przekierowania w Apache.

Początkowo chciałem napakować swój .htaccess dyrektywami z mod_alias-a:

Redirect 301 /old-post-slug /new-post-slug

# old url posted on some external website 
Redirect 301 /1999/hacking-forum /hacking-forum-scripts

Wydawało mi się to jedynym wyjściem. To przy dużej ilości wpisów, biorąc pod uwagę, że mam aktualnie ponad 500 postów, choć przekierowań będzie jakiś mały tego odsetek, plik ten szybko stałby się spuchniętym wielkim śmietnikiem.

Niedawno całkiem przypadkiem natrafiłem w dokumentacji mod_rewrite-a na dyrektywę RewriteMap. To odkrycie zmieniło moje podejście do tego zadania. Dzięki niej, na podstawie “mapy”, którą może być plik tekstowych, funkcja, baza danych lub nawet zewnętrzny program, można dokonać masowych “przepisań” w prosty i wygodny sposób.

Dla moich potrzeb prostego statycznego przekierowywania (i oddzielenia tabeli przejść od logiki) idealnie nadaje się plik tekstowy. Jego budowa jest banalna, każda linijka zawiera parę klucz - wartość oddzieloną spacją, co w moim przypadku odzwierciedla mapowanie starego adresu na nowy. Plik z mapą (redirects.txt) mógłby wyglądać jakoś tak:

#
# Redirect Map File
# [old] [new]
#

/old-post-slug /new-post-slug

# old url posted on some external website 
/1999/hacking-forum /hacking-forum-scripts

Po “zmapowaniu” tego pliku przez RewriteMap, samo przekierowanie można załatwić w 2 linijkach:

RewriteMap redirects txt:/home/.../redirects.txt

RewriteCond ${redirects:$1} !=""
RewriteRule ^(.*)$ ${redirects:$1} [R=301,L]

Piękno tego rozwiązania polega na tym, że jeden prosty plik tekstowy przechowuje wszystkie przekierowania.

Jednakże, gdy plik ten nieco urośnie, to pomimo buforowania (cache) znalezionych już kluczy przez Apache, wyszukiwanie i dopasowywanie mapowań może nieco zwolnić. Wtedy można posłużyć się działającą znacznie szybciej indeksowaną bazą danych - DBM Hash File. Apache ma nawet narzędzie httxt2dbm służące do stworzenia takiego pliku:

httxt2dbm -i redirects.txt -o redirects.dbm

Do którego następnie można się odwołać dyrektywą RewriteMap:

RewriteMap redirects dbm:/home/.../redirects.dbm

Wyszukiwanie w takim pliku, w porównaniu do zwyklej mapy tekstowej, powinno być prawie natychmiastowe.

Standardowo Apache rozróżnia małe/wielkie znaki w adresach i do porównania trafiają ciągi w takiej postaci jakie przyszły do serwera. To może czasem być problematyczne, więc aby mapowanie było case-insensitive, to przed porównaniem trzeba byłoby przekonwertować na małe/duże litery cały ciąg, adekwatnie do zawartości mapy.

Z pomocą przychodzi znów mapowanie, ale tym razem z wykorzystaniem funkcji wbudowanych:

RewriteMap redirects txt:/home/.../redirects.txt
RewriteMap lc int:tolower

RewriteCond ${lc:%{REQUEST_URI}} ^(.*)$
RewriteCond ${redirects:%1} !=""
RewriteRule ^(.*)$ ${redirects:%1} [R=301,L]

Dla mnie to jednak nie jest problemem, bo mapuję adresy dokładnie w takiej postaci jakie były przeze mnie opublikowane w sieci, a to że ktoś wrzucił coś w zmienionej formie i wielkości liter to już nie mój problem ;)

Niestety ten wspaniały mechanizm z mapowaniem ma jedną wielką wadę:

The RewriteMap directive may not be used in <Directory> sections or .htaccess files. You must declare the map in server or virtualhost context. You may use the map, once created, in your RewriteRule and RewriteCond directives in those scopes. You just can’t declare it in those scopes.

Dyrektywa RewriteMap nie może być używana w kontekście pliku .htaccess jedynie w konfiguracji serwera lub wirtualnego hosta. Dlatego jeśli nie mamy dostępu do tych plików konfiguracyjnych to cały piękny plan w pizdu!

Ja na swoim hostingu w DreamHost nie mam takiej możliwości i jestem zmuszony powrócić do mojego początkowego planu z definicją masy przekierowań za pomocą dyrektywy Redirect w .htaccess. Oczywiście nie ręcznie, bo tak czy siak, będę w jakiś sposób generował plik z mapą przekierowań. A dopiero z niego na etapie post-build będzie tworzona docelowa zawartość doklejana do pliku .htaccess.

Na koniec jeszcze mała wzmianka o wydajności. Mozilla swego czasu (trochę już dawno) zrobiła kilka testów przy migracji swoich stron i porównywała różne sposoby przekierowań, o rezultatach można poczytać na tej stronie. Z danych tych jednoznacznie wynika, że RewriteMap bije wszystko na głowę, a zwykle Redirect-y w .htaccess wcale nie są takie szybkie, jak można byłoby się spodziewać - momentami wręcz nawet wolne.

Aczkolwiek, gdzieś w sieci też czytałem, że duża ilości linijek w pliku .htaccess znacznie pogarsza TTFB, ale tak do 100 linijek, a nawet do 1000 nie powinno to być zbytnio zauważalne…

Niemniej, jak jest możliwość to polecam używać mapowań, ale jak się nie ma co się lubi… ;)


BTW. Nie tylko Apache potrafi wykorzystać pliki z mapą przekierowań, podobnie można poczynić z Nginx:

map $request_uri $redir_uri {
	include /home/.../redirects.txt;
}

server {

	if ($redir_uri) {
		return 301 $redir_uri;
	}
}

A także można je użyć w innych serwisach i usługach hostingowych, jak np. Netlify, GitLab Pages, …

Komentarze (0)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/