0
1

More than 3 years have passed since last update.

[Jersey]埋め込みTomcatでJerseyを動かしてみる

Last updated at Posted at 2021-07-05

はじめに

Tomcat + Jersey という構成の Web アプリケーションの動作を理解するために、Tomcat Embed を使って実験をしていきます。
いまどきこんな構成で開発を始めることは少ないかと思いますが、レガシーソフトウェアと戦う人たちの助けになれば幸いです。

関連記事の一覧(予定)

リポジトリ

静的コンテンツを配置する

Tomcat 9.0.x と Jersey 2.x において1最低限必要な依存関係2は以下です。

build.gradle
...
dependencies {
    implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: TOMCAT_VERSION
    implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: TOMCAT_VERSION
    implementation group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: JERSEY_VERSION
    // ↓ Jersey 2.26 以降で必要
    implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: JERSEY_VERSION
}
...

ドキュメントルートとなるディレクトリを作成し、適当な静的ファイルを配置します。本記事では src/main/webapp としました。

ファイル構成
src/main
├── java
│   └── (省略)
│              └── App.java
└── webapp
    └── index.html

エントリポイントとなるクラスの main メソッドで、ドキュメントルートのパスを指定します。

App.java
public class App {
    public static void main(String[] args) throws Exception {
        Tomcat tomcat = new Tomcat();
        tomcat.getConnector(); // Tomcat 9 以降では必須
        var context = tomcat.addWebapp("", Paths.get("src/main/webapp/").toAbsolutePath().toString());
        tomcat.start();
        tomcat.getServer().await();
    }
}

作成したクラスを実行してみましょう。このとき、Java アプリケーションの作業ディレクトリから、指定した相対パスでドキュメントルートに到達できることに注意してください。
Tomcat のデフォルトポート 8080 にアクセスすると、 index.html が返ってきます。

Terminal
curl localhost:8080
# <!DOCTYPE html>
# <html>
#   <body>
#     Hello Tomcat!
#   </body>
# </html>

リソースを web.xml で配置する

Jersey による動的コンテンツを追加してみましょう。

BasicResource.java
...
@Path("")
public class BasicResource {
    @GET
    public String get() {
        return "Hello Jersey👕";
    }
}

ドキュメントルートに WEB-INF/web.xml を追加し、作成した BasicResource の完全修飾名を記載します。

ファイル構成
src/main
├── (省略)
└── webapp
    ├── WEB-INF
    │   └── web.xml
    └── index.html
web.xml
<web-app version="3.0"
  xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <servlet>
    <servlet-name>Basic</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>jersey.config.server.provider.classnames</param-name>
      <param-value>com.github.at6ue.jersey.basic.BasicResource</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>Basic</servlet-name>
    <url-pattern>/basic</url-pattern>
  </servlet-mapping>
</web-app>

アプリケーションを再起動して、/basic にアクセスしてみましょう。

Terminal
curl localhost:8080/basic
# Hello Jersey👕

リソースを ResourceConfig で配置する

リソースの可換性が必要なければ web.xml よりも Java でリソースを指定するほうが、コンパイル時にリソースを参照できることが保証されるため3、より安全です。
Jersey では、どのリソースをアプリケーションに追加するかを ResouceConfig のサブクラスに指定できます。ここでは自身の属するパッケージを指定することで、その配下にあるすべてのリソースが自動的にアプリケーションへ追加されます。

AppConfig.java
...
@ApplicationPath("app")
public class AppConfig extends ResourceConfig {
    public AppConfig() {
        packages(getClass().getPackage().getName());
    }
}

ここで先ほど作成した BasicResource を含むパッケージを指定した場合、 web.xml で指定した /basic と、 AppConfig で指定したパス app の両方で BasicResource に到達できるようになります。
確認してみましょう。

Terminal
curl localhost:8080/basic
# Hello Jersey👕
curl localhost:8080/app
# Hello Jersey👕

どちらも同じレスポンスが返っていますね。
なお、本記事では web.xml を併用しましたが、すべて Java で記述する場合には WEB-INF/web.xml は存在しなくても構いません。

フィルターを設定する

ところで、 localhost:8080/appブラウザから アクセスしてみると、絵文字👕が正しく出力されず以下のように文字化けしてしまいました。
Firefox で表示したところ "Hello Jersey👕" などと表示される
これは HTTP レスポンスに文字セットの指定がない場合、デフォルトの文字セット ISO-8859-1 で解釈されるためです4
この対策として、文字セットの指定がない場合には UTF-8 の指定を追加するフィルターを設定してみます5

AddCharsetUtf8ToResponseFilter.java
...
public class AddCharsetUtf8ToResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
            throws IOException {
        var mediaType = responseContext.getMediaType();
        if (mediaType == null || mediaType.toString().contains(MediaType.CHARSET_PARAMETER)) {
            return;
        }
        responseContext.getHeaders().putSingle("Content-Type", mediaType.toString() + ";charset=utf-8");
    }
}

AppConfig で作成したフィルターを登録します。

AppConfig.java
...
@ApplicationPath("app")
public class AppConfig extends ResourceConfig {
    public AppConfig() {
        packages(getClass().getPackage().getName());
        register(AddCharsetUtf8ToResponseFilter.class); // 追加
    }
}

アプリケーションを再起動して、localhost:8080/app に再アクセスしてみましょう。

"Hello Jersey👕" と表示される

正しく表示されました。
ここで、フィルターが追加されたのは localhost:8080/app のみで、 web.xml で登録した localhost:8080/basic では文字化けしたままであることから、それぞれが 別のアプリケーションとして 同じリソースを呼び出していることが分かります。

参考


  1. Tomcat 10.x と Jersey 3.x でも同様ですが、 javaxjakarta に変更された影響で、どちらも以前のバージョンとは共存できないようです。また、 Java や XML ファイルにある javaxjakarta に書き換える必要があります。経緯については Java EEからJakarta EEへ | Oracle Technology Network Japan Blog を参照のこと。 

  2. Minimal Embedded Tomcat - A Memorandum に記載の tomcat-embed-logging-juli は Tomcat 8.5 以降では不要なようです。 

  3. ここで保証されるのはリソースクラスへの参照だけで、リソースに注入される依存関係は実行時にはじめて検証されることに注意。 

  4. rfc5987 の冒頭に言及されています。 

  5. この実装アイディアは android - Jersey / Rest default character encoding - Stack Overflow を参考にしました。 

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1