simplify app creation and rename "custom_branch" to "alt"

This commit is contained in:
Sven Fuchs 2013-02-25 17:42:03 +01:00
parent 86d8ebbdcb
commit bc4092ad4d
3 changed files with 103 additions and 102 deletions

View File

@ -25,7 +25,7 @@ use Travis::Web::ApiRedirect do |app|
app.settings.api_endpoint = ENV['API_ENDPOINT'] if ENV['API_ENDPOINT']
end
run Travis::Web::App.new(
run Travis::Web::App.build(
environment: ENV['RACK_ENV'] || 'development',
api_endpoint: ENV['API_ENDPOINT'],
pusher_key: ENV['PUSHER_KEY'],

View File

@ -8,6 +8,8 @@ require 'time'
class Travis::Web::App
autoload :MobileRedirect, 'travis/web/app/mobile_redirect'
S3_URL = 'https://s3.amazonaws.com/travis-web-production/assets'
# Simple Rack router that behaves like a hash.
# Key is the path, value the response.
class Router < DelegateClass(Hash)
@ -18,90 +20,110 @@ class Travis::Web::App
end
def call(env)
if main_app.custom_branch?(env)
main_app.response_for_custom_branch(env)
else
self[env['PATH_INFO']]
end
self[env['PATH_INFO']]
end
end
def self.new(options = {})
return super unless options[:environment] == 'development'
proc { |e| super.call(e) } # poor man's reloader
class AltVersions
attr_reader :app
def initialize(app)
@app = app
end
def call(env)
alt = alt_from(env)
env['travis.alt'] = alt if alt
status, headers, body = app.call(env)
set_cookies(headers, env['travis.alt']) if env.key?('travis.alt')
[status, headers, body]
end
def set_cookies(headers, alt)
headers['Set-Cookie'] = "alt=#{alt}; Max-Age=#{alt == 'default' ? 0 : 86400}"
end
def alt_from(env)
alt_from_params(env) || alt_from_cookie(env)
end
def alt_from_params(env)
alt_from_string env['QUERY_STRING']
end
def alt_from_cookie(env)
alt_from_string env['HTTP_COOKIE']
end
def alt_from_string(string)
$1 if string =~ /alt=([^&]*)/
end
end
attr_reader :app, :router, :environment, :version, :last_modified, :age, :options, :root
class << self
def new(options = {})
return super unless options[:environment] == 'development'
proc { |e| super.call(e) } # poor man's reloader
end
def build(options = {})
builder = Rack::Builder.new
if options.fetch(:environment) == 'production'
builder.use Rack::SSL
builder.use Rack::Cache
end
builder.use Rack::Deflater
builder.use Rack::Head
builder.use Rack::Protection::XSSHeader
builder.use Rack::Protection::FrameOptions
builder.use Rack::Protection::PathTraversal
builder.use Rack::ConditionalGet
builder.use Travis::Web::App::AltVersions
builder.run new(options)
builder.to_app
end
end
attr_reader :routers, :environment, :version, :last_modified, :age, :options, :root
def initialize(options = {})
@options = options
@environment = options.fetch(:environment)
@root = options.fetch(:root)
@router = Router.new(self)
@app = builder.to_app
@version = File.read File.expand_path('version', root)
@last_modified = Time.now
@age = 60 * 60 * 24 * 365
load_routes
@routers = { default: create_router }
end
def call(env)
app.call(env)
end
def response_for_custom_branch(env)
status, headers, body = response_for File.join(root, 'index.html'), custom_branch: custom_branch(env)
response = Rack::Response.new body, status, headers
if disable_custom_branch?(env)
response.delete_cookie 'custom_branch'
elsif custom_branch_from_params(env)
response.set_cookie 'custom_branch', value: custom_branch_from_params(env), expires: Time.now + 31536000
end
response.finish
end
def custom_branch?(env)
custom_branch(env) || disable_custom_branch?(env)
name = env['travis.alt'] || :default
routers[name] ||= create_router(alt: name)
routers[name].call(env)
end
private
def disable_custom_branch?(env)
env['QUERY_STRING'] =~ /disable[_-]custom[_-]branch/
def create_router(options = {})
router = Router.new(self)
load_routes(router, options)
router
end
def custom_branch_from_params(env)
branch = custom_branch_from_string env['QUERY_STRING']
end
def custom_branch_from_cookie(env)
custom_branch_from_string env['HTTP_COOKIE']
end
def custom_branch_from_string(string)
$1 if string =~ /(?<!disable.)custom[_-]branch=([^&]+)/
end
def custom_branch(env)
custom_branch_from_params(env) || custom_branch_from_cookie(env)
end
def load_routes
each_file { |f| router[route_for(f)] = response_for(f) }
def load_routes(router, options = {})
each_file { |file| router[path_for(file)] = response_for(file, options) }
router.default = router['/']
end
def response_for(file, options = {})
content = File.read(file)
set_config(content, options) if config_needed? file
set_config(content, options) if config_needed?(file)
headers = {
'Content-Length' => content.bytesize.to_s,
'Content-Location' => route_for(file),
'Content-Location' => path_for(file),
'Cache-Control' => cache_control(file),
'Content-Location' => route_for(file),
'Content-Location' => path_for(file),
'Content-Type' => mime_type(file),
'ETag' => version,
'Last-Modified' => last_modified.httpdate,
@ -109,7 +131,7 @@ class Travis::Web::App
'Vary' => vary_for(file)
}
[ 200, headers, [ content ] ]
[ 200, headers, [content] ]
end
def each_file
@ -126,18 +148,11 @@ class Travis::Web::App
end
def index?(file)
file.end_with? 'index.html'
end
def route_for(file)
file = file.sub("#{root}/", '')
file = File.join(version, file) if prefix? file
file = "" if index? file
"/#{file}"
file.end_with?('index.html')
end
def cache_control(file)
case route_for(file)
case path_for(file)
when '/' then "public, must-revalidate"
when '/version' then "no-cache"
else "public, max-age=#{age}"
@ -145,13 +160,20 @@ class Travis::Web::App
end
def vary_for(file)
case route_for(file)
case path_for(file)
when '/' then 'Accept'
when '/version' then '*'
else ''
end
end
def path_for(file)
file = file.sub("#{root}/", '')
file = File.join(version, file) if prefix?(file)
file = "" if index?(file)
"/#{file}"
end
def mime_type(file)
Rack::Mime.mime_type File.extname(file)
end
@ -162,28 +184,7 @@ class Travis::Web::App
end
string.gsub! %r{(src|href)="(?:\/?)((styles|scripts)\/[^"]*)"} do
if opts[:custom_branch]
url = "https://s3.amazonaws.com/travis-web-production/assets/#{opts[:custom_branch]}/#{$2}"
%(#$1="#{url}")
else
%(#$1="/#{version}/#$2")
end
%(#{$1}=#{opts[:alt] ? "#{S3_URL}/#{opts[:alt]}/#{$2}" : "/#{version}/#{$2}"})
end
end
def builder
builder = Rack::Builder.new
if environment == 'production'
builder.use Rack::SSL
builder.use Rack::Cache
end
builder.use Rack::Deflater
builder.use Rack::Head
builder.use Rack::Protection::XSSHeader
builder.use Rack::Protection::FrameOptions
builder.use Rack::Protection::PathTraversal
builder.use Rack::ConditionalGet
builder.run router
builder
end
end

View File

@ -6,7 +6,7 @@ describe Travis::Web::App do
end
describe 'catch all' do
before { get('/foo/bar') }
before { get('/foo/bar') }
example { last_response.should be_ok }
example { headers['Content-Location'].should be == '/' }
example { headers['Cache-Control'].should include('must-revalidate') }
@ -15,7 +15,7 @@ describe Travis::Web::App do
end
describe 'assets' do
before { get('/favicon.ico') }
before { get('/favicon.ico') }
example { last_response.should be_ok }
example { headers['Content-Location'].should be == '/favicon.ico' }
example { headers['Cache-Control'].should_not include('must-revalidate') }
@ -24,30 +24,30 @@ describe Travis::Web::App do
end
describe 'version' do
before { get('/version') }
before { get('/version') }
example { last_response.should be_ok }
example { headers['Content-Location'].should be == '/version' }
example { headers['Cache-Control'].should be == 'no-cache' }
example { headers['Vary'].split(',').should_not include('Accept') }
end
describe 'custom branch' do
context 'when passing custom branch as a param' do
before { get('/?custom-branch=foo') }
describe 'alternate asset versions' do
context 'not passing an alt param' do
before { get('/') }
example { headers['Set-Cookie'].should be_nil }
end
context 'passing an alt param' do
before { get('/?alt=foo') }
example { last_response.should be_ok }
example { last_response.body.should include('/assets/foo/styles/app.css') }
example { last_response.body.should include('/assets/foo/scripts/app.js') }
example { headers['Set-Cookie'].should include('custom_branch=foo') }
example { headers['Set-Cookie'].should == 'alt=foo; Max-Age=86400' }
end
context 'disabling custom branch' do
before { get('/?disable-custom-branch=true') }
example { last_response.should be_ok }
example { last_response.body.should =~ %r{src="/[^\/]+/scripts/app.js} }
example { last_response.body.should_not include('/assets/true/styles/app.css') }
example { last_response.body.should_not include('/assets/foo/styles/app.css') }
example { last_response.body.should_not include('/assets/foo/scripts/app.js') }
example { headers['Set-Cookie'].should include('custom_branch=;') }
context 'passing default as an alt param' do
before { get('/?alt=default') }
example { headers['Set-Cookie'].should == 'alt=default; Max-Age=0' }
end
end
end