Integrace Apache a Tomcat s mod_proxy

V tomto návodu ukážu, jak integrovat Apache HTTP server a Tomcat (nebo i jiný!) dohromady pomocí Apache mod_proxy. Apache je hlavní web server, ale některé URL mají být obsluhovány Tomcatem. Apache všechna volání neviditelně pro návštěvníka přesměruje na Tomcat.

Možnosti

Jaké máme možnosti, když chceme Apache a Tomcat spojit a jaký Apache modul použijeme?

  • reverzení proxy a mod_proxy – znamená, že Apache potichu a transparentně pro návštěvníka přesměřuje požadavek z jedné URL na jinou URL. Návštěvník používá veřejnou URL (např. http://www.mujserver.cz/kosik?step=checkout), kterou Apache přeposílá na http://localhost:8080/necoNeco. Cílovým serverem nemusí být samozřejmě jen Tomcat. Více o reverzních proxy.
  • protokol AJP a mod_jk – AJP13 (Apache JServ Protocol 1.3) je speciální protokol vyvinutý pro Tomcat. Data přenáší binárně a tedy rychleji, než HTTP. Kromě Tomcatu lze pomocí mod_jk spojit i Jetty, které také AJP implementuje.

Výhodou mod_proxy je super jednoduchá konfigurace: obvykle dva řádky pro Apache a žádná konfigurace na straně Tomcat. Komunikace může probíhat přes HTTP a HTTPS (modul se ve skutečnosti nazývá mod_proxy_http). Technicky mod_proxy sice podporuje i AJP (modul mod_proxy_ajp), ale traduje se, že tato implementace není příliš stabilní (nezkoušeli jsme).

AJP a mod_jk byste měli zvážit, pokud vám jde o nejvyšší možnou rychlost nebo hlubší integraci. AJP třeba umožňuje přenášet detaily SSL certifikátu na Tomcat. Daní za to je složitější konfigurace jak Apache, tak Tomcatu. mod_jk se proto zabývat nebudu.

Postup pro mod_proxy

Ukážeme si postup s mod_proxy používající HTTP. Budeme potřebovat tedy mod_proxy a modul mod_proxy_http.

Nejprve si ověřte, jestli už není mod_proxy není zkompilovaný s Apache (v Ubuntu nepravděpodobné).

apache2ctl -l

Jestli ve výstupu mod_proxy moduly chybí, pak je povolíme a Apache restartujeme:

$ sudo a2enmod proxy proxy_http
$ sudo service apache2 restart

Ověříme výstupem apache2ctl -M, zda byl oba moduly opravdu nahrány:

$ apache2ctl -M
...
proxy_module (shared)
proxy_http_module (shared)
..

Přesměrování části URL prostoru

Zadání:

veřejná URL http://www.firma.cz/eshop
lokální URL http://localhost:8080/eshop

Pokud je web hostovaný na Apache dosažitelný např. na URL http://www.firma.cz, ale pouze požadavky na http://www.firma.cz/eshop mají být přesměrovány na Tomcat běžící na http://localhost:8080/eshop, pak v <VirtualHost> nastavíme následovně

<VirtualHost *:80>
    ...
    ProxyRequests Off
    ProxyPass /eshop http://localhost:8080/eshop
    ProxyPassReverse /eshop http://localhost:8080/eshop
    ...
</VirtualHost>

Co jsem zde nastavil? Direktiva ProxyRequests Off vypíná forward-proxy, kterou mod_proxy také dovede. Důrazně to doporučuji, pokud skutečně nemá Apache fungovat jako běžná (forward) proxy.

Direktiva ProxyPass vytváří reverzní proxy a říká Apache, aby všechny požadavky odpovídající URL /eshop (tj. i /eshop?show, /eshop/show-order/3889 ap.) přeposlal na http://localhost:8080/eshop (resp. http://localhost:8080/eshop?show, http://localhost:8080/eshop/show-order/3889 ap.).

Direktiva ProxyPassReverse s přesně stejnými parametry ošetřuje, aby všechna přesměrování, která by mohl cílový server poslat zpět, odpovídala veřejné URL, nikoli soukromé URL cílového serveru. Konkrétně kontroluje HTTP hlavičky jako Location, Content-Location and URI, zda neobsahují http://localhost:8080/eshop a pokud ano, tak je přeloží na /eshop.

Přesměrování celého webu (/)

Zadání:

veřejná URL http://www.firma.cz/
lokální URL http://localhost:8080/firma/

Můžete chtít přesměrovat i celý web firma.cz (/) na Tomcat. Pak by konfigurace vypadala:

<VirtualHost *:80>
    ...
    # Pozor nezapomeňte na ukončovací / u obou direktiv!
    ProxyPass / http://localhost:8080/firma.cz/
    ProxyPassReverse / http://localhost:8080/firma.cz/
    ...
</VirtualHost>

Absolutní URL a self-referring URL

Jestli používáte ne své aplikaci absolutní URL nebo kdekoli odkazujete sami na sebe, pak předchozí příklady nebudou funkční. Resp. Uvidíte úvodní stránku, ale všechny odkazy na další stránky stejně jako obrázky, CSS a JS soubory se nenačtou. Jejich URl bude totiž po „překladu“ mod_proxy neplatá.

S absolutními URL jsou jen potíže. Kdykoli můžete, použijte relativní URL (../css/main.css místo /css/main.css/ nebo dokonce ještě hůř http://www.firma.cz/css/main.css). Zdůraňuju, že direktiva ProxyPassReverse ošetřuje lokální URL jen v HTTP hlavičkách, nikoli v přenášeném HTML.

Znamená to, že když aplikace za proxy (např. http://localhost:8080/firma/) vytváří HTML stránku, kde jsou vedeny odkazy na sebe sama (self-referring) jako /firma.cz/css/main.css, tak vlastně po překladu proxy znamenají www.firma.cz/firma.cz/css/main.css a tudíž jsou neplatné.

Context path Apache a Tomcat by měla být stejná

Silně doporučuji, aby proto context path ve veřejné URL Apache a lokálí URL Tomcatu zůstala stejná. Jinými slovy přeřazení lokální URL http://localhost:8080/eshop (context path je „eshop“) na http://www.firma.cz/e-shop (context path je „e-shop“) je cestou do pekel.

Problém se týká zejm. Java servlet/JSP aplikací. Ty jsou běžně umístěné v ne-root (/) context path (např. v http://localhost:8080/eshop/basket/2990 je context path /eshop).

Řeknete si: „Tak to deplojnu na root (/) context path“, jenže Tomcat může samozřejmě na stejném hostu a portu mít jen jednu aplikaci na / context path. Když musíte přes mod_proxy nabízet jen jedinou Java web aplikaci nevadí to. Ale v opačném případě byste museli hostovat každý WAR na samostané instanci Tomcatu. Uf…

Řešením je virtuální hosting v Tomcatu. Nastítím postup:

  1. Všechny servlety a JSP nikdy nepoužívají absolutní cesty, ale prefixují všechny URL context path (v JSP místo <a href="/">domů</a> je <a href="${pageContext.request.contextPath}/">domů</a>).
  2. Každý WAR umístíte na kontextovou cestu / (root)
  3. Nakonfiguruje Tomcat na virtuální hosting. Aplikace web1.war bude na http://webA:8080/, web2.war na http://web2:8080/ ap.
  4. Konfigurace mod_proxy bude vypadat ProxyPass / http://web1:8080/ a ProxyPassReverse / http://web1:8080/ atd.

mod_proxy_html

Ideální je se absolutních URL zbavit. Když to ale opravdu nejde je jediným řešením procházet obsah HTML a nahrazovat všechny lokální URL veřejnými ekvivalenty. Tuto prostředkově náročnou operaci dokáže modul mod_proxy_html, který však není ve standardní distribuci Apache. Kdyby vás zajímali detaily.

V Ubuntu nicméně žádnou kompilaci provádět nemusíme a stačí staré známé a2enmod. Používáte-li znaky mimo US-ASCII, pak budete ještě potřebovat modul mod_xml2enc vyžadovaný mod_proxy_html pro správný překlad těchto znaků.

sudo a2enmod proxy_html xml2enc

Tipy

Kdyby nestačila prostá pevná URL, mod_proxy nabízí od Apache 2.2.5 i dvojici direktiv ProxyPassMatch a ProxyPassReverseMatch umožňující specifikovat přesměrovávané URL jako regulární výrazy.

Ještě bych chtěl zmínit, že tato přesměrování je možné zadat i jako RewriteRule s flagem [P] pro mod_rewrite modul.

Předchozí příklad s přesměrováním části URL prostoru by se dal přespsat do mod_rewrite takto:

RewriteEngine On
RewriteRule ^/eshop(.*)$ http://localhost:8080/eshop$1 [P]
ProxyPassReverse / http://localhost:8080/eshop/

Časté potíže

Spoustu problémů způsobuje závěrečné lomítko. Např.

ProxyPass / http://172.16.1.11:8080/
### není totéž co ###
ProxyPass / http://172.16.1.11:8080

Záleží na cílové aplikaci, jak si poradí s požadavkem. Může způsobit nekonečnou redirect smyšku, nebo HTTP 502 „Proxy Error“ ap. Obecné pravidlo je, že když je nakonci veřejné URL lomítko, mělo by být i na konci lokální URL.

Libor Jelinek

Nadšený programátor Java a Eclipse/OSGi, štastný uživatel Ubuntu Linux, nedočkavý zkoušení nových verzí čehokoli. Žije v malém městě u Prahy a volno rád tráví vařením, běháním a s pejskem.

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Můžete používat následující HTML značky a atributy: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>