誤り : JavaScript のオブジェクト型は参照型ではないというお話

コンピュータ科学の分野における 「参照型」 (reference type) というのは、「何らかの値を参照するデータ型」 のことだと思っていたのですが、実際の意味は 「参照によってのみアクセス可能なデータ型」 のようです。 そんなわけで前者が正しい定義だと思って書いたこの記事は間違っています、ごめんなさい。 JavaScript のオブジェクト型が参照型 (コンピュータサイエンス用語) である、ということはさておきこの記事内容は間違っているのでご注意ください。 もうちょっとちゃんとまとめてから書き直したいと思います。

JavaScript の型は、大別するとプリミティブ型と参照型に分けられる。 そしてオブジェクト型は参照型である」 と言われることが多々あります (そしてその説明は Java 経験者などにとってわかりやすいと思われます)。 例えば以下のような web ページでそう述べられています。

そういえば、『JavaScript 第 5 版』 にもそう書いていましたね。

しかし、JavaScript (ECMAScript) の言語仕様 (ECMA-262) によると、「オブジェクト型は参照型である」 という言説は正しくありません *1

JavaScript のオブジェクト型を参照型だと思っていてもさほど悪影響はないと思いますが、一応仕様上は違うんですよー、ってことを JavaJavaScript を対比しながらまとめておきます。 この記事を読んだ貴方は、今後 「JavaScript のオブジェクト型は参照型なんだよ」 なんて言わないようにしてくださいね!!

*1:「オブジェクトの代入は参照渡し」 と書かれていることもありますが、それは 「参照型」 以上におかしいです。

続きを読む

Java で Base64 エンコード, OAuth, JSON を扱うためのライブラリ WSCUtils を公開しました

JavaTwitter クライアントを作ろうと思ったときに、とりあえず OAuth や JSON を扱えるようにする必要があったのでライブラリを作ってみました。 既に twitter4j とかあるんで需要はない気はしますが興味のある方はどうぞ。

フィードバックなど頂けると嬉しいです。

続きを読む

組合せ (コンビネーション) を求めるプログラム

今日の TopCoder SRM において 組合せ (コンビネーション; いわゆる nCm と書くやつ) を求める必要があったものの、ぱっと処理を書くことができなかったので反省を込めてメモを。

組合せを求める

組合せ nCm を求める式は以下のようになります。

nCm = \frac{n!}{m!(n-m)!} = \frac{ n \cdot (n-1) \cdots 1}{\{ m \cdot (m-1) \cdots 1 \}\{ ( n - m ) \cdot ( n - m - 1) \cdots 1 \}} = \frac{ n \cdot (n-1) \cdots ( n - m + 1)}{ m \cdot (m-1) \cdots 1 }

簡単に実装できそうな感じですが、分子と分母をどういう順番で掛ければ良いかでちょっと悩んでしまいました。 分子と分母を別々に計算して最後に分子を分母で割る、という方法では分子および分母の値があっという間に大きくなってしまいますし、1 個ずつ分子を分母で割ってから掛け合わせる、という方法では丸め誤差が発生してしまうし。。

で、どうしたらいいんだろう、と悩んだんですが、前 (数の大きい方) から掛けるんじゃなくて、後ろのほうから分子、分母を順番に計算していけば問題ないんですね。 Java で書くとこんな感じでしょうか。

/**
 * 組合せ (nCm) を求めるメソッド.
 * n は任意の非負整数, m は 0 以上 m 以下の整数.
 * 値によってはオーバーフローする可能性がある.
 */
long calcCombination( int n, int m ) {
    if( n < m || m < 0 ) {
        throw new IllegalArgumentException( "引数の値が不正です ( n : " + n + ", m : " + m + ")" );
    }
    long c = 1;
    m = ( n - m < m ? n - m : m );
    for( int ns = n - m + 1, ms = 1; ms <= m; ns ++, ms ++ ) {
        c *= ns;
        c /= ms;
    }
    return c;
}

Java における文字列とバイナリ列の相互変換についてと OAuth のパーセントエンコードの方法

Java で文字列を扱うのはあまり慣れておらず、文字列をパーセントエンコードするのにちょっとてこずったので軽くメモを。

文字列 (String オブジェクト) とバイナリ列 (byte 型配列) の相互変換

Java において、文字列を任意のエンコーディングエンコードしてバイナリ列を得るには、String#getBytes( Charset ) メソッド を使用します。

// "あいうえお" という文字列が UTF-8 エンコードされたバイナリ列
byte[] encodedStr = "あいうえお".getBytes( Charset.forName( "UTF-8" ) );

また、バイナリ列 (byte 型配列) を文字列に戻す場合には、String クラスのコンストラクタ String( byte[], Charset ) を使用します。

String str = new String( encodedStr, Charset.forName( "UTF-8" ) );

パーセントエンコード (The OAuth 1.0 Protocol 仕様)

The OAuth 1.0 Protocol (RFC5849) の仕様に合うように、文字列をパーセントエンコードします。 パーセントエンコードというのは、URL エンコードに用いられるように、エスケープすべき文字がある場合に、そのバイナリ列を 1 バイト毎に '%' + XX (XX は、バイト値を 16 進数表示したもの) の形式で表現するものです。

文字列をパーセントエンコードするには、まず文字列をエンコードしたバイナリ列を取得し、バイナリ列に対して処理を行います。 バイナリ列の取得は、上で述べた方法でできます。 また、バイナリ列に対して処理を行う際、byte 型の可変長配列が欲しいところですが、java.io.ByteArrayOutputStream クラス をその用途に用いることができます。

パーセントエンコードを行う機能を提供するクラスのサンプルコードを以下に示します。

package info.vividcode.oauth;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする
 * 機能を提供するクラス
 * @author nobuoka
 */
public class OAuthEncoder {
    
    /** "0123456789ABCDEF" の ASCII バイト列 */
    static final private byte[] BS = { 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70 };
    /** 指定のバイトをパーセントエンコードする必要があるかどうかの真理値を格納した配列 
     * (インデックスがバイト値に対応. ただし最上位ビットが 1 のものは含まない) */
    static final private boolean[] NEED_ENCODE = new boolean[ 0x7F ];
    
    // NEED_ENCODING の初期化
    static {
        for( int i = 0; i < NEED_ENCODE.length; i++ ) {
            // a(97)-z(122), A(65)-Z(90), 0(48)-9(57), -(45), .(46), _(95), ~(126)
            if( ( 65 <= i && i <= 90 ) || ( 97 <= i && i <= 122) || 
                    ( 48 <= i && i <= 57 ) || i == 45 || i == 46 || i == 95 || i == 126 ) {
                NEED_ENCODE[i] = false;
            } else {
                NEED_ENCODE[i] = true;
            }
        }
    }
    
    /**
     * can't instantiate from this class
     */
    private OAuthEncoder() {}
    
    /** 
     * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする.
     * パーセントエンコードの対象になるのは 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', '~' を除く全ての文字である. 
     * @param str パーセントエンコードの対象文字列
     * @return str をパーセントエンコードした文字列
     */
    static public String encode( String str ) {
        String encodedStr = null;
        ByteArrayOutputStream os = null;
        try {
            os = new ByteArrayOutputStream();
            for( byte b : str.getBytes( Charset.forName( "UTF-8" ) ) ) {
                if( b < 0 || NEED_ENCODE[b] ) {
                    // "%"
                    os.write( 37 );
                    // 上の 4 ビット
                    os.write( BS[ ( b >> 4 ) & 0x0F ] );
                    // 下の 4 ビット
                    os.write( BS[ b & 0x0F ] );
                } else {
                    os.write( b );
                }
            }
            encodedStr = os.toString();
        } finally {
            try {
                // close する意味はないが, 一応
                if( os != null ) os.close();
            } catch( IOException err ) {
                err.printStackTrace();
            }
        }
        return encodedStr;
    }
    
}

次のように使うことができます。

String parcentEncodedStr = OAuthEncoder.encode( "あい" ); //=> "%E3%81%82%E3%81%84"

ECMAScript 5 の strict mode でグローバルオブジェクトを得るひとつの方法

ECMAScript 5th edition (JavaScript 系統のコア部分をまとめた言語みたいなもの) には strict mode というものがあり、strict mode では関数コード (Function Code) 内の this キーワードの参照先オブジェクトが、strict mode でない場合と異なる可能性があります。 例えば、関数式を即実行する Immediate Function Pattern (((function () { /* ... */ })(); という形式のもの。 特に正式名称があるわけではないと思いますが、『JavaScriptパターン ―優れたアプリケーションのための作法』 でこの名前が使われていたのでこの名前で呼んでいます)) と呼ばれる手法を使う際、strict mode でなければ関数コード内で this キーワードがグローバルオブジェクトを参照しますが、strict mode では未定義値 (undefined) になります。

詳しくは下記参照。

その対策として、id:think49 さんのところでは以下のように new Function() を利用する方法が紹介されています。

関数コードでは new Function() を利用します。

"use strict";

(function () {
  console.log(new Function('return this')()); // [object Window]
  console.log(Function('return this')());     // [object Window]
})();

グローバルコード (Global Code) からグローバルオブジェクトを渡す方法

別の方法としては、Immediate Function の引数としてグローバルオブジェクトを渡してやる方法もあります。 グローバルコードでは this キーワードはグローバルオブジェクトを参照するため、グローバルコードにおける Immediate Function に対しては、引数としてグローバルオブジェクトを渡してやることができます。

"use strict";

(function ( globalObj ) {
    // 引数 globalObj としてグローバルオブジェクトへの参照を受け取る
    print( globalObj ); // [object Window]
    // strict mode: Immediate Function 内の this キーワードは undefined
    print( this );      // undefined
})( this ); // グローバルコードにおける this はグローバルオブジェクト

どこででも使えるという意味では new Function() を使う手法の方が便利ではあるのかなーと思いますが。

new を不当に貶める陰謀と JavaScript におけるクラスの継承構造の話

私は陰謀論者じゃないですし JavaScriptnew 演算子が大好きなわけでも大嫌いなわけでもないです。 念のため。 本記事は Hiraku さんが書かれた下記記事への言及です。

new 演算子は使うな!?

newを封印するべき4つの理由」 でも new がいかに糞であるかが書かれていますし、その記事からも言及があるように Crockford さんが書かれた書籍 『JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス』 でも new 演算子は Bad Parts に分類されています。

new 演算子が忌避される理由はいろいろあるみたいですが、Hiraku さんの記事では

  • new を書き忘れてもコンストラクタ関数が実行されて意図しない動作になる
  • 継承を考慮する必要がある
  • 何をやっているのかわかりにくい
  • 読みづらい

という点が指摘されています。

常に new 演算子を使うことが最良の選択だとは思っていませんが、ことさら new 演算子を貶める言説も好きじゃないのでちょっと反論しておこうかな、というのがこの記事の主題です。

各継承方法のオブジェクトの関係図

はじめに、議論の土台として、純粋なプロトタイプ継承と、クラスの継承構造を持ち込んだ場合の継承方法について、オブジェクトの関係図を示します。

純粋なプロトタイプ継承

まずは JavaScript の純粋なプロトタイプ継承の形を見てみましょう。

ECMA-262 (5th edition) を満たしている JavaScript 処理系なら、Object.create メソッドで簡単にプロトタイプ継承が可能です。 Hiraku さんの記事で定義されている object 関数は、この Object.create メソッドと同じようなものです。

var baseObj = {
  name: "ベースオブジェクト", 
  printName: function(){ print( this.name ) }
};
// Object.create メソッドでプロトタイプ継承する
var subObj = Object.create( baseObj, { name: { value: "サブオブジェクト" } } );
// 使ってみる
subObj.printName() // サブオブジェクト

このような純粋なプロトタイプ継承による継承構造を図で表すと次のようになります。 非常に単純ですね。


new 演算子とクラスの継承構造

次に、プロトタイプ継承と new 演算子によるオブジェクト生成の機構を利用して、クラスの継承構造を JavaScript で実現してみましょう。 詳しい実現方法は 「JavaScript におけるクラスの作成と継承」 の記事で述べましたので、ここでは詳細には立ち入りません。 基本的な思想としては、インスタンス (に相当するオブジェクト) で全てのインスタンス変数を保持し、プロトタイプ継承の先祖のオブジェクトでインスタンスメソッドを定義する、という形式です。 コンストラクタ関数オブジェクトがクラスそのものを表しています。

少しややこしいですが、インスタンス生成には new 演算子、クラスの継承構造の生成には関数定義、という風に明確に分離しています。 また、クラスを表現するオブジェクト (コンストラクタ) とインスタンスメソッドを定義するオブジェクトが分かれているため、コンストラクタのプロパティによってクラスメソッドやクラス変数を実現できます。

Hiraku さんの記事におけるクラスの継承構造

最後に、Hiraku さんの記事に書かれている方法でクラスの継承構造を実現した場合のオブジェクトの関係を図に示します。

JavaScript における純粋なプロトタイプ継承の継承構造に、無理やりクラスとインスタンスという概念を持ち込んでいるように私は思います。 以下のような問題点があるように思います。

  • クラスとインスタンスの区別が明確でない : クラスの継承もインスタンスの生成も完全に同じ機構で実現している。 同じプロトタイプ継承によって実現しているとしても、クラスとインスタンスという概念を持ち込むのであれば、区別するようにしたほうが良い
  • クラスメソッドとインスタンスメソッドのを区別が明確でない : クラスを表すオブジェクトとインスタンスメソッドを定義するオブジェクトが同一なので、クラスメソッドとインスタンスメソッドを区別できない
  • 明示的なコンストラクタ呼び出しが必要 : new 演算子によるオブジェクト生成の場合は暗黙的にコンストラクタ関数が呼び出されましたが、この方法では明示的にコンストラクタの呼び出しが必要です

4 つの new 批判に対する反論

それでは 「newを封印するべき4つの理由」 に対して反論してみます。

new を書き忘れてもコンストラクタが実行されてしまう問題
var Person = function Person( name ) {
    this.name = name;
};
Person.prototypte.sayHello = function sayHello() {
    print( "Hello, I'm " + this.name + "." );
};

// new を忘れて実行
var tarou = Person( tarou );

確かに、new を書き忘れてもコンストラクタ関数が実行されてしまいます。 それは認めましょう。 ですが、これの何が問題なんでしょうか? new を書き忘れることってありますか? 書き忘れたとして、それが発見困難なバグに繋がりますか?

私の感覚では、new の書き忘れは引数の個数不足での関数呼び出しと同じレベルの問題であると思います。 引数の個数や型チェックが必要なら関数内でチェックをするように、チェックする必要があるならコンストラクタ関数内で this が何を指しているのかチェックすればいいだけです。

new の書き忘れで時間を浪費するようなプログラマが居るならば、その人はプログラマに向いていないので転職を勧めてあげましょう。

継承を考慮する必要があるという問題

『この書き方は、object関数をインライン展開したのと同義です…。それならいっそのこと、常にobject関数を使うようにした方が楽だと思いませんか?』 とありますが、クラスの継承を行う関数を定義すればいいだけのことです。 例えば 「JavaScript におけるクラスの作成と継承」 の記事では、クラスの継承のための extend という関数を定義しました。

何をやっているのかわかりにくいという問題

JavaScript自体はプロトタイプベースで作られているくせに、newを使うとクラスベースみたいな書き方をする必要があります。』 って、new メソッドはプロトタイプ継承を応用してクラスの継承構造を JavaScript に持ち込むためのものなので当然でしょう。 クラスの継承構造を持ち込むのであれば、インスタンス化に new 演算子を使うことは他の言語との類似性から言ってもわかりやすい と思います。 もちろんクラスの継承構造を使わないのに new 演算子を使うことは混乱の元になると思いますが。

読みづらいという問題

個人的にはオブジェクトリテラルの中に関数式を書くのは好きじゃないのでどっちかというと

var Animal = {
  name: "動物"
, breathe: function(){alert("すーはー")}
, sayName: function(){alert(this.name)}
};

よりも

var Animal = function Animal( name ) {
    this.name = name;
};
Animal.prototype.breath = function breath() {
    print( "すーはー" );
};
Animal.prototypte.sayName = function sayName() {
    print( this.name );
};

の書き方が好きなんですが。 まあでも多くの人は前者の書き方の方が好みなんだろうなー、とは思います。

ていうかこの name ってなんなんでしょう。。 クラス変数?

そもそも new 演算子のどの部分を批難しているのか?

というわけで 4 つの問題点に対する反論 (になってるかどうかは微妙ですが) を書いてきましたが、そもそも new を封印する目的がいまいちよくわかりません。

  • 本当に、ただ純粋に new 演算子を使わないようにすべき、という主張?
  • JavaScript にクラスの継承構造を持ち込むな、という主張?

本当に、ただ純粋に new 演算子を使わないようにすべき、という主張は JavaScript に限らず Java などでもあります。 new 演算子によるインスタンス化は柔軟性に欠ける、というものです。 インスタンス化のための API としてユーザーにはファクトリーメソッドを提供し、new 演算子は表から見えないようにするべきであるという主張であり、『Effective Java 第2版 (The Java Series)』 などに書かれています。

しかし、今回の場合は単純に new 演算子を表から隠すだけでなく継承構造も変えていますし、単純に new 演算子を表から隠せ、という主張ではないように思います。

それでは、JavaScript にクラスの継承構造を持ち込むな、という主張なのかというとそれも違います。 Hiraku さんの記事では 「クラス(に相当するオブジェクト)を作る」 という表現が出てきていますし、クラスの継承構造を実現したいように思えます。

クラスの継承構造が不要な場面ではわざわざクラスの継承構造なんか作らずに、JavaScript の基本的な継承であるプロトタイプ継承を使いましょう!」 という主張なら納得するのですが、Hiraku さんの記事では結局のところ何が目的なのかよくわからなくなっており、「俺俺オブジェクト指向な気がする」 と言われても仕方ないかなー、と思います。

new 演算子を使うとわかりやすい? わかりにくい? (追記)

ちょっと重要なことなので追記。

本記事の上のほうでも 「クラスの継承構造を持ち込むのであれば、インスタンス化に new 演算子を使うことは他の言語との類似性から言ってもわかりやすい」 と述べましたし、id:teramako さんも 「僕はnew演算子は好きです。書き忘れが発生してorzすることはあるけど。何故好きかというと、コードを読んでいてインスタンスを作成していることが明確に分かるから」 と仰っています。 一方で、Hiraku さんは 「JavaScript自体はプロトタイプベースで作られているくせに、newを使うとクラスベースみたいな書き方をする必要があります。中でやっていることと、外側から見えるインターフェースが違いすぎる」 という立場です。

もちろん、どちらが間違っている、ということはありません。 とはいえどちらかの立場をとらなければいけないのであれば、「誰にとってわかりやすくすべきか」 という視点を取り入れるべきでしょう。

クラスという概念を取り入れる以上、クラスの設計者とクラスのユーザーという 2 人の視点で考える必要があります。 これはしばしば同一人物でありますが、同一人物ではない可能性もあります。 そして、クラスというものは クラスのユーザーにとってわかりやすい ものであるべきだと思います。 クラスの設計・実装者は当然 JavaScript に詳しいはずですので、プロトタイプ継承がどうとか、そういう点は問題なく認識しているはずです。 一方、クラスの使用者はクラスを提供されているだけなので、「クラスに見えるけどプロトタイプ継承で実装されている」 という点は認識していないかもしれません。 また、そのように認識させる必要もありません。 なぜなら 「クラス」 を提供しているのですからクラスのように扱えればいいのです。

例えば、Person というクラスを提供するとしましょう。 このクラスは人物を表すもので、人物の名前を引数にとってインスタンス化するものとしましょう。 こう聞くと、クラスのユーザーはほぼ間違いなく以下のようなコードでインスタンス化できるものと期待するはずです。

var tarou = new Person( "田中 太郎" );

「クラスをプロトタイプ継承したオブジェクトを作って、自分で初期化関数を呼んでください」 などと言われるとどうでしょう?

var tarou = Object.create( Person );
tarou.init( "田中 太郎" );

という感じでしょうか。 お世辞にも良いインターフェイスだとは言えません。 ではオブジェクトの生成と初期化を行う関数を Person オブジェクトに追加してみますか?

// ファクトリーメソッド
Person.newInstance = function newInstance( name ) {
    Object.create( this );
    this.init( name );
};
// インスタンス化
var tarou = Person.newInstance( "田中 太郎" );

すっきりしました。 これなら良いでしょう。 しかしこうなるともはや従来の手法でいい気がします。 クラスの実装方法に違いはありますが、上でも言ったようにクラスの設計・実装者には十分な知識があるはずなのでどちらの実装方法でもあまり問題にはなりません。 それよりも、ユーザーが標準的な使用方法でクラスを使用できるかどうか、が重要なのです。

標準的な使用方法で使えるのかどうかという点において、インスタンス化についてよりももっと大きな問題があります。 instanceof 演算子を実直に使えないという問題です。 (そもそも instanceof 演算子は使うべきではない、という主張もありますが、それはさておき。) ユーザーは、Person クラスをインスタンス化した tarou は、以下の式で true になると期待するでしょう。

tarou instanceof Person; //=> true

しかし、Hiraku さんとこの方法では

tarou instanceof type( Person ); //=> true

などとしなければいけません。 大規模なフレームワークとしてクラス機構を提供するのであれば新しい書き方を導入するのもひとつの手ではありますが、単一、または少数個のクラスを提供する際にこのような書き方をクラスのユーザーに強要するのは無理というものです。 様々なクラスの提供者がそれぞれ独自に type 関数のような関数を提供したらどうでしょう? 使いにくいですよね。 これが 「俺俺オブジェクト指向」 と呼ばれてしまったひとつの理由でしょう。

プロトタイプ継承という機構を隠してしまったことの弊害

しかしながら、JavaScript が 「クラスの継承構造のような機構」 を提供し、プロトタイプ継承という機構を隠してしまったがための弊害もあります。 Hiraku さんが仰るように 「実際の挙動 (プロトタイプ継承) と見た目 (クラスの継承やインスタンス化) が異なっている」 というものです。

JavaScriptnew Date() のようにインスタンス化を行ったことがある人はたくさんいると思いますが、プロトタイプ継承という継承方法を知っている人は思っている以上に少ないのではないか、と思います。 プロトタイプ継承という機構が裏に隠れてしまっているために、(誰かが作ったクラスを使うだけなら) プロトタイプ継承について勉強する必要が無いためです。 特に JavaScriptプログラマだけでなく、web デザイナーなど本職がプログラマじゃない人にも使われていますので、ちゃんと勉強してない人も多いはずです。

そのような 「クラスを使ったことはあるけどプロトタイプ継承はよく知らない」 というような人がクラスを設計・実装しようとするとよくわからなくなって混乱してしまいます *1。 そして、「JavaScript のクラスってよくわからない」 ということになってしまうのではないかと思います。

この点はどうしようもないかなーという気がするのですが、とりあえず言えることは 「他の言語と同じようにクラスを使えるからといってろくに勉強せずクラスの設計・実装をしようとすると痛い目を見るので、まず JavaScript の勉強をしましょう」 ということですね。

私の主張

最後に JavaScript におけるオブジェクトに関して私の主張をちょこっとしておきます。

new 演算子がしばしば攻撃の対象になるのは、他の言語との類推から JavaScript においてもなんでもかんでも new 演算子を使ってオブジェクト生成するような人がいるからだと思います。 他の言語、例えば Java などではオブジェクトを生成するために基本的 *2new を使います。 一方、JavaScript では new 演算子が必要となる場面はそれほど多くありません。

JavaScript は弱い動的型付け言語であり、使い捨てのオブジェクトをオブジェクトリテラルで生成することができます。 なんらかのオブジェクトを継承したい場合、クラスの継承などしなくてもプロトタイプ継承によりオブジェクトそのものを継承することができます。 特に web サイト上で動く JavaScript は使い捨てのオブジェクトを使うことが多く、クラスの継承構造が必要になることはあまり多くないでしょう。

一方で、クラスの継承構造を利用すると便利な場面もあります。 同じメソッドを持ち、異なるデータを保持する多くのオブジェクトを使いたい場面では、クラスの継承構造が役に立ちます。 そういうところでは、クラスの継承構造をつくり、new 演算子 ((new じゃなくてファクトリーメソッドなどを使うべき、という議論もありますが、それはまた別のお話ということで。)) を使ってインスタンス化する、ということをすれば良いでしょう。

使いどころを考えて、使うべきところで使うべき方法をとるというのが重要です。

*1:Hiraku さんが仰っている 「よくわからない」 という指摘はこの点なんじゃないかと思います。

*2:ファクトリーメソッドなどもありますが。。

WEBrick の ProcHandler で DELETE メソッドや PUT メソッドを扱えるようにする

WEBrick で HTTP サーバーを作ってリクエストを WEBrick::HTTPServlet::ProcHandler オブジェクトで処理する、ということをしていたのですが、DELETE メソッドを投げると 405 エラーが返ってきてしまうという問題に直面しました。 レスポンスボディは以下のような感じでした。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
  <HEAD><TITLE>Method Not Allowed</TITLE></HEAD>
  <BODY>
    <H1>Method Not Allowed</H1>
    unsupported method `DELETE'.
    <HR>
    <ADDRESS>
     WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18) at
     localhost
    </ADDRESS>
  </BODY>
</HTML>

Ruby 1.9.2-p180 に添付されている webrickWEBrick::HTTPServlet::ProcHandler は、デフォルトでは HEAD メソッドと GET メソッドと POST メソッドと OPTIONS メソッドしか処理しないようです。

DELETE メソッドと PUT メソッドも受け取って欲しかったので、以下のように WEBrick::HTTPServlet::ProcHandler クラスにインスタンスメソッドを追加して対応しました。 こういうときはオープンクラスであることは非常に便利でいいですね。 (オープンクラスじゃなくてもサブクラスを作ったりして対応はできますが。。)

##
# WEBrick::HTTPServlet::ProcHandler が DELETE メソッドや PUT メソッドを認識しないため
# 認識するように変更するための拡張
module WEBrick
  module HTTPServlet

    class ProcHandler < AbstractServlet
      alias do_PUT    do_GET
      alias do_DELETE do_GET
    end

  end
end