`Server` is a concern mixed into `Environment` and `Index` that provides a Rack compatible `call` interface and url generation helpers.
`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
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
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 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
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
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
# 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
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
# 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
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
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
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
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
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
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.