Help us understand the problem. What is going on with this article?

JSP & Servlet 入門

More than 1 year has passed since last update.

はじめに

  • Javaの基本的な構文について理解している前提です。
  • Eclipse (Pleiades) を使用している前提です。

開発環境のセットアップについては Eclipseの超基本的な使い方 - Qiita を参照してください。

まずは、ServletとJSPの動作について確認します。(当記事)
その後、TODOアプリの開発を通してJSP&ServletによるWebアプリ開発の基本について解説しようと思います。(次回予定)

プロジェクトの作成

  • パッケージ・エクスプローラー を右クリックし 新規 -> プロジェクト... を選択
  • Web -> 動的Webプロジェクト を選択して 次へ> をクリック
  • プロジェクト名、ターゲット・ランタイムを入力して 完了 をクリック
    • プロジェクト名: TodoServlet
    • ターゲット・ランタイム: Tomcat8 (Java8)
1 2 3
プロジェクトの作成(1) プロジェクトの作成(2) プロジェクトの作成(3)

HelloServletの作成

まずは動作確認を兼ねて、非常にシンプルな Servlet を作成します。

Servletクラスの作成

  • パッケージ・エクスプローラーsrc を右クリックし 新規 -> その他... を選択
  • Web -> サーブレット を選択して 次へ> をクリック
  • Javaパッケージ、クラス名を入力して 完了 をクリック
    • Javaパッケージ: todo.controller
    • クラス名: HelloServlet

Servletの実行

  • パッケージ・エクスプローラーTodoServlet を右クリックし 実行 -> サーバーで実行 をクリック
  • Tomcat v8.0 サーバー を選択して 完了 をクリック
    • コンソールに Tomcat の起動ログが出力され、しばらくするとEclipseの内蔵ブラウザが表示されます。
    • HTTPステータス 404 - /TodoServlet/ というページが表示されます。
  • URLを http://localhost:8080/TodoServlet/HelloServlet として Enterキーを押してください。
    • Served at: /TodoServlet というページが表示されれば正常にServletが実行できています。
1 2 3
スクリーンショット 2017-06-22 14.51.48.png スクリーンショット 2017-06-22 14.52.29.png スクリーンショット 2017-06-22 14.53.09.png

index.jspの作成

つづいて、JSPの動作確認を行います。

JSPファイルの作成

  • パッケージ・エクスプローラーWebContent を右クリックし 新規 -> その他... を選択
  • Web -> JSPファイル を選択して 次へ> をクリック
  • ファイル名を入力して 完了 をクリック
    • ファイル名: index.jsp
  • 以下のように編集します。
    • <title> を変更
    • <body> に 日時を出力するようコードを追加
index.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>
    <%= new java.util.Date() %>
</body>
</html>

Eclipseの内蔵ブラウザかご自身が普段使用しているブラウザで http://localhost:8080/TodoServlet/ にアクセスします。

スクリーンショット 2017-06-22 15.13.18.png

ここまでのソースコード: 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 に転送するようにしています。

HelloServlet.java
/**
 * @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引数に値をセットします。

HelloServlet.java
/**
 * @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 で特に宣言をしなくても使用可能になっています。

index.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 の記載を省略して値を取得できます。

index.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>
    ${foo}
</body>
</html>

JSPからServletに値を渡す

JSPにテキストボックスを配置し、入力された値をServletで受け取ってみましょう。

JSPでユーザーに何かしらの入力を受け付けるためには form タグと input タグを利用します。

index.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") %>

    <form method="post" action="./HelloServlet">
        何か入力して: <input type="text" name="hoge">
        <button type="submit">送信</button>
    </form>
</body>
</html>

form タグの method属性で submitボタンが押された際に、どのHTTPメソッドを使用するかを指定します。
getpost を使用することになります。

action属性は送信先のURLを指定します。
現在ブラウザに表示されているURLからの相対パスで指定するのが良いでしょう。
絶対パスで指定した場合、本番環境と開発環境でURLが異なる場合などの対応が困難になります。

getメソッドはブラウザからリクエストがあった際に使用していますので、formからのデータ受け取りは postメソッドを使用します。

inputタグを使用してテキストボックスを表示します。

buttonタグのtype属性をsubmitとすると、ボタンクリック時にformaction属性に指定されたURLに対してmethod属性で指定されたHTTPメソッドでformタグ内の各要素の値を送信します。

HelloServlet.java
/**
 * @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アプリケーションっぽい動きを実装してみます。

  1. GETでアクセスがあると "こんにちは、Guest さん!" というメッセージと、名前入力フォームを表示する
  2. 名前入力して送信ボタンをクリックすると Servlet に入力内容をPOSTする
  3. "Guest" をPOSTされた名前に置き換え、フォームを非表示とする

名前入力フォームの準備

まずは「こんにちは」のメッセージと名前入力フォームを用意します。

index.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("userName") %> さん!

    <form method="post" action="./Hello">
        名前を入力してください: <input type="text" name="name">
        <button type="submit">送信</button>
    </form>
</body>
</html>

初回アクセス時の変数初期化

初回にGETでアクセスしてきた際は "Guest" と表示したいので、userName に "Guest" をセットしておきます。

HelloServlet.java
/**
 * @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 と呼ばれます) を扱う場合、文字コードを適切に指定しないと文字化けが発生しますので注意してください。

HelloServlet.java
/**
 * @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" に上書きされてしまうので、修正しましょう。

userNamenull か空っぽの場合のみ、"Guest" をセットするようにします。

HelloServlet.java
/**
 * @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" の場合のみ、入力フォームが表示されるようにします。

index.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>
    <% 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
スクリーンショット 2017-06-27 10.19.16.png スクリーンショット 2017-06-27 10.19.31.png

ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.3.0

クロスサイト・スクリプティング (XSS) の脆弱性

実は、先程作成したWebアプリケーションには クロスサイト・スクリプティング (XSS) の脆弱性があります。
XSSとは 他人のWebサイトに悪意のあるスクリプトを埋め込む 事です。

実際に名前フォームに <script>alert("アホ");</script> と入力してください。

Google Chrome の場合、以下のように ERR_BLOCKED_BY_XSS_AUDITOR というエラー画面が表示されます。

スクリーンショット 2017-06-27 10.29.59.png

ブラウザによってはスクリプトの動作がブロックされず、アホ というダイアログが表示されるでしょう。

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ファイルをダウンロードします。 スクリーンショット 2017-06-27 10.49.17.png
Eclipseの /WebContent/WEB-INF/lib にダウンロードした 4つの jar をドラッグ&ドロップします。確認メッセージが表示されるので、ファイルをコピーを選択してOKをクリックします。 スクリーンショット 2017-06-27 10.52.45.png
libフォルダにファイルが存在することを確認します。 スクリーンショット 2017-06-27 10.53.02.png

※ Tomcatが起動中であれば、 サーバー タブで再起動が必要です。

index.jsp の修正

<%@ taglib %> ディレクティブにて JSTL を読み込んでいます。
出力部分を <%= %> から <c:out> タグに変更しています。<c:out>タグはデフォルトでエスケープ処理を行います。

参考: JSTLリファレンス:

index.jsp
<%@ 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
スクリーンショット 2017-06-27 11.02.14.png スクリーンショット 2017-06-27 11.02.23.png

ここまでのソースコード: Kazunori-Kimura/todo-servlet at v2.4.0

参考


ServletとJSPの動作について確認し、非常にシンプルなWebアプリケーションで Servlet <-> JSP の連携方法を取り上げました。
また、最後に紹介したXSS対策は非常に重要なので、Webアプリケーションを作成する際は常に注意をしてください。

Kazunori-Kimura
フリーランスのプログラマーです。JavaScript好き。 C#, Javaもやれます。 お仕事ください。
https://kazunori-kimura.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした