Fork me on GitHub

2010/07/14

はてな
JScript.NETはMicrosoftが.NET Framework向けに拡張したJavasSriptで、Common Language Runtime(CLR)上で動作するJavaScript実装です。
CLR上ではC#、VB.NET等が動作しますが、JScript.NETは言語がJavaScriptという事もあって実はC#やVB.NET等と同等に使えない機能がいくらかあります。
JavaScriptは動的言語です。オブジェクトにプロパティを生やしてメソッドにしたり、prototypeを弄ったり、eval()で関数を生成したりも出来ます。つまりコンパイルするとは言えど、型が動的に変えられる言語です。
その為、.NET Frameworkの機能の一つであるDelegateが使えません。.NET FrameworkのThreadはDelegateという関数型拘束によりスレッドを安全に呼び出せる様になっています。この関数型拘束に緩い拘束なJavaScript(JScript)の関数を渡す事は出来ないのです。
MSDNでもJScriptのDelegate利用制限が書かれています。
ThreadStart デリゲート

[JScript] JScript では、.NET Framework のデリゲートを利用することができます。ただし、独自に定義することはできません。

http://msdn.microsoft.com/ja-jp/library/system.threading.threadstart(VS.71).aspx
Delegate.CreateDelegate()というメソッドも存在しますが、こちらもJScriptからは利用出来ません。
じゃぁDelegateを引数に取るThreadはJScriptから作れないじゃないか!そう思っておられる方が殆どだと思います。

いえ、出来ます。

import System
import System.Reflection
import System.CodeDom.Compiler
import System.Threading
import Microsoft.CSharp

/**
 * createThread()
 * @param   f   thread function
 * @param   a   argument passing to function
 * @return  thread object
 */
var createThread = (function() {
    var source = [
        "using System.Threading;",
        "public class ThreadInvoker {",
        "  private Microsoft.JScript.Closure func;",
        "  private object arg = null;",
        "  public ThreadInvoker(Microsoft.JScript.Closure func, object arg) { this.func = func; this.arg = arg; }",
        "  private void invokerThread() { func.Invoke(null, new object[]{this.arg}); }",
        "  public Thread createInvoker() { return new Thread(invokerThread); }",
        "}",
    ].join("\n");

    var cp = CodeDomProvider.CreateProvider("CSharp");
    var cps = new CompilerParameters();
    cps.ReferencedAssemblies.Add("Microsoft.JScript.dll");
    cps.GenerateInMemory = true;
    var cr = cp.CompileAssemblyFromSource(cps, source);
    var asm = cr.CompiledAssembly;
    return function(f, a) {
        var tt : Type = asm.GetType("ThreadInvoker");
        var args = new System.Object[2];
        args[0] = f;
        args[1] = a;
        return tt.InvokeMember(
            "createInvoker",
            BindingFlags.InvokeMethod,
            null,
            Activator.CreateInstance(tt, args),
            null);
    };
})();
C#上で書かれたスレッドインボーカをメモリ内でコンパイルして、それにJScriptの関数、正しくはクロージャを渡します。
ここから先はCLRなので、どうとでも出来るはずでC#からMicrosoft.JScript.Closureのメソッドを叩く事も出来ます。
これを使えば
var th = createThread(function(v) {
    for (var n = 1; n <= 6; n++) {
        Thread.Sleep(200);
        print("thread" + n + " " + v);
    }
}, "foo");

th.Start();

Thread.Sleep(200);
print("main1");
th.Suspend();
Thread.Sleep(200);
print("main2");
Thread.Sleep(200);
print("main3");
th.Resume();
こんなコードで
thread1 foo
main1
main2
main3
thread2 foo
thread3 foo
thread4 foo
thread5 foo
thread6 foo
こんな動作をさせる事も出来ます。JavaScript(JScript)でスレッドが使えると色々とやりたくなってきます。JScriptでサーバアプリなんて事も簡単に出来そうですね。
ちなみに、このcreateThread()を使うと、ウェブサーバなんかも書けたりします。試しに書いてみたらコードの少なさ(createThreadは除いて)に少し驚きました。JScript.NET & C# ばんざい!
import System
import System.Reflection
import System.CodeDom.Compiler
import System.Threading
import Microsoft.CSharp

/**
 * createThread()
 * @param   f   thread function
 * @param   a   argument passing to function
 * @return  thread object
 */
var createThread = (function() {
    var source = [
        "using System.Threading;",
        "public class ThreadInvoker {",
        "  private Microsoft.JScript.Closure func;",
        "  private object arg = null;",
        "  public ThreadInvoker(Microsoft.JScript.Closure func, object arg) { this.func = func; this.arg = arg; }",
        "  private void invokerThread() { func.Invoke(null, new object[]{this.arg}); }",
        "  public Thread createInvoker() { return new Thread(invokerThread); }",
        "}",
    ].join("\n");

    var cp = CodeDomProvider.CreateProvider("CSharp");
    var cps = new CompilerParameters();
    cps.ReferencedAssemblies.Add("Microsoft.JScript.dll");
    cps.GenerateInMemory = true;
    var cr = cp.CompileAssemblyFromSource(cps, source);
    var asm = cr.CompiledAssembly;
    return function(f, a) {
        var tt : Type = asm.GetType("ThreadInvoker");
        var args = new System.Object[2];
        args[0] = f;
        args[1] = a;
        return tt.InvokeMember(
            "createInvoker",
            BindingFlags.InvokeMethod,
            null,
            Activator.CreateInstance(tt, args),
            null);
    };
})();

import System.Net
import System.Net.Sockets

var listener = new System.Net.Sockets.TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
listener.Start();

while (true) {
    createThread(function(client) {
        var stream = client.GetStream();
        var buf = new System.Byte[client.ReceiveBufferSize];
        var bytesRead = stream.Read(buf, 0, buf.length);
        var str = [
            "HTTP/1.0 200 OK",
            "Content-Type: text/html",
            "",
            "<html><body>",
            "<b>hello world</b>: " + (new Date).toString(),
            "</body></html>",
        ].join("\r\n");
        var bytes = System.Text.Encoding.Default.GetBytes(str);
        stream.Write(bytes, 0, bytes.length);
        stream.Close();
        client.Close();
    }, listener.AcceptTcpClient()).Start();
}
マルチスレッドウェブサーバです。カッコイー!

2010/03/24

はてな
追記
os0xさんにjson2.jsで使われている手法である事を教えてもらいました
正しくはエスケープが必要。

以下フォーラムで議論されている内容から拝借
validating json unnecessarily is killing firefox - jQuery Forum
// Try to use the native JSON parser first
if (window.JSON && window.JSON.parse) {
    try {
           return window.JSON.parse( data );
    } catch (err) {
            jQuery.error( "Invalid JSON: " + data );
    }
} else {
    if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
        .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) {
        return  (new Function("return " + data))();
    } else {
        jQuery.error( "Invalid JSON: " + data );
    }
http://forum.jquery.com/topic/validating-json-unnecessarily-is-killing-firefox
この検証手順は
  • 使用可能な制御文字および改行を文字参照を"@"に置き換え
  • 文字列と定数を"["に置き換え
  • 全ての空白を削除
すると、その結果は
^[\],:{}\s]*$
という正規表現で表す事が出来るという物。
何個か試してみましたが、100%完璧な検証というよりも簡易的な物みたい。でも不正なスクリプト除けには使えそうな感じ。要検証

最近私が書くvimscriptではeval()を使ってJSONをパースさせてる物があるので、使えないかと思いサクっとvimscriptに移植してみた。
function! s:ValidateJSON(str)
  let str = a:str
  let str = substitute(str, '\\\%(["\\\/bfnrt]\|u[0-9a-fA-F]\{4}\)', '\@', 'g')
  let str = substitute(str, '"[^\"\\\n\r]*\"\|true\|false\|null\|-\?\d\+\%(\.\d*\)\?\%([eE][+\-]\?\d\+\)\?', ']', 'g')
  let str = substitute(str, '\%(^\|:\|,\)\%(\s*\[\)\+', '', 'g')
  return str =~ '^[\],:{} \t\n]*$'
endfunction

let json = substitute(join(readfile(expand('<sfile>')), "\n"), '.*\nfinish\n', '', '')
if s:ValidateJSON(json)
  echo "is valid json"
else
  echo "is invalid json"
endif

finish
{
    "testID": "2",
    
    "testResults": [
        
            {
                "testResultID": "2",
                "contactID": "1",
                "answers": [
                    
                            {
                                "questionID": "6",
                                "answer": "A",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "7",
                                "answer": "I do not know.",
                                "num_attempts": "0",
                            },      
                        
                            {
                                "questionID": "9",
                                "answer": "blah",
                                "num_attempts": "0",
                            }      
                            
                ]
            },
        
            {
                "testResultID": "4",
                "contactID": "231",
                "answers": [
                        
                ]
            },
        
            {
                "testResultID": "5",
                "contactID": "231",
                "answers": [
                    
                            {
                                "questionID": "6",
                                "answer": "d",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "7",
                                "answer": "hello.",
                                "num_attempts": "1",
                            },      
                        
                            {
                                "questionID": "9",
                                "answer": "This is just test posted text.",
                                "num_attempts": "1",
                            }      
                            
                ]
            }
        
    ]
}
サンプルでもvalidが出てる。
明日にでもeval()使ってるvimscriptに入れていこう。

こういう記事書くと、勝手に検証してくれる人がいて、最近はありがたい世の中になったもんだ...と勝手に期待。
Posted at 03:12 in ソフトウェア::javascript
Tagged as: javascript, regexp
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2009/10/13

はてな
twitter stream APIとは、twitterのステータス更新に対してキーワードでtrackしたり、あるグループ内に属するステータス更新をフィルタしたり出来るAPIなのだけど、実際にはchunkedなストリームが流れて来ているのであって、これを使ったWebアプリを作る際にはlong pollを使うのが良い。ただしクライアントサイドでjavascriptを処理する際に
  • サーバサイドでステータス更新をFIFOに溜め込む
  • クライアントからリクエストをブロック(long poll)しFIFOからステータスを送出する
  • クライアントサイドでlong pollを行い画面を更新する
  • 再度サーバへリクエストを投げる
を繰り返すのであれば、せっかくストリームなのにアプリサーバとの接続を切ってしまう事になる。出来ればクライアントからWebアプリもストリーミングとしたい。しかしながらサーバからのステータス更新を受け取るのであれば、JSON/JSONPなアクションが必要になる。
ここで使える!と思ったのがmultipart/mixedです。

multipart/mixedはboundaryで区切られたボディ部を繰り返し送出する際に用いられる仕組みで、実はXHR(XMLHttpRequest)はこのmultipart/mixedなレスポンスを受け取る事が出来る。
受け取るとは言っても、実際にはreadyStateが変化する...でしかないのだけど、これを扱うのに良いライブラリがある。
diggで使われているDUI.jsとStream.jsです。
digg's stream at master - GitHub

Alpha repository for DUI.Stream. When it stabilizes, it will be merged back into DUI

http://github.com/digg/stream
このstreamライブラリは内部でreadyStateが3になるのを待ち、タイマを張ってboundaryを監視するものです。つまり1 body部を扱う事になるので、使う際にはmime typeを指定する事になります。
var s = new DUI.Stream();
s.listen('text/html', function(payload) {
  ...
});
s.load('/push');
この仕組みを使って、twitterからのストリームをクライアントにmultipart/mixedで送出し、かつクライアントも接続を保持したままレスポンスを受け取るのです。接続を切らないので、ほぼタイムロス無しのリアルタイムと言って良いでしょう。
今日はこれをsinatraを使って実装してみました。出来れば一度動かしてみて下さい。これまでリクエストを繰り返し送っていたアプリケーションと比べてスムーズにデータを受け取っているのが分かって頂けるかと思います。
上記diggのライブラリを作業フォルダのstatic/jsフォルダ配下に置き、以下のファイル(fast-twitter-stream.rb)を作業フォルダ直下に置きます。
require 'rubygems'
require 'sinatra'
require 'json'  
require 'tweetstream'  
require 'pit'

# doesn't work on thin and webrick
set :server, 'mongrel'
set :public, File.dirname(__FILE__) + '/static'

config = Pit.get("twitter.com", :require => {
    "username" => "your username in twitter",
    "password" => "your password in twitter"
})

get '/' do
  <<HTML
  <html> <head>
    <title>Server Push</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
    <script type="text/javascript" src="/js/DUI.js"></script>
    <script type="text/javascript" src="/js/Stream.js"></script>
    <script type="text/javascript">
    $(function() {
      var s = new DUI.Stream();
      s.listen('text/javascript', function(payload) {
        var status = eval(payload);
        $('#content').append('<p>' + status.text + '</p>');
      });
      s.load('/push');
    });
    </script>
  </head>
  <body>
    <h1>Server Push</h1>
    <div id="content"></div>
  </body>
</html>
HTML
end

get '/push' do
  boundary = '|||'
  response['Content-Type'] = 'multipart/mixed; boundary="' + boundary + '"'

  MultipartResponse.new(boundary, 'text/javascript')
end

class MultipartResponse
  def initialize(boundary, content_type)
    @boundary = boundary
    @content_type = content_type
  end

  def each
    TweetStream::Client.new(config[:username], config[:password]).sample do |status|  
      yield "--#{@boundary}\nContent-Type: #{@content_type}\n(#{status.to_json})"
    end  
  end
end
元となったコードはyoupyさんの物を参考にさせて頂きました。

ブラウザから"http://localhost:4567/"にアクセスすると、もの凄い勢いでステータスが追加されていくのが分かるかと思います。しかも切断していないのでかなり高速です。
お試しの効果には個人差があります。

このmultipart/mixed、結構使えるなーと思いました。得にリアルタイムな画像ストリーミングとして送出したり、リアルタイム電光掲示板の様な物にも使えるかと思います。

今回のサンプルもgithubに置いてあります。
mattn's fast-twitter-stream at master - GitHub

fast twitter stream client using multipart/mixed.

http://github.com/mattn/fast-twitter-stream
よろしければご参考までに。

JavaScript & Ajax プロが教える“本当の使い方” JavaScript & Ajax プロが教える“本当の使い方”
MdN編集部
MdN / ¥ 2,940 (2009-07-31)
 
発送可能時間:在庫あり。

Posted at 01:06 in ソフトウェア::lang::javascript
Tagged as: javascript
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2009/09/07

はてな
個人的には一番使っていて無いとちょっと不便に感じる自作のグリモンといえば「Google Reader Full Feed」なのですが、最近メインのブラウザをGoogle Chromeに変えた事もあり、使えずにちょっぴり不便になってました。
しかしながらGoogle Chromeに移植するとなれば簡単には行かないだろう事が分かっていたので移植するのを躊躇していました。先日、はてなブックマーク数を表示するGoogle Chrome Extensionも作った事だし、少しは知識もついたので、ようやく重い腰をあげて作ってみました。
最初は移植を考えてましたが、結構元にしているLDR FullFeedのコードがまばらになっていてメンテナンス性も悪かったので、今回は元のコードを捨てて1から作り直しました。とはいっても中で使っている部品などはConstellationさんの物や、os0xさんの物を使わせて頂いています。感謝

画面キャプチャは以下みたいな感じです。
chrome-grff
操作感はFirefox版とほとんど同じになってます。
今のところ、残課題はSITEINFOをGearsを使ってキャッシュする事くらいだと思ってますが、もしよければ使ってみた感想など頂けると助かります。
ソースは例のごとく、githubに置いてあります。
mattn's chrome-grff at master - GitHub

google reader full feed for chrome

http://github.com/mattn/chrome-grff/tree/master
インストールは以下のリンクから...
chrome-grff.crx
追記
キーは g じゃなく、z ですので、お気をつけて。