仅当ContentType==JSON时,才更改Java筛选器中的ContentType或CharacterEncodi



我正在努力确保来自基于Jersey的java应用程序的所有JSON响应都在其ContentType标头中附加了一个UTF-8字符编码参数。

因此,如果是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()
    {
    }
}

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

多亏了这个页面上的其他答案,我找到了一种方法……非常接近他们的建议,但事实证明,我能让它工作的唯一方法是覆盖"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
    }
}

这样做是行不通的。

当您调用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")

使用此答案作为参考,您的问题的解决方案是重新编码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();
    }
}

使用ClientFilter也可以实现这一点,我刚刚在StackOverflow的一篇文章中看到了类似的目的:

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

成功使用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());
    }
}

不能100%确定我达到了你想要达到的目标。调用后是否要设置标题字符集

chain.doFilter(request, response)

如果是这种情况,恐怕你不能,因为很可能在那一点上,在链.doFilter(请求,响应)返回并处理请求之后,内容字符集已经发送到客户端,因此你不能再更改它了。

最新更新