Liferay Dynamic CSS Filter方法的研究 - 总体过程

背景知识:

最近项目组遇到一个问题就是改了一个new theme之后导致某些css文件不起作用了,这也激起了我的好奇心,让我有机会去研究下Liferay Dynamic CSS Filter的原理。


引入

这个Filter 和一般的Filter一样,会配置在portal-web.xml中,并且声明了对于.css文件和.jsp资源文件请求时候会触发:

134414615.png


然后执行它的processFilter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected  void  processFilter(
             HttpServletRequest request, HttpServletResponse response,
             FilterChain filterChain)
         throws  Exception {
         Object parsedContent = getDynamicContent(
             request, response, filterChain);
         if  (parsedContent ==  null ) {
             processFilter(
                 DynamicCSSFilter. class , request, response, filterChain);
         }
         else  {
             if  (parsedContent  instanceof  File) {
                 ServletResponseUtil.write(response, (File)parsedContent);
             }
             else  if  (parsedContent  instanceof  String) {
                 ServletResponseUtil.write(response, (String)parsedContent);
             }
         }
     }



调试场景:

比如当我们刚加载 liferay首页,因为上面有许多css资源文件,所以会自动触发这个调用,走入processFilter方法,而它会调用getDynamicContent()方法来获取jRuby解析Sass后的变成的普通css文件。这方法是我们这文章研究的重点。


getDynamicContent()的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
protected  Object getDynamicContent(
             HttpServletRequest request, HttpServletResponse response,
             FilterChain filterChain)
         throws  Exception {
         String requestURI = request.getRequestURI();
         String requestPath = requestURI;
         String contextPath = request.getContextPath();
         if  (!contextPath.equals(StringPool.SLASH)) {
             requestPath = requestPath.substring(contextPath.length());
         }
         String realPath = ServletContextUtil.getRealPath(
             _servletContext, requestPath);
         if  (realPath ==  null ) {
             return  null ;
         }
         realPath = StringUtil.replace(
             realPath, CharPool.BACK_SLASH, CharPool.SLASH);
         File file =  new  File(realPath);
         String cacheCommonFileName = getCacheFileName(request);
         File cacheContentTypeFile =  new  File(
             cacheCommonFileName +  "_E_CONTENT_TYPE" );
         File cacheDataFile =  new  File(cacheCommonFileName +  "_E_DATA" );
         if  ((cacheDataFile.exists()) &&
             (cacheDataFile.lastModified() >= file.lastModified())) {
             if  (cacheContentTypeFile.exists()) {
                 String contentType = FileUtil.read(cacheContentTypeFile);
                 response.setContentType(contentType);
             }
             return  cacheDataFile;
         }
         String dynamicContent =  null ;
         String content =  null ;
         try  {
             if  (realPath.endsWith(_CSS_EXTENSION) && file.exists()) {
                 if  (_log.isInfoEnabled()) {
                     _log.info( "Parsing SASS on CSS "  + file);
                 }
                 content = FileUtil.read(file);
                 dynamicContent = DynamicCSSUtil.parseSass(
                     request, realPath, content);
                 response.setContentType(ContentTypes.TEXT_CSS);
                 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
             }
             else  if  (realPath.endsWith(_JSP_EXTENSION) || !file.exists()) {
                 if  (_log.isInfoEnabled()) {
                     _log.info( "Parsing SASS on JSP or servlet "  + realPath);
                 }
                 StringServletResponse stringResponse =
                     new  StringServletResponse(response);
                 processFilter(
                     DynamicCSSFilter. class , request, stringResponse,
                     filterChain);
                 CacheResponseUtil.setHeaders(
                     response, stringResponse.getHeaders());
                 response.setContentType(stringResponse.getContentType());
                 content = stringResponse.getString();
                 dynamicContent = DynamicCSSUtil.parseSass(
                     request, realPath, content);
                 FileUtil.write(
                     cacheContentTypeFile, stringResponse.getContentType());
             }
             else  {
                 return  null ;
             }
         }
         catch  (Exception e) {
             _log.error( "Unable to parse SASS on CSS "  + realPath, e);
             if  (_log.isDebugEnabled()) {
                 _log.debug(content);
             }
             response.setHeader(
                 HttpHeaders.CACHE_CONTROL,
                 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
         }
         if  (dynamicContent !=  null ) {
             FileUtil.write(cacheDataFile, dynamicContent);
         }
         else  {
             dynamicContent = content;
         }
         return  dynamicContent;
     }



我们附上调试信息:

135028226.png


从上述调试信息一目了然,主要是第5行获取当前请求的URI,从调试信息看,它的内容是:

html/portlet/login/css/main.css , 这个也正符合我们的猜想,因为当前请求的资源文件main.css符合CSS扩展名的模式,所以才被这个Dynamic CSS Filter所过滤到并且进入这个断点。


第16行获取这个资源文件的真实路径realPath(疑问1: 如何获取这个真实的path的? 答案在以后讨论中给出) ,这里给出的路径是

/app/Liferay/RI/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/html/portlet/login/css/main.css

,这也正符合我们当初的设想,因为这个main.css我们的确是一年前把它手动复制到了该目录下。


然后第19行计算出这个文件对应的缓存base文件名,因为一个缓存文件总是由2部分组成,一个是内容类型文件,一个是数据文件,他们的各自名字都是由base名字加上指定后缀拼接而成。内容类型文件的名字是<cacheCommonFileName>_E_CONTENT_TYPE,而缓存数据文件的名字是<cacheCommonFileName>_E_DATA. (疑问2:如何计算得到这个缓存base文件名?答案也在后续讨论中给出) ,所以我们通过计算得到的缓存base文件名为:

/app/Liferay/RI/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/temp/liferay/css/portal/623927847558055413


下面既然得到了缓存base文件名,并且依照后缀定则拼接了相应的内容类型文件名和数据文件名,那么下面的工作就是第20行和第21行在相应位置创建相应的File对象了。因为new File()按照我们对于java的语义,就是如果这个文件不存在,那么则创建新文件,如果存在,只File对象指向已知文件。

我们到服务器目录下看到了这个2个文件:

140141891.png


当缓存内容类型文件和缓存内容文件都固定下来后,下面就考虑到更新或者填入内容到这些文件了。

首先,从第35行开始,还是从原始的带有Sass的css文件入手:

1
2
3
4
5
6
7
8
9
10
if  (realPath.endsWith(_CSS_EXTENSION) && file.exists()) {
                 if  (_log.isInfoEnabled()) {
                     _log.info( "Parsing SASS on CSS "  + file);
                 }
                 content = FileUtil.read(file);
                 dynamicContent = DynamicCSSUtil.parseSass(
                     request, realPath, content);
                 response.setContentType(ContentTypes.TEXT_CSS);
                 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
             }

它先判断原始文件是不是CSS扩展名的文件,如果是,那么就读取这个原始的css文件到一个字符串变量content中,见以下的调试信息:

140514722.png


然后调用DynamicCSSUtil的parseSass()方法吧这个带Sass的css文件解析成一个不带Sass的普通css文件,并且结果存放在dynamicContent变量中,比如上述content被解析后存放到的dynamicContent的值如下:

140720482.png

读者很容易看出这个新的样式文件是和原来不一样了,不仅仅是排版格式还有语法。


最后,把相应的内容写入到刚才最早的生成的内容类型文件和数据文件中。

源代码的第64行在内容类型文件(<cacheCommonFileName>_E_CONTENT_TYPE)中写入内容为 text/css

FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);


而第82行在数据文件中(<cacheCommonFileName>_E_DATA)写入刚才生成Sass解析后生成的普通css文件内容

FileUtil.write(cacheContentTypeFile, stringResponse.getContentType()

(疑问3:这个写入过程细节是这样的呢?比如文件为空和文件中已经有内容各是如何处理的? 这个答案也在以后讨论中给出)


最后在第88行中返回的动态生成的普通css文件字符串。




现在我们返回到processFilter方法中,既然已经得到了Sass解析后生成的普通css字符串,所以最后就是把这个字符串返回到客户端,所以在processFilter()方法的行末:

ServletResponseUtil.write(response, (String)parsedContent);

这样我们访问页面时候就可以正确的看到和使用这里的样式了。



总结:

从非常宏观的角度,我们至少有以下几点收获:

(1)DynamicCssFilter的调用时机是在站点请求响应的资源文件的时候触发的。

(2)访问资源文件时,它会从原始含有Sass语法的css文件中获取原始内容,然后用jRuby引擎进行解析从而获得新的解析后的普通css文件。

(3)解析后的css文件总会最终被写入到缓存内容文件中,这个缓存数据文件的后缀总是_E_DATA,并且它总是对应一个内容类型文件,这个内容类型文件的格式总是_E_CONTENT_TYPE.

(4)解析后的文件会被服务器写到最终输出流中,从而你在浏览器中可以看到并且使用这个被解析后的普通css文件。


我们还留着几个疑点,会在接下来的文章中得到解决。





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1282753,如需转载请自行联系原作者
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值