HTTPサーブレットは、HTTPのサーバー側の処理を行う、Javaでの標準的な方法。JavaEEの仕様で定められている。
「サーブレット」と言うと、広義にはJavaでHTTPを扱う仕様・ライブラリー全体を指す。
狭義には「Servletクラス」(HTTPの処理を実際に行うクラス)を指す。
|
|
|
|
|
→サーブレットは、実際にはJSPやStruts等で隠蔽されることが多い。
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式(<%=
)・スクリプトレット(<%
)は使ってはならない。
(これらの事は、きちんと排他をかけられるなら問題ない)
→サーブレット系全般の注意点
サーブレットコンテナとは、サーブレットを管理するもの。
サーブレットのライフサイクルの管理や、サーブレットのメソッドの呼び出しを行う。
APサーバー(TomcatやWebLogic)がサーブレットコンテナの機能を持っている。
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には日本語が直接は使えないので、情報として日本語を扱いたい場合は変換する必要がある。[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()で同じ体系を指定しないと、うまくデコードできない。
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]