はじめに
- Javaの基本的な構文について理解している前提です。
- Eclipse (Pleiades) を使用している前提です。
開発環境のセットアップについては Eclipseの超基本的な使い方 - Qiita を参照してください。
まずは、ServletとJSPの動作について確認します。(当記事)
その後、TODOアプリの開発を通してJSP&ServletによるWebアプリ開発の基本について解説しようと思います。(次回予定)
プロジェクトの作成
-
パッケージ・エクスプローラー
を右クリックし新規
->プロジェクト...
を選択 -
Web
->動的Webプロジェクト
を選択して次へ>
をクリック - プロジェクト名、ターゲット・ランタイムを入力して
完了
をクリック- プロジェクト名:
TodoServlet
- ターゲット・ランタイム:
Tomcat8 (Java8)
- プロジェクト名:
1 | 2 | 3 |
---|---|---|
HelloServletの作成
まずは動作確認を兼ねて、非常にシンプルな Servlet を作成します。
Servletクラスの作成
-
パッケージ・エクスプローラー
でsrc
を右クリックし新規
->その他...
を選択 -
Web
->サーブレット
を選択して次へ>
をクリック - Javaパッケージ、クラス名を入力して
完了
をクリック- Javaパッケージ:
todo.controller
- クラス名:
HelloServlet
- Javaパッケージ:
Servletの実行
-
パッケージ・エクスプローラー
でTodoServlet
を右クリックし実行
->サーバーで実行
をクリック -
Tomcat v8.0 サーバー
を選択して完了
をクリック- コンソールに
Tomcat
の起動ログが出力され、しばらくするとEclipseの内蔵ブラウザが表示されます。 -
HTTPステータス 404 - /TodoServlet/
というページが表示されます。
- コンソールに
- URLを
http://localhost:8080/TodoServlet/HelloServlet
として Enterキーを押してください。-
Served at: /TodoServlet
というページが表示されれば正常にServletが実行できています。
-
1 | 2 | 3 |
---|---|---|
index.jspの作成
つづいて、JSPの動作確認を行います。
JSPファイルの作成
-
パッケージ・エクスプローラー
でWebContent
を右クリックし新規
->その他...
を選択 -
Web
->JSPファイル
を選択して次へ>
をクリック - ファイル名を入力して
完了
をクリック- ファイル名: index.jsp
- 以下のように編集します。
-
<title>
を変更 -
<body>
に 日時を出力するようコードを追加
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
<%= new java.util.Date() %>
</body>
</html>
Eclipseの内蔵ブラウザかご自身が普段使用しているブラウザで http://localhost:8080/TodoServlet/
にアクセスします。
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v1.0.0
ServletとJSPの作成方法および動作確認を行いました。
Eclipseがクラスやファイルの雛形を作成してくれるので、肉付けだけすればWebアプリケーションが作成できそうな感じですね。
ServletとJSPの連携
JSPはTomcatにてServletに変換されて実行されています。
したがって、Servletで出来ることは基本的にJSPでも出来るはずです。
しかしながら、HTMLにコードを埋め込むJSPに詳細なロジックまで詰め込んでしまうと、途端に見通しの悪いソースコードになってしまいます。
- 詳細なロジックは Servlet にまとめ、その処理結果を JSP に引き渡す
- JSP は受け取った内容をどのように表示するのか、に専念する
という役割分担を行うことで見通しが良くなり、開発をスムーズに進められます。
簡単な例を用いて、ServletとJSPの連携方法について解説します。
ServletからJSPにforwardする
まずは、単純に Servlet でリクエストを受け取り、JSPの内容を返すように実装します。
index.jspを移動させる
直接JSPにアクセスされないように、 WEB-INF
の下に index.jsp
を移動させます。
今後、JSPファイルが複数になった場合に管理が容易になるように WEB-INF/view
フォルダを作成し、その下にJSPファイルを配置することにします。
-
パッケージ・エクスプローラー
でWebContent/WEB-INF
を右クリックし新規
->フォルダ
を選択 - フォルダー名を入力して
完了
をクリック- フォルダー名:
view
- フォルダー名:
-
パッケージ・エクスプローラー
でWebContent/index.jsp
を右クリックしリファクタリング
->移動
を選択 -
WebContent/WEB-INF/view
を選択してOK
をクリック
HelloServletの修正
クライアント (ブラウザ) から HelloServlet
にリクエストがあると、 doGet
メソッドが実行されます。
RequestDispatcher#forward
メソッドを使用すると、処理を他のServletやJSPに転送することができます。
今回は /HelloServlet
にリクエストが来ると、先程の WEB-INF/view/index.jsp
に転送するようにしています。
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
String view = "/WEB-INF/view/index.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(view);
dispatcher.forward(request, response);
}
サーバービューの表示とTomcatの再起動
-
ウィンドウ
->ビューの表示
->その他
を選択 -
サーバー
を選択してOK
をクリック -
サーバー
タブが表示されるので(▶)
をクリック
Tomcatが再起動され、Servletの変更が反映されます。
ブラウザで http://localhost:8080/TodoServlet/HelloServlet
にアクセスし、index.jsp
の内容が表示されることを確認します。
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.0.0
ServletからJSPに値を渡す
ServletからJSPに値を渡すには HttpServletRequest#setAttribute
メソッドを使用します。
第1引数にキー、第2引数に値をセットします。
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// JSPに適当な文字列を渡す
request.setAttribute("foo", "bar");
// JSPにforward
String view = "/WEB-INF/view/index.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(view);
dispatcher.forward(request, response);
}
Servletで setAttribute
された値は JSPの request.getAttribute
メソッドで取得できます。
request
オブジェクトは JSP で特に宣言をしなくても使用可能になっています。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
<%= request.getAttribute("foo") %>
</body>
</html>
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.1.0
参考: EL式
Expression Language の略。
${...}
と書くことで式が評価・出力されます。
詳細な解説はこちらをご覧ください > 【初心者向け】EL式 - Qiita
EL式では、request.getAttribute
の記載を省略して値を取得できます。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
${foo}
</body>
</html>
JSPからServletに値を渡す
JSPにテキストボックスを配置し、入力された値をServletで受け取ってみましょう。
JSPでユーザーに何かしらの入力を受け付けるためには form
タグと input
タグを利用します。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
<%= request.getAttribute("foo") %>
<form method="post" action="./HelloServlet">
何か入力して: <input type="text" name="hoge">
<button type="submit">送信</button>
</form>
</body>
</html>
form
タグの method
属性で submit
ボタンが押された際に、どのHTTPメソッドを使用するかを指定します。
get
か post
を使用することになります。
action
属性は送信先のURLを指定します。
現在ブラウザに表示されているURLからの相対パスで指定するのが良いでしょう。
絶対パスで指定した場合、本番環境と開発環境でURLが異なる場合などの対応が困難になります。
get
メソッドはブラウザからリクエストがあった際に使用していますので、formからのデータ受け取りは post
メソッドを使用します。
input
タグを使用してテキストボックスを表示します。
button
タグのtype
属性をsubmit
とすると、ボタンクリック時にform
のaction
属性に指定されたURLに対してmethod
属性で指定されたHTTPメソッドでform
タグ内の各要素の値を送信します。
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// formから値を取得
String value = request.getParameter("hoge");
System.out.println(value);
doGet(request, response);
}
ユーザーがボタンをクリックすると、doPost
メソッドに処理が移ります。
form
に入力された値は HttpServletRequest#getParameter
メソッドで取得できます。
引数には input
タグのname
属性を指定します。
今回は name="hoge"
の値を取得し、コンソールに出力した後に doGet
メソッドを実行しています。
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.2.0
JSP -> Servlet -> JSP
JSPからServletへの連携とServletからJSPへの連携を組み合わせて、Webアプリケーションっぽい動きを実装してみます。
- GETでアクセスがあると "こんにちは、Guest さん!" というメッセージと、名前入力フォームを表示する
- 名前入力して送信ボタンをクリックすると Servlet に入力内容をPOSTする
- "Guest" をPOSTされた名前に置き換え、フォームを非表示とする
名前入力フォームの準備
まずは「こんにちは」のメッセージと名前入力フォームを用意します。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
こんにちは、<%= request.getAttribute("userName") %> さん!
<form method="post" action="./Hello">
名前を入力してください: <input type="text" name="name">
<button type="submit">送信</button>
</form>
</body>
</html>
初回アクセス時の変数初期化
初回にGETでアクセスしてきた際は "Guest" と表示したいので、userName
に "Guest" をセットしておきます。
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("userName", "Guest");
String view = "/WEB-INF/view/index.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(view);
dispatcher.forward(request, response);
}
入力データの受け取り
フォームに入力された名前を受け取り、userName
を更新します。
日本語、中国語、韓国語などの言語 (Chinese, Japanese, Korean の頭文字から CJK、あるいはベトナム語を加えて CJKV と呼ばれます) を扱う場合、文字コードを適切に指定しないと文字化けが発生しますので注意してください。
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 文字コードの指定
request.setCharacterEncoding("utf-8");
// formから値を取得
String name = request.getParameter("name");
request.setAttribute("userName", name);
doGet(request, response);
}
入力データの表示
初回アクセス時の変数初期化 にて userName
を問答無用で初期化してしまっています。
このままでは、せっかく doPost
でセットした値が "Guest" に上書きされてしまうので、修正しましょう。
userName
が null
か空っぽの場合のみ、"Guest" をセットするようにします。
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = (String) request.getAttribute("userName");
if (name == null || "".equals(name)) {
request.setAttribute("userName", "Guest");
}
String view = "/WEB-INF/view/index.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(view);
dispatcher.forward(request, response);
}
入力フォームの非表示
一度名前を入力したら、入力フォームは不要なので隠してしまいます。
userName
が "Guest" の場合のみ、入力フォームが表示されるようにします。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
<% String userName = (String) request.getAttribute("userName"); %>
こんにちは、<%= userName %> さん!
<% if ("Guest".equals(userName)) { %>
<form method="post" action="./HelloServlet">
名前を入力してください: <input type="text" name="name">
<button type="submit">送信</button>
</form>
<% } %>
</body>
</html>
動作確認
1 | 2 |
---|---|
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.3.0
クロスサイト・スクリプティング (XSS) の脆弱性
実は、先程作成したWebアプリケーションには クロスサイト・スクリプティング (XSS) の脆弱性があります。
XSSとは 他人のWebサイトに悪意のあるスクリプトを埋め込む 事です。
実際に名前フォームに <script>alert("アホ");</script>
と入力してください。
Google Chrome の場合、以下のように ERR_BLOCKED_BY_XSS_AUDITOR
というエラー画面が表示されます。
ブラウザによってはスクリプトの動作がブロックされず、アホ
というダイアログが表示されるでしょう。
XSSの回避方法
XSSの原因は、ユーザーが入力したデータをそのままHTMLに出力していることにあります。
XSSの対策として有効な方法は、<
や >
といった文字をタグとして認識させないようにします。
このような処理のことを エスケープ と言います。
PHPやJavaScriptにはエスケープ処理を行う関数が標準で提供されていますが、Java (JSP) にはありません。
自作することも可能ですが、バグが混入するなども考えられるので、実績あるライブラリを導入するのが良いと思われます。
また、入力チェックを行い <
や >
といった文字を入力させない、という方法を検討する方もいらっしゃるかもしれませんが、XSS対策の良い方法とは言えません。
今回のようなシンプルなフォームであれば入力チェックも容易ですが、複雑なWebアプリケーションで漏れなく確実に入力チェックを実施するのは実装もテストも大変です。入力チェックを迂回する方法が絶対に無いようにするのも難しいでしょう。
やはり、XSS対策としては出力時にエスケープするのが簡単・確実です。
taglibの導入
JSPには タグライブラリ
という仕組みがあります。
JSPの処理をカプセル化することでJSPの見通しを良くし、機能の再利用性を高めることができます。
標準的な機能を集めた JSTL (JSP Standard Tag Library) が提供されていますので、それを導入します。
手順 | 画像 |
---|---|
Apache Tomcat® - Apache Taglibs Downloadsから4つのjarファイルをダウンロードします。 | |
Eclipseの /WebContent/WEB-INF/lib にダウンロードした 4つの jar をドラッグ&ドロップします。確認メッセージが表示されるので、ファイルをコピー を選択してOK をクリックします。 |
|
lib フォルダにファイルが存在することを確認します。 |
※ Tomcatが起動中であれば、 サーバー タブで再起動が必要です。
index.jsp の修正
<%@ taglib %>
ディレクティブにて JSTL を読み込んでいます。
出力部分を <%= %>
から <c:out>
タグに変更しています。<c:out>
タグはデフォルトでエスケープ処理を行います。
参考: JSTLリファレンス:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
<% String userName = (String) request.getAttribute("userName"); %>
こんにちは、<c:out value="${userName}" /> さん!
<% if ("Guest".equals(userName)) { %>
<form method="post" action="./HelloServlet">
名前を入力してください: <input type="text" name="name">
<button type="submit">送信</button>
</form>
<% } %>
</body>
</html>
動作確認
1 | 2 |
---|---|
ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.4.0
参考
ServletとJSPの動作について確認し、非常にシンプルなWebアプリケーションで Servlet <-> JSP の連携方法を取り上げました。
また、最後に紹介したXSS対策は非常に重要なので、Webアプリケーションを作成する際は常に注意をしてください。