Apache – precompressing static files with gzip

Some time ago I’ve show how to precompress js and css file with gzip to be available for Nginx’s mod_gzip. In default configuration Apache don’t have such module but similar functionality could be achieved with few custom rewirtes.

Basically we will start with these rewrites to serve gzipped CSS/JS files if they exist and the client accepts gzip compression:

RewriteEngine on
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.(js|css)$ $1\.$2\.gz [QSA]

Then we need to setup proper content types for such compressed files – I know how to do this in two ways:

  • pure rewrites with mod_header – witch should serve correct content type and prevent mod_deflate to gzip files that are already gzipped
    RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=manualgzip:1]
    RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=manualgzip:1]
    
    <ifmodule mod_headers.c>
    # setup this header only if rewrites above were used
    Header set Content-Encoding "gzip" env=manualgzip
    </ifmodule>
  • by using Files clause (we could add this globally in httpd.conf)
    <files *.css.gz>
    ForceType text/css
    Header set Content-Encoding "gzip"
    </files>
    <files *.js.gz>
    #ForceType text/javascript
    # lately this one is more popular
    ForceType application/javascript
    Header set Content-Encoding "gzip"
    </files>

Both ways work fine. First one sets no-gzip variable to bypass second time compression. Second one rely on such option in my mod_deflate’s config:

SetEnvIfNoCase Request_URI \.(?:exe|t?gz|zip|bz2|sit|rar|gz)$ no-gzip dont-vary

which won’t compress any gz file, and this is why I have to setup Content-Encoding to gzip manually.

In both cases you will end with javacript and CSS files served from earlier prepared precomressed versions, with proper content type without engaging mod_deflate regardless you use js/css or js.gz/css.gz extension. But I strongly suggest to use extensions without gz – you will be able to disable this mechanism without any change in website code.

If you don’t know how to prepare files just look here.

P.S.
I found another similar but BAD example – it’s using AddEncoding clause to add gzip content type to ALL gzip files – this will cause problems with other compressed files with gz extension ex. tar.gz. Don’t do this. My rules above are more selective.

Source:
http://stackoverflow.com/questions/7947906/add-expiry-headers-using-apache-for-paths-which-dont-exist-in-the-filesystem
http://stackoverflow.com/questions/9076752/how-to-force-apache-to-use-manually-pre-compressed-gz-file-of-css-and-js-files

mod_rewrite – wymuszenie małych liter w adresie URL

Co prawda adresy URL pozwalają na stosowanie zarówno dużych jak i małych liter ale różne systemy mogą je różnie obsługiwać i może się trafić sytuacja, w której nie zechcemy by np. duże litery w ogóle pojawiały się w adresach URL. Doskonały przykład to mój niedawny wpis: Apache: ograniczenie dostępu dla zalogowanych użytkowników z mod_rewrite i mod_auth_basic.

Zachodzi tam sytuacja, w której katalog użytkownika jest jego loginem małymi literami (bądź dużymi – jak kto woli), a użytkownik wpisując login może użyć zarówno małych jak i dużych liter i tutaj zaczyna się jazda. Można użyć modyfikatora NC (no case), ale to wpłynie tylko na porównania – przepisanie ścieżki na nazwę podaną przez usera (z dużymi i małymi literami) przekieruje do katalogu, którego nie ma (bo jest katalog tylko małymi/dużymi).

I wtedy przyda się taka sztuczka:

RewriteEngine On
RewriteMap  lc int:tolower
RewriteCond %{REQUEST_URI} [A-Z]
RewriteRule (.*) ${lc:$1} [R=301,L]

Definiujemy mapę korzystając z wbudowanego w moduł słownika tolower, a następnie jeśli w URL’u występują (w tym przypadku) duże litery to przekierowujemy na URL’a z małymi.

Apache: ograniczenie dostępu dla zalogowanych użytkowników z mod_rewrite i mod_auth_basic

Niedawno trafiłem na ciekawy problem w mod_rewrite – by przekierowywać użytkowników logujących się jednym z modułów mod_auth_basic do dedykowanych im katalogów, równocześnie blokując dostęp do katalogów innych użytkowników. Nie brzmi to jakoś strasznie ale problem okazał się być całkiem nietrywialnym. Teoretyczne rozwiązanie sprowadzało się do wyszukania loginu użytkownika ze ścieżki URI i porównania z nazwą użytkownika ze zmiennej %{REMOTE_USER} – jeśli wartości się różnią to Forbidden. Ale szybko okazało się że w RewriteCond zmienne z dopasowań można podstawiać tylko w pierwszym parametrze i że o ile można RewriteCond’y połączyć wyrażeniami logicznymi typu AND/OR to nie ma możliwości porównania czy dopasowania z kolejnych RewriteCond’ów są identyczne. Po kilku dniach szperania w dokumentacji i różnych tutorialach udało mi się trafić na jedną wartościową wskazówkę ale tej stronki już nie ma, więc opiszę problem dla potomnych.

Założenia są takie:

  • mamy vhost’a który udostępnia wszystkie foldery użytkowników,
  • każdy użytkownik posiada folder o nazwie dokładnie takiej samej jak jego login,
  • użytkownik po zalogowaniu ma być przekierowany do swojego folderu i przy próbie przejścia do folderów innych użytkowników albo nawracamy go do jego folderu/albo dajemy forbidden.

Konfiguracja vhost’a

Poniżej podstawowa konfiguracja vhost’a:

<VirtualHost *:80>
ServerName files.example.com
ServerAlias www.files.example.com
DocumentRoot /var/www/files
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/files>
 AllowOverride none
 AuthType basic
 AuthName "Zaloguj sie"
 AuthUserFile /etc/apache2/passwd
 Require valid-user
</Directory>
</VirtualHost>

Rewrite’y

I najciekawsza część czyli rewrite’y. Zaczynamy od przekierowania użytkownika do jego folderu:

RewriteRule ^$ /%{REMOTE_USER} [R,L]

Powyższy rewrite sprawdza czy próbujemy wejść do głównego katalogu, jeśli tak to przekierowujemy do katalogu użytkownika.

To teraz magia, której długo szukałem 🙂

RewriteCond %{REMOTE_USER} ^(.+)
RewriteCond %1:$1 !^([^:]+):\1$
RewriteRule ^([^/]+)/ - [F,L]

Już wyjaśniam co to robi – zacznę od przypomnienia że pomimo takiego zapisu w konfiguracji reguły są przetwarzane nieco inaczej: Apache najpierw sprawdza czy dany URI pasuje do wyrażenia w RewriteRule, a dopiero gdy tak jest sprawdzane są warunki w RewriteCond. Czyli RewriteRule dopasowuje pierwszą część URI aż do znaku ukośnika / i zapamiętuje w zmiennej $1. Dopiero teraz RewriteCond dopasowuje i zapamiętuje login użytkownika w zmiennej %1 (tak rule zapamiętuje w zmiennych z $, cond w zmiennych z %). Teraz gdy mamy już zapamiętane loginy z URI i zmiennej to możemy je zapisać obok siebie w kolejnym RewriteCond oddzielając znakiem który w loginie wystąpić nie powinien (np. dwukropkiem) – $1:%1. Teraz dopasowujemy pierwszą część “sklejki”, czyli  ^([^:]+): i zaraz potem wymagamy by pojawiła się ta sama wartość przez wsteczną referencję \1$ – to porównywane jest z pierwszym parametrem cond’a. To dopasowanie jest prawdziwe gdy użytkownik loguje się prawidłowo, więc negujemy je stawiając ! na początku regexp’a, by każde błędne logowanie powodowało wywołanie RewriteRule, czyli Forbidden.

Pogmatwane? Więc teraz na przykładzie:

  • błędne logowanie (bo prostsze):
    login: roman
    uri: zbyszek/
    po dopasowaniu w $1 mamy zbyszek, a w %1 mamy roman, więc $1:%1 to zbyszek:roman, ostatni cond sprawdza czy zbyszek:roman różni się od zbyszek:zbyszek – a skoro tak to blokujemy dostęp,
  • dobre logowanie:
    login: roman
    uri: roman/
    po dopasowaniu w $1 i %1 mamy roman i sprawdzamy czy $1:%1 jest zgodne z roman:roman, a jest więc po negacji nie blokujemy dostępu. Skoro dostępu nie blokujemy to roman może dostać się do swoich plików.

Finalna konfiguracja

Zostało przedstawienie całościowo konfiguracji:

<VirtualHost *:80>
ServerName files.example.com
ServerAlias www.files.example.com
DocumentRoot /var/www/files
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/files>
 AllowOverride none
 AuthType basic
 AuthName "Zaloguj sie"
 AuthUserFile /etc/apache2/passwd
 Require valid-user

 RewriteEngine on
 RewriteRule ^$ /%{REMOTE_USER} [R,L]
 RewriteCond %{REMOTE_USER} ^(.+)
 RewriteCond %1:$1 !^([^:]+):\1$
 RewriteRule ^([^/]+)/ - [F,L]
</Directory>
</VirtualHost>

Duże i małe litery wpisywane w loginie przez userów

Zerknij tutaj.