Sprockets::Server

`Server` is a concern mixed into `Environment` and `Index` that provides a Rack compatible `call` interface and url generation helpers.

Public Instance Methods

call(env) click to toggle source

`call` implements the Rack 1.x specification which accepts an `env` Hash and returns a three item tuple with the status code, headers, and body.

Mapping your environment at a url prefix will serve all assets in the path.

    map "/assets" do
      run Sprockets::Environment.new
    end

A request for `“/assets/foo/bar.js“` will search your environment for `“foo/bar.js“`.

    # File lib/sprockets/server.rb, line 22
22:     def call(env)
23:       start_time = Time.now.to_f
24:       time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
25: 
26:       msg = "Served asset #{env['PATH_INFO']} -"
27: 
28:       # Mark session as "skipped" so no `Set-Cookie` header is set
29:       env['rack.session.options'] ||= {}
30:       env['rack.session.options'][:defer] = true
31:       env['rack.session.options'][:skip] = true
32: 
33:       # Extract the path from everything after the leading slash
34:       path = unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
35: 
36:       # URLs containing a `".."` are rejected for security reasons.
37:       if forbidden_request?(path)
38:         return forbidden_response
39:       end
40: 
41:       # Strip fingerprint
42:       if fingerprint = path_fingerprint(path)
43:         path = path.sub("-#{fingerprint}", '')
44:       end
45: 
46:       # Look up the asset.
47:       asset = find_asset(path, :bundle => !body_only?(env))
48: 
49:       # `find_asset` returns nil if the asset doesn't exist
50:       if asset.nil?
51:         logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
52: 
53:         # Return a 404 Not Found
54:         not_found_response
55: 
56:       # Check request headers `HTTP_IF_NONE_MATCH` against the asset digest
57:       elsif etag_match?(asset, env)
58:         logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
59: 
60:         # Return a 304 Not Modified
61:         not_modified_response(asset, env)
62: 
63:       else
64:         logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
65: 
66:         # Return a 200 with the asset contents
67:         ok_response(asset, env)
68:       end
69:     rescue Exception => e
70:       logger.error "Error compiling asset #{path}:"
71:       logger.error "#{e.class.name}: #{e.message}"
72: 
73:       case content_type_of(path)
74:       when "application/javascript"
75:         # Re-throw JavaScript asset exceptions to the browser
76:         logger.info "#{msg} 500 Internal Server Error\n\n"
77:         return javascript_exception_response(e)
78:       when "text/css"
79:         # Display CSS asset exceptions in the browser
80:         logger.info "#{msg} 500 Internal Server Error\n\n"
81:         return css_exception_response(e)
82:       else
83:         raise
84:       end
85:     end

Private Instance Methods

body_only?(env) click to toggle source

Test if `?body=1` or `body=true` query param is set

     # File lib/sprockets/server.rb, line 182
182:       def body_only?(env)
183:         env["QUERY_STRING"].to_s =~ /body=(1|t)/
184:       end
css_exception_response(exception) click to toggle source

Returns a CSS response that hides all elements on the page and displays the exception

     # File lib/sprockets/server.rb, line 116
116:       def css_exception_response(exception)
117:         message   = "\n#{exception.class.name}: #{exception.message}"
118:         backtrace = "\n  #{exception.backtrace.first}"
119: 
120:         body =           html {            padding: 18px 36px;          }          head {            display: block;          }          body {            margin: 0;            padding: 0;          }          body > * {            display: none !important;          }          head:after, body:before, body:after {            display: block !important;          }          head:after {            font-family: sans-serif;            font-size: large;            font-weight: bold;            content: "Error compiling CSS asset";          }          body:before, body:after {            font-family: monospace;            white-space: pre-wrap;          }          body:before {            font-weight: bold;            content: "#{escape_css_content(message)}";          }          body:after {            content: "#{escape_css_content(backtrace)}";          }
121: 
122:         [ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
123:       end
escape_css_content(content) click to toggle source

Escape special characters for use inside a CSS content(“…”) string

     # File lib/sprockets/server.rb, line 168
168:       def escape_css_content(content)
169:         content.
170:           gsub('\', '\\005c ').
171:           gsub("\n", '\\000a ').
172:           gsub('"',  '\\0022 ').
173:           gsub('/',  '\\002f ')
174:       end
etag(asset) click to toggle source

Helper to quote the assets digest for use as an ETag.

     # File lib/sprockets/server.rb, line 243
243:       def etag(asset)
244:         %("#{asset.digest}")
245:       end
etag_match?(asset, env) click to toggle source

Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest

     # File lib/sprockets/server.rb, line 177
177:       def etag_match?(asset, env)
178:         env["HTTP_IF_NONE_MATCH"] == etag(asset)
179:       end
forbidden_request?(path) click to toggle source
    # File lib/sprockets/server.rb, line 88
88:       def forbidden_request?(path)
89:         # Prevent access to files elsewhere on the file system
90:         #
91:         #     http://example.org/assets/../../../etc/passwd
92:         #
93:         path.include?("..")
94:       end
forbidden_response() click to toggle source

Returns a 403 Forbidden response tuple

    # File lib/sprockets/server.rb, line 97
97:       def forbidden_response
98:         [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
99:       end
headers(env, asset, length) click to toggle source
     # File lib/sprockets/server.rb, line 196
196:       def headers(env, asset, length)
197:         Hash.new.tap do |headers|
198:           # Set content type and length headers
199:           headers["Content-Type"]   = asset.content_type
200:           headers["Content-Length"] = length.to_s
201: 
202:           # Set caching headers
203:           headers["Cache-Control"]  = "public"
204:           headers["Last-Modified"]  = asset.mtime.httpdate
205:           headers["ETag"]           = etag(asset)
206: 
207:           # If the request url contains a fingerprint, set a long
208:           # expires on the response
209:           if path_fingerprint(env["PATH_INFO"])
210:             headers["Cache-Control"] << ", max-age=31536000"
211: 
212:           # Otherwise set `must-revalidate` since the asset could be modified.
213:           else
214:             headers["Cache-Control"] << ", must-revalidate"
215:           end
216:         end
217:       end
javascript_exception_response(exception) click to toggle source

Returns a JavaScript response that re-throws a Ruby exception in the browser

     # File lib/sprockets/server.rb, line 108
108:       def javascript_exception_response(exception)
109:         err  = "#{exception.class.name}: #{exception.message}"
110:         body = "throw Error(#{err.inspect})"
111:         [ 200, { "Content-Type" => "application/javascript", "Content-Length" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
112:       end
not_found_response() click to toggle source

Returns a 404 Not Found response tuple

     # File lib/sprockets/server.rb, line 102
102:       def not_found_response
103:         [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
104:       end
not_modified_response(asset, env) click to toggle source

Returns a 304 Not Modified response tuple

     # File lib/sprockets/server.rb, line 187
187:       def not_modified_response(asset, env)
188:         [ 304, {}, [] ]
189:       end
ok_response(asset, env) click to toggle source

Returns a 200 OK response tuple

     # File lib/sprockets/server.rb, line 192
192:       def ok_response(asset, env)
193:         [ 200, headers(env, asset, asset.length), asset ]
194:       end
path_fingerprint(path) click to toggle source

Gets digest fingerprint.

    "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
    # => "0aa2105d29558f3eb790d411d7d8fb66"
     # File lib/sprockets/server.rb, line 224
224:       def path_fingerprint(path)
225:         path[/-([0-9a-f]{7,40})\.[^.]+$/, 1]
226:       end
unescape(str) click to toggle source
     # File lib/sprockets/server.rb, line 231
231:         def unescape(str)
232:           str = URI::DEFAULT_PARSER.unescape(str)
233:           str.force_encoding(Encoding.default_internal) if Encoding.default_internal
234:           str
235:         end
unescape(str) click to toggle source
     # File lib/sprockets/server.rb, line 237
237:         def unescape(str)
238:           URI.unescape(str)
239:         end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.