Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 157 lines (131 sloc) 4.844 kb
b7950ff @josh Move url and path methods to server concern
josh authored
1 require 'rack/request'
9b7d2a5 @josh Import asset server from stagecoach
josh authored
2 require 'time'
3
49d224e @sstephenson Initial commit
authored
4 module Sprockets
350ccd9 @josh Docco server
josh authored
5 # `Server` is a concern mixed into `Environment` and
6 # `EnvironmentIndex` that provides a Rack compatible `call`
7 # interface and url generation helpers.
f0abe13 @josh Turn Server into a concern
josh authored
8 module Server
350ccd9 @josh Docco server
josh authored
9 # `call` implements the Rack 1.x specification which accepts an
10 # `env` Hash and returns a three item tuple with the status code,
11 # headers, and body.
12 #
13 # Mapping your environment at a url prefix will serve all assets
14 # in the path.
15 #
16 # map "/assets" do
17 # run Sprockets::Environment.new
18 # end
19 #
20 # A request for `"/assets/foo/bar.js"` will search your
21 # environment for `"foo/bar.js"`.
9b7d2a5 @josh Import asset server from stagecoach
josh authored
22 def call(env)
350ccd9 @josh Docco server
josh authored
23 # URLs containing a `".."` are rejected for security reasons.
9b7d2a5 @josh Import asset server from stagecoach
josh authored
24 if forbidden_request?(env)
25 return forbidden_response
26 end
27
350ccd9 @josh Docco server
josh authored
28 # Lookup the asset by `PATH_INFO`
6f4fb38 @josh Add immutable Index classes
josh authored
29 asset = find_asset(env['PATH_INFO'])
9b7d2a5 @josh Import asset server from stagecoach
josh authored
30
350ccd9 @josh Docco server
josh authored
31 # `find_asset` returns nil if the asset doesn't exist
4daa04a @josh Raise missing source file error
josh authored
32 if asset.nil?
350ccd9 @josh Docco server
josh authored
33 # Return a 404 Not Found
9b7d2a5 @josh Import asset server from stagecoach
josh authored
34 not_found_response
350ccd9 @josh Docco server
josh authored
35
36 # Check request headers `HTTP_IF_MODIFIED_SINCE` and
37 # `HTTP_IF_NONE_MATCH` against the assets mtime and md5
4daa04a @josh Raise missing source file error
josh authored
38 elsif not_modified?(asset, env) || etag_match?(asset, env)
350ccd9 @josh Docco server
josh authored
39 # Return a 403 Not Modified
4daa04a @josh Raise missing source file error
josh authored
40 not_modified_response(asset, env)
350ccd9 @josh Docco server
josh authored
41
9b7d2a5 @josh Import asset server from stagecoach
josh authored
42 else
350ccd9 @josh Docco server
josh authored
43 # Return a 200 with the asset contents
9b7d2a5 @josh Import asset server from stagecoach
josh authored
44 ok_response(asset, env)
45 end
46 end
47
350ccd9 @josh Docco server
josh authored
48 # `path` is a url helper that looks up an asset given a
49 # `logical_path` and returns a path String. By default, the
50 # asset's md5 fingerprint is spliced into the filename.
51 #
52 # /assets/application-3676d55f84497cbeadfc614c1b1b62fc.js
53 #
54 # A third `prefix` argument can be pass along to be prepended to
55 # the string.
b7950ff @josh Move url and path methods to server concern
josh authored
56 def path(logical_path, fingerprint = true, prefix = nil)
57 if fingerprint && asset = find_asset(logical_path)
4d119e9 @josh Move fingerprinting utils into static compilation concern
josh authored
58 url = path_with_fingerprint(logical_path, asset.digest)
b7950ff @josh Move url and path methods to server concern
josh authored
59 else
8920f22 @josh Move fingerprint utils out of Pathname
josh authored
60 url = logical_path
b7950ff @josh Move url and path methods to server concern
josh authored
61 end
62
63 url = File.join(prefix, url) if prefix
64 url = "/#{url}" unless url =~ /^\//
65
66 url
67 end
68
350ccd9 @josh Docco server
josh authored
69 # Similar to `path`, `url` returns a full url given a Rack `env`
70 # Hash and a `logical_path`.
b7950ff @josh Move url and path methods to server concern
josh authored
71 def url(env, logical_path, fingerprint = true, prefix = nil)
72 req = Rack::Request.new(env)
73
74 url = req.scheme + "://"
75 url << req.host
76
77 if req.scheme == "https" && req.port != 443 ||
78 req.scheme == "http" && req.port != 80
79 url << ":#{req.port}"
80 end
81
82 url << path(logical_path, fingerprint, prefix)
83
84 url
85 end
86
9b7d2a5 @josh Import asset server from stagecoach
josh authored
87 private
88 def forbidden_request?(env)
350ccd9 @josh Docco server
josh authored
89 # Prevent access to files elsewhere on the file system
90 #
91 # http://example.org/assets/../../../etc/passwd
92 #
9b7d2a5 @josh Import asset server from stagecoach
josh authored
93 env["PATH_INFO"].include?("..")
94 end
95
350ccd9 @josh Docco server
josh authored
96 # Returns a 403 Forbidden response tuple
9b7d2a5 @josh Import asset server from stagecoach
josh authored
97 def forbidden_response
98 [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
99 end
100
350ccd9 @josh Docco server
josh authored
101 # Returns a 404 Not Found response tuple
9b7d2a5 @josh Import asset server from stagecoach
josh authored
102 def not_found_response
7768aba @josh Set X-Cascade header for 404s
josh authored
103 [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
9b7d2a5 @josh Import asset server from stagecoach
josh authored
104 end
105
350ccd9 @josh Docco server
josh authored
106 # Compare the requests `HTTP_IF_MODIFIED_SINCE` against the
107 # assets mtime
9b7d2a5 @josh Import asset server from stagecoach
josh authored
108 def not_modified?(asset, env)
8134923 @josh Set asset mtime to the most recent source file mtime
josh authored
109 env["HTTP_IF_MODIFIED_SINCE"] == asset.mtime.httpdate
9b7d2a5 @josh Import asset server from stagecoach
josh authored
110 end
111
350ccd9 @josh Docco server
josh authored
112 # Compare the requests `HTTP_IF_NONE_MATCH` against the assets MD5
9b7d2a5 @josh Import asset server from stagecoach
josh authored
113 def etag_match?(asset, env)
5d74ed8 @sstephenson Move ConcatenatedAsset#etag into Server
authored
114 env["HTTP_IF_NONE_MATCH"] == etag(asset)
9b7d2a5 @josh Import asset server from stagecoach
josh authored
115 end
116
350ccd9 @josh Docco server
josh authored
117 # Returns a 304 Not Modified response tuple
9b7d2a5 @josh Import asset server from stagecoach
josh authored
118 def not_modified_response(asset, env)
119 [ 304, {}, [] ]
120 end
121
350ccd9 @josh Docco server
josh authored
122 # Returns a 200 OK response tuple
9b7d2a5 @josh Import asset server from stagecoach
josh authored
123 def ok_response(asset, env)
124 [ 200, headers(asset, env), asset ]
125 end
126
127 def headers(asset, env)
128 Hash.new.tap do |headers|
350ccd9 @josh Docco server
josh authored
129 # Set content type and length headers
9b7d2a5 @josh Import asset server from stagecoach
josh authored
130 headers["Content-Type"] = asset.content_type
131 headers["Content-Length"] = asset.length.to_s
1bb7e14 @josh Include Content-MD5 header
josh authored
132 headers["Content-MD5"] = asset.digest
9b7d2a5 @josh Import asset server from stagecoach
josh authored
133
350ccd9 @josh Docco server
josh authored
134 # Set caching headers
5a135dc @josh Permanent assets don't need must-revalidate
josh authored
135 headers["Cache-Control"] = "public"
8134923 @josh Set asset mtime to the most recent source file mtime
josh authored
136 headers["Last-Modified"] = asset.mtime.httpdate
5d74ed8 @sstephenson Move ConcatenatedAsset#etag into Server
authored
137 headers["ETag"] = etag(asset)
9b7d2a5 @josh Import asset server from stagecoach
josh authored
138
350ccd9 @josh Docco server
josh authored
139 # If the request url contains a fingerprint, set a long
140 # expires on the response
4d119e9 @josh Move fingerprinting utils into static compilation concern
josh authored
141 if path_fingerprint(env["PATH_INFO"])
2e571fa @josh Adjust year seconds
josh authored
142 headers["Cache-Control"] << ", max-age=31536000"
350ccd9 @josh Docco server
josh authored
143
144 # Otherwise set `must-revalidate` since the could be modified.
5a135dc @josh Permanent assets don't need must-revalidate
josh authored
145 else
146 headers["Cache-Control"] << ", must-revalidate"
9b7d2a5 @josh Import asset server from stagecoach
josh authored
147 end
148 end
149 end
5d74ed8 @sstephenson Move ConcatenatedAsset#etag into Server
authored
150
350ccd9 @josh Docco server
josh authored
151 # Helper to quote the assets MD5 for use as an ETag.
5d74ed8 @sstephenson Move ConcatenatedAsset#etag into Server
authored
152 def etag(asset)
3feeb3e @sstephenson md5 -> digest
authored
153 %("#{asset.digest}")
5d74ed8 @sstephenson Move ConcatenatedAsset#etag into Server
authored
154 end
49d224e @sstephenson Initial commit
authored
155 end
156 end
Something went wrong with that request. Please try again.