這裡介紹在 Java 程式中,如何使用 java.net.URLEncoderjava.net.URLDecoder 對網址進行百分比編碼與解碼。

有時常在處理網址的人應該會發現某些網址在從瀏覽器複製下來之後,就會變成一些百分比加上十六進位碼的奇怪網址,而貼回去瀏覽器又變正常了,就個就是網址 URL 百分比編碼的問題,這裡介紹如何以 Java 程式來處理網址的百分比編碼與解碼。

網址 URL 百分比編碼

RFC 3986 標準中規範一些 URI 的保留字元(reserved characters),若網址中出現這些保留字元時,就必須把這些保留字元轉為百分比編碼的表示方式,例如:!#:& 等。

百分比編碼的規則就是把字元的 ASCII 碼以十六進位表示,前方再加上一個百分比符號,例如井字號 # 的 ASCII 碼是 0x23,所以經過百分比編碼轉換之後,就是 %23

若是遇到非 ASCII 的字元(例如中文字),則會將資料以 UTF-8 的編碼方式表示成一連串的位元組序列,再將每個位元組以百分比編碼的方式進行編碼。

關於網址的百分比編碼介紹,可以參考維基百科的 Percent-encoding 說明。

平常最常會遇到的網址百分比編碼的地方就是瀏覽器的網址列,當我們在用 Google 搜尋後,若想要把網址複製起來,有時候就會遇到這種編碼問題:

Google 搜尋

雖然在瀏覽器的網址列中看到的中文字是正常顯示的,但是如果將這串文字複製起來,貼在一般的文字編輯器時,就會出現這樣經過編碼的 URL 網址:

https://www.google.com.tw/search?q=%E4%B8%AD%E6%96%87

原本網址的中文兩個字,在這裡變成了 %E4%B8%AD%E6%96%87,就這是因為瀏覽器在使用者複製網址時,自動把網址經過一道百分比編碼轉換了,事實上這才是標準的網址寫法。

HTML 的 form 表單在用 GET 或 POST 等管道送出資料時(content-typeapplication/x-www-form-urlencoded),其欄位名稱以及資料內容也會經過百分比編碼,不過在編碼規則上有一些小改變(請參考 W3C 網站),最常見的差異就是原本的空白字元會被編碼成 %20,而在這裡就會轉為加號 +

這個差異在瀏覽器的網址列就可以時常看到,當我們在 Google 搜尋含有空白的關鍵字時,網址會變成這樣:

Google 搜尋

在 HTTP 的 GET 參數中的空白會被瀏覽器轉換為加號 +,所以 Google 搜尋 ubuntu linux 的網址會變成這樣:

https://www.google.com.tw/search?q=ubuntu+linux

簡單來說,在網址中問號 ? 左邊的空白會轉換成 %20,而右邊的空白則會轉換成 +

Java 處理網址百分比編碼與解碼

Java 本身的 java.net.URLEncoderjava.net.URLDecoder 就可以處理網址的百分比編碼與解碼,以下是編碼的範例程式碼:

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class URLEncode {
  public static void main(String[] args) {

    // 待編碼的網址
    String url = "http://www.gtwang.org/目錄?var1=中文&var2=spa ce";

    try {
      // 進行 URL 百分比編碼
      String encodedURL = URLEncoder.encode(url, "UTF-8");

      // 輸出結果
      System.out.println(encodedURL);

    } catch (UnsupportedEncodingException e) {
      // 例外處理 ...
    }
  }
}

javac 編譯:

javac URLEncode.java

執行:

java URLEncode
http%3A%2F%2Fwww.gtwang.org%2F%E7%9B%AE%E9%8C%84%3Fvar1%3D%E4%B8%AD%E6%96%87%26var2%3Dspa+ce

這是解碼的範例程式碼:

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class URLDecode {
  public static void main(String[] args) {

    // 待解碼的網址
    String encodedURL = "http%3A%2F%2Fwww.gtwang.org%2F%E7%9B%AE%E9%8C%84%3Fvar1%3D%E4%B8%AD%E6%96%87%26var2%3Dspa+ce";

    try {
      // 進行 URL 百分比解碼
      String url = URLDecoder.decode(encodedURL, "UTF-8");

      // 輸出結果
      System.out.println(url);

    } catch (UnsupportedEncodingException e) {
      // 例外處理 ...
    }
  }
}

javac 編譯:

javac URLDecode.java

執行:

java URLDecode
http://www.gtwang.org/目錄?var1=中文&var2=spa ce

實務上我們可以將編碼與解碼的函數寫成獨立的包裝函數,這樣會比較方便,另外在解碼時,要考慮網址被重複編碼兩次以上的的狀況,以下是一個較完整的範例程式碼。

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public class URLEncodeDecode {
  public static void main(String[] args) {
    String url = "http://www.gtwang.org/目錄?var1=中文&var2=spa ce";

    String encodedURL = encode(url);
    System.out.println("Encoded URL: " + encodedURL);

    String decodedURL = decode(encodedURL);
    System.out.println("Decoded URL: " + decodedURL);

  }
  // 百分比解碼函數
  public static String decode(String url) {
    try {
      String prevURL = "";
      String decodeURL = url;
      while(!prevURL.equals(decodeURL)) {
        prevURL = decodeURL;
        decodeURL = URLDecoder.decode( decodeURL, "UTF-8" );
      }
      return decodeURL;
    } catch (UnsupportedEncodingException e) {
      return "Error: " + e.getMessage();
    }
  }
  // 百分比編碼函數
  public static String encode(String url) {
    try {
      String encodeURL = URLEncoder.encode( url, "UTF-8" );
      return encodeURL;
    } catch (UnsupportedEncodingException e) {
      return "Error: " + e.getMessage();
    }
  }
}

參考資料:DZoneericsk.netStackOverflow