カテゴリー「技術情報もしくは仕事ネタ」の26件の記事

2009年10月 3日 (土)

今時の職業プログラマー見習いの人達は自分たちがそうだった頃より恵まれていないのかもしれない

これからのネット初心者は自分たちがそうだった頃より恵まれていないのかもしれないと思った話

上記はネット黎明期から共に育ってきた人はネットの一般化の流れに合わせて学習する時間があったけど、今時の人はいきなり広大なネット世界に放り込まれるし、以前より悪意のある人が流入してるので大変だろーなって話。

これってプログラマーにも同じよーな事が言えると思います。
今時のプログラマーに求められるスキルは技術革新によりかなり広くそして深くなったと思います。

私がバブル期にこの世界に入った時は業務用途でPCは異端でしたし、メインフレームでCOBOLとJCLとSORTユーティリティの使い方とISAMファイルの扱い方を理解してれば十分仕事が出来ました。
プログラムのアルゴリズムもNewKeyとOldKeyを比較しながらループでぶん回すマッチング処理を綺麗にコーディングできれば一人前でした。

そして技術革新をリアルタイムで体験&学習してきたことでスキルを積み重ねる時間的余裕がありました。
で、気がつくと今時のシステムはたくさんの技術を組み合わせて駆使しないと成立しないよーになってしまいました。

なので、これからの(お金を貰う"プロ"としての)「職業プログラマー」として入ってくる人は、いきなり今のIT業界の技術レベルに合わせる必要があるので大変だなぁと。
就職前から趣味プログラマーである人は勝手にバリバリやってくので例外でしょーけど・・・。

あと、今後増えるであろうコンピュータ体験がケータイ中心の世代だと「プログラミング」って行為自体が疎遠になっていくよーな気がするし・・・。
(iPhoneのよーなユーザがアプリを追加できるスマートフォンがIT世界の中心になってくるとプログラミング行為が復権してくるかもしんないけど)

以前にも似たよーな事を書いてる気がしてますが、職業プログラマー見習いの人達が一人前になるのが以前よりもハードルがかなり上がってて危機感を持ってる人は結構居ると思います。
しかし、教える側も業務に忙殺されているのが現状・・・。

なので、自分のスキルに自信が無い方はせめてもの足掻きとして「他人のコードを読んで理解する」事を心がけて欲しいし、さらに参考にしたコードをコピペするだけで終わらせるだけではなく、必ず弄って自分なりに改良して「スマートに実装できた」と自己満足に浸って欲しいです。

「綺麗なコードは雄弁」です。
そんな雄弁なコードを見つけたら、そのコード(=プログラマーの意図)と"対話"して「そのコードの存在意義」を確認してみてください。
日々の業務に追われて中で対応する仕様書をチラ見しても内容を理解するのは大変かも知れませんが、プログラマーとして価値ある行為だと思います。

多分、連結テストを経たプログラムだったらある程度綺麗なコードになってるんじゃないかな?(←無責任)

| | コメント (0) | トラックバック (0)

2009年8月21日 (金)

[VB.NET]DPAPIを利用して手軽に暗号化を実装する

まず、「DPAPI」は「Data Protection API」の略でWindows2000以降で提供されているデータの暗号化と復号化を行うためのWin32 APIです。

暗号化やその復号には暗号キーの管理がセキュリティ上非常に重要ですが、保存場所に気を使う必要があり、実装する場合に結構面倒な場合があります。

で、「DPAPI」はWindowsのユーザアカウントのパスワードを使用して、暗号化キーを生成し、その暗号キーをWindowsがアプリに代わって管理してくれる事で「暗号化キーの保護」と言う面倒な事を肩代わりしてくれます。

以前にご紹介した暗号化方法はキーの保存が面倒でしたので「Windowsアプリでパスワードなどをアプリ上で簡単に保存したい」って場合はDPAPIを利用した方が便利でしょう。

で、ソースは以下の通りとなります。

Public Class DPAPIClass

    Public Shared Function ProtectedString(ByVal strSrc As String) As String

        Try
            '文字列をバイト型配列に変換
            Dim srcData As Byte() = System.Text.Encoding.UTF8.GetBytes(strSrc)

            'DPAPIで暗号化(暗号化用の追加バイト配列は未使用・スコープはユーザ)
            Dim returnValue As Byte() = System.Security.Cryptography.ProtectedData.Protect(srcData, Nothing, System.Security.Cryptography.DataProtectionScope.CurrentUser)

            '暗号化されたデータをBASE64で文字列に変換
            ProtectedString = System.Convert.ToBase64String(returnValue)

        Catch ex As Exception
            ProtectedString = ""
        End Try

    End Function

    Public Shared Function UnProtectedString(ByVal strSrc As String) As String

        Try
            'BASE64の文字列になってる暗号化データに戻す
            Dim srcData As Byte() = System.Convert.FromBase64String(strSrc)

            'DPAPIで復号化(暗号化用の追加バイト配列は未使用・スコープはユーザ)
            Dim returnValue As Byte() = System.Security.Cryptography.ProtectedData.Unprotect(srcData, Nothing, System.Security.Cryptography.DataProtectionScope.CurrentUser)

            '復号化されたデータを文字列に変換
            UnProtectedString = System.Text.Encoding.UTF8.GetString(returnValue)

        Catch ex As Exception
            UnProtectedString = ""
        End Try

    End Function

End Class

実行するには、System.Securityの参照が必要となります。

DPAPIClass.ProtectedStringメソッドに暗号化したい文字列を渡すと暗号化した文字列が返ってきて、DPAPIClass.UnProtectedStringメソッドに暗号化された文字列を渡すと復号した文字列が返ってきます。
(便宜的にエラーが発生した場合は長さゼロの文字列を返しています)

ちなみにSystem.Security.Cryptography.ProtectedDataのProtectメソッドやUnprotectメソッドの第2引数(サンプルではNothingになってる箇所)は暗号化のための追加バイト配列で同一ユーザや同一マシンでも追加バイト配列の中身を知らないと元に戻せないよーにする事も可能です。

また、同メソッドの第3引数(サンプルではSystem.Security.Cryptography.DataProtectionScope.CurrentUserになってる箇所)は暗号化の保護範囲を示すもので、CurrentUserであれば現在のユーザー、LocalMachineであればコンピュータ単位となります。

ご察しの通り、「DPAPI」は暗号キーの管理をWindowsに依存するため、PCが吹っ飛んでWindowsを再インストールした場合、復号が行えなくなる可能性が高いです。(試してないので不明)
なので「DPAPI」の使用はアプリに必要とされる耐障害性と照らし合わせて決める必要があります。

| | コメント (0) | トラックバック (0)

2009年6月 3日 (水)

[VB.NET]行儀の悪いWebサーバのせーで「サーバーによってプロトコル違反が発生しました」が発生する場合の回避方法

System.Net.WebClientやSystem.Net.WebRequestを利用していて、ある特定のWebサーバとのやり取りすると・・・

サーバーによってプロトコル違反が発生しました. Section=ResponseStatusLine

と、WebExceptionがスローされる場合があります。
念のためWebExceptionのStatusプロパティを確認してみると"ServerProtocolViolation"となってたりします。

つまり、応答したWebサーバのレスポンスがHTTP的に正しくないため、.NET Frameworkの厳密なHTTPの検証に引っかかってしまってエラーとなるよーです。

メッセージ中のSectionが示している通り「ステータス行の不正」らしいので確認してみるとmsdn曰く本来のHTTPのステータス行は

HTTP/1.1 200 OK

と、ステータスコード(200)だけでなくステータスの説明(OK)も必要となるみたい。
ですが、今回の現象が起きたWebサーバは

HTTP/1.1 200

と、ステータスコードしか返さないためダメだったよーです。

.NET Frameworkがセキュリティを考慮して厳密なチェックを行っている訳ですのでサーバ側に改善を求めたいところですが、そーも言ってられないので、.NET Framework version 2.0以降は、上記エラーを無視できるよーになっています。

で、とりあえず今回は手軽なアプリケーション構成ファイル(App.config)で設定を行います。

おそらくStandard Editionなら「新しい項目に追加」でテンプレート内に「アプリケーション構成ファイル」があって追加できると思いますが、当方はMicrosoft Visual Basic 2008 Express Editionだったのでテンプレート内にアプリケーション構成ファイルがありませんでした。

なので、ソースが収められているディレクトリに空のApp.configファイルを作成して「既存の項目の追加」でプロジェクトに追加しました。

そして、App.configの内容を以下の様にして、httpWebRequest要素(ネットワーク設定)のuseUnsafeHeaderParsing属性をtrueにして厳密なHTTP検証を行わないようにします。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.net>
    <settings>
      <httpWebRequest useUnsafeHeaderParsing = "true" />
    </settings>
  </system.net>
</configuration>

この後にビルドやデバックを行うと、App.configがexeファイルと同じフォルダに「アプリ名.exe.config」としてコピーされて実行時に利用されました。

なので、配布時にはexeファイルと同時にApp.configの分身であるexe.configファイルも配布する必要があります。

ちなみに、HttpWebRequestElementクラスにUseUnsafeHeaderParsingプロパティが用意されててプログラムコードからでも上記設定を変更できるよーですが、にわか仕込みの私ではどーやって実装するのか良く分かりませんでした。_| ̄|○

| | コメント (2) | トラックバック (0)

2009年6月 2日 (火)

マイミク&マイミクx2一覧を改良してみた

マイミクのマイミクまでを含めたマイミク一覧表示してる自作mixiアプリですが、前回から数回ほどちょこちょこと以下の改良を加えました。

  • ページオーナーのマイミクやそのマイミクのマイミクに閲覧者本人や閲覧者のマイミクが居た場合に備考を付けるよーにした。
  • サーバやブラウザに負荷をなるべくかけないよーにマイミクのマイミク取得はマイミク1人単位で処理するよーにした。
  • IE6でも動くよーにした。

まぁ、「IE6対応」と言ってもIE6が対応していないArrayのforEachメソッドを使っちゃってたので、for文に改めただけですが・・・。(^^;A

で、コードは↓こんな感じです。(前回とは全然構造が変わってます)

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="FriendList">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html"><![CDATA[

<STYLE type="text/css">
.fixinline {
    display:-moz-inline-box;
    -moz-box-align: center;
    display: inline-block;
    vertical-align: middle;
    width:90px;
}
.thumimg {
    margin: 5px;
    vertical-align: middle;
}
</STYLE>

<div id="friendlist">
マイミク一覧を取得しています・・・
</div>

<script type="text/javascript">
var vfid = [];
var isOwner = false;
var owners;
var viewers;

gadgets.util.registerOnLoadHandler(function () {
   
    //VIEWERのプロフィールとOWNERとVIEWERのマイミク一覧を取得
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //VIEWERのプロフィール取得リクエストを追加
    request.add(request.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER),"viewer_data");
   
    //newFetchPeopleRequestのオプションを指定
    var fetchOpt = [];
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL];

   
    //OWNERのマイミク取得リクエストを追加
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.OWNER;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"owner_friends_data");
   
    //VIEWERのマイミク取得リクエストを追加
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"viewer_friends_data");
   
    //リクエストの送信
    request.send(get_response_level1);
});

function get_response_level1(response) {
   
    var item;
    var i;
   
    //取得したVIEWERのプロフィールでOWNERと同一人物かチェック
    item = response.get("viewer_data");
    if (item.hadError()) {
        if (window.console) console.log("VIEWERプロフィールの取得に失敗:" + item.getErrorMessage());
        return;
    }
    isOwner = item.getData().isOwner();
    if (window.console) console.log("isOwner:" + isOwner);
   
    //OWNERのマイミク一覧を取得
    item = response.get("owner_friends_data");
    if (item.hadError()) {
        if (window.console) console.log("OWNERのマイミク取得に失敗:" + item.getErrorMessage());
        return;
    }
    owners = item.getData().asArray();
    if (window.console) console.log("対象:OWNER");
    if (window.console) console.log("取得数:" + owners.length);
   
    //VIEWERのマイミク一覧を取得
    item = response.get("viewer_friends_data");
    if (item.hadError()) {
        if (window.console) console.log("VIEWERのマイミク取得に失敗:" + item.getErrorMessage());
        return;
    }
    viewers = item.getData().asArray();
    if (window.console) console.log("対象:VIEWER");
    if (window.console) console.log("取得数:" + viewers.length);
   
    //比較用にVIEWERのマイミクIDを配列にセット
    for (i = 0; i < viewers.length; i++) {
        vfid[viewers[i].getId()] = true;
    };
   
    //メッセージを消去
    document.getElementById("friendlist").innerHTML = "";
   
    //OWNERのマイミク一覧を表示
    show_friends(owners,1,"friendlist");
   
    //OWNERのマイミクのマイミクを取得し表示
    setTimeout("get_friends_level2(0)",0);
}

function get_friends_level2(ocnt) {
   
    //全てのOWNERのマイミクの処理を終えたら終了
    if (owners.length <= ocnt) {
        if (window.console) console.log("【処理終了!!】");
        return;
    }
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //newFetchPeopleRequestのオプションを指定
    var fetchOpt = [];
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL];
   
    //OWNERのマイミクのマイミク取得リクエストを追加
    var ofid = owners[ocnt].getId();
    var param = [];
    param[opensocial.IdSpec.Field.USER_ID] = ofid;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
    request.add(request.newFetchPeopleRequest(friends,fetchOpt),"friends_data");
   
    if (window.console) console.log("--------------------");
    if (window.console) console.log("Index:" + ocnt + ":(" + owners[ocnt].getDisplayName() + ")");
    if (window.console) console.log(ofid);
   
    //リクエストの送信
    request.send(function (response) {
        var item = response.get("friends_data");
        if (item.hadError()) {
            // エラー処理
            if (window.console) console.log("OWNERのマイミクのマイミク取得に失敗:" + item.getErrorMessage());
            return;
        }
       
        //マイミクの配列をセット
        var people = item.getData().asArray();
        if (window.console) console.log("対象:" + ofid);
        if (window.console) console.log("取得数:" + people.length);
       
        //OWNERのマイミクのマイミク一覧を表示
        show_friends(people,2,"fid" + ofid);
       
        //次のOWNERのマイミクのマイミクの処理を行う
        setTimeout("get_friends_level2(" + (ocnt + 1) + ")",0);
    });
}

//マイミク一覧の設定
function show_friends(people,level,elementId) {
   
    //マイミク一覧の表示要素を設定
    var element = document.getElementById(elementId);
   
    //マイミク一覧の表示
    var fcnt = 0;
    var i;
    for (i = 0; i < people.length; i++) {
        //IDの取得
        var gid = people[i].getId();
        //ニックネームの取得
        var nickname = people[i].getDisplayName();
        //プロフィールURLの取得
        var prourl = people[i].getField(opensocial.Person.Field.PROFILE_URL);
        //サムネイルURLの取得
        var thumurl = people[i].getField(opensocial.Person.Field.THUMBNAIL_URL);
       
        if (level == 1) {
            //閲覧者または共通の友人用メッセージの設定
            var msg = "";
            if (isOwner == false){
                if (people[i].isViewer()){
                    msg = " (←あなた)";
                } else if (gid in vfid){
                    msg = " (←共通の友人)";
                }
            }
            //マイミク一覧の生成(マイミク用)
            fcnt = fcnt + 1 ;
            var ol = document.createElement('ol');
            ol.id = "fid" + gid;
            var div = document.createElement('div');
            if (fcnt%2 == 0) {
                div.style.backgroundColor = '#FFFFFF';
            } else {
                div.style.backgroundColor = '#FFF8E9';
            }
            div.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><b><a href='" + prourl + "' target='_blank'>" + nickname + "</a></b>" + msg;
            div.appendChild(ol);
            element.appendChild(div);
        } else {
            //閲覧者または共通の友人用メッセージの設定
            var msg = "";
            if (people[i].isViewer()){
                msg = " (←あなた)";
            } else if (gid in vfid){
                msg = " (←共通の友人)";
            }
            //マイミク一覧の生成(マイミクのマイミク用)
            var li = document.createElement('li');
            li.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><a href='" + prourl + "' target='_blank'>" + nickname + "</a>" + msg;
            element.appendChild(li);
        }
    };
}
</script>

]]></Content>
</Module>

やってみて思ったんですが、閲覧者=オーナーなのか判断するために、わざわざ閲覧者のプロフィール取得しなきゃいけないのは、ちと面倒ですな。( ̄з ̄)

インディーズアプリとして下記URLで公開していますので、興味のある方は試してみてください。
http://platform001.mixi.jp/view_appli.pl?id=1377

あと、UIを改良したいと思ってるんですが、それにはFlash化するしか無さそうな感じ。
Flash未経験なのでチャレンジするかは微妙だったりします。(^^;A

あぁ、日記に関するAPIを提供して欲しいなぁ・・・。
マイミクのマイミクさんが「友達の友達まで公開」に設定されている日記の更新状況を知りたくないですか?
そーすれば、マイミクにこだわる事無く緩く繋がれると思うので。
(案外みんな「友達まで公開」にしか設定してないかもしんないけど・・・。(^^;A)


【2009.06.18追記】

  • 何となくメモリリークしている気がするので、get_friends_level2関数はsetTimeout関数経由で呼び出すよーに変更しました。
  • mixiアプリの仕様変更でPROFILE_URLはPROFILE_DETAILSを指定しないと取得できなくなったので対応しました。

| | コメント (0) | トラックバック (1)

2009年4月24日 (金)

mixiアプリを作ってみた、その2(マイミクのマイミクまでを含めた一覧表示)

前回、マイミクのマイミクまでを含めた一覧を表示するmixiアプリを作ってみたもののAPIの動きが安定してない感じで取得できない不具合が続いてましたが、先日mixi側が対応したらしくマイミク一覧を取得できるよーになってました。

で、動かしてみた様子が↓こんな感じ。

20090423_mixiapp_01
プライバシーの関係でモザイクだらけだけなのは勘弁。m(_ _)m

チャンとニックネームやプロフィール画面へのリンクも取得できてて動作するっぽい。
ただ、1回に取得できるのは20人までとなっている模様。

で、調べてみるとnewFetchPeopleRequestにオプションのMAXを指定しないとデフォルトの20が適用されちゃうらしい。

で、改良してみたコードが↓こんな感じ。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="FriendList">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html" preferred_height="400"><![CDATA[

<STYLE type="text/css">
.fixinline {
    display:-moz-inline-box;
    -moz-box-align: center;
    display: inline-block;
    vertical-align: middle;
    width:90px;
}
.thumimg {
    margin: 5px;
    vertical-align: middle;
}
</STYLE>

<div id="friendlist">
</div>

<script type="text/javascript">

//マイミク一覧の表示
gadgets.util.registerOnLoadHandler(function () {
    try {
        //アプリを組み込んだオーナーのマイミクから取得開始
        get_friends(opensocial.IdSpec.PersonId.OWNER,1,"friendlist");
    } catch(e) {
        element.innerHTML = element.innerHTML + "error!:" + e.description + "<br>";
    }
});

function get_friends(pId,level,elementId) {
   
    //マイミク一覧の表示要素を設定
    var element = document.getElementById(elementId);
   
    //opensocial.DataRequestオブジェクトを生成
    var request = opensocial.newDataRequest();
   
    //取得元となる人と取得内容がマイミクである事を指定
    var param = {};
    param[opensocial.IdSpec.Field.USER_ID] = pId;
    param[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS";
    var friends = opensocial.newIdSpec(param);
   
    //フェッチする際のオプションを指定
    var fetchOpt = {};
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX ] = 1000;
    fetchOpt[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = opensocial.DataRequest.SortOrder.NAME;
   
    //マイミクを取得
    request.add(request.newFetchPeopleRequest(friends,fetchOpt), "friends_data");
    request.send(function (response) {
        var item = response.get("friends_data");
        if (item.hadError()) {
            // エラー処理
            element.innerHTML = element.innerHTML + "error!:" + item.getErrorMessage() + "<br>";
            return;
        }
       
        //マイミクのコレクションを返す
        var people = item.getData();
       
        //デバッグ用表示
        if (window.console) console.log("マイミクの全体数:" + people.getTotalSize());
        if (window.console) console.log("マイミクの取得数:" + people.size());
        if (window.console) console.log("階層      :" + level);
        if (window.console) console.log("要素ID     :" + elementId);
        if (window.console) console.log(people);
       
        //マイミクの表示処理
        var fcnt = 0;
        if (people.size() > 0) {
            people.each(function (person) {
                //IDの取得
                var gid = person.getId();
                //ニックネームの取得
                var nickname = person.getDisplayName();
                //プロフィールURLの取得
                var prourl = person.getField(opensocial.Person.Field.PROFILE_URL);
                //サムネイルURLの取得
                var thumurl = person.getField(opensocial.Person.Field.THUMBNAIL_URL);
                               
                if (level == 1) {
                    //マイミク一覧の生成(マイミク用)
                    fcnt = fcnt + 1 ;
                    var ol = document.createElement('ol');
                    ol.id = "fid" + gid;
                    var div = document.createElement('div');
                    if (fcnt%2 == 0) {
                        div.style.backgroundColor = '#FFFFFF';
                    } else {
                        div.style.backgroundColor = '#FFF8E9';
                    }
                    div.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><b><a href='" + prourl + "' target='_blank'>" + nickname + "</a></b>";
                    div.appendChild(ol);
                    element.appendChild(div);
                   
                    //マイミクのマイミクを取得
                    get_friends(gid,2,ol.id);
                } else {
                    //マイミク一覧の生成(マイミクのマイミク用)
                    var li = document.createElement('li');
                    li.innerHTML = "<span class='fixinline'><img src='" + thumurl + "' class='thumimg'></span><a href='" + prourl + "' target='_blank'>" + nickname + "</a>";
                    element.appendChild(li);
                }
            });
        } else {
            element.innerHTML = element.innerHTML + "友達いないよ・・・(´・ω・`)";
        }
    });
}
</script>

]]></Content>
</Module>

一応、並びを一定にするためにSORT_ORDERを指定して名前で並ぶよーにしているけど・・・ニックネーム表示じゃ、あんまり意味が無いかもね。(^^;A

あと、再帰処理の実装がかなり泥臭いですが気にしないで下さい。( ̄▽ ̄;A

ちなみにプロフィール画像のサムネイルのサイズが揃ってないために、ニックネームの表示開始位置が揃わない事を防ぐため、ココに記載されているCSSテクニックの「インライン要素に固定幅を与える」を利用しています。

インディーズアプリとして下記URLで公開していますので、興味のある方は試してみてください。
http://platform001.mixi.jp/view_appli.pl?id=1377

【2009.06.02追記】

この後、数回改良を加えてます。その詳細はコチラです。

| | コメント (0) | トラックバック (2)

2009年4月18日 (土)

mixiアプリを作ってみた

オープンβ版が公開されている「mixiアプリ」ですが、何気に作ってみました。

@Niftyビデオ共有で提供されている、このブログの左上にも張られてるブログパーツ(Flashコンテンツ)をmixi上で表示してます。

で、コードは↓こんな感じ。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="VideoFlash">
    <Require feature="flash" />
    <Require feature="dynamic-height"/>
  </ModulePrefs>
  <Content type="html"><![CDATA[

<div id="videoplayer" align="center"></div>

<script type="text/javascript">
//@Niftyビデオ共有のブログパーツを表示します
gadgets.util.registerOnLoadHandler(function () {
   
    //プレーヤーの表示要素の指定
    var element = document.getElementById("videoplayer");
   
    //プレーヤーのURL&表示内容パラメータの設定
    var uri = "http://dl.video.nifty.com/blog_player.swf?id=0801181843253486287&skin=simple&mode=4&user_id=0000004865&tag=&color=CCCCCC&cb=so60018";
   
    //Flashコンテンツの埋め込み
    gadgets.flash.embedFlash(uri, element, 7,{
         wmode:"transparent"
        ,width:150
        ,height:140
    });
   
    //ガジェットの高さを調整
    gadgets.window.adjustHeight(140);
});
</script>

]]></Content>
</Module>

ちなみにコードは、公式サンプルをほとんど丸写ししています。(笑)

ただ、そのままだとホームやプロフィール画面で表示された場合にデフォルト表示枠の高さより大きくなってスクロールバーが表示されちゃうのでdynamic-heightを利用して高さの調整を行っています。
(ちなみにModulePrefsやContentにheight要素を付けてみたけどダメでした。今のところ高さを調整するにはdynamic-heightを利用するしかないっぽい)

あと、Flashに渡るパラメータを埋め込んじゃっているので全く汎用性がありません。(^^;A

動かして見た感じは↓こんな感じ。

20090417_mixiapp_01

つーか、アプリって右ペインにしか表示できないんだね。
オマケに順番の入れ替えも出来ないし、中央ペインにも設置できれば良いのになぁ・・・。( ̄з ̄)

ちなみに友達の友達まで含めたマイミク一覧表示機能を作りましたが、オープンβや規約の関係のせーなのか、オープンβに参加者してる人しか取得できないよーです。

で、そんな人がマイミクには居ないのでテストできず・・・。_| ̄|○

非同期処理が多いので変なところで苦戦しちゃいますが、JavaScriptを中心にWeb系アプリの開発経験がある人なら、エディタ1つですんなり作れちゃうのは楽しいかも?
(でも、Firebugのコンソール出力機能ぐらいは使わないとデバッグは難しいと思う)

あと、実現したい機能があるんだけどAPIがまだ用意されていないのでAPIの充実を期待してます。
それとドキュメントが貧弱ゥ!なのでgooを見習って欲しいところ。

ただ、折角のアプリもケータイ側の性能的問題でモバイル対応できないのが勿体無いですな。

| | コメント (0) | トラックバック (1)

2009年2月25日 (水)

PostgreSQLを高速化する16のポイント

PostgreSQLを高速化する16のポイント

心意気みたいなのも多々含まれますが参考になるかもしれません。
つーか、しばらく追っかけていないうちに結構な機能アップをしてるのにちょっと驚いてたりして・・・。(^^;A

で、「便利だなぁ」と思ったのは「式インデックス」と「部分インデックス」。

特に「部分インデックス」は少々冗長化しててもフラットな構造の方が扱いやすいよーなトランザクション系データの場合に便利かもしんない。

あと、「遅いSQLを観測する」についてはアプリ側でコッソリ実装する方が個人的には好きだったりします。
(DBMS側でやっちゃうと設定を変更する際に稼動中の状態を変更する必要があるがイヤなので)

| | コメント (0) | トラックバック (0)

2009年2月15日 (日)

[VB.NET]「Atom API」を実装してのデータ送信

Webサービスとやり取りを行う「Atom API」をVB.NETで実装してみました。

この場合、キモとなるのは「WSSE認証」だと思うのですが、「WSSE認証」は要するにワンタイムパスワードみたいなものなので規格の内容を把握してればそれほど難しくは無いよーです。
(ちなみに「WSSE認証」は「SOAP」でも使われてます)

以下のコードは「WSSE認証」用のヘッダを付加してXMLドキュメントをPOSTしています。

'--------------------------------------
' ATOMを利用してXMLドキュメントを送信します
'--------------------------------------
Private Sub postXML()

    Dim strURL As String = ""
    Dim strResStr As String = ""

    'エンドポイントの設定
    strURL = "http://hogehoge.jp/atom/hoehogeService"

    'WSSE認証文字列の生成
    Dim wsseHeader As String = createWSSE("ID", "Password")

    'リクエスト用XMLの作成
    '内容をHTMLエンコードしないとXMLとして不正となる
    Dim strPostData As String = "<?xml version='1.0' encoding='utf-8'?>" & _
                                "<entry xmlns='http://purl.org/atom/ns#'>" & _
                                    "<title>" & Web.HttpUtility.HtmlEncode("題名") & "</title>" & _
                                    "<summary>" & Web.HttpUtility.HtmlEncode("本文") & "</summary>" & _
                                "</entry>"
    Dim byteData As Byte() = System.Text.Encoding.UTF8.GetBytes(strPostData)

    '送信用設定(XMLとして送信)
    Dim webreq As Net.HttpWebRequest = DirectCast(System.Net.WebRequest.Create(strURL), System.Net.HttpWebRequest)
    With webreq
        .Headers.Add("X-WSSE", wsseHeader)
        .Method = "POST"
        .ContentType = "application/atom+xml"
        .ContentLength = byteData.Length
    End With

    '接続&XMLドキュメントを送信
    Using reqStream As IO.Stream = webreq.GetRequestStream()
        reqStream.Write(byteData, 0, byteData.Length)
    End Using

    '送信結果の取得
    Using webres As Net.HttpWebResponse = webreq.GetResponse
        'ヘッダの内容を取得
        With webres
            For i = 0 To .Headers.Count - 1
                Debug.Print(.Headers.Keys(i) & ":" & .Headers(i))
            Next i
        End With

        '返信内容を取得
        Using resStream As IO.Stream = webres.GetResponseStream()
            Using sr As New IO.StreamReader(resStream, System.Text.Encoding.GetEncoding("UTF-8"))
                Debug.Print(sr.ReadToEnd())
            End Using
        End Using
    End Using

End Sub

'--------------------------------------
' ATOMのWSSE認証用ヘッダ文字列を生成します
'--------------------------------------
Private Function createWSSE(ByVal strID As String, ByVal strPw As String) As String

    createWSSE = ""

    ' nonce(HTTPリクエスト毎に生成するセキュリティ・トークン=ランダムな文字)の作成
    Dim nonce As String = RandomStringClass.CreateRandomString(20)

    ' Created(Nonceが作成された日時をISO-8601で表記)
    Dim created As String = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ")

    ' PasswordDigest(Nonce+Created+パスワードをSHA1でダイジェスト化)
    Dim sha1 As New Security.Cryptography.SHA1Managed()
    Dim pwdigest As Byte() = sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(nonce & created & strPw))

    ' WSSE認証用のヘッダを作成(PasswordDigestとnonceはBase64でエンコード)
    Dim format As String = "UsernameToken Username=""{0}"", PasswordDigest=""{1}"", Nonce=""{2}"", Created=""{3}"""
    createWSSE = String.Format(format, strID, Convert.ToBase64String(pwdigest), Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(nonce)), created)

End Function

基本的にデータ送信はHttpWebRequestクラスを使ったオーソドックスなデータ送信です。
それにWSSE認証用のヘッダが追加されているって事になります。

WSSE認証用の各パラメータについては、Webで色々調べるて頂くとして、HTTPリクエスト毎に生成するセキュリティトークンである「nonce」だけちょっと触れます。

今回のサンプルの場合、nonceは「暗号乱数ジェネレータを使った厳密にランダムな文字列の生成」を利用してランダムに生成された文字列としていますが、本来は、8bitのByte値であり、0~9・A~Z・a~zの文字に限定されません。
また、サイズについても本来は任意のバイト数となっているよーです。

ちなみにパスワードを含んでいるPasswordDigestはハッシュ値なので、要求を受け取ったサーバ側で認証する際にサーバ側のDBに格納されているパスワードは平文に変換できるよーになってないと比較する事ができなかったりします。
もし、サーバ側DBに格納されているパスワードがハッシュ値しか無い場合は、クライアントはサーバ側と同じ方式でパスワードのハッシュ値を得てPasswordDigestにセットする事になるはずです。
クライアント側もたまたま同じ方式でハッシュ値を保存してれば、サーバ・クライアントイ共に平文のパスワードを得る方法が存在しないので脆弱性を突かれた場合を考えるとセキュリティ的に良いんですが、オープンなAPIとなった場合、上記理由から結果的にパスワードの保存方法の手の内を晒すことになったりします・・・。

| | コメント (0) | トラックバック (0)

2009年2月11日 (水)

[VB.NET]オブジェクトの逆シリアル化で空白や改行を再現する

XmlSerializerクラスの、オブジェクトのプロパティをXMLドキュメントとして保存する「シリアル化」、そこから復元してくれる「逆シリアル化」はiniファイル代わりになって結構便利だったりします。

しかし、String型のプロパティの値に空白や改行が含まれている状態で単純に逆シリアル化をすると空白や改行除去されてしまいます。

シリアル化されたXMLドキュメントを確認すると空白や改行が含まれているんですが、逆シリアル化の際に除去されているよーです。

原因はXmlSerializerクラスのDeserializeメソッドが、XmlDocumentクラスのPreserveWhitespaceプロパティがFalseに相当する方法でXMLドキュメントを読み込むからです。

しかし、XmlSerializerクラスにはPreserveWhitespaceプロパティ相当するプロパティが存在しません。
そこで、XmlDocumentクラスを利用してXMLドキュメントを読み込み、XmlReader経由で逆シリアル化を行う事で、空白や改行を有効なまま逆シリアル化を行う事ができます。

PreserveWhitespaceプロパティは本来空白のために用意されているんですが、改行も再現してくれるよーです。
(おそらく除去をしないで、そのまま要素の内容をスルーさせてるんだと思う)

つーか、単純に逆シリアル化出来るよーに、XmlSerializerクラスのSerializeメソッドが、シリアル化する際に自動的にエンコードしてくれても良いよーな気がするんですが、どーなんでしょ?(^^;A

具体的なコードは以下の通りとなります

''' <summary>
''' 設定をファイルから読み込む
''' </summary>
''' <param name="clsACC">AppConfigClass</param>
Public Shared Sub ReadConfig(ByRef clsACC As AppConfigClass)

    'XmlSerializerオブジェクトの作成
    Dim serializer As New System.Xml.Serialization.XmlSerializer(GetType(AppConfigClass))

    '空白・改行コードを維持するためにPreserveWhitespaceを有効にする
    Dim doc As New System.Xml.XmlDocument()
    doc.PreserveWhitespace = True

    'XmlReaderを使って読み込んで逆シリアル化
    doc.Load("hogehoge.xml")
    Using reader As New System.Xml.XmlNodeReader(doc.DocumentElement)
        Dim o As Object = serializer.Deserialize(reader)
        clsACC = DirectCast(o, AppConfigClass)
    End Using

End Sub

ReadConfigプロシージャの引数に逆シリアル化するクラスのインスタンスを指定して呼び出すと、インスタンスの内容をXMLドキュメント「hogehoge.xml」から取得して復元してくれます。

| | コメント (0) | トラックバック (0)

2009年2月 7日 (土)

[VB.NET]文字列をチャンと暗号化する

.NETアプリの開発Tipsとして有名なDOBON.NETさんの「.NET Tips」に「ファイルを暗号化する」や「文字列を暗号化する」にサンプルのコードが挙がっていますが、初期化ベクタを生成せずデフォルト値をそのまま使っちゃっているため暗号化の処理としてちょっとマズイ事になっています。

そこで上記リンク先のコードを参考に改造を行いました。
ついでに暗号化アルゴリズムをRijndaelに変更してあります。

暗号化キーと初期化ベクタを生成は、RijndaelManagedのGenerateKeyメソッド・GenerateIVメソッドでランダムな暗号化キーと初期化ベクタを生成するようにしました。
そして、生成した暗号化キーと初期化ベクタはレジストリのHKEY_CURRENT_USER以下の任意の場所に保存し、復号化に備えています。

上記改造を施したコードは以下の通りとなります。

Public Class CrypticClass

    Public RegistryKey As String    'レジストリのキー

    Private Key As String       '暗号化キー(16進文字列)
    Private IV As String        '初期化ベクタ(16進文字列)

    Private aes As System.Security.Cryptography.RijndaelManaged

    '定数
    Private Const SubkeyKey = "Key"     '暗号キー保存用のレジストリのサブキー
    Private Const SubkeyIV = "IV"       '初期化ベクタ保存用のレジストリのサブキー

    Public Sub New()
        '初期化
        RegistryKey = ""
        Key = ""
        IV = ""
        'RijndaelManagedオブジェクトの作成
        aes = New System.Security.Cryptography.RijndaelManaged
    End Sub

    ''' <summary>
    ''' 暗号化&(無ければ)暗号化キーの生成と保存もします
    ''' </summary>
    ''' <param name="strSrc">暗号化する文字列</param>
    ''' <returns>暗号化された文字列</returns>
    Public Function Encrypt(ByVal strSrc As String) As String

        Encrypt = ""

        If strSrc = "" Then
            Exit Function
        End If

        If RegistryKey = "" Then
            Throw New ApplicationException("CrypticClass.RegistryKeyプロパティが設定されていません。")
        End If

        '暗号化キー・初期化ベクタを取得
        GetKey()

        '暗号化キー・初期化ベクタが無ければランダムな暗号化キー・初期化ベクタを生成&保存
        If (Key = "") Or (IV = "") Then
            'ランダムな暗号化キーを生成
            aes.GenerateKey()
            'ランダムな初期化ベクタを生成
            aes.GenerateIV()
            'ハイフン区切りの16進数表記に変換
            Key = BitConverter.ToString(aes.Key)
            IV = BitConverter.ToString(aes.IV)
            '生成された暗号化キー・初期化ベクタをレジストリに保存
            SaveKey()
            '念のため暗号化キー・初期化ベクタを再度読み込む
            GetKey()
        End If

        'キー・初期化ベクタの内容をチェック
        If (Key = "") Or (IV = "") Then
            Throw New ApplicationException("暗号化キーの生成に失敗しました。")
        End If

        '暗号化
        Encrypt = EncryptString(strSrc)

    End Function

    ''' <summary>
    ''' キーの取得&復号化
    ''' </summary>
    ''' <param name="strSrc">復号化する文字列</param>
    ''' <returns>復号化された文字列</returns>
    Public Function Decrypt(ByVal strSrc As String) As String

        Decrypt = ""

        If strSrc = "" Then
            Exit Function
        End If

        If RegistryKey = "" Then
            Throw New ApplicationException("CrypticClass.RegistryKeyプロパティが設定されていません。")
        End If

        '暗号化キーを取得
        GetKey()

        '暗号化キーが無い場合
        If (Key = "") Or (IV = "") Then
            Throw New ApplicationException("暗号化キーが存在しません。")
        End If

        '復号化
        Decrypt = DecryptString(strSrc)

    End Function

    ''' <summary>
    ''' キーの所得
    ''' </summary>
    Private Sub GetKey()

        'レジストリのキーを開く
        Using regkey As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegistryKey, False)
            'レジストリのキーが存在しない場合
            If (regkey Is Nothing) Then
                Exit Sub
            End If

            'サブキーに保存されている暗号化キーを読み込む
            Dim strKeyValue As String = CType(regkey.GetValue(SubkeyKey), String)
            'サブキーが存在しない場合
            If (strKeyValue Is Nothing) Then
                Exit Sub
            End If

            'サブキーに保存されている初期化ベクタを読み込む
            Dim strIVValue As String = CType(regkey.GetValue(SubkeyIV), String)
            'サブキーが存在しない場合
            If (strIVValue Is Nothing) Then
                Exit Sub
            End If

            '値のセット
            Key = strKeyValue
            IV = strIVValue
        End Using

    End Sub

    ''' <summary>
    ''' キーの保存
    ''' </summary>
    Private Sub SaveKey()

        'レジストリのキーを開く(無い場合は作成)
        Using regkey As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegistryKey)
            'レジストリへの書き込み
            'サブキーに暗号化キーと初期化ベクタを書き込む
            regkey.SetValue(SubkeyKey, Key)
            regkey.SetValue(SubkeyIV, IV)
        End Using

    End Sub

    ''' <summary>
    ''' 文字列を暗号化する
    ''' </summary>
    ''' <param name="str">暗号化する文字列</param>
    ''' <returns>暗号化された文字列</returns>
    Private Function EncryptString(ByVal str As String) As String

        EncryptString = ""

        '文字列をバイト型配列にする
        Dim bytesIn As Byte() = System.Text.Encoding.UTF8.GetBytes(str)

        '共有キーと初期化ベクタを設定
        aes.Key = HexToByte(Key)
        aes.IV = HexToByte(IV)

        '暗号化されたデータを書き出すためのMemoryStream
        Using msOut As New System.IO.MemoryStream
            'AES暗号化オブジェクトの作成
            Dim aesdecrypt As System.Security.Cryptography.ICryptoTransform = aes.CreateEncryptor()
            '書き込むためのCryptoStreamの作成
            Using cryptStreem As New System.Security.Cryptography.CryptoStream(msOut, aesdecrypt, System.Security.Cryptography.CryptoStreamMode.Write)
                '書き込む
                cryptStreem.Write(bytesIn, 0, bytesIn.Length)
                cryptStreem.FlushFinalBlock()
                '暗号化されたデータを取得
                Dim bytesOut As Byte() = msOut.ToArray()
                'Base64で文字列に変更して結果を返す
                EncryptString = System.Convert.ToBase64String(bytesOut)
            End Using
        End Using

    End Function

    ''' <summary>
    ''' 暗号化された文字列を復号化する
    ''' </summary>
    ''' <param name="str">暗号化された文字列</param>
    ''' <returns>復号化された文字列</returns>
    Private Function DecryptString(ByVal str As String) As String

        DecryptString = ""

        '共有キーと初期化ベクタを設定
        aes.Key = HexToByte(Key)
        aes.IV = HexToByte(IV)

        'Base64で文字列をバイト配列に戻す
        Dim bytesIn As Byte() = System.Convert.FromBase64String(str)
        '暗号化されたデータを読み込むためのMemoryStream
        Using msIn As New System.IO.MemoryStream(bytesIn)
            'AES復号化オブジェクトの作成
            Dim aesdecrypt As System.Security.Cryptography.ICryptoTransform = aes.CreateDecryptor()
            '読み込むためのCryptoStreamの作成
            Using cryptStreem As New System.Security.Cryptography.CryptoStream(msIn, aesdecrypt, System.Security.Cryptography.CryptoStreamMode.Read)
                '復号化されたデータを取得するためのStreamReader
                Using srOut As New System.IO.StreamReader(cryptStreem, System.Text.Encoding.UTF8)
                    '復号化されたデータを取得する
                    Dim result As String = srOut.ReadToEnd()
                    DecryptString = result
                End Using
            End Using
        End Using

    End Function

    ''' <summary>
    ''' BitConverter.ToStringの16進数表記をバイト配列に復元する
    ''' </summary>
    ''' <param name="strHex">16進数表記の文字列</param>
    ''' <returns>復元されたバイト配列</returns>
    Private Function HexToByte(ByVal strHex As String) As Byte()

        Dim i As Integer = 0

        Dim strHexChr() As String = Split(strHex, "-")

        Dim newBytes(UBound(strHexChr)) As Byte
        For i = 0 To UBound(strHexChr)
            newBytes(i) = Convert.ToByte(strHexChr(i), 16)
        Next i

        HexToByte = newBytes

    End Function
End Class

CrypticClassのインスタンスを生成後、RegistryKeyプロパティに暗号化キーと初期化ベクタを保存する(してある)レジストリの場所を指定します。

その後、暗号化する場合はEncryptメソッドを、復号化する場合はDecryptメソッドを呼び出します。
暗号化された文字列は扱いやすいよーにBase64でエンコードされています。

また、Encryptメソッドは指定されたレジストリの場所に暗号化キーと初期化ベクタの値が存在しているのかをチェックし、無ければRijndaelManagedのGenerateKeyメソッド・GenerateIVメソッドでランダムな暗号化キーと初期化ベクタを生成してレジストリに保存しています。

ちなみに生成された暗号化キーと初期化ベクタはBitConverter.ToStringメソッドでハイフン区切りの16進数表記の文字列でレジストリに登録されています。

また、暗号化キーと初期化ベクタはアクセスコントロールがチャンとしている場所に保存しないと第三者が覗けてしまって意味が無くなるので保存場所に気を使う必要があります。

あと、ブロック長やキー長はデフォルトのままになっています。(^^;A


【2009.08.21追記】

「DPAPI」を利用して暗号化キーの保存をWindows任せにすることで手軽に暗号化を実装することが可能です。
詳細についてはコチラをご覧ください。

| | コメント (4) | トラックバック (0)