跳到主要內容

Java 實現 JavaScript 的 encodeURIComponent

參考文章:encodeURIComponent and decodeURIComponent in Java

很多時候需要將字串轉成 UTF-8 編碼後的ascii,特別是在 Web 端的輸出
Java 裡的內建 Class URLEncoder 裡的 encode 方法看起來雖然像是為此產生
少部分沒那麼嚴謹的教學也會說用這個方式就能編譯 UTF-8
但之後可能會碰到意料之外的問題
雖然大部分的文字編碼結果都沒有問題,但極少部分的符號編碼卻跟JavaScript的不同
最明顯的就是 space,空白用Java的 URLEncoder 會編譯成 + 而不是 %20
在 Java 端產生的網址字串可能就因此不能作用

看起來兩邊的實作必定有一邊做錯了
但翻開規格卻會發現雖然實作有出入,但都符合各自的定義
原來 Java 的 URLEncoder 是用在 HTML Form element 上
還記得 form element 的 encrypt 屬性能設定為 application/x-www-form-urlencoded 嗎?
form 在部分符號的編譯上,跟網址或其他 Web規格有那麼一點點差異
當然如果你很確定你的需求並不包含罕見符號,也可以全程用 URLEncoder 處理

Java 端要完全實現 JavaScript 的 encodeURIComponent 需要一些特殊處理
參考文章中的做法是做法裡我比較喜歡的一種,做法如下:

public static String encodeURIComponent(String input) {
    String ALLOWED_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()";

    int l = input.length();
    StringBuilder o = new StringBuilder(l * 3);
    try {
        for (int i = 0; i < l; i++) {
            String e = input.substring(i, i + 1);
            if (ALLOWED_CHARS.indexOf(e) == -1) {
                byte[] b = e.getBytes("utf-8");
                o.append(getHex(b));
                continue;
            }
            o.append(e);
        }
        return o.toString();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return input;
}

private static String getHex(byte buf[]) {
    StringBuilder o = new StringBuilder(buf.length * 3);
    for (int i = 0; i < buf.length; i++) {
        int n = (int) buf[i] & 0xff;
        o.append("%");
        if (n < 0x10) {
            o.append("0");
        }
        o.append(Long.toString(n, 16).toUpperCase());
    }
    return o.toString();
}

decodeURIComponent 個人認為相較之下沒什麼在 Java 端實作的必要
因為傳到後端可以用 UTF-8 的編碼讀取進來或有其他的替代方式
如果真的有需求不建議用參考文章的 decode 作法
經測試,該作法在decode沒有encode過的文字會碰到問題,行為與JavaScript端並不一致
如果你對效能沒有非常強烈的要求,建議作法可以改用ScriptEngine處理

public static String decodeURIComponent(String encodedURI) {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
try {
Object eval = engine.eval("decodeURIComponent(\"" + encodedURI + "\")");
return eval.toString();
} catch (ScriptException e) {
return "";
}
}

這個方式簡單、而且可以確保結果跟前端一致
實際上在前端的 encodeURIComponent 也可以這樣執行
目前我使用他的 encodeURIComponent 還沒碰上問題,所以會採用效率較佳的這個版本

留言