DelegateでSSLリバースプロキシ
Delegateは、RELAY=delegateというモードにおいて、
http://(Delegateサーバ)/-_-(目的のURL)
のような形でリバースプロキシを構築できますが、Delegateサーバ自体にSSLを設定し、
https://(Delegateサーバ)/-_-(目的のURL)
とし、目的のURLにhttp、httpsの両方を許容するように設定したい場合、多少工夫が必要でしたので、そのメモです。
設定
STLS="fcl,fsv:https" -P443 SERVER=https RELAY=delegate
STLSのfsv:httpsの部分が肝で、目的のURLがhttpsのときのみ、SSLを使用するようにします。
:httpsを書かないと、目的のURLがhttpのときに空のページが帰ってきますし、fsv自体はずすと、逆にhttpsのときにうまくいきません。
書いてみると何の変哲もないような感じなのですが、マニュアルには、
SSL が使用できなくてもセッションを継続したい場合、 "-" を "fsv" または、"fcl" の前につけます。
とあるので、-fsvでOKじゃんというところで思考停止し、大分ハマりました。
Click Onceアプリケーションにコマンドラインから引数を渡す
Visual StuidioのExpress版の発行機能を使って作成したClick Onceアプリケーションには、コマンドラインから直接引数を渡すことができません。
(通常、HTTPサーバに配備し、.applicationのURLに、URLパラメータで渡す必要があると思います。)
HTTPサーバを用意できる場合であれば、それで全く問題ありません。
ただし、種々の事情でそれが難しいケースもあると思います。
その場合に、コマンドラインでなんとか引数を渡す方法を考えてみました。
アプリ側でexeのパスを記憶する
Click Onceからアプリケーションをセットアップすると、インストール後にexeが起動されます。
ただし、exeは
C:\Users\[ユーザー名]\AppData\Local\Apps\2.0\L4AX7N83.7TE\B80T7A4X.MEW
(Windows7調べ。最後の2階層は環境によって違うかも?)
の下にバージョン毎にコピーされ、静的に決定することができません。
そこで、exeが起動された際に、テキストファイル等に自分自身のexeのパスを記録しておくようにしておきます。
static class Program ... /// <summary> /// アプリケーションのメイン エントリ ポイントです。 /// </summary> [STAThread] static void Main() { ... WriteExePathToStartupConfig(); ... } /// <summary> /// EXEパスを設定ファイルに書き込みます。 /// </summary> private static void WriteExePathToStartupConfig() { using (StreamWriter writer = new StreamWriter(@"C:\MyApp\startup.txt", false, Encoding.GetEncoding(932))) { writer.Write(Assembly.GetEntryAssembly().Location); } } ... }
すると、C:\MyApp\startup.txtには次のような文字列が残ります。
C:\Users\jfuruya\AppData\Local\Apps\2.0\L4AX7N83.7TE\B80T7A4X.MEW\myap..tion_bb87480e67debe12_0001.0000_9a89019a5cc6f2c5\MyApp.exe
こういった要件では、アプリは引数なしでは動作できないと思われますので、引数が渡されなかった場合は、exeのパスの記録だけ行って終了するなどの動作にすれば、ユーザには、アプリのインストールがされただけのように見えます。
上記の情報を利用した起動プログラムを用意する
上記のexeのパスの情報を利用し、アプリの起動を行うプログラムを用意します。
呼び出し側は、このプログラムに引数を渡して起動します。
下記は、vbsを利用した例です。
Set arg = WScript.Arguments If arg.Count < 2 Then MsgBox("プログラムの呼び出し方法が正しくありません。") WScript.Quit End If company_code = arg(0) emp_id = arg(1) Set fso = CreateObject("Scripting.FileSystemObject") Set file = fso.OpenTextFile("C:\MyApp\startup.txt") exe_path = file.ReadAll file.Close Set file = Nothing Set fso = Nothing command_line = """" & exe_path & """ " & company_code & " " & emp_id Set shell = CreateObject("WScript.Shell") shell.Run command_line Set shell = Nothing
こうすれば、HTTPサーバに配備されていないClick Onceアプリケーションでも、アップグレード時などにexeのパスが最新になりますので、常に最新のexeパスでアプリを起動することができます。
トリッキーな方法にはなるので、呼び出しそのものだけを考えると、クラスライブラリとして作成し、外部から呼び出す方がすっきりしているかもしれません。
それと、[コントロールパネル] -> [プログラムと機能]からアプリをロールバックされた場合、exeのパスは戻らないので、スタートメニューから手動で1回アプリを起動する必要があると思います。
SeasarのpublicフィールドでFormTableを使う
FormTableにコントロールカラムを配置するためには、通常のTableで使用するColumnの替わりにFieldColumnを使用する必要がありますが、これはSeasarで推奨されるpublicフィールドに対応していません。
publicフィールドに対応させるには、FieldColumnを継承し、getPropertyメソッドをオーバーライドして、publicフィールドに対応させなければいけません。
public class PublicFieldFormTableColumn extends FieldColumn { ... @Override public Object getProperty(String name, Object row) { try { String[] names = new String[] { name }; if (name.indexOf('.') >= 0) { names = name.split("\\."); } Class<?> target = row.getClass(); Object value = row; for (String propertyName : names) { Field field = target.getField(propertyName); value = field.get(value); target = field.getType(); } return value; } catch (Exception ex) { throw new RuntimeException(ex); } } }
なおこれは、S2Clickに含まれるPublicFieldColumnの実装方法をそのまま用いています。
FormTableをAjax化する際の注意点
click-extrasには、表形式のフォームを実現するための、FormTableというコンポーネントがあります。
http://click.avoka.com/click-examples/table/form-table.htm
これはTableを拡張したコンポーネントなので、前回の記事にあるようにページリンクのAjax化が可能です。
また、サブミットボタンをAjax化したいというニーズもあるでしょう。
(これは、通常のSubmitをS2Clickに含まれるAjaxSubmitに置き換えれば実現できます。)
ただ、やってみるといくつか注意しなければいけないポイントがありましたので、順に説明します。
フォーム要素にDateFieldが含まれる場合の注意
DateFieldを使用すると、カレンダーをセットアップするためのscriptタグがDateFieldの数だけHTMLに追加され、ページがロードされたときに1回ずつ実行されます。
<head> … <script id="table_form_date_0-js-setup" type="text/javascript"> Click.addLoadEvent(function(){ Event.observe('table_form_date_0-button', 'click', function(){ calendar = new CalendarDateSelect($('table_form_date_0'), { minute_interval: 1, popup_by: 'table_form_date_0-button', embedded: false, footer: false, buttons: false, time: false, formatValue: 'yyyy/MM/dd', year_range: [1930,2050] });});}); </script> <script id="table_form_date_1-js-setup" type="text/javascript"> Click.addLoadEvent(function(){ Event.observe('table_form_date_1-button', 'click', function(){ calendar = new CalendarDateSelect($('table_form_date_1'), { minute_interval: 1, popup_by: 'table_form_date_1-button', embedded: false, footer: false, buttons: false, time: false, formatValue: 'yyyy/MM/dd', year_range: [1930,2050] });});}); </script>
ところが、AjaxでFormTableのHTMLの置き換えを行うと、これらのscriptタグが実行されないので、カレンダーのアイコンをクリックしても、カレンダーが開かなくなります。
そこで、FormTableのHTMLをAjaxのレスポンスとして戻す際、その中に含まれるDateFieldセットアップ用のJavaScriptもレスポンスの一部として戻し、Ajaxのレスポンスを処理する際に実行してあげます。
まず、Java側でレスポンスを戻す際に、FormTableのHTMLとともに、そのheaderElementに含まれるDateFieldセットアップ用のJavaScriptを収集し、配列として戻します。
public boolean onPageMove() { String tableHTML = table.toString(); Set<String> setupJsScripts = getSetupJsScripts(); Map<String, Object> result = new HashMap<String, Object>(); result.put("tableHTML", tableHTML); result.put("setupJsScripts", setupJsScripts); renderJSON(result); return false; } private Set<String> getSetupJsScripts() { Set<String> scripts = new HashSet<String>(); for (Element e : table.getHeadElements()) { if (e instanceof JsScript) { JsScript js = (JsScript) e; if (js.getId() != null && js.getId().endsWith("-js-setup")) { scripts.add(js.getContent()); } } } return scripts; }
JavaScript側でレスポンスを処理する際に、HTMLの置き換えを行った後、受け取ったJavaScriptの配列の中身を全て実行します。
var movePageComplete = function (res) { var json = eval('(' + res.responseText + ')'); $('tab').innerHTML = json['tableHTML']; executeSetupScripts(json['setupJsScripts']); ... } var executeSetupScripts = function (scripts) { for (var i = 0; i < scripts.length; i++) { eval(scripts[i]); } }
SubmitをAjaxSubmitに置き換える場合の注意
AjaxSubmitを使用すると、それが含まれるフォームのonsubmitにAjax呼び出しを行って本来のサブミット動作をキャンセルするイベントハンドラを登録するscriptタグが追加され、ページがロードされたときに1回だけ実行されます。
<tr class="buttons"><td class="buttons"><input type="submit" name="ajaxSubmit" id="table_form_ajaxSubmit" value="Ajax Submit"/><script type="text/javascript"> $('table_form').onsubmit = function(){ this.request({ method: 'post', onComplete: ajaxSubmitComplete}); return false;} </script> </td></tr>
しかし、AjaxでFormTableのHTMLの置き換えを行った場合、そのscriptタグが実行されないので、ボタンを押すと普通にフォームがサブミットされてしまいます。
これを防ぐため、HTMLの置き換えを行った後、FormTable内のフォームのonsubmitに上記イベントハンドラを追加します。
"table-form"は、FormTableのformタグに自動的に割り振られるidです。
var movePageComplete = function (res) { var json = eval('(' + res.responseText + ')'); $('tab').innerHTML = json['tableHTML']; ... $('table_form').onsubmit = function () { this.request({ method: 'post', onComplete: ajaxSubmitComplete}); return false; }; }
※このコードだけを実行時にAjaxSubmitから取得することが難しかったので、出力されたHTMLから抽出しました。ただし、フォームのID等は変わる可能性がありますので、もう少し良いやり方があるかもしれません。
以下、今回の全てのサンプルコードです。
TableのページリンクをAjax化する
Click ExampleにあるTable Ajax Page
http://click.avoka.com/click-examples/ajax/table/table-ajax.htm
には、TableコンポーネントのページリンクをAjax化する方法が示されていますが、S2ClickのAjaxLinkを使用すると、もっと簡単に同じことを実現することができます。
S2ClickPageを継承したPageクラスを作成し、その中でTableを継承したインナークラスを宣言します。
ここで、Table#getControlLinkメソッドをオーバーライドして、ActionLinkの替わりにAjaxLinkのインスタンスを返します。
public class AjaxLinkInTablePage extends S2ClickPage { ... public Table table = new Table() { @Override public ActionLink getControlLink() { if (controlLink == null) { AjaxLink al = new AjaxLink(AjaxLinkInTablePage.this, "onPageMove"); al.addAjaxHandler(AjaxUtils.ON_COMPLETE, "movePageComplete"); controlLink = al; } return controlLink; } }; ...
onPageMoveはリンクがクリックされた際にJava側で呼び出されるメソッド名です。
movePageCompleteは、レスポンスが正常に返った後に呼び出されるJavaScript側の関数名です。
onPageMoveでは、S2ClickPage#renderJSONを使用し、テーブルのHTMLをJSONのレスポンスとして戻します。
public class AjaxLinkInTablePage extends S2ClickPage { ... public boolean onPageMove() { Map<String, Object> jsonObj = new HashMap<String, Object>(); jsonObj.put("tab", table.toString()); renderJSON(jsonObj); return false; } ...
movePageCompleteでは、サーバから受け取ったJSONをパースし、その内容でHTMLを書き換えます。
... <head> $imports <script> var movePageComplete = function (res) { var json = eval('(' + res.responseText + ')'); $('tab').innerHTML = json['tab']; }; </script> </head> <body> <div id="tab"> $table </div> ...
以下、今回書いた全サンプルコードです。
Androidのライフサイクルを実機で検証してみた
Androidのライフサイクルについては色々なところで解説されているが、実際に端末で操作を行った際に、OnCreate等のメソッドがどう呼ばれるのかがいまいち分かりづらかったので、軽くまとめてみた。
検証に使用した端末は、Nexus S(2.3.6)。
検証用Activity
次のような実装に。
- ライフサイクルに関連する7つのメソッド(よく解説の図とかに載ってるやつ)を全てオーバーライドし、呼び出されたメソッドに対応する文字列をログに出力する。
- 上記に加え、onRestoreInstanceState/onSaveInstanceStateも同じように見る。
- finish()を呼んだときの挙動も見るため、Viewにボタンを1個配置し、クリックされたときにfinish()を呼ぶ。
- onRestore()でActivityのインスタンスとインスタンスフィールド、クラスとstaticフィールドのhashCode()を出力する。
HelloAndroidActivity.java
public class HelloAndroidActivity extends Activity implements OnClickListener { private static final String TAG = "LifeCycleTest"; private Object instanceField = new Object(); private static Object staticField = new Object(); /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "----------OnCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.main); ((Button) findViewById(R.id.finish_button)).setOnClickListener(this); } @Override protected void onStart() { Log.d(TAG, "----------OnStart"); super.onPause(); } @Override protected void onResume() { Log.d(TAG, "----------OnResume"); Log.d(TAG, String.format( "Activity-Class:hashCode=%1$x,static field hashCode:%1$x", this .getClass().hashCode(), HelloAndroidActivity.staticField.hashCode())); Log.d(TAG, String.format( "Activity-Instance:hashCode=%1$x,instance field hashCode:%1$x", this.hashCode(), this.instanceField.hashCode())); super.onResume(); } @Override protected void onRestart() { Log.d(TAG, "----------OnRestart"); super.onPause(); } @Override protected void onPause() { Log.d(TAG, "----------OnPause"); super.onPause(); } @Override protected void onStop() { Log.d(TAG, "----------OnStop"); super.onPause(); } @Override protected void onDestroy() { Log.d(TAG, "----------OnDestroy"); super.onPause(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { Log.d(TAG, "----------onRestoreInstanceState"); super.onRestoreInstanceState(savedInstanceState); } @Override protected void onSaveInstanceState(Bundle outState) { Log.d(TAG, "----------onSaveInstanceState"); super.onSaveInstanceState(outState); } @Override public void onClick(View v) { finish(); } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/finish_button" android:text="終了"> </Button>
色々動かしてみる。
アプリを起動
10-28 15:05:12.542: DEBUG/LifeCycleTest(25813): ----------OnCreate 10-28 15:05:12.562: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:05:12.562: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:05:12.566: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:05:12.566: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40516160,instance field hashCode:40516160
バックボタンでアプリを閉じる
10-28 15:05:43.085: DEBUG/LifeCycleTest(25813): ----------OnPause (中略) 10-28 15:05:43.605: DEBUG/LifeCycleTest(25813): ----------OnStop 10-28 15:05:43.605: DEBUG/LifeCycleTest(25813): ----------OnDestroy
もう一回起動
10-28 15:06:05.660: DEBUG/LifeCycleTest(25813): ----------OnCreate 10-28 15:06:05.664: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:06:05.664: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:06:05.664: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:06:05.664: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=4051f0a8,instance field hashCode:4051f0a8
ホームボタンでアプリを閉じる
10-28 15:06:55.167: DEBUG/LifeCycleTest(25813): ----------onSaveInstanceState 10-28 15:06:55.167: DEBUG/LifeCycleTest(25813): ----------OnPause
再び起動
10-28 15:07:14.578: DEBUG/LifeCycleTest(25813): ----------OnRestart 10-28 15:07:14.578: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:07:14.578: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:07:14.578: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:07:14.578: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=4051f0a8,instance field hashCode:4051f0a8
finish()
10-28 15:07:40.613: DEBUG/LifeCycleTest(25813): ----------OnPause (中略) 10-28 15:07:41.058: DEBUG/LifeCycleTest(25813): ----------OnStop 10-28 15:07:41.058: DEBUG/LifeCycleTest(25813): ----------OnDestroy
もう一回起動
10-28 15:08:05.117: DEBUG/LifeCycleTest(25813): ----------OnCreate 10-28 15:08:05.121: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:08:05.121: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:08:05.121: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:08:05.121: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40517968,instance field hashCode:40517968
スクリーンオフ
10-28 15:08:20.882: DEBUG/LifeCycleTest(25813): ----------onSaveInstanceState 10-28 15:08:20.882: DEBUG/LifeCycleTest(25813): ----------OnPause
スクリーンオン
10-28 15:08:39.031: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:08:39.031: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:08:39.031: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40517968,instance field hashCode:40517968
パスワードロックをかけているが、ロックを外す前にログが出た。
端末の向きを変える→戻す
10-28 15:16:04.257: DEBUG/LifeCycleTest(25813): ----------onSaveInstanceState 10-28 15:16:04.257: DEBUG/LifeCycleTest(25813): ----------OnPause 10-28 15:16:04.257: DEBUG/LifeCycleTest(25813): ----------OnStop 10-28 15:16:04.257: DEBUG/LifeCycleTest(25813): ----------OnDestroy 10-28 15:16:04.261: DEBUG/LifeCycleTest(25813): ----------OnCreate 10-28 15:16:04.265: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:16:04.265: DEBUG/LifeCycleTest(25813): ----------onRestoreInstanceState 10-28 15:16:04.265: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:16:04.265: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:16:04.265: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40538598,instance field hashCode:40538598 (中略) 10-28 15:16:07.468: DEBUG/LifeCycleTest(25813): ----------onSaveInstanceState 10-28 15:16:07.468: DEBUG/LifeCycleTest(25813): ----------OnPause 10-28 15:16:07.468: DEBUG/LifeCycleTest(25813): ----------OnStop 10-28 15:16:07.468: DEBUG/LifeCycleTest(25813): ----------OnDestroy 10-28 15:16:07.468: DEBUG/LifeCycleTest(25813): ----------OnCreate 10-28 15:16:07.472: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:16:07.472: DEBUG/LifeCycleTest(25813): ----------onRestoreInstanceState 10-28 15:16:07.476: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:16:07.476: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:16:07.476: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40539f20,instance field hashCode:40539f20
電話がかかってきた→切る
10-28 15:17:06.281: DEBUG/LifeCycleTest(25813): ----------onSaveInstanceState 10-28 15:17:06.281: DEBUG/LifeCycleTest(25813): ----------OnPause (中略) 10-28 15:17:06.406: INFO/power(109): *** set_screen_state 1 10-28 15:17:06.609: DEBUG/LifeCycleTest(25813): ----------OnStop (中略) 10-28 15:17:09.699: DEBUG/LifeCycleTest(25813): ----------OnRestart 10-28 15:17:09.699: DEBUG/LifeCycleTest(25813): ----------OnStart 10-28 15:17:09.699: DEBUG/LifeCycleTest(25813): ----------OnResume 10-28 15:17:09.699: DEBUG/LifeCycleTest(25813): Activity-Class:hashCode=40515d80,static field hashCode:40515d80 10-28 15:17:09.699: DEBUG/LifeCycleTest(25813): Activity-Instance:hashCode=40539f20,instance field hashCode:40539f20
まれにOnDestroy()まで呼ばれ、OnCreate()から始まるときもある。
分かったこと
大体書籍やWebに書いてあるのと同じ動作であったが、下記は個人的に今回分かったこと。
- onRestoreInstanceState()は画面の回転以外では呼ばれない。
- ホームボタンを押した際の動作は、アプリの終了ではなく一時停止。(ホームアプリが起動してアプリが一時停止した状態なのだろう。)
- バックボタンとfinish()時の動作は同じ。(これらは終了扱いか。)
- OnDestroy()が呼ばれるとインスタンスは破棄されるが、クラスはアンロードされない。
qemu-kvmにACPIのshutdownシグナルを送る方法
libvirt使いなさいよって話なんだと思うけど…。
https://wiki.archlinux.org/index.php/QEMU#Starting_qemu_virtual_machines_on_boot
を読んでいたら、
qemu_vm1="-enable-kvm -m 512 -hda /dev/mapper/vg0-vm1 -net nic,macaddr=DE:AD:BE:EF:E0:00 \ -net tap,ifname=tap0 -serial telnet:localhost:7000,server,nowait,nodelay \ -monitor telnet:localhost:7100,server,nowait,nodelay -vnc :0" qemu_vm1_haltcmd="echo 'system_powerdown' | nc.openbsd localhost 7100" # or netcat/ncat
て書いてあって。
-monitorオプション付きでVMの外からQEMUのモニターに接続できるようにしておいて、そこにnetcatでsystem_powerdownコマンドを発行すれば良いと。
そもそも-monitorオプションを知らなかったとかだった。orz
さくらさんのブログ
http://research.sakura.ad.jp/2010/03/08/kvm-install/
にも書いてあったりして、完全に調査不足。
ゲスト側の準備(Scientific Linux 6.1の場合)
ゲスト側でACPIのイベントを監視する必要があるので、
# yum -y install acpid # chkconfig acpid on # /etc/init.d/acpid start
としてacpidを動かしてお…こうとするのだが、なぜかデーモンの起動に失敗するので、一度再起動する。