IEでFunction.prototype.extendsというプロパティが使えない。

JavaScriptで継承を実現するために、

Function.prototype.extends = function(superObj) {
  for (var prop in superObj)
    this.prototype[prop] = superObj[prop];
};

このようなメソッドを作ってました。FireFoxで開発している分には全く問題なく動作していたのですが、IEでテストしてみたところエラーとなってしまいました。
で、調べてみたところ、下記のエントリで全く同じ問題を紹介してました。
IEでextendsって名前が使えない

MSのサイトに予約語の一覧がのってますが、「将来的に使用される」のところは要注意ですね。exportとかついつい使ってしまいそう・・・。
http://msdn.microsoft.com/ja-jp/library/cc391861.aspx

そもそも、ブラウザによって予約語まで違うとは思わなんだ。orz

JavaからコンパイルされてないGroovyを呼び出す方法(クラス、メソッド)

前回に続き、JavaからGroovyスクリプを実行する方法。今回は通常のクラスの、インスタンスメソッドを実行する方法について紹介。

  GroovyClassLoader loader = new GroovyClassLoader(this.getClass().getClassLoader());
  Class cls = loader.parseClass(new File("....name.groovy"));

  // 以降は、通常のJavaクラスの呼び出しと同じようにリフレクションする

ルーズステートメントと違い、ただスクリプトを読み込んだだけでは、runメソッドは実行できないようです。そこで上記例のように、GroovyClassLoaderを使ってクラスを読み込みます。
前回の例と同じように、....name.groovy のところを該当のスクリプトファイル名に置換すれば動作するはずです。その他、オプションなど詳しいことは、Javadochttp://groovy.codehaus.org/api/index.html)で確認してください。

JavaからコンパイルされてないGroovyを呼び出す方法(ルーズステートメント)

JavaからGroovyスクリプトを実行する方法。やり方はいくつかあるのですが、その中から比較的シンプルなやりかたを紹介。

  new GroovyShell().parse(new File("....name.groovy")).run();

これだけです。....name.groovy のところを該当のスクリプトファイル名に置換すれば動作するはずです。詳しいことは、Javadochttp://groovy.codehaus.org/api/index.html)で確認してください。ちなみに、Groovy1.5 と、Groovy1.6 ではそこそこ変更されているようでした。

また、実行されるスクリプトの中身についてですが、ルーズステートメントと、クラス定義の両方がある場合に、エラーが発生することがあるようます。というのは、Foo.groovyの中のルーズステートメントは、実行時にFoo.Mainメソッド内の処理とみなされるためです。つまり、Foo.groovyのスクリプト中に、Fooクラスの定義を書いている場合、Foo.Mainを実行するためにFooクラスが作成されるので、結果、クラスの定義が2つできてしまい、コンパイルエラーとなります。解決策としては、Foo.groovyのファイル名を変えるか、Fooクラスのクラス名を変えるかしかないと思います。。。

なお、実行に必要なライブラリですが、

  • antlr-2.7.6.jar
  • asm-2.2.jar
  • commons-cli-1.0.jar
  • groovy-1.5.6.jar
僕がためしたサンプルはこれだけあれば実行できました。

Socketに出力中に Connection reset by peer: socket write error

Javaでプロキシサーバのようなものを作っていたところ、クライアントへレスポンスを返している途中に、以下の例外が多発。


SocketException: Connection reset by peer: socket write error

プログラムはすごく簡単なサンプルを参考にしてたので間違ってるとは思えなかった。。。


String CrLf = "\r\n";

ServerSocket ss = new ServerSocket(9950);
Socket s = ss.accept();

StringBuffer b = new StringBuffer();
b.append("HTTP/1.1 200 OK" + CrLf);
b.append("Connection: close" + CrLf);
b.append("Date: Tue, 20 May 2008 18:02:41 GMT" + CrLf);
b.append("Content-Type: text/html;charset=UTF-8" + CrLf);
b.append("Transfer-Encoding: chunked" + CrLf);
b.append(CrLf);

OutputStream o = s.getOutputStream();
o.write(b.toString().getBytes());

InputStream contents = any....    ←なんらかのコンテンツ

for (int data = 0; (data = contents.read()) != -1; )
o.write(data);

o.flush();
o.close();
s.close();

が、うまく動いているサンプルと比べてみると「Transfer-Encoding: chunked」のところが違っている。うまく動いているサンプルは代わりに「Content-Length」ヘッダーがある。これは怪しそう、と思って調べてみたところ、以下のサイトに完璧な説明がありました。

http://www.cresc.co.jp/tech/java/Servlet_Tutorial/Att_01.htm

「Chunked Transfer-Encoding=厚切りエンコーディング」である、と。なるほど。以下のように修正したらバッチリ動きました。


String CrLf = "\r\n";

ServerSocket ss = new ServerSocket(9950);
Socket s = ss.accept();

StringBuffer b = new StringBuffer();
b.append("HTTP/1.1 200 OK" + CrLf);
b.append("Connection: close" + CrLf);
b.append("Date: Tue, 20 May 2008 18:02:41 GMT" + CrLf);
b.append("Content-Type: text/html;charset=UTF-8" + CrLf);
b.append("Transfer-Encoding: chunked" + CrLf);
b.append(CrLf);

OutputStream o = s.getOutputStream();
o.write(b.toString().getBytes());

InputStream contents = any....    ←なんらかのコンテンツ

byte[] bt = new byte[500];

for (int len = 0; (len = contents.read(bt, 0, 500)) != -1; ) {
o.write(Integer.toString(len, 16).getBytes()); ←←この部分を追加
o.write('\r');
o.write('\n');
o.write(data, 0, len);
}
o.write('0');

o.flush();
o.close();
s.close();

XMLHttpRequestオブジェクトでRedirectをハンドリングするには?(リダイレクトを拾う方法)

XMLHttpRequestオブジェクトから送信したリクエストに対し、サーバがリダイレクト応答(ステータスコード=302)を返したときのハンドリングってどうやればいいんだろうか?と悩んだのでその記録。

解決したかったのは、以下のような問題。

  1. XMLHttpRequestで送ったリクエストが、サーバで認証エラー(セッションタイムアウトなど)となる
  2. このときサーバは認証エラー(=401)を返すのではなく、ログインページへのリダイレクトを返す
  3. クライアント(XMLHttpRequest)は、リダイレクト応答をうけとり、自動的にリダイレクト先を追跡する
  4. 結果、クライアントはログインページのHTMLを取得する(このときステータスコード=200)
  5. しかし、そもそもクライアントはJSON形式のデータを期待しているためスクリプトエラーとなる

調べてみたところ、XMLHttpRequestはリダイレクト応答をうけとると何事もなかったかのようにリダイレクト先にリクエストを送信してしまうようだ。そのため、クライアント側ではリダイレクトされたかどうかが判別できない(ステータスコードは常に200)。これでは困るのでステータスコード以外について調べてみたところ、レスポンスヘッダーの取得はできるようだ。これは都合がいい。
というのは、今回問題が発生したWebアプリではXMLHttpRequestのレスポンスは『必ず』JSON形式(=text/plain)としている。なので、text/htmlのレスポンスが返されたら、それはリダイレクトとみなすことができる。
完全な解決策とはいえないけど、以下のコードで問題解決。

httpRequest.onreadystatechange = function() {
  if (httpRequest.readyState == 4 && httpRequest.status == 200) {
    var cType = httpRequest.getResponseHeader("Content-Type");
    if (cType.toLowerCase().indexOf("text/plain") == -1) 
      window.location.href = url;                                             ← ここで認証エラーとなったリクエストを再送→リダイレクト
    // 以下、省略

Rails2.0 + Lighttpd + CentOS5の環境構築での問題


Apache + lighttpd で Rails を動かす
を参考にして、Railsアプリの実行環境を構築していたところ、僕の環境ではなぜか問題が発生したのでメモ。僕の環境は、以下のとおり。

各アプリのインストールは上記サイトの通りにやって順調に行ってたのだが、いざlighttpdを実行しようとすると、

2008-05-11 15:37:35: (log.c.75) server started
2008-05-11 15:37:35: (mod_fastcgi.c.1029) the fastcgi-backend /var/lighttpd/demo/public/dispatch.fcgi failed to start:
2008-05-11 15:37:35: (mod_fastcgi.c.1033) child exited with status 9 /var/lighttpd/demo/public/dispatch.fcgi
2008-05-11 15:37:35: (mod_fastcgi.c.1036) If you're trying to run PHP as a FastCGI backend, make sure you're using the FastCGI-enabled version.
You can find out if it is the right one by executing 'php -v' and it should display '(cgi-fcgi)' in the output, NOT '(cgi)' NOR '(cli)'.
For more information, check http://trac.lighttpd.net/trac/wiki/Docs%3AModFastCGI#preparing-php-as-a-fastcgi-programIf this is PHP on Gentoo, add '
fastcgi' to the USE flags.
2008-05-11 15:37:35: (mod_fastcgi.c.1340) [ERROR]: spawning fcgi failed.
2008-05-11 15:37:35: (server.c.908) Configuration of plugins failed. Going down.
こんなエラーを吐いてlighttpdが終了してしまう。ネットで調べてもイマイチ原因がわからない。ならば development モードでやったらどうかと思って、Railsのプロジェクトディレクトリで以下のコマンドをたたく。

# ruby script/server lighttpd
するとあっさりと動いた。はて???

ということは、Railsで生成したconf/lighttpd.confを使えば良いのか、と思って /etc/lighttpd/lighttpd.conf を置き換えてみると、、、

動いた。

元のlighttpd.confと、Railsで生成したlighttpd.conf との違いは良く分からなかったが(設定内容はだいたい同じはずなのだが)、とりあえずやりたいことは実現できてるので良しとしよう。

静的HTMLで、インクルードを実現するためのJavaScript

ローカルディスク上に存在する静的HTMLで、外部ファイルをインクルードするためのスクリプト(つまり、XMLHttpRequestオブジェクトの代わり)。

以前にも同じようなエントリを書いたのですが(http://d.hatena.ne.jp/kenpoco/20080228/1204198090)、いくつか不具合があったので修正してます。

  • IE6,7に対応(前回はFirefox2のみ)。
  • インクルードするファイルの拡張子が.HTMLと.TXTで動作が異なっていたので、その対応。
  • インクルードした後で、その内容を変更するための仕組みを追加。

最近HTMLを作る機会が多いので自分で使ってみてるのですが、けっこう重宝してます。全ページに同じようなヘッダー部分がある場合なんかに、ヘッダー部分を1ファイルにまとめておけるので、その後の修正がかなり楽。

【使用例】
-----------------------
 インクルード部分
-----------------------
<div>
   <script type="text/javascript" >
      include("a.html");    ←HTML内のインクルードしたいところに記入
   </script>
</div>

-----------------------
 インクルードされるファイル(a.html)
-----------------------
<div style="border: solid 1px #000000;">hello;</div>

-----------------------
 インクルード結果(表示後にinnerHTMLで確認)
-----------------------
<div>
   <script type="text/javascript" >
   </script>
   <div style="border: solid 1px #000000;">hello;</div>  ←includeした内容が<script>タグの下のところに入る
</div>
【関数本体】
-----------------------
function include(filename, afterfunc) {

  include.seq = (include.seq)? include.seq + 1: 1;

  var id = new Date().getTime() + "-" + include.seq;
  var inc = document.createElement("iframe");

  inc.id = "inc-" + id;
  inc.src = filename;
  inc.style.display = "none";
  document.write("<span id=\"" + id + "\"></span>");
    
  var incfunc = function() {
    
    var s = (function() {
      var suffix = (n = filename.lastIndexOf(".")) >= 0 ? filename.substring(n): "default";
      if (suffix == ".html") return inc.contentWindow.document.body.innerHTML;
      if (suffix == ".txt") return inc.contentWindow.document.body.firstChild.innerHTML;
      if (suffix == "default") return inc.contentWindow.document.body.innerHTML;
    })();

    var span = document.getElementById(id);

    var insertBeforeHTML = function(htmlStr, refNode) {
      if (document.createRange) {
        var range = document.createRange();
        range.setStartBefore(refNode);
        refNode.parentNode.insertBefore(range.createContextualFragment(htmlStr), refNode);
      } else {
        refNode.insertAdjacentHTML('BeforeBegin', htmlStr);
      }
    };

    insertBeforeHTML(s.split("&gt;").join(">").split("&lt;").join("<"), span);
    document.body.removeChild(inc);
    span.parentNode.removeChild(span);
    if (afterfunc) afterfunc();
  };

  if (window.attachEvent) {
    window.attachEvent('onload', 
      function() {
        document.body.appendChild(inc); 
        inc.onreadystatechange = function() { if (this.readyState == "complete") incfunc(); };
      });
  }
  else {
    document.body.appendChild(inc);
    inc.onload = incfunc;
  }
}