環境構築は こちら。
コードは GitHub に上げてます。
https://github.com/opengl8080-javaee-samples/servlet-jsp
#Servlet とは
Java で Web アプリを作るときの基礎となる仕組み・フレームワーク。
Java EE は知らないけど、 Servlet なら知っているという人は多いかと。
Java で Web アプリを作る場合、 Servlet を直接実装することは基本的に無い。普通は、フレームワークを使う(Struts, JSF, JAX-RS, Spark, etc...)。
しかし、フレームワークは Servlet の上で動いているので、フレームワークを使いこなすためには Servlet の知識が必要になるときがある。また、フレームワークが対応していない機能を補完するために、 Servlet が提供する API をプログラマが直接触る機会は結構多い(HttpServletRequest
や HttpServletResponse
など)。
#Hello World
あえて web.xml
を使った基本的な方法で Servlet を実装する。
コンテキストルートを servlet
にして Web プロジェクトを作成する。
package sample.javaee.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
writer.println("<h1>Hello Servlet!!</h1>");
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>sample.javaee.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
プロジェクトを実行して、 Web ブラウザで http://localhost:8080/servlet/hello
を開く。
- Servlet は、
HttpServlet
クラスを継承したクラスを作成する。 - リクエストのあった URL と実行する Servlet のマッピングは、
web.xml
に記述する。-
<servlet>
タグを使って、 Servlet クラスを宣言する。 -
<servlet-mapping>
タグを使って、 Servlet と URL をマッピングする。
-
-
HttpServlet
クラスが持つdo***()
メソッドをオーバーライドすると、 Servlet の URL に送られてきた HTTP リクエストを受け取ることができる。-
***
の部分は、 HTTP で定義されているメソッド(GET, POST, PUT, DELETE など)が対応している。 - オーバーライドしていない HTTP メソッドでリクエストが飛んでくると、
HttpServlet
のデフォルトの実装が405 - Method Not Allowed
を返すようになっている。
-
#Servlet のライフサイクル
Servlet のインスタンスがいつ生成されて、いつ破棄されるのかについて。
package sample.javaee.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LifeCycleServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("init() : hash=" + this.hashCode());
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
System.out.println("doGet() : hash=" + this.hashCode());
}
@Override
public void destroy() {
System.out.println("destroy() : hash=" + this.hashCode());
}
}
<servlet>
<servlet-name>LifeCycleServlet</servlet-name>
<servlet-class>sample.javaee.servlet.LifeCycleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LifeCycleServlet</servlet-name>
<url-pattern>/lifecycle</url-pattern>
</servlet-mapping>
Web ブラウザで http://localhost:8080/servlet/lifecycle
を開き、何度か F5 を実行したあと GlassFish を停止させる。
情報: init() : hash=1092915953, thread=http-listener-1(4)
情報: doGet() : hash=1092915953, thread=http-listener-1(4)
情報: doGet() : hash=1092915953, thread=http-listener-1(1)
情報: doGet() : hash=1092915953, thread=http-listener-1(5)
情報: doGet() : hash=1092915953, thread=http-listener-1(3)
情報: Server shutdown initiated
情報: Unregistered com.sun.enterprise.glassfish.bootstrap.osgi.EmbeddedOSGiGlassFishImpl@70e02081 from service registry.
情報: FileMonitoring shutdown
情報: JMXStartupService: Stopped JMXConnectorServer: null
情報: JMXStartupService and JMXConnectors have been shut down.
情報: destroy() : hash=1092915953, thread=RunLevelControllerThread-1419038746038
- Servlet は、最初に URL にアクセスされたタイミングでインスタンスが生成される。
- このとき、
init()
メソッドがコールバックされる。
- このとき、
- 一度生成された Servlet のインスタンスは、破棄されることなく使いまわされる。
- このとき、インスタンスを使用するスレッドは毎回異なる。
- よって、 Servlet を実装するときは、スレッドセーフにしなければならない。
- 一定時間 Servlet にアクセスが無かったり、サーバーが停止するときに Servlet のインスタンスは破棄される。
- このとき、
destroy()
メソッドがコールバックされる。
- このとき、
#HTTP リクエストの情報を取得する
package sample.javaee.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RequestParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String queryParam = req.getParameter("queryParameter");
System.out.println("queryParam=" + queryParam);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try (BufferedReader br = req.getReader()) {
br.lines().forEach(System.out::println);
}
}
}
<servlet>
<servlet-name>RequestParameterServlet</servlet-name>
<servlet-class>sample.javaee.servlet.RequestParameterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RequestParameterServlet</servlet-name>
<url-pattern>/request-param-servlet</url-pattern>
</servlet-mapping>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Serlvet Request</title>
</head>
<body>
<form action="/servlet/request-param-servlet" method="GET">
<input type="text" name="queryParameter" />
<input type="submit" value="GET" />
</form>
<form action="/servlet/request-param-servlet" method="POST">
<input type="text" name="textbox" />
<textarea name="textarea"></textarea>
<input type="submit" value="POST" />
</form>
</body>
</html>
Web ブラウザで、 http://localhost:8080/servlet/html/request.html
を開く。
GET ボタンを押下
情報: queryParam=get parameter
POST ボタンを押下
情報: textbox=textbox&textarea=textarea%0D%0Avalue
- HTTP リクエストの情報は、
HttpServletRequest
から取得できる。 - クエリパラメータは、
getParameter()
メソッドで取得できる。 - リクエストボディは、
getReader()
メソッドで取得したBufferedReader
から取得できる。- リクエストボディの取得は、
getInputStream()
メソッドでも取得可能。 - どちらか一方を先に呼び出した場合、他方のメソッドは使えなくなる(
IllegalStateException
がスローされる)。
- リクエストボディの取得は、
- これら以外にも、 HTTP ヘッダーやクライアントの情報(IP アドレスなど)も、
HttpServletRequest
から取得できる。
#Servlet 初期化時にパラメータを渡す
<servlet>
<servlet-name>InitParamServlet</servlet-name>
<servlet-class>sample.javaee.servlet.InitParamServlet</servlet-class>
<init-param>
<param-name>hoge</param-name>
<param-value>HOGE</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>InitParamServlet</servlet-name>
<url-pattern>/init-param</url-pattern>
</servlet-mapping>
package sample.javaee.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class InitParamServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
String hoge = config.getInitParameter("hoge");
System.out.println("hoge=" + hoge);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response){
}
}
Web ブラウザから http://localhost:8080/servlet/init-param
にアクセスすると、以下のようにコンソールに出力される。
情報: hoge=HOGE
-
web.xml
の<servlet>
タグ内で、<init-param>
タグを設定すると、 Servlet 初期化時に任意のパラメータを渡すことができる。 - Servlet 側は、
ServletConfig
を受け取るinit()
メソッドをオーバーライドすることで、このパラメータを受取ることができる。
#サーバー起動時に Servlet の初期化処理を実行する
<servlet>
<servlet-name>FirstStartupServlet</servlet-name>
<servlet-class>sample.javaee.servlet.FirstStartupServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>SecondStartupServlet</servlet-name>
<servlet-class>sample.javaee.servlet.SecondStartupServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet>
<servlet-name>ThirdStartupServlet</servlet-name>
<servlet-class>sample.javaee.servlet.ThirdStartupServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
package sample.javaee.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
public class FirstStartupServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("FirstStartupServlet.init()");
}
}
※SecondStartupServlet.java
, ThirdStartupServlet.java
は出力文字列が違うだけで、同じ実装なので省略。
サーバーを再起動する。
情報: FirstStartupServlet.init()
情報: SecondStartupServlet.init()
情報: ThirdStartupServlet.init()
-
web.xml
の<servlet>
タグ内で、<load-on-startup>
タグを設定することで、サーバー起動時に Servlet の初期化処理を実行させることができる。 - このとき、
<load-on-startup>
タグで指定した順序で Servlet の初期化処理が実行される。
#URL のマッピング
<servlet>
<servlet-name>UrlPatternWildCardServlet</servlet-name>
<servlet-class>sample.javaee.servlet.UrlPatternWildCardServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UrlPatternWildCardServlet</servlet-name>
<url-pattern>/wildcard/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>UrlPatternExtensionServlet</servlet-name>
<servlet-class>sample.javaee.servlet.UrlPatternExtensionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UrlPatternExtensionServlet</servlet-name>
<url-pattern>*.sample</url-pattern>
</servlet-mapping>
##動作確認
URL | 実行される Servlet |
---|---|
/servlet/wildcard | UrlPatternWildCardServlet |
/servlet/wildcard/test | UrlPatternWildCardServlet |
/servlet/test.sample | UrlPatternExtensionServlet |
/servlet/test/test.sample | UrlPatternExtensionServlet |
/servlet/wildcard/test.sample | UrlPatternWildCardServlet |
-
<url-pattern>
にはワイルドカード*
を使用することができる。
#別の Servlet に処理を委譲する
<servlet>
<servlet-name>FromServlet</servlet-name>
<servlet-class>sample.javaee.servlet.FromServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FromServlet</servlet-name>
<url-pattern>/forward/from</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ToServlet</servlet-name>
<servlet-class>sample.javaee.servlet.ToServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ToServlet</servlet-name>
<url-pattern>/forward/to</url-pattern>
</servlet-mapping>
package sample.javaee.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FromServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("FromServlet.doGet()");
req.getRequestDispatcher("/forward/to").forward(req, resp);
}
}
package sample.javaee.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ToServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("ToServlet.doGet()");
}
}
Web ブラウザで http://localhost:8080/servlet/forward/from
にアクセスする。
情報: FromServlet.doGet()
情報: ToServlet.doGet()
-
request.getRequestDispatcher("<処理を委譲する Servlet のパス>").forward(request, response)
で、指定した Servlet に処理を委譲することができる。 - これをフォワードと呼ぶ。
#非同期処理を実装する
##基本
<servlet>
<servlet-name>AsyncServlet</servlet-name>
<servlet-class>sample.javaee.servlet.AsyncServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>AsyncServlet</servlet-name>
<url-pattern>/async</url-pattern>
</servlet-mapping>
package sample.javaee.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("AsyncServlet start.");
AsyncContext ctx = req.startAsync();
ctx.start(() -> {
try (PrintWriter pw = ctx.getResponse().getWriter()) {
Thread.sleep(5000);
System.out.println("async process.");
pw.println("<h1>Async Process</h1>");
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
} finally {
ctx.complete();
}
});
resp.getWriter().println("<h1>AsyncServlet</h1>");
System.out.println("AsyncServlet end.");
}
}
Web ブラウザで http://localhost:8080/servlet/async
にアクセスする。
情報: AsyncServlet start.
情報: AsyncServlet end.
情報: async process.
5秒ほど経過してから、画面が表示される
- 非同期処理を実装する場合は、 Servlet の定義に
<asyncasync-supported>
を追加して true を指定する。- もし Servlet の処理の前に Filter が処理を挟む場合は、その Filter にも
async-supported
を設定する必要がある。
- もし Servlet の処理の前に Filter が処理を挟む場合は、その Filter にも
-
request.startAsync()
でAsyncContext
のインスタンスを取得する。 -
AsyncContext#start(Runnable)
で、非同期処理を開始する。 - 非同期処理中に
HttpServletRequest
やHttpServletResponse
を取得したい場合は、AsyncContext
から取得する。 - 非同期処理が完了したら、
AsyncContext#complete()
メソッドを実行して非同期処理が完了したことをコンテナに知らせる(知らせないとタイムアウトまで待つことになる)。
###レスポンスは非同期処理が完了されるまでコミットされない
「非同期」とあるので、まるで非同期処理が完了する前にクライアントにレスポンスが返されるのかと勘違いしそうになるけど、そうではない。
レスポンスは、 AsyncContext#complete()
が実行されるまでクライアントに返されることはない(コミットされない)。
Java™ Servlet Specification Version 3.1 - 2.3.3.3 Asynchronous processing (P.2-11)
The response isn't committed till complete (see below) is called on the AsyncContext.
日本語訳版はこちら→ サーブレット3.0仕様書邦訳版。
その応答はAsyncContext上でcompleteが呼ばれるまで(下記参照)コミットされない。
つまり、クライアントは非同期の分も含めて、処理が完了するまで待たされる。
非同期処理にするメリットは、サーブレットの実行に割り当てられているスレッドをコンテナがさっさと回収して、別のリクエストの処理に割り当てられて無駄が少なくなる、という点。
前述の日本語訳版の仕様書より抜粋
(前略)
サーブレット内での待機は非効率な使い方である。
(中略)
多くのスレッドをアクセス待ちの状態でブロックさせ、ウェブ・コンテナ全体のスレッドの枯渇とサービス品質劣化をもたらす。サーブレット第3版では要求の非同期処理を導入しており、そのスレッドをコンテナに返して他のタスクを実行できるようにしている。
クライアントを待たせたくないのであれば、自分でスレッドを作って非同期処理を実装する必要がある。
その場合は、作成したスレッドからコンテナが管理しているリソース(HttpServletRequest
など)にするのは避けたほうが良い。
その辺については Servletがスレッドを生成してはいけないのか | code up を参照。
リソースを非同期処理中で使いたいのであれば、 Java EE 7 で追加された Concurrency Utilities for Java EE を使うのがいいのかもしれない。
参考:JavaEE - Concurrency Utilities for Java EEをつかってみる - Qiita
##非同期処理のイベントを監視する
package sample.javaee.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("AsyncServlet start.");
AsyncContext ctx = req.startAsync();
ctx.addListener(new AsyncListenerImpl());
ctx.start(() -> {
try (PrintWriter pw = ctx.getResponse().getWriter()) {
Thread.sleep(5000);
System.out.println("async process.");
pw.println("<h1>Async Process</h1>");
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
} finally {
ctx.complete();
}
});
resp.getWriter().println("<h1>AsyncServlet</h1>");
System.out.println("AsyncServlet end.");
}
private static class AsyncListenerImpl implements AsyncListener {
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
System.out.println("onStartAsync");
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
System.out.println("onComplete");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
System.out.println("onTimeout");
}
@Override
public void onError(AsyncEvent event) throws IOException {
System.out.println("onError");
}
}
}
-
AsyncListener
を実装したクラスを作成して、AsyncContext#addListener()
で登録する。 - 非同期処理のイベントごとに、各メソッドがコールバックされる。
#セッション管理
<servlet>
<servlet-name>SessionServlet</servlet-name>
<servlet-class>sample.javaee.servlet.SessionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SessionServlet</servlet-name>
<url-pattern>/session/*</url-pattern>
</servlet-mapping>
package sample.javaee.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
if (this.notExistsSession(req)) {
this.createSession(req);
}
if ("/delete".equals(req.getPathInfo())) {
this.deleteSession(req);
} else {
this.countUp(req);
}
}
private boolean notExistsSession(HttpServletRequest request) {
return request.getSession(false) == null;
}
private void createSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("count", 0);
System.out.printf("session is created. id=%s%n", session.getId());
}
private void deleteSession(HttpServletRequest request) {
HttpSession session = request.getSession(true);
session.invalidate();
System.out.printf("session is deleted. id=%s%n", session.getId());
}
private void countUp(HttpServletRequest request) {
HttpSession session = request.getSession(false);
int count = (int)session.getAttribute("count");
session.setAttribute("count", ++count);
System.out.printf("count up. id=%s, count=%d%n", session.getId(), count);
}
}
Web ブラウザで、 http://localhost:8080/servlet/session/
に何度かアクセスする。
情報: session is created. id=7d32d5e4a2958576fd139692be1a
情報: count up. id=7d32d5e4a2958576fd139692be1a, count=1
情報: count up. id=7d32d5e4a2958576fd139692be1a, count=2
情報: count up. id=7d32d5e4a2958576fd139692be1a, count=3
情報: count up. id=7d32d5e4a2958576fd139692be1a, count=4
ブラウザの Cookie を見ると、 JSESSIONID
という名前でセッションの ID が保存されている。
http://localhost:8080/servlet/session/delete
にアクセスする。
情報: session is deleted. id=7d32d5e4a2958576fd139692be1a
もう一度 http://localhost:8080/servlet/session/
にアクセスする。
情報: session is created. id=7d6023ef3e12cc524fd29fa7f5ad
情報: count up. id=7d6023ef3e12cc524fd29fa7f5ad, count=1
-
request.getSession(boolean)
でセッションを取得する。- true を渡した場合、セッションが存在しないとセッションが新規に作成される。
- false を渡した場合、セッションが存在しないと null が返される。
- セッションの ID はブラウザに保存される(普通は Cookie)。
- ブラウザは、リクエストのたびにこの ID をサーバーに渡す。
- サーバーは、受け取った ID をもとにクライアントを識別してセッションを管理する。
-
session.invalidate()
でセッションを破棄できる。
#セッションタイムアウトの時間を設定する
<session-config>
<session-timeout>60</session-timeout>
</session-config>
-
<session-config>
タグをweb.xml
に記述することで、セッションタイムアウトの時間を設定できる。 - 値は分数を指定する。
- デフォルトは 60 (1時間)。
- -1 を指定した場合は、セッションタイムアウト無しになる。
#アプリケーション起動・終了時に処理を実行する
##基本
<listener>
<listener-class>sample.javaee.servlet.listener.WebAppListener</listener-class>
</listener>
package sample.javaee.servlet.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class WebAppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
System.out.println("contextInitialized()");
}
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("contextDestroyed()");
}
}
GlassFish を起動する。
情報: contextInitialized()
GlassFish を停止する。
情報: contextDestroyed()
-
ServletContextListneer
を実装したクラスを作成し、<listener>
タグを使ってweb.xml
に登録する。 - アプリケーション起動・終了時に、
contextInitialized()
とcontextDestroyed()
がそれぞれ呼ばれる。
##パラメータを渡す
<context-param>
<param-name>hoge</param-name>
<param-value>HOGE</param-value>
</context-param>
<listener>
<listener-class>sample.javaee.servlet.listener.WebAppListener</listener-class>
</listener>
package sample.javaee.servlet.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class WebAppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
String value = (String)event.getServletContext().getInitParameter("hoge");
System.out.println("contextInitialized() hoge=" + value);
}
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("contextDestroyed()");
}
}
GlassFish を起動する。
情報: contextInitialized() hoge=HOGE
-
<contex-param>
タグをweb.xml
に記述することで、ServletContextEvent
から値を取得することができる。
#セッションの作成・終了時に処理を実行する
<listener>
<listener-class>sample.javaee.servlet.listener.SessionListener</listener-class>
</listener>
package sample.javaee.servlet.listener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
System.out.printf("sessionCreated() id=%s%n", event.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
System.out.printf("sessionDestroyed() id=%s%n", event.getSession().getId());
}
}
先ほど作成した SessionServlet
にアクセスしてみる。
情報: sessionCreated() id=7f5d729e7319528e4a8316d16555
情報: session is created. id=7f5d729e7319528e4a8316d16555
情報: count up. id=7f5d729e7319528e4a8316d16555, count=1
情報: sessionDestroyed() id=7f5d729e7319528e4a8316d16555
情報: session is deleted. id=7f5d729e7319528e4a8316d16555
-
HttpSessionListener
を実装したクラスを作成して、<listener>
タグを使ってweb.xml
に登録する。 - セッションの作成と削除のときに、
sessionCreated()
とsessionDestroyed()
がそれぞれ呼ばれる。 - 引数の
HttpSessionEvent
から、対象のHttpSession
オブジェクトを取得できる。
#リクエストの前後で処理を挟む
<filter-mapping>
<filter-name>RequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package sample.javaee.servlet.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("RequestFilter before");
chain.doFilter(request, response);
System.out.println("RequestFilter after");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
http://localhost:8080/servlet/test.sample
にアクセスする。
情報: RequestFilter before
情報: UrlPatternExtensionServlet.doGet()
情報: RequestFilter after
-
Filter
を実装したクラスを作成して、<filter>
タグでweb.xml
に登録する。 -
<filter-mapping>
タグで、フィルターを適用する URL パターンをマッピングする。 - URL パターンにマッチするリクエストがあると、
doFilter()
メソッドが実行される。-
FilterChain#doFilter()
メソッドで、次の処理へ進むことができる(呼ばなくても良い)。
-
#トップページを指定する
<welcome-file-list>
<welcome-file>welcome.html</welcome-file>
</welcome-file-list>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Servlet JSP Sample</title>
</head>
<body>
<h1>Welcome Page</h1>
</body>
</html>
Web ブラウザで http://localhost:8080/servlet
にアクセスする。
-
<welcome-file-file>
タグで、ルートにアクセスしたときに表示するパスを指定できる。 -
<welcome-file>
タグは複数記述できる。- 複数記述した場合は、先頭から順番に存在をチェックして、最初に存在見つかったファイルが表示される。
- ファイルだけでなく、以下のように Servlet にマッピングしたパスも指定可能。
<welcome-file-list>
<welcome-file>hello</welcome-file>
</welcome-file-list>
#エラーが発生したときのページを指定する
##HTTP のステータスコードごとにページを指定する
<error-page>
<error-code>404</error-code>
<location>/error/404.html</location>
</error-page>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>
Web ブラウザを開いて、 http://localhost:8080/servlet/xxx
にアクセスする
-
<error-page>
タグで、エラーが発生したときに表示するページを指定できる。 -
<error-code>
タグを指定すると、 HTTP のステータスコードごとにページを指定できる。
##スローされた例外ごとにページを指定する
<servlet>
<servlet-name>NullPointerServlet</servlet-name>
<servlet-class>sample.javaee.servlet.NullPointerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>NullPointerServlet</servlet-name>
<url-pattern>/null</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error/null.html</location>
</error-page>
package sample.javaee.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class NullPointerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
throw new NullPointerException("NullPointerServlet");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>NullPointerException</title>
</head>
<body>
<h1>NullPointerException!!</h1>
</body>
</html>
Web ブラウザで http://localhost:8080/servlet/null
にアクセスする。
-
<exception-type>
タグで、例外ごとにエラーページを指定できる。
#アノテーションで設定する
Servlet 3.0 からは、 web.xml
で記述していた設定をアノテーションで指定することができる。
##Servlet
package sample.javaee.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/annotated")
public class AnnotatedServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("AnnotatedServlet.doGet()");
}
}
Web ブラウザで http://localhost:8080/servlet/annotated
にアクセスする。
情報: AnnotatedServlet.doGet()
設定できるパラメータについては API ドキュメント を参照。
##Listener
package sample.javaee.servlet.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AnnotatedListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("AnnotatedListener.contextInitialized()");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
GlassFish を起動する。
情報: AnnotatedListener.contextInitialized()
##Filter
package sample.javaee.servlet.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
@WebFilter("/*")
public class AnnotatedFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("AnnotatedFilter before");
chain.doFilter(request, response);
System.out.println("AnnotatedFilter after");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
Web ブラウザで http://localhost:8080/servlet
にアクセスする。
情報: AnnotatedFilter before
情報: AnnotatedFilter after
ただし、アノテーション指定の場合は Filter の順序を定義できない。
順序を定義したい場合はこちら を参照。
#JSP
##JSP とは
JavaServer Pages の略。
ビュー(画面)を動的に作るための仕組み(テンプレートエンジン)。 HTML タグと JSP が持つタグなどを使って画面を記述できる。
Servlet は MVC の C (Controller) を担い、 JSP は V (View) を担う。
しかし、書き方によってはゴリゴリ Java の処理を書くことができるため、魔窟のような JSP も世の中には存在する。
ただし、 Java EE 6 からは、 Facelets という別のテンプレートエンジンがメインとなっている(JSP は無くなってはいない)。
##Hello World
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<h1>Hello JSP!!</h1>
</body>
</html>
Web ブラウザで、 http://localhost:8080/servlet/jsp/hello.jsp
にアクセスする。
- 普通に HTML を記述することができる。
- 拡張子を
.jsp
にして保存し、直接 URL をアクセスすれば JSP として実行される。
##JSP は Servlet の実装に変換されて実行される
JSP にアクセスがあると、サーバーは JSP を Servlet の実装に変換する。
GlassFish の場合、以下のフォルダを見ると生成された Servlet の実装を確認できる。
<GlassFish 本体が存在するフォルダ>\domains\<ドメイン名>\generated\jsp\<アプリケーション名>\
以下が、実際に出力された Servlet のソースコード。
package org.apache.jsp.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static java.util.List<String> _jspx_dependants;
private org.glassfish.jsp.api.ResourceInjector _jspx_resourceInjector;
public java.util.List<String> getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
response.setContentType("text/html");
response.setHeader("X-Powered-By", "JSP/2.3");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
_jspx_resourceInjector = (org.glassfish.jsp.api.ResourceInjector) application.getAttribute("com.sun.appserv.jsp.resource.injector");
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <meta charset=\"UTF-8\">\r\n");
out.write(" <title>Hello JSP</title>\r\n");
out.write(" </head>\r\n");
out.write(" <body>\r\n");
out.write(" <h1>Hello JSP!!</h1>\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
単純に HTML で記述した JSP ファイルは、上記のように Servlet による実装に置き換えられてから実行され、処理結果がクライアントに返されている。
##ページのエンコーディングを指定する
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>日本語</h1>
</body>
</html>
HTML の <meta>
タグを使って文字コードを指定しているが、このままで日本語を記述すると文字化けが発生する。
これは、 JSP が HTML を生成するときの文字コードがデフォルトで ISO-8859-1 になっているのが原因。
HTML を出力するときの文字コードは、以下のように指定する。
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>日本語</h1>
</body>
</html>
##任意の Java コードを埋め込む
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<% String msg = "Hello Scriptlet!!"; %>
<h1><%=msg %></h1>
</body>
</html>
-
<% %>
はスクリプトレットと呼び、任意の Java コードを記述できる。 -
<%= %>
で任意の Java コードを評価した結果を HTML 中に埋め込むことができる。
##JSP 内で暗黙的に使用できる変数
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<% out.println("<h1>Implicit Objects</h1>"); %>
</body>
</html>
- JSP 内では、宣言せずに暗黙的に使用できる変数が存在する。
- 上記例の場合は、
out
という変数を使用している。 - これの実体は
PrintWriter
で、 HTML へ任意の文字列を出力できる。
- 上記例の場合は、
out
以外にも、以下のような変数を使用できる。
変数 | 型 | 説明 |
---|---|---|
out | PrintWriter | HTML に任意の文字列を出力できる。 |
request | HttpServletRequest | 現在のリクエストオブジェクト。 |
response | HttpServletResponse | 現在のレスポンスオブジェクト。 |
pageContext | PageContext | 現在の JSP ページのコンテキストを持つオブジェクト。 |
session | HttpSession | 現在のセッションオブジェクト。 |
application | ServletContext | アプリの ServletContext オブジェクト。 |
page | HttpJspPage | JSP から生成された Servlet のオブジェクト。 |
##Java のクラスをインポートする
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.List" %>
<%@page import="java.util.Arrays" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<ul>
<%
List<String> list = Arrays.asList("hoge", "fuga", "piyo");
for (String value : list) {
out.println("<li>" + value + "</li>");
}
%>
</ul>
</body>
</html>
-
<%@page import="..." %>
で、任意の Java クラスをインポートできる。
##コメントを記述する
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<!--HTML comment-->
<%-- JSP comment --%>
</body>
</html>
-
<%-- --%>
で JSP のコメントを記述できる。 - JSP のコメントは、 HTML に出力されない。
##別のファイルを埋め込む
<%@include file="hello.jsp" %>
-
<%@include file="..." %>
で、任意のファイルを JSP 内に埋め込むことができる。
##Servlet から JSP に処理を移す
package sample.javaee.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/jsp-servlet")
public class JspServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("JspServlet.doGet()");
req.getRequestDispatcher("jsp/hello.jsp").forward(req, resp);
}
}
Web ブラウザで http://localhost:8080/servlet/jsp-servlet
にアクセスする。
情報: JspServlet.doGet()
- Servlet と同じ要領でフォワードすることができる。
##EL 式
###EL 式とは
Expression Language の略。
式言語とも呼ばれ、スクリプトレットよりも簡潔な記述で演算結果の出力などができる。
最初は JSP の一部だったが、 Java EE 7 からは独立した仕様となった。
Facelets でも使える。
###基本
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>${"Hello Expression Language"}</h1>
</body>
</html>
-
${<評価したい式>}
で記述する。 - 評価結果は、そのまま HTML 上に出力される。
###演算する
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>100 + 10 + 1 = ${100 + 10 + 1}</h1>
</body>
</html>
-
${}
の中には、任意の演算式を記述できる。
###暗黙的に参照できるオブジェクト
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>${pageContext.request.serverName}</h1>
</body>
</html>
- JavaBeans の仕様に従って命名された
getter
メソッドは、get
を省略してプロパティ名だけで参照することができる。 - スクリプトレットと同じように、 EL 式内では暗黙的に参照できる変数が存在する。
変数名 | 説明 |
---|---|
sessionScope | セッションスコープ |
requestScope | リクエストスコープ |
applicationScope | アプリケーションスコープ |
pageSope | ページスコープ |
pageContext | 現在のページのコンテキスト |
param | リクエストパラメータのマップ |
paramValues | リクエストパラメータ(配列)のマップ |
header | HTTP のヘッダ |
headerValues | HTTP のヘッダ(配列) |
cookie | クッキー(マップ) |
###各スコープへのアクセス
package sample.javaee.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/el-scope-servlet")
public class ELScopeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setAttribute("hoge", "HOGE");
req.getSession().setAttribute("fuga", "FUGA");
req.getServletContext().setAttribute("piyo", "PIYO");
req.setAttribute("same", "RequestValue");
req.getSession().setAttribute("same", "SessionValue");
req.getServletContext().setAttribute("same", "ApplicationValue");
req.getRequestDispatcher("jsp/el4.jsp").forward(req, resp);
}
}
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>${requestScope.hoge} - ${sessionScope.fuga} - ${applicationScope.piyo}</h1>
<h2>${hoge} - ${fuga} - ${piyo}</h2>
<ul>
<li>${same}
<li>${requestScope.same}
<li>${sessionScope.same}
<li>${applicationScope.same}
</ul>
<% pageContext.setAttribute("same", "PageScopeValue"); %>
<h3>${same}</h3>
</body>
</html>
Web ブラウザで http://localhost:8080/servlet/el-scope-servlet
にアクセスする。
-
requestScope
、sessionScope
、applicationScope
は、それぞれHttpServletRequest
、HttpSession
、ServletContext
のsetAttribute()
でセットする。 - 各スコープに保存された値は、 EL 式上ではいきなりキーに指定した名前で参照することができる。
- 異なるスコープに同じ名前で保存されている値を EL 式で参照した場合、より狭いスコープで設定されている値が取得される。
###配列・リストへのアクセス
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
String[] array = {"hoge", "fuga", "piyo"};
List<String> list = Arrays.asList("HOGE", "FUGA", "PIYO");
pageContext.setAttribute("array", array);
pageContext.setAttribute("list", list);
%>
<h2>${array[0]}, ${array[1]}, ${array[2]}</h2>
<h2>${list[0]}, ${list[1]}, ${list[2]}</h2>
</body>
</html>
- 配列、 List は、
[]
を使ったインデックス指定で要素にアクセスできる。
###マップのエントリへのアクセス
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
Map<String, String> map = new HashMap<String, String>();
map.put("hoge", "HOGE");
map.put("fuga", "FUGA");
map.put("piyo", "PIYO");
pageContext.setAttribute("map", map);
%>
<h2>${map.hoge} - ${map["fuga"]} - ${map['piyo']}</h2>
</body>
</html>
- Map の各エントリへのアクセスには、
map.キー名
かmap["キー名"]
のいずれかの方法が利用できる。
##標準タグライブラリ(JSTL)を使用する
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="flag" value="true" />
<c:if test="${flag}">
flag is true
</c:if>
</body>
</html>
Web ブラウザで http://localhost:8080/servlet/jsp/jstl.jsp
を表示する。
- JSP には、標準タグライブラリ(JSP Standard Tag Library)というロジックなどを扱うためのタグが用意されている。
- タグライブラリを使う場合は、
<%@taglib prefix="..." uri="..." %>
で使用するタグを宣言する。-
prefix
属性には、タグを使用するときの名前空間を指定する。 -
uri
属性には、使用するタグを識別するための URI を指定する。 - URI に指定する値は、 jar ファイルに入っている
*.tld
ファイルを見ると書いている。
-
c.tld
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<description>JSTL 1.2 core library</description>
<display-name>JSTL core</display-name>
<tlib-version>1.2</tlib-version>
<short-name>c</short-name>
<uri>http://java.sun.com/jsp/jstl/core</uri>
(略)
- 標準カスタムタグには、以下の5種類が存在する。
名前 | 説明 | URI |
---|---|---|
core | if や for など、最もよく使うロジック処理用のタグが用意されている。 | http://java.sun.com/jsp/jstl/core |
i18n-capable formatting | i18n 対応したフォーマット出力などのタグが用意されている。 | http://java.sun.com/jsp/jstl/fmt |
functions | 文字列操作ようのタグが用意されている。 | http://java.sun.com/jsp/jstl/functions |
sql | データベース接続用のタグが用意されている。 | http://java.sun.com/jsp/jstl/sql |
xml | XML の解析、処理するためのタグが用意されている。 | http://java.sun.com/jsp/jstl/xml |
- 各タグの使い方については、 JSTLリファレンス | 忘れっぽいエンジニアのJakarta Strutsリファレンス が日本語でまとめられてて重宝します。
##複数のJSPで共通する宣言をまとめる
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<page-encoding>UTF-8</page-encoding>
<include-prelude>/jsp/taglib.jsp</include-prelude>
</jsp-property-group>
</jsp-config>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="flag" value="true" />
<c:set var="message" value="日本語" />
<c:if test="${flag}">
${message}
</c:if>
</body>
</html>
Web ブラウザで http://localhost:8080/servlet/jsp/jsp-config.jsp
を開く。
-
web.xml
で<jsp-config>
タグを使用することで、複数ファイルで共通する設定を記述することができる。 -
<url-pattern>
で指定したパターンにマッチする URL にアクセスがあった場合に限り、ここで指定した共通設定が適用される。 -
<include-prelude>
で指定したファイルは、全ての JSP ファイルの先頭に埋め込まれる。ここで標準タグライブラリの使用宣言を記述することで、各ページでの読み込み宣言を省略できる。
#セキュリティ
Servlet では、特定の URL へのアクセスに対して認証による制限を設けることができる。
このアクセス制御は、ロール単位に行える。
つまり、「/admin/*
という URL に対しては admin
というロールを持ったユーザしかアクセスできない」といった制御ができる。
Servlet が定めているのはアクセス制御のところだけで、ユーザの定義方法やユーザとロールの紐付けはアプリケーションサーバー毎に方法が異なる。
##ユーザを定義する
- Web ブラウザを開いて
http://localhost:4848/
にアクセスする。 - [Configurations] → [server-config] → [Security] → [Realms] → [file] を選択する。
- [Manage Users] をクリックする。
- [New...] をクリックして、以下のユーザーを追加する。
User ID | Group List | New Password |
---|---|---|
admin | admin_group | admin |
user | user_group | user |
###レルム
ここで定義したものをレルムと呼ぶ。
レルムとは、ユーザ名とパスワードの組み合わせのように、 Webアプリケーションのユーザを一意に定めるための"データベース"と、 認証された各ユーザに付与されているロールの一覧を列挙するものの両方を合わせたものを指します。
Tomcat5 サーブレット/JSP コンテナ - レルム設定方法
つまり、複数のユーザ情報をまとめてレルムという単位で管理する。
アプリケーションからは、使用するレルムを指定する。
##ロールを定義する
<security-role>
<role-name>admin_role</role-name>
</security-role>
<security-role>
<role-name>user_role</role-name>
</security-role>
- 管理者用のロールと、一般ユーザ用のロールを定義している。
##ユーザ(グループ)とロールを紐付ける。
<security-role-mapping>
<role-name>admin_role</role-name>
<group-name>admin_group</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>user_role</role-name>
<principal-name>user</principal-name>
<group-name>admin_group</group-name>
</security-role-mapping>
- ユーザおよびグループとロールの紐付けは、 GlassFish の場合は
glassfish-web.xml
で定義する。-
glassfish-web.xml
はWEB-INF
の直下に配置する。
-
- ユーザとロールを紐付ける場合は
<principal-name>
タグを使用する。 - グループとロールを紐付ける場合は、
<group-name>
タグを使用する。 - 上記設定は、以下のような意味になる。
- グループ「
admin_group
」に所属するユーザは、ロール「admin_role
」を持つ。 -
user
というユーザとグループ「admin_group
」に所属ユーザは、ロール「user_role
」を持つ。
- グループ「
##アクセス制御を定義する
<security-constraint>
<web-resource-collection>
<web-resource-name>User Resource</web-resource-name>
<url-pattern>/secure/user/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>user_role</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Resource</web-resource-name>
<url-pattern>/secure/admin/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin_user</role-name>
</auth-constraint>
</security-constraint>
-
<security-constraint>
タグで、1つのアクセス制御を定義できる。 -
<web-resource-collection>
タグで、アクセス制御の対象となる URL パターンや HTTP メソッドを指定できる。-
<http-method>
は省略可。省略した場合は、全てのメソッドが制限の対象になる。 -
<web-resource-collection>
、<url-pattern>
、<http-method>
は複数指定可能。
-
-
<auth-constraint>
タグで、そのリソースにアクセスしていいロールを指定する。-
<role-name>
タグは複数指定可。
-
- 上記設定は、以下のような意味になる。
-
/secure/user/*
への GET リクエストは、user_role
ロールを持つユーザだけが許可される。 -
/secure/admin/*
への GET リクエストは、admin_role
ロールを持つユーザだけが許可される。
-
##ログイン方法と使用するレルムを指定する
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>file</realm-name>
</login-config>
-
<auth-method>
でログイン方法を定義する。- ここでは、 BASIC 認証を指定している。
-
<realm-name>
で、使用するレルムを指定している。- ここでは、先ほどユーザ定義を追加した
file
レルムを指定している。
- ここでは、先ほどユーザ定義を追加した
ログイン方法には、以下のようなものが使える。
設定値 | ログイン方法 |
---|---|
BASIC | BASIC 認証 |
DIGEST | DIGEST 認証 |
FORM | HTML の form タグを使った認証 |
SSL | SSL 認証 |
##ページを作成する
Web ページの下に、 secure/user/user.html
と secure/admin/admin.html
を作成する。
##動作確認
Web ブラウザで http://localhost:8080/servlet/secure/admin/admin.html
を開く。
BASIC 認証用のログインダイアログが表示されるので、 admin
ユーザでログインする。
続いて、そのまま http://localhost:8080/servlet/secure/user/user.html
を開く。
ログインダイアログが表示されることなく、ページが表示される。
次に、ブラウザを一旦終了させてから、 http://localhost:8080/servlet/secure/user/user.html
にアクセスする。
再びログインダイアログが表示されるので、今度は user
ユーザでログインする。
続いて http://localhost:8080/servlet/secure/admin/admin.html
にアクセスする。
403 で弾かれる。
#参考
- initメソッドとdestroyメソッド - サーブレットの基本 - サーブレット入門
- 2. サーブレットの基本 | TECHSCORE(テックスコア)
- web.xml デプロイメント記述子の要素
- 5. includeとforward (3) | TECHSCORE(テックスコア)
- 7. リスナー | TECHSCORE(テックスコア)
- 8. リスナー2 | TECHSCORE(テックスコア)
- Tomcat 7の新機能で何ができるようになるのか?(2):Tomcat 7も対応したServlet 3.0の変更点 後編 (1/3) - @IT
- JavaServer Pages - Wikipedia
- JSPリファレンス(@page:import、session)
- 12. EL式 | TECHSCORE(テックスコア)
- JSTLリファレンス(JSTLの種類)
- Home: Java Platform, Enterprise Edition (Java EE) 7 Release 7
- @IT:Java TIPS -- 個別のJSPページでJSTL宣言を省略する
- ユーザー、グループ、ロール、およびレルムについて (Sun GlassFish Enterprise Server 2.1 管理ガイド)