Skip to content
Playground

3.フォームとセッション

学習目標

  • フォームを構成する <form><label><input>・送信ボタンの役割を説明できる
  • フォームから送信された値を POST で受け取れる
  • HTTP がリクエストごとに独立していることを説明できる
  • セッションで、複数のリクエストにまたがる値を保持できる

ここまでのサーブレットは、サーバーが用意した値を表示するだけでした。利用者が入力した値を受け取るには、入力欄と送信ボタンを持つ画面が必要です。入力欄と送信ボタンからなる HTML を フォーム と呼びます。

名前を入力するフォーム form.html です。

form.html
<!DOCTYPE html>
<html>
<head>
<title>名前の入力</title>
</head>
<body>
<form action="/greet" method="post">
<label for="name">名前:</label>
<input type="text" id="name" name="name">
<button type="submit">送信</button>
</form>
</body>
</html>

フォームは <form> の中に、ラベルの <label>、入力欄の <input>、送信ボタンの <button> を並べて作ります。それぞれの役割は次のとおりです。

タグ役割
<form>入力欄と送信ボタンをまとめ、送信先と送信方法を指定する
<label>入力欄が何を入力する欄かを示す見出し
<input>利用者が値を入力する欄
<button type="submit">押すとフォームの内容を送信するボタン

<form> には 2 つの属性を指定しています。action は入力値の送信先のパス、method は送信に使う HTTP メソッドです。この例では、送信先が /greet、メソッドが post です。

<input>type="text" は 1 行のテキスト入力欄を表します。name="name"name 属性は、入力された値に付けるパラメーター名です。送信時に「どの欄の値か」を識別するため、<input> には name を付けます。

<label> は、その入力欄が何を入力する欄かを画面に表示します。<label>for<input>id を同じ値にすると、2 つが結びつきます。この例ではどちらも name で、ラベルの「名前:」をクリックすると入力欄にカーソルが移ります。id は同じページの中で要素を識別する属性で、name(送信される値のパラメーター名)とは役割が異なります。

フォームは値を埋め込まない静的な HTML なので、src/main/resources/static.html ファイルとして置きます。src/main/resources/static は、Spring Boot が静的ファイルを配信する場所です。ここに置いたファイルはファイル名がそのままパスになり、form.html なら /form.html でブラウザーから開けます。

type="submit" のボタンを押すと、ブラウザーは次の処理を行います。まずフォーム内の各 <input> の値を name=値 の形に集めます。次にそれを <form>action のパスへ、method の HTTP メソッドで送信します。最後に、返ってきたレスポンスを新しいページとして表示します。

form.html田中 と入力して送信すると、name=田中/greetPOST で送られ、サーバーが返した HTML に画面が切り替わります。

リクエストのメソッドには GETPOST があり、HTTP とサーブレット では GET を扱いました。2 つは値の送信先が異なります。

メソッド用途値の送信先
GETリソースの取得URL のクエリパラメーター(/greet?name=田中
POSTデータの送信リクエストボディ

GET は値が URL に現れます。利用者の入力のように URL に残したくない値には適しません。フォームの送信には POST を使い、値は URL に現れないリクエストボディに入れて送ります。form.htmlmethod="post" は、この POST を指定しています。

GETdoGet で処理したように、POSTdoPost で処理します。送られた値は、GET のクエリパラメーターと同じく request.getParameter で取得します。引数には、<input>name を渡します。

/greet への POST を受け取り、入力された名前であいさつを返す GreetServlet です。

GreetServlet.java
package com.example.hello;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/greet")
public class GreetServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 入力欄 name の値を取得する
String name = request.getParameter("name");
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<p>こんにちは、" + name + "さん</p>");
}
}

request.getParameter("name") が、<input name="name"> に入力された値を返します。田中 と入力して送信すると、name田中 になり、次の HTML が表示されます。

<p>こんにちは、田中さん</p>

/form.html を開いて名前を入力し、送信ボタンを押すと、GreetServletdoPost が呼ばれ、入力した名前を含むあいさつが表示されます。

ここでは表示を out.println で組み立てています。JSP で扱ったように、本来は表示を JSP に分けますが、この章はフォームの受け取りとセッションに集中するため、表示は最小限の out.println にとどめます。

リクエストのメソッドに応じて doGetdoPost が呼び分けられるのは、多態性 の働きです。同じパスでも、GET でアクセスすれば doGet、フォームを POST で送信すれば doPost が呼ばれます。

HTTP では、個々のリクエストは互いに独立しています。サーバーは 1 つのリクエストにレスポンスを返すと、そのやり取りで使った値を保持しません。次のリクエストは、前のリクエストの内容を知らない状態で届きます。

そのため、GreetServlet が受け取った名前も、あいさつを表示した時点で失われます。続けて別のページを開いても、サーバーに先ほどの名前は残っていません。

リクエストサーバーが保持する値
/greet(1 回目)入力された 田中
別のページ(2 回目)1 回目の名前は残っていない

利用者の名前のように、複数のページにまたがって使いたい値は、リクエストの独立を越えて保持する仕組みが要ります。それがセッションです。

セッション は、利用者ごとにサーバー側で値を保持する仕組みです。1 つのリクエストが終わっても値が残り、同じ利用者からの次のリクエストで取り出せます。リクエストの独立を越えて、利用者という単位で値を持ち越せます。

複数のブラウザーが同時にアクセスしても、サーバーは利用者ごとに別々の値を保持します。誰の値かを区別するため、サーバーは利用者ごとに保管領域を用意し、それぞれに セッション ID という識別子を割り当てます。

セッション ID は、最初のリクエストへのレスポンスでブラウザーに渡されます。ブラウザーはこの ID を保存し、次からのリクエストで毎回サーバーに送り返します。サーバーは送られてきた ID を見て、どの利用者の保管領域かを判別します。

セッション ID でブラウザーと保管領域を結びつける

ブラウザーが ID を送り返すのは、Cookie の働きです。Cookie は、サーバーがブラウザーに保存させる小さな値で、ブラウザーは同じサーバーへのリクエストのたびに自動で送り返します。セッション ID はこの Cookie に入れて渡されるため、利用者が ID を意識することはありません。

セッションは request.getSession() で取得します。戻り値の HttpSession が、その利用者の保管領域です。値の保存には、request と同じく setAttribute を使います。保存する値には、あとで取り出すための属性名を付けます。

HttpSession session = request.getSession();
session.setAttribute("属性名", 値);

GreetServlet で、受け取った名前をセッションに保存します。request ではなくセッションに入れることで、この値は次のリクエストにも残ります。

GreetServlet.java
@WebServlet("/greet")
public class GreetServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 入力された名前を取得し、セッションに保存する
String name = request.getParameter("name");
HttpSession session = request.getSession();
session.setAttribute("name", name);
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<p>こんにちは、" + name + "さん</p>");
out.println("<a href=\"/mypage\">マイページ</a>");
}
}

session.setAttribute("name", name) で、入力された名前を name という属性名でセッションに保存します。属性名の "name" は取り出すときの目印で、保存する値(利用者が入力した名前)とは別のものです。HttpSessionjakarta.servlet.http.HttpSessionimport が加わります。あわせて、別のページ /mypage へのリンクを表示しています。

保存した値は、別のサーブレットから getAttribute で取り出せます。マイページを表示する MyPageServlet で、セッションに保存された名前を取得します。importGreetServlet と同じく jakarta.servlet 系と HttpSessionjava.ioPrintWriterIOException です。

Object 値 = session.getAttribute("属性名");
MyPageServlet.java
@WebServlet("/mypage")
public class MyPageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// セッションから名前を取り出す
HttpSession session = request.getSession();
String name = (String) session.getAttribute("name");
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<p>" + name + "さんのマイページ</p>");
}
}

getAttribute の戻り値は Object 型です。セッションには文字列でも数値でも保存できるため、取り出すときの型は Object に統一されています。Object のままでは String のメソッドを呼べないので、保存したときの型に戻してから使います。

変数の前に (型) を付けて、指定した型として扱う記法を キャスト と呼びます。"name" には String の値を保存したので、(String) でキャストして受け取ります。保存したときと違う型でキャストすると、実行時にエラーになります。

/greet で名前を送信したあと、リンクから /mypage を開くと、別のサーブレットである MyPageServlet が、GreetServlet の保存した名前を取り出して表示します。

<p>田中さんのマイページ</p>

/greet/mypage は別々のリクエストですが、同じ利用者からのリクエストなので、セッションを通じて名前が引き継がれます。