仅在内容类型 === JSON 时更改 Java 过滤器中的内容类型或字符编码

Posted

技术标签:

【中文标题】仅在内容类型 === JSON 时更改 Java 过滤器中的内容类型或字符编码【英文标题】:Change ContentType or CharacterEncoding in Java Filter ONLY IF ContentType === JSON 【发布时间】: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,但他们似乎都没有这个问题。我尝试将我的过滤器注册为第一个也是最后一个过滤器,但没有成功。

【问题讨论】:

【参考方案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 post 和 this 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 来实现,我刚刚看到一个 *** 帖子用于类似目的:

https://***.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 之前设置内容类型

以上是关于仅在内容类型 === JSON 时更改 Java 过滤器中的内容类型或字符编码的主要内容,如果未能解决你的问题,请参考以下文章

React Hooks:确保 useEffect 仅在自定义钩子的数组参数内容更改时运行的惯用方法

Rails 为 json api 请求返回不正确的 MIME 类型(仅在测试中)

jQuery - 更改 iframe src 时仅在第二次单击时触发

if 语句导致解析,但分支内没有其他内容

HTMX 的多个触发器?

标头内容类型字符集 UTF-8 和 BOM