【问题标题】:Change ContentType or CharacterEncoding in Java Filter ONLY IF ContentType === JSON仅在内容类型 === JSON 时更改 Java 过滤器中的内容类型或字符编码
【发布时间】:2014-05-29 20:02:16
【问题描述】:

我正在尝试确保来自基于 Jersey 的 java 应用程序的所有 JSON 响应都有一个 UTF-8 字符编码参数附加到它们的 ContentType 标头。

因此,如果它是 JSON 响应,我希望 Content-Type 的响应标头为

内容类型:application/json;charset=UTF-8

EDIT: I know I can do this on a case by case basis, but I'd like to do it globally, so it affects all content responses that have a content type of "application/json".

如果我只是尝试在过滤器中设置字符编码而不考虑内容类型,它可以正常工作。但是如果 ContentType 是“application/json”,我只想设置字符编码。我发现 response.getContentType() 方法总是返回 null ,除非我先调用 chain.doFilter 。但是,如果我在此之后尝试更改字符编码,它似乎总是被覆盖。

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ws.rs.core.MediaType;

public class EnsureJsonResponseIsUtf8Filter implements Filter
{
    private class SimpleWrapper extends HttpServletResponseWrapper
    {
        public SimpleWrapper(HttpServletResponse response)
        {
            super(response);
        }

        @Override
        public String getCharacterEncoding()
        {
            return "UTF-8";
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        chain.doFilter(request, response);

        if (response.getContentType() != null && response.getContentType().contains(MediaType.APPLICATION_JSON))
        {
            response.setCharacterEncoding("UTF-8");
            chain.doFilter(request, new SimpleWrapper((HttpServletResponse) response));
        }
    }

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

    @Override
    public void destroy()
    {
    }
}

我见过其他similar questions,但他们似乎都没有这个问题。我尝试将我的过滤器注册为第一个也是最后一个过滤器,但没有成功。

【问题讨论】:

    标签: java filter content-type jersey-2.0


    【解决方案1】:

    感谢此页面上的其他答案,我找到了一种方法......非常接近他们的建议,但事实证明我可以让它工作的唯一方法是覆盖“getOutputStream”并在那时查看 contentType 。我已将此过滤器作为链中的第一个过滤器,它似乎工作正常。

    import java.io.IOException;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.ws.rs.core.MediaType;
    
    public class EnsureJsonIsUtf8ResponseFilter implements Filter
    {
        final String APPLICATION_JSON_WITH_UTF8_CHARSET = MediaType.APPLICATION_JSON + ";charset=" + java.nio.charset.StandardCharsets.UTF_8;
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
        {
            HttpServletResponse r = (HttpServletResponse) response;
            HttpServletResponse wrappedResponse = new HttpServletResponseWrapper(r) 
            {
                @Override
                public ServletOutputStream getOutputStream() throws java.io.IOException
                {
                    ServletResponse response = this.getResponse();
    
                    String ct = (response != null) ? response.getContentType() : null;
                    if (ct != null && ct.toLowerCase().startsWith(MediaType.APPLICATION_JSON))
                    {
                        response.setContentType(APPLICATION_JSON_WITH_UTF8_CHARSET);
                    }
    
                    return super.getOutputStream();
                }
            };
    
            chain.doFilter(request, wrappedResponse); 
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException
        {
            // This method intentionally left blank
        }
    
        @Override
        public void destroy()
        {
            // This method intentionally left blank
        }
    }
    

    【讨论】:

    • 如果内容类型为 RESPONSE 标头,我想将 Cache-Control 设置为“no-cache, no-store, must-revlidate, max-age=0”是“text/html”我在这种情况下被阻止了。你能帮忙吗?
    • 嗯......似乎同样的想法应该对我有用。 Here's an untested guess at how this would go 和谷歌搜索,this postthis additional post 可能会回答你的问题
    【解决方案2】:

    这种方式是行不通的。

    当您调用 chain.doFilter(request, response); 时,您的标头已被刷新,您以后无法重置它们。

    你能做的实际上是一个快速而肮脏的把戏:

    public void doFilter(...) {
        HttpServletResponse resp = new HttpServletResponseWrapper(response) {
        public void setContentType(String ct) {
            if(ct!=null && ct.toLowerCase().startsWith("application/json")) {
                super.setContentType("application/json;charset=UTF-8");
            } else {
                super.setContentType(ct);
            }
       }
    }
    
    // Set content type manually to override any potential defaults,
    // See if you need it at all
    response.setContentType("application/json;charset=UTF-8");
    
    chain.doFilter(request, resp); // Inject our response!
    }
    

    编辑: ct.toUpperCase().startsWith("application/json") 更改为 ct.toLowerCase().startsWith("application/json")

    【讨论】:

    • 嘿!感谢您的想法....它似乎工作正常,并被希望的其他响应覆盖(例如 Content-Type: image/jpeg).... 赞!请注意,我必须确保这是链中的第一个过滤器才能使其按预期工作....
    • hmmm....我认为它有效,但我似乎无法让它再次工作....在赏金之前我有点急于尝试解决方案结束了,所以也许我看到了一些不存在的东西......我确实注意到在上面的代码中,它说ct.toUpperCase().startsWith("application/json,这永远不会是真的......所以我猜这永远不会有工作...感谢您的建议...如果我发现有什么不同,我会报告回来...
    • 嗯,你说你只是想添加字符集。如果你在做其他事情,你总是可以用同样的方式抓住它。您如何 DO 知道响应是否是您的 JSON?通过网址?
    • 好吧,我想这可能是问题的一部分。我知道的唯一方法是查看 ContentType 标头,在调用 chain.doFilter 之后,它似乎只有一些东西(即不为空)。我不想在所有响应中都将字符集设置为 UTF-8,因为它会导致我阅读的 IE8 图像出现问题。
    【解决方案3】:

    使用this answer 作为参考,您的问题的解决方案是重新编码 JSON 文本,如下所示:

    public void doFilter(...) {
        final CharResponseWrapper wrappedResponse =
                new CharResponseWrapper((HttpServletResponse) response);
    
        chain.doFilter(request, wrappedResponse);
    
        final String content = wrappedResponse.toString();
    
        final String type = wrappedResponse.getContentType();
        if (type != null && type.contains(MediaType.APPLICATION_JSON)) {
            // Re-encode the JSON response as UTF-8.
            response.setCharacterEncoding("UTF-8");
            final OutputStream out = response.getOutputStream();
            out.write(content.getBytes("UTF-8"));
            out.close();
        }
        else {
            // Otherwise just write it as-is.
            final PrintWriter out = response.getWriter();
            out.write(content);
            out.close();
        }
    }
    

    【讨论】:

      【解决方案4】:

      这也可以使用ClientFilter 来实现,我刚刚看到一个 StackOverflow 帖子用于类似目的:

      https://stackoverflow.com/a/7464585/26510

      【讨论】:

        【解决方案5】:

        使用ContainerResponseFilter 成功:

        public class ContentTypeEncodingFilter implements ContainerResponseFilter {
            @Override
            public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
                String contentType = responseContext.getHeaderString(HttpHeaders.CONTENT_TYPE);
                if (contentType == null) {
                    return;
                }
                ContentType parsedType = ContentType.parse(contentType);
                if (parsedType.getCharset() != null) {
                    return;
                }
                ContentType encodedType = parsedType.withCharset(StandardCharsets.UTF_8);
                responseContext.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, encodedType.toString());
            }
        }
        

        【讨论】:

          【解决方案6】:

          不是 100% 确定我得到了您想要实现的目标。 调用后是否要设置标题字符集

          chain.doFilter(request, response)
          

          ?

          如果是这种情况,恐怕你不能,因为很可能在那个时候,在 chain.doFilter(request, response) 返回并处理请求之后,内容字符集已经发送到客户端,因此你不能再改变它了。

          【讨论】:

          • 嘿...谢谢...我正在努力确保所有 ContentType 为“application/json”的响应也响应字符集标题为“UTF-*”.. ..
          • 我能想到的最简单的方法是将所有 json 操作分组到一个公共路径前缀中,然后在过滤器中根据请求路径设置内容类型。例如,如果您的主机名是 foo.com,那么您的所有 json 操作都具有以下 url 前缀 foo.com/json*。现在在过滤器中,如果 url 前缀是 foo.com/json*,则在调用 chain.doFilter 之前设置内容类型
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-02-05
          • 2016-12-07
          • 2012-09-22
          • 2017-06-19
          • 1970-01-01
          • 2019-01-03
          相关资源
          最近更新 更多