- フィルターについて調べてみます。
- ざっくりとフィルターとは何かというと
- 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
xml version="1.0" encoding="UTF-8"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlnsxsi="http://www.w3.org/2001/XMLSchema-instance"
xsischemaLocation="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>
<projectbuildsourceEncoding>UTF-8</projectbuildsourceEncoding>
<mavencompilersource>11</mavencompilersource>
<mavencompilertarget>11</mavencompilertarget>
</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
動かしてみる
- フィルターを実際に作ってみるとこんな感じ
- フィルターの初期化のタイミングはリクエストが来る前にと言及がある
- Jettyの場合、サーブレットがデプロイされたタイミングでフィルターも初期化される
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);
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 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");
}
}
- フィルターを複数作成した場合の順序の指定はアノテーションのみでは指定できないらしく、 web.xml が必要になるらしい
- web.xmlなくても一応動いたけど順不同なのかも?この場合どうなるかはよくわからん
- web.xmlはこんな感じに
xml version="1.0" encoding="UTF-8"
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlnsxsi="http://www.w3.org/2001/XMLSchema-instance"
xsischemaLocation="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>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>demo-filter-2</filter-name>
<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 で例外を投げてみます
@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"
xmlnsxsi="http://www.w3.org/2001/XMLSchema-instance"
xsischemaLocation="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>
<url-pattern>/filter</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>demo-filter-2</filter-name>
<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 で失敗して動き続けられても困るんで、そりゃそうなるよねって感じです