Apache+Tomcatの構成で静的リソースをApacheに処理させる

はじめに

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をビルドし直してみる。

修正前
default-page-view

修正後
default-page-view-2

このように、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は極力変えたくない。

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/

とすることで権限を与える。

表示できていることを確認できた。
default-page-view-3

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+Tomcatの構成で静的リソースの処理のさせ方を変えるということを実践した。
元々業務で、先輩のアドバイスを頂いてこれを実践したのだが、確かにTomcatはTomcatにしかできないことのみやらせるのがベストだなと思った。
Apacheはそのためにあるのでこういう構成にしているのだろうとも思う。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。