はじめに
Apache+Tomcatの構成において、Tomcat側で静的リソースを処理させずに、Apacheに静的リソースを処理させたい。
Tomcat側ではJavaアプリケーションを動かすのみとしたいので、前段のApacheで可能な限り静的リソースのリクエストを処理させてあげることでTomcatへのリクエストを減らすことを目指す。
環境
MacOS sonoma 14.2.1
Homebrew 4.2.4
Docker 24.0.6
Docker Composer v2.22.0-desktop.2
準備
準備段階として、実際にApache+Tomcatを動作させる環境を用意した。
https://github.com/katsuobushiFPGA/apache-tomcat-docker
Docker+Docker composeが入ってる環境であれば動作するはずである。
git clone https://github.com/katsuobushiFPGA/apache-tomcat-docker.git
docker compose up -d
で構築は完了する。
ローカルの80ポートを使用するため、他に使用しているソフトがある場合は終了しておく必要がある。
目指すべき形
今回は、Tomcatのsample.war
の中にある、images
ディレクトリが静的リソースである。
現状、AJPでApacheから全てのリクエストをTomcatに流しているが、静的リソースに関しては、Tomcatの方に流さずにApacheの方で処理するということを行う。
今回のケースで具体的な例を挙げると、リクエストされたパスが images
の場合は、Apacheで処理させる。
それ以外の場合は、Tomcatに処理をさせる。
効果確認
効果確認として以下ができていれば問題ない。
- Apacheで静的リソースを返すよう設定を行う
- tomcatのlocalhost_accesslogのログとして静的リソースに関するログが無くなることを確認する
手順
1. AJPでTomcatにリクエストを流している箇所の確認
今回作成したサンプルのDockerプロジェクトでは、/etc/httpd/conf.d/httpd-ajp.conf
が該当する。
<Location />
ProxyPass ajp://localhost:8009/
</Location>
中身は、全てのパスをajp
でlocalhost:8009に流していることがわかる。
2. 特定のURLのみProxyPassを使用しない
段階を踏んで設定を適用していく。
まずは、images
のパスをTomcatに処理させないようにする。
そのため、httpd-ajp.conf
に以下を追記する。
<Location />
ProxyPass ajp://localhost:8009/
</Location>
+<Location /sample/images>
+ ProxyPass !
+</Location>
設定を反映し、Dockerをビルドし直してみる。
このように、gif
画像がリンク切れとなる。
また、この時の Tomcat
のlocalhost_access_logを覗いてみる。
docker compose exec web bash
bash-4.2# cat /usr/local/tomcat/logs/localhost_access_log.2024-01-18.txt
192.168.65.1 - - [18/Jan/2024:18:47:48 +0000] "GET /sample HTTP/1.1" 302 -
192.168.65.1 - - [18/Jan/2024:18:47:48 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:18:49:57 +0000] "GET /sample HTTP/1.1" 302 -
192.168.65.1 - - [18/Jan/2024:18:49:57 +0000] "GET /sample/ HTTP/1.1" 200 636
このような感じになっており、 gif
画像の処理はこちら側ではしていない。
Apache側はというと、
bash-4.2# cat /var/log/httpd/access_log
192.168.65.1 - - [18/Jan/2024:18:47:48 +0000] "GET /sample HTTP/1.1" 302 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:18:47:48 +0000] "GET /sample/ HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:18:49:57 +0000] "GET /sample HTTP/1.1" 302 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:18:49:57 +0000] "GET /sample/ HTTP/1.1" 200 636 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:18:49:57 +0000] "GET /sample/images/tomcat.gif HTTP/1.1" 404 196 "http://localhost/sample/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
"GET /sample/images/tomcat.gif HTTP/1.1" 404
となっており、gif
画像を処理しようとしているが、404エラーとなっているログが確認できる。
つまり、ここまででApche側で処理させるというところまでは完了できた。
補足
複数のディレクトリを指定したい場合は、LocationMatch
が使える。
※ Apacheに処理させる場合は、DocumentRootから見に行くのでこれを修正する必要がある。 とはいえ、DocumentRootは極力変えたくない。
- 参考
https://blog.cles.jp/item/8818
https://www.softel.co.jp/blogs/tech/archives/5780
https://shuji-w6e.hatenadiary.org/entry/20090526/1243383335
3. Apacheに静的リソースを処理させる
Apacheに静的リソースを処理させるために、Aliasを使用して ファイルシステムのディレクトリパスとマッチさせる必要がある。
ということなので、以下を設定する。
<Location />
ProxyPass ajp://localhost:8009/
</Location>
<Location /sample/images>
ProxyPass !
</Location>
+Alias /sample/images /usr/local/tomcat/webapps/sample/images
+<Directory /usr/local/tomcat/webapps/sample/images>
+ Require all granted
+</Directory>
特に、Alias を DocumentRoot ディレクトリの外側に配置した場合は、行き先のディレクトリに対する アクセス権限を明示的に制限しなければならないでしょう。 https://httpd.apache.org/docs/trunk/ja/mod/mod_alias.html
とあるので、Direcotry
セクションを含めている。
設定を反映し、Dockerをビルドし直してみる。
Apacheのエラーログを見ると下記が出ていた。
[core:error] [pid 27] (13)Permission denied: [client 192.168.65.1:21435] AH00035: access to /sample/images/tomcat.gif denied (filesystem path '/usr/local/tomcat/webapps/sample/images') because search permissions are missing on a component of the path, referer: http://localhost/sample/
Apacheプロセスが読み取り権限を持っていないことが原因のようなので。
chmod +x /usr/local/tomcat/webapps/sample
chmod -R +r+x /usr/local/tomcat/webapps/sample/images/
とすることで権限を与える。
Apacheのアクセスログ
docker compose exec web tail -n 10 /var/log/httpd/access_log
192.168.65.1 - - [18/Jan/2024:20:00:29 +0000] "GET /sample/images/tomcat.gif HTTP/1.1" 403 199 "http://localhost/sample/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:20:01:12 +0000] "GET /sample/ HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
192.168.65.1 - - [18/Jan/2024:20:01:12 +0000] "GET /sample/images/tomcat.gif HTTP/1.1" 200 1441 "http://localhost/sample/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
Tomcatのアクセスログ
docker compose exec web tail -n 10 /usr/local/tomcat/logs/localhost_access_log.2024-01-18.txt
192.168.65.1 - - [18/Jan/2024:19:59:38 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:19:59:38 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:19:59:38 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:25 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:26 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:28 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:28 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:29 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:00:29 +0000] "GET /sample/ HTTP/1.1" 304 -
192.168.65.1 - - [18/Jan/2024:20:01:12 +0000] "GET /sample/ HTTP/1.1" 304 -
静的リソースの処理はしていないのでOK
今回の例とは異なる設定例
よくある設定として、ROOT以下にアプリケーションをデプロイしており、なおかつ静的リソースが複数箇所にある場合の例を記載する。
AJPの設定に加え、下記を設定すれば良い。
静的リソースのディレクトリは、 css
, images
, js
としている。
+<LocationMatch "^/(css|images|js)/">
+ ProxyPass !
+</LocationMatch>
+
+AliasMatch ^/(css|images|js)(.*) /usr/share/tomcat9/webapps/ROOT/$1$2$3
+<DirectoryMatch ^/usr/share/tomcat9/webapps/ROOT/(css|images|js)>
+ AllowOverride None
+ Require all granted
+</DirectoryMatch>
+
+AliasMatch ^/(resource1|resource2)(.*) /var/static-resource/$1$2
+<DirectoryMatch ^/var/static-resource/(resource1|resource2)>
+ AllowOverride None
+ Require all granted
+</DirectoryMatch>
とすることで、複数の静的リソースをまとめて処理できる。
場所が異なる場合は、Alias, Directoryのペアを都度書く必要があるが。
今回の記事について
今回の記事はChatGPTを使用して作成を行なった。
ChatGPTにプロンプトとして
Apache + Tomcatの構成において、AJPで全てのリクエストをTomcatに流しております。
これを特定のパスのみApacheで処理するようにしたいです。
どのように設定すれば良いでしょうか
を入力することで設定が出てくるので、それを今回の動作試験用に書き直し記事にしたものである。
参考
Apache コア機能
https://httpd.apache.org/docs/2.4/ja/mod/core.htmlApache と Tomcat の連携時に一部のパスだけ Proxy しない方法
https://blog.cles.jp/item/8818【Apache】LocationMatchで否定条件
https://www.softel.co.jp/blogs/tech/archives/5780静的ファイルはTomcatに処理させない
https://shuji-w6e.hatenadiary.org/entry/20090526/1243383335Apache モジュール mod_alias
https://httpd.apache.org/docs/trunk/ja/mod/mod_alias.html
おわりに
Apache+Tomcatの構成で静的リソースの処理のさせ方を変えるということを実践した。
元々業務で、先輩のアドバイスを頂いてこれを実践したのだが、確かにTomcatはTomcatにしかできないことのみやらせるのがベストだなと思った。
Apacheはそのためにあるのでこういう構成にしているのだろうとも思う。