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
  
83
  
84
  
85
  
86
  
87
  
88
  
89
  
90
  
91
  
92
  
93
  
94
  
95
  
96
  
97
  
98
  
99
  
100
  
101
  
102
  
103
  
104
  
105
  
106
  
107
  
108
  
109
  
110
  
111
  
112
  
113
  
114
  
115
  
116
  
117
  
118
  
119
  
120
  
121
  
122
  
123
  
124
  
125
  
126
  
127
  
128
  
129
  
130
  
131
  
132
  
133
  
inherit "module"; 
#include <module.h> 
 
constant module_unique = 1; 
constant module_name = "Tags: Static Resource Server"; 
string module_doc =  
  #" 
<p>This module can help you improve browser caching of semi-static 
resources (Javascript files, CSS, images etc. that may change every 
once in a while) by:  
  <ol>  
    <li>Setting a far-future Expires HTTP header, as described by <a href=\"http://developer.yahoo.com/performance/rules.html#expires\">Yahoo!</a></li> 
    <li>Injecting a variable part in the resource URL that changes whenever the linked resource file changes</li>  
  </ol>  
</p>  
 
<p>These two features combined will make sure that the resource files are browser-cached for as long as possible while avoiding overcaching.</p> 
"; 
 
constant module_type = MODULE_FILTER | MODULE_TAG; 
 
constant expire_time = 86400*365; // One year. 
 
constant default_process_tags = ([ "link"   : "href", 
                                   "script" : "src" ]); 
 
void create(Configuration conf) 
{ 
  defvar("process_tags", 
         Variable.Mapping(default_process_tags, 0, 
                          "Tags to process", 
                          "The tags to process and the corresponding " 
                          "attribute that refers an external resource.")); 
} 
 
class TagServeStaticResources 
{ 
  inherit RXML.Tag; 
  constant name = "serve-static-resources"; 
 
  string mangle_resource_urls(string s, RequestID id) 
  { 
    mapping process_tags = query("process_tags"); 
    Parser.HTML parser = Parser.HTML(); 
    parser->xml_tag_syntax(0); 
 
    function process_tag = lambda(Parser.HTML p, mapping args) 
    { 
      string tag_name = p->tag_name(); 
      string attr_name = process_tags[tag_name]; 
      string link = args[attr_name]; 
      if(link && has_prefix (link, "/") && !has_prefix (link, "//")) { 
        sscanf(link, "%s%*[?#]", string raw_link); 
 
        array(int)|Stdio.Stat st = 
          id->conf->try_stat_file(raw_link, id); 
 
        if(st) { 
          if(arrayp(st)) 
            st = Stdio.Stat(st); 
 
          string varystr = sprintf("mtime=%d", st->mtime); 
 
          args[attr_name] = 
            Roxen.add_pre_state(link, (< "cache-forever", varystr >)); 
 
          m_delete(args, "/"); 
 
          return ({ Roxen.make_tag(tag_name, args, has_suffix (tag_name, "/"), 
                                   1) }); 
        } 
      } 
      return 0; 
    }; 
 
    foreach(process_tags; string tag_name; string attr_name) { 
      parser->add_tag(tag_name, process_tag); 
      parser->add_tag(tag_name + "/", process_tag); 
    } 
 
    parser->ignore_unknown (1); 
    string res = parser->finish(s)->read(); 
    parser = 0; 
    process_tag = 0; 
    return res; 
  }; 
 
  class Frame 
  { 
    inherit RXML.Frame; 
    array do_return(RequestID id) 
    { 
      result = mangle_resource_urls(content || "", id); 
    } 
  } 
} 
 
mapping|void filter(mapping res, RequestID id) 
{ 
  if (!res) return; 
 
  if(id->prestate["cache-forever"]) { 
    if (res->extra_heads) { 
      m_delete(res->extra_heads, "cache-control"); 
      m_delete(res->extra_heads, "Cache-Control"); 
      m_delete(res->extra_heads, "expires"); 
      m_delete(res->extra_heads, "Expires"); 
    } 
 
    RAISE_CACHE(expire_time); 
    PROTO_CACHE(); 
 
    id->misc->vary = (<>); 
    return res; 
  } 
} 
 
TAGDOCUMENTATION; 
#ifdef manual 
constant tagdoc = ([ 
  "serve-static-resources":#"<desc type='tag'><p><short>Specifies a block of tags referring static resources.</short> Wrap this tag around your block of static resource referring tags.</p> 
<ex-box> 
<serve-static-resources> 
  <link rel=\"stylesheet\" href=\"/index.css\"/> 
  <script type=\"text/javascript\" src=\"/index.js\"/> 
</serve-static-resources> 
</ex-box> 
<p>Note: Only local absolute paths will be processed, i.e. they have to begin with a '/'.</p> 
</desc>" 
]); 
#endif // manual