Javaサーブレット (Java servlet)

サーブレットは、Webシステムにおいてサーバー・サイドで動くJavaプログラムです。

Table of Contents

  1. 1 設定ファイル
    1. 1.1 web.xml
  2. 2 パッケージ
    1. 2.1 javax.servlet
    2. 2.2 javax.servlet.http
      1. 2.2.1 インタフェース
        1. 2.2.1 HttpServletRequest
        2. 2.2.2 HttpSession

サーブレットとは

サーブレットは、クライアント・サイドで動くアプレットに対して、サーバー・サイドで動くプログラムです。サーブレットは、ブラウザとサーバーのインタフェースとしてよく使われるCGIに似た機能を提供します。つまり、サーブレットは単独で動作するJavaアプリケーションではありません。HTTPを解釈して、期待する応答を返すサーバーの上で動作します。

CGIで使用する言語は決まっていないので、JavaでCGIを書くこともできます。しかし、CGIをJavaで書いたものはサーブレットではありません。サーブレットはServlet APIを使っています。具体的には、上位クラスの中にjavax.servlet.Servletインタフェースがあります。

サーブレットはCGIに比べると、拡張性のあるマルチプラットフォーム名インタフェースとなっています。CGIでできることはサーブレットでもでき、次のようなことを行います。

サーブレットの優れた点

新たなプロセスを必要としない

サーブレットは、サーブレットを起動するサーバー上でひとつのスレッドとして動きます。このため、通常のCGIのように別プロセスを起動することないので、2回目以降の起動は速いという利点があります。1回目は、サーブレットをインスタンス化してディスパッチする作業が必要なので時間がかかります。

マルチスレッドで動作する

あるサーブレットの同じメソッド(たとえばdoPost)に同時にリクエストが来ると、あるいは先に来たdoPostリクエストの処理が終わらないうちに次のdoPostリクエストが来ると、それぞれ別のスレッドにリクエストを割り当てて処理します。パフォーマンスが挙げられる分、複数のリクエストが並列的に実行されていることを考慮しないと思ったような結果が得られないことがあります。

サーバー上に常駐する

サーブレットはサーバー上にロードされると、Admin Toolなどでアンロードする(サーブレットのdestroyメソッドが起動される)まで生きています。これが応答が速いことの理由になります。

多層モデルを作るときには、その先のサーバーへのコネクションを張ったまま常駐します。コネクション張りなおし処理が必要ないので、これもまた応答が速いことの理由になります。

セキュリティモデルがある

Javaならではの機能で、いわゆるサンドボックスといわれるセキュリティモデルがあります。CLASSPATHが通っているところからロードしてくるクラスには適用されません。リモートからアップロードするサーブレットにのみ適用されます。"file read", "file write", "execute"などを制限できます。

Webサーバーでは重要なインターネット・セキュリティ、CGI、サーブレットのセキュリティ・ホール対策にはなりません。

Servlet: CGIに代わるサーバ側Javaプログラム

Servletはブラウザから呼び出せるサーバ側のJavaプログラムです。ブラウザからの呼び出し方はCGIと同様です。

CGIはUNIXコマンドをそのままラップできるなど良い点もありますが、リクエストのたびに起動と終了を繰り返してメモリから消えてしまう欠点もあります。CGIプログラムからデータベースサーバへ接続する場合にも、毎回セッションを張りなおすことになるでしょう。

Servletは最初の起動は必要ですが、その後はメモリ中に残りつづけます。データベースサーバとの接続も、最初にコネクションを張って使いつづけることができます。セッション管理やプログラム間のデータの受け渡しなども自然な形で実装することができます。

Java Servlet
Figure 1. Java Servlet

Java Servlet API

Servlet とは「クライアントのリクエストをサーバー上で処理して結果をリプライ」するサーバー・サイド・アプレットであり、コアAPIをServlet APIで拡張して記述します。Servlet APIを利用することで、サーバー/クライアントの通信が「ただのファイル入出力」並に簡素になります。

Servletは下記のような構造で記述します。

import java.io.*;
import javax.servlet.http.*;
import javax.servlet.*;

public class MyServlet extends HttpServlet {

サーブレットを書くには、まずjavax.servlet.GenericServletまたはjavax.servlet.http.HttpServletをextendsするか、javax.servlet.Servletをimplemnentsする。そして、次の4つのメソッドをoverrideする。

init
サーブレットがインスタンス化されたときに実行される。"Properties"の読み込みなどを行う。多層モデルを作る場合には、このメソッド内でコネクションを生成する。スレッドを使うときもこのメソッド内で初期化しておくとよい。こうするとHTTPコネ九村が切れてもスレッドは生きている。このメソッドでsuper.init(ServletConfig)を実行しておくと、log(String)でevent_logに情報を出力できる。
service(またはdoGet, doPost, doPut, doDelete, doOptions, doTrace)
サーブレットで行う処理の本体。サーバーへのリクエストがあるたびに実行される。HttpServletをextendsしたクラスでserviceメソッドをoverrideした場合はHTTPのヘッダにあるMETHODでdoGet, doPost, などのうちどれを実行するか判断してくれる。各メソッドの使い分けはHTTPのGET, POSTなどの使い分けと同じになる。たとえば、ファイルのアップロードなど、1回のコネクションで大量のデータを送り込みたいときはdoPostである。
getServerInfo
サーブレットの情報をStringでreturn する。
destroy
サーブレットをdestroyするときの処理をここで支持する。スレッドを使ったときは後始末をする。コネクションのクローズも行う。

これがサーブレットの基本パターンになる。

メソッド 説明
init() 初期化処理。サーブレット起動時に実行されるが、必要なければ省略可。
service() Servletで行う処理の主要部。
クライアントの要求に応じて処理を行い、結果をリプライする。
doGet()
doPost()
doPut()
doDelete()
doOptions()
doTrace()
getServletInfo() Servletの説明文などを記述。要求があったときにStringで返す。省略可。
destroy() Servletが破棄される直前に呼び出される。省略可。

ロードとアンロード

Servletは起動後、サーバーにロードされて処理を行うが、処理終了後も破棄されることなく待機状態となる。つまりアンロードされずにサーバー上のメモリーに常駐します。この点がアプレットと大きく異なります。

では、Servletを更新する場合はどうするのでしょうか。普通に考えたら稼動しているサーブレットを安全に停止・削除後、新しいServletを格納・起動する手順の気がしますが・・・。指定のフォルダ(JWSの場合は"/servlets")にクラスファイルを格納すればサーバーがクラスファイル更新を識別して、次回リクエスト時に自動的にリロードしてくれます。

CGIと比較した利点

クライアントのリクエスト(要求)をサーバー上で処理してリプライ(応答)を返すという処理は、Servletの他にもCGI (Common Gateway Interface) で行えます。CGIと比較したサーブレットの利点を次に示します。

サーバーのソフトの供給元では、このパフォーマンスの問題を何とかするために、ISAPI (Microsoft) や NSAPI (Netscape) のような独自規格の代替方法を開発しました。Java Softがより能率的なCGIの代替規格としてJava Servletを提案しました。

このServlet APIは、Javaエクステンションです。Javaエクステンションは標準パッケージのオプションです。標準パッケージであるため、Java Softと業界から広くサポートされます。また、オプションであるためコアAPI (java.io, java.awt, etc) とは違い、すべてのプラットフォームで利用されるわけではありません(サーバー以外ではサポートする理由が無い)。

Servlet Engine

Servlet Engineというのは、Servletを動かす機能を持っているサーバーで、Java言語で作られているアプリケーションです。Servletというのは「プログラム・モジュール」という呼び方をしますが、Servletのクラスを用意しただけでは動作しません。ServletはAppletと同様にJava VM上で動作します。これがServlet Engineと呼んでいるJavaのアプリケーション、Java VMです。Appletのブラウザに相当するものがServletにとってはWebサーバーということになります。

ブラウザにはHotJavaのように自分自身もJavaで作られていてAppletを動かす機能があるものや、Netscape Navigator / Communicator、あるいはInternet ExplorerのようにAppletを動かす機能を持っているもの、さらにPlug-inにより機能を追加するものがあります。Webサーバーも同様にそれ自身がJavaで作られていてServletを動かす機能があるJava Web ServerやJigsaw、Webサーバー自体はJavaで作られていませんが最初からServeletを動かす機能を持っているNetscapeのサーバー等、WebサーバーにPlug-in的に追加するものがあります。

おもなサーブレットエンジン
サーブレットエンジン 説明
JSWDK (JavaServer™ Web Development Kit) Servlet APIと JSP のリファレンスインプリメンテーションです。JSDK (Java Servlet Development Kit) とJSPを組み合わせたものです。
Apache Tomcat TomcatはThe Jakarta Projectが開発しているサーブレットとJSPのリファレンスインプリメンテーションです。TomcatはこれまでのJSWDK同様、単独で使用することも、Apacheなどの一般的なWebサーバーにアドオンして使うこともできます。
JRun Live Software, Inc.からリリースされているServelet Engineです。

ソースファイル作成

サンプルソース(HelloWorldServlet.java)

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorldServlet extends HttpServlet {

  public void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {

      response.setContentType("text/html; charset=Shift_JIS");
      PrintWriter out = response.getWriter();

      // HTMLを出力
      out.println("<html>");
      out.println("<head>");
      out.println("<title>Hello World</title>");
      out.println("</head>");
      out.println("<body>");
      out.println("Hello World");
      out.println("</body>");
      out.println("</html>");
  }
}

サーブレットのクラスはHttpServletクラスを継承して作ります。サーブレットの主な処理は serviceまたはdoGet、doPostメソッドをオーバーライトして記述します。

応答を出力するため、HttpServletResponse#setContentTypeメソッドで Content-Typeを指定します。

コンパイル

サーブレットのコードが書けたら、次はソースファイルをコンパイルします。サーブレットのコンパイルにはCLASSPATHの中にServlet APIへのパスが必要です。

環境変数 CLASSPATH を設定したら、サンプルソースをコンパイルします。

javac HelloWorldServlet.java

コンパイルしてできたHelloWorldServlet.classを以下のフォルダにコピーします。

Tomcatのフォルダ\webapps\servlets-examples\WEB-INF\classes

Webブラウザをから以下のURLを開きます。

http://localhost:8080/servlets-examples/servlet/HelloWorldServlet

CLASSPATHの設定

Javaのソースファイルをコンパイルする前に、CLASSPATHを設定する必要があります。パッケージ javax.servlet が含まれているJavaアーカイブファイルをCLASSPATHに設定します。このJavaアーカイブファイルはアプリケーションサーバーから提供されていますので、お使いになっているアプリケーションサーバーによってそのファイル名が異なります。

Servlet APIを使用するJavaプログラムを実行する前に、CLASSPATH環境変数にクラス・ライブラリ・ファイルを含める必要があります。

UNIXのCシェルでCLASSPATH環境変数を設定する例を次に示します。

setenv CLASSPATH .:/opt/sample/lib/servlet-api.jar

Microsoft WindowsでCLASSPATH環境変数を設定する例を次に示します。

set CLASSPATH=.;C:\Program Files\sample\lib\servlet-api.jar

CLASSPATH環境変数に含めるクラス・ライブラリ・ファイルのパス名は、使用するアプリケーション・サーバーによって異なります。

たとえば、Microsoft Windows版のApache Tomcat 5.0では、サーブレットAPIのJARファイルのパス名は Tomcatのフォルダ\common\lib\servlet-api.jar です。

Servletパッケージのインポート

サーブレットを作成するには、javax.servletパッケージとjavax.servlet.httpパッケージをインポートする必要があります。

import javax.servlet.*;
import javax.servlet.http.*;

Servletパッケージがスローする例外

Servletパッケージのメソッドは、 javax.servlet.ServletException 例外をスローすることがあります。したがって、Servletパッケージのメソッドを呼び出す箇所では、これをキャッチまたはスロー宣言する必要があります。

javax.servlet.ServletException 例外をキャッチする例を次に示します。

try {
  // Servletパッケージのメソッド呼び出し
} catch (ServletException e) {
  // 例外処理
}

javax.servlet.ServletException 例外をスロー宣言する例を次に示します。

public void service(HttpServletRequest req, HttpServletResponse res)
  throws ServletException
{
  // Servletパッケージのメソッド
}

パラメータの受け取り

HTTPリクエストからパラメータを取得するには、javax.servlet.http.HttpServletRequestインタフェースのメソッドを使用する。

パラメータはgetParameterメソッドで受け取ることができます。同じ名前のパラメータが複数ある場合はgetParameterValuesメソッドで受け取ることができます。

パラメータの値はASCIIコードを前提としています。パラメータの値に日本語の文字列が含まれる場合は、getParameterメソッドで値を読み取る前に setCharacterEncoding メソッドで文字コードを指定する必要があります。次にシフトJISコードでパラメータを受け取るサーブレットの例を示します。

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

  response.setContentType("text/html; charset=Shift_JIS");
  PrintWriter out = response.getWriter();

  request.setCharacterEncoding("Shift_JIS");
  String bookTitle = request.getParameter("bookTitle");
  String[] authors = request.getParameterValues("authors");
  int i;
  for (i = 0; i < authors.length; i++) {

XML文書をXSLTスタイルシートでHTML文書に変換した場合、FORMからの送信データは UTF-8 でエンコーディングされます。 この場合、HttpServletRequest#secCharacterEncoding("UTF-8") でエンコーディングを UTF-8 に指定しなければなりません。その他のエンコーディング方法を指定すると文字化けしてしまいます。

プロパティの取得

サーブレットからプロパティを取得するには、Webアプリケーションのローカルなパスをjavax.servlet.ServletContextインタフェースのgetRealPath()メソッドで取得します。

public class SampleServlet extends HttpServlet {
private static String realPath;

public void init(ServletConfig config) throws ServletException {
  super.init(config);
  ServletContext context = config.getServletContext();
  realPath = context.getRealPath("/");
}

public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
  Properties prop = new Properties();
  prop.load(new FileInputStream(realPath + "/WEB-INF/sample.properties"));
  String host = prop.getProperty("host"));

ファイルのアップロード

ファイルのアップロードはJakarta Commonsプロジェクトから提供されているファイルアップロードライブラリを使用します。

ファイルアップロードライブラリ commons-fileupload-1.0.jar はThe Jakarta Siteからダウンロードします。

ファイルアップロードライブラリを使用するには、パッケージ org.apache.commons.fileupload をインポートします。commons-fileupload-1.0.jar はアプリケーションサーバの適切なディレクトリに配置します。

次にファイルをアップロードするサーブレットの例を示します。

import java.io.*;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;

public class FileUploadServlet extends HttpServlet {

public void doPost(HttpServletRequest req, HttpServletResponse res)
                  throws ServletException, IOException {

  String path = getServletContext().getRealPath("WEB-INF/data");
  DiskFileUpload dfu = new DiskFileUpload();
  dfu.setSizeMax(1000000);
  dfu.setSizeThreshold(1024);
  dfu.setRepositoryPath(path);

  try {
    List lst = dfu.parseRequest(req);
    Iterator itr = lst.iterator();

    while (itr.hasNext()) {
      FileItem fi = (FileItem)itr.next();
      if (!fi.isFormField()) {
        String fileName = fi.getName();
        if (fileName != null) {
          if (!fileName.equals("")) {
            fileName = (new File(fileName)).getName();
            fi.write(new File(path + "/" + fileName));
          }
        }
      }
    }
  } catch (FileUploadException e) {
    e.printStackTrace();
  } catch (Exception e) {
    e.printStackTrace();
  }
}
}

DiskFileUpload#setSizeMax はアップロードするファイルの最大サイズを指定します。 DiskFileUpload#setSizeThreshold はバッファのサイズを指定します。 DiskFileUpload#setRepositoryPath は一時ファイルの保存先フォルダを指定します。

次にHTMLの例を示します。

<html>
  <head>
    <title>ファイルのアップロード</title>
  </head>
  <body>
    <h1>ファイルのアップロード</h1>
    <form method="POST" enctype="multipart/form-data" action="FileUploadServlet">
      ファイルパス:
      <input type="file" name="filepath" size="60">
      <input type="submit" value=" アップロード">
    </form>
  </body>
</html>

サンプルではアップロードするファイル名を変更していませんが、アップロードするディレクトリに同じ名前のファイルが存在する可能性がありますので、工夫が必要でしょう。 また、クライアントとサーバでOSが異なる場合、サーバ側ではファイル名として使用できない (またはメタキャラクタとして扱われる)文字が含まれている可能性がありますので、その変換も必要となります。