前回は、サーブレットからJSPにデータを渡して表示するところまでをやりました。

今回は、多くのWebアプリケーションでその前処理にあたる部分、すなわちフォームからデータをサーブレットに渡すところをやってみます。

1. フォームの復習

HTMLのフォームの復習です。

下図4.1のように新しく「04Form」というフォルダ(ディレクトリ)を用意してその中にウェルカムファイル「index.html」を用意してください。

図4.1 フォルダ構成

index.htmlは以下の通りです。

<!DOCTYPE html>
<html>
    <head>
        <title>index.html</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <form action="/03_JavaWebText/AddServlet" method="get">
            <input type="text" name="num1"/><br>
            <input type="text" name="num2"/><br>
            <button type="submit">送信</button>
        </form>
    </body>
</html>
  • このフォームのaction属性で指定している飛び先は「/プロジェクト名」から始まっています。このようなパス指定方式を何と呼ぶのでしたか?
あなたの答え:
  • このフォームのhttpメソッドは何ですか?
あなたの答え:
  • このメソッドの指定は省略できますか?
あなたの答え:
  • フォームの中の部品はいくつありますか?
あなたの答え:
  • それぞれどのような部品でしょうか?
あなたの答え:
  • 「name=」で付けられた名前はサーブレットでどのような用途で使われますか?
あなたの答え:

このname属性に設定された名前はリクエストパラメータとして、文字列でサーブレットに送られます。

送られた文字列をサーブレットで受け取るための記述が「request.getParameter("パラメータ名")」です。

フォームは「何を、どのメソッドで、どのプログラムに送るのか」を書くものだと理解しましょう。上記の例でいえば、「num1,mum2という名前のつけられた1行テキストに入力された文字列を、getメソッドで、/03_JavaWebText/AddServletに送る」ということが書かれているのですね。

2. Servletの復習

次に下図4.2のようにServletを用意します。

「p04」というパッケージを作って「AddServlet.java」としました。

図4.2 ソースパッケージの構成

ソースコードは以下のとおりです。

package p04;

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(urlPatterns = {"/AddServlet"})
public class AddServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, 
            HttpServletResponse response)
            throws ServletException, IOException {
        
        int num1 = Integer.parseInt(request.getParameter("num1"));
        int num2 = Integer.parseInt(request.getParameter("num2"));
        
        request.setAttribute("ans", num1 + num2);
        request.getRequestDispatcher("/03Servlet/ans.jsp")
                .forward(request, response);
    }
}
  • このServletはどのようなurlPatternで呼び出されますか?
あなたの答え:
  • リクエストパラメータにはどの様な値がどの様な名前で格納されていますか?
あなたの答え:
  • リクエスト属性にはどの様な値がどの様な名前で格納されますか?
あなたの答え:

リクエストパラメータとリクエスト属性は名前が似ているので混同しないように注意しましょう。

  • フォワード先のJSPのパス名とファイル名をそれぞれ答えなさい。
あなたの答え:

※今回は3章(03Servlet)のJSPを再利用しているわけですね。

  • フォワードした後のアドレスバーの表記はどうなりますか? ただし、フォームにはそれぞれ1と2を入力したものとします。
あなたの答え:http://localhost:8080/

2.1.フォームからServletを呼び出す際の記述方法

今回のポイントはHTMLの9行目、actionで指定している送信先です。

<form action="/03_JavaWebText/AddServlet">

/プロジェクト名/URLPatternという表記です。

このようにServletはパッケージ名やクラス名とは無関係に URLPattern で呼び出すことができます

決して、以下のようにパッケージ名は必要ではありませんから注意してください。パッケージ構成は良く変更されるのでURLパターンだけでTomcatから呼び出されるようになっているのです。さらにはクラス名も無関係だったことを思い出して下さい。

<form action="/03_JavaWebText/p04/AddServlet">

3. httpリクエストの読み方

ここでは下図4.4を使いhttpリクエスト の読み方について解説します。

①は通信プロトコルです。

②はFQDN【Fully Qualified Domain Name】で絶対ドメイン名と呼ばれます。
FQDNはwww.saycon.co.jpのようにホスト名(www)とドメイン名(saycon.co.jp)をドットでつなぐ表記が一般的です。しかし、今回はホスト名のlocalhostだけです。なお、localhostの代わりに直接IPアドレス(例.192.168.0.1)を指定することもできますので試してみましょう。

③はポート番号です。8080番はTomcatでデフォルトで使用され、httpの80番に見た目が似ていること、ウェルノウンポート以外であることから使われています。

④コンテキスト名はWebアプリケーションの名前です。プロジェクト名がそのままコンテキスト名になります。

⑤URLパターンはServletが呼び出される際の名前でした。本研修では@WebServletアノテーションで設定しています。

⑥getリクエストの場合は最後にパラメータが付きます。

図4.4 httpリクエストの読み方

また、下図4.5(a)のとおり④と⑤の間の/をコンテキストルートと呼びます。

ウェルカムファイルの場合はファイル名を省略できますからWebページフォルダの直下の「index.html」は下図4.5の(b)の方法でも指定できます。

混乱しがちなのがServletの指定方法です。Webのパス指定とは無関係にURLパターンで指定します。 (下図4.5の(c)

図4.5 Webページがコンテキストルートにあたる

なお、「WEB-INF」というディレクトリがありますから探してみてください。

このディレクトリには外部に公開しない内部処理用の重要なファイルを格納します。「WEB-INF」 は外部から直接アクセスできないようになっています。(時々操作を誤ってこのフォルダにファイルを移動してしまうことがありますので気をつけてください)

4. getParameterメソッド

先程のAddServlet.javaの20行目を見てください。

int num1 = Integer.parseInt(request.getParameter("num1"));

  • Integer.parseInt()はインスタンスメソッドでしょうか? それともstaticメソッドでしょうか?
あなたの答え:
  • このメソッドは引数の文字列を何に変換しますか?
あなたの答え:

忘れてしまった人は、Integerクラスまで戻って復習してください。

今回の注目点はこの部分です。

request.getParameter("num1")

requestのgetParameterメソッドを使っています。

のメソッド はフォームから送られたリクエストのインスタンスに入っている文字列を取り出すことができます。ここでフォームから送られるデータはすべて文字列として送られることを思い出してください。

フォームから送られるデータは1つとは限りません。そのため“num1”などのテキストフィールドに付けた名前で区別して受け取る必要があるのです。

今回の処理の概要を下図4.6にまとめておきます。

図4.6 計算処理の全体像

注)上記図4.6の①ではURLPatternを「Add」にしていることに注意。

5. フォームに入力された日本語文字列を表示する

今度はフォームに入力された日本語を表示するWebアプリケーションを作成してみます。フォームに名前を入れると名前を呼んで「こんにちは」と表示するプログラムです。

入力:「今井」

出力:「こんにちは今井さん」

というイメージです。

まずは、入力フォームjapanese_form.htmlです。

<!DOCTYPE html>
<html>
    <head>
        <title>japanese_form.html</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <form action="/03_JavaWebText/Japanese">
            <input type="text" name="name"/><br>
            <button type="submit">送信</button>
        </form>
    </body>
</html>
  • 送信ボタンを押したあとのブラウザのURL表記はどうなりますか?
あなたの答え:
  • どのようなフォーム部品を使って、どのような名前で文字列データを送っていますか?
あなたの答え:

次にControllerとしてのServletです。

package p04;

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(urlPatterns = {"/Japanese"})
public class Japanese extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        String name = request.getParameter("name");
        
        request.setAttribute("name", name);
        request.getRequestDispatcher("/04Form/japanese.jsp").forward(request, response);
    }
}
  • 18行目では何をしていますか?
あなたの答え:
  • 20行目では何をしていますか?
あなたの答え:
  • 21行目では何をしていますか?
あなたの答え:

最後にViewとしてのJSPです。

中身は特に今までと変わったところはありません。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>japanese.jsp</title>
    </head>
    <body>
        <h1>こんにちは${requestScope.name}さん</h1>
    </body>
</html>

このように日本語文字列も問題なく表示できました。

※環境によっては文字化けが起こることがありえます。その場合は「便利なFilterを使いたい」という記事の「日本語文字化け対策のFilter」のサンプルコードを参考にしてください。

例題1

上記、Japanese.javaでフォームから確実にデータが送られていることを①printデバッグ、②デバッガを使ったデバッグで確認しなさい。

確かめた結果:

例題2

上記、japanese_form.htmlで「name="name"」となっているところを「name="namae"」と書き換えたとします。そうすると他にどこをどのように書き換える必要がありますか?

あなたの答え:

6. エラー入力を適切に処理する

ユーザーはエンジニアが想定しない使い方をするものです。ここではフォームに想定していない入力がされた場合の対処方法を数当てゲームのWebアプリケーションを使って学びましょう。

ところで、ここまで学ぶと下図4.7の通りMVCモデルの解説のところの数当てゲームも理解できたのではないでしょうか?

図4.7 答えが5で正解したケース

入力画面、ModelとViewは以下の通りでした。(再掲)

package p02;

import java.util.Random;

public class Kazuate {

    private int answer;

    public Kazuate() {
        Random random = new Random();
        this.answer = random.nextInt(10);
    }

    public String checkTheAnswer(int guess) {

        if (answer == guess) {
            return "あたり";

        } else if (answer > guess) {
            return "もっと大きいよ";

        } else {
            return "もっと小さいよ";
        }
    }

    public int getTheAnswer() {
        return this.answer;
    }

    public void setTheAnswer(int answer) {
        this.answer = answer;
    }
}

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset=UTF-8">
        <title>result.jsp</title>
    </head>
    <body>
        <h1>ゲームの結果は!</h1>
        ${requestScope.message}<br>
        答えは${requestScope.answer}<br>
    </body>
</html>

このとき、入力値を0-9に制限するにはどのファイルのどの部分を変更すればよいでしょうか?

いろいろな方法が考えられますが、ひとまずservlet(と入力用HTML)だけに手を加える方法を試してみます。

package p04;

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;
import p02.Kazuate;

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String str = request.getParameter("guess");

        int guess = Integer.parseInt(str);

        Kazuate kazuate = new Kazuate();
        String message;

        if (guess < 0 || guess > 9) {
            message = "0-9の整数を入力してください";
        } else {
            message = kazuate.checkTheAnswer(guess);
        }
        request.setAttribute("message", message);
        request.setAttribute("answer", kazuate.getTheAnswer());

        request.getRequestDispatcher("/02MVC/result.jsp").forward(request, response);
    }
}

確かにこれで想定外の整数値をエラーにすることができました。

しかし、フォームに「あ」のような文字列や「5.0」のような浮動小数点数を入力されると下図4.8のNumberFormatExceptionがでます。

図4.8 NumberFormatException

このような不適切な入力に対処するにはどうしたら良いのでしょうか?

JavaSEで学んだ例外処理が使えます。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import p02.Kazuate;

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String str = request.getParameter("guess");

        int guess = -1;

        try {
            guess = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            request.setAttribute("errorMessage", "整数以外が入力されました");
            request.getRequestDispatcher("/04Form/result3.jsp").forward(request, response);
        }

        Kazuate kazuate = new Kazuate();
        String message;

        if (guess < 0 || guess > 9) {
            message = "0-9の整数を入力してください";
        } else {
            message = kazuate.checkTheAnswer(guess);
        }
        request.setAttribute("message", message);
        request.setAttribute("answer", kazuate.getTheAnswer());

        request.getRequestDispatcher("/02MVC/result.jsp").forward(request, response);
    }
}

※整数以外が入力された時の「答えは」という文言が気になる方は、後でJSTLを学ぶと条件に応じてこのような文字列を消せるようになりますのでしばらくお待ち下さい。

ここでのポイントは整数以外が入力された場合、19行目でServletの処理が終了しているという点です。22行目以降の処理はなされないという点です。

  • GameServlet2からGameServlet3の変更点を説明してください。
あなたの答え:

7. ハイパーリンクを使って送られたデータを受け取る

ここでは、天気予報プログラムのプロトタイプを考えてみましょう。全国のユーザーが現在の天気を送るとリアルタイムで全国の天気が分かり、また、予測も可能になるというシステムのプロトタイプです。

図4.9 天気予報サイトのイメージ

リンクを使ってデータを送ることができます。以下のように?の後ろにリクエストパラメターを付けて送ります。

<a href="/03_JavaWebText/Weather2?weather= 晴れ ">晴れ</a>

このとき「?weather="晴れ"」のように文字列扱いしたくなりますが、 「?weather=晴れ」 で大丈夫です。

<!DOCTYPE html>
<html>
    <head>
        <title>link.html</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <p>今の天気を教えて下さい</p>
        <a href="/03_JavaWebText/Weather1?weather=晴れ">晴れ</a>
        <a href="/03_JavaWebText/Weather1?weather=曇り">曇り</a>
        <a href="/03_JavaWebText/Weather1?weather=雨">雨</a>
    </body>
</html>

では、もしも仮に「Weather1.java」にdoGetメソッドの代わりにdoPostメソッドがあったとしたらどうなると思いますか?

ServletとJSPには変更を加える必要はありませんので試してみてください。

下図4.10のようなエラーになります。

図4.10 サポートされていないメソッドのエラー画面

aタグを使ったリンクは常にGETメソッドになることを覚えておいてください。

元来、HTTPのGETメソッドは主にWebページや他のリソース(例えば、画像やCSSファイル)を取得するために使用されてきました。ブラウザがリンクをクリックするか、URLを直接入力すると、ブラウザはそのURLに対応するリソースを取得するためにGETリクエストをサーバーに送信するのです。GET=ゲットする、POST=投稿するという元々の意味を考えれば2つのhttpメソッドを記憶するのは容易になると思います。

例題3

上記link.htmlに雪の場合を加えなさい。

例題4

これまで作成してきた数当てゲームをリンクを使って実現しなさい。ただし、再利用できるファイルはできるだけ再利用すること。

図4.11

8. ボタンを使って送られたデータを受け取る

テキストボックスからリンクに入力方法を変えたことでユーザーの使いやすさが向上しました。さらに入力チェック(バリデーション)も楽になりました。ここでは、さらにユーザー入力がしやすいようにボタンを使ってデータを送ります。ボタンはスマートフォンでも押しやすいですからね。

Weather1.javaには変更点はありませんが、念のため再掲しました。

package p04;

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(urlPatterns = {"/Weather1"})
public class Weather1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        String weather = request.getParameter("weather");
        
        request.setAttribute("weather", weather);
        request.getRequestDispatcher("/04Form/weather.jsp").forward(request, response);
    }
}

<!DOCTYPE html>
<html>
    <head>
        <title>button.html</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <p>今の天気を教えて下さい</p>
        <form action="/03_JavaWebText/Weather1">
            <button type="submit" name="weather" value="晴れ">晴れ</button>
            <button type="submit" name="weather" value="曇り">曇り</button>
            <button type="submit" name="weather" value="雨">雨</button>
        </form>
    </body>
</html>

例題5

上記button.htmlに雪の場合を加えなさい。

例題6

数当てゲームをボタンを使って実現しなさい。

図4.12

サーバーを再起動するかどうかの判断基準

JSPやサーブレットを実行する際にサーバーを再起動するかどうかを尋ねられることがあります。

基本的に再起動しないほうが実行は早いです。しかし、再起動しないとキャッシュが邪魔をして変更が反映されないことがあります。

サーブレットを変更した場合は自動的にコンパイルされますので再起動は不要なことが多いです。

JSPを変更した場合は再起動が必要なことがあります。


<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ Servletはパッケージ名やクラス名とは無関係に URLPattern で呼び出すことができる

□ フォームから送られるデータはすべて文字列として送られるため数値にするにはInteger.parseIntメソッドを使う

□ requestのgetParameter はフォームから送られたリクエストのインスタンスに入っている文字列をname属性を手がかりに取り出す

□ プログラムでサニタイズとは、JavaScriptなどの特別な意味を持つ可能性のある文字列を置き換えて無効化することをいう

□ if文や例外処理をつかって入力エラーを適切に処理する

□ aタグを使ったリンクは常にGETメソッドになる

今回はフォームからデータをServletに渡す方法について学びました。

フォームにはラジオボタンやセレクトボックスもあります。最終課題でそれらのフォームも使いたい方は、以下の補講を見てください。

補講:その他のフォームからデータをServletに渡す

次回は、ログイン処理の実装とセッション属性を学んでいきます。

ログイン処理が書けるとかなりWebアプリケーションらしくなります。

「フォームから送ったデータをServletで受け取る」 最後までお読みいただきありがとうございます。