コンテキストパラメータのエンコード方法、SEO的には以前の方が良かった?

コンテキストパラメータのエンコード方法が変わるようです - らくさん で書いたように、5.0.16からコンテキストパラメータのエンコード方法がかわりましたが、新しい方法だと日本語の文字は $xxxx にエンコードされるので検索エンジンはキーワードとして認識してくれないよね、たぶん。以前のエンコード方式は、標準的なURLエンコードをベースにスラッシュなどの扱いだけカスタマイズしたものだったから、それならキーワードとして認識されてたはず。標準的なURLエンコードだとサーブレットコンテナによっては問題があったから変更したみたいなんですが。


あーあと、ブラウザのURL欄に直接入力できなくなったのも微妙に残念かも。


新しいコンテキストパラメータのエンコーダはIoCサービスとして実装されたから差し替えることはできるはずなんで、差し替えてみるかなぁ。

やってみた (2008-11-19追記)

org.apache.tapestry5.services.URLEncoder インターフェースを実装したクラス MyURLEncoderImpl を作って、AppModule.java内で AliasOverrides の設定をする。MyURLEncoderImpl の実装は org.apache.tapestry5.internal.services.URLEncoderImpl をベースにしました。


フレームワークのソースに全く手を入れずにこんなものまで差し替えできるのが、Tapestryの凄いところのひとつです。


AppModule.java:

    public static void contributeAliasOverrides(
            Configuration<AliasContribution> configuration) {

        configuration.add(AliasContribution.create(URLEncoder.class, new MyURLEncoderImpl()));
    }


MyURLEncoderImpl.java:

import java.io.UnsupportedEncodingException;
import java.util.BitSet;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.tapestry5.ioc.internal.util.Defense;
import org.apache.tapestry5.services.URLEncoder;

public class MyURLEncoderImpl implements URLEncoder {

    static final String ENCODED_NULL = "$N";
    static final String ENCODED_BLANK = "$B";

    /**
     * Bit set indicating which character are safe to pass through (when encoding or decoding) as-is.
     */
    private final BitSet safe = new BitSet(128);

    private final URLCodec percentDecoder = new URLCodec();

    {
        markSafe("abcdefghijklmnopqrstuvwxyz");
        markSafe("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        markSafe("01234567890-_.:");
    }

    private void markSafe(String s) {
        for (char ch : s.toCharArray()) {
            safe.set((int) ch);
        }
    }

    public String encode(String input) {
        if (input == null)
            return ENCODED_NULL;

        if (input.equals(""))
            return ENCODED_BLANK;

        byte[] bytes;
        try {
            bytes = input.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }

        boolean dirty = false;

        boolean percentEncoded = false;

        int length = bytes.length;

        StringBuilder output = new StringBuilder(length * 3);

        for (int i = 0; i < length; i++) {
            int b = bytes[i] & 0xff;

            switch (b) {
                case '$':
                    output.append("$$");
                    dirty = true;
                    continue;
                case '/':
                    output.append("$S");
                    dirty = true;
                    continue;
            }

            if (safe.get(b)) {
                output.append((char) b);
                continue;
            }

            output.append(String.format("%%%02x", b));
            percentEncoded = true;
        }

        return percentEncoded ? output.insert(0, "%09").toString() : dirty ? output.toString() : input;
    }

    public String decode(String input) {
        Defense.notNull(input, "input");

        if (input.equals(ENCODED_NULL))
            return null;

        if (input.equals(ENCODED_BLANK))
            return "";

        boolean percentEncoded = input.startsWith("%09");

        if (percentEncoded) {
            input = input.substring(3);
        } else if (input.charAt(0) == 0x09) {
            input = input.substring(1);
        }

        boolean dirty = false;

        int length = input.length();

        StringBuilder output = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            char ch = input.charAt(i);

            if (ch == '$') {
                dirty = true;

                if (i + 1 < length) {
                    switch (input.charAt(i + 1)) {
                        case '$':
                            output.append('$');
                            i++;
                            dirty = true;
                            continue;
                        case 'S':
                            output.append('/');
                            i++;
                            dirty = true;
                            continue;
                    }
                }

                throw new IllegalArgumentException(String.format(
                        "Input string '%s' is not valid; the '$' character at position %d should be followed by another '$' or 'S'.",
                        input, i + 1));
            }

            output.append(ch);
        }

        try {
            return percentEncoded ? percentDecoder.decode(output.toString()) : dirty ? output.toString() : input;
        } catch (DecoderException e) {
            throw new IllegalArgumentException("Input string '%s' is not valid.", e);
        }
    }
}

パラメータの先頭に %09 を埋め込んでるのは、Formコンポーネントを使ったときの t:ac パラメータにも対応するため。%09 がデコード済みであればパス経由で渡されたコンテキストパラメータで、デコードされてなければフォーム経由で渡されたと判定してます。あまり奇麗な対応方法ではないけど、奇麗に対応しようとすると結構大変なので。