QA@IT

Servlet での静的コンテンツの扱い

13131 PV

こんばんは。Servlet について質問です。

Web アプリケーションを開発する際, 静的コンテンツは普通 Web サーバでサーブすると思うのですが、
もし Java の Servlet で静的コンテンツをサーブしようと思ったとき、どうなるのかな?と不思議に思ったのでご存知であれば教えて下さい。
(Servlet に不慣れなため少々間違っている点があるかもしれませんがご容赦ください)

ざっと調べたところ、Servlet では WEB-INF 外のコンテンツは公開されるとあります。
Jersey と Guice を使って簡単な API を実装して試してみました。構成はこんな感じです。

  • src/main/webapp
    • index.html
    • styles
      • application.css
    • WEB-INF
      • web.xml

GuiceServletContextListener を使って以下のように rest/ 以下を Jersey でサーブするようにしてみました。

serve("/rest/*").with(GuiceContainer.class);

さらに @Path("/hoge") に対応する Resource を作って Guice に登録してみたところ、app/rest/hoge/* で Resource が表示されるようになり、app/index.html や app/styles/application.css にもアクセス出来るようになったので、確かに WEB-INF 外のコンテンツは自動的に公開されるようです。

ここで質問なのですが, このような静的コンテンツのサーブ機能はサーブレットコンテナが持っているものだと思うのですが(違っていたらご指摘下さい)、

  1. app/index.html にアクセスするとき、app/ という URL と src/main/webapp が対応していると思うのですが、この関係はどこで決まっているのでしょうか?
  2. 1. のマッピングを変更することは可能なのでしょうか?例えば app/index.html を src/main/webapp/static/index.html にマッピングするなど。
  3. ひとつの URL に対して、ふたつの静的コンテンツを格納したディレクトリを対応させるということは出来るのでしょうか?(src/main/webapp/styles/application.css と src/main/webapp/thirdparty/styles/lib.css があったとして、それぞれを app/styles/application.css と app/styles/lib.css にマージしてマッピングすることは可能なのでしょうか?)

3 を実現しようと思うと web.xml で URL パターン毎にそれに応じた静的コンテンツをサーブするようなサーブレットを実装して振り分けるしかないのかな、という気がしているのですがどうなのでしょうか・・・(これも余りどうすれば良いのか分かりませんが・・・)。

お手数ですが教えて頂けると大変助かります。

回答

開発時のディレクトリ構成と、サーブレットコンテナへデプロイしたときのディレクトリ構成は別です。tm_senda さんやasahina_cp さんが指摘しているように、例えば開発時の src/main/webapp/styles/application.css というファイルがデプロイ時にサーブレットコンテナ用のWARファイル内のどこに入れられるのかは、ant、maven といったビルドツールでの指定が使われます。Eclipseを使っているなら、Eclipseプロジェクトのプロパティによりどうなるか決まります。まずは、この点について理解が必要です。
 
 

静的コンテンツのサーブ機能はサーブレットコンテナが持っているものだと思う

サーブレットコンテナが提供しています。
 
 

 1. app/index.html にアクセスするとき、app/ という URL と src/main/webapp が対応していると思うのですが、この関係はどこで決まっているのでしょうか?

app/ (この後はURLだとわかるように http://example.jp/app/ と表記します)という URL と src/main/webapp は直接は対応していません。下記の構成でデプロイされているとすると、「http://example.jp/app/ という URL に対して app.war/index.html を対応づける」という設定がサーブレットコンテナで指定できます。

app.war
|-- styles
|    |-- application.css
|    `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- index.html

Tomcatの場合は $CATALINA_HOME/conf/web.xml で下記のように指定されていて、これが適用されています。ちなみに、この指定はWebアプリ単位もできて、Webアプリごとにカスタマイズしたい場合は app.war/WEB-INF/web.xml に同様に指定します。

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

 
 

 2. 1. のマッピングを変更することは可能なのでしょうか?例えば app/index.html を src/main/webapp/static/index.html にマッピングするなど。

下記の構成でデプロイするときに、src/main/webapp/static/index.html を app.war/index.html へコピーするようにビルドを設定すれば、希望している動作になるはずです。

app.war
|-- styles
|    |-- application.css
|    `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- index.html

なお、Tomcat であれば $CATALINA_HOME/conf/Catalina/localhost/app.xml を用意して、下記のような指定をすれば、/var/www/tomcat-static/app を http://example.jp/app/ に対応づけることができます。このようにして、好きなディレクトリをドキュメントベースにできます。

<Context path="/app/"
 docBase="/var/www/tomcat-static/app/">
</Context>

この場合は、/var/www/tomcat-static/app/ 配下に下記のようなディレクトリ構成で必要なファイルを用意することになります。

/var/www/tomcat-static/app/
 |-- styles
 |    |-- application.css
 |    `-- lib.css
 |-- WEB-INF
 |   `-- web.xml
 `-- index.html

ところで、質問の意図としては、下記のような構成でデプロイして、サーブレットコンテナの設定で app.war/static を静的コンテンツのROOTにする方法を聞かれているかもしれませんが、これについて簡単に実現する方法は知りません。

app.war
|-- styles
|    |-- application.css
|    `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- static
    `-- index.html

例えば、Tomcat においては、デフォルトではHTMLなどのスタティックファイルのリソース処理は、org.apache.naming.resources.FileDirContext クラスが行います。これの処理を変更すれば良いので、これを extends した独自クラス MyDirContext クラスを用意すればできそうです。クラスを用意したら、app.war/META-INF/context.xml へ <Context><Resources className="MyDirContext" /></Context> のような感じで指定すればできるかもしれません(自分では確認してません)。
 
 

 3. ひとつの URL に対して、ふたつの静的コンテンツを格納したディレクトリを対応させるということは出来るのでしょうか?

下記のような構成のときに、http://example.jp/app/styles/ に対して、app.war/styles と app/thirdparty/styles/ の2つのディレクトリを対応させることはできません。

app.war
|-- styles
|    `-- application.css
|-- thirdparty
|    `-- styles
|         `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- index.html

開発中は「src/main/webapp/styles/application.css」と「src/main/webapp/thirdparty/styles/lib.css」と分けておいて、デプロイ時に下記のような構成になるようビルドの設定をすることはできます。このようにすることで、http://example.jp/app/styles/application.csshttp://example.jp/app/styles/lib.css とでCSSへアクセスできるようになります。

app.war
|-- styles
|    |-- application.css
|    `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- index.html
編集 履歴 (1)
  • 回答ありがとうございます。
    なるほど基本的にはビルドツールの設定なのですね。

    ところで上記の例を見ると、war にするケースでは特段の設定を行わなければ war 直下のリソース(かつ WEB-INF 外)がそのまま公開されるという理解で良いでしょうか?また、これは Web サーバによらず Java サーブレットの仕様でしょうか?
    -
  • 「開発時のディレクトリ構成と、サーブレットコンテナへデプロイしたときのディレクトリ構成は別です。」という点は良く考えればそうですね。多分ここが混乱していたところだと思います。ご指摘ありがとうございます。 -
  • Java Servlet Specification Version 3.0 の「10.5 Directory Structure」にあるように、war 直下はそのままドキュメントルートになります。Servletコンテナによらず、Java Servletの仕様です。 -
  • WEB-INFは特別なディレクトリになります。アーカイブしたWARファイルではMETA-INFも特別なディレクトリになります。特別なディレクトリは公開されません。なお、WEB-INF/lib/においたJARファイル内のMETA-INF/resources/にあるファイルもスタティックリソースとなるので注意が必要です。詳細は仕様を確認してください。 -

src/main/webapp にWeb リソースを配置されているとのことで、プロジェクト管理ツールとして Maven を利用されている前提で回答してみます。
Maven を利用する場合、Servlet は登場しません。
Servlet での静的コンテンツの扱い ということで、ご質問の意図とは異なるかもしれません。

※ Maven プロジェクトであれば、プロジェクトディレクトリのルートに pom.xml が含まれています

app/index.html にアクセスするとき、app/ という URL と src/main/webapp が対応していると思うのですが、この関係はどこで決まっているのでしょうか?

Maven (pom.xml) により 決定されています。
src/main/webapp は Maven における標準の Web コンテンツディレクトリであり、ビルド時に、src/main/webapp 以下の内容が War ファイルの直下に配置されます。
各種 IDE (Eclipse, IntelliJ IDEA, NetBeans 等) においては pom.xml の内容を読み取り、ビルド/実行 ができるよう、よきに計らうようになっています。(Eclipse では m2e-wtp Plugin を利用すると良いです)

Maven では各種リソースの標準ディレクトリ構造が定められています。
詳細については Introduction to the Standard Directory Layout をご参照ください。

  1. のマッピングを変更することは可能なのでしょうか?例えば app/index.html を src/main/webapp/static/index.html にマッピングするなど。

Maven (pom.xml) の設定を行うことで可能になります。
「3.」への回答で詳述します。

ひとつの URL に対して、ふたつの静的コンテンツを格納したディレクトリを対応させるということは出来るのでしょうか?(src/main/webapp/styles/application.css と src/main/webapp/thirdparty/styles/lib.css があったとして、それぞれを app/styles/application.css と app/styles/lib.css にマージしてマッピングすることは可能なのでしょうか?)

pom.xml の記述で対応できます。
動的 Web プロジェクトの場合、Maven では Maven War Plugin が暗黙的に利用されますが、設定により複数のリソースパスを War ファイルの配下にマッピングすることができるようになっています。

例えば以下のようにディレクトリを構成し、

/app
|-- pom.xml
`-- src
    `-- main
        |-- webapp
        |   `-- WEB-INF
        |       `-- web.xml
        |-- webapp-static
        |   |-- index.html
        |   `-- styles
        |       `-- application.css
        `-- webapp-thirdparty
            `-- styles
                `-- lib.css

以下のように pom.xml を記述した場合、

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>jp.co.atmarkit.qa</groupId>
  <artifactId>app</artifactId>
  <packaging>war</packaging>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <!-- 標準の Web リソース -->
          <warSourceDirectory>src/main/webapp</warSourceDirectory>
          <webResources>
            <!-- 静的 Web リソース -->
            <resource>
              <directory>src/main/webapp-static</directory>
            </resource>
            <!-- サードパーティの静的 Web リソース -->
            <resource>
              <directory>src/main/webapp-thirdparty</directory>
            </resource>
          </webResources>
        </configuration>
      </plugin>
      ...
    </plugins>
  </build>
  ...
</project>

最終成果物は以下のようになります。

app.war
|-- styles
|    |-- application.css
|    `-- lib.css
|-- WEB-INF
|   `-- web.xml
`-- index.html

また、Web リソースを複数のディレクトリで管理しておくと

  • Maven Assembly Plugin を併用して War ファイルとは別に静的コンテンツのみのアーカイブを作成して HTTP サーバ にデプロイ

といったこともやりやすくなります。

ビルド/プロジェクト管理ツールを上手く使うことで管理しやすい構成で、かつアプリケーションそのものの仕様をシンプルに保てるのではないかと思います。

蛇足ですが Servlet 3.0 で Web Fragment という仕様が追加されています。
こちらの仕様は大規模分散開発向けとも取れますが、使い方次第では目的とマッチするかもしれません。

編集 履歴 (0)
  • 回答ありがとうございます。大変分かりやすく助かりました。
    回答を Accept しようと思っていたのですが、ひとつの質問につきひとつしか Accept 出来ないことを知らず、hiro345 さんの方を Accept してしまいました。すみません……。
    -

app/index.html にアクセスするとき、app/ という URL と src/main/webapp が対応していると思うのですが、この関係はどこで決まっているのでしょうか?

これは Eclipse 側の設定です。

Tomcat などの場合 [http;//localhost:8080/app は $CATALINA_HOME/webapps/app] にマッピングされます。

その他の APサーバーでも同様の箇所(アプリディレクトリ)に配置されます。

  1. のマッピングを変更することは可能なのでしょうか?例えば app/index.html を src/main/webapp/static/index.html にマッピングするなど。

設定ファイル(context.xml 等)である程度は可能です。

ひとつの URL に対して、ふたつの静的コンテンツを格納したディレクトリを対応させるということは出来るのでしょうか?

ワイルドカードマッピングを利用すれば可能です。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。