PHP4ではprivateや__constructといったオブジェクト指向的書き方に必要な要素が使えないので、今回からの内容はPHP5以上の話となります。
クラスを使って何が便利かというと、クラス内の関数でデータを共有できる点です。
普通にサブルーチンとして関数を使用した場合、必要なパラメータをすべて引数として渡さなければなりません。
その点、クラスの場合は一旦適当なsetParameterを呼び出せば、プログラム終了までその値を持ち続けてくれます。
HTTPの限界上、さすがにリロードされたりすると消えてしまいますが、使いこなせば非常に便利です。
はっきり言ってデータと手続きの一体化とか、ポリモフィズムとか、上に比べるとどうでもいい話です。
どうせ規模が大きくなったらまたわからなくなりますし。
とりあえず図形の面積を求めるクラスを作ってみましょう。
area.php
| class Area{ //変数宣言 private $base=0; private $height=0; //セッター public function setBase($base){ $base=$base+0; $this->base=$base; } public function setHeight($height){ $height=$height+0; $this->height=$height; } //ゲッター public function getTriangle(){ return $this->base*$this->height/2; } public function getQuadrangle(){ return $this->base*$this->height; } public function getOval(){ return $this->base*$this->height*M_PI/4; } } |
まず変数宣言部分で、クラス内で共通して使う変数を定義します。
privateをつけると、クラス外からその値を直接参照することが出来なくなります。
セッターメソッドは、クラス内変数に値を設定するメソッドです。
上記のようにprivate変数はクラス外から変更することが出来ないため、setBase等のpublic関数を通じて行わなければなりません。
引数として渡される値は数値を想定していますが、必ず数値が渡されるとは限りません。
うっかり、あるいは故意に文字列やスクリプトが渡されるかもしれません。
それを防ぐためにバリデートが必須となります。
今回は手っ取り早く+0とすることで数値化しています。
PHPのデータ型の扱いは極めてアバウトなので、文字列だろうがなんだろうが+0すると数値にしてくれます。
まあ配列やオブジェクトが来ると死にますが、今回はそこまでやっていません。
ゲッターメソッドは、クラスから値を受け取るメソッドです。
クラス内のprivate変数の値を参照したい場合など、このメソッドを通じて値の引取りを行います。
このクラスを呼び出す側で、
$area=new Area();としてインスタンス化し、
$area->setBase(10);$area->setHeight(20);
としてクラス内の変数$baseと$heightに値をセットしてあげます。
その後はいつでも$area->getTriangle()や$area->getOval()で値を取り出すことが出来るようになります。
値を変更したくなったら再度$area->setBase(50);等としてやればいいです。
index.php
| include_once('area.php'); //インスタンス化 $area=new Area(); //セッター $area->setBase(10); $area->setHeight(20); //ゲッター print($area->getTriangle().'<br />'); print($area->getQuadrangle().'<br />'); print($area->getOval().'<br />'); //値を変化させてみる $area->setBase(50); print($area->getTriangle().'<br />'); |
以上のクラスで行ったことを、関数で行うと以下のようになります。
index2.php
|
$Base=10; |
量的には関数のほうが少なくてすみますが、呼び出す側では毎回getTriangle($base,$height)というふうに引数を入れてやらなければならなかったり、関数毎に引数のチェックをしたりしなければならなかったりと手間がかかります。
ただまあ、タイトルに反していますが、これが実用的なクラスかといわれると断じて否と言えます。
面積程度のことならば直接書いたほうがよっぽど早いですし。
次回はもう一歩だけ実用に近づいていくとしましょう。
前回、いきなりout.printlnという命令を書いていましたが、このoutはメソッドを見てわかるとおりPrintWriterオブジェクトです。
Javaサーブレットでは毎回毎回PrintWriter out = response.getWriter();と書く必要がありましたが、JSPでは何の宣言も無くいきなり使用しています。
どうなっているのでしょうか。
JSPでは、面倒なクラスのインスタンス化宣言をしなくてすむように、よく使うクラスは最初から自動的にインスタンス化されています。
つまり、こちらの目に見えないところでJspWriter out = pageContext.getOut();が実行されているということです。
これらはJSPの暗黙オブジェクトと呼ばれ、out以外にも最初からインスタンス化されているオブジェクトが幾つか存在します。
前回使ったところではapplication、これは見てのとおりアプリケーションスコープのことで、pageContext.getServletContext()を示します。
applicationに突っ込んだ変数は、Javaサーブレット同様どのファイルからでも参照することができます。
それ以外に有用なところとしては文字通りセッションスコープを示すsession、リクエストとレスポンスを示すrequestとresponseがあります。
他にも何種類かありますが、どうせ使わないので覚えなくていいです。
データをDBに保存する等の永続化の必要がないサイトであれば、概ね暗黙オブジェクトだけで作成することができるでしょう。
ところでこれまでアプリケーションスコープにいろいろなデータを突っ込んできました。
アプリケーションスコープの利点は各サーブレットで簡単にデータを共有できることですが、逆に言うとこれがアプリケーションスコープの欠点となります。
Javaサーブレットで作成したカウンター
http://yuubiseiharukana.blog.shinobi.jp/Entry/26/
JSPで作成したカウンター
http://yuubiseiharukana.blog.shinobi.jp/Entry/28/
同時に動かしてみるとわかりますが、両者で変数strCntが共有されています。
これが一人で作成しているのであれば変数名を変更するだけですみますが、これが複数人で開発しているシステムだったら、あるいは一人で行っていても規模が大きくなってきたら把握しきれるものではありません。
両カウンターともstrCntのパース時に例外処理を行っていないため、誰かが変数strCntに文字を入れたりしたらエラーが発生してしまいます。
場合によってはクラックの突破口となってしまうかもしれません。
Javaにはvar_dumpのような便利関数も存在しないため、どの変数が何処で使われているのかといった情報を調べるのも簡単なことではありません。
面倒なことになる前に面倒なことになる危険性のある行為はやめましょう。
最後にいろいろなスコープに値を突っ込んだ場合の取り出し方を見てみましょう。
どのようなスコープに変数をどうやって突っ込んで、どうやって取り出せばいいかがわかると思います。
セッションやアプリケーションスコープについてはこの例ではあまり意味がないのですが。
ページスコープは本当に1ページだけしか値を保持してくれないので、一回のリクエストで完結するならリクエストスコープ、繋がっている間データを保存したいならセッションスコープが適当です。
ELについてはまた次回。
src/xxx/scopetest.java
|
package xxx; import java.io.*; public class scopetest extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String strContext="strContext"; //アプリケーションスコープにセット //セッションスコープにセット //リクエストスコープにセット //指定したページをインクルードします |
/jsp/scopetest.jsp
|
<%@ page contentType="text/html;charset=Shift_JIS" %> session.getAttributeで呼んでみる<br> application.getAttributeで呼んでみる<br> EL:pageScopeで呼んでみる<br> EL:requestScopeで呼んでみる<br> EL:sessionScopeで呼んでみる<br> EL:applicationScopeで呼んでみる<br> EL:いきなり呼んでみる<br> </body> |
KWICを作ってみるの続きです。
あちらでフロント部分を作成したので、今回はバックエンド部分の作成です。
整形は面倒ですが、プログラム的には特に難しいことを行っているわけではありません。
innerHTMLがGET形式で送られてくるので、それを受け取ってテキストを作成して返す、それだけです。
返すといっても、行うことはブラウザに表示するのと全く同じです。
ブラウザからのリクエストの場合、表示した文はブラウザの画面上に表示されますが、XMLHttpRequestからのリクエストへの返答はresponseTextの中に入ります。
'gingatetsudono_yoru.txt'は、青空文庫から拾ってきてください。
このプログラムの場合、読み込むファイル名をHTML側で決めているので、簡単に詐称が可能です。
隠しておきたいサーバ内のファイルを読み出されたりしかねませんので、サーバ側で必ず引数のチェックを行ってください。
以下の例の場合は拡張子がtxtかどうかというだけの緩い制限になっていますが、本来は許可するファイルのリストを作り、それ以外であれば不許可にすべきです。
特に、以下のコードそのままだとディレクトリトラバーサルの格好の餌食となります。
それ以前に、HTMLにファイル名を出さなくていい方法もありますので、実用する際はできるだけセキュリティ的に安全な方法を選びましょう。
inclemental_server.php
|
<?php |
一見面倒ですが、やっていることはテキストからHTML文を作成しているだけです。
XMLHttpRequestはUTF-8しか受け付けません。
そのために表示文字列の文字コードをUTF-8にする必要があるのですが、PHPファイルがEUC-JP、テキストファイルとjsはUTF-8、そしてHTMLファイルはSJISで書かれているという不健康極まりない状態です。
テキストファイルのUTF-8データと、PHPで付け足したEUC-JPのデータを、一見無用な文字コード変換を入れることで無理矢理統一しています。
まあ揃えられるなら全部揃えたほうが安全です。
さあ、これでリロードを伴わない動的な検索サービスを作成することができました。
http://yuubiseiharukana.creativeroot.jp/js2/2-5-1.html
さて、今回は直接HTMLタグまで書き込んだ上で返信を行っていますが、これはMVCの観点からあまり正しい方法とは言えません。
データの送受信はJSONで行い、jsとcssで装飾を行ったほうがレイアウト変更等にも対処しやすく便利です。
が、個人的にJavaScriptでのパースが苦手なのでPHPで行っています。
XML?
企業同士ならともかく、個人ユースでXMLは資源の無駄としか言い様が。
クロスサイトリクエストフォージェリの続きです。
今回の話は、Mixiに常時ログインできるようになっていないとうまくいきません。
ニコニコ動画の右上あたりに、ブログに貼り付けボタンがあります。
<OPTION value=mixi|http://mixi.jp/favicon.ico|http://mixi.jp/proxy_post_video.pl?pid=NC&vid=sm9&video_title=%E6%96%B0%E3%83%BB%E8%B1%AA%E8%A1%80%E5%AF%BA%E4%B8%80%E6%97%8F%20-%E7%85%A9%E6%82%A9%E8%A7%A3%E6%94%BE%20-%20%E3%83%AC%E3%83%83%E3%83%84%E3%82%B4%E3%83%BC%EF%BC%81%E9%99%B0%E9%99%BD%E5%B8%AB selected>mixi</OPTION>
ちょっと試してみましょう。
ブラウザのURLに、
http://mixi.jp/proxy_post_video.pl?pid=NC&vid=sm9&video_title=%E6%96%B0%E3%83%BB%E8%B1%AA%E8%A1%80%E5%AF%BA%E4%B8%80%E6%97%8F%20-%E7%85%A9%E6%82%A9%E8%A7%A3%E6%94%BE%20-%20%E3%83%AC%E3%83%83%E3%83%84%E3%82%B4%E3%83%BC%EF%BC%81%E9%99%B0%E9%99%BD%E5%B8%AB
見事、新規日記の本文に内容が書き込まれています。
勿論ニコニコ動画はMixiのアカウント情報など知りませんし、URLにもそのようなものはありません。
URLを見るとproxy_post_video.plにパラメータを渡せばいいようです。
通常日記を書くのはadd_diary.plですから、外部からパラメータを送信すれば新たな日記を作成するようなプログラムを作成したようです。
かつてMixiには、外部から簡単に日記を作成できるというCSRF脆弱性そのものが存在していました。
http://www.atmarkit.co.jp/fsecurity/column/ueno/33.html
現在は対処されているということになっています。
どのように対策されているのでしょう。
通常日記を書くときの流れを順番に見てみましょう。
まず最初の日記を書くリンク。
<a href="http://mixi.jp/add_diary.pl?id=9999999">日記を書く</a>
普通にリンクが張ってあるだけです。
試しにIDを書き換えてみるとアクセスできません。
内部的にセッションで監視しているようです。
次に日記作成画面。
| <form name="diary" method="post" enctype="multipart/form-data" action="add_diary.pl"> <input type="hidden" name="news_id" value=""> <input type="hidden" name="campaign_id" value=""> <input type="hidden" name="invite_campaign" value=""> <input type="hidden" name="id" value="9999999"> <input type="hidden" name="news_title" value=""> <input type="hidden" name="news_url" value=""> <input type="hidden" name="movie_id" value=""> <input type="hidden" name="movie_title" value=""> <input type="hidden" name="movie_url" value=""> <input type=hidden name=submit value=main> <input value="" name="diary_title" class="editareaWidth" size="40" /> <textarea name="diary_body" cols="65" rows="20" id="diaryBody"></textarea> <input type="file" size="40" name="photo1" /> <input type="file" size="40" name="photo2" /> <input type="file" size="40" name="photo3" /> <input type="checkbox" name="orig_size" checked="checked" id="origSize" /> <select NAME=tag_id><optgroup label="標準の公開設定"><option value="0" selected>全体に公開</option></optgroup><optgroup label="個別の公開設定"><option value="1" >非公開</option><option value="2" >友人まで公開</option><option value="3" >友人の友人まで公開</option><option value="4" >全体に公開</option></optgroup></select> <input type="submit" class="formBt01" value="入力内容を確認する" /> |
よくわからないパラメータがたくさんありますが、重要なのはdiary_titleとdiary_body、そしてtag_idでしょうか。
よく見ると送信先がadd_diary.plと、最初のURLと同じになっています。
いずれかのパラメータで振り分けているのでしょう。
次に送信確認画面です。
| <form action="add_diary.pl" method="post"> <input type="hidden" name="submit" value="confirm" /> <input type="hidden" name="packed" value="" /> <input type="hidden" name="post_key" value="123456789abcdef0123456789abcdef0" /> <input type="hidden" name="news_id" value="" /> <input type="hidden" name="campaign_id" value="" /> <input type="hidden" name="invite_campaign" value=""> <input type="hidden" name="id" value="9999999" /> <input type="hidden" name="diary_title" value="タイトル" /> <input type="hidden" name="diary_body" value="本文" /> <input type="hidden" name="news_title" value="" /> <input type="hidden" name="news_url" value="" /> <input type="hidden" name="movie_id" value="" /> <input type="hidden" name="movie_title" value="" /> <input type="hidden" name="movie_url" value="" /> <input type="hidden" name="tag_id" value="0" /> <input type="submit" class="formBt01" value="作成する" /> </form> |
<input type=hidden name=submit value=main>
<input type="hidden" name="submit" value="confirm" />
名前からしてこのパラメータが怪しいですね。
内部的にここで振り分けていると思って間違いないでしょう。
では試してみましょう。
上のソースをメモ帳に貼り付けて、action="http://mixi.jp/add_diary.pl"と書き換えます。
ボタンを押すと、文字化けこそしていますが見事に日記が作成されました。
というわけで外部から日記を一気に書き込む条件がわかりました。
以上のパラメータを適切に設定してやればよいということになります。
同時にMixiのCSRF対策が判明しました。
submit=confirmのとき、すなわち日記作成の確定処理の際に、正しいidとpost_keyを一緒に送信しないといけないようにしてあります。
試しにどちらかを変更すると、日記の作成に失敗してしまいます。
現在のMixiに対してCSRFを行うためには、何処からかidとpost_keyを取得してこなければなりません。
さて、これを一体どうすればいいでしょうか。
リンクで飛んだ先から、リンクを踏んだ者のidを取得するのはちょっと困難です。
そのリンクを貼っている者のidは簡単に取得できますがあまり意味がありません。
ましてやpost_keyなどどうやって取得したものか。
cookieさえ覗ければ簡単ですが、Mixiで取得したcookieは、通常Mixi以外に開示されることはありません。
ちょっと現在の技術力では手の出しようがないみたいです。
なにやらCSRFの説明というよりMixiの穴探しになりましたが、CSRF対策を忘れるとどうなるか、そしてCSRF対策の方法はなんとなくわかったのではないでしょうか。
Javaサーブレットは結局のところJava言語そのものであり、たかだかWebページを作成するためにJavaサーブレットを用いるというのは鶏を割くのに牛刀を用いるようなものです。
他にも、ちょっとでもソースを変更するたびにコンパイルが必要だったり、ヒアドキュメントが無いので文字表示を行うたびに
response.getWriter().println("Hello World");
等面倒なコードを書かなければならなかったり、デザインとロジックの分離がまったく出来ないという問題点もあり、Webアプリとしてはお話になりません。
また複数言語で同じ処理を行うコードの例を載せているところがありますが、だいたいJavaサーブレットだけコード量が1.5倍から2倍くらいになっています。
いくら理念が優れていたところで現実的に面倒なら使う意味がないのが世の常です。
その欠点を解消すべく登場したのがJSPです。
例によってとりあえずHelloWorldと書いてみましょう。
配置場所はWebContentフォルダ内です。
HelloWorld.jsp
| <html> <head> <title>Hello, World!!</title> </head> <body> <% out.println("<p>Hello,World!!</p>"); %> </body> </html> |
PHPやASPにしか見えません。
JSPには、サーブレットで面倒だったコンパイルやクラスファイルの配置が一切必要ありません。
普通にHTMLを作成する感覚で作成することができます。
また、必要となるクラスのインポートやHttpServletの継承、一部オブジェクトのインスタンス化等も自動的に行ってくれるうえ、ややこしいweb.xmlの記入も基本的に必要ありません。
どうして最初からこれらの機能を搭載しなかったのか不思議でなりません。
内部的には、JSPは初めて呼び出されたときに自動的にJavaサーブレットに変換され、さらにコンパイルされてクラスファイルとして実行されます。
では今後JavaサーブレットはすべてJSPに置き換えてしまえばいいかというと全くそういうことはありません。
JSPだけだと却って面倒になったり見難くなったりするからです。
最後に、例によってJavaサーブレットでも行った簡単カウンターをさくっと作ってみます。
counter.jsp
|
<%@ page contentType="text/html;charset=Shift_JIS" %> |
Javaサーブレットは文字コードのことを全く考えません。
考えないだけならまだしも、何もしなければ勝手にヘッダにISO-8859-1という文字コードを指定してしまいます。
ISO-8859-1は日本語を扱えないので、日本語を表示するためには、あらゆるサーブレットで毎回必ず
req.setCharacterEncoding("Windows-31J");
res.setContentType("text/html; charset=EUC-JP");
とヘッダを上書きするコードを書かなければなりません。
また、ショッピングサイト等、必ず上や左にメニューが付いてくるページがありますが、それを実現するためには全てのページでメニューを書かなければなりません。
Java以外の言語を使用しているサイトでは、メニュー部分を別に作成しておき毎回インクルードする、入口を一本化してパラメータで内容を振り分ける等の仕組みで実現しています
Javaでも同様にincludeする仕組みがいくつもあるのですが、それ以外にもJavaアプリケーションには最初からJavaサーブレットに横断的にフィルタを実装する仕組みがあるので使ってみましょう。
とりあえずweb.xmlから。
| <filter> <filter-name>charsetFilter</filter-name> <filter-class>charsetFilter</filter-class> </filter> <filter-mapping> <filter-name>charsetFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
servletとほぼ同じ指定方法です。
servletはurl_patternにマッチしたJavaサーブレットを実行したあと終了しますが、filterは該当するJavaサーブレットを実行した後も、終了せずにservletをそのまま続けて実行します。
またservletは複数のURLにマッチした場合優先順位の高いほう一つしか実行されませんが、filterはマッチするサーブレットすべてが実行されます。
横断的に処理を行うのにうってつけのクラスです。
url-patternには前回までのような直接指定のほかにワイルドカード*が使えます。
Linuxの*ではなくMS-DOSの*なので注意が必要です。
<url-pattern>/*</url-pattern>と指定することで、すべてのクラスにフィルタを適用することができます。
ではfilter-classで指定したcharsetFilterを作成しましょう。
charsetFilter.java
|
import java.io.*; public class charsetFilter implements Filter { |
フィルタを行うクラスはFilterインターフェイスをimplementsしなければなりません。
Filterインターフェイスにはinit、destroy、doFilterの3つのメソッドが定義されていますが、initとdestroyは使わないので放置します。
<filter>が呼び出されるたびにdoFilterメソッドが呼び出されるのでこれを上書きすることでフィルタを実装します。
このdoFilterメソッドでは文字コードを指定しています。
全てのサーブレットは自動的にこのcharsetFilterクラスを通って実行されるので、今後すべてのサーブレットでsetContentTypeを書く必要がなくなります。
たとえば認証処理を行いたい場合、Javaサーブレット以外の言語では、すべてのファイルで認証用のルーチンを呼び出したりということを行わなければなりません。
Javaサーブレットでは、認証処理を行いたいディレクトリをweb.xmlに指定し、認証を行うサーブレットを書けば、該当するディレクトリに入っているサーブレットは自動的に認証処理を呼び出してくれます。
doFilter内で再度doFilterを呼び出していますが、前者はjavax.servlet.FilterインターフェイスのdoFilterで、後者はjavax.servlet.FilterChainインターフェイスのdoFilterであり、この両者は違うものです。
名前が同じわりに引数も違います。
FilterのdoFilterメソッドにはそこで行う処理を記述しますが、FilterChainのdoFilterメソッドは、次に当てはまる<filter>のdoFilterメソッドを実行します。
次に当てはまる<filter>が無ければ<servlet>を実行します。
よくわからなければとりあえずこう書くものだとだけわかっていればいいです。
最後に、企業や携帯サイトのように必ず上下にヘッダ、フッタを出力するフィルタを作成してみます。
headerFilter.java
| public class headerFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { PrintWriter out = response.getWriter(); out.println("ヘッダ<hr>"); chain.doFilter(req, res); out.println("<hr>フッタ"); } public void init(FilterConfig config) throws ServletException{} public void destroy(){} } |
web.xml
| <filter> <filter-name>charsetFilter</filter-name> <filter-class>charsetFilter</filter-class> </filter> <filter> <filter-name>headerFilter</filter-name> <filter-class>headerFilter</filter-class> </filter> <filter-mapping> <filter-name>charsetFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>headerFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
フィルタの実行順は、<filter-mapping>の順番です。
文字コードを設定するcharsetFilterサーブレットを先に書く必要があります。
前回までに作成した適当なサーブレットを呼び出してみると、当のサーブレットはまったくいじっていないのに、ヘッダとフッタが勝手に追加されているはずです。
doFilter部分が次のフィルタ、およびJavaサーブレットを実行している部分なので、それより下に処理を書くことでフッタ部分を設定することもできます。
このようにサーブレットを横断する処理を作成できるのが、Javaサーブレットの数少ない利点の一つです。
また、<url-pattern>に適合する限り、そのうち出てくるJSPにも適用されます。
これは便利な機能だ、と思いきやgetServletContext().getRequestDispatcher().forwardでJSPにフォワードした場合のみフィルタが適用されないという例によって意味のわからない仕様になっています。
counter.java
|
import java.io.*; |
一番意味のわからない型変換は気にしないとして、リロードすると値が増えていきます。
別のPCからアクセスしてもカウンターの値は共通しています。
ファイルに保存していないカウンターの値が何故保持されるのか、それはJavaサーブレットの動作方法に理由があります。
HTTPやPerlといった旧来のWebコンテンツはサーバ上で常に待機しているわけではなく、リクエストがあった場合にサーバから呼び出され、プログラムを実行するなりHTMLファイルを返すなりしたらそのまま終了してしまいます。
プログラムを終了してしまうのですから、それまでメモリ上にあった値はすべて破棄されます。
必要な情報はファイルやDB等に保存しなければなりません。
それに対しJavaやPHPといった最近のWebコンテンツは、サーバ上で常駐し、リクエストがあると常駐しているプログラム内部で処理を行います。
プログラムを終了しないので、メモリ上の値はずっと保存されます。
中でも特に、アクセスしてきた者全員で共有できるスコープをアプリケーションスコープといいます。
web.xmlでは<context-param>で、JavaサーブレットではServletContextで、JSPではapplicationで、ELではapplicationScopeとinitParamでアクセスすることができます。
JSPやELについてはまたそのうち。
アプリケーションスコープに突っ込むだけで簡単に情報を共有できるので、ファイルに保存せずに掲示板等が作成できてしまいます。
bbs.java
|
import java.io.*; |
サニタイジングとぬるぽ対策以外は何もやっていない非常にいい加減な、とりあえず動けばいい掲示板です。
サーバの再起動等があると当然メモリ上のデータもすべて消えてしまいます。
簡易チャット程度ならいいですが、長期にわたりログを保存する場合は、やはりストレージに記憶することになるでしょう。
次回はリスナーについて、はどうせ使わないからいいか。フィルタの話でもしましょうか。
どうせ後で嫌というほど混乱するので、Javaアプリケーションのスコープについて今は簡単に解説しておきます。
一番広いのがアプリケーションスコープという単位です。
範囲はexampleパッケージ全体であり、いわゆるグローバル領域よりも広い範囲です。
ここに保存された値は、サーバが再起動されるまで保持され続け、誰からでも参照できるようになります。
次がセッションスコープで、いわゆるセッションです。
次がリクエストスコープで、一回のサーブレットの呼び出しの単位で、他プログラミング言語で言うところのグローバル領域です。
最も小さいのがページスコープであり、プログラム一本に値します。
リクエストスコープとページスコープの違いは、サーブレットから他のプログラムを呼び出したとき等に呼び出し先に値が渡されるか否かということです。
前回や今回のようにプログラム一本で完結する場合、両者は同じ意味です。
それを踏まえたうえで初期化パラメータの設定と取得を行ってみましょう。
初期化パラメータとは、サーバ全体、あるいはサーブレット全体に共通して設定しておきたい値を保存しておく場所です。
例えばDBのアドレス、ログインIDやパスワード等です。
WebContents/WEB-INF/web.xml
|
<web-app> <servlet> |
<web-app><context-param>で設定するのはアプリケーション単位で、どのサーブレットからでも参照することのできる値です。
<web-app><servlet><init-param>がサーブレット単位で、この場合HelloWorldサーブレットからしか参照できません。
ユーザ側から見た場合わけのスコープに対して、初期化パラメータはサーバ側から見た場合わけなので、init-paramに対応するスコープはありません。
次に取得側。
index.java
|
import java.io.*; |
getServletContextメソッド、getServletConfigメソッドはそれぞれServletContextインターフェイスおよびServletConfigインターフェイスを呼び出しています。
ServletContextインターフェイスのgetInitParameterメソッドでcontext-param初期化パラメータを、ServletConfigインターフェイスのgetInitParameterメソッドでinit-param初期化パラメータを取得することができます。
取得したいパラメータのスコープによって、取得するのに必要なインターフェイスが変わりますので注意が必要です。
例えばgetServletContext().getInitParameter("i-paramater");とすると、アプリケーションスコープのc-paramaterを取得しに行きますが、アプリケーションスコープのi-paramaterには何もセットされていないのでnullが返ってきます。
次はセッションスコープです。
|
import java.io.*; |
HttpSession.getSession()で自動的にセッション管理を行ってくれます。
この場合、最初の一度だけ新しいセッションと判断され、以後は何度アクセスしても古いセッションとなります。
しかしスコープがセッション単位なので、ブラウザを立ち上げなおせばまた新しくなります。
リクエストスコープとページスコープについてはJavaサーブレットだけでは少々面倒なのでJSPで行うことにします。
オブジェクト指向の極致として登場し、鎧袖一触に他のサーバサイド言語を駆逐するはずだったJavaですが、設計者実装者のセンスが絶望的に悪すぎたため全く普及しませんでした。
間違いなく30年後には滅びてますが(でもCは残ってる)、Web2.0とかが大好きな人たちの間で流行っているようなので現在は使わざるを得ません。
一口にJavaと言ってもWebで使用されるのは主にJavaサーブレットと呼ばれる言語です。
Web系だけでも他にJSP、XMLシンタックス、ELといった各種言語があり、それぞれに全く互換性がありません。
それらをXMLとで無理矢理結び付けただけの寄せ集め言語Javaを学習していきます。
Java実行環境を手に入れるには、まずJDK(JREではない)、Apache、Tomcatをインストールし、各設定ファイルを正しく変更し、コンテナを記述してWebアプリケーションを登録しなければなりません。
一般人には不可能なので、統合開発環境に任せてしまいましょう。
Javaの統合開発環境としてはEclipseが有名ですが、英語です。
最初から各種プラグインを搭載した上で和訳してくれているPleiadesがありますのでそちらを使用します。
http://mergedoc.sourceforge.jp/
こちらからJRE付きのJavaWTPをダウンロードしてインストールします。
上記全ソフトウェアのインストール及び設定ファイルの変更等を自動的に行ってくれます。
それ以外にも開発環境支援機能が満載なので使い慣れましょう。
インストールしたら早速Javaサーブレットを作ってみます。
Javaサーブレットといっても、Web向けに拡張されたクラスを使用した、単なるJavaアプリケーションです。
Javaの基本を学習するだけで年が明けますが、基本を学んだところで使わないのでスルーということで。
まずウィンドウ→ビューの表示→ナビゲーターで左側にナビゲータを開きます。
次にナビゲータの上で右クリック→新規→プロジェクト(R)→動的Webプロジェクトを作成します。
パッケージ名はとりあえずexampleにしておきます。
よくわからない部分はデフォルトのままにしておいて問題ありません。
自動的にいくつかフォルダが作成されますが、使用するのは「src」と「WebContext/WEB-INF/web.xml」ファイルです。
srcフォルダにはJavaサーブレットのソースを置き、web.xmlではサーブレットとURLの関係を記述します。
まあまずはsrcフォルダに新規ファイルを作成します。
Javaではファイル名とクラス名を同じにしなければならないという決まりがあるので注意しましょう。
HelloWorld.java
|
//パッケージ |
次にweb.xmlを編集します。
WebContents/WEB-INF/web.xml
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>example</display-name> <servlet> <servlet-name>Hello</servlet-name> <servlet-class>HelloWorld</servlet-class> </servlet> <servlet-mapping> <servlet-name>Hello</servlet-name> <url-pattern>/Hello</url-pattern> </servlet-mapping> </web-app> |
この後Ctrl+F11を押すと、自動的にサーバが起動した挙句404エラーが出てきますが、気にせずブラウザに、
http://localhost:8080/example/Hello
と入力しましょう。
問題が無ければブラウザに「HelloWorld!」と表示されるはずです。
既にうんざりですが今からこれの解説を行いましょう。
まずフォルダ配置から。
WebContentフォルダがドキュメントルートとなります。
Javaサーブレット以外の通常のHTMLコンテンツは、WebContent以下の自由な場所に置いてかまいません。
アクセスする場合も、
http://localhost:8080/example/index.html
と普通にファイル名を記入するだけでアクセスすることができます。
WebContent/WEB-INFフォルダはJavaによって制御されており、普通にアドレスを入力しても見ることができません。
srcフォルダは、Javaサーブレットのソースファイルを置いておく場所です。
今度はファイルの中身です。
最上段のパッケージですが、srcからのフォルダ構成を記入します。
今回は直接src直下に置いているので使用しませんが、基本的にpackage要素は必須となります。
上の例では、このファイルはsrc/com/hogehoge/www/HelloWorld.javaに存在するということになります。
ちなみにpackageはドメインを逆から設定するという緩やかな掟があり、そうすることで世界中で重複を防ぐという意図があります。
今回はローカルなので意味がありませんが。
次のimportですが、Javaは素の状態ではテキストの表示すらできません。
処理を行うのに必要なクラスを毎回ロードする必要があります。
こんなとこだけcを真似なくてもよいものを。
java.io.PrintWriterおよびjava.io.IOExceptionクラスは、文字の読み書き機能およびその際起こる可能性のあるエラー処理機能を提供します。
javax.servlet.http.HttpServletクラスはあらゆるJavaサーブレットが必ず継承しなければならないクラスです。
クライアントからの要求やサーバ情報をjavax.servlet.http.HttpServletRequestインターフェイスで受け取り、その要求への応答をjavax.servlet.http.HttpServletResponseインターフェイスに返します。
javax.servlet.ServletExceptionはサーブレットでエラーが起きた際の処理機能です。
以上はサーブレットを作成する際に最低限必要なクラスです。
それ以外にも日時を取得したくなったらjava.util.Calendarをインポート、セッションを使いたければjavax.servlet.http.HttpSessionをインポート、といったふうに馬鹿みたいなimport文を書く必要があります。
public class HelloWorld extends HttpServlet
は、こう書く決まりです。
HelloWorld部分にファイル名を入れますが、それ以外はそのままにします。
ここから、HttpServletクラスを継承するHelloWorldクラスを作成していきます。
public void service(HttpServletRequest req,HttpServletResponse res) throws ServletException, IOException
こちらは、一字一句違わずにこう書く決まりです。
HttpServletクラスには幾つかのメソッドが定義されており、それを変更することで目的の動作を作成していく、というのがJavaサーブレットの基本的な作成方法となります。
Javaサーブレットは、呼び出されたときに自動的にserviceメソッドが実行されます。
serviceメソッドは、デフォルトではリクエストがGETならばdoGetメソッドを実行し、POSTならばdoPostメソッドを実行するといった振り分け処理を行っています。
今回は振り分けを行う必要が無いのでserviceメソッドを上書きします。
service,doXXXメソッドは引数にHttpServletRequestとHttpServletResponseを取り、ServletExceptionとIOExceptionをスローするという決まりになっています。
リクエスト元から渡されたヘッダやパラメータをHttpServletRequestから取得し、応答をHttpServletResponseに乗せて送り返すという流れです。
PrintWriter out = res.getWriter();
HttpServletResponseインターフェイスがServletResponseから継承したgetWriterメソッドを使用し、PrintWriterオブジェクトを作成します。
PrintWriterオブジェクトのprintlnメソッドを使用することで、応答メッセージをHttpServletResponseに返すことができます。
何を言ってるのかさっぱりわかりませんね。
とりあえずは全部コピペしておいて、必要部分だけ変更することにしていけばいいです。
次にweb.xmlですが、これはJavaサーブレットと、ブラウザのURL欄に入力するアドレスとを結びつけるファイルです。
まずはservlet-mappingです。
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/Hello</url-pattern>
</servlet-mapping>
こう書くことで、URL欄にHelloと記入した際、HelloServletという名前のサーブレットが実行されます。
これで終われば早かったのですがそうはいきません。
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>HelloWorld</servlet-class>
</servlet>
こう書くことで、HelloServletという名前のサーブレットに、HelloWorldというクラスを結び付けます。
面倒ですね。
まあこうなっているのにも理由があるのですがまたいずれ。
ちなみにweb.xmlには、直感とは逆に<servlet><servlet-mapping>の順番で書き込まないと動作しません。
他にも面妖極まりない仕様満載で非常に大変なのですがまあまた今度。
HelloWorldと表示するだけで日が暮れそうですが、次回は日が明けそうなリクエストパラメータで。
innerHTMLとXMLHttpRequestを利用してKWICを作ってみましょう。
http://ja.wikipedia.org/wiki/KWIC
簡単に言うとテキスト検索ですが、その前後の文字ごと表示することによって実際に探している文脈をみつけやすくするものです。
Google検索結果でいうところの検出サイトの下に出てくる文字列、まさにあれです。
実のところJavaScriptで行う部分はXMLHttpRequestの送受信とinnerHTMLの変更だけなので、JavaScript2-1でやってることとほぼ同じです。
具体的な文書解析やらHTML構文作成やらはどうしてもPHPのほうが楽なので、そちらに任せてしまいます。
本来ならばXMLやDOMツリー等を使ってJavaScript側で行うべきなのですが、個人的にXML嫌いなので。
XMLといいJavaといいcssといいどうして面倒なほうに進んでいくのでしょうかね。
HTML構文とかは相当適当なので気にしない方針で。
あとJavaScriptももっと構造化すべきなのですがそこらへんもスルーの方向で。
上から順につらつら書いているとどうしてもこんな再利用が困難な書き方になってしまいます。
大事なHTML生成部分はすべてPHPに投げ渡してしまっているので、次回はPHP部分を作りましょう。
index.html
| <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0.1//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="ja"> <head> <meta http-equiv="Content-Type" Content="text/html;charset=Shift_JIS"> <link rel=stylesheet type="text/css" href="./index.css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <script type="text/javascript" src="./index.js"></script> <title>いんくりめんと</title> <script type="text/javascript"> <!-- function openFile(){ //検索して表示 peekQuery('gingatetsudono_yoru.txt'); //1秒毎に実行 window.setTimeout("openFile();",1000); } --> </script> </head> <body onload="openFile()"> <div id="inclementWatch2"></div> <h2>いんくりめんと</h2><br /> <form> <div id="ooooo" style="display:none;"></div> <input type="text" name="sampleWatch" size="60" style="display:none;"> 検索ワードを入力 <input type="text" size="30" maxlength="30" onClick="true" id="searchText"> <br /> <div id="searchField" style="display:none;"></div> <br /> <input type="button" value="全文を読む" onClick="loadText('gingatetsudono_yoru.txt')" id="dispButton"> </form> <div id="dispField" style="display:none;"></div> </body> </html> |
index.js
|
/*==========================================================*/ |
index.css
| body{ background:#fafafa; margin:10px 3% 0px; } div{ margin:2px; padding:3px; border-width:1px 1px 1px 1px; border-style:solid; } div a{ text-decoration:none; } div:hover{ background:#eff; } |