split up config.ru

This commit is contained in:
Sven Fuchs 2012-09-30 15:03:42 +02:00
parent fbee663505
commit 73fe04185d
16 changed files with 459 additions and 68 deletions

11
Gemfile
View File

@ -28,8 +28,19 @@ group :assets do
gem 'guard'
end
group :development, :test do
gem 'rake', '~> 0.9.2'
end
group :development do
gem 'foreman'
gem 'rerun'
gem 'rb-fsevent', '~> 0.9.1'
end
group :test do
gem 'rspec', '~> 2.11'
gem 'factory_girl', '~> 2.4.0'
gem 'mocha', '~> 0.12'
end

View File

@ -60,7 +60,7 @@ GIT
GIT
remote: git://github.com/travis-ci/travis-core.git
revision: ddbb703d196834b7f82c6a86eff4d40b27ce588b
revision: 60f38e45ce1d739894839ef74f405348fb5f8481
branch: sf-travis-api
specs:
travis-core (0.0.1)
@ -149,10 +149,13 @@ GEM
debugger-linecache (1.1.2)
debugger-ruby_core_source (>= 1.1.1)
debugger-ruby_core_source (1.1.3)
diff-lcs (1.1.3)
erubis (2.7.0)
eventmachine (1.0.0)
execjs (1.4.0)
multi_json (~> 1.0)
factory_girl (2.4.2)
activesupport
faraday (0.8.4)
multipart-post (~> 1.1)
foreman (0.60.0)
@ -173,11 +176,14 @@ GEM
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
metaclass (0.0.1)
metriks (0.9.9.1)
atomic (~> 1.0)
avl_tree (~> 1.1.2)
hitimes (~> 1.1)
mime-types (1.19)
mocha (0.12.6)
metaclass (~> 0.0.1)
multi_json (1.3.6)
multipart-post (1.1.5)
net-http-persistent (2.7)
@ -221,6 +227,14 @@ GEM
rerun (0.7.1)
listen
rollout (1.1.0)
rspec (2.11.0)
rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.1)
rspec-expectations (2.11.3)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.3)
sass (3.2.1)
signature (0.1.4)
simple_states (0.1.1)
@ -265,17 +279,21 @@ DEPENDENCIES
coffee-script
compass
debugger
factory_girl (~> 2.4.0)
foreman
gh!
guard
hubble!
mocha (~> 0.12)
newrelic_rpm (~> 3.3.0)
pg (~> 0.13.2)
rack-contrib!
rake (~> 0.9.2)
rake-pipeline!
rake-pipeline-web-filters!
rb-fsevent (~> 0.9.1)
rerun
rspec (~> 2.11)
sinatra
sinatra-contrib
tilt

View File

@ -1,68 +1,7 @@
require 'travis'
require 'travis/api/app'
# Make sure we set that before everything
ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] || ENV['ENV']
ENV['RAILS_ENV'] = ENV['RACK_ENV']
env, api_endpoint, client_endpoint, run_api, watch, deflate = ENV.values_at('RACK_ENV', 'API_ENDPOINT', 'CLIENT_ENDPOINT', 'RUN_API', 'WATCH', 'DEFLATE')
env ||= "development"
api_endpoint ||= "https://api.#{Travis.config.host}" unless run_api and run_api != '0'
run_api ||= api_endpoint.to_s.start_with? '/'
api_endpoint ||= "/api" if run_api and run_api != '0'
client_endpoint ||= "/"
watch ||= false #env == "development"
deflate ||= env == "production"
c = proc do |value|
case value
when nil, false, '0' then "\e[31m0\e[0m"
when true, '1' then "\e[32m1\e[0m"
else "\e[33m\"#{value}\"\e[0m"
end
end
$stderr.puts "RACK_ENV = #{c[env]}",
"API_ENDPOINT = #{c[api_endpoint]}",
"CLIENT_ENDPOINT = #{c[client_endpoint]}",
"RUN_API = #{c[run_api]}",
"WATCH = #{c[watch]}",
"DEFLATE = #{c[deflate]}"
class EndpointSetter < Struct.new(:app, :endpoint)
DEFAULT_ENDPOINT = 'https://api.travis-ci.org'
def call(env)
status, headers, body = app.call(env)
if endpoint != DEFAULT_ENDPOINT and headers.any? { |k,v| k.downcase == 'content-type' and v.start_with? 'text/html' }
headers.delete 'Content-Length'
body, old = [], body
old.each { |s| body << s.gsub(DEFAULT_ENDPOINT, endpoint) }
old.close if old.respond_to? :close
end
[status, headers, body]
end
end
use Rack::SSL if env == 'production'
use Rack::Deflater if deflate and deflate != '0'
app = proc do |env|
Rack::File.new(nil).tap { |f| f.path = 'public/index.html' }.serving(env)
end
if run_api and run_api != '0'
map api_endpoint.gsub(/:\d+/, '') do
run Travis::Api::App.new
end
end
map client_endpoint do
use EndpointSetter, api_endpoint
if watch and watch != '0'
require 'rake-pipeline'
require 'rake-pipeline/middleware'
use Rake::Pipeline::Middleware, 'AssetFile'
run app
else
run Rack::Cascade.new([Rack::File.new('public'), app])
end
end
$: << 'lib'
require 'travis/web'
run Travis::Web::App.new

5
lib/travis/web.rb Normal file
View File

@ -0,0 +1,5 @@
module Travis
module Web
autoload :App, 'travis/web/app'
end
end

48
lib/travis/web/app.rb Normal file
View File

@ -0,0 +1,48 @@
require 'rack'
require 'rack/protection/path_traversal'
module Travis::Web
class App
autoload :Api, 'travis/web/app/api'
autoload :Config, 'travis/web/app/config'
autoload :Files, 'travis/web/app/files'
autoload :Filter, 'travis/web/app/filter'
autoload :Terminal, 'travis/web/app/terminal'
autoload :Version, 'travis/web/app/version'
Rack.autoload :SSL, 'rack/ssl'
Rack.autoload :Deflater, 'rack/deflater'
include Terminal
attr_accessor :app
def initialize
config = Config.new
announce(config)
@app = Rack::Builder.app do
use Rack::SSL if config.production?
use Rack::Protection::PathTraversal
use Rack::Deflater if config.deflate?
# TODO this doesn't work, how can i extract this to a separate file/class
# use Travis::Web::App::Api, config if config.run_api?
if config.run_api?
require 'travis/api/app'
map config.api_endpoint do
run Travis::Api::App.new
end
end
use Travis::Web::App::Version, config
use Travis::Web::App::Filter, config
run Travis::Web::App::Files.new
end
end
def call(env)
app.call(env)
end
end
end

32
lib/travis/web/app/api.rb Normal file
View File

@ -0,0 +1,32 @@
require 'travis/api/app'
class Travis::Web::App
class Api
attr_reader :app, :api, :config
def initialize(app, config)
@app = app
@api = Travis::Api::App.new
@config = config
end
def call(env)
path = env['PATH_INFO']
if matches?(path)
api.call(env.merge('PATH_INFO' => api_path(path)))
else
app.call(env)
end
end
def matches?(path)
# TODO there's a redirect through /auth/post_message which doesn't have the /api
# prefix. is that safe_redirect in travis-api? not sure how to solve this
path.starts_with?(config.api_endpoint) || path.starts_with?('/auth')
end
def api_path(path)
path.sub(/^#{config.api_endpoint}/, '')
end
end
end

View File

@ -0,0 +1,71 @@
class Travis::Web::App
class Config
OPTIONS = %w(ENV API_ENDPOINT CLIENT_ENDPOINT RUN_API WATCH DEFLATE)
def keys
@keys ||= OPTIONS.map(&:downcase)
end
def each
keys.each do |key|
yield key, send(key)
end
end
def env
config.fetch(:env, 'development')
end
def production?
env == 'production'
end
def run_api?
!!config.fetch(:run_api, config[:api_endpoint].to_s.start_with?('/'))
end
def api_endpoint
config.fetch(:api_endpoint, run_api? ? '/api' : "https://api.travis-ci.org").gsub(/:\d+/, '')
end
def client_endpoint
config.fetch(:client_endpoint, '/')
end
def deflate?
!!config.fetch(:deflate, production?)
end
def watch?
!!config.fetch(:watch, false)
end
alias run_api run_api?
alias deflate deflate?
alias watch watch?
def version
production? ? @version ||= read_version : read_version
end
private
def config
@config ||= Hash[*OPTIONS.map do |key|
[key.downcase.to_sym, cast(ENV[key])] if ENV.key?(key)
end.compact.flatten]
end
def cast(value)
case value
when '1', 'true' then true
when '0', 'false' then false
else value
end
end
def read_version
File.read('public/version').chomp
end
end
end

View File

@ -0,0 +1,17 @@
class Travis::Web::App
class Files < Rack::Cascade
def initialize
super([public_dir, index])
end
def public_dir
Rack::File.new('public')
end
def index
proc do |env|
Rack::File.new(nil).tap { |f| f.path = 'public/index.html' }.serving(env)
end
end
end
end

View File

@ -0,0 +1,34 @@
class Travis::Web::App
class Filter
autoload :Endpoint, 'travis/web/app/filter/endpoint'
autoload :Version, 'travis/web/app/filter/version'
attr_reader :app, :config, :filters
def initialize(app, config)
@app = app
@config = config
@filters = [Endpoint.new(config), Version.new(config)]
end
def call(env)
status, headers, body = app.call(env)
headers, body = filter(headers, body) if content_type?(headers, 'text/html')
[status, headers, body]
end
private
def filter(headers, body)
headers.delete 'Content-Length' # why don't we just set this to the new length?
filtered = []
body.each { |s| filtered << filters.inject(s) { |s, filter| filter.apply(s) } }
body.close if body.respond_to?(:close)
[headers, filtered]
end
def content_type?(headers, type)
headers.any? { |key, value| key.downcase == 'content-type' and value.start_with?(type) }
end
end
end

View File

@ -0,0 +1,15 @@
class Travis::Web::App::Filter
class Endpoint
DEFAULT_ENDPOINT = 'https://api.travis-ci.org'
attr_reader :config
def initialize(config)
@config = config
end
def apply(string)
string.gsub(DEFAULT_ENDPOINT, config.api_endpoint)
end
end
end

View File

@ -0,0 +1,16 @@
class Travis::Web::App::Filter
class Version
ASSET_DIRS = %r(/(stylesheets|javascripts)/)
attr_reader :config
def initialize(config)
@config = config
end
def apply(string)
string.gsub(ASSET_DIRS) { |match| "/#{config.version}/#{$1}/" }
end
end
end

View File

@ -0,0 +1,17 @@
class Travis::Web::App
module Terminal
def announce(config)
config.each do |key, value|
$stderr.puts("#{key.upcase.rjust(15)} = #{colorize(config.send(key))}")
end
end
def colorize(value)
case value
when nil, false, '0' then "\e[31m0\e[0m"
when true, '1' then "\e[32m1\e[0m"
else "\e[33m#{value}\e[0m"
end
end
end
end

View File

@ -0,0 +1,35 @@
class Travis::Web::App
class Version
attr_reader :app, :config
def initialize(app, config)
@app = app
@config = config
end
def call(env)
path = env['PATH_INFO']
if pass?(path)
app.call(env)
elsif versioned?(path)
app.call(env.merge('PATH_INFO' => strip_version(path)))
else
[404, { 'Content-Type' => 'text/html', 'Content-Length' => '9' }, ['not found']]
end
end
private
def pass?(path)
['/', '/index.html', 'current'].include?(path)
end
def versioned?(path)
path.starts_with?("/#{config.version}/")
end
def strip_version(path)
path.sub(%r(/#{config.version}/), '')
end
end
end

1
public/version Normal file
View File

@ -0,0 +1 @@
1

113
spec/app/config_spec.rb Normal file
View File

@ -0,0 +1,113 @@
require 'spec_helper'
describe Travis::Web::App::Config do
let(:config) { Travis::Web::App::Config.new }
before :each do
@env = ENV.clone
ENV.clear
end
after :each do
ENV.replace(@env)
end
describe 'env' do
it 'given ENV=foo it returns foo' do
ENV['ENV'] = 'foo'
config.env.should == 'foo'
end
it 'defaults to development' do
config.env.should == 'development'
end
end
describe 'run_api?' do
it 'given RUN_API=1 it returns true' do
ENV['RUN_API'] = '1'
config.run_api?.should be_true
end
it 'given RUN_API=0 it returns false' do
ENV['RUN_API'] = '0'
config.run_api?.should be_false
end
it 'defaults to true if api_endpoint is local' do
ENV['API_ENDPOINT'] = '/api'
config.run_api?.should be_true
end
it 'defaults to false if api_endpoint is not local' do
ENV['API_ENDPOINT'] = 'https://api.travis-ci.com'
config.run_api?.should be_false
end
end
describe 'api_endpoint' do
it 'given API_ENDPOINT=https://api.travis-ci.com it returns the given url' do
ENV['API_ENDPOINT'] = 'https://api.travis-ci.com'
config.api_endpoint.should == 'https://api.travis-ci.com'
end
it 'defaults to /api if run_api? is true' do
config.stubs(:run_api?).returns(true)
config.api_endpoint.should == '/api'
end
it 'defaults to https://api.travis-ci.org if run_api? is false' do
config.stubs(:run_api?).returns(false)
config.api_endpoint.should == 'https://api.travis-ci.org'
end
end
describe 'client_endpoint' do
it 'given CLIENT_ENDPOINT=/client it returns the given url' do
ENV['CLIENT_ENDPOINT'] = '/client'
config.client_endpoint.should == '/client'
end
it 'defaults to /' do
config.client_endpoint.should == '/'
end
end
describe 'deflate?' do
it 'given DEFLATE=1 it returns true' do
ENV['DEFLATE'] = '1'
config.deflate.should be_true
end
it 'given DEFLATE=0 it returns false' do
ENV['DEFLATE'] = '0'
config.deflate.should be_false
end
it 'defaults to true if env is production' do
config.stubs(:env).returns('production')
config.deflate.should be_true
end
it 'defaults to false if env is not production' do
config.stubs(:env).returns('development')
config.deflate.should be_false
end
end
describe 'watch?' do
it 'given WATCH=1 it returns true' do
ENV['WATCH'] = '1'
config.watch?.should be_true
end
it 'given WATCH=0 it returns false' do
ENV['WATCH'] = '0'
config.watch?.should be_false
end
it 'defaults to false' do
config.watch?.should be_false
end
end
end

19
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,19 @@
# ENV['RACK_ENV'] = ENV['RAILS_ENV'] = ENV['ENV'] = 'test'
require 'rspec'
require 'travis/web'
require 'sinatra/test_helpers'
# require 'logger'
# require 'gh'
# require 'multi_json'
RSpec.configure do |config|
config.mock_framework = :mocha
config.expect_with :rspec, :stdlib
# config.include TestHelpers
# config.before :each do
# set_app Travis::Web::App.new
# end
end