前几天客户现场国产服务器部署应用时,出现了和weblogic服务器表现不一致的地方,案列很简单:就是样式在加载过程中使用了css的var() 函数。而导致后面css样式加载失败失败,var定义的值没有被替换的情况。
先说下css var()函数正常使用,以下面的demo为例:查看图1可以看出,background-color已成功替换为具体的颜色。(其中 :root 选择器匹配文档根元素。在 HTML 中,根元素始终是 html 元素。所以var(--primart-color)会在加载时被替换成对应的#B1D7DC值。)
style.css案列如下:
:root {
--primary-color: #B1D7DC;
}
html {
background-color: var(--primary-color);
}
切回正题:那么为什么出现了有服务器出现样式加载的问题呢,笔者带着这样的问题先去看了业务的css(ps:由于业务代码属于其他厂商的,且不开源)所以笔者以demo来描述和业务场景一样的问题。下面的css是基于demo来实现与现场业务一样问题的场景。
该业务的css笔者一开始好奇只存在var()函数,而没有对函数的替换值,显然与上面所提及的案例不一致,但是访问页面如下图2所示。也是描述的状态var(--primary-color)值没有被替换。
对比正常的weblogic服务器显示结果,该处的值是被替换成具体的颜色。那么问题就很清晰了肯定在某处提供了key值替换的功能,于是基于此搜索了业务代码发现,对应的替换放在了java的过滤器中。代码如下:可以清晰的可见String replacedText = new String(content, wrapperResponse.getCharacterEncoding());对无边码的字节数组进行创建css对象,再此过程种对css对象中对primary-color等字段进行颜色替换,最后在将文本按照对应的编码转化成无编码格式的字节数组输出文件流操作。仔细看了下这是通用的代码不存在差异,那么问题出现在哪了,于是wireshark抓了下包发现端倪如下面图3所示。服务器发过来的css的Content-Encoding是gzip的格式被压缩了,而当前过滤器的代码正好基于content内容进行更改的,因此压缩前后的内容不一致,导致文本内容替换失败。
既然我们排查到了问题,那么接下来就是如何解决:
通过查看服务器的代码的代码如图4所示:服务器代码存在preCompressedFile,默认值为true,即对字节数组输出流进行GZIP压缩处理。可能是某些服务器对于静态文件等资源进行传输等性能上的调优,默认的将文件流进行了压缩处理,导致了出现了上述的问题,后面我们把该服务器的preCompressedFile的属性改成false,然后再测试发现,问题解决。具体的结果我们再次抓包如图5所示。可见当前的响应格式是没有经过任何压缩处理的,至此问题解决。在下也想吐槽下,服务器作为比较中间层面的东西,很多厂商的优化应尽量考虑全客户的应用场景,这样对于排查问题等事情会简单很多。
拓展:tomcat也支持对输出内容进行压缩处理,具体属性为server.xml中 compression=”on” 打开压缩功能。具体其他的针对某些具体的text/css等进行压缩可以设置web.xml中的配置进行设置即可。
TestFilter.java过滤器实现
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest)request;
String requestUri = getCanonicalURIWithoutContextPath(httpRequest);
if (requestUri.endsWith(".css")) {
replaceCssFile((ServletRequest)httpRequest, response, filterChain);
} else {
filterChain.doFilter(request, response);
}
}
private void replaceCssFile(ServletRequest httpRequest, ServletResponse response, FilterChain filterChain) {
CssResponseWrapper wrapperResponse = new CssResponseWrapper((HttpServletResponse)response);
try {
filterChain.doFilter(httpRequest, (ServletResponse)wrapperResponse);
byte[] content = wrapperResponse.getCaptureAsBytes();
if (content != null && content.length > 0) {
String replacedText = new String(content, wrapperResponse.getCharacterEncoding());
Map<String, String> colorMap = new HashMap<String, String>();
colorMap.put("--primary-color", "#B1D7DC");
colorMap.put("--accent-color", "##FF3F90");
colorMap.put("--primaryColorOnHover", "##FF0000");
for (Map.Entry<String, String> entry : colorMap.entrySet())
replacedText = replacedText.replaceAll("var\\(--" + (String)entry.getKey() + "\\)", entry.getValue());
response.setContentLength(-1);
response.setContentType(wrapperResponse.getContentType());
ServletOutputStream out = response.getOutputStream();
out.write(replacedText.getBytes(wrapperResponse.getCharacterEncoding()));
out.flush();
} else {
response.setContentLength(-1);
response.setContentType(wrapperResponse.getContentType());
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private String getCanonicalURIWithoutContextPath(HttpServletRequest request) {
if (request == null)
return null;
StringBuilder sb = new StringBuilder();
if (null != (request.getServletPath()) && !"".equals(request.getServletPath()))
sb.append(request.getServletPath());
if (null != (request.getPathInfo()) && !"".equals(request.getPathInfo()))
sb.append(request.getPathInfo());
return sb.toString();
}
style.css
html {
background-color: var(--primary-color);
}
h3 {
border-bottom: 2px solid var(--primary-color);
}
p {
background:var(--primaryColorOnHover);
}
button {
color: var(--accent-color);
border: 1px solid var(--accent-color);
}
.test {
background:var(--primaryColorOnHover);
}