仅当 ContentType === JSON 时更改 Java 过滤器中的 ContentType 或 CharacterEncoding

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23084182/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 20:35:56  来源:igfitidea点击:

Change ContentType or CharacterEncoding in Java Filter ONLY IF ContentType === JSON

javafiltercontent-typejersey-2.0

提问by Brad Parks

I'm trying to ensure that all JSON responses from a Jersey based java application have a UTF-8 character encoding parameter appended to their ContentType header.

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

So if it's a JSON response, I would like the response header for the Content-Typeto be

所以,如果它是一个JSON响应,我想响应报头的Content-Type

Content-Type: application/json;charset=UTF-8

内容类型: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".

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".

If I just try and set the character encoding in my filter regardless of the content type, it works fine. But I only want to set the character encoding if the ContentType is "application/json". I find that the response.getContentType() method always returns null unless I call chain.doFilter first. But if I try and change the Character Encoding after this, it seems to always get overwritten.

如果我只是尝试在我的过滤器中设置字符编码而不管内容类型如何,它都可以正常工作。但如果 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()
    {
    }
}

I've seen other similar questions, but none them seem to have this issue. I've tried registering my filter as the first, and last filter with no luck.

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

采纳答案by Brad Parks

Thanks to the other answers on this page, I found a way to do it.... Very close to what they were suggesting, but it turned out the only way I could get it to work was to override "getOutputStream" and look at the contentType at that point. I've put this filter as the first filter in the chain, and it seems to work fine.

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

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
    }
}

回答by Torindo

Not 100% sure I got what you're trying to achieve. Do you want to set the header charset after calling

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

chain.doFilter(request, response)

?

?

If that is the case I'm afraid you cannot because very likely at that point, after that chain.doFilter(request, response) has returned and the request processed, the content charset has already been sent to the client and therefore you cannot alter it anymore.

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

回答by Cebence

Using this answeras reference the solution to your question is to re-encode the JSON text as shown here:

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

回答by boky

This is not going to work in this way.

这不会以这种方式工作。

When you call chain.doFilter(request, response);your headers are already flushed and you can't reset them later on.

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

What you can do is actually a quick and dirty trick:

你可以做的实际上是一个快速而肮脏的技巧:

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!
}

EDIT:ct.toUpperCase().startsWith("application/json")changed to ct.toLowerCase().startsWith("application/json").

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

回答by Brad Parks

This also may be doable using a ClientFilter, which I've just come across a StackOverflow post for a similar purpose:

这也可以使用 a ClientFilter,我刚刚遇到了一个 StackOverflow 帖子,用于类似目的:

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

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

回答by Ben Romberg

Had success using a ContainerResponseFilter:

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