サーバーサイド編(追加)
POSTへの対応
HTTP の POST に対応します。POST で送信されたデータを標準出力に表示します。
ソースコードの修正
src/main/java/com/example/HelloServlet.javaを修正します。
HelloServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
/**
* 共通処理: GET/POSTのパラメータを同様に取得して応答を生成します。
*/
private void process(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 文字コードをUTF-8に統一
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=UTF-8");
Map<String, String> resMap = new HashMap<>();
try {
Map<String, String[]> params = req.getParameterMap();
if (params.isEmpty()) {
throw new IllegalArgumentException("No parameters");
}
// 受信パラメータをログ出力
for (String key : params.keySet()) {
for (String value : params.get(key)) {
System.out.println(key + " = " + value);
}
}
resp.setStatus(HttpServletResponse.SC_OK);
resMap.put("RET", "0000");
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
System.out.println("Raw query string: " + req.getQueryString());
resMap.put("RET", "9999");
resMap.put("ERROR", e.getMessage());
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
// JSONで応答
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(resMap);
try (PrintWriter out = resp.getWriter()) {
out.println(json);
out.flush();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
}
ソースコードを修正したらデプロイします。
動作確認
PowerShell のcurlコマンドはInvoke-WebRequestコマンドのエイリアス(別名)です。Linux や MacOS で使うcurlコマンドとパラメータや動作が異なります。API テストや Web 開発は本物のcurl.exeを使う方が便利です。
winget コマンドでインストールします。
winget install cURL.cURL
次のコマンドで HTTP GET の動作を確認します。
curl -G http://localhost:8080/servlet-demo/hello
--data-urlencode "name=鈴木太郎" --data-urlencode "age=37"
次のコマンドで、HTTP POST の動作を確認します。
curl -X POST http://localhost:8080/servlet-demo/hello
`-H "Content-Type: application/x-www-form-urlencoded"` -d "name=鈴木太郎&age=37"
ロガーの導入
アプリケーションにログの機能を追加します。ログの機能を実現する方には様々な方法があります。今回は、SLF4J と Logback を使います。SLF4J (Simple Logging Facade for Java) は、ロギングのインタフェースを提供します。実際のログ出力は別の実装に委譲します。Logback は SLF4J の推奨実装です。設定は logback.xml で行います。
依存関係の追加
pom.xmlに Logback の依存関係を追加します。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.x</version>
</dependency>
Logback の設定ファイル
ログを標準出力(コンソール)とファイル/var/log/tomcat10/app.logに出力します。
ファイルsrc/main/resource/logback.xmlを作成します。
logback.xml
<!-- src/main/resources/logback.xml -->
<configuration>
<!-- コンソール出力 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- JST表示。%d{ISO_OFFSET_DATE_TIME,Asia/Tokyo} 等も可 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Tokyo} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 日毎ローテーションのファイル出力 -->
<appender name="APP_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Tokyo} %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- ルートロガー -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="APP_FILE"/>
</root>
<!-- パッケージ単位で詳細化(例:自作コードはDEBUG) -->
<logger name="com.example" level="DEBUG"/>
</configuration>
アプリケーションの修正
SLF4J を使うようにsrc/main/java/com/example/HelloServlet.javaを修正します。
HelloServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger; // ロギング用に追加
import org.slf4j.LoggerFactory; // ロギング用に追加
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
// ロガーの生成
private static final Logger LOG = LoggerFactory.getLogger(HelloServlet.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private void process(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=UTF-8");
Map<String, Object> resMap = new HashMap<>();
Map<String, Object> echo = new HashMap<>();
// JSTタイムスタンプ(ISO_OFFSET_DATE_TIME)
String ts = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
try {
Map<String, String[]> params = req.getParameterMap();
for (String key : params.keySet()) {
String[] values = params.get(key);
for (String v : values) {
LOG.debug("{} = {}", key, v); // ログ出力
}
echo.put(key, values.length == 1 ? values[0] : Arrays.asList(values));
}
resMap.put("RET", "0000");
resMap.put("params", echo);
resMap.put("timestamp", ts);
resp.setStatus(HttpServletResponse.SC_OK); // 成功時のみOKに設定
} catch (RuntimeException e) {
// 予期せぬ実装バグなど
LOG.error("Unexpected server error", e); // ログ出力
resMap.put("RET", "9999");
resMap.put("ERROR", "Unexpected server error");
resMap.put("params", echo);
resMap.put("timestamp", ts);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500
}
// JSON生成と書き込み
String json = MAPPER.writeValueAsString(resMap);
try (PrintWriter out = resp.getWriter()) {
out.println(json);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
LOG.info("Request from {} {}", req.getMethod(), req.getRequestURI()); // ログ出力
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
LOG.info("Request from {} {}", req.getMethod(), req.getRequestURI()); // ログ出力
process(req, resp);
}
}
Tomcat にデプロイします。
ロギング機能の動作確認
curl コマンドで HTTP GET を実行します。
curl.exe -G http://localhost:8080/servlet-demo/hello
--data-urlencode "name=鈴木太郎" --data-urlencode "age=37"
応答が確認できます。
{
"RET": "0000",
"params": {
"name": "鈴木太郎",
"age": "37"
},
"timestamp": "2025-11-09T20:11:10.572529924+09:00"
}
curl コマンドで HTTP POST を実行します。
curl -X POST http://localhost:8080/servlet-demo/hello
`-H "Content-Type: application/x-www-form-urlencoded"` -d "name=鈴木太郎&age=37"
応答が確認できます。
{
"RET": "0000",
"params": {
"name": "鈴木太郎",
"age": "37"
},
"timestamp": "2025-11-09T21:16:29.768942856+09:00"
}
Linux でログファイルを確認します。
$ tail /var/log/tomcat10/app.log
2025-11-09 21:20:44.559 INFO com.example.HelloServlet - Request from GET /servlet-demo/hello
2025-11-09 21:20:44.560 DEBUG com.example.HelloServlet - name = 鈴木太郎
2025-11-09 21:20:44.560 DEBUG com.example.HelloServlet - age = 37
2025-11-09 21:20:47.667 INFO com.example.HelloServlet - Request from POST /servlet-demo/hello
2025-11-09 21:20:47.668 DEBUG com.example.HelloServlet - name = 鈴木太郎
2025-11-09 21:20:47.669 DEBUG com.example.HelloServlet - age = 37
データをJSONでPOST
これまでのプログラムはクエリパラメータでデータを送信していました。フロントエンド(ブラウザ)でJavaScriptを使えば、フォームに入力されたデータをJSONでPOSTできます。JSONの処理はOSSのFasterXML/jacksonを使います。
HelloServlet.java
package com.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(HelloServlet.class);
private static final ObjectMapper MAPPER = new ObjectMapper(); // jacksonのORM (Object Relation Mapper)
private void process(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=UTF-8");
Map<String, Object> resMap = new HashMap<>();
Map<String, Object> echo = new LinkedHashMap<>(); // ★ 受信JSONのエコー用
// JSTタイムスタンプ(ISO_OFFSET_DATE_TIME)
String ts = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
try {
// ★ JSON対応:Content-Type を見て JSON の場合はボディをパース
String contentType = req.getContentType();
boolean isJson = contentType != null && contentType.toLowerCase(Locale.ROOT).contains("application/json");
if (isJson) {
// ボディを Map として受け取る(オブジェクト想定)
try {
// InputStreamとマッピングする型を指定してObjectMapper#TypeReferenceオブジェクトを渡す
echo = MAPPER.readValue(req.getInputStream(), new TypeReference<Map<String, Object>>() {});
// 空ボディ等で readValue が失敗した場合は空オブジェクト扱い
if (echo == null) {
echo = new LinkedHashMap<>();
}
} catch (Exception parseEx) {
// 仕様上は検証エラーにしないので、ログのみ出して空オブジェクトで進行
LOG.debug("Failed to parse JSON body: {}", parseEx.toString());
echo = new LinkedHashMap<>();
}
} else {
// ★ 非JSONのときも仕様上はエラーにしない(空で返す)。
LOG.debug("Non-JSON request received (Content-Type: {}). Echo as empty object.", contentType);
}
// レスポンス作成
resMap.put("RET", "0000"); // ★ 成功
resMap.put("params", echo); // ★ JSONのエコー
resMap.put("timestamp", ts);
resp.setStatus(HttpServletResponse.SC_OK);
} catch (RuntimeException e) {
// 予期せぬ実装バグなど
LOG.error("Unexpected server error", e);
resMap.put("RET", "9999");
resMap.put("ERROR", "Unexpected server error");
resMap.put("params", echo);
resMap.put("timestamp", ts);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500
}
// JSON生成と書き込み
String json = MAPPER.writeValueAsString(resMap);
try (PrintWriter out = resp.getWriter()) {
out.println(json);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
LOG.info("Request from {} {}", req.getMethod(), req.getRequestURI());
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
LOG.info("Request from {} {}", req.getMethod(), req.getRequestURI());
process(req, resp);
}
}
curlコマンドでJSONをPOSTしてテストします。nameとageを送信します。
curl.exe -X POST http://localhost:8080/servlet-demo/hello
-H "Content-Type: application/json" -d "{\"name\":\"田中太郎\",\"age\":28}"
次の応答が表示されます。
{
"RET": "0000",
"params": {
"name": "田中太郎",
"age": 28
},
"timestamp": "2025-11-11T11:18:53.864661845+09:00"
}
課題
課題1
HTML のフォームで「氏名」と「年齢」を入力するホームページを作成します。「送信」ボタンをクリックすると、HTTP GET でデータを送信します。
応答の JSON を解析して、RET が0000なら「OK」を表示します。0000以外は「NG」を表示します。
課題2
HTML のフォームで「氏名」と「年齢」を入力するホームページを作成します。「送信」ボタンをクリックすると、HTTP POST でデータを送信します。
応答の JSON を解析して、RET が0000なら「OK」を表示します。0000以外は「NG」を表示します。
課題3
- ブラウザーで身長
[\m]と体重[kg]を入力します。「計算」ボタンをクリックすると、データをサーバーに送信します。 - サーバは、受信したデータで BMI を計算します。BMI の値(小数点以下2桁)とメッセージをJSONで返信します。
- ブラウザーは、受信した JSON を解析して、BMI 値とメッセージをホームページに表示します。