apache module

なるほど。
mod_jkとか、mod_rewriteって、C言語で書かれたApacheの拡張モジュールのことなんだなぁ。

Apacheの基本動作:
・クライアントからのリクエストURLを受付
・URLを解釈して該当するリソースを返却
・各種ログ出力

これしか(あってるかわからないけど)機能がないから、
ServletJSPが呼び出されたらTomcatに処理をさせたいけど、
モジュールが設定されていない状態だと処理ができないわけなのね。

だから、mod_jkを使ってTomcatと連携するぞ!とか、
タイムアウトの設定ってどこでやってるの?mod_jk?とか、
そういう会話になるわけですか…。

インストール→モジュールの読み込み設定→httpd.confに設定を書く

 モジュールの設定

httpd.confとは別に設定ファイルを作って、httpd.confに読み込ませるのもアリ

Include 設定ファイルのパス

Strutsの動き

フレームワーク覚えたてのときって、中の動きまで覚えませんでした。

おおざっぱにはこんな仕組みで動いてるけど細かい中身は知らない。
偉い人が上手いことやってくれるから知らない。
とりあえず、業務ロジックを動かすためには
この設定ファイルにあんなことやこんなことをかいて、
こういうクラスを作ればおk!

みたいな軽い気持ちでいじっていました。
(実際にそれで十分使えましたし、Webアプリケーションを作れました)

ですが、フレームワークを独自拡張するなど、
実際深く開発に携わる立場になってくると、
そういう簡単な理解だけではダメな部分が出てきました。

なので、まだまだ軽い段階ですが、おさらいしました。

基本は、WebアプリケーションなのでServletがいる

前々回の日記で書いた通りStrutsを使ってるといえども
れっきとWebアプリケーションなので、サーブレットがいます。
Strutsが用意している共通のサーブレットをActionServletという。
このServletが全ての入り口と出口を司る。
このクラスが呼ばれると、同じくStrutsが用意しているリクエスト処理用のクラスRequestProcessorが呼ばれる。

次はRequestProcessor

RequestProcessorは、ActionMappingやActionFormを設定し(必要であれば)バリデーションチェック等を行い、Actionクラス(業務ロジック)を呼び出す。
process()というメソッドから、localeを決めるprocessLocale()や、
ActionMappingのインスタンスを生成するprocessMapping()や、
バリデーションチェックを行うprocessValidate()などが呼ばれる。

業務ロジック(Action)

Actionクラスのexecuteメソッドが、実行される。
ここに業務ロジックを記述し、最終的にActionForwardクラスのオブジェクト返却する。
すなわち、この時return文に書いたものが、RequestProcessorへ返却れる。

RequestProcessorに戻って遷移

RequestProcessorは、返却されたオブジェクトの情報を元に
クライアントに情報を返却する。

ということで、二重遷移防止にトークンを置いてチェックするための処理や、Actionクラスを呼ぶ前にしておきたいこと、追加でログを週力したい…等があれば、このRequestProcessorを拡張すると良い。

ライブラリ管理のお話(未完)

開発作業と切っても切り離せないライブラリ管理の話。
今回は実際に開発で使っているSubversionTortoiseSVN/Subclipse)で勉強会します。

一応軽く、SVNクライアントの紹介。

TortoiseSVN

何て読むのか実は知らなかったのですが、「トータスエスブイエヌ」と読むそうです。
Windowsで使えるSVNクライアント。
個人的には設計書をSVNで管理する時に使っています。
閲覧のみの場合はリポジトリブラウザから見て、
更新する場合だけ、ローカルにチェックアウトします。

Subclipse

Ecllipseで使えるSVNクライアント。
プログラムソースなどの各種資源はこちらで管理します。

チェックアウト/エクスポート/コミット/ブランチ作成/タグ打ち…
は結構スムーズに行くのですが、未だにわかっていないのが、

マージ

開発作業をやっていくと、ライブラリ管理に任命されたメンバーには色々な作業が発生します。

  • ブランチAのリビジョンxの資源をブランチBにコミットしといてね(はぁと
  • ブランチのリビジョンxからリビジョンyまでの変更内容をトランクにマージしてね(はぁと
  • ブランチAのリビジョンxの資源をブランチBにコミットしといてね(はぁと

これは、結構簡単でしょうか。
指定されたリビジョンの資源がブランチBになければ、コピーしてコミット(add)すれば良いだけですし、
ブランチBに既にある資源であれば、右クリックして比較、マージすれば良いわけです。

  • ブランチの7/1から7/31までの変更内容をトランクにマージしてね(はぁと

これは、なかなか大変です。マージ対象がどれだけあるかわからないので…。
また、トランクも更新されていたとしたら大問題です。
どちらが正しくてどちらが正しくないのか、ライブラリ管理だけをやっている人にはわからないからです。

  • 7/1から7/31までに変更された資源の一覧を出してね(はぁと

こんな要求もあるかもしれない。

って

ここまで書いた後で思いましたが、もしかして最初にライブラリ管理方法を
きっちり決めておけば、後になってごちゃごちゃにならなくてすむんでしょうね…。

eclipseを使ったデバッグ方法まとめ(その2)

監視式ビュー

変数や式の値を監視することができる。

変数ビューを右クリック>監視式の作成
ここに式や変数名を書く。

例)

request.getContentLength()
hogehoge

すると、式ビューに先ほどの監視式が追加され、値を見ることができる。

request.getContentLength() = "123"
hogehoge = "0"

または、変数を右クリックして「監視」でもOK。

表示ビュー

変数の値を変更したり、強制的に例外をスローさせたりできる。

ウインドウ>ビュー>表示

ここには、Javaソースコードを書くことができる。
Javaソースコードを書いたら、実行させたいソースコードを選択して「実行」(Ctrl+U)で
実行中のプログラムに変化を与えることができる。

例)

hogehoge = 3;
throw new NullPointerException();

リモートデバッグ

Eclipseで起動していないJavaプログラムをデバッグすることもできるらしい。
結合テスト環境でトラブルがあった際等に役立つかもしれない。要勉強。

次は

SVN周りのことも書きたい。

eclipseを使ったデバッグ方法まとめ

ちょっと名古屋に行ってたので勉強してませんでした

観光ついでにオクトーバーフェストで呑んだくれてました。
こういう無茶ができるのも今ぐらいかなぁ、とか。へろへろです。

今日からセルフ勉強会再開…です。

eclipseを使ったデバッグ方法

単体試験は主にデバッガを使って値を変更しながら実施します。
意外と面倒。

とりあえず、ブレークポイント置いて、デバッガモードで起動して…
ブレークポイントで処理を止めることができたらファンクションキーで操作をする。
F5 -- 次の処理へ
F6 -- 1行下へ
F7 -- メソッドの呼び出し元へ
は基本として。

F8 -- 次のブレークポイント

フレームにドロップボタン -- 現在のスタックフレームに再突入する。
平たくいえば現在実行されているメソッドの先頭に戻る。
ローカル変数はクリアされるけれども、グローバル変数はクリアされない。
つまり、以下のような場合、エラーになる。

BufferedReader br;
try{
 br = new BufferedReader(new InputStreamReader(req.getInputStream(), "UTF-8"));
 String str;
 StringBuilder sb = new StringBuilder();
 while((str = br.readLine()) != null){
    sb.append(str);
  } 
}catch(IOException e){
 e.printStackTrace();
}finally{
 if(br != null){
   br.close(); //この処理を終えた上で、フレームにドロップで再突入する
 }
}

req.getInputStream() … ローカル変数ではない …がもう読めない状態になっているので、
IOExceptionが発生する。

逆に言うと、これを使えば単体試験で例外を簡単に発生させられる。

監視式と表示ビュー

次回の話のタネに。実はよくわかっていない部分なので勉強します。

タグに指定するアクションパスと拡張子マッピング

Struts1で私が勘違いしていたこと。

 URLパターンはすべて〜.doとなる。

ある意味正解、ある意味不正解。

Strutsといえども、基本はWebアプリケーション

web.xmlに様々な設定がしてあるのです。

Strutsフレームワークの基盤部分の設定がきちんとあります。
普段、開発者(プログラマ)はあまり意識しなくてすむ部分。

<servlet>
  <servlet-name>action</servlet-name>
  <servlet-class>org.apache.struts.action.servlet.ActionServlet</servlet-class>
   <init-param>
      <param-name>config</param-name>
      <param-value>struts-config.xml</param-value>
   <init-param>
</servlet>
<servlet-mapping>
  <servlet-name>action</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

ざっくり読むと、以下の3つが書いてある。
①末尾.doと付いているURLに対して、ActionServletが起動する。
②ActionServletが生成された時に、ServletContext内にconfigという名前でパラメータをもつ。
(余談:Servletは一番最初に呼び出されたときに生成され、以後消滅するまで同じインスタンスを使いまわす)
③configパラメータに、strutsの設定ファイルへのパスを定義しておき、ActionServletから利用できるようにしておく。

以下は想像

ActionServletは呼ばれたらstrutsの設定ファイルを取得する。(ServletContext#getInitParameter)
ActionFormや処理するActionクラスを取得する。
よしなにする。
ActionクラスにActionFormやHttpRequest等を渡して起動させる。

さて、ここで、url-patternタグを変更してみる。

もちろん可能。
例えば/do/*とかにしてみる。
http://〜/do/test というURLでアクセスするとActionServletが呼ばれる。
http://〜/do/test.doでも同様。

.do指定は拡張子マッピングといい、
/do/*指定はパスマッピングという。
ついでに/sample指定は特定のファイルへのマッピング
/指定はデフォルトマッピング(何も指定しなくてもURL全パターンに引っかかる)という。

つまり、拡張子はあってもなくても本当は良かったわけです。
デフォルトがそうなっているというだけの理由です。

本題

<html:form action="/sample/actionPath">
  <%-- 省略 --%>
</html:form>

ここに注目。

 action="/sample/actionPath"

ここは、struts-configで指定したアクションパスを指定する必要がある。

ちなみに、上記のタグはJSPに出力されるときは

<form action="http://〜/sample/actionPath.do">
</form>

というような感じで出力される。

どうやら.doという拡張子を勝手に付けてくれているらしい。

ちなみに

action="/sample/actionPath.do"

と指定しても、

<form action="http://〜/sample/actionPath.do">
</form>

というような感じで出力される。

絶対URLになるだけじゃなく、
どうも、最後の拡張子は変換時に読み捨てられるらしいですよ。
つまり内部で、ActionServletが呼ばれるように拡張子がある場合は読み捨てて拡張子を新たに付与、
拡張子がない場合は何もせず拡張子を付与するということをしているように見える。

かしこい。

action="/sample/actionPath.do.abc"

と指定すると、

<form action="http://〜/sample/actionPath.do.do">
</form>

というような感じで出力されるので、推測はただしそう。

ということでソースを見てみた

String actionMapping = getActionMappingName(action);
if (servletMapping.startsWith("*.")) {
     value.append(actionMapping);
     value.append(servletMapping.substring(1)); //.doを付与(url-patternが.doの場合)
} else if (servletMapping.endsWith("/*")) {
     value.append(servletMapping.substring(0,
          servletMapping.length() - 2));
     value.append(actionMapping);
} else if (servletMapping.equals("/")) {
     value.append(actionMapping);
}

ビンゴでしたね。

HTTP Cookie

クライアントに少量のデータを保存できる仕組み。

例えば、Webサイトの掲示板への書き込みにおける名前、メールアドレス等を
クッキーに保存しておき、次回以降のアクセスで予め入力してある状態にさせる等の用途で使われる。

クッキー発行方法

public void service(HttpServletRequest req, HttpServletResponse res) {

 // Cookieの作成
 Cookie cookie = new Cookie("cookieName", "cookieData");
 // 有効範囲ドメインの設定
 cookie.setDomain("domain");
 // 有効期限の設定
 cookie.setMaxAge("expire[/s]")l
 // レスポンスにクッキーを追加
 res.addCookie(cookie);

}

setDomainメソッドで、このクッキーの取得できるドメインの範囲を指定する。
指定がない場合、クッキーを発行した際のURLのみに許可。
例えば、hogehoge.dummy.co.jpというホストで発行したクッキーだが、
foobar.dummy.co.jpというホストでもそのクッキーを利用したい場合は、

cookie.setDomain("dummy.co.jp");

とする。
ドメイン名はトップレベルの一階層下までしか設定できないため、汎用的なクッキーは作成できない。
(comやnetのようなものであればdummy.comというように、.が1個以上、
co.jpのようなものであればdummy.co.jpのように、.が2個以上。)

ドメインは何を基準にするのか。

HTTPリクエストヘッダのhostヘッダ。

http://localhost:8080/sampleにアクセスすると、
Host: localhost:8080

http://127.0.0.1:8080/sampleにアクセスすると、
Host: 127.0.0.1:8080

setMaxAgeメソッドで、このクッキーの有効期限を秒単位で指定する。
負数の場合や、指定がない場合、ブラウザを閉じるまでが有効期限。
0秒をセットすると、クッキーが削除される。

最後に、レスポンスにaddCookieメソッドでクッキーを追加する。

クッキー受領/変更方法

public void service(HttpServletRequest req, HttpServletResponse res) {

 // Cookieの取得
 Cookie[] cookies = req.getCookies();
 Cookie targetCookie;
 if (cookies != null) {
  for (Cookie cookie : cookies) {
    if (cookie.getName().equals("targetCookieName") {
      Cookie targetCookie = cookie;
      break;
    }
  }

   // Cookieに保存したデータを取得
   String value = targetCookie.getValue();
   // 新しいCookieデータを保存
   String newValue = "newCookieValue";
   targetCookie.setValue(newValue);
   // レスポンスにクッキー追加(名前が同じであれば上書き)
   res.addCookie(targetCookie);
   
 }
}

大きくわけて3つある。

クッキーの取得

Javaではクッキーはリクエストから配列で一括取得する方法しか提供されていない。
配列で一括取得してきた中から、必要なクッキーか否かをクッキー名で判断し、取得している。
getCookies()メソッドの実行結果、一つもクッキーが保存されていなければnullが返るため、nullチェックが必要。

クッキーに保存したデータの取得

getValue()メソッドで文字列を取得する。

クッキーに保存する情報

文字化けを防ぐために、URLEncodeを行う必要がある。
java.url.URLEncoderクラスのencode()メソッドを使い、エンコードした値をセットする。
すなわち、クッキーから情報を取り出す際はURLDecodeを行う必要がある。
java.url.URLDecoderクラスのdecode()メソッドを使い、デコードしてから利用する。
*content-typeがapplication/x-www-form-urlencodedの場合、半角スペースを+として扱うため、注意が必要。

クッキーに保存させる情報の形式

なんでも良い。
複数の値を保存する際、一般的には&を使う。
また、1個1個の値にキー値を用意し、キー値と値を=で繋ぐ。
(URLEncodeを行うと、&や=の文字がエスケープされるため、使いやすい)

key=value&key=value...

固定長にすることでも対応できる。

loginId1%20%20password%20%20        

ログインIDが先頭10桁、パスワードがその次の10桁。
*10桁未満のデータの場合は半角スペース(URLエンコードすると%20)で詰めるなどの約束を決める。