Есть ли способ использовать UTF-8 с движком приложения?

Я ищу некоторое объяснение того, как движок приложения имеет дело с кодировкой символов. Я работаю над клиент-серверным приложением, где сервер находится в приложении.

Это новое приложение, созданное с нуля, поэтому мы используем UTF-8 везде. Клиент отправляет некоторые строки на сервер через POST, x-www-form-urlencoded. Я получаю их и повторяю их. Когда клиент вернет его, он будет ISO-8859-1! Я также вижу это поведение, когда POSTing в blobstore, с параметрами, отправленными как UTF-8, multipart/form-data encoded.

Для записи, я вижу это в Wireshark. Поэтому я на 100% уверен, что отправляю UTF-8 и получаю ISO-8859-1. Кроме того, я не вижу mojibake: кодированные строки ISO-8859-1 прекрасно подходят. Это также не проблема неправильного толкования Content-Type. Это не клиент. Что-то по пути правильно распознает, что я отправляю параметры UTF-8, но по какой-то причине конвертирует их в ISO-8859-1.

Я убежден, что ISO-8859-1 является кодировкой символов по умолчанию для сервлетов GAE. Мой вопрос в том, есть ли способ сказать GAE не конвертировать в ISO-8859-1 и вместо этого использовать UTF-8 везде?

Скажем, сервлет делает что-то вроде этого:

public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    resp.setContentType("application/json");
    String name = req.getParameter("name");
    String json = "{\"name\":\"" + name + "\"}";
    resp.getOutputStream().print(json);
}

Я попытался настроить кодировку символов ответа и запрос на "UTF-8", но это ничего не изменило.

Спасибо заранее,

Ответы

Ответ 1

Найден способ обойти его. Вот как я это сделал:

  • В качестве типа содержимого используется "application/json; charset = UTF-8". В качестве альтернативы, установите кодировку ответов на "UTF-8" (либо будет работать нормально, не нужно делать оба).

  • Base64 кодирует входные строки, которые не являются ASCII-безопасными, и поступают как UTF-8. В противном случае они, очевидно, преобразуются в ISO-8859-1, когда они попадают в сервлет.

  • Используется resp.getWriter() вместо resp.getOutputStream() для печати ответа JSON.

После all эти условия были выполнены, я, наконец, смог вывести UTF-8 обратно клиенту.

Ответ 2

Я вижу две вещи, которые вы должны сделать.

1) установить системные свойства (если вы используете его) в utf8 в appengine-web.xml

<system-properties>
    <property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
    <property name="file.encoding" value="UTF-8" />
    <property name="DEFAULT_ENCODING" value="UTF-8" />
</system-properties>

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

<env-variables>
    <env-var name="DEFAULT_ENCODING" value="UTF-8" />
</env-variables>

https://developers.google.com/appengine/docs/java/config/appconfig

2) укажите кодировку, когда вы устанавливаете тип содержимого, или она вернется к стандартным

Тип содержимого может включать в себя тип кодировки символов, используемый для пример, text/html; кодировка = ISO-8859-4.

Я бы попробовал

resp.setContentType("application/json; charset=UTF-8");

Вы также можете попробовать сценарий, который позволяет напрямую установить для него тип содержимого.

http://docs.oracle.com/javaee/1.3/api/javax/servlet/ServletResponse.html#getWriter%28%29
http://docs.oracle.com/javaee/1.3/api/javax/servlet/ServletResponse.html#setContentType(java.lang.String)

Для чего это стоит, мне нужно utf8 для японского контента, и у меня нет проблем. В любом случае, я не использую фильтр или setContentType. Я использую gwt и # 1 выше, и он работает.

Ответ 3

Это не относится к GAE, но если вы сочтете это полезным: я создал свой собственный фильтр:

В web.xml

<filter>
    <filter-name>charsetencoding</filter-name>
    <filter-class>mypackage.CharsetEncodingFilter</filter-class>
</filter>
    ...
<filter-mapping>
   <filter-name>charsetencoding</filter-name>
   <url-pattern>/*</url-pattern> 
</filter-mapping>

(поместите фрагмент, сопоставляющий фильтр, в начале сопоставлений фильтра и проверьте свой шаблон url.

И

public class CharsetEncodingFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        req.setCharacterEncoding("UTF-8");
        chain.doFilter(req, res);
        res.setCharacterEncoding("UTF-8");
    }

    public void destroy() { }

    public void init(FilterConfig filterConfig) throws ServletException { }
}

Ответ 4

Обходной путь (безопасный)

Ничто из этих ответов не сработало для меня, поэтому я написал этот класс кодировать UTF-строки в ASCII-Strings (заменяя все символы, отсутствующие в таблице ASCII, с их табличным номером, предшествующий и сопровождаемый знаком), используя AsciiEncoder.encode(yourString)

Строка может быть декодирована обратно в UTF с помощью AsciiEncoder.decode(yourAsciiEncodedString).

package <your_package>;

import java.util.ArrayList;

/**
 * Created by Micha F. aka Peracutor.
 * 04.06.2017
 */

public class AsciiEncoder {

    public static final char MARK = '%'; //use whatever ASCII-char you like (should be occurring not often in regular text)

    public static String encode(String s) {
        StringBuilder result = new StringBuilder(s.length() + 4 * 10); //buffer for 10 special characters (4 additional chars for every special char that gets replaced)
        for (char c : s.toCharArray()) {
            if ((int) c > 127 || c == MARK) {
                result.append(MARK).append((int) c).append(MARK);
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }

    public static String decode(String s) {
        int lastMark = -1;
        ArrayList<Character> chars = new ArrayList<>();
        try {
            //noinspection InfiniteLoopStatement
            while (true) {
                String charString = s.substring(lastMark = s.indexOf(MARK, lastMark + 1) + 1, lastMark = s.indexOf(MARK, lastMark));
                char c = (char) Integer.parseInt(charString);
                chars.add(c);
            }
        } catch (IndexOutOfBoundsException | NumberFormatException ignored) {}

        for (char c : chars) {
            s = s.replace("" + MARK + ((int) c) + MARK, String.valueOf(c));
        }
        return s;
    }
}

Надеюсь, это поможет кому-то.