HTTP (Hypertext Transfer Protocol) は、Webブラウザとサーバー間でデータをやり取りするための基盤となるプロトコルです。このプロトコルには、GETやPOSTといった様々なリクエストメソッドが存在し、それぞれが特定の目的とパラメータの伝送方法を持っています。特に、GETとPOSTはWebアプリケーションで頻繁に利用されますが、パラメータの送信方法と、それをWebサーバー(例: Tomcat)がどのように解釈するかに違いがあります。
GETリクエストでのパラメータ伝送
GETリクエストの主な目的は、サーバーからリソースを取得することです。この際、必要なパラメータは通常、URLのクエリ文字列として付与されます。例えば、/search?keyword=example&page=1 のように、URLの疑問符(?)以降にキー=値の形式で連結されます。
ServletにおけるGETパラメータの取得
Java Servlet環境において、HttpServletRequest.getParameter()メソッドは、GETリクエストのクエリ文字列からパラメータを容易に取得できます。しかし、GETリクエストでリクエストボディにパラメータを含めた場合、getParameter()がそのボディ内のデータを取得しないことが一般的です。以下のServletコードは、この挙動を検証するために使用できます。
package com.example.web;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ParameterEchoServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("--- リクエスト受信開始 ---");
// クエリ文字列の取得
String queryStr = request.getQueryString();
System.out.println("受信クエリ文字列: " + (queryStr != null ? queryStr : "なし"));
// パラメータの取得を試みる
String requestedId = request.getParameter("userId");
String requestedPassword = request.getParameter("userPassword");
String requestedAge = request.getParameter("userAge");
String requestedAddress = request.getParameter("userAddress");
System.out.println("getParameter(\"userId\"): " + (requestedId != null ? requestedId : "取得不可"));
System.out.println("getParameter(\"userPassword\"): " + (requestedPassword != null ? requestedPassword : "取得不可"));
System.out.println("getParameter(\"userAge\"): " + (requestedAge != null ? requestedAge : "取得不可"));
System.out.println("getParameter(\"userAddress\"): " + (requestedAddress != null ? requestedAddress : "取得不可"));
// クライアントへ簡単な応答を返す
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().write("パラメータ処理済みです。サーバーログを確認してください。");
System.out.println("--- リクエスト処理完了 ---");
}
}
このServletに対して、例えばツールを使ってGETリクエストを送信する際、URLにuserId=testuser&userPassword=password123を含め、かつリクエストボディにuserAge=30&userAddress=Tokyoのような形式でパラメータを送信したとします。この場合、ServletのログにはuserIdとuserPasswordの値は表示されますが、リクエストボディに含めたuserAgeとuserAddressはgetParameter()では取得されないことが確認できます。
生のHTTPリクエストの観察
HTTPプロトコル自体は、技術的にはGETリクエストでもリクエストボディを持つことを禁止していません。しかし、Webサーバーやフレームワークの実装が、GETリクエストのボディを処理しないのが一般的です。以下のカスタムソケットサーバーを使って、生のHTTPリクエストを傍受することで、このプロトコルと実装の間の違いを理解できます。
package com.example.network;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class HttpRequestLogger {
private static final int LISTEN_PORT = 8081; // 監視ポート
private static final String BIND_ADDRESS = "127.0.0.1"; // バインドアドレス
public static void main(String[] args) {
try (ServerSocket serverListenSocket = new ServerSocket(LISTEN_PORT, 5, InetAddress.getByName(BIND_ADDRESS))) {
System.out.println("HTTPリクエスト監視サーバーが " + BIND_ADDRESS + ":" + LISTEN_PORT + " で起動しました。");
while (true) { // 継続的にリクエストを監視
try (Socket clientConnection = serverListenSocket.accept()) {
System.out.println("\n--- クライアント接続受付: " + clientConnection.getInetAddress() + " ---");
InputStreamReader isr = new InputStreamReader(clientConnection.getInputStream(), StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr);
StringBuilder receivedRequestBuilder = new StringBuilder();
String line;
int contentLength = 0;
// ヘッダーの読み込み
while ((line = reader.readLine()) != null && !line.isEmpty()) {
receivedRequestBuilder.append(line).append("\n");
if (line.toLowerCase().startsWith("content-length:")) {
try {
contentLength = Integer.parseInt(line.substring("content-length:".length()).trim());
} catch (NumberFormatException e) {
System.err.println("Content-Lengthヘッダーの解析に失敗しました: " + e.getMessage());
}
}
}
receivedRequestBuilder.append("\n"); // ヘッダーとボディの間の空行
// ボディの読み込み (Content-Lengthが存在する場合)
if (contentLength > 0) {
char[] bodyChars = new char[contentLength];
int bytesRead = reader.read(bodyChars, 0, contentLength);
if (bytesRead != -1) {
receivedRequestBuilder.append(bodyChars, 0, bytesRead);
}
}
System.out.println("受信したHTTPリクエスト:\n" + receivedRequestBuilder.toString());
// クライアントへシンプルなHTTPレスポンスを返す
String responseBody = "<h1>リクエストを正常に受信しました!</h1>";
String httpResponse = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Content-Length: " + responseBody.getBytes(StandardCharsets.UTF_8).length + "\r\n" +
"\r\n" +
responseBody;
OutputStream outputStream = clientConnection.getOutputStream();
outputStream.write(httpResponse.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
System.out.println("--- リクエスト処理完了 ---");
} catch (IOException e) {
System.err.println("クライアント接続処理中にエラーが発生しました: " + e.getMessage());
}
}
} catch (IOException e) {
System.err.println("サーバーの起動またはI/Oエラー: " + e.getMessage());
System.exit(1);
}
}
}
このHttpRequestLoggerに対してGETリクエストを送信し、URLクエリとリクエストボディの両方にパラメータを含めると、サーバーログにはリクエストボディの内容も明示的に表示されます。これは、HTTPプロトコルレベルではGETリクエストでもボディが伝送されうることを示していますが、TomcatなどのWebサーバーがGETリクエストのボディをgetParameter()の対象としない実装になっていることを裏付けています。
POSTリクエストでのパラメータ伝送
POSTリクエストは、主にサーバーにデータを送信(作成または更新)するために使用されます。GETリクエストとは異なり、POSTリクエストではパラメータをURLのクエリ文字列として送ることも、リクエストボディに含めて送ることも、両方行うことも可能です。
ServletにおけるPOSTパラメータの取得
POSTリクエストの場合、HttpServletRequest.getParameter()メソッドは、URLのクエリ文字列とリクエストボディの両方からパラメータを適切に取得します。上記のParameterEchoServletをPOSTリクエストで利用した場合の挙動を見てみましょう。
例えば、POSTリクエストを送信する際に、URLにuserId=postuser&userPassword=securepassを含め、同時にリクエストボディにContent-Type: application/x-www-form-urlencodedとしてuserAge=25&userAddress=Osakaのような形式でパラメータを送信したとします。この場合、ServletのログにはuserId、userPassword、userAge、userAddressの全ての値がgetParameter()によって正常に取得されていることが確認できます。
生のHTTPリクエストの観察
POSTリクエストにおける生のHTTPプロトコルも、上記のHttpRequestLoggerで確認できます。POSTリクエストの場合、リクエストヘッダーにContent-LengthとContent-Type(例: application/x-www-form-urlencodedやapplication/jsonなど)が明示され、その後に指定されたContent-Length分のデータがリクエストボディとして続きます。HttpRequestLoggerの出力は、このリクエストボディの内容がサーバーに正確に伝送されていることを明確に示します。
まとめ
HTTPプロトコルにおいて、GETとPOSTはパラメータの伝送方法において重要な違いがあります。プロトコルレベルでは、GETリクエストでもリクエストボディにデータを付与することは可能ですが、Tomcatのような一般的なWebサーバーやServletコンテナの実装では、GETリクエストのHttpServletRequest.getParameter()はURLのクエリ文字列からのパラメータのみを処理し、リクエストボディ内のデータは通常無視します。
これに対し、POSTリクエストの場合、getParameter()メソッドはURLクエリ文字列と、application/x-www-form-urlencodedなどの適切なContent-Typeが指定されたリクエストボディの両方からパラメータを抽出して処理します。この挙動の違いは、Webアプリケーション設計において、データの種類(機密性、量)、目的(取得か送信か)、キャッシュの必要性などを考慮し、適切なHTTPメソッドとパラメータ伝送方法を選択するための重要な指針となります。