Jake's BLOG

最近のIT技術動向と社会問題など・・・・

jCryption.NET HTTP FORM POSTの暗号化

Fork me on GitHub

jCryptionとは

http://www.jcryption.org/

jCryptionはウェブページのフォームデータをHTTPでPOSTする際に、データをRSA/AESで暗号化してサーバーに送信するjQueryのプラグインライブラリです。

jCryption.NETはjCryptionの送信した暗号データをサーバーで復号化するためのASP.NET用のライブラリです。

背景

ログインフォーム、問い合わせフォーム、アンケートフォームなど、ウェブページ上で利用者の入力を受けサーバーへデータを送信する場面は多くあります。一方で、個人情報、IDやパスワードなど他者に見られたくない内容を含む場合、そのフォーム送信は「暗号化」によって保護された状態で送信されるべきで、通常、平文通信であるHTTPでの送信は避けHTTPSで保護されるべきです。しかし、HTTPS/SSLの通信をサポートするためには高額(ベリサインの場合8万円程度)のサーバー証明書のための費用とウェブサーバーのIPアドレスを一つ独占するというコストがかかり、ちょっとしたアンケートフォームなどの目的には導入を躊躇する場面もあります。

本格的にウェブサーバーとの通信にHTTPS/SSLを導入することなく、クライアントからサーバーへ送信される情報を暗号化により保護しようというのがjQueryのプラグインであるjCryptionというJavascriptのライブラリのコンセプトです。

一般にHTTPS/SSLには

  1. 通信の相手であるサーバーが本物であるかどうかを保証すること(サーバー証明書)
  2. 通信中のデータの暗号化

の2種類の目的がありますが、jCryptionは2つ目の通信中のデータの暗号化のみをサポートします。

jCryptionが使用する暗号は RSA/AESで、RSAはクライアント側で生成されたAESの鍵を暗号化してサーバーへ送信するために使われます。実際にPOSTされるフォームのデータはAESで暗号化されて送信されます。

これから先のjCryptionそのものの詳しい内容は本家のウェブサイトに譲ります。http://www.jcryption.org/(英語)

ここでは、jCryption.NETを使った例を紹介します。

jCryption.NET

ソースコードとサンプルはGitHub で公開しています。

クライアント側の使用法は本家で解説されているように、jQueryとjCryptionのライブラリを<script>で読み込み
 $(“#form”).jcryption(….)
で指定したフォームをjCryption対応にセットします。
このフォームを送信するにあたって、暗号の鍵の交換、データの解読にサーバー側のプログラムが必要になります。本家のパッケージではPHPやPerl CGIによる実装がすでに含まれています。ここでは、Windows Server + IIS + ASP.NETな環境でサーバー側の実装を紹介します。

想定環境

  • IIS6以上のウェブサーバー(Windows 2003 Server以降)
  • .NET 4.0 以上の.NET Framework
  • ASP.NET WebPagesと呼ばれる cshtml ファイルによるページ

ASPXページの場合

ページをjCryption.SecurePage を継承させることで、暗号処理を処理させられます。javascript部分は手作業で挿入します。
<%@  Language="C#" Inherits="jCryption.SecurePage" %>
<html>
<head>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery.jcryption.3.1.0.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#normal").jCryption({
                getKeysURL: "<%= Request.Path %>?getPublicKey=true",
		            handshakeURL: "<%= Request.Path %>?handshake=true"
		        });
		    });
    </script>
</head>

CSHTMLページの場合

  1. jCryption.csファイルを App_Codeフォルダへ配置します。
  2. フォームのあるcshtmlページの先頭部分に@using jCryption を記述します。
  3. 鍵の交換、復号化などを同じページ上(URL)で行うため、処理の初めに
    jCryption.HandleRequest(Request);
    を呼び出します。この呼び出しの後で、Request.Formは復号化されたデータにアクセスできます。
  4. HTML内で、依存するjqueryを読み込みます。jquery.validateも読み込み可能です。
  5. HTML内、上記のjqueryの読み込みの後、適当な場所に
    @jCryption.RenderScriptFor(“#login”, src:”/Scripts/jquery.jcryption….js”);
    を記述します。jcryptionのjsファイルを直接<script>で指定しないのは、このページがHTTPSでアクセスされた場合に、自動的にjcryptionを無視して読み込まない動作を行うためです。

@using jCryption
@{
    jCryption.HandleRequest(Request);
}
<!doctype html>
<html>
   <head>
      <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
      @jCryption.RenderScriptFor("#normal", src: "Scripts/jquery.jcryption.3.1.0.js")
   </head>
   ......
   <form id="normal">
      .....
   </form>
   ......

フォームの初期データ、部分的にHTMLを暗号化して読み込む機能

jCryption.NETをcshtmlで使用する場合に、フォームデータと、HTMLテキストの暗号化にも対応しています。もともとjCryptionはクライアントからサーバーへ送信されるデータの保護を目的としていますが、ログインIDやパスワード、住所氏名などはフォーム上であらかじめわかっている内容を入れてHTML出力されることがあります。この場合HTTPのままですと、データは平文で保護されておらず、片手落ちとなります。

  1. フォームの各要素を次のように記述します。
    <input type=text name=Name value=”山田” />は
    <input type=text @jCryption.SecureNameValue(“Name”,”山田”) />

    チェックボックスやラジオボタンは
    <input type=”checkbox” @jCryption.SecureNameValueCheck(“Sex”,”Male”, true ) />

  2. HTMLまたはテキスト領域を暗号化で保護したい場合は
    @jCryption.SecureHtml()
    @jCryption.SecureText()
  3. 上記の呼び出しの後に
    @jCryption.LoadSecureContents()
    を呼び出します。ここに、一連の復号化処理、サーバーとの通信処理が記述されます。

ライブラリリファレンス

@jCryption.RenderScriptFor( String selector, String src = null, String script = null )

jCryption を利用するFORMをjQueryのセレクタで指定します。この呼び出しは通常 head タグか body 内最後の方に配置します。jquery.validate と同時に使用する場合は jquery.validate を読み込む script タグの後になるようにします。

selector: jCryptionで初期化する対象のFORMを jQuery のセレクタで指定します。

src: jCryption のjsファイルへのパスを指定します。

script: その他のjavascriptが必要な場合に <script>...</script>を含むHTMLを指定します。


@jCryption.SecureNameValue( String name, String value )

フォーム要素の初期状態で表示されるデータを保護します。<input> タグ内に記述します。.

 <input type='text' @jCryption.SecureNameValue("Name", "Smith") />

input タグの name と value を引数に指定します。


@jCryption.SecureNameValueCheck( String name, String value, bool check )

input の checkbox radiobutton に対応したバージョンです。
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Dog", true ) />
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Cat", false ) />

@jCryption.SecureText( String text )

暗号化されたテキストコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.SecureHtml( String html )

暗号化されたHTMLコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.LoadSecureContents()

SecureNameValue/SecureNameValueCheck/SecureText/SecureHtml の内容を保護された実際のデータをDOM要素中に読み込むことを指示します。上記4種類メソッド呼び出しの一番最後にこの呼び出しを追加してください。

jCryption version 3.1.0に内在するいくつかの技術的問題点

最後に本家jCryptionに見られるいくつかの技術的問題点を指摘しておきます。

  1. AESの鍵が<form>単位で異なる問題
    クライアントはformのsubmit時にAESキーを生成しajax呼び出しでサーバーに通知します。この時サーバーはSessionにAES鍵を保存します。同じSession上で同時にformのsubmitが起こることはまれですが、原理上、クライアントとサーバーで鍵の1対1の整合性が保てていません。
  2. 先の理由により、<form> POST後にブラウザのF5(更新)によって再度POSTした場合にSession上に保存された鍵との一貫性の保証がなく、再POSTがエラーとなります。これには、POSTデータに暗号化されたフォームデータとともに暗号化されたAES鍵も同時に送信されるようにすべきです。
  3. jquery.validateライブラリと一緒に使用したときの問題があります。jCryptionはSubmitボタンclickイベントをフックして暗号化処理ののち改めてformのsubmitを呼び出します。一方で、jquery.validateはformのsubmitイベントでvalidationを実行しています。処理順序が逆転することで2つのライブラリを同時に使用することができません。(jCryption.NETではこの点を改善するjavascriptのコードを出力しています。)
※上記の問題を修正したバージョンを jquery.jcryption.3.1.0.mod.js として同梱しています。


ASP.NET WebPagesと相性のよい w2ui grid

ASP.NET WebPages と Razor(cshtml)の組み合わせに相性のよいTableソリューションを求め歩いていたところ DataTablase に続いて w2ui という優れたライブラリを発見。

w2ui はレイアウト、グリッド、ツールバー、サイドバー、タブ、フォーム、ポップアップその他のユーティリティで構成されるライブラリですが、軽量設計で使いやすくまとまっています。

今回Gridを試したわけですが、このGridの優れたところは、ページングの概念を捨てて「無限スクロール」を導入したこと。しかもその動作は軽快です。

DataTablesと同様にサーバーへは form data、サーバーからはJSONによる応答です。form dataの仕様がこちらのほうが簡単で使いやすいかもしれません。また、開発中の Ver 1.4 には拙翻訳を提供したので日本語表記の利用も可能です。

jQuery DataTablesでやったことと同じことを w2ui を使って再現してみます。

1:  @{  
2:       if (IsPost)  
3:       {  
4:            if (UrlData[0] == "GetList" )  
5:            {  
6:                 var all = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures).AsEnumerable<System.Globalization.CultureInfo>();  
7:                 var filtered = all;  
8:                 int iDisplayStart = Request["offset"].AsInt(0);  
9:                 int iDisplayLength = Request["limit"].AsInt(10);  
10:                 bool sorted = false;  
11:                 for (int i = 0; Request[String.Format("sort[{0}][field]", i)] != null; i++)  
12:                 {  
13:                      String fld = Request[String.Format("sort[{0}][field]", i)];  
14:                      bool asc = Request[String.Format("sort[{0}][direction]", i)] == "asc";  
15:                      switch( fld)  
16:                      {  
17:                           case "Name":  
18:                                filtered = asc ? filtered.OrderBy(m => m.Name) : filtered.OrderByDescending(m => m.Name);  
19:                                break;  
20:                           case "DisplayName":  
21:                                filtered = asc ? filtered.OrderBy(m => m.DisplayName) : filtered.OrderByDescending(m => m.DisplayName);  
22:                                break;  
23:                           case "TwoLetterISOLanguageName":  
24:                                filtered = asc ? filtered.OrderBy(m => m.TwoLetterISOLanguageName) : filtered.OrderByDescending(m => m.TwoLetterISOLanguageName);  
25:                                break;  
26:                           case "ThreeLetterISOLanguageName":  
27:                                filtered = asc ? filtered.OrderBy(m => m.ThreeLetterISOLanguageName) : filtered.OrderByDescending(m => m.ThreeLetterISOLanguageName);  
28:                                break;  
29:                      }  
30:                      sorted = true;  
31:                 }  
32:                 if (!sorted) { filtered = filtered.OrderBy(m => m.Name); }  
33:                 for (int i = 0; Request[String.Format("search[{0}][field]", i)] != null; i++)  
34:                 {  
35:                      String fld = Request[String.Format("search[{0}][field]", i)];  
36:                      String sSearch = Request[String.Format("search[{0}][value]", i)];  
37:                      if (!String.IsNullOrEmpty(sSearch))  
38:                      {  
39:                           switch (fld)  
40:                           {  
41:                                case "Name":  
42:                                     filtered = filtered.Where(m => m.Name.Contains(sSearch));  
43:                                     break;  
44:                                case "DisplayName":  
45:                                     filtered = filtered.Where(m => m.DisplayName.Contains(sSearch));  
46:                                     break;  
47:                                case "TwoLetterISOLanguageName":  
48:                                     filtered = filtered.Where(m => m.TwoLetterISOLanguageName.Contains(sSearch));  
49:                                     break;  
50:                                case "ThreeLetterISOLanguageName":  
51:                                     filtered = filtered.Where(m => m.ThreeLetterISOLanguageName.Contains(sSearch));  
52:                                     break;  
53:                           }  
54:                      }  
55:                 }  
56:                 var output = new  
57:                 {  
58:                      status = "success",  
59:                      total = filtered.Count(),  
60:                      records = filtered.Skip(iDisplayStart).Take(iDisplayLength).AsEnumerable().Select((o, i) => new { recid = o.Name, o.Name, o.DisplayName, o.TwoLetterISOLanguageName, o.ThreeLetterISOLanguageName })  
61:                 };  
62:                 Response.ContentType = "application/json";  
63:                 Response.Write(Json.Encode(output));  
64:                 Response.End();  
65:                 return;  
66:            }  
67:       }  
68:  }  
69:  <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>  
70:  <link rel="stylesheet" href="http://w2ui.com/src/w2ui-1.3.min.css" type="text/css" />  
71:  <script src="http://w2ui.com/src/w2ui-1.3.min.js"></script>  
72:  <script>  
73:    $(document).ready(function () {  
74:         $('#sampleTable').w2grid({  
75:              name: 'grid',  
76:              header: 'List of Names',  
77:              show: {  
78:                   toolbar: true,  
79:                   footer: true  
80:              },  
81:              columns: [  
82:                      { field: 'Name', caption: 'Name', size: '20%', sortable: true, attr: 'align=center' },  
83:                      { field: 'DisplayName', caption: 'DisplayName', size: '30%', sortable: true, resizable: true },  
84:                      { field: 'TwoLetterISOLanguageName', caption: 'TwoLetterISOLanguageName', size: '30%', sortable: true, resizable: true },  
85:                      { field: 'ThreeLetterISOLanguageName', caption: 'ThreeLetterISOLanguageName', size: '30%', resizable: true }  
86:              ],  
87:              searches: [  
88:                      { field: 'Name', caption: 'Name', type: 'text' },  
89:                      { field: 'DisplayName', caption: 'DisplayName', type: 'text' },  
90:                      { field: 'TwoLetterISOLanguageName', caption: 'TwoLetterISOLanguageName', type: 'text' },  
91:              ],  
92:              sortData: [{ field: 'Name', direction: 'ASC' }],  
93:                 url: '@Href(Request.AppRelativeCurrentExecutionFilePath,"GetList")'  
94:         });  
95:    });  
96:  </script>  
97:  <div>  
98:       <div id="sampleTable" style="height: 600px;">  
99:       </div>  
100:  </div>  

ASP.NET WebPagesと相性のよい jQuery DataTables

ASP.NET WebPages (MVCではない)で、Razor(cshtml)で書くのがマイブームです。ASPXより圧倒的に書きやすく、MVCほどコードの分散が起こらず、一人でコードを書くには最適な選択だと感じています。

さて、cshtml上で、サーバーサイドのデータでもって、ページング、ソート、フィルター付きのテーブルを探していた時に jQuery DataTables にたどり着きました。ほかにも類似のTableソリューションはたくさんありますが、これが、軽量、簡単で ASP.NET Web Pagesと非常に相性がいいと思ったので紹介します。

「DataTables https://datatables.net/index はjQueryのプラグインライブラリで、HTMLのtableに機能を追加する形で動作します。」

詳細は原典を参照してもらうことにし、ASP.NET Web Pages から使用する場合のキーポイントに絞ると

  • AJAXによるサーバーサイドからのデータの取り出しに対応。
  • サーバーサイドのページング、ソート、フィルター(検索)に対応している。
  • サーバーへのリクエストは FORMエンコーディングで、サーバー側のパラメータの取り出しに Request.Form が使える。
  • クライアントへの応答は json で、サーバーからは Json(System.Web.Helpers.Json)で簡単に応答できる。
  • 受け渡すデータは型が規定されていない(ただの配列渡し)のでルーズだが、簡単。サーバーサイドでLINQと anonymous object を組み合わせれば非常に簡単。

サンプルとしてシステムにある System.Globalization.CultureInfo を列挙してテーブルに表示するコードを書いてみました。1ページアプリとしてもこんなに簡単に短く、高機能テーブルを書くことができました。

http://www.yo-ki.com/study/ASPNET/ExperimentDataTables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@{
    if( IsPost ){
        if( UrlData[0] == "GetList" ){
            String sEcho = Request.Form["sEcho"];
            int iDisplayStart = Request.Form["iDisplayStart"].AsInt(),
                iDisplayLength = Request.Form["iDisplayLength"].AsInt(),
                iSortingCols = Request.Form["iSortingCols"].AsInt();
            String sSearch = Request.Form["sSearch"];
            var data = new List<object>();
            var all = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures);
            var filtered = all.AsEnumerable<System.Globalization.CultureInfo>();
            if( !String.IsNullOrEmpty(sSearch) ){
                filtered = filtered.Where(p => p.DisplayName.Contains(sSearch) || p.Name.Contains(sSearch) || p.ThreeLetterISOLanguageName.Contains(sSearch) || p.TwoLetterISOLanguageName.Contains(sSearch));
            }
            for( var i=0; i<iSortingCols; i++ ){
                var iSort = Request.Form["iSortCol_" + i].AsInt(-1);
                var ascending = Request.Form["sSortDir_" + i];
                if( ascending == "asc" ){
                    filtered = filtered.OrderBy(p => iSort == 0 ? p.Name : iSort == 1 ? p.DisplayName : iSort == 2 ? p.TwoLetterISOLanguageName : p.ThreeLetterISOLanguageName);
                }
                else
                {
                    filtered = filtered.OrderByDescending(p => iSort == 0 ? p.Name : iSort == 1 ? p.DisplayName : iSort == 2 ? p.TwoLetterISOLanguageName : p.ThreeLetterISOLanguageName);
                }
            }
            var result = filtered;
            foreach( var ci in filtered.Skip(iDisplayStart).Take(iDisplayLength) ){
                var row = new List<object>();
                row.Add(ci.Name);
                row.Add(ci.DisplayName);
                row.Add(ci.TwoLetterISOLanguageName);
                row.Add(ci.ThreeLetterISOLanguageName);
                data.Add(row);
            }
            Response.Clear();
            Response.ContentType = "text/json";
            Response.Write( Json.Encode(new {
                sEcho = sEcho,
                iTotalRecords = all.Count(),
                iTotalDisplayRecords = filtered.Count(),
                aaData = data
            }));
            Response.End();
            return;
        }
    }
}
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script>

<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css" type="text/css"/>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.js"></script>

<script>
    $(document).ready(function () {
        $("#sampleTable").dataTable({
            "bProcessing": true,
            "bServerSide": true,
            "bPaginate": true,
            "sPaginationType": "full_numbers",
            "sServerMethod": "POST",
            "sAjaxSource": '@(Request.Path)/GetList'
        });
    });
</script>
<div>
    <table id="sampleTable">
        <thead>
            <tr><th>Name</th><th>DisplayName</th><th>TwoLetterISOLanguageName</th><th>ThreeLetterISOLanguageName</th></tr>
        </thead>
        <tbody>
        </tbody>
    </table>
</div>

VAIO DUO 11をWiMAX - WiFi アクセスポイントにする方法(マニュアル設定編)

ここでは WiMAX 搭載 SONY VAIO DUO 11 を前提に、WiFiアクセスポイントとして動作させるための方法を紹介します。

特別に必要なもの

  • USB WiFi アダプター

私は Logitechの LAN-W300N/U2S というUSBアダプターを使いました。一般的に動作可能であればなんでもいいでしょう。

前置き

VAIO DUO 11ではオーダーメイドで構成としてWiMAX搭載を選択することができます。VAIO単体で電源オンからすぐにネットへつながる便利さは格別です。DUO11を中心にネット環境を充実させるべく次に考えることが、DUO11をWiMAX母艦にして、周辺のデバイス(スマートフォンやタブレットなど)からWiFi経由でインターネットを利用したいということです。

ところが、DUO11には、これを実現するために致命的な問題が1つあります。「WiMAXを使用中はWiFiデバイスは使用できない」というもの。つまりWiMAXとWiFiは排他利用だということです。これは、Intel のワイヤレスデバイスの仕様によるもので、OSやソフトなどではどうしようもないのです。

そこで、次善のアイデアとして別途USBでWiFiアダプタを追加してやればそれを通じてWiMAXを共有できるかもしれないというものです。

手順

ここで紹介する手順はコマンドプロンプトを管理者モードで操作するもので、自信のある方のみ自己責任でチャレンジしてください。

1.USB接続のWiFiアダプタが正常にインストールされているか確認

2.本体に内蔵のWiFiアダプタを「無効」に

 

3.コマンドプロンプトを管理者モードで開きます。

C:\Windows\system32>netsh wlan set hostednetwork mode=allow ssid="DUO11WiMAX" key="ILoveDuo11"

ssidはその名の通りアクセスポイントの公開名称となるSSID。keyは接続するときのパスワードになります。正常に実行されれば次のように表示されます。

ホストされたネットワーク モードは許可に設定されています。
ホストされたネットワークの SSID が正常に変更されました。
ホストされたネットワークのユーザー キー パスフレーズが正常に変更されました。

次のコマンドでWiFiアクセスポイントをスタートさせます。

C:\Windows\system32>netsh wlan start hostednetwork

ホストされたネットワークが開始しました。

この時点で、「ネットワーク接続」にSSIDの名前とともに新しい接続(ここでは「ローカルエリア接続16 Microsoft Hosted Network Virtual Adapter 」という名前)が1つ増えていることを確認します。

4.WiMAXのアダプタを「インターネット共有」モードに設定します。

「ローカルエリア接続 Intel Centrino  WiMAX 6150」を右クリックしてプロパティを開き、共有のタブをクリックします。

「ネットワークの他の・・・許可する」をチェックして、ホームネットワーク接続から先ほど確認した Microsoft Hosted Network Virtual Adapter である「ローカルエリア16」を選択します。

OKを押してプロパティを閉じるとようやくWiFi経由でWiMAXが使えるようになります。

 

 

5.元に戻す場合

使い終わって、元に戻す場合は次の3点

  1. コマンドプロンプトから
    C:\Windows\system32>netsh wlan stop hostednetwork
  2. 共有プロパティからチェックをはずしてOKを押す。
  3. 「無効」設定したWiFiアダプタを再び「有効」に戻す。

 

 

 

 

 

Yokin's 2ch Browser を Windows ストアアプリで公開

2015年3月から2ch専用ブラウザに関して2ch運営元の方針変更により当アプリはWindowsストアから撤去する予定です。ご利用ありがとうございました。

Windows 8/RT のいわゆるストアアプリ (Windows Store App)のプログラミングから登録手続きまでの流れの学習を兼ねて作成しました。2chリーダーです。アプリストアで検索したところでは、いまだ2chリーダーは登録されていないようでしたので、気合を入れて作ってみました。Androidでは 2chMate にいつもお世話になっているので、仕様は2chMateを強く意識した作りにしました。

http://apps.microsoft.com/windows/ja-JP/app/yokins-2ch-browser/203ac772-a869-4c3d-ac09-0e5d9b4499a1

主な実装済みの機能は

  • 板一覧 -> スレッド一覧 -> 記

    事 までの UIをWindowsアプリの作法でナビゲーション

  • 各板のバナー画像を自動的に取得
  • URLからブラウザを起動
  • 書き込み内の画像リンクとYoutubeリンクのサムネイルを表示

近く実装したい機能は

  • スレッドのソート
  • 記事のソート機能(タグによる参照順など)
  • IDによるフィルター
  • 各種設定画面の追加

いずれ必要な機能は

  • Sync2ch 対応
  • 書き込みの機能

各所でお世話になっていることもあり、アプリ自体はフリーですが、Microsoft Advertising の広告を使用しています。Microsoftアプリストアを通じてダウンロード数や、ユーザーの反響などが得られるようなので、そちらを通じても、応援いただけたら機能追加の励みになります。

 

 

SONY VAIO DUO11の個人的評価

購入から1か月ほど経過したため簡単に個人的な評価を書いてみようと思います。

プロセッサ・メモリ

ネットの閲覧、動画・音楽の再生など通常の使用には問題なし。

ストレージ

オフィスを追加的に入れても空き容量は50GB。巨大な動画ファイルや大量の画像を保存するわけでもないので問題なし。

WiMAX

docomo LTEのXperia GXでテザリングして使う予定で、その保険としてWiMAXもつけました。携帯でのテザリングの不安定さに辟易した結果WiMAXを契約。オプションとしてつけておいて正解。LTEと比べると今や速度的にはそれほどアドバンテージがあるとはいなくなりましたが、接続の安定性は抜群。安心して使えます。ただIntelのWiMAXチップセットはWiFiと排他利用となるため、DUO11自身をWiMAX回線のアクセスポイントとして使うことができません。小型のUSB WiFiアダプターを追加すればできるかもしれませんが、未確認です。

USB WiFiアダプタを使ってWiMAXのアクセスポイントにする実験成功しました。

GPS×

Androidタブレットのノリでつけてみましたが、どうもGPSを使えるWindowsのアプリ(標準の地図プリを含む)がほとんどなく、宝の持ち腐れとなりました。

HDウェブカメラ×

これも、Androidタブレット的に、簡単にTwitterなどへ投稿するための写真が撮れたらと思って背面のカメラをつけてみましたが、本体が想像以上に重く、気軽に持ち上げて写真を撮る気も起らず、また、画質も最近の携帯についているものと比べるとありえないほど悪くがっかりでした。

キーボード○

個人的な趣味になりますが、英字キーボードを選択しました。おかげで、海外通販でキーボードカバーを入手して使うことができました。ご興味の方はAliexpressからどうぞ。

http://ja.aliexpress.com/wholesale?SearchText=SONY+VAIO+DUO11&catId=0

 

 

キーボードバックライト

こちらも、趣味の範囲と思い、省略。

拡張シートバッテリー○

かつてSONYストアで5000円で追加できた時期もあったようですが、私の時はそうではなく、また、巷で購入してもほとんど定価売り。ヨドバシカメラなら10%がポイントで戻ってくるので、結局あとで勝ったほうがお得ということで先延ばしにしました。DUO11を使い始めてからはやはりバッテリの持続時間は3時間前後といったところで、1日外にいるときなど、電源なしではとてもやってられません。そこで、シートバッテリですが、つけてさらに重く、背面のカメラで撮影とは、片手で支えるには重すぎの状態になりますが、それでも、フル充電で7時間前後の残り時間が表示されひとまず安心といったところです。日中の外出はあえてアダプターを置いて出ても大丈夫そうです。

 

 

Androidの非同期通知機能 Google Cloud Messaging (GCM) のサーバー側コードをC#で書いてみた

.NETの標準以外に Newtonsoft のJSON.NETを使用します。http://james.newtonking.com/pages/json-net.aspx
使い方は

    var container = new Google.GoogleCloudMessaging.Container {
        RegistrationIds = new String [] { "APA*******PQZQ" },
        Data = new { message = "Hello World!" }
    };
    var response = Google.GoogleCloudMessaging.Broadcast( "AIz************", container );

のようになります。

------------------------------

 

using System;
using System.Net;

using Newtonsoft.Json;

namespace Google
{
    public class GoogleCloudMessaging
    {
        public class Container
        {
            [JsonProperty("registration_ids")]
            public String[] RegistrationIds { get; set; }
            [JsonProperty("collapse_key", NullValueHandling=NullValueHandling.Ignore )]
            public String CollapseKey { get; set; }
            [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
            public object Data { get; set; }
            [JsonProperty("delay_while_idle", NullValueHandling = NullValueHandling.Ignore)]
            public Boolean? DelayWhileIdle { get; set; }
            [JsonProperty("time_to_live", NullValueHandling = NullValueHandling.Ignore)]
            public int? TimeToLive { get; set; }
            [JsonProperty("restricted_package_name", NullValueHandling = NullValueHandling.Ignore)]
            public String RestrictedPackageName { get; set; }
            [JsonProperty("dry_run", NullValueHandling = NullValueHandling.Ignore)]
            public Boolean? DryRun { get; set; }
        }
        public class Response
        {
            [JsonProperty("multicast_id")]
            public String MulticastId { get; set; }
            [JsonProperty("success")]
            public int Success { get; set; }
            [JsonProperty("failure")]
            public int Failure { get; set; }
            [JsonProperty("canonical_ids")]
            public int CanonicalIds { get; set; }
            [JsonProperty("results")]
            public Result[] Results { get; set; }
        }
        public class Result
        {
            [JsonProperty("message_id")]
            public String MessageId { get; set; }
            [JsonProperty("registration_id")]
            public String RegistrationId { get; set; }
            [JsonProperty("error")]
            public String Error { get; set; }
        }
        static public Response Broadcast(String API_KEY, Container container)
        {
            var request = WebRequest.Create("https://android.googleapis.com/gcm/send");
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(container);
            using( WebClient web = new WebClient()){
                web.Headers.Add("Authorization", "key=" + API_KEY );
                web.Headers.Add("Content-Type", "application/json");
                byte[] result = web.UploadData("https://android.googleapis.com/gcm/send", "POST", System.Text.Encoding.UTF8.GetBytes(json) );
                String resultString = System.Text.Encoding.UTF8.GetString(result);
                var response = Newtonsoft.Json.JsonConvert.DeserializeObject<Response>( resultString );
                return response;
            }
        }
    }
}

Windows Server 2008 R2 Hyper-V 2.0 上に CentOS 6.2をインストールする際のメモ

想定環境は:

一部始終はこちらの方のブログにわかりやすく解説されています。

http://sashiz.seesaa.net/article/253087250.html

 

ただし、これに加えて陥った問題ありました。ヘビーなファイル転送などで高負荷になった時にディスクドライバがエラーをはきアクセス不能になったり、カーネルパニックを起こしてしまったりが頻発しました。いろいろ対処法を探し回りましたが結局、やってみて効果が認められたのは kernel のオプション設定です。

情報はVMWareのKnowledge Baseで解説されています。

http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1006427

/boot/grub/brub.conf ファイルの kernel 行に1こと追加します。

今回の環境では clocksource=acpi_pm divider=10 (64bit 32bit 共)

kernel /vmlinuz-2.6.32-220.13.1.el6.x86_64 ro root=/dev/mapper/VolGroup-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD rd_LVM_LV=VolGroup/lv_swap SYSFONT=latarcyrheb-sun16 rhgb crashkernel=auto quiet  d_LVM_LV=VolGroup/lv_root  KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM  clocksource=acpi_pm divider=10

 注意)試行錯誤が続いていますが 私の利用している CentOS 6.2 x86_64 のカーネルは notsc をサポートしていない様子で、notscオプションを付けると別のデフォルトクロックを使用して動作するようでした。その結果再び悪夢のカーネルパニックに陥ってしまいました。したがって notsc オプションは意図的に除外することにしました。

 

注意更新)それでも高負荷時に kernel panic に陥るため再調整を行いました。clocksource オプションを指定しないとデフォルトで hyper-v の指定する hv_timesource が選択されていたところ、明示的に acpi_pm を指定しました。結果、SAMBAのファイル転送が敵的に早くなったのを確認。安定性についてはしばらく様子を見ます。

さらに、システムクロックが少しずつ早くなるという問題があります。少しずつなのですが、Linux上にSAMBAを立ち上げてADS認証などを設定しているとドメインコントローラと自国のずれが大きくなるとKerberos認証にエラーが変えるようになってしまい致命的です。

CentOS6のデフォルトでは ntp はインストールされてないようなので追加の必要あり。

yum install ntp

chkconfig ntpd on

service ntpd start

 

 原因と対策はまとめると次の通り

  • 最近のLinux Kernel ではシステムの割り込みタイマーが早すぎる
    → カーネルに  divider=10 オプション 
  • clocksource にhyper-vの hv_timesource が選択されていると不安定
    → カーネルに clocksource=acpi_pm オプション 
  • Hyper-V ホストとの時刻同期はGuest OSが5秒以上遅れた時だけ発動する
    →期待しすぎて陥るトラップですが、GuestOSの時計が進みすぎると誰も補正してくれないという仕様
    つまり正確を期すにはLinux OS上にNTPDを設定して、定期的に自分で時刻を合わせなさい。 

 

いまのところ落ちたりせずに動作しています。意外に、Hyper-VとLinuxの組み合わせの情報が多くないので、参考までに。

 

SoftbankでSIMフリーHTC Desireと最近のAndroid HTC Sense系 ROM(含備忘録)

海外で購入したHTC DesireにいろんなROMを焼いてしばらくこの話題から遠ざかっていました。気がつくとSoftbankからたくさんのAndroid端末が発売されており、その間に andglobal.softbank.ne.jp なる新しいAPNも設定されていました。

Softbankでの契約内容と使用可能なAPNの組み合わせについて 2ch でまとめられていたものを備忘録としてここにコピーしておきます。

 

◆パケットし放題S(3G) 

mailwebservice.softbank.ne.jp → ウェブ 

のみの接続であれば上限4,410円で定額 

open.softbank.ne.jp → PCダイレクト 

smile.world → PCダイレクト 

は上限が9,800円。 

andglobal.softbank.ne.jp → スマートフォン通信 

は青天井(未確認) 

 

◆パケットし放題フラット(iPhone、3G) 

ウェブ PCダイレクト分のみ定額になる。黒simフラット契約、銀simガラケー共に 

open.softbank.ne.jp → PCダイレクト 

smile.world → PCダイレクト 

mailwebservice.softbank.ne.jp → ウェブは定額範囲だったが、 

andglobal.softbank.ne.jp → スマートフォン通信 

に設定すると定額外で別枠だった。パケ死する恐れあり。 

Desireだとフラット契約を適用できたが、DHDでパケットし放題フラットを適用できなかったのも、 

APNとプランの設定で範囲で、初期DHD内でのAPNがandglobalな為、定額外扱いだからだと思われる。 

 

◆パケットし放題S forスマートフォン 

◆パケットし放題MAX forスマートフォン(受付終了) 

現時点(2011/5/10)では、銀Simでも緑Simでも 

mailwebservice.softbank.ne.jp → ウェブ(ガラケー扱い) 

open.softbank.ne.jp → PCダイレクト 

andglobal.softbank.ne.jp → スマートフォン通信 

smile.world → PCダイレクト 

のどれも定額内。 

(緑sim用の機種ならandglobal、銀sim用の機種Xシリーズならopenに接続されるが、契約はどちらでも同じ。) 

 

◆パケットし放題フラット forスマートフォン(2011/05/01より) 

・SoftBankスマートフォン(Xシリーズの場合、最新の販売履歴がXシリーズであること)のみ、お申し込みいただけます。 

・国内でご利用のS!メール[MMS]送信・読出料、ウェブ・PCサイトブラウザ(PCメールを含む)・PCサイトダイレクト(Xシリーズ専用)通信料が定額の対象となります。 

スマホ用プランでパケットし放題MAX/Sと同様となので 

mailwebservice.softbank.ne.jp → ウェブ(ガラケー扱い) 

open.softbank.ne.jp → PCダイレクト 

andglobal.softbank.ne.jp → スマートフォン通信 

smile.world → PCダイレクト 

のどれも定額内と思われる。 

 

あと、私自身がひっかかった非常に重要な落とし穴として最近のHTC DesireのROMにははじめからSoftbankのAPN設定が含まれていますが、中に andglobal な設定しかないROMがあり、こういったROMを焼いてAndroidが起動、アカウントでサインインすると、以前のバックアップとアプリのダウンロードをすべくネットに自動的に接続に行きます。この間有無を言わさず通信が始まりますからどこのAPNに接続しているか未確認の状態では非常に危険です。

/system/etc/apns-conf.xmlの内容を十分確認するか、SIMを抜いた状態で初期設定を行うなど気を使う必要があります。

皆様もご注意ください。

台湾でプリペイドSIM - 遠傳電信 FarEasTone編

今回台湾で4日ほど滞在する際に、我がHTC Desire(UK版SIMフリー)でパケット通信すべくプリペイドSIMを購入しました。

手続きについてはこちらのサイトを参考にさせて頂きました。

http://blog.nabe.jp/archives/000211.html

台北駅新光三越裏の店舗カウンターでプリペイドSIMを購入したいと伝えると、1日100NTD 3日間250NTD 5日間350NTDのプランがあるとのこと。手続きにはパスポートのほかにもう一つ日本での身分証明書が要求されましたので私の場合は運転免許証を使いました。

登録手続きから開通まで1時間かかるという説明を受けましたが実際には手続きをしていろいろ説明を聞いているうちに開通したようですぐに利用可能でした。

作業の手順としてはSIMを携帯に刺して、ネットワークの選択でFarEasToneを選択、その後アクセスポイントの設定でAPNを internet になっていたものを f3prepaid に変更。あとは携帯を再起動すると完了です。携帯上でアクティベーションなどの作業はありませんでした。

購入したNT$350は5日間の無制限3Gデータ通信用で、音声通話用のクレジットは含まれていませんでした。ただし電話番号はありますからSMSの受信、音声通話の着信は可能です。

ちなみに、私は中国語はカタコトですが、店頭では英語が通じるスタッフが一人いたためお陰でほぼ英語だけで滞り無くコミュニケーション可能でした。