Настройка кодировки символов по умолчанию и типа контента во встроенной Jetty - PullRequest
0 голосов
/ 25 июня 2018

Я делаю DSL для встроенного Jetty, и у меня возникают проблемы с установкой characterEncoding и contentType.Я хочу, чтобы пользователи могли указывать значения по умолчанию для этих двух полей, но Jetty усложняет жизнь.

res.characterEncoding = null дает res.characterEncoding значение iso-8859-1.

res.characterEncoding = "", дает res.characterEncoding значение "", но res.contentType становится application/json;charset=

res.characterEncoding = "" THEN res.characterEncoding = null имеет тот же эффект, что и просто res.characterEncoding = ""

Iв итоге получился смешной фрагмент кода, основанный на этом странном поведении:

if (res.characterEncoding.contains(";javalin-default") || res.contentType.contains(";javalin-default")) {
    res.contentType = res.contentType.replace(";javalin-default", "")
    res.characterEncoding = null
    if (res.contentType.contains("application/json")) {
        res.contentType = "application/json"
    } else {
        res.characterEncoding = defaultCharacterEncoding
    }
}

Но это не может быть правильным решением.Любые идеи?

У меня есть проблема для этого здесь: https://github.com/tipsy/javalin/issues/259

1 Ответ

0 голосов
/ 27 июня 2018

Вы боретесь с чрезмерно сложным аспектом спецификации Servlet.

Мнение: метод HttpServletResponse.setContentType(String) никогда не должен был существовать, он должен был быть просто .setMimeType(String) и .setCharacterEncoding(Charset)

Давайте начнем с важности кодировки символов.

При обращении к HttpServletResponse.getWriter() реализация должна разрешить кодировку и локаль ответа, которые будут использоваться для созданного PrintWriter. Это означает, что кодировке символов в этой точке будет присвоено значение.

Обратите внимание, что локаль также используется, об этом часто забывают, но, поскольку вы являетесь автором библиотеки, на это следует обратить ваше внимание. См HttpServletResponse.setLocale(Locale) и HttpServletResponse.getLocale().

Что еще нужно учитывать, это то, что если вы уже получили доступ к HttpServletResponse.getWriter(), то использование HttpServletResponse.setCharacterEncoding(String) позднее приведет к неработоспособности, а использование HttpServletResponse.setContentType(String) с charset может привести к charset будучи раздетым от произведенного заголовка. (опять же, это соответствует поведению спецификации Servlet).

HttpServletResponse.getCharacterEncoding() может возвращать кодировку символов, если вручную установить допустимое значение ранее, или на основе Content-Type, если она уже объявлена, в противном случае по умолчанию используется ISO-8859-1. Если он использует Content-Type, он сначала проверяет параметр charset и использует его. Если Content-Type не имеет charset, тогда он использует конфигурацию mime-type в ваших веб-метаданных. Этот вызов никогда не должен приводить к пустой или нулевой кодировке символов.

Спецификация сервлета по умолчанию для использования HttpServletResponse.getCharacterEncoding(): ISO-8859-1 (это значение взято из RFC2616, когда был определен этот аспект спецификации сервлета).

Веб-метаданные поступают из веб-дескриптора (он же WEB-INF/web.xml), дескриптора по умолчанию, дескриптора переопределения, ресурса org/eclipse/jetty/http/mime.properties, org/eclipse/jetty/http/encoding.properties, других доступных функций (таких как GzipHandler) и программных конфигураций.

В Jetty все эти различные источники конфигурации для mime-типа приводят к сконфигурированному объекту Jetty org.eclipse.jetty.http.MimeTypes.

Когда вызывается HttpServletResponse.setCharacterEncoding(String), он также обязан изменить поле и заголовок Content-Type в ответе.

Предполагая, что .getWriter() еще не был вызван, использование setCharacterEncoding(null) удалит любой существующий параметр charset в поле Content-Type и заголовке. setCharacterEncoding("utf-8") добавит / изменит параметр charset на utf-8 в поле и заголовке Content-Type.

Когда вызывается HttpServletResponse.setContentType(String), он также обязан изменить поле Кодировка символов, если был предоставлен параметр charset.

Зная все это, вы поймете, что вам нужно соблюдать осторожность в отношении порядка различных вызовов API, особенно когда выполняется вызов HttpServletResponse.getWriter().

Вместо того, чтобы использовать API Servlet HttpServletResponse для управления этим, вы, возможно, должны управлять этим через веб-метаданные для application/json при запуске веб-приложения.

В вашем случае вы просто сконфигурируете MimeTypes на ServletContextHandler, который создает ваш JettyServerUtil.kt, без необходимости каких-либо хаков, которые вы используете.

Поведение по умолчанию конфигурации Jetty ServletContextHandler и MimeTypes не добавит кодировку из-за того, как application/json определено в ресурсе org/eclipse/jetty/http/encoding.properties (как предполагаемая кодировка).

Пример:

package jetty;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;

public class MimeTypeJsonExample
{
    public static void main(String[] args) throws Exception
    {
        Server server = new Server(9090);
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.addServlet(JsonServlet.class, "/demo");
        context.addServlet(DefaultServlet.class, "/"); // handle static content and errors for this context
        HandlerList handlers = new HandlerList();
        handlers.addHandler(context);
        handlers.addHandler(new DefaultHandler()); // handle non-context errors
        server.setHandler(context);
        server.start();

        try
        {
            demonstrateJsonBehavior(server.getURI().resolve("/"));
        }
        finally
        {
            server.stop();
        }
    }

    private static void demonstrateJsonBehavior(URI serverBaseUri) throws IOException
    {
        HttpURLConnection http = (HttpURLConnection) serverBaseUri.resolve("/demo").toURL().openConnection();
        dumpRequestResponse(http);
        System.out.println();
        try (InputStream in = http.getInputStream())
        {
            System.out.println(IO.toString(in, UTF_8));
        }
    }

    private static void dumpRequestResponse(HttpURLConnection http) throws IOException
    {
        System.out.println();
        System.out.println("----");
        System.out.printf("%s %s HTTP/1.1%n", http.getRequestMethod(), http.getURL());
        System.out.println("----");
        System.out.printf("%s%n", http.getHeaderField(null));
        http.getHeaderFields().entrySet().stream()
                .filter(entry -> entry.getKey() != null)
                .forEach((entry) -> System.out.printf("%s: %s%n", entry.getKey(), http.getHeaderField(entry.getKey())));
    }

    public static class JsonServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("application/json");
            PrintWriter writer = resp.getWriter();
            resp.setHeader("X-Charset", resp.getCharacterEncoding());
            writer.println("{\"mode\":[\"a=b\"],\"animals\":[[\"kiwi bird\",\"kea\",\"skink\"]]}");
        }
    }
}

Это приводит к выводу ...

2018-06-27 09:00:32.754:INFO::main: Logging initialized @360ms to org.eclipse.jetty.util.log.StdErrLog
2018-06-27 09:00:32.898:INFO:oejs.Server:main: jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 9.0.4+11
2018-06-27 09:00:32.969:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@5dd6264{/,null,AVAILABLE}
2018-06-27 09:00:33.150:INFO:oejs.AbstractConnector:main: Started ServerConnector@60707857{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-06-27 09:00:33.151:INFO:oejs.Server:main: Started @764ms

----
GET http://192.168.0.119:9090/demo HTTP/1.1
----
HTTP/1.1 200 OK
Server: Jetty(9.4.11.v20180605)
X-Charset: utf-8
Content-Length: 58
Date: Wed, 27 Jun 2018 14:00:33 GMT
Content-Type: application/json

{"mode":["a=b"],"animals":[["kiwi bird","kea","skink"]]}

2018-06-27 09:00:33.276:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@60707857{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-06-27 09:00:33.278:INFO:oejsh.ContextHandler:main: Stopped o.e.j.s.ServletContextHandler@5dd6264{/,null,UNAVAILABLE}

Как видите, JsonServlet устанавливает только mime-тип Content-Type, обращается к PrintWriter, устанавливает заголовок X-Charset, чтобы показать текущее значение кодировки символов, а затем записывает содержимое json.

Заголовок ответа Content-Type, видимый клиентом, не включает предполагаемое charset из utf-8.

...