仅在内容类型 === 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 类型(仅在测试中)