1.HTTP とサーブレット
学習目標
- リクエストとレスポンスのやり取りを説明できる
- HTTP リクエストとレスポンスの構成を読み取れる
HttpServletを継承しdoGetをオーバーライドして、レスポンスを返せる- URL のパスとサーブレットの対応を設定できる
- クエリパラメーターでリクエストに付いた値を受け取れる
1. HTTP
Section titled “1. HTTP”ブラウザーがサーバーに送る要求を リクエスト、サーバーが返す応答を レスポンス と呼びます。ブラウザーが 1 つのリクエストを送ると、サーバーは 1 つのレスポンスを返します。
リクエストとレスポンスの形式は HTTP で定められています。HTTP は、リクエストにどの情報をどの順で書き、レスポンスをどう構成するかを規定するプロトコルです。
1-1. リクエストの構成
Section titled “1-1. リクエストの構成”リクエストは、次のような形式の文字列です。
GET /hello HTTP/1.1Host: example.comAccept: text/html最初の行は リクエストライン で、3 つの要素からなります。
| 要素 | 例 | 意味 |
|---|---|---|
| メソッド | GET | リソースに対する操作の種類 |
| パス | /hello | 対象のリソース |
| プロトコル | HTTP/1.1 | 使う HTTP のバージョン |
メソッド は操作の種類を表します。リソースの取得には GET、フォームでの送信には POST を使い、この章では GET を扱います。パス は、サーバー上のどのリソースを対象にするかを表します。
リクエストラインに続く行は ヘッダー で、ヘッダー名と値を : で区切って補足情報を表します。Host は接続先のサーバー、Accept はブラウザーが受け取れる形式を表します。
1-2. レスポンスの構成
Section titled “1-2. レスポンスの構成”レスポンスは、次のような形式の文字列です。
HTTP/1.1 200 OKContent-Type: text/html; charset=UTF-8
<h1>Hello</h1>最初の行は ステータスライン で、処理結果を表します。200 OK の 200 が status code、OK がその意味を表す reason phrase です。status code は処理結果を 3 桁の数値で表します。
| status code | reason phrase | 意味 |
|---|---|---|
200 | OK | 成功した |
404 | Not Found | 要求されたパスが見つからない |
500 | Internal Server Error | サーバー側の処理でエラーが起きた |
ステータスラインに続く行はヘッダーです。ヘッダーの後ろの空行をはさんで、ボディ が続きます。ボディには、ブラウザーに表示させる HTML などの中身が入ります。
1-3. Content-Type
Section titled “1-3. Content-Type”レスポンスのヘッダー Content-Type は、ボディがどの形式のデータかを表します。ブラウザーは、この値を見てボディの扱い方を決めます。
Content-Type: text/html; charset=UTF-8text/html はボディが HTML であること、charset=UTF-8 は文字コードが UTF-8 であることを表します。Content-Type が text/html であれば、ブラウザーはボディを HTML として解釈し、画面に描画します。charset が実際の文字コードと合っていないと、日本語が文字化けします。
2. サーブレット
Section titled “2. サーブレット”リクエストを受け取ってレスポンスを返す処理を、Java では サーブレット として記述します。サーブレットは、次の 3 つの要素で構成します。
@WebServlet("担当するパス")public class クラス名 extends HttpServlet { @Override protected void doGet(...) { // レスポンスを組み立てる処理 }}@WebServlet でこのサーブレットが担当するパスを指定し、HttpServlet を継承して、リクエストを処理する doGet をオーバーライドします。/hello への GET リクエストに HTML を返すサーブレットは、次のようになります。
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("/hello")public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter(); out.println("<h1>Hello</h1>"); out.println("<p>最初のサーブレットです</p>"); }}HelloServlet が HttpServlet を extends し、doGet を @Override する形は、継承 で書いた、親クラスを継承して子クラスでメソッドをオーバーライドする形と同じです。doGet の protected も、HttpServlet の doGet が protected で宣言されているので、オーバーライド として同じシグネチャに合わせたものです。
2-1. doGet によるレスポンスの組み立て
Section titled “2-1. doGet によるレスポンスの組み立て”doGet は、1 件の GET リクエストを処理してレスポンスを返すメソッドです。引数の request から送られてきた値を読み取り、response に返す内容を書き込みます。戻り値が void なのは、結果を戻り値ではなく response に書き込んで返すためです。
HelloServlet の doGet は request を読まず、response にレスポンスを次の手順で組み立てます。
-
Content-Type を設定する
response.setContentType("text/html; charset=UTF-8")で、ボディが UTF-8 の HTML であることを Content-Type ヘッダーに設定します。 -
ボディの出力先を取得する
response.getWriter()がボディへの出力先PrintWriterを返します。これを変数outで受けます。 -
ボディを書き込む
out.println(...)でボディに HTML を 1 行ずつ書き込みます。複数行を書くときはout.printlnを繰り返します。
doGet の宣言の throws IOException は、getWriter での出力が チェック例外 IOException を投げる可能性があるためです。
2-2. サーブレットのライフサイクル
Section titled “2-2. サーブレットのライフサイクル”HelloServlet のコードには、new HelloServlet() も doGet() の呼び出しもありません。インスタンスの生成も doGet の呼び出しも、サーバーが行うためです。
| タイミング | サーバーが行うこと |
|---|---|
| 起動後、最初に必要になったとき | インスタンスを 1 つ生成する |
| リクエストが届くたび | doGet を呼び出す |
インスタンスは 1 つだけ生成され、/hello への複数のリクエストは同じインスタンスで処理されます。リクエストが届くたびに、サーバーが request と response を用意して doGet に渡します。
HttpServlet は、リクエストの HTTP メソッドに応じて呼ぶメソッドを振り分けます。GET なら doGet、POST なら doPost で、サーブレットには処理したい HTTP メソッドに対応するものを定義します。このとき実際に動くのは、HttpServlet の doGet ではなくオーバーライドした HelloServlet の doGet です。これは 多態性 が継承で働く実例です。
3. パスとサーブレットの対応
Section titled “3. パスとサーブレットの対応”HelloServlet に付けた @WebServlet("/hello") は、このサーブレットが /hello へのリクエストを処理することを指定しています。
リクエストのパスごとに、対応するサーブレットが決まります。ブラウザーで /hello を開くと、サーバーは /hello に対応する HelloServlet の doGet を呼び、そのレスポンスを返します。別のパスを処理するには、そのパスを @WebServlet に指定したサーブレットを別に定義します。1 つのパスに対応するサーブレットは 1 つです。
3-1. サーブレットの登録と起動
Section titled “3-1. サーブレットの登録と起動”作成したサーブレットを動作させるには、プロジェクトの準備 で作成したプロジェクトのサーバーに、そのサーブレットを登録します。
-
サーブレットを登録する
メインクラス(
@SpringBootApplicationが付いたクラス)に@ServletComponentScanを追加します。これで@WebServletを付けたサーブレットがサーバーに登録され、リクエストが届くようになります。HelloApplication.java @SpringBootApplication@ServletComponentScanpublic class HelloApplication {public static void main(String[] args) {SpringApplication.run(HelloApplication.class, args);}} -
アプリケーションを起動する
メインクラスの
mainメソッドの上に表示されるRunをクリックして、サーバーを起動します。ターミナルにStarted HelloApplicationと表示されれば、ポート8080で待ち受けている状態です。 -
ブラウザーで確認する
ブラウザーで
http://localhost:8080/helloを開くと、HelloServletが返した HTML が表示されます。
サーブレットのコードを書き換えたときは、ターミナルの停止ボタン(赤い四角)でサーバーを止め、Run で起動し直すと変更が反映されます。
4. クエリパラメーター
Section titled “4. クエリパラメーター”ここまでのサーブレットは、パスが同じなら常に同じレスポンスを返しました。リクエストに含まれる値に応じてレスポンスを変えるには、GET では クエリパラメーター としてパスに値を付加して送信します。
4-1. クエリパラメーターの構成
Section titled “4-1. クエリパラメーターの構成”クエリパラメーターは、パスの後ろに ? を続けて、パラメーター名と値を = でつなげて書きます。
/hello?name=Tanaka? 以降がクエリパラメーターで、name というパラメーター名に Tanaka という値を渡しています。複数のパラメーターは & でつなぎます。
/hello?name=Tanaka&lang=janame に Tanaka、lang に ja の 2 つを渡しています。
4-2. パラメーターの取り出し
Section titled “4-2. パラメーターの取り出し”リクエストに含まれるクエリパラメーターは、request.getParameter で取得します。引数にパラメーター名を渡すと、その値が文字列で返ります。
String 変数 = request.getParameter("パラメーター名");/hello?name=Tanaka を受け取り、name の値をボディに含めるサーブレットです。
@WebServlet("/hello")public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String name = request.getParameter("name");
response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<h1>Hello, " + name + "</h1>"); }}request.getParameter("name") が、リクエストの name=Tanaka から Tanaka を取り出します。/hello?name=Tanaka を開くと、ボディは次のようになります。
Hello, TanakagetParameter が返すのは文字列です。数値として扱う値は、Integer.parseInt で int に変換してから使います。