herokuの無料SSL(https://?.herokuapp.com)を使ってHTTPS通信を強制する方法

まず、ローカルで起動した際にHTTPS通信を強制してみます。
コントローラとしてApplicationクラスのみを使っているとして、Applicationクラスに次のメソッドを追加します。

    @Before
    static void before() {
        if(!request.secure) {
            int port = Integer.parseInt(Play.configuration.getProperty("https.port", "443"));
            redirect("https://" + request.domain + (port == 443 ? "" : ":" + port) + request.url);
        }
    }

それと、application.conf にたとえば次のように port の設定をして、証明書を用意しておきます。
http.port=8880
https.port=4443
これで起動して http://localhost:8880 にアクセスすれば、強制的に https://localhost:4443 にリダイレクトされるようになります。

次に heroku で同じようにしたいところですが、このままではうまくいきません。
heroku では https.port の設定や証明書がなくても、普通に play アプリを push すれば https のURLが使えるようになります。
heroku 内部で複数のサーバが連携しているためです。
それに対応するために、上で追加した before メソッドを次のように修正します。

    @Before
    static void before() {
        Header proto = request.headers.get("x-forwarded-proto");
        if(proto != null) {
            if("http".equals(proto.value())) redirect("https://" + request.domain + request.url);
            request.secure = true;
        } else if(!request.secure) {
            int port = Integer.parseInt(Play.configuration.getProperty("https.port", "443"));
            redirect("https://" + request.domain + (port == 443 ? "" : ":" + port) + request.url);
        }
    }

heroku 上のアプリにアクセスした際、requestヘッダには x-forwarded-proto がセットされ、http通信の場合には http、https通信の場合にはhttps が値としてセットされています。
普通にローカルで動かしている場合、これらはセットされません。
なので、ヘッダがあるかどうかで場合分けをして、ヘッダがない場合は上で最初に書いた処理を動かします。
ヘッダがあり、かつ、値が http の場合は https のURLにリダイレクトします。
最後に、値が https でも request.secure が false のままになっているので、true に設定します。
これで、play の redirect メソッド等を使用した場合でも https を保ったままにすることができます。

Playframework1系でhtmlファイルをサーバを通さず開いた場合と、通して開いた場合で処理を切り替える方法

最近の私の開発の流れとしては、仕様を決めるために、htmlとJavascriptjQueryjsonでまずプロトタイプとして画面の動きを作り、仕様の確認が取れてから、DBのロジックを組み込むようにしていて、設計書ベースでの設計を最小限にしています。プロトタイプから本物のアプリケーションにする際、できるだけそのままプロトタイプを使えるように作っておくと、jsonの固定ファイルの部分を動的に生成する処理に置き換えて、更新処理を追加するだけで完成させることができます。
そのなかで、次のようなことを考えました。

サーバ側を起動しなくてもhtmlファイルをそのまま開くだけで見せられるプロトタイプをできるだけ維持したい
サーバを起動すれば自動的に本物の処理の方が動くようにしたい
これらを実現するには、
1.サーバサイドでのテンプレートを使用しない
2.htmlを直接開いた場合とサーバを投資た場合で実行するjavascriptを切り替える
があればできます。
1.はクライアント側でjsonからデータを組み立てる形にすればそんなに大変ではありません。
2.は以下のようにすると自動切り替えができます。

<script type="text/javascript">
/*{}*//*
(APサーバを通す場合の処理)
*{}*//*{*/
(APサーバを通さない場合の処理)
/*}*/
</script>

APサーバを通さない場合は上記をよく見ると、通す場合の処理部分がすべてコメントになるのがわかると思います。わかりやすくコメント部分を除去すると次のようになります。

<script type="text/javascript">
(APサーバを通さない場合の処理)
</script>

上記はAPサーバを通すと*{〜}*部分が除去されるので次のようになります。

<script type="text/javascript">
///*
(APサーバを通す場合の処理)
///
</script>

最終的には下の方になるので、7文字ほど余分な文字が残りますが、そのまま残しておいてもほとんど問題ないでしょう。
それより、いつでも、サーバなしで画面を見せることができるメリットが大きいと思います。

一覧の並び順の変更方法について

システム開発でよく、一覧表示の並び順を任意に変更したいといった要望が出たりします。
簡単な対応方法としては並び順の数値フィールドを設けて、数値の小さい順に表示するようにしておいて、
並び順の数値を設定してもらうなどがあると思います。
ただ、それだと一度連番を振った後に、間に新しい内容を持ってきたいときに、並び順の修正に手間がかかってしまうなど、
利用者側が並び順の数値を意識する必要があって大変です。
そこで、並びを上下に移動させられるようなボタンを一覧に表示させると便利なんですが、
どのように処理すればいいか悩むところです。
すべての行の並び順を見ながら数値を調整していくのがベタなやり方ですが、
全行数分UPDATE文を発行したりすることになって、いまいちスマートではありません。
そこで、もっといい方法はないかと考えてみて、うまくいきそうな方法があったので、忘れないうちに記録しておきます。
[考え方]
並び順の数値の初期値は0で昇順で並び、同じ値の場合は主キーの昇順で表示される一覧を想定します。
3行目を下げて4行目と入れ替えたい場合の処理は、

  1. 4行目の並び順の数値を取得しaとする。
  2. 3行目の並び順の数値-aをbとする。
  3. 5行目以降の並び順の数値をそれぞれからb+2を足した値に更新する。
  4. 3行目の並び順の数値をb+1を足した値に更新する。

とすると、行数にかかわらず1行を取得する2つのSELECT文と2つのUPDATE文だけで並び順を変更できます。
PHPぽく書くと、

    $table = [対象テーブル(id, sorter列を持つ)];
    $id = [下げたい行の主キー];
    if($sorter = get_one("SELECT sorter FROM $table WHERE id = $id")) {
        $where = "sorter > $sorter OR (sorter = $sorter AND id > $id)";
        if($near = get_row("SELECT id, sorter FROM $table WHERE $where ORDER BY sorter, id")) {
            $offset = $sorter - $near['sorter'];
            query("UPDATE $table SET sorter = sorter + ($offset + 2) WHERE $where AND id <> $near[id]");
            query("UPDATE $table SET sorter = sorter + ($offset + 1) WHERE id = $id");
        }
    }

※get_oneはSELECT結果の1行1列目の値を取得、get_rowは1行目の値を列名との連想配列で取得、queryはSQLを実行するような関数

逆に、4行目を上げて3行目と入れ替えたい場合は、

  1. 3行目の並び順の数値を取得しaとする。
  2. a-4行目の並び順の数値をbとする。
  3. 3行目より上の行の並び順の数値をそれぞれからb+2を引いた値に更新する。
  4. 3行目の並び順の数値をb+1を引いた値に更新する。
    $table = [対象テーブル(id, sorter列を持つ)];
    $id = [上げたい行の主キー];
    if($sorter = get_one("SELECT sorter FROM $table WHERE id = $id")) {
        $where = "sorter < $sorter OR (sorter = $sorter AND id < $id)";
        if($near = get_row("SELECT id, sorter FROM $table WHERE $where ORDER BY sorter DESC, id DESC")) {
            $offset = $near['sorter'] - $sorter;
            query("UPDATE $table SET sorter = sorter - ($offset + 2) WHERE $where AND id <> $near[id]");
            query("UPDATE $table SET sorter = sorter - ($offset + 1) WHERE id = $id");
        }
    }

WPFでQuickTimeやFlashのActiveXを使おうとして引っかかったこと

  • 64bit OSを使っている場合はプラットフォームターゲットをx86にしないとActiveXが動かない(REGDB_E_CLASSNOTREGエラーが発生)
  • WPFにはActiveXコントロールを貼り付けられないので、コードで生成することになるが、BeginInitなどを省略するとうまくいかないことがある(InvalidActiveXStateException例外が発生)

うまくいかない場合がある例

            form = new System.Windows.Forms.Form();
            var flash = new AxShockwaveFlashObjects.AxShockwaveFlash() { Dock = System.Windows.Forms.DockStyle.Fill };
            form.Controls.Add(flash);
            form.Show();

うまくいく例

            form = new System.Windows.Forms.Form();
            var flash = new AxShockwaveFlashObjects.AxShockwaveFlash() { Dock = System.Windows.Forms.DockStyle.Fill };
            ((System.ComponentModel.ISupportInitialize)(flash)).BeginInit();
            form.SuspendLayout();
            form.Controls.Add(flash);
            form.ResumeLayout();
            ((System.ComponentModel.ISupportInitialize)(flash)).EndInit();
            form.Show();
  • VisualStudio2010でウインドウフォームにActiveXコントロールを貼り付けてもうまくいかない(2010/9現在,AxInterop.QTOControlLib.dllやAxInterop.ShockwaveFlashObjects.dllの生成に失敗する)

→これは仕方ないので、VisualStudio2008等で作成したdllをコピーすることでなんとかした

Expression Encorder 4を使ってビデオキャプチャのプレビュー表示を行う

あらかじめ、VisualStudio2010とExpressionEncorder4をインストールしておいてください。

  1. 新規「C#ウインドウフォームアプリケーション」プロジェクト作成します。(プロジェクト名はVideoCaptureEE4としました。)
  2. 参照設定を右クリックし、「参照の追加」「.NET」から を追加します。
  3. フォームイベントプロパティから、Load, FormClosing, Resizeイベントを追加し、以下の内容を記述します。
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            Microsoft.Expression.Encoder.Live.LiveDeviceSource source;
            Microsoft.Expression.Encoder.Live.LiveJob job;
            System.Collections.ObjectModel.Collection<Microsoft.Expression.Encoder.Devices.EncoderDevice> videoDevices;
    
            //使用できるカメラのリストを取得
            void checkCamera()
            {
                videoDevices = Microsoft.Expression.Encoder.Devices.EncoderDevices.FindDevices(Microsoft.Expression.Encoder.Devices.EncoderDeviceType.Video);
            }
    
            //カメラプレビュー開始
            void startCapture()
            {
                if (videoDevices.Count > 0)
                {
                    job = new Microsoft.Expression.Encoder.Live.LiveJob();
                    source = job.AddDeviceSource(videoDevices[0], null); //見つかった一つめのカメラを使用
                    source.PreviewWindow = new Microsoft.Expression.Encoder.Live.PreviewWindow(new System.Runtime.InteropServices.HandleRef(this, Handle));
                    job.ActivateSource(source);
                }
            }
    
            //カメラプレビュー終了
            void stopCapture()
            {
                if (videoDevices.Count > 0)
                {
                    job.RemoveDeviceSource(source);
                    source = null;
                    job.Dispose();
                    job = null;
                }
            }
    
            //起動時の処理
            private void Form1_Load(object sender, EventArgs e)
            {
                checkCamera();
                if (videoDevices.Count <= 0)
                {
                    MessageBox.Show("使用できるカメラが見つかりませんでした。");
                    Application.Exit();
                }
                startCapture();
            }
    
            //終了時の処理
            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                stopCapture();
            }
    
            //ウインドウサイズ変更時の処理
            private void Form1_Resize(object sender, EventArgs e)
            {
                stopCapture();
                startCapture();
            }
        }
    

これで次のようなウインドウが表示されます。

電子書籍内の広告について考えた

 iPodTouchとiPadを使うようになって、その直感的な使いやすさにPCにない魅力を感じている。
 とくにキーボードが通常隠れているのが画期的で、PCだと触ろうともしなかった親が、iPadだと、勧めもしないのに自分から触っている姿には感動すら覚えた。PCはキーボードが隠れない限り永遠に一般化しないのではないかと最近は思っている。
 また、電子書籍が話題になってきていて、様々な議論が行われている。
 私は実際に使ってみて、iPodTouchやiPhoneのような手のひらサイズの端末で本を読むのが、文庫本で読むより数倍便利だと感じている。
 一番大きいのは片手だけで操作が完結するということ。荷物を持ったままでも、電話しながらでもストレスなく読める。文庫本だとページめくりは片手では難しい。またちょっとした空き時間にすぐ読み始めて、すぐ中断できる。中断する際にボタンをひとつ押すだけで、栞を挟む手間が必要ない。とにかく最小の労力で、気軽に読めるという点で、電子書籍は既存の本より優れていると感じている。
 たくさんの書籍が同じ体積の中に収まる点も大きい。
 満員電車での通勤時間などで「音系」とともに有効に活用できるのではないかだろうか。
 以上のことから、今後は電子書籍がスタンダードになるのは自然の流れだと思われる。
 次に、ブログと電子書籍の違いを考えてみた。
 私が思う一番の違いは、ブログは、記事以外の要素がごちゃごちゃありすぎて読むことのみをメインにしていない点である。
 ただし、広告があることで、記事を書く人のコスト的な敷居が下がると思われるので、電子書籍が個人でも出版しやすくするためには必要だと考えている。
 けれども、ブログのように電子書籍の各ページの一部分や章の間に広告が出てくるような形は好ましくないと考えている。本の内容への没入状態が阻害されるからである。
 では、どのように広告を出せばいいのかを考えてみた。
 電子書籍の利点の一つは、意味を見たい単語などをすぐに調べられるところにある。(いまはそういう機能がないものもあるが、将来的には標準になるだろう)
 その時に広告を出すようにすれば、普通に読み進めるときは一再広告が出ないのし、自ら没入状態を解除すると思われるので、書籍本来の目的をはたせる。さらに、何かを調べるときは、のでそれに関連する商品情報なども有用な調査項目に含まれるので、広告の自然さが増す。
 上記の機能を持つブックリーダがあればすぐ使いたいし、内容なら近いうちにつくってみたいと考えている。

ウインドウ全体を半透明にする(Vista以降)

WPFアプリケーションを新規作成し、MainWindowクラスを以下のように書き換えます。

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += delegate {
                IntPtr hWnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
                this.Background = Brushes.Transparent;
                System.Windows.Interop.HwndSource.FromHwnd(hWnd).CompositionTarget.BackgroundColor = Colors.Transparent;
                DwmExtendFrameIntoClientArea(hWnd, new MARGINS());
            };
        }

        [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public class MARGINS
        {
            public int cxLeftWidth = -1;
            public int cxRightWidth;
            public int cyTopHeight;
            public int cyBottomHeight;
        };

        [System.Runtime.InteropServices.DllImport("DwmApi")]
        public static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, MARGINS pMarInset);
    }

これで実行すると次のようなウインドウになります。