在嵌入式Jetty中设置默认字符编码和内容类型

时间:2018-06-24 22:29:48

标签: java servlets kotlin jetty embedded-jetty

我正在为嵌入式Jetty制造DSL,但在设置characterEncodingcontentType时遇到问题。我希望用户能够为这两个字段指定默认值,但是Jetty使得生活变得艰难。

res.characterEncoding = null赋予res.characterEncodingiso-8859-1

res.characterEncoding = "",为res.characterEncoding赋予值"",但是res.contentType变成application/json;charset=

res.characterEncoding = ""然后res.characterEncoding = null具有与执行res.characterEncoding = ""相同的效果

基于这种奇怪的行为,我最终得到了一个荒谬的代码片段:

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 :(得分:4)

您正在与Servlet规范的一个过于复杂的方面作斗争。

  

意见:HttpServletResponse.setContentType(String)方法应该永远不存在,应该只是.setMimeType(String).setCharacterEncoding(Charset)

让我们从字符编码的重要性开始。

访问HttpServletResponse.getWriter()时,实现必须解析用于创建的PrintWriter的响应字符编码和语言环境。这意味着此时的字符编码将分配一个值。

请注意,也使用了语言环境,这经常被忽略,但是由于您是图书馆作家,因此应向您指出。参见HttpServletResponse.setLocale(Locale)HttpServletResponse.getLocale()

要考虑的另一件事是,如果您已经访问过HttpServletResponse.getWriter(),则以后使用HttpServletResponse.setCharacterEncoding(String)会导致无操作,而将HttpServletResponse.setContentType(String)与{{1}一起使用}可能导致charset从产生的标题中剥离。 (同样,这符合Servlet规范的行为)。

如果先前手动将charset设置为有效值,则HttpServletResponse.getCharacterEncoding()可以返回字符编码,或者如果已经声明,则返回基于Content-Type的字符编码,否则默认为ISO-8859-1。如果它正在使用Content-Type,它将首先检查charset参数并使用它。如果Content-Type没有charset,则它将在您的Web元数据中使用mime-type配置。此调用绝不能产生空字符或空字符编码。

使用HttpServletResponse.getCharacterEncoding()的Servlet规范默认值为ISO-8859-1(此值取自RFC2616,定义了Servlet规范的这一方面)。

Web元数据来自Web描述符(又名WEB-INF/web.xml),默认描述符,替代描述符,org/eclipse/jetty/http/mime.properties资源,org/eclipse/jetty/http/encoding.properties,存在的其他功能(例如{{1} })和程序化配置。

在Jetty中,所有用于mime类型的各种配置源都会导致配置的Jetty GzipHandler对象。

调用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)参数,它也有责任修改字符编码字段。

了解了所有这些信息后,您将意识到必须注意各种API调用的顺序,尤其是在进行charset调用时。

您可能应该在启动Web应用时通过HttpServletResponse.getWriter()的Web元数据来控制此操作,而不是使用Servlet HttpServletResponse API来管理此操作。

在您的情况下,您只需在application/json正在创建的MimeTypes上配置ServletContextHandler,就无需使用任何黑客手段。

Jetty的JettyServerUtil.ktServletContextHandler配置的默认行为不会添加字符集,这是由于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哑剧类型,访问Content-Type,设置标头PrintWriter以显示当前字符编码值,然后写json内容。

客户端看到的X-Charset响应标头不包含Content-Type的假定charset