Skip to content
Playground

1.HTTP とサーブレット

学習目標

  • リクエストとレスポンスのやり取りを説明できる
  • HTTP リクエストとレスポンスの構成を読み取れる
  • HttpServlet を継承し doGet をオーバーライドして、レスポンスを返せる
  • URL のパスとサーブレットの対応を設定できる
  • クエリパラメーターでリクエストに付いた値を受け取れる

ブラウザーがサーバーに送る要求を リクエスト、サーバーが返す応答を レスポンス と呼びます。ブラウザーが 1 つのリクエストを送ると、サーバーは 1 つのレスポンスを返します。

ブラウザーがリクエストを送り、サーバーがレスポンスを返す

リクエストとレスポンスの形式は HTTP で定められています。HTTP は、リクエストにどの情報をどの順で書き、レスポンスをどう構成するかを規定するプロトコルです。

リクエストは、次のような形式の文字列です。

GET /hello HTTP/1.1
Host: example.com
Accept: text/html

最初の行は リクエストライン で、3 つの要素からなります。

要素意味
メソッドGETリソースに対する操作の種類
パス/hello対象のリソース
プロトコルHTTP/1.1使う HTTP のバージョン

メソッド は操作の種類を表します。リソースの取得には GET、フォームでの送信には POST を使い、この章では GET を扱います。パス は、サーバー上のどのリソースを対象にするかを表します。

リクエストラインに続く行は ヘッダー で、ヘッダー名と値を : で区切って補足情報を表します。Host は接続先のサーバー、Accept はブラウザーが受け取れる形式を表します。

レスポンスは、次のような形式の文字列です。

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
<h1>Hello</h1>

最初の行は ステータスライン で、処理結果を表します。200 OK200status codeOK がその意味を表す reason phrase です。status code は処理結果を 3 桁の数値で表します。

status codereason phrase意味
200OK成功した
404Not Found要求されたパスが見つからない
500Internal Server Errorサーバー側の処理でエラーが起きた

ステータスラインに続く行はヘッダーです。ヘッダーの後ろの空行をはさんで、ボディ が続きます。ボディには、ブラウザーに表示させる HTML などの中身が入ります。

レスポンスのヘッダー Content-Type は、ボディがどの形式のデータかを表します。ブラウザーは、この値を見てボディの扱い方を決めます。

Content-Type: text/html; charset=UTF-8

text/html はボディが HTML であること、charset=UTF-8 は文字コードが UTF-8 であることを表します。Content-Typetext/html であれば、ブラウザーはボディを HTML として解釈し、画面に描画します。charset が実際の文字コードと合っていないと、日本語が文字化けします。

リクエストを受け取ってレスポンスを返す処理を、Java では サーブレット として記述します。サーブレットは、次の 3 つの要素で構成します。

@WebServlet("担当するパス")
public class クラス名 extends HttpServlet {
@Override
protected void doGet(...) {
// レスポンスを組み立てる処理
}
}

@WebServlet でこのサーブレットが担当するパスを指定し、HttpServlet を継承して、リクエストを処理する doGet をオーバーライドします。/hello への GET リクエストに HTML を返すサーブレットは、次のようになります。

HelloServlet.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("/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>");
}
}

HelloServletHttpServletextends し、doGet@Override する形は、継承 で書いた、親クラスを継承して子クラスでメソッドをオーバーライドする形と同じです。doGetprotected も、HttpServletdoGetprotected で宣言されているので、オーバーライド として同じシグネチャに合わせたものです。

2-1. doGet によるレスポンスの組み立て

Section titled “2-1. doGet によるレスポンスの組み立て”

doGet は、1 件の GET リクエストを処理してレスポンスを返すメソッドです。引数の request から送られてきた値を読み取り、response に返す内容を書き込みます。戻り値が void なのは、結果を戻り値ではなく response に書き込んで返すためです。

HelloServletdoGetrequest を読まず、response にレスポンスを次の手順で組み立てます。

  1. Content-Type を設定する

    response.setContentType("text/html; charset=UTF-8") で、ボディが UTF-8 の HTML であることを Content-Type ヘッダーに設定します。

  2. ボディの出力先を取得する

    response.getWriter() がボディへの出力先 PrintWriter を返します。これを変数 out で受けます。

  3. ボディを書き込む

    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 への複数のリクエストは同じインスタンスで処理されます。リクエストが届くたびに、サーバーが requestresponse を用意して doGet に渡します。

HttpServlet は、リクエストの HTTP メソッドに応じて呼ぶメソッドを振り分けます。GET なら doGetPOST なら doPost で、サーブレットには処理したい HTTP メソッドに対応するものを定義します。このとき実際に動くのは、HttpServletdoGet ではなくオーバーライドした HelloServletdoGet です。これは 多態性 が継承で働く実例です。

HelloServlet に付けた @WebServlet("/hello") は、このサーブレットが /hello へのリクエストを処理することを指定しています。

リクエストのパスごとに、対応するサーブレットが決まります。ブラウザーで /hello を開くと、サーバーは /hello に対応する HelloServletdoGet を呼び、そのレスポンスを返します。別のパスを処理するには、そのパスを @WebServlet に指定したサーブレットを別に定義します。1 つのパスに対応するサーブレットは 1 つです。

作成したサーブレットを動作させるには、プロジェクトの準備 で作成したプロジェクトのサーバーに、そのサーブレットを登録します。

  1. サーブレットを登録する

    メインクラス(@SpringBootApplication が付いたクラス)に @ServletComponentScan を追加します。これで @WebServlet を付けたサーブレットがサーバーに登録され、リクエストが届くようになります。

    HelloApplication.java
    @SpringBootApplication
    @ServletComponentScan
    public class HelloApplication {
    public static void main(String[] args) {
    SpringApplication.run(HelloApplication.class, args);
    }
    }
  2. アプリケーションを起動する

    メインクラスの main メソッドの上に表示される Run をクリックして、サーバーを起動します。ターミナルに Started HelloApplication と表示されれば、ポート 8080 で待ち受けている状態です。

  3. ブラウザーで確認する

    ブラウザーで http://localhost:8080/hello を開くと、HelloServlet が返した HTML が表示されます。

サーブレットのコードを書き換えたときは、ターミナルの停止ボタン(赤い四角)でサーバーを止め、Run で起動し直すと変更が反映されます。

ここまでのサーブレットは、パスが同じなら常に同じレスポンスを返しました。リクエストに含まれる値に応じてレスポンスを変えるには、GET では クエリパラメーター としてパスに値を付加して送信します。

クエリパラメーターは、パスの後ろに ? を続けて、パラメーター名と値を = でつなげて書きます。

/hello?name=Tanaka

? 以降がクエリパラメーターで、name というパラメーター名に Tanaka という値を渡しています。複数のパラメーターは & でつなぎます。

/hello?name=Tanaka&lang=ja

nameTanakalangja の 2 つを渡しています。

リクエストに含まれるクエリパラメーターは、request.getParameter で取得します。引数にパラメーター名を渡すと、その値が文字列で返ります。

String 変数 = request.getParameter("パラメーター名");

/hello?name=Tanaka を受け取り、name の値をボディに含めるサーブレットです。

HelloServlet.java
@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, Tanaka

getParameter が返すのは文字列です。数値として扱う値は、Integer.parseIntint に変換してから使います。