HttpRequestのパラメータに小細工をしたい時にどうするか(・ω・)?

今日は趣味方面からのネタ。
HttpRequest.Form、HttpRequest.QueryStringで取得できる値について、クライアントから送られてきた値ではなく、何かしらの前処理で書き換えた値を使用したい時にどうするか(・ω・)?


どんなニーズかと言えば、リクエスト/レスポンスのエンコーディングにSjift_JISを使って、特定キャリアの端末から送られてきた絵文字(Unicode Private Use Area)について、内部の処理ではUnicodeを0x1000ずらした値に変換して使いたいとき、とか、そんな時の話。
絵文字の削除をしたい時もかな(・ω・)?

携帯の絵文字

ちょっと背景説明。
携帯サイトを.NETで、っという話もあまり聞かないので、この辺はPHPなんかで携帯サイトを作っている人の方が詳しい話ですが。


DoCoMoauSoftbankの3キャリアについて、全てUTF-8を使用すれば.NET上(他言語も)の文字コードの扱いに問題は無いんですが、UTF-8は携帯での扱いに色々と問題があったりして(´д`;)
携帯での扱いに都合の良いSjift_JISを使おうとすると、絵文字の文字コードの扱いに問題が発生という感じで(´д`;;)
要するに、auSoftbankの絵文字については、キャリアのSjift_JIS Unicode対応表と、Encoding.GetEncoding( "Shift_JIS" )が一致しないので、それへの対処がなにかしら必要となるという話。


SoftbankについてはUTF-8の方が都合がよいのでそうするとして、それでもauとの衝突が起こるので、文字コードを0x1000ずらして衝突しないようにするか、標準のエンコード処理ではなく、キャリアの定義通りのエンコードを自前でやるか。


っで、この処理をどこに書くかというのが今日の本題(・∀・)
1つの案としては、Presentation Framework上で値をバインドする時(IPostBackDataHandler.LoadPostData()、ModeiBinderとか)という案がありますが。
この方法だと個別に対応が必要だったり、Framework外での処理には対応出来ないのでイヤンな感じ。
そこで、HTTPリクエストを取得する部分に細工を入れられないかと考えるわけです。


っで、参考までに、まずは.NET以外ではどんな風にリクエストの書き換えが出来るのか見てみます(・∀・)

PHP

PHPの場合、リクエストの値についてはこんな荒技が使えるので。

<?php
    foreach( $_GET as $key => $value )
    {
        $_GET[ $key ] = convert( $value );
    }

    foreach( $_POST as $key => $value )
    {
        $_POST[ $key ] = convert( $value );
    }
?>

さすがPHPさん、パネェっすΣ(゜Д゜;)
他の言語にはなかなか出来ないことを平然とやってのける。


っで、$_GET、$_POSTの値の書き換えが可能なので、絵文字の特殊処理も対応が簡単ですね。
もっとも、エンコード処理って基本的にバイト配列をチマチマ操作する処理なので、パフォーマンスとかを考えたらApacheのモジュールで行う方が良いという話もありますが(´д`)

Java

Javaの場合、Filterでリクエスト/レスポンスの差し替えが可能なので、こんなFilterとHttpServletRequestWrapperを作れば対処が可能です。

public class MobileFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        final HttpServletRequest req = (HttpServletRequest)request;

        // PC/携帯(キャリア毎)に応じてエンコーディング決定
        String encoding = ...;

        // APサーバ、decode-strictの設定に応じて必要な処理を行う
        // request.setCharacterEncoding( encoding ) or 一端"ISO-8859-1"にしておいて後で自前処理
        //
        // Jetty(GAE)用
        // request.setAttribute( "org.mortbay.jetty.Request.queryEncoding", encoding );

        // ラッパー作成
        HttpServletRequest requestWrapper = new MobileResquestWrapper( req, encoding );

        // 処理
        chain.doFilter( requestWrapper, response );

        // 絵文字、カナ変換、空行削除、encodeURL()とか、出力用の小細工が必要な場合は
        // responseもラッパーを作って処理する
...
    }
}
public class MobileResquestWrapper extends HttpServletRequestWrapper {

    private String encoding;

    public MobileResquestWrapper(HttpServletRequest request, String encoding)
    {
        super( request );
        this.encoding = encoding;
    }

    @Override
    public String[] getParameterValues(String name)
    {
        // ここでsuper.getParameterValues( name )した値を小細工
...
    }

    @SuppressWarnings("unchecked")
    @Override
    public Map getParameterMap()
    {
        // この辺も若干小細工
...
    }
...
}

.NET

.NETの場合、HttpRequest.FormやHttpRequest.QueryStringは読み取り専用ですし、Javaのような差し替えはできません。
Request.Filterの差し替えは可能ですが、ここでの小細工はQueryStringには反映されません。


っで、どうしたものかと思案して、Request.ContentEncodingを差し替えるという手段を考えてみました(・ω・)

public class CustomEncoding : Encoding
{
    private readonly Encoding encoding;

    public CustomEncoding(Encoding encoding)
    {
        this.encoding = encoding;
    }

    public override int GetByteCount(char[] chars, int index, int count)
    {
        return this.encoding.GetByteCount( chars, index, count );
    }

    public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
    {
        return this.encoding.GetBytes( chars, charIndex, charCount, bytes, byteIndex );
    }

    public override int GetCharCount(byte[] bytes, int index, int count)
    {
        return this.encoding.GetCharCount( bytes, index, count );
    }

    public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
    {
        int length = this.encoding.GetChars( bytes, byteIndex, byteCount, chars, charIndex );

        // ここでchars、lengthを小細工する
        // 元々のencoding、キャリアによって、charsの中身を書き換える
        // 絵文字削除の場合はlengthも減らす

        return length;
    }

    public override int GetMaxByteCount(int charCount)
    {
        return this.encoding.GetMaxByteCount( charCount );
    }

    public override int GetMaxCharCount(int byteCount)
    {
        return this.encoding.GetMaxCharCount( byteCount );
    }
}

こんなエンコーディングを作って、

public class Global : System.Web.HttpApplication
{
    public Global()
    {
        this.BeginRequest += OnBeginRequest;
    }

    private void OnBeginRequest(object sender, System.EventArgs e)
    {
        Request.ContentEncoding = new CustomEncoding( Encoding.GetEncoding( "Shift_JIS" ) );
...
    }
}

こんな風にしてContentEncodingを差し替える事により、リクエストの書き換えを行うという小細工。
一応、この方法でHttpRequest.Form、HttpRequest.QueryStringの値の操作はできたんですが、さて(・ω・)?

注意点1

Web Formsで実験したときに、IEとかではCustomEncodingによる変換が上手く動作するけど、iモード HTML シミュレータを使うとCustomEncodingが使用されないという現象に遭遇して。
なにかと原因を調査してみたら、ブラウズ定義ファイル(%WINDIR%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers\docomo.browser)で定義されているpreferredRequestEncodingのせいでした(´д`;)

preferredRequestEncoding/preferredResponseEncodingが設定されている場合、Pageクラス内でHttpRequest.ContentEncoding/HttpResponse.ContentEncodingの置き換えが行われるようで。
しょうがないので、下記のようなブラウズ定義ファイルをアプリケーションに追加して、標準の設定を無効化するようにしました。

<browsers>
  <browser id="DocomoOverride" parentID="Docomo">
    <identification>
      <userAgent match="^DoCoMo/" />
    </identification>
    <capabilities>
      <capability name="preferredRequestEncoding"  value="" />
      <capability name="preferredResponseEncoding" value="" />
    </capabilities>
  </browser>
</browsers>

ブラウズ定義ファイルもな〜(´・ω・`)
標準でキャリア判別の仕組みがある反面、定義ファイルの自前メンテは必要だし。
この辺のデフォルト設定も把握しておかないと、ちょっとはまるかも。

注意点2

Form、QueryStringのHttpValueCollectionは、最初のアクセス時に作成されてその値が保持されます。
なので、その前にContentEncodingの差し替えを行わないと、処理の適用は行われません。


…っというか、このやりかたってどこまで合法なんだろうね(´・ω・`)
まあ、ケータイの世界なんてバッドノウハウの固まりなので、色々と小細工が必要になるのは仕方がないんですが…。
さっさとWebKit一色な世界にならないかな:p


っで、入力についてはこんなんで処理するとして。
出力についてはHttpResponse.Filterの差し替えで処理できるので、キャリア間コンバータ、絵文字レンダーあたりを作れば、複数キャリア・マルチエンコーディング・キャリア間絵文字変換対応のサイトが作れますね(・∀・)