Skip to content
Code Blue プログラミング学習ノート
Java/Servlet/JSP

【Sevlet/JSP】クライアント/サーバーモデルとWebアプリケーションの仕組み

はじめに

Webアプリケーション開発の基礎となるクライアント/サーバーモデルと、JSPがどのようにこのモデルの中で機能するかを理解することは、効果的なWebアプリケーションを作成する上で不可欠です。本記事では、既にHTTPプロトコルの基礎を理解している読者を対象に、JSPを用いたWebアプリケーション開発に必要なクライアント/サーバーモデルの詳細と、その内部動作について詳しく解説します。

クライアント/サーバーモデルの基本概念

クライアントとサーバーの役割分担

Webアプリケーションは、クライアント(ブラウザ)サーバーの明確な役割分担があります。Webアプリケーションは、クライアントとサーバーがそれぞれ異なる役割を担当することで動作しています。

クライアント

クライアントとは主にWebブラウザのことで、画面を表示したり、ユーザーの入力やボタンのクリックを受け付けたりして、その内容をサーバーへ送信します。

サーバー

一方、サーバーは受け取ったリクエストをもとに必要な処理を実行し、データベースから情報を取得・更新したり、ビジネスロジックを実行したりします。そして、その結果をHTMLなどのレスポンスとしてクライアントへ返します。JSPやServletは、このサーバー側の処理を担当する代表的な技術です。

クライアント/サーバー間の通信フロー

JSPアプリケーションでは、ユーザーがブラウザでURLを入力したりリンクをクリックしたりすると、ブラウザは接続先のサーバーを特定し、HTTPリクエストを送信します。

サーバーではApache TomcatなどのWebサーバーがリクエストを受け取り、JSPコンテナが対象のJSPを実行します。必要に応じてJavaのプログラムを実行したり、データベースと連携して情報を取得したりした後、HTMLを生成します。

最後に、そのHTMLをHTTPレスポンスとしてブラウザへ返し、ブラウザが内容を解析して画面に表示します。この一連の流れによって、動的なWebページが表示されます。

ステートレスな通信の特性

HTTPはステートレスプロトコルであり、各リクエストは独立して扱われます。そのため、サーバーは前回の通信内容を記憶できません。従ってログイン状態や買い物かごの内容などを維持するには状態管理の仕組みが必要です。

JSPやServletではHttpSessionを利用してユーザーごとの情報をサーバー側で管理できます。また、クッキーを利用すると、ユーザーを識別するための情報をブラウザ側に保存できます。

さらに、クッキーが利用できない環境では、セッションIDをURLに付与するURLリライティングという方法でセッションを維持することもできます。

JSPにおけるHTTPプロトコルの詳細実装

JSPのライフサイクルとHTTP

JSPページは、最初のリクエスト時にそのまま実行されるのではなく、まずServletのソースコードへ変換され、その後Javaクラスとしてコンパイルされます。

コンパイルが完了すると、JSPはServletとして動作します。初回のみjspInit()が実行されて初期化が行われ、その後はリクエストを受け取るたびに_jspService()が呼び出され、GETやPOSTなどのリクエストを処理します。

最後に、JSPが不要になるとjspDestroy()が実行され、リソースの解放などの終了処理が行われます。

HTTPリクエストの詳細解析

このコードでは、JSPでHTTPリクエストの情報を取得する方法を学びます。

リクエストラインの解析

request.getMethod()はGETやPOSTなどのリクエストメソッドを取得し、request.getRequestURI()はアクセスされたURLのパス、request.getProtocol()はHTTPのバージョンを取得します。

<%
  String method = request.getMethod(); // "GET", "POST" etc.
  String uri = request.getRequestURI();
  String protocol = request.getProtocol();
%>

ヘッダー情報の取得

request.getHeaderNames()を利用すると、ブラウザから送信されたすべてのヘッダー名を取得でき、request.getHeader()でそれぞれの値を取り出せます。これらの情報を利用することで、ブラウザや通信内容に応じた処理を実装できるようになります。

<%
  Enumeration headerNames = request.getHeaderNames();
  while(headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    String value = request.getHeader(name);
    out.println(name + ": " + value + "
"); } %>

クエリパラメータの処理

request.getParameter()は、テキストボックスなどから送信された1つの値を取得する際に使用します。一方、request.getParameterValues()は、チェックボックスのように同じ名前で複数の値が送信される場合に利用し、値を配列として取得できます。

<%
  // 単一パラメータ取得
  String param = request.getParameter("paramName");

  // 複数値パラメータ取得(チェックボックスなど)
  String[] values = request.getParameterValues("multiParam");
%>

これらのメソッドを使用することで、フォームから送信された入力内容を受け取り、条件分岐やデータベースへの登録など、さまざまなサーバー側の処理を実装できます。

HTTPレスポンスの制御

JSPでHTTPレスポンスを制御する方法についてです。

ステータスコード設定

response.setStatus()は処理結果を表すステータスコードを設定し、sendError()はエラーページを返す際に利用します。

<% response.setStatus(HttpServletResponse.SC_OK); %>
<% response.sendError(HttpServletResponse.SC_NOT_FOUND, "ページが見つかりません"); %>

ヘッダー設定

setHeader()やsetDateHeader()はレスポンスヘッダーを設定し、キャッシュを無効にするなどの制御を行います。

<% 
  response.setHeader("Cache-Control", "no-cache");
  response.setHeader("Pragma", "no-cache");
  response.setDateHeader("Expires", -1);
%>

コンテントタイプ設定

contentTypeではブラウザへ送信するデータの種類や文字コードを指定します。

<%@ page contentType="text/html; charset=UTF-8" %>

リダイレクト処理

sendRedirect()を使用すると、別のページへ自動的に移動させることができます。

<% response.sendRedirect("newLocation.jsp"); %>

これらはJSPでWebページの動作を制御するための基本的な機能です。

2.4 各種HTTPメソッドの実装例

HTTPメソッドに応じて異なる処理を実行する方法について。

GETメソッドの処理

request.getMethod()を使用すると、リクエストがGETかPOSTかを判定できます。GETメソッドではURLに含まれるパラメータを取得し、検索などの処理で利用します。

<%@ page import="java.util.Map" %>
<%
  if ("GET".equals(request.getMethod())) {
    Map params = request.getParameterMap();
    // GETパラメータ処理
  }
%>

POSTメソッドの処理

一方、POSTメソッドではフォームから送信されたユーザー名やパスワードを取得し、ログイン認証などの処理を行います。

<%
  if ("POST".equals(request.getMethod())) {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    // 認証処理など
  }
%>

フォームの実装例

フォームではmethod=”post”を指定することで、入力内容をPOSTメソッドでサーバーへ送信できます。このようにHTTPメソッドを使い分けることで、目的に応じた安全で適切な処理を実装できます。

<form method="post" action="process.jsp">
  <input type="text" name="username">
  <input type="password" name="password">
  <input type="submit" value="ログイン">
</form>

セッション管理と状態維持

セッションの基本概念

HTTPは前回の通信内容を記憶しないため、ログイン状態などを保持するにはセッションが必要です。request.getSession(true)はセッションを取得し、存在しない場合は新しく作成します。

setAttribute()でデータを保存し、getAttribute()で保存した値を取得できます。また、invalidate()を実行するとセッションが削除され、保存されていた情報も破棄されます。

<%
  // セッション取得(存在しなければ新規作成)
  HttpSession session = request.getSession(true);

  // セッション属性設定
  session.setAttribute("user", "username");

  // セッション属性取得
  String user = (String) session.getAttribute("user");

  // セッション無効化
  session.invalidate();
%>

セッションはログイン機能やショッピングカートなどで広く利用される重要な仕組みです。

セッション追跡のメカニズム

  1. クッキーを使用 (JSESSIONID)

最も一般的なのは、JSESSIONIDをクッキーに保存し、ブラウザからの次回以降のリクエストで同じユーザーを識別する方法です。

   <%
     Cookie cookie = new Cookie("JSESSIONID", session.getId());
     cookie.setMaxAge(60*60*24); // 1日
     response.addCookie(cookie);
   %>
  1. URLリライティング

クッキーが利用できない場合は、response.encodeURL()を使用してセッションIDをURLへ付与するURLリライティングを利用できます。

<a href="<%= response.encodeURL("page.jsp") %>">リンク</a>
  1. 隠しフォームフィールド

隠しフォームフィールドにセッションIDを埋め込み、フォーム送信時に引き継ぐ方法もあります。

<input type="hidden" name="jsessionid" value="<%= session.getId() %>">

これらの仕組みによって、HTTPのステートレス性を補い、ユーザーごとの状態を維持できます。

セッションタイムアウト設定

セッションの有効時間を設定について。

web.xml<session-config>での設定方法

アプリケーション全体のセッション有効時間を分単位で設定できます。この例では、30分間操作がないとセッションが自動的に終了します。

<session-config>
  <session-timeout>30</session-timeout> <!-- 分単位 -->
</session-config>

JSPページ内での設定方法

session.setMaxInactiveInterval()を使用すると、特定のセッションだけ有効時間を秒単位で変更できます。この例では1800秒、つまり30分に設定しています。

<% session.setMaxInactiveInterval(1800); // 秒単位 %>

セッションの有効時間を適切に設定することで、セキュリティの向上や不要なセッションの削除につながります。

JSPの高度なHTTP機能

ファイルアップロード処理

以下のコードについて。フォームではenctype="multipart/form-data"を指定することで、ファイルをサーバーへ送信できるようにしています。

サーバー側では、リクエストがファイルアップロードかどうかを確認し、送信されたファイルを取得して保存します。この例では、アップロードされたファイル名を取得し、uploadsフォルダへ保存しています。

<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="java.io.*" %>

<%
  if (ServletFileUpload.isMultipartContent(request)) {
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload upload = new ServletFileUpload(factory);

    List<FileItem> items = upload.parseRequest(request);
    for (FileItem item : items) {
      if (!item.isFormField()) {
        // ファイル処理
        String fileName = new File(item.getName()).getName();
        String filePath = "uploads/" + fileName;
        item.write(new File(filePath));
      }
    }
  }
%>

<form method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit" value="アップロード">
</form>

ファイルアップロード機能は、画像やPDFなどを登録するWebアプリケーションでよく利用される代表的な機能です。

クッキーの操作

Cookieオブジェクトを作成し、addCookie()を使用してブラウザへ送信すると、ユーザーのブラウザに情報が保存されます。

この例では、画面テーマの設定を1年間保存しています。また、request.getCookies()を使用すると、ブラウザから送信されたクッキーを取得でき、名前を確認して必要な値を取り出せます。

<%
  // クッキー作成
  Cookie cookie = new Cookie("userPref", "darkMode");
  cookie.setMaxAge(60*60*24*365); // 1年間有効
  response.addCookie(cookie);

  // クッキー読み取り
  Cookie[] cookies = request.getCookies();
  if (cookies != null) {
    for (Cookie c : cookies) {
      if ("userPref".equals(c.getName())) {
        String value = c.getValue();
        // テーマ設定などに使用
      }
    }
  }
%>

クッキーは、ログイン状態の維持や表示設定など、ユーザーごとの情報を保存するためによく利用されます。

HTTPキャッシュ制御

このコードでは、HTTPキャッシュを制御する方法を学びます。Cache-ControlやPragma、Expiresを設定することで、ブラウザに古いページを保存させず、常に最新の情報を取得できるようにしています。

また、If-Modified-Sinceヘッダーを利用すると、ブラウザが前回取得したデータの更新日時をサーバーへ送信できます。サーバー側で更新がないと判断した場合は、304 Not Modifiedを返し、データを再送信せずに通信量を削減できます。

<%
  // キャッシュ防止
  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  response.setHeader("Pragma", "no-cache");
  response.setDateHeader("Expires", 0);

  // 条件付きGET処理
  long lastModified = /* 最終更新日時を取得 */;
  String ifModifiedSince = request.getHeader("If-Modified-Since");
  if (ifModifiedSince != null && 
      lastModified <= Long.parseLong(ifModifiedSince)) {
    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    return;
  }
  response.setDateHeader("Last-Modified", lastModified);
%>

この仕組みは、Webアプリケーションの表示速度や通信効率を向上させるために利用されます。

実践的なJSPアプリケーションの構築

MVCアーキテクチャの実装

HTTPリクエストを効率的に処理するためのMVCパターンについてです。

リクエスト → コントローラ(Servlet) → モデル(JavaBean) → ビュー(JSP) → レスポンス

コントローラの例 (Servlet)

ブラウザから送られたHTTPリクエストは、まずServletが受け取り、必要な処理を実行します。続いて、JavaBeanなどのモデルがデータの取得や計算などのビジネスロジックを担当します。

@WebServlet("/user")
public class UserController extends HttpServlet {
  protected void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException {
    UserService service = new UserService();
    List users = service.getAllUsers();
    request.setAttribute("users", users);
    request.getRequestDispatcher("/userList.jsp").forward(request, response);
  }
}

JSPビューの例 (userList.jsp)

その結果をJSPへ渡し、JSPが画面を生成してHTMLとしてブラウザへ返します。

<%@ page import="java.util.List" %>
<%@ page import="com.example.User" %>
<%
  List<User> users = (List<User>) request.getAttribute("users");
%>
<table>
  <% for (User user : users) { %>
    <tr>
      <td><%= user.getId() %></td>
      <td><%= user.getName() %></td>
    </tr>
  <% } %>
</table>

役割を分離することで、プログラムが整理され、保守や機能追加を行いやすいWebアプリケーションを開発できるようになります。

RESTfulなJSPアプリケーション

HTTPメソッドを活用した設計はおおよそ以下を用意します。

<%@ page import="javax.servlet.http.HttpServlet" %>
<%
  String method = request.getMethod();
  switch(method) {
    case "GET":
      // リソース表示
      break;
    case "POST":
      // リソース作成
      break;
    case "PUT":
      // リソース更新
      break;
    case "DELETE":
      // リソース削除
      break;
  }
%>

エラーハンドリング

web.xmlでの設定にてエラーハンドリングを実現します。

<error-page>
  <error-code>404</error-code>
  <location>/error/404.jsp</location>
</error-page>
<error-page>
  <exception-type>java.lang.Exception</exception-type>
  <location>/error/general.jsp</location>
</error-page>

JSPでのエラーページの表示は以下の通りです。

<%@ page isErrorPage="true" %>
<h1>エラーが発生しました</h1>
<p><%= exception.getMessage() %></p>

パフォーマンスとセキュリティ

HTTPパフォーマンス最適化

キャッシュ設定やGZIP圧縮を利用すると、通信量を減らしてページを高速に表示できます。

<%-- 静的リソースのキャッシュ --%>
<%
  response.setHeader("Cache-Control", "public, max-age=31536000");
%>

<%-- 圧縮有効化 --%>
<%@ page import="java.util.zip.GZIPOutputStream" %>
<%
  String acceptEncoding = request.getHeader("Accept-Encoding");
  if (acceptEncoding != null && acceptEncoding.indexOf("gzip") != -1) {
    response.setHeader("Content-Encoding", "gzip");
    out = new JspWriter(new GZIPOutputStream(response.getOutputStream()));
  }
%>

セキュリティ対策

CSRF対策

CSRF対策ではトークンを発行して送信時に検証し、不正なリクエストを防止します。

<%-- トークン生成 --%>
<%
  String csrfToken = UUID.randomUUID().toString();
  session.setAttribute("csrfToken", csrfToken);
%>

<%-- フォームに埋め込み --%>
<input type="hidden" name="csrfToken" value="<%= csrfToken %>">

<%-- トークン検証 --%>
<%
  String sessionToken = (String) session.getAttribute("csrfToken");
  String requestToken = request.getParameter("csrfToken");
  if (sessionToken == null || !sessionToken.equals(requestToken)) {
    throw new ServletException("CSRFトークンが無効です");
  }
%>

XSS対策

XSS対策では、ユーザー入力をエスケープして悪意のあるスクリプトの実行を防ぎます。

<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%
  // JSTLのfn:escapeXmlを使用
  String userInput = request.getParameter("input");
%>

${fn:escapeXml(userInput)}

SQLインジェクション対策

SQLインジェクション対策としてPreparedStatementを使用し、SQL文へ安全に値を渡すことで不正なデータベース操作を防止できます。

<%@ page import="java.sql.PreparedStatement" %>
<%
  // PreparedStatementを使用
  String username = request.getParameter("username");
  PreparedStatement stmt = connection.prepareStatement(
    "SELECT * FROM users WHERE username = ?");
  stmt.setString(1, username);
  ResultSet rs = stmt.executeQuery();
%>

まとめ

本記事では、JSPを用いたWebアプリケーション開発において重要なクライアント/サーバーモデルとHTTPプロトコルの詳細について解説しました。

HTTPのステートレスな性質を理解し、セッション管理やクッキーを適切に扱うことで、効果的なWebアプリケーションを構築できます。また、MVCアーキテクチャの採用やRESTfulな設計、適切なエラーハンドリング、セキュリティ対策など、実践的なテクニックも紹介しました。

JSP開発においてHTTPプロトコルを深く理解することは、パフォーマンスの良い、安全で、ユーザーフレンドリーなアプリケーションを作成するための基礎となります。これらの知識を活用して、より高度なJSPアプリケーションの開発に挑戦してください。