ServletContextの初期化を捉える

f:id:bau1537:20210925143925j:plain

ServletContext

環境

  • プロジェクト構成
    • いろいろ試しながらやってるんで、ここでやりたいこととは無関係なファイル/ディレクトリがちらほらある
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── example
        │           ├── filter
        │           │   ├── DemoFilter1.java
        │           │   └── DemoFilter2.java
        │           ├── listener
        │           │   └── DemoServletContextListener.java
        │           └── servlet
        │               └── DemoServlet.java
        └── webapp
            └── WEB-INF
                └── web.xml

10 directories, 6 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • web.xml に書いてある内容は今回関係ないのでパス

Event Listener で ServletContext が初期化された時を捕捉する

  • ではやってみましょー
  • Event Listener というものを使っていく
  • Event Listener を使うと、ウェブアプリケーションで発生する特定のイベントを捕捉することができる
    • HttpSession とか ServletRequest とかのイベントもOK
  • ServletContext のライフサイクルに関係するイベントを捕捉するのであれば、 jakarta.servlet.ServletContextListener を継承すればよさそう
    • Event Listener として登録しないと動いてくれないので、 WebListener アノテーションをつける必要
  • 次に示すとおり、 contextInitialized メソッドで、 ServletContext 初期化のタイミングを捕捉する
package org.example.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class DemoServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContextListener.super.contextInitialized(sce);
        System.out.println("context initialized");
        System.out.println("getServerInfo : " + sce.getServletContext().getServerInfo());
        System.out.println("getServletContextName : " + sce.getServletContext().getServletContextName());
    }

}
  • デプロイして動かすとログが出力される
    • ログの出力タイミングから推測すると、サーブレットクラスや、フィルタークラスよりも先にServletContextが初期化されてる
    • それもそうで、サーブレットクラス/フィルタークラスの初期化メソッドの引数には初期化済みのServletContextが渡されるので、先に初期化しないといけない
context initialized
getServerInfo : jetty/11.0.6
getServletContextName : /servlet-1.0-SNAPSHOT
  • 確かに、ウェブアプリケーション共通の情報にアクセスできる
  • 公式では contextInitialized の使い方の例としてデータベースのコネクションの作成&保持と書いてる
    • HTTPリクエストを受け付ける前にやっておきたい作業場所と考えるのがいいだろう
    • Servletの初期化メソッドでも同じことができるけど、Jettyだと最初のリクエストが来ないと実行されないし、最初のリクエストだけレスポンスに時間がかかってしまう

Servlet Request が作成されるタイミングも捕捉してみる

  • Servlet Request が作成されるタイミングを補足できるようなので、アクセスログとかを出したいならこれかも?と思ってやってみた
  • jakarta.servlet.ServletRequestListener を継承する
  • requestInitialized メソッドをオーバーライドする
package org.example.listener;

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpServletRequest;

@WebListener
public class DemoServletRequestListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        sre.getServletContext().log("request url : " + httpServletRequest.getRequestURL());
    }

}
  • デプロイしてアクセスするとログが出てくる
2021-10-02 21:47:07.839:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/WEB-INF/
2021-10-02 21:47:13.633:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-29: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo
2021-10-02 21:47:41.114:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-32: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/filter
  • フィルターでもログは出せるけど、Event Listenerの場合はどのパスにアクセスが来た場合でもログが出る
    • フィルターは対象のURLを絞れる
    • 正直アクセスログの使い分けはどちらにすればいいか悩みどころ
    • どんなリクエストも出したい場合は Event Listener がいいかも?
  • Event Listener では Servlet Response の作成を捕捉できない
  • ちなみに Event Listener クラスへのイベント通知は必ずしも同期的ではない

Ubuntu でキーマップを変更する

  • 普段使用しているUbuntuのキーボードの設定を変えようと思いまして、その記録です
  • やりたいことは、JIS配列キーボードのスペースキー左右にある「英語」、「かな」キーを押して、ローマ字入力、日本語入力を切り替えるようにすること
    • 手元のキーボードだと「eng」「kana」とあるので、表記はキーボードそれぞれ違うと思います
    • 普通は「無変換」「変換」かもしれない
  • Windowsではこの設定してたんですが、まぁいいかって感じで今までしてませんでした
  • 普通、左上の全角半角で日本語とローマ字を切り替えていると思うんですが、それには次の2つのデメリットがあると思ってます
    • トグル方式なので、文字を打ってから切り替えることがしばしば起きる
    • キーボードの中央から遠い
  • ちなみに今使ってるキーボードはこれ

OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

やったこと

  • 全体としてはこんな感じ
    • xev でキーコードを調べる
    • xmodmap でキーを割り当てる
    • mozc で入力方法が切り替わるよう設定する

xev でキーコードを調べる

  • xev というコマンドでキーボードのキーコードを調べることができるみたいです
  • ターミナルを開いて xev と打つと小さいウィンドウが出てくる
  • そのウィンドウにフォーカスがあたった状態で該当のキーを押すと、ターミナルにイベント情報が表示される
  • イベント情報の中にキーコードがあるので、それをメモる
    • 私の環境では英語が 131 日本語が 130 でした

xmodmap でキーを割り当てる

  • xmodmap を使ってキーマップを変更します
  • xmodmap に読み込ませる設定ファイルを作成
    • ファイル名は .Xmodmap としました
    • 英語キーを無変換に
    • 日本語を全角半角に
keycode 130 = Zenkaku_Hankaku
keycode 131 = Muhenkan
  • xmodmap を使うと他にもいろいろと設定ができそうな気がしますが、それはおいおいで
  • 次のコマンドで xmodmap に設定ファイルを食わせます
xmodmap ~/.Xmodmap
  • ただ、xmodmap へ設定した内容は永続化されず、毎度設定する必要があります
  • なので、下記をシェルスクリプトとして書いて、PCが起動したときに自動的に読み込むよう設定しました
    • Ubuntu で 「自動起動するアプリケーションの設定」に設定できます
xmodmap ~/.Xmodmap
  • こうすればPCが起動すると、キーマップの設定が行われるようになります

mozc で入力方法が切り替わるよう設定する

  • あとは mozc の設定画面から「キー設定の変更」の「編集」を選び、
    • 「Muhenkan」がすでに割り当てられている設定は削除
    • すべての「IMEを無効化」コマンドの入力キーを「Muhenkan」へ
    • すべての「IMEを有効化」コマンドの入力キーを「Hankaku/Zenkaku」へ
  • これで完了です

Jakarta Servlet 5.0 のFilterを使ってみる

f:id:bau1537:20210925143925j:plain

  • フィルターについて調べてみます。
  • ざっくりとフィルターとは何かというと
    • Servlet にリクエストが到達する、またはレスポンスがクライアントに返される際に、呼び出されるメソッド
    • 引数で渡される HttpServletRequest と HttpServletResponse を変更することができる
    • 引数で渡される FilterChain を呼び出す、呼び出さないを切り替えることで後続のフィルーターまたはサーブレットに処理をさせるかを決定できる
    • どのURLパターンに対して適用するかを決定できる
    • 複数のフィルターをつなぎ合わせて構成できる
  • ざっくりと例を絵にするとこんな感じになるかな?
Client
   │
   │
   ▼
Filter 1  doFilter()
   │
   │
   ▼
Filter 2  doFilter()
   │
   │
   ▼
Filter N  doFilter()
   │
   │
   ▼
Servlet doGet()

環境

  • プロジェクト構成
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── example
        │           ├── filter
        │           │   ├── DemoFilter1.java
        │           │   └── DemoFilter2.java
        │           └── servlet
        │               └── DemoServlet.java
        └── webapp
            └── WEB-INF
                └── web.xml

9 directories, 5 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar

動かしてみる

package org.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.io.PrintWriter;

// アノテーションでフィルターであることと、名前を定義
@WebFilter(filterName = "demo-filter-1")
// jakarta.servlet.Filterをimplementsする決まり
public class DemoFilter1 implements Filter {

    // フィルターの初期化時に呼ばれる
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("DemoFilter init");
    }

    // フィルターの破棄時に呼ばれる
    @Override
    public void destroy() {
        Filter.super.destroy();
        System.out.println("DemoFilter destroy");
    }

    // フィルター処理
    // アクセスログを出力し、 /filter にリクエストが来ている場合は chain を呼び出さずリクエストを返す
    // URLパターンで指定されたものに一致するリクエストが来るとServletよりも前に呼び出される
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        StringBuffer requestURL = ((HttpServletRequest) request).getRequestURL();

        // アクセスログを出力する
        System.out.println("filter 1");
        System.out.println("request url : " + requestURL);

        if (requestURL.toString().endsWith("/filter")) {
            // レスポンスの中身を書き込み、クライアントへ返す
            writeResponse(response);
        } else {
            // 後続のフィルターまたはServletに処理を委譲する
            chain.doFilter(request, response);
        }
    }

    private void writeResponse(ServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();
        writer.write("Hello from Jakarta Servlet 5 Filter 1");
    }

}
  • 同じ内容で、HTTPレスポンスの内容と、ログの出力内容をちょっと変えたフィルターをもう一つ作る
package org.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.io.PrintWriter;

@WebFilter(filterName = "demo-filter-2")
public class DemoFilter2 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("DemoFilter init");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
        System.out.println("DemoFilter destroy");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        StringBuffer requestURL = ((HttpServletRequest) request).getRequestURL();

        System.out.println("filter 2");
        System.out.println("request url : " + requestURL);

        if (requestURL.toString().endsWith("/filter")) {
            writeResponse(response);
        } else {
            chain.doFilter(request, response);
        }
    }

    private void writeResponse(ServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();
        writer.write("Hello from Jakarta Servlet 5 Filter 2");
    }

}
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
         https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <filter-mapping>
        <filter-name>demo-filter-1</filter-name>
        <!--suppress WebProperties -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>demo-filter-2</filter-name>
        <!--suppress WebProperties -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
  • デプロイしてみる
    • 初期化のログが出てくる
    • 今回フィルターを2つ用意しているので、二行ログが出てくる
DemoFilter init
DemoFilter init
  • リクエストを飛ばす
    • サーブレットまでリクエストが届いた
    • /demo へのリクエストはFilterでchainを呼び出すようにしているので、こうなる
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/demo
hello from Jakarta Servlet 5 Servlet : service
  • ログはこう出る
    • web.xmlで定義した順に処理されているのがわかる
filter 1
request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo
filter 2
request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo
  • 別のリクエストを飛ばす
    • demo-filter-1 がレスポンスを返してくる
    • /filter へのリクエストはFilterでレスポンスを返し、後続に処理を委譲しないようにしている
    • 今回の場合、最初に実行されるフィルターは demo-filter-1 なので、こうなる
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/demo/filter
Hello from Jakarta Servlet 5 Filter 1
  • ログにはこう出る
filter 1
request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo/filter

例外を投げてみる

  • Filterで例外が発生するとどうなるんでしょうか
  • init で例外を投げてみます
    • ServletException を投げます
@WebFilter(filterName = "demo-filter-1")
public class DemoFilter1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("DemoFilter init");

        throw new ServletException();
    }
  • ログはこう
    • 初期化ログが2つ出ているので、一つのフィルターが死んでもその他のフィルターは初期化される模様
jakarta.servlet.ServletException
    at org.example.filter.DemoFilter1.init(DemoFilter1.java:18)
    at org.eclipse.jetty.servlet.FilterHolder.initialize(FilterHolder.java:133)
    at org.eclipse.jetty.servlet.ServletHandler.lambda$initialize$2(ServletHandler.java:690)
    at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
    at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762)
    at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:714)
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:392)
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1305)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:891)
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:306)
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:533)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
    at org.eclipse.jetty.deploy.bindings.StandardStarter.processBinding(StandardStarter.java:40)
    at org.eclipse.jetty.deploy.AppLifeCycle.runBindings(AppLifeCycle.java:183)
    at org.eclipse.jetty.deploy.DeploymentManager.requestAppGoal(DeploymentManager.java:516)
    at org.eclipse.jetty.deploy.DeploymentManager.addApp(DeploymentManager.java:151)
    at org.eclipse.jetty.deploy.providers.ScanningAppProvider.fileChanged(ScanningAppProvider.java:203)
    at org.eclipse.jetty.deploy.providers.WebAppProvider.fileChanged(WebAppProvider.java:398)
    at org.eclipse.jetty.deploy.providers.ScanningAppProvider$1.fileChanged(ScanningAppProvider.java:64)
    at org.eclipse.jetty.util.Scanner$DiscreteListener.pathChanged(Scanner.java:271)
    at org.eclipse.jetty.util.Scanner.reportChange(Scanner.java:881)
    at org.eclipse.jetty.util.Scanner.reportDifferences(Scanner.java:805)
    at org.eclipse.jetty.util.Scanner.scan(Scanner.java:709)
    at org.eclipse.jetty.util.Scanner$ScanTask.run(Scanner.java:145)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at java.base/java.lang.Thread.run(Thread.java:831)
DemoFilter init
DemoFilter init
  • リクエストを /demo へ投げると HTTP Status Code は 503 が返ってきました
    • つまり、フィルターが死ぬとどうやらアプリ全体死ぬ感じですかね?
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/demo
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 503 Service Unavailable</title>
</head>
<body><h2>HTTP ERROR 503 Service Unavailable</h2>
<table>
<tr><th>URI:</th><td>/servlet-1.0-SNAPSHOT/demo</td></tr>
<tr><th>STATUS:</th><td>503</td></tr>
<tr><th>MESSAGE:</th><td>Service Unavailable</td></tr>
<tr><th>SERVLET:</th><td>-</td></tr>
</table>
<hr><a href="https://eclipse.org/jetty">Powered by Jetty:// 11.0.6</a><hr/>

</body>
</html>
  • フィルターが対象とするURLパターン以外だったらリクエスト届くのか?と思い、試してみる
  • web.xmlを書き換え
    • フィルターは /filter のURLのみ処理するようにする
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
         https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <filter-mapping>
        <filter-name>demo-filter-1</filter-name>
        <!--suppress WebProperties -->
        <url-pattern>/filter</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>demo-filter-2</filter-name>
        <!--suppress WebProperties -->
        <url-pattern>/filter</url-pattern>
    </filter-mapping>
</web-app>
  • デプロイして起動し、 /demo へリクエストを投げる
    • フィルターは処理が走らないはず
    • でも HTTP Status Code は 503 が返ってくるんで、やはりサーブレットごと死んでますね
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/demo
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 503 Service Unavailable</title>
</head>
<body><h2>HTTP ERROR 503 Service Unavailable</h2>
<table>
<tr><th>URI:</th><td>/servlet-1.0-SNAPSHOT/demo</td></tr>
<tr><th>STATUS:</th><td>503</td></tr>
<tr><th>MESSAGE:</th><td>Service Unavailable</td></tr>
<tr><th>SERVLET:</th><td>-</td></tr>
</table>
<hr><a href="https://eclipse.org/jetty">Powered by Jetty:// 11.0.6</a><hr/>

</body>
</html>
  • doFilter で例外が出た場合は?
  • web.xmlは上記の書き換えたバージョンで
  • Filterは以下のように書き換え
package org.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.io.PrintWriter;

@WebFilter(filterName = "demo-filter-1")
public class DemoFilter1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("DemoFilter init");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
        System.out.println("DemoFilter destroy");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        StringBuffer requestURL = ((HttpServletRequest) request).getRequestURL();

        System.out.println("filter 1");
        System.out.println("request url : " + requestURL);


        throw new ServletException();
    }

}
  • デプロイしてリクエストを投げてみる
    • /demo はフィルターが見てないんでふつーに200ですね
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/demo
hello from Jakarta Servlet 5 Servlet : service
  • /filter にリクエストを投げると、例外が発生して 500 になります
    • ただ、この場合、アプリごと死んでしまうわけではなくて、この後もリクエストを処理してくれます
$ curl -v http://localhost:8080/servlet-1.0-SNAPSHOT/filter
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /servlet-1.0-SNAPSHOT/filter HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Server Error
< Cache-Control: must-revalidate,no-cache,no-store
< Content-Type: text/html;charset=iso-8859-1
< Content-Length: 3740
< Connection: close
< Server: Jetty(11.0.6)
< 
  • つまり init で死ぬと、アプリごと道連れになりますが、 doFilter だと道連れにならなそうって感じですかねー
    • init で失敗して動き続けられても困るんで、そりゃそうなるよねって感じです

Jakarta Servlet 5.0 リクエスト処理時の例外

f:id:bau1537:20210925143925j:plain

  • Servlet がリクエストを処理中に例外をスローするとどうなるのか?
    • 仕様ではこちらで言及されている模様
    • 内容としては
      • Servlet はリクエスト処理中に ServletExceptionUnavailableException を投げる可能性がある
      • ServletException はリクエスト処理中に何らかのエラーが発生したことを示す例外であり、Servlet Container は適切に request かつ response をクリーンアップすること
      • UnavailableExceptionServlet が一時的、または永続的にリクエストを処理できないことを示す例外
        • 永続的にリクエストを処理できない場合は Servlet は取り除かれ、 destroy メソッドが呼ばれる
          • 以降 HTTP Status Code は 404 が返る
        • 一時的にリクエストを処理できない場合は指定された時間リクエストを受け付けなくなる
          • 一時的に HTTP Status Code 503 が返る
        • Servlet Container は一時的、永続的な意味を解釈せず、どちらも永続的とみなす可能性がある
          • Servlet Container を意識したくないのであれば UnavailableException を使わないほうがいいかも
          • というか、なんでこんな仕様になってるんだろう?
  • 試す

環境

$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • プロジェクト
.
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── example
                    └── servlet
                        └── DemoServlet.java

6 directories, 2 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

ServletException

package org.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init() {
        log("Servlet init");
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
        throw new ServletException("demo exception");
    }

}
  • デプロイし、アクセスしたときのログ
    • HTTP Status Code は 500 が返される
    • 何度もアクセスしてもリクエストをハンドリングしたログが繰り返し出力されるため、 Servlet が取り除かれているわけではない
2021-09-26 16:58:22.876:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet init
2021-09-26 16:58:22.876:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet handle get request
2021-09-26 16:58:22.877:WARN :oejs.HttpChannel:qtp293907205-25: /servlet-1.0-SNAPSHOT/
jakarta.servlet.ServletException: demo exception
    at org.example.servlet.DemoServlet.doGet(DemoServlet.java:30)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:500)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:587)
    at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1410)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:508)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)

UnavailableException

  • UnavailableException を投げるとログはこうなる
    • HTTP Status Code は 404 が返ってきた
    • 何度アクセスしてもリクエストをハンドリングしたというログは出てこないので、Servlet が取り除かれているようだ
    • つまり、もう Servlet へリクエストを送ることができなくなっている
    • destroy が呼び出されていないようだが、コードが間違っているのか?もしかしてJettyがよろしくないのか?
      • それっぽいIssueを見つけたが2016年にCloseされている
      • https://github.com/eclipse/jetty.project/issues/97
      • 手元の環境では Jettyを終了させるときには destroy が呼ばれているが、これが正しい挙動なのかは微妙
2021-09-26 17:20:31.472:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet init
2021-09-26 17:20:31.472:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet handle get request
  • 例外クラスに5秒指定してみる
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
        throw new UnavailableException("demo exception", 5);
    }
  • ログはこうなる
    • 5秒間 HTTP Status Code は 503 を返すようになった
      • その間、リクエストをハンドリングするログは出ていないので、 Servlet にリクエストが届いていない
    • 5秒立つとリクエストをハンドリングするログが出るようになる
    • つまり、指定した期間 503 を返し、その後通常処理にもどる
    • Servlet は取り除かれない
2021-09-26 17:24:20.908:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet init
2021-09-26 17:24:20.909:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet handle get request
2021-09-26 17:24:28.868:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-34: org.example.servlet.DemoServlet: Servlet handle get request
2021-09-26 17:24:33.916:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet handle get request

Jakarta Servlet 5.0 初期化時の例外

f:id:bau1537:20210925143925j:plain

  • Jakarta Servlet 5 のライフサイクル中における例外について
  • 例外の種類はぱっと見 ServletExceptionUnavailableException の2つがある
    • ServletException を継承しているのが UnavailableException
  ServletException
         ▲
         │
         │
         │
         │
UnavailableException
$ java -jar $JETTY_HOME/start.jar --version 
Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • プロジェクト構成
.
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── example
                    └── servlet
                        └── DemoServlet.java

6 directories, 2 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • DemoServlet.java
    • ServletException をスローする
package org.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        log("Servlet init");
        throw new ServletException("demo exception");
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
    }

}
  • 起動すると以下のログが出力される
  • リクエストを投げても 404 が返ってくるようになる
  • ログを見る限り destroy メソッドも呼び出されていない
2021-09-26 13:25:31.678:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: org.example.servlet.DemoServlet: Servlet init
2021-09-26 13:25:31.678:WARN :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: unavailable
jakarta.servlet.ServletException: demo exception
    at org.example.servlet.DemoServlet.init(DemoServlet.java:18)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at org.eclipse.jetty.servlet.ServletHolder$Wrapper.init(ServletHolder.java:1305)
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:633)
    at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:486)
    at org.eclipse.jetty.servlet.ServletHolder.prepare(ServletHolder.java:731)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)
2021-09-26 13:25:31.679:WARN :oejs.HttpChannel:qtp1175259735-26: /servlet-1.0-SNAPSHOT/
jakarta.servlet.ServletException: demo exception
    at org.example.servlet.DemoServlet.init(DemoServlet.java:18)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at org.eclipse.jetty.servlet.ServletHolder$Wrapper.init(ServletHolder.java:1305)
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:633)
    at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:486)
    at org.eclipse.jetty.servlet.ServletHolder.prepare(ServletHolder.java:731)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)
  • 次に UnavailableException をスローしてみる
package org.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.UnavailableException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        log("Servlet init");
        throw new UnavailableException("demo exception");
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
    }

}
  • デプロイすると以下のログが出てくる
  • ServletException と異なり、スタックトレースが出てきていない
    • この違いは以外だった。ログだけ見ると、サーブレットの起動に成功しているように見える。
  • アクセスすると404が出力される
2021-09-26 13:29:16.228:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-25: org.example.servlet.DemoServlet: Servlet init
  • 次に秒数を指定して UnavailableException をスローしてみる
package org.example.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.UnavailableException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        log("Servlet init");
        throw new UnavailableException("demo exception", 10);
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
    }

}
  • 起動するとログはこう
  • 起動して10秒間は 503 が返されるが、10秒くらいたった後だと 200 が返されリクエストが処理されるようになる
    • 一行目と二行目のログの時間をみてもらえれば約10秒間差があるのがわかる
  • スタックトレースはログに出てこない
021-09-26 13:33:19.598:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet init
2021-09-26 13:33:30.067:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet handle get request
  • 最後に RuntimeException をスローしてみる
package org.example.servlet;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init() {
        log("Servlet init");
        throw new RuntimeException("demo exception");
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        log("Servlet handle get request");
        PrintWriter writer = resp.getWriter();
        writer.write("hello from Jakarta Servlet 5");
    }

}
  • ログはこう
  • リクエストを投げると 404 が返ってくる
  • これまでと同様 destroy メソッドは呼ばれない
2021-09-26 13:38:34.255:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: org.example.servlet.DemoServlet: Servlet init
2021-09-26 13:38:34.255:WARN :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-25: unavailable
java.lang.RuntimeException: demo exception
    at org.example.servlet.DemoServlet.init(DemoServlet.java:17)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at org.eclipse.jetty.servlet.ServletHolder$Wrapper.init(ServletHolder.java:1305)
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:633)
    at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:486)
    at org.eclipse.jetty.servlet.ServletHolder.prepare(ServletHolder.java:731)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)
2021-09-26 13:38:34.257:WARN :oejs.HttpChannel:qtp293907205-25: /servlet-1.0-SNAPSHOT/
jakarta.servlet.ServletException: org.example.servlet.DemoServlet==org.example.servlet.DemoServlet@3134ca0b{jsp=null,order=-1,inst=true,async=false,src=ANNOTATION:org.example.servlet.DemoServlet,STARTED}
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:651)
    at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:486)
    at org.eclipse.jetty.servlet.ServletHolder.prepare(ServletHolder.java:731)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: 
java.lang.RuntimeException: demo exception
    at org.example.servlet.DemoServlet.init(DemoServlet.java:17)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at org.eclipse.jetty.servlet.ServletHolder$Wrapper.init(ServletHolder.java:1305)
    at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:633)
    at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:486)
    at org.eclipse.jetty.servlet.ServletHolder.prepare(ServletHolder.java:731)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:580)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1370)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1292)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:192)
    at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
    at org.eclipse.jetty.server.Server.handle(Server.java:562)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)

アスキーアートはこのツールを使ってみた https://asciiflow.com/

Jakarta Servlet 5.0 の Servlet Life Cycle

f:id:bau1537:20210925143925j:plain

$ tree .
.
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── example
                    └── servlet
                        └── DemoServlet.java

6 directories, 2 files
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • DemoServlet.java
package org.example.servlet;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        log("Servlet init (with config)");
    }

    @Override
    public void init() {
        log("Servlet init");
    }

    @Override
    public void destroy() {
        log("Servlet destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        log("Servlet handle get request");
        var writer = resp.getWriter();
        writer.write("Hello, from Jakarta Servlet 5");
    }

}
  • HttpServlet を継承したクラスを作り、 @WebServlet でリクエストを受け付けるURLを設定
  • initdestroy メソッドをそれぞれオーバーライドし、サーブレットのライフサイクルで任意のコードを実行できるようにする
    • それぞれのライフサイクルでログが出るようにした
    • init は設定クラスを引数に取るものと、取らないものがある
  • Jettyにデプロイして動かすと、こんな感じのログが出てくる
    • ここで、ログが出るタイミングはいつなのかというと、対象のURLにアクセスしたときだった
    • つまり、サーバーを起動した(今回の場合はJetty)ではサーブレットは初期化されず、初めてアクセスが来たタイミングで初期化されたということ
    • この動作がJetty特有なものなのか、Servletの決まりなのかはわからぬ
2021-09-25 15:44:58.848:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-34: org.example.servlet.DemoServlet: Servlet init
2021-09-25 15:44:58.848:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-34: org.example.servlet.DemoServlet: Servlet init (with config)
2021-09-25 15:44:58.848:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-34: org.example.servlet.DemoServlet: Servlet handle get request
2021-09-25 15:45:00.130:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp293907205-28: org.example.servlet.DemoServlet: Servlet handle get request
  • Jettyを Ctrl + c で止めると、 destroy メソッドが呼ばれる
2021-09-25 15:56:08.664:INFO :oejs.Server:JettyShutdownThread: Stopped Server@6989da5e{STOPPING}[11.0.6,sto=5000]
2021-09-25 15:56:08.664:INFO :oejs.Server:JettyShutdownThread: Shutdown Server@6989da5e{STOPPING}[11.0.6,sto=5000]
2021-09-25 15:56:08.671:INFO :oejs.AbstractConnector:JettyShutdownThread: Stopped ServerConnector@75c56eb9{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2021-09-25 15:56:08.672:INFO :oejshC.servlet_demo:JettyShutdownThread: org.example.servlet.DemoServlet: Servlet destroy
  • 設定クラスを引数に取る init メソッドでは ServletContext なるものにアクセスできるということなので試す
  • こんな感じに書き換え
    • ServletContextName なるものをログに出してみる
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        log("Servlet init (with config)");
        log("Servlet context name is " + config.getServletContext().getServletContextName());
    }
  • デプロイするとこんな感じにログが出た
    • Servlet context name is /servlet-1.0-SNAPSHOT と出力された
    • どうやら ServletContextName とはサーブレットにアクセスするときのURLの一部であるらしい
    • 正確なことはまたとで...
2021-09-25 15:58:07.115:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: org.example.servlet.DemoServlet: Servlet init
2021-09-25 15:58:07.115:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: org.example.servlet.DemoServlet: Servlet init (with config)
2021-09-25 15:58:07.116:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: org.example.servlet.DemoServlet: Servlet context name is /servlet-1.0-SNAPSHOT
2021-09-25 15:58:07.116:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: org.example.servlet.DemoServlet: Servlet handle get request
2021-09-25 15:58:07.308:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-31: org.example.servlet.DemoServlet: Servlet handle get request

Jakarta Servlet 5.0 を web.xml なしで動かす

f:id:bau1537:20210925143925j:plain

  • Jakarta Servlet を web.xml なしで動かしたい
    • 何故かうまく行かず5時間くらい苦戦したが、動かせたのでとりあえずメモを
  • 環境
$ java -version
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)
$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • とりあえずプロジェクトを作る
  • ディレクトリはこんな感じ
$ tree .
.
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── example
                    └── servlet
                        └── DemoServlet.java

6 directories, 2 files
  • pomはこう
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • maven-war-pluginfailOnMissingWebXmlfalse にする
  • DemoServlet.javaはこんなかんじ
package org.example.servlet;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(urlPatterns = "/")
public class DemoServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        var writer = resp.getWriter();
        writer.write("Hello, from Jakarta Servlet 5");
    }

}
$ java -jar $JETTY_HOME/start.jar --add-module=annotations
INFO  : jndi            transitively enabled
INFO  : annotations     initialized in ${jetty.base}/start.d/annotations.ini
INFO  : plus            transitively enabled
INFO  : Base directory was modified
  • ちなみに他には以下のモジュールが有効になっている
$ java -jar $JETTY_HOME/start.jar --list-modules
〜略〜
Enabled Modules:
----------------
    0) resources       transitive provider of resources for logging-jetty
    1) logging/slf4j   transitive provider of logging/slf4j for logging-jetty
                       dynamic dependency of logging-jetty
    2) logging-jetty   transitive provider of logging for threadpool
                       transitive provider of logging for bytebufferpool
                       transitive provider of logging for server
    3) bytebufferpool  transitive provider of bytebufferpool for server
                       init template available with --add-module=bytebufferpool
    4) threadpool      transitive provider of threadpool for server
                       init template available with --add-module=threadpool
    5) server          transitive provider of server for http
                       transitive provider of server for plus
                       transitive provider of server for security
                       transitive provider of server for servlet
                       transitive provider of server for jndi
                       init template available with --add-module=server
    6) jndi            transitive provider of jndi for plus
    7) security        transitive provider of security for webapp
                       transitive provider of security for plus
    8) servlet         ${jetty.base}/start.d/servlet.ini
    9) webapp          ${jetty.base}/start.d/webapp.ini
   10) plus            transitive provider of plus for annotations
   11) annotations     ${jetty.base}/start.d/annotations.ini
   12) deploy          ${jetty.base}/start.d/deploy.ini
   13) http            ${jetty.base}/start.d/http.ini
  • デプロイして動作確認
$ curl http://localhost:8080/servlet-1.0-SNAPSHOT/
Hello, from Jakarta Servlet 5