しいしせねっとわーく Amazon.co.jp アソシエイト
[技術資料室] [Java] [J2EE] [java.dev.jp]
[Tomcat]

Servlet / JSP / タグライブラリ / カスタムタグ(taglib)
Webアプリケーションの製作

Tomcat編の中に書こうと思っていましたが、Tomcatの設定について長くなったので、別に分けて書く事にします。
Servlet と JSP、JavaBeans は、セットのようなものなのですが、まずはひとつずつの機能を理解して、そのあとで総合的にどのような動きになるのかを理解するのが近道です。よく、全体像の説明から入るものがありますが、構成要素を先に知っておいて、あとから全体を見たほうがいいと思います。
とりあえず試してみたいという方は、Tomcat より SunONE Studio 5、NetBeans などを使うのがいいでしょう。SunONE Studio を使うと、Java 2 SDKや Tomcat のための環境を設定する手間がかかりません。

また、Servlet/JSPはJ2EEのWebコンポーネントとして動作します。ServletやJSPは単体ではなく全体で動作するために、Webコンポーネントの構成も知っておきましょう。

もくじ?

(工事中)

前提となる知識/ツール

1.どれとどれを選べばいいのか

書籍などではServlet/JSPについて、それとStrutsなどについて書かれているものがほとんどですが、JSP/Strutsの中で使われているカスタムタグについても詳しく知っておく方がいいと思います。あと、JavaBeansやJDBCなんかも、Servletなどとよく組み合わせて使われますが、ここではとりあえずまだ扱いません。

1.1.Servlet について

Servlet は、Web的にCGIのような役割をするものです。 出力形式はHTMLに限らずJPEGなどの画像やその他データファイル形式でも何でもかまいませんが、CGIと同様、HTML形式の出力によく使われます。クラスは、javax.servlet.Http.Servlet を継承(拡張)して作ります。主なメソッドは、doGet()とdoPost() でしょう。それぞれ、HTTPリクエストのGET、POSTに対応して呼び出されます。

HTMLの出力はできるのですが、Servlet内ではformからpostされたデータの処理などにとどめ、HTMLのデザイン的なことは後のJSPやカスタムタグに任せるのが基本です。

1.2 Java Server Pages (JSP)について

JSPは、java.dev.jp のJSPメモに書いてます。こっちへ持ってきましょうか?
Java Server Pagesは、サーバサイドでデザイン的な部分を担当する機能です。全体像は、どこみればいいんだろ・・・。
HTMLの中にJavaのコードを埋め込んで使える技術です。Java Servletのclassに変換され、実行されます。拡張子はjsp
Java Servletについて知っておくと、JSPでもより便利かも?

JSPは単体でも簡単な処理ができるのですが、JSPではServletから受け取ったデータをHTMLに整形するという役割を持たせる方がJavaのコードと分離できてすっきりします。JSP側ではコード的なものは極力タグライブラリやカスタムタグなどで記述するようにします。はじめはJSPの中にコードを埋め込むのもいいですが、慣れてきたらServletとJSPの処理は明確に分けるようにしましょう。

1.3 タグライブラリ/カスタムタグについて

タグライブラリ/カスタムタグは、JSPの中で使えるタグです。XMLファイルとJavaのクラスやJSPの組み合わせで作るので、XMLファイルが面倒かもしれませんが、IDEに任せることができる時代なので、面倒な部分は任せて積極的に利用しましょう。Tomcatなどの入門書ではServlet/JSPの入門だけで終わってしまっている場合が多々ありますが、その間を取り持つものとしてJavaBeansやタグライブラリは重要です。

そのままでも使えますが、出来上がったらカスタムタグのライブラリを作っておくこともできます。

1.4 全体を見渡すということをする

Webアプリケーションの形であるWebコンポーネントは全体的な構成としてどうなっているのか。これも重要なことです。見てみましょうか。

Tomcatであれば webapps/ のディレクトリの下にWebアプリケーションが配置されています。

このディレクトリを見てみると、ROOT/ や examples/ というようなディレクトリがあるかもしれません。ROOT/ はそのドメイン/サーバのトップとして http://www.example.com/ などで表示されるWebアプリケーションです。各ディレクトリは、それぞれのサブディレクトリと対応しているWebアプリケーションです。ROOTだけが特種ですが、その他はディレクトリ名で分けられているだけです。この例は自動配備される場合にのみ当てはまります。各WebアプリケーションをばらばらのディレクトリやURLに配置することも、もちろん可能です。

で、適当に1つ開いてみましょう。

だいたいこんな感じでしょうか。各Webアプリケーションには各種あれこれな設定ファイルなどが使われているのでそれ用にWEB-INF/ というディレクトリが用意されています。このディレクトリはWebからは見えなくなっていて、公開しないパスワード等の設定ファイルなどなどを置いたり置かなかったりします。

その中でまず見ないといけないのがweb.xml ファイルです。このファイルがこのWebアプリケーション全体の定義ファイルです。この設定を変えることでいろいろ動作を変えることができます。

classes/ や lib/ は、Servlet 等々このWebアプリケーションで使用するJavaのクラス群を配置します。jar形式にして lib/ に入れても同じですが、lib/ には自作ではなくまとめて配布されているライブラリなどを入れておくことが多いかもしれません。

tags/ と tlds/ は、カスタムタグのためのものです。tags/ にJSP的なtag ファイル、tlds にはタグの定義を書いたXML形式のtldファイルを配置します。カスタムタグはjarにもできるので、そのときは別でもいい?

その他、必要に応じてServletから呼び出すjspファイルを入れておくディレクトリなどを作ります。

初期のServletやJSPではWebコンポーネントという概念があまり意識されていませんでした。web.xml をしっかり記述しないとセキュリティ的な問題にもなることからServletを使うためにはweb.xmlを書くことが必須となりました。多少敷居は高くなったのかもしれませんが、warファイルにまとめて管理できるなど便利な面もあるので、きっちり習得しておかないといけません。web.xml はWebアプリケーションやJ2EEのバージョンによって記述方法が多少異なっています。上位互換性はあるので古いweb.xmlも新しい環境で使えます。IDEによってほとんどが自動的に更新されますが、自分がどのバージョンのweb.xmlを書いているのか把握しておきましょう。WebコンポーネントはJ2EEの中で動作する場合には、そうでない場合と比べて制約が多くなります。

とりあえずここまで更新してみた。

2.Servlet

Servlet 内では、他のページをinclude する前に文字コードを指定しましょう。
作るだけではだめで、WEB-INF/web.xml にServlet の配置方法も書いてあげる必要があります。
Servlet で最低限書かなければいけないのは、この2つ(の内どちらか)だけです。

void doGet(HttpServletRequest request, HttpServletResponse response) {
}

書く必要のあるのは、ContentType と本文を出力することです。

web.xml 内でのサーブレットのマッピング

サーブレットはURLと関連させて使用しますが、どのような関連づけができるのでしょうか?
雑誌等々のServletの入門記事ではweb.xml の説明が全くないものまであったりしてしまいます。でもServletには必須です。

*.do などはできるようです。 /*.do というのはうまくいきませんねぇ。/Servlet* というのもだめでしょうか。次のようなパターンが設定可能です。

/ ルートディレクトリへ?
/example ファイルへ
/example/ ディレクトリへ
/example/* ディレクトリ以下の全てへ
*.ext 拡張子へマッピング
/example* できません
/*.ext できません
example できません

拡張子にServletを関連づける場合以外では、最初の / は必要です。どうだったかな・・・。 コンテキストルートからのパスです。

コンテキストルートは、Web ApplicationのTOPになるディレクトリです。配備のときに指定する場合は、/dir のように、/からはじめて/をつけずに終わります。

これ以外は使えないようです。 Servlet 2.3仕様の11章に書かれているバターンは詳しくはみていませんがこれだけでした。

古いTomcatなどでは web.xmlに記述しなくても /servlet/package.classname というのも可能でしたが、これはセキュリティに問題があるようなのでお勧めされなくなり廃止されました。ファイル型でマッピングすれば従来と同じURLにServletを割り当てることもできます。

ファイル型のように割り当てても、ディレクトリ型のようなURLでアクセスすることはできてしまいます。その違いは、Servletの名前がどこまでで、パラメータがその後ろにどのようについているか、という判別方法の違いなのかもしれません。試していないので推測だけです。

request と response

Servlet (のdoGet()メソッドとdoPost()メソッド) にも JSP にも、 request と response が用意されています。これは、HTTPのRequest と Response にあたる部分をカプセル化したものです。
Servlet の場合は、doGet(HttpServletRequest request, HttpServletResponse response) のように、引数で受け取って使うことができます。JSPでも同じ名前で使えるようにあらかじめ準備されています。
request から受け取った内容に従って、response に出力結果を作り出すのが基本的な JSP/Servlet の役割です。
JSPでは、タグで各処理ができるので、基本的にrequest や response 自体があることは気にしなくていいようになってます。

参考
HTTP の Request と Respons

例題
アクセスカウンターをつくる

requestから取得できるものの違い

コンテキストルート(warのTOPになるディレクトリ)を /ctx にする。
サーブレットのマッピングを *.extにした場合と/dir/* にした場合を比較しながらみてみます。

GET /ctx/dir/sample/sub.ext?para=data HTTP/1.1
Host: www.example.com

のようにして Requestする場合です。

メソッド 概要 *.ext /dir/* の場合 Errorページ
getRequestURL()

フルパス
scheme + serverName + RequestURI (エラー以外)
404エラーの場合でもリクエストされたURL (Tomcatの場合)

元のページ
  http://www.example.com/ctx/dir/sample/sub.ext
getRequestURI()

サーバルートからの?パス?
HTTPのパラメータとして渡されるURL
contextPath + servletPath + pathInfo
404エラーの場合はエラーページのURI (Tomcatの場合)

エラーページのURI
/404error.jsp など
  /ctx/dir/sample/sub.ext /ctx/dir/sample/sub.ext
getPathInfo() *の部分?
/*以外のマッピングの場合や付加Pathがない場合はnull
  null /sample/sub.ext null
getProtocol() プロトコル規格? HTTP/1.1 または HTTP/1.0
getMethod() HTTPのメソッド GET または POST
getScheme()   http
getServerName() サーバ名 www.example.com
getContextPath() コンテキストルート /ctx
getServletPath()

サーブレットのPath
404エラーの場合はエラーページのPath

  /dir/sample/sub.ext /dir /404error.jsp
getQueryString() ?以下 para=data
getPathTranslated() サーバ内のファイルのある位置
  null /tomcat/webapp/ctx/dir/sample/sub.ext  

include や forwardした場合に変わるものはどれでしょう? includeする側でもされる側でも変化なしです。404エラーページのときには、一部の状態が変わるようですが、どこまで標準で決まっているのかは仕様をみてみないとわかりません。

servletPathやPathInfo、queryStringなどには悪意のあるクロスサイトスクリプティングなどのコードが含まれる場合があるので、そのまま表示するのはやめましょう。

<jsp:include page= /> やRequestDispatcherなどで指定する場合は、servletPath + pathInfo を指定します。pathInfoは要らないかも?
page= で指定できるものは、/WEB-INF/以下のものも含まれます。servletは、mappingしたURLを指定します。たぶん。

サーブレットをマッピングしたことで見えなくなったファイルなどにアクセスするには? /dir/test.txt など
request.getServletContext().getResource() なのかな? これでアクセスするとjspなどもソースの形で取得されます。

他のコンテキスト内のサーブレットなどを呼び出すことは?
一応できるようですが、基本的には1つのサーブレットコンテキスト内で。

dirをサーブレットに割り当て、PathInfoをメソッドとして扱ってもいいかもしれませんね。

パラメータの文字コード

requestからパラメータを取得する場合には、文字コードがわかりません。Servlet 2.4ぐらい?からは、requestにset????Encoding()などのようなメソッドが用意されています。

JSP

JSPファイルの先頭に必要なのが、<%@ page %> タグです。ここで、JSPの基本的な情報を与えます。普通は文字コードだけ指定する事が多いです。<%@ page contentType=text/html; charset=UTF-8" pageEncoding="Shift_JIS" %> など。JSPの文字コードは、pageEncodingがソースコードの文字コード、contentTypeで指定できるのが出力文字コードです。EUC-JPで書いて、Shift_JIS や UTF-8 で出力することもできます。Tomcat 4.0 (JSP1.2)の場合です。JSP1.1の場合は、pageEncoding が使えないので、ページの文字コードと出力文字コードは同じになります。
それから、<% %> の中に書いたコードは、そのままServlet の doGet() や doPost() メソッド内に書かれたのと同じ意味になります。また、 <%=式 %> とすると、Servletの response.getWriter().prontln(式) と同等の処理に置き換えられます。
他にも、各種書式等々があります。
実行されるときには、JSPはServlet に変換されて実行されます。そのために使える機能は、Servlet とほぼ同じですが、ここではデザイン的なことだけを記述するようにしましょう。

参考

JSPとServlet の組み合わせ

JSPとServlet は、組み合わせることができます。
servlet なら、 getServletContext().getRequestDispatcher("JSP/Servletのドキュメントベースからの絶対パスのURL").include(request,response);かな。
JSPからは、<jsp:include page="JSP/ServletのURL" flush="true" /> だったかな?
で、SunOne Studio 4 (旧Forth for Java) などで、 新しくServletを作成すると、out.close() という記述が書かれていますが、このような処理はしてはいけません。これをすると、include などの処理が正しく機能しなくなります。必ず削除しておきましょう。Servlet の入門書などにも同じ記述がある場合は間違いですので注意しましょう。
request、response は、Servlet / JSP をいくつも経由して使うものであり、ServletエンジンのTomcatなどが管理すべきものなのです。勝手にそれらから Reader / Writer / Stream などのリソースを勝手に取り出してclose してしまうのは間違いなのです。
これは、フレームワークとの関係で、よくおこる問題です。フレームワーク側でより安全な作りに変えるという方法もなくはないですが、ルールにそって作るのがいちばんよいです。

*注意 JSPと組み合わせるには、カスタムタグの方がよい場合もあります。

TAGLIB でカスタムタグを作る

ServletとJSPを完全に分けるには、JSP内にJavaらしいコードを一切書かないことです。それにはカスタムタグを使ってタグを作る必要があります。

カスタムタグは、クラス(WEB-INF/classesの中に置くかWEB-INF/libのjarファイルと、タグの定義をしたxmlファイル(拡張子 .tld)で構成されています。使うときはjarファイルにまとめると再利用できますが、まずはWEB-INFの中にクラスとxmlを置けばいいでしょう。

Servletにweb.xml があるのと同じような関係です。tldはXMLファイルで、タグの定義やクラスを連ねます。JSPの先頭で使いたいものを指定できます。

<%@ taglib uri='tldか略称、またはjar' prefix='prefix' %>

web.xml 内でtldの略称を指定することもできます。 直接tld入りのjarファイルを指定する方法があったと思って探していましたが、うまくいかない。なぜ。? 残念。JRunか何かに書かれていたような気がした。

クラスは、javax.servlet.jsp.tagext パッケージのTagSupport や、BodyTagSupport を継承してつくります。とりあえずServletとおなじWEB-INF/classesに入れておいてもいいかもしれないです。JSPのなかのタグは、Tagクラスで説明されているような動作をします。doStartTag()とdoEndTag()を書いてやればできあがりです。TagSupportクラス中のpageContextから、関連する機能をいろいろ取り出せます。

カスタムタグを定義するtldファイル

これは、開発ツールに任せておいた方が楽なのですが。

タグをjarにする

(予定) WEB-INF/classes のパッケージ名とカスタムタグのパッケージ名が重複していると、使えません。

参考

組み合わせの中での言語・文字コード

 Servlet と JSP や、Servlet 2つ、JSP2つなどをincludeで組み合わせる場合、どの文字コードにあわせて処理がされるのか、あんまりはっきりしないので、確かめてみたくなります。
 文字コードは、ばらばらでも呼出元の最上位の呼出元のServlet/JSPで指定したコードに合わせてくれます。本来は、ブラウザで可能なエンコードにされるべきなので、指定しなくてもよいのかもしれません。
 ServletからJSPを呼び出せばいろいろな文字コードにできますが、JSPからServlet を呼び出すと、出力されるのはJSPのcontentTypeで指定した文字コードになります。
これはどういう仕組みかというと、Java内部では文字コードはUnicodeです。そのためにJSPもServlet もUnicodeで処理され、最終的につなぎ合わされてから指定された文字コードに変換して、ブラウザへ送信されているのです。

Webコンポーネントの中での Servlet / JSP のお役目

開発をする中で、よく Model-View-Control(MVC) という言葉をききますが、これは、どういったものでしょうか。
たとえば、 Servlet が Control (手続き的プログラム要素)、 JSP が View (デザイン的要素) に相当します。それではModel は?
Bean やその他 Class が Model です。(データ的要素)
J2EE環境では、Model が Enterprise Java Beans (EJB) などになるのかも。
ここで、JSPとServlet の役割がわかりましたが、このように役割分担することで、開発者も分けることができます。JSPはデザイナーが、Servlet はプログラマーが作って、それらを組み合わせるということになります。もちろん、分担する前にどういうつながりになるかは、きちんと決めておく必要があります。JSPは、デザイナー的役割のひとが作ることを前提にして、Javaのコードをほとんど書かなくてもタグだけで多くのことができるようになっています。

おこめ的組み合わせ方

Servlet と JSP は、それぞれWebを形作る上での部品でしかありません。なので私は JSPから別のJSPを include したり、servlet を include したりすることがよくあります。これは、プログラムを関数/メソッドに分けるのとよく似ています。たとえば、developer.jp では、リンク、タイトルなどのいくつかの JSP パーツを作っておき、それを別のJSPで組み立てて表示しています。JSPひとつだけで出力を完結させなくてもよいのです。

web.xml (配備記述子)

これは、ServletとURLの対応等を記述するファイルです。主な役割はServletとURLを対応させることですかね。

その他、JNDIの名前とJDBCなどのリソースを対応させる場合などにも記述が必要です。

初期値

web.xml内にいろいろな初期値を指定することができます。これにより、サーブレットとリソースの分離がやりやすくなります。

サーブレットの初期値

web.xml
<servlet>
<init-param>
<param-name>パラメータ名</param-name>
<param-value>値</param-value>
</init-param>
</servlet>

Servletのinit(ServletConfig config) {
   config.getInitParameterNames();
   config.getInitParameter(パラメータ名);
}

Webアプリの初期値

JNDIの設定

3段階ぐらいあります

Tomcatのconfファイルの設定

adminツールから設定するのが楽です。

JNDI名は、 jdbc/dbname のように設定します。

web.xmlの設定

JNDI名とクラス名などを対応させます。

<web-app>
  <resource-ref>
     <res-ref-name>jdbc/dbname</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
     <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

こんな感じで<web-app>の最後の方に入れておこう。

ソースファイルでの参照

javax.naming.~で参照するときは、 java:comp/env/jdbc/dbname などのように指定します。

配布

Webアプリケーションは、warファイルというものに圧縮して配布することができます。J2EEの中で、他のEJBなどと組み合わせて使う場合に有用です。

認証系

認証やアクセス制限を行う場合には、レルム(Realm)というものが関係してきます。レルムには、ユーザやグループなどが登録されていて、その単位で認証するとかしないとかを決めたりします。JAASと関係あるのかどうか、よくわかりません。

基本的にはBASIC認証のように、アクセス制限のかかっているページにアクセスしてはじめて認証ダイアログなどが開く形式です。ユーザでもそれ以外でもアクセスできるページ(URL)での認証は、別の方法で実現する必要があります。

Webコンポーネントで使える認証方式は、4つぐらいあります。ダイジェスト認証はブラウザによっては使えない場合もあるとか。

FORMベース認証ではエラーページが指定できますが、その他では指定できないのでしょうかね。

FORMベース認証

FORM認証は、HTMLのFORMを使った認証方法です。アプリケーション設計ガイドなどには詳しく書かれていなかったような気がするので、仕様書の翻訳してほしいところですが、まぁそういうわけで。FORMに特殊なURLなどを組み合わせて認証します。

セキュリティ制限のかかったページにアクセスすると、FORMのページにリダイレクとされます。認証されると、あらためてページが表示される仕組みです。認証されなかった場合にはエラーページを表示します。

認証する場合のURLは、j_security_checkです。これは相対パスでこの形、そしてmethodはPOSTのみ。フォームとして必要なものは、j_usernameとj_password の2つです。この3つの名前は変更することができません。

ログイン用jsp/servletに記述するフォームの例

<form action="j_security_check" method="POST">
  <input type="text" name="j_username">
  <input type="password" name="j_password">
  <input type="submit" value="ログイン">
</form>

こんな感じでしょうか。フォームはアクセス制限のかかったページにアクセスしようとするときにはじめて表示されます。web.xmlでフォームのページとエラーページを指定します。FORM認証なのですが、BASIC認証と同じようなタイミングで認証することしかできません。

非認証のページから、認証が必要なページにリンクをしておくだけです。認証が必要なページにアクセスした時点でログインページにリダイレクトされ、ログインすると、j_security_checkから認証ページにリダイレクトされます。

できないこと

レルム(realm) / 認証用アカウント

認証に使うユーザのデータベースのような感じのものがレルムです。ユーザとグループ、のような感じになります。一応JAASとして互換性はあるようですが、各サーブレットエンジンによって使えるものは異なります。たとえばこんなもの

場合によっては新しいレルムの仕組みを作る必要があるのですが、この部分の資料はあまり多くありません。

データベースに登録したユーザで認証するには?

ここはServletエンジンによって互換性のない部分です。

Tomcatの場合

DataSourceとJDBCの両方が使えます。

org.apache.catalina.realm.JDBCRealm
org.apache.catalina.realm.DataSourceRealm

ユーザとロールのテーブルを用意しませう

参考

Sun ONE Application Serverの場合

謎が多い

Sun ONE Application Server 7 パーフェクトガイドに書かれていたかな

JBossの場合

プリシンパル

JAASでアカウント等のことをプリシンパルという形で扱うようですが、Servletで使えるようなので、使えるのでしょう。

ログアウト

ログインしたらログアウトしたくなります。

セッションを無効にすることでログアウトできます。

WebSphere では、別の方法も用意されているらしいです。

セッションを使う

Last update 2003.10.29

session という概念があります。これは、これです。(続く)
Cookie というのもあります。どっちを先にしましょうか。

Cookieを使ったセッション管理でできることは、ブラウザ単位の固体判別です。1つのブラウザでも複数ウインドウやタブを使って1つのサーブレットにアクセスしていた場合に、どのウインドウやタブからアクセスしたり登録された情報なのか、ということは判定できません。どうすればいいのでしょうね。常にFORMでつないでいくのでしょうか? それではあまりにも、なので、何か対策をしなければなりません。

まず最初に考えられることは、URLの中にセッション情報を埋め込むことです。この方法も基本的にはCookieを使ったセッション管理と同じですが、ウインドウ単位で個別の情報を扱うこともできます。別々のウインドウから操作している、という判定基準は必要です。

セッション情報をURLに埋め込む場合に、セッション情報に常に同じ値を使うのではなく、毎回変化させながら固体を追跡することで、アクセスするURLの前後関係も把握しやすくなります。同じページから複数のページが開かれた場合には、セッション情報を分岐する、という方法をとることができます。

<form> でセッションを扱うのは、少々難解です。Cookieを受け付けないブラウザの場合にどうしていけばよいでしょうか。

Get Thunderbird


[しいしせねっと] © 2003,2005,2006 Siisise Net