java.net.URLDecoder зависит от кодировки исходного файла?

Я столкнулся с особой проблемой. Мой сервлет получает строку в кодировке урлен, и из журнала я могу сказать, что эта строка верна.

Я пытался с этой строкой:

"test+%F0%9F%98%8E+1+%E2%99%A7+%E2%99%A2+%E2%99%A1+%E2%99%A4+%E3%80%8A"

что является следующим:

"test  1 ♧ ♢ ♡ ♤ 《"

Однако, когда я запускаю тест, я получаю тот же результат, что и на моем сервере:

"test ? 1 ? ? ? ? ?"

Сбрасывая шестнадцатеричные коды, которые я получаю

00: 74 65 73 74 20 3F 20 31  20 3F 20 3F 20 3F 20 3F | test ? 1  ? ? ? ? 
10: 20 3F -- -- -- -- -- --  -- -- -- -- -- -- -- -- |  ?                

Где я ожидал:

00: 74 65 73 74 20 F0 9F 98  8E 20 31 20 E2 99 A7 20 | test ... . 1 ... 
10: E2 99 A2 20 E2 99 A1 20  E2 99 A4 20 E3 80 8A -- | ... ...  ... ...

Теперь для "интересного" немного. Это происходит на моем сервере и в Eclipse IDE, но если я затем сохраню исходный файл в UTF-8, URLDecoder вернет правильные данные! Это не помогло на моем сервере, хотя.

1: я не могу понять, как это может быть, URLDecoder должен прослушивать запрошенную кодировку. 2: мне очевидно нужна замена для java.net.URLDecoder, если это делает это, это в корне сломано. Какие-либо предложения?

Тестовый код:

public class URLDecoderTest {
    public static void main(String[] args) {
        String reqMsg = "test+%F0%9F%98%8E+1+%E2%99%A7+%E2%99%A2+%E2%99%A1+%E2%99%A4+%E3%80%8A";
        System.out.println("reqMsg      : " + reqMsg);
        try {
            reqMsg = URLDecoder.decode(reqMsg, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("reqMsg      : " + reqMsg);
        System.out.println(HexTools.dump(reqMsg));
        System.out.println("Expected (fixed):");
        System.out.println("00: 74 65 73 74 20 F0 9F 98  8E 20 31 20 E2 99 A7 20 | test ... . 1 ... ");
        System.out.println("10: E2 99 A2 20 E2 99 A1 20  E2 99 A4 20 E3 80 8A -- | ... ...  ... ...");
    }
}

Примечание. HexTools принадлежит Mobicents: http://code.google.com/p/mobicents/source/browse/trunk/commons/src/main/java/org/mobicents/commons/HexTools.java?r=21908

Изменить: глядя на источник для URLDecoder.decode, он использует новую строку (байты, 0, pos, enc) для декодирования байтов. По какой-то причине это не удается, однако для юникода новая String(bytes, 0, pos) работает нормально.

Есть ли ошибка в классе Java StringCoding, что он автоматически возвращается к кодировке "по умолчанию", независимо от того, что ему передано? Метод декодирования, вызываемый String, является статическим, и он устанавливает запрашиваемую кодировку в другом статическом методе перед вызовом декодирования, которое затем будет использовать эту статическую. Другими словами: это не потокобезопасно!!!

Обновление: у меня были проблемы практически со всеми уровнями моих реализаций. Например, символ Emoji (4-байтовые символы utf-8) вызывал проблемы в MySQL. Я получил от него символы, обозначенные осциллами, даже если для него было установлено значение utf8.

Заключительное замечание: Часть проблемы, или воспринимаемая проблема на самом деле, была вызвана неправильным использованием HexTools.dump(String), класса, созданного для обработки двоичных данных, где даже символы String содержали данные только в младшем байте.

Для дальнейшего использования вызов к HexTools.dump должен был быть:

        System.out.println(HexTools.dump(reqMsg.getBytes("UTF-8")));

с блоком catch для UnsupportedEncodingException, перемещенным вниз, чтобы покрыть эту строку, конечно. Делая это, возвращает шестнадцатеричный фрейм, идентичный ожидаемому.

2 ответа

Решение

Этот код работает как ожидалось:

import java.io.IOException;
import java.net.URLDecoder;

public class Dump {
  public static void main(String[] args) throws IOException {
    String reqMsg = 
         "test+%F0%9F%98%8E+1+%E2%99%A7+%E2%99%A2+%E2%99%A1+%E2%99%A4+%E3%80%8A";
    String decoded = URLDecoder.decode(reqMsg, "UTF-8");
    // UTF-16
    for (char ch : decoded.toCharArray()) {
      System.out.format("%04x ", (int) ch);
    }
    System.out.println();
    // UTF-8
    for (byte ch : decoded.getBytes("UTF-8")) {
      System.out.format("%02x ", 0xFF & ch);
    }
  }
}

Тем не менее, вы можете потерять информацию здесь:

System.out.println

Приведенный выше PrintStream выполнит операцию транскодирования (потенциально с потерями). Из документации:

Все символы напечатаны PrintStream преобразуются в байты с использованием кодировки символов платформы по умолчанию.

Во многих системах Java использует устаревшую унаследованную кодировку.

Может также случиться так, что ваш контейнер сервлета неправильно настроен. Не уверен, верно ли это для последних версий, но Tomcat исторически использовал ISO-8859-1 для кодирования URL.

HexTools.dump должен ошибаться. Это передано String = Текст Unicode. Так как же он может сбрасывать байты? Кроме использования кодировки платформы по умолчанию, вероятно, Windows ANSI.

Попробуйте что-то вроде:

System.out.println(Arrays.toString(reqMsg.getBytes(StandardCharsets.UTF_8)));

Вы не увидите вопросительный знак (0x3F == 63).

Другие вопросы по тегам