S-JIS[2003-06-16/2010-01-14] 変更履歴

Java HTTPサーブレット

HTTPサーブレットは、HTTPサーバー側の処理を行う、Javaでの標準的な方法。JavaEEの仕様で定められている。
「サーブレット」と言うと、広義にはJavaでHTTPを扱う仕様・ライブラリー全体を指す。
狭義には「Servletクラス」(HTTPの処理を実際に行うクラス)を指す。

→サーブレットは、実際にはJSPStruts等で隠蔽されることが多い。


基本的な動作手順

  1. javax.servlet.http.HttpServletを継承したクラスを作る。
    処理したいHTTPリクエストのメソッドに該当する関数をオーバーライドする。
    (例えばGETメソッドを処理したいのであれば、doGet()をオーバーライドする )
  2. ソースをコンパイルして、出来たclassファイルを所定の場所(サーブレットコンテナが認識できる位置)に配置する。
  3. クライアントからの要求に応じて、サーブレットの関数が呼ばれる。(これはサーブレットコンテナの仕事)
    (例えばサーバーにGETメソッドが来たときにはdoGet()が呼ばれる)

コーディング例

SampleServlet.java:

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

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

public class SampleServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

		res.setContentType("text/html; charset=Shift_JIS");

		PrintWriter out = res.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println("<title>サンプル</title>");
		out.println("</head>");
		out.println("<body>");
		out.print("日付:");out.println(new Date());
		out.println("</body>");
		out.println("</html>");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

		〜
	}

}

要するに、HTTPのGETメソッドが来たときにdoGet()が呼ばれる。(POSTメソッドならdoPost()が呼ばれる。→POSTデータの取得方法
HTTPリクエストの内容がreqに入ってくるので、HTTPレスポンスの内容をresに入れてやればよい。

処理としてはHTML文書を返すことが多いと思うが、ソースを見て分かる通り、out.println()を使用してHTMLのテキストを直接出力している。これによってHTMLファイルを生成するのと同様のことが行われる。

こういったHTML文をプログラムから出力するのは、複雑なHTMLを扱うには向いていない。
(特に、一旦作ったHTMLを修正しようとしたら、相当大変だろう)
その為に、JSPという仕様がある。→JSPでの記述例

Tomcat5.5で動かしてみる方法
WebLogic10で動かしてみる方法
JBoss4.2.3で動かしてみる方法


コーディングの注意点

一番気をつけなければならないのは、サーブレットはマルチスレッドで呼ばれる、ということ。
つまりMTセーフ(スレッドセーフ)な作りにしなければならない

サーブレットコンテナは、基本的にサーブレットのインスタンスを1つずつしか作らない。
クライアントからの要求は複数同時に処理される(すなわちマルチスレッドで処理される)ので、1つのインスタンスのメソッドが同時に複数スレッドから呼ばれることになる。

“MTセーフにする方法”を理解していないなら、少なくとも、サーブレットのクラスでは、staticフィールドもstaticでないフィールド(クラスのメンバー変数)も使ってはならない。
また、ローカル変数以外の変数を使ってはならない。
さらに、MTセーフでないメソッドを呼んではならない。
JSPの場合、定数以外のJSP宣言(<%!JSP式(<%=スクリプトレット(<%は使ってはならない。
(これらの事は、きちんと排他をかけられるなら問題ない)
サーブレット系全般の注意点

DateFormatの同期をとる例


サーブレットコンテナ

サーブレットコンテナとは、サーブレットを管理するもの。
サーブレットのライフサイクルの管理や、サーブレットのメソッドの呼び出しを行う。

APサーバー(TomcatやWebLogic)がサーブレットコンテナの機能を持っている。

  1. サーブレットコンテナ(JavaEEサーバー)が起動する。
  2. サーブレットコンテナが備えるクラスローダーにより、サーブレットがインスタンス化される。
    インスタンスは初期化されて、サーブレットコンテナに常駐する。(サーブレットクラス1つにつきインスタンス1つだけ)
  3. 処理がリクエストされると、サーブレットコンテナはスレッドを生成 して、該当サーブレットの(インスタンスの)メソッドを呼び出す。
    (つまりマルチスレッドで並行して実行される。複数リクエストがあると複数スレッド出来るが、プロセス(JavaVM)はあくまで1つ)
  4. サーブレットコンテナが終了すると、サーブレットのインスタンスも消える

init()とdestroy()

Servletには、init()とdestroy()というメソッドがある。[2009-12-05]

サーブレットがインスタンス化される時に、初期化用にinit()が呼ばれる。
サーブレットが破棄される時に、destroy()が呼ばれる。

init()は「throws ServletException」が宣言されている。
初期化に失敗した場合はServletExceptionをスローすればいい。
(そのサーブレットは使えなくなる/そのサーブレットを呼び出すリクエストはエラーになる)

が、UnavailableExceptionという例外が用意されているので、実際にスローするのはこれがいいと思う。

UnavailableExceptionのコンストラクターには、時間(秒)を引数にとるものがある。
init()がServletExceptionを返すと、APサーバーはそのサーブレットを使用不可として認識する。(Tomcatだと、そのサーブレットに「利用不可能」というマークを付ける)
時間を指定しない場合は、(サーバーを再起動するまで)永久に使用不可能のまま。だが、
時間を指定すると「その時間内は使えない」という意味になり、時間が経過した後に再度そのサーブレットに対するリクエストが来たら、再度サーブレットの初期化が試みられる(再度init()が呼び出される)。
(なお、Tomcatの場合は上記の動作になるが、こういった再呼び出しが行われるかどうかはAPサーバー次第のようだ)

Strutsでのinit()およびdestroy()(プラグイン)


フォワードとリダイレクトの違い

リダイレクトHTTPの仕様で、新しいURLをクライアントに返し、クライアントがそのURLに改めてアクセスするもの。
(ブラウザーが勝手にやってくれるので、エンドユーザーは滅多に気付かない)
そのURLが仮に同じAPサーバー(同じWebアプリ)で処理するものであっても、HTTPリクエストは新しいものとなる。

		res.sendRedirect("/redirect_sample.html");
		//セッションの継続を必要とする場合は、以下の様にする
		res.sendRedirect(res.encodeRedirectURL("/redirect_sample.html"));

フォワードはサーブレットの仕様で、APサーバー内の別URL(別のサーブレット)へ処理を渡すもの。
これはサーバー内で完結するので、クライアントは無関係。
リクエストの情報は(セッションの情報も)そのまま引き継がれる。

	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

		RequestDispatcher rd = getServletContext().getRequestDispatcher("/foward_sample.jsp");
		rd.forward(req, res);
		//rd.include(req, res);
	}

forward()を呼ぶと、その中でフォワード先のサーブレットの処理(doGet())が呼ばれる。[2007-08-27]
その処理が終わるまでforward()から制御が戻ってこない。

インクルードもフォワードと同じような動作をする。[2007-08-27]
フォワードは前処理を行う為のもので、インクルードは共通処理を呼ぶ為のもの。と思われる。フォワードする前にレスポンスに出力(フラッシュ)すると、フォワード先で例外が発生するらしい。

フォワード
自分が前処理を行い、後続処理を別サーブレットに任せる(後続処理に進む)。
インクルード
自分の処理の途中で共通処理(別サーブレット)を呼び出す(自分の処理の途中に含める)。

また、ファイルアップロード(マルチパート/フォームデータ)でフォワードやインクルードをする際には注意が必要。[2007-08-27]


URLエンコード

URLには日本語が直接は使えないので、情報として日本語を扱いたい場合は変換する必要がある。[2007-06-16]

	String src = "ABC=あいうえお";
	String enc = URLEncoder.encode(src, "SJIS");
	System.out.println(enc);
	String dec = URLDecoder.decode(enc, "SJIS");
	System.out.println(dec);
	String src = "ABC=あいうえお";
	String enc = URLEncoder.encode(src, "EUC_JP");
	System.out.println(enc);
	String dec = URLDecoder.decode(enc, "EUC_JP");
	System.out.println(dec);
ABC%3D%82%A0%82%A2%82%A4%82%A6%82%A8
ABC=あいうえお
ABC%3D%A4%A2%A4%A4%A4%A6%A4%A8%A4%AA
ABC=あいうえお

文字コード(数値)を1バイトずつ、「%」を付けた十六進数で表す。
なので、どの文字コードを使うか体系を指定しなければならない。(推奨はUTF-8らしい)

上記の実行例を見れば分かる通り、エンコードされた文字列(文字コードの表現)が異なっている。
当然、encode()とdecode()で同じ体系を指定しないと、うまくデコードできない。

java.net.URI#toASCIIString()


クッキー

Javaでは、クッキーCookieクラスで扱う。

		Cookie[] cs = req.getCookies();	//リクエストからクッキーを取得
		Map<String, Cookie> cookieMap;
		if (cs == null) {
			cookieMap = new HashMap<String, Cookie>(0);
		} else {
			cookieMap = new HashMap<String, Cookie>(cs.length);
			for (Cookie c : cs) {
				cookieMap.put(c.getName(), c);
			}
		}
		Cookie c = new Cookie("名前", "値");
		c.setMaxAge(10 * 60); //有効期間[秒単位]
		res.addCookie(c);			//レスポンスへクッキーを追加

サーブレット仕様

Servlet2.4仕様で変更された点(の一部)。[2009-10-09]

Servlet2.5仕様で変更された点(の一部)。[2008-08-24]


JavaEEへ戻る / Java目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま