いよいよこの章でServletの説明は終わりです。ここではServletを活用していく上で便利な機能をトピックス的に取り上げ、関連するメソッドとその使い方について説明します。また、本章の最後ではこれまで出てきたメソッドの一覧も示します。

他サーバへのリクエストの転送(リダイレクト)

他サーバへのリクエストの転送(リダイレクト)

リクエストを受け取ったアプリがその内容から別のサイトで処理した方が良いと判断した場合、アプリは指定のサイトにリダイレクト(転送)することができます。 それには次のように宛先のURLを引数としてsendRedirectメソッドを呼びます。


図解: リダイレクト

sendRedirectメソッドはHttpServletResponseインターフェースで定義されているので、そのオブジェクトであるresを使って呼び出します。

このメソッドが呼ばれるとコンテナは、クライアントに対しHTTPのレスポンスで301(Moved Permanently)と新ページのURLを返します。クライアントはこれを受け取ると新しく示されたページに自動的にジャンプします。このジャンプは自動的に行われるので、クライアントのユーザに気づかれることはありません。

他のサーバへのリクエストの転送は上記のようなコンテナによる方法ではなく、HTMLのmetaタグやスクリプトで新アドレスを指定することによっても実現可能です。Webサイトを開くと5秒後にジャンプします、などと表示されることがありますが、それはこちらです。これはサイトのアドレスが変わったような場合に使用され、リクエストの内容を見て判断するのではなく一律に転送するものです。 これはHTTPリダイレクトではありません。

他アプリとの処理の分担(リクエストディスパッチャ)

他アプリとの処理の分担(リクエストディスパッチャ)

リダイレクト(転送)と似た機能にディスパッチャ(分配者)の機能があります。リダイレクトはレスポンスの処理全体を相手に任せるのに対し、ディスパッチャでは処理の一部を任せます。

リダイレクトでは別のサーバのURLを指定してそこに転送できたのに対し、ディスパッチャでは同じサーバ内にしか制御を渡せません。またリダイレクトでは制御が一度クライアントに戻り、そこから再度リクエストが発せられるのに対し、ディスパッチャではサーバ内部で制御が移動し、クライアントに通知されることはありません。

ディスパッチャには先方に制御が完全に移行し戻って来ないforwardメソッドと、処理が終わると制御が戻って来るincludeメソッドとがあります。すなわちforwardはgoto文、includeはサブルーチンコールに相当します。ディスパッチ先としてServletだけでなくJSPを指定することができます。JSPは表示が得意ですから、JSPにforwardして表示をまかせることによりServletとJSPとの処理分担を実現することができます。また共通処理をするServletを作っておき、他のServletがそれをincludeして利用することで機能の部品化をはかることもできます。

includeメソッドとforwardメソッドはRequestDispatcherインターフェースで定義されています。したがってこれらのメソッドを実行するには、まず以下のようにRequestDispatcherインターフェースのオブジェクトを取得する必要があります。引数として与えるのはディスパッチ先のServlet(やJSP)のアドレスです。


図解: RequestDispatcherインターフェースのオブジェクトの取得

includeやforwardした先では入出力の情報が必要なので、HttpServletRequestのオブジェクトであるreqとHttpServletResponseのオブジェクトであるresとを引数として渡します。これら以外の変数は3章で説明したリクエストスコープでのデータとして引き渡すことができます。


図解: includeメソッドとforwardメソッド

リスナー

リスナー

リスナーとは、Servlet内部で何らかのイベントが発生した際にそれをプログラムに通知する機能です。例えばセッションが生成されたときや破棄されたときにイベントが発生するので、それを聞き取るリスナーを登録しておくとその通知を受け取ることができる仕組みになっています。

セッションの生成/破棄の通知を受けるプログラムの例を示します。まずHttpSessionListenerインターフェースを実装したクラスを作ります。以下の例ではMySessionListenerがそのクラスです。次にそのクラス内にsessionCreatedメソッドとsessionDestroyedを定義します。このメソッド内にセッションの生成/破棄時に実行させたい処理を書きます。以下の例では生成/破棄の通知を受けるとそれぞれのメッセージを出力しています。


図解: セッションの生成/破棄の通知を受けるプログラム

そして、このクラス名を次のようにして配備記述子に登録します。<listener>要素はルート要素<web-app>の直下に記述します。


図解: <listener>要素

リスナーにはこの他にServletContextの生成と破棄時に通知されるServletContextListenerやアプリケーションスコープのデータの変更(追加、削除、置換)時に通知されるServletContextAttributeListenerなどがあります。

リスナーの機能はServlet固有のものではなく、Javaの本来の機能として備わったものです。特にAppletでは欠かせない機能で画面のボタン押下をリスナーで検出して必要な処理を行います。

エラーページ

エラーページ

Servletアプリは処理中にエラーを検出すると、それをクライアントに通知しなければなりません。しかし、エラーの種類ごとに画面を作り通知するのはなかなか大変です。ここではまず、いちいち画面を作らずにすむsendErrorメソッドをご紹介します。

sendErrorメソッドを出すと、エラーステイタスがクライアントに送られ、クライアントのブラウザがエラー画面を生成して表示します。このメソッドではアプリが指定したメッセージも送ることができます。このメソッドはHttpServletResponseのメソッドとして定義されているので、そのオブジェクトであるresを使って以下のように呼び出します。

図解: sendErrorメソッドの呼び出し

ここで最初の引数はHTTPのステイタスコードですが、この値はHttpServletResponseインターフェースにint型の定数値として定義されています。この例のSC_INTERNAL_SERVER_ERRORの値は500です。すなわちこの例ではステイタスコード500がクライアントに返されます。2番目の引数はクライアントの画面に表示するメッセージです。

エラー発生以前にバッファに書き込んだ内容があると、コンテナはそれをクリアしてからステイタスを送信します。しかし、もしsendErrorメソッドが実行される前に、バッファがフルになるなどの理由でクライアント宛にレスポンスが返送されてしまうとsendError実行時に、IllegalStateExeption例外がスローされます。しかも、クライアントには正常なレスポンスが返送されてしまっているので、クライアント側にはエラーは表示されません。このようなことが無いようにアプリを注意深く作る必要があります。

sendErrorメソッドを使う代わりにsetStatusメソッドでエラーステイタスを返す方法もあります。setStatusメソッドもHttpServletResponseで定義されているので、次のようにresで呼び出すことができます。このメソッドはメッセージを付加することはできません。SC_NOT_FOUNDの値は404ですので以下の例では引数に404と書いても同じです。


図解: setStatusメソッドの呼び出し

エラー処理をサポートする別の手段として、エラーページは自分で作りエラーの判定をコンテナに行わせるやり方があります。配備記述子で例外やエラーコードと対応するエラーページを指定しておくと、エラー発生時に指定のページが送られます。

具体的には<exception-type>で例外を指定するか、または<error-code>でエラーステイタスを指定し、<location>でそのエラー発生時に表示するページのURLを指定します。以下にその例を示します。<error-page>要素はルート要素<web-app>の直下に記述します。


図解: エラー処理の別の手段

マルチスレッドとスレッドセーフ

マルチスレッドとは複数の処理が並行して走る仕組みです。この機能はServlet固有のものではなくJava自身に備わったものです。この機能を使うと1つのプログラム実行中に別のプログラムを起動し、並行動作をさせることができます。

一般のJavaでは異なるプログラムが並行動作するのが普通ですが、Servletではあるアプリの処理中に、別のクライアントからリクエストが来ると、その同じアプリが並行して動作します。これはメモリの節約と起動の高速化に大変有効です。またスレッドはスタック以外のメモリ空間を共有します。すなわちスレッドが切り替わってもメモリの退避や復元がごく僅かしか発生しないので、切替のオーバヘッドが少なくてすみます。

このように良いことだらけのマルチスレッドですが、もちろん問題点もあります。それは共有メモリの使い方に注意が必要であることです。Javaではローカル変数(メソッド内で定義された変数)はスタックに格納されますが、static変数やインスタンス変数は非スタック領域(ヒープ領域)に格納されるので、他のスレッドと競合しないように気をつけなければなりません。

すなわち、Servletではメソッド内でローカルに定義している変数以外の変数を扱うときは、必要に応じて他のスレッドからのアクセスを禁止(ロック)するなど、競合が起こらないように注意する必要があります。このようにマルチスレッドでも問題なく動作するプログラムの作りを、スレッドセーフと言います。Servletのようにマルチスレッドで動くプログラムは、スレッドセーフに作らねばなりません。

Servletはデフォルトではマルチスレッドで動作します。シングルスレッドで動作するように設定することもできますが、性能上の問題が発生しやすくお勧めできません。

大切なことなので、スレッドセーフな変数とスレッドアンセーフな変数についてまとめておきます。スレッドセーフな変数は普通に使えますが、スレッドアンセーフな変数は値を更新するときにロックをかけて競合が起こらないようにしなければなりません。

スレッドセーフ  メソッド内のローカル変数
スレッドアンセーフ  インスタンス変数とstatic変数

セキュリティ

Web環境では特にセキュリティが重要です。ここではセキュリティの要素として、認証(Authentication)、認可(Authorization)、機密性(Confidentiality)、一貫性(Integrity)、監査(Auditing)の5つを取り上げます。

認証とは利用者がシステムが利用を許可した正当なユーザであることを確かめることです。ご承知のように認証は通常はユーザIDとパスワードとで行います。ユーザIDで利用者の識別を行い、それに対応したパスワードによって本人であることを証明します。

これに対し認可は、ユーザがシステムに対して何ができるかを規定することです。あるユーザが認証をパスしてシステムを利用できるようになったとしても、そのユーザがシステムのすべての機能を制限なく使用できるとは限りません。例えば一般社員と管理職ではアクセスできる情報の範囲に差があることもあるでしょう。このようにユーザごとに何ができるかを定めるのが認可の働きです。Authorizationは権限、承認などと訳されることもあります。

機密性は当事者以外にデータの内容が理解できないようにすること、一貫性はデータが途中で改ざんされていないことを確認することです。これらは暗号化や冗長情報の付加などにより対応が行われます。

監査とはシステム上での様々な出来事を記録することです。データの不正アクセスは必ずしも外部の者が行うとは限りません。むしろ内部の人間によるものも多いものです。この場合、誰がアクセスしたかを記録に残しておくことで、不正を追及することができます。また監査していることを公開することにより、不正を未然に防ぐことも期待できます。

認証(Authentication)

上述の概念のうち、Servletコンテナがサービスしているのは認証だけです。 以下ではコンテナが提供している4種類の認証方法を紹介します。

HTTP 基本認証 HTTPの仕様で規定されている基本的な認証方式
HTTPダイジェスト認証 ダイジェスト方式による暗号化を使った認証方式
Form-based認証 自作のログインフォームを使用できる認証方式
HTTPSクライアント認証 SSL(Secure Socket Layer)を使用した認証方式

HTTP基本認証はHTTPの仕様で規定されている認証方式です。基本認証を行うように設定されているサーバの保護の対象となるリソースへリクエストが来ると、サーバは401(Unauthorized)のステイタスコードと基本認証を指定するヘッダーをクライアントのブラウザに返します。ブラウザはこれを受け取るとユーザIDとパスワード入力を要求する認証画面を表示します。入力した情報はサーバに送られます。それが正しいものであればアクセスは許可され、正しくなければ再度401ステイタスを送信します。

このようにHTTP基本認証は簡単に実現できますが、ユーザIDとパスワードが暗号化されていないので盗聴される危険があります。SSLやVPNなどで通信路を暗号化すれば、基本認証でも安全な認証ができます。

HTTPダイジェスト認証もHTTPの仕様で規定されています。クライアントはパスワードの代わりにダイジェストと呼ばれる文字列を送ります。ダイジェストはサーバから送られた乱数とユーザが入力したパスワードを基に一方向関数で演算して作ったものです。一方向関数には入力データからダイジェストを作るのは容易だが、逆にダイジェストから元の入力データを作るのは非常に難しいという特徴があります。したがって通信路上のダイジェストからパスワードを解読される心配はありません。サーバはクライアントと同じやり方でダイジェストを計算し、値を比較して認証します。

このようにHTTPダイジェスト認証では基本認証より安全にパスワードを送ることができます。ただし保護されるのはパスワードだけであり、通信路上を流れる他の情報は暗号化されません。 それにはやはりSSLやVPNなどでの保護が必要になります。また古い版数のブラウザにはダイジェスト認証に対応していないものがあります。

基本認証とダイジェスト認証では決まった認証画面しか表示できませんが、Form-based認証を使うと認証画面を自分で作ることができます。また認証に失敗した場合に表示する画面も独自に用意することができます。ただし基本認証と同様パスワードの暗号化は行われません。

HTTPSクライアント認証ではサーバからの要求に対してクライアントは信頼された証明機関(CA)が発行した証明書を送ります。証明書の準備が必要になりますが、SSLを使用した安全な認証方式と言えます。企業内でのシングルサインオンの手段として有用です。

認証方式は配備記述子の<login-config>の下の<auth-method>要素で指定します。<login-config>要素はルート要素<web-app>の直下に記述します。認証方式はBASIC、DIGEST、FORM、CLIENT-CERTのいずれかで指定します。


図解: 認証方式の指定

メソッド一覧

これまでに説明したすべてのメソッドを、それが定義されているクラスとインターフェースごとに示します。カッコ内の数字は初めて出てきた章の番号です。実際のServletコンテナの各クラス、インターフェースにはこの他に多くのメソッドが定義されています。詳細は(注5)でご紹介したAPI仕様書を参照してください。

Servletインターフェース

説明:このインターフェースはServletの基本的な3つの機能を宣言している。次に示すHttpServletクラスがこのインターフェースを実装している。
init(2) 初めてロードされたときにコンテナによって一度だけ呼び出される
service(2) クライアントからのリクエストごとにコンテナによって呼び出される
destroy(2) アプリを破棄するときにコンテナによって一度だけ呼び出される

HttpServletクラス

説明:通常、Servletアプリはこのクラスを継承して作る。 このクラスはServletインターフェース、ServletConfigインターフェースを実装している。
doGet(2) HTTPのgetメソッドが発行されるたびにコンテナによって呼び出される
doPost(2) HTTPのpostメソッドが発行されるたびにコンテナによって呼び出される
doHead(2) HTTPのheadメソッドが発行されるたびにコンテナによって呼び出される

ServletRequestインターフェース

説明:このインターフェースはクライアントからのリクエストに関する情報を操作するメソッド群を定義したものである。
setCharacterEncoding(2) 受け取る文字データのコード系を指定する
getParameter(2) ブラウザの画面から入力したデータを取り込む
getParameterValues(2) ブラウザの画面から入力したデータを全部取り込む
getParameterNames(2) ブラウザの画面から入力したデータの名前(key)を取り込む
getAttribute(3) リクエストスコープに格納したデータを取り出す
getAttributeNames(3) リクエストスコープに格納したデータのキー名を取得する
setAttribute(3) リクエストスコープにデータを格納する
removeAttribute(3) リクエストスコープに格納したデータを消去する
getRequestDispatcher(5) RequestDispatcherオブジェクトを取得する

HttpServletRequestインターフェース

説明:このインターフェースはServletRequestを拡張(継承)して、HTTPの情報を付け加えたものである。 このインターフェースを実装して作るオブジェクトはdoGetやdoPostの入力パラメーターとしてServletアプリに渡される。
getHeader(2) httpのヘッダー情報を取り込む
getHeaders(2) httpのヘッダー情報を全部取り込む
getHeaderNames(2) httpのヘッダー情報の名前(key)を取り込む
getSession(3) sessionオブジェクトを取得する

ServletResponseインターフェース

説明:Servletはこのインターフェースを実装したオブジェクトを使用してMIMEでエンコードしたデータをクライアントに送信する。
setContentType(2) 出力データのコンテントタイプ(ファイルの種類)を指定する
getWriter(2) PrintWriterクラスのオブジェクトを取得する
getOutputStream(2) ServletOutputStreamクラスのオブジェクトを取得する

HttpServletResponseインターフェース

説明:このインターフェースはHTTPプロトコル固有のデータが操作できるようにServletResponseインターフェースを拡張(継承)したものである。
encodeURL(3) URL書き換え方式を使用してセッションIDをクライアントに送る
sendRedirect(5) 指定のサイトにリダイレクト(転送)を指示する
sendError(5) クライアントに対しエラーを返す
setStatus(5) クライアントに対しステイタス情報を返す

PrintWriterクラス

説明:テキストデータをクライアントに送るために使用する。 このクラスのオブジェクトを取得するにはServletResponseのgetWriterメソッドを使うが、必ず取得の前にsetContentTypeメソッドで文字セットを指定する必要がある。
println(2) 文字列を出力する

ServletOutputStreamクラス

説明:バイナリデータをクライアントに送るために使用する。 ServletアプリはServletResponseのgetOutputStreamメソッドでこのクラスのオブジェクトを取得する。
write(2) バイナリデータを出力する

HttpSessionインターフェース

説明:このインターフェースを実装したオブジェクトはServletを利用した人の情報を一時的に格納することができる。
getID(3) セッションIDを取得する
invalidate(3) セッションを無効化する
getAttribute(3) セッションスコープに格納したデータを取り出す
getAttributeNames(3) セッションスコープに格納したデータのキー名を取得する
setAttribute(3) セッションスコープにデータを格納する
removeAttribute(3) セッションスコープに格納したデータを消去する

ServletContextインターフェース

説明:このインターフェースはサーバおよびサーバ上の各アプリケーションスコープの情報を取得するために使用する。
getAttribute(3) アプリケーションスコープに格納したデータを取り出す
getAttributeNames(3) アプリスコープに格納したデータのキー名を取得する
setAttribute(3) アプリケーションスコープにデータを格納する
removeAttribute(3) アプリケーションスコープに格納したデータを消去する
getInitParameter(4) 配備記述子の<context-param>で定義した値を取得する
getInitParameterNames(4) 配備記述子の<context-param>で定義した名前を取得する
getContext(4) 他のアプリのServletContextオブジェクトを返す
getResource(4) 指定されたパスにマップされたリソースへのURLを返す
getResourceAsStream(2) 指定リソースの内容を読み込むInputStreamを返す
getServerInfo(4) Servletコンテナの名前とバージョンを返す
getVersion(4) このコンテナがサポートするServletAPIの版数を返す

ServletConfigインターフェース

説明:初期化情報をServletに渡す役割を果たす。 HttpServletクラスがこのインターフェースを実装しているので、HttpServletを継承しているServletアプリはこのインターフェースで定義されているメソッドを直接呼び出すことができる。
getServletContext(3) ServletContextのオブジェクトを取得する
getInitParameter(4) 配備記述子の<init-param>で定義した値を取得する
getInitParameterNames(4) 配備記述子の<init-param>で定義した名前を取得する

RequestDispatcherインターフェース

説明:このインターフェースを使うとサーバ上のすべての資源(Servlet、htmlファイル、JSPファイルなど)にディスパッチ(作業分配)することができる。
forward(5) 制御が分配先に移行して戻ってこない
include(5) 分配先の処理が終わると制御が戻ってくる

HttpSessionListenerインターフェース

説明:セッションの生成/消滅を監視したいときにこのインターフェースを実装したクラスを作り、以下のメソッドを定義する
sessionCreated(5) セッションが生成されたときに呼ばれる
sessionDestroyed(5) セッションが破棄された(無効化された)ときに呼ばれる

確認問題

(1) 他のサーバへリクエストを転送するメソッドは何ですか。
(2) 同一サーバ内の他のアプリに処理を渡すために必要なオブジェクトは何ですか。 そこで定義されている2つのメソッドとその機能の相違を説明してください。 処理をServletで行った後に表示をJSPに任せるにはどちらのメソッドを使いますか。
(3) イベント発生時にそれを捕捉する仕組みを何と言いますか。 その仕組みを配備記述子に登録するときに使用する要素を2つ挙げてください。
(4) クライアントに対しエラーを伝えるメソッドを2つ挙げてください。
(5) マルチスレッドでもスレッドセーフな変数は何ですか。スレッドセーフでない変数は何ですか。
(6) Servletコンテナが提供している4つの認証方法を挙げてください。そのうち認証画面を自分で作ることができる方式はどれですか。またSSLを使った認証方式はどれですか。

解答

(1) sendRedirectメソッド
(2) RequestDispatcherオブジェクト、forwardメソッド(先方に制御が渡り戻ってこない)とincludeメソッド(制御が戻ってくる)、表示をJSPに任せるにはforwardメソッド
(3) リスナー、<listener>と<listener-class>
(4) sendErrorメソッド、setStatusメソッド
(5) スレッドセーフ:ローカル変数、スレッドセーフでない:static変数、インスタンス変数
(6) HTTP基本認証、HTTPダイジェスト認証、Form-based認証、HTTPSクライアント認証、認証画面自作可能:Form-based認証、SSLを使った認証:HTTPSクライアント認証
ページの先頭へ