rambo 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,32 @@
1
+ To install the gem:
2
+
3
+ gem sources -a http://gems.github.com
4
+ sudo gem install moomerman-rambo
5
+
6
+ Smallest example (see the hello app in the example folder)
7
+
8
+ class HomeController < Rambo::Controller
9
+ def index
10
+ "hello world"
11
+ end
12
+ end
13
+
14
+ To run the Blog example:
15
+
16
+ git clone git://github.com/moomerman/rambo.git
17
+ cd rambo/example/blog/
18
+ rake db:setup # check config.yml for your specific db settings
19
+ rake server
20
+
21
+ head over to http://localhost:4000/
22
+
23
+ Features:
24
+ = No Ruby object base class modifications
25
+ = Lightweight and fast
26
+ = Rack-based so works with most web servers (thin, mongrel, passenger)
27
+ = Ability to proxy request to n backend app servers [experimental]
28
+ = Request caching built in
29
+ = fast static file serving
30
+ = works with erb or haml templates (more to come)
31
+ = DataMapper integration
32
+ = Library only 60k
@@ -0,0 +1,4 @@
1
+ require 'rambo/server'
2
+
3
+ module Rambo
4
+ end
@@ -0,0 +1,52 @@
1
+ module Rambo
2
+ class ApplicationContext
3
+ attr_accessor :application_name
4
+
5
+ def initialize(application_name = nil)
6
+ @application_name = application_name
7
+ puts "Initializing application: #{application_name || 'default'}"
8
+ @prefix = "#{self.application_name}/" if self.application_name
9
+ @prefix ||= ''
10
+ load_classes
11
+ end
12
+
13
+ def load_classes
14
+ Dir["#{@prefix}controller/*.rb"].each { |x| funkyload x; }
15
+ Dir["#{@prefix}model/*.rb"].each { |x| funkyload x }
16
+ Dir["#{@prefix}lib/*.rb"].each { |x| funkyload x }
17
+ Dir["#{@prefix}*.rb"].each { |x| funkyload x unless x == 'Rakefile.rb' }
18
+ end
19
+
20
+ def reload
21
+ load_classes
22
+ end
23
+
24
+ def view_path
25
+ "./#{@prefix}view"
26
+ end
27
+
28
+ private
29
+ def funkyload(file)
30
+ @@loadcache ||= {}
31
+ begin
32
+ if cache = @@loadcache[file]
33
+ return if Env.config['rambo'] and Env.config['rambo']['reload_classes'] == false
34
+ if (mtime = File.mtime(file)) > cache
35
+ puts "rambo: reloading: #{file}"
36
+ load file
37
+ @@loadcache[file] = mtime
38
+ end
39
+ else
40
+ puts " -> loading: #{file}"
41
+ load file
42
+ @@loadcache[file] = File.mtime(file)
43
+ end
44
+ rescue Exception => e
45
+ puts "Exception loading class [#{file}]: #{e.message}"
46
+ puts e.backtrace.join("\n")
47
+ raise e
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ module Rambo
2
+ class ApplicationRequest < Rambo::Request
3
+
4
+ def initialize(env)
5
+ super
6
+ end
7
+
8
+ def application
9
+ path_components[1]
10
+ end
11
+
12
+ def controller
13
+ path_components[2] || default_controller
14
+ end
15
+
16
+ def action
17
+ path_components[3] || 'index'
18
+ end
19
+
20
+ def controller_class
21
+ self.application.gsub(/^[a-z]|\s+[a-z]/) { |a| a.upcase } + '::' + super
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ require 'rambo/controller/template'
2
+ require 'rambo/controller/cache'
3
+ require 'rambo/controller/params'
4
+ require 'rambo/controller/redirect'
5
+
6
+ module Rambo
7
+ class Controller
8
+ attr_accessor :request, :response
9
+
10
+ include Template
11
+ include Cache
12
+ include Params
13
+ include Redirect
14
+
15
+ def already_rendered?
16
+ @rendered
17
+ end
18
+
19
+ def controller
20
+ self.request.controller
21
+ end
22
+
23
+ def action
24
+ self.request.action
25
+ end
26
+
27
+ def session
28
+ request.env['rack.session'] ||= {}
29
+ end
30
+
31
+ def host
32
+ self.request.env['HTTP_HOST']
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ module Cache
2
+ def fresh?(model, options={})
3
+ @@etags ||= {}
4
+ etag = Digest::SHA1.hexdigest(model.inspect)
5
+ response.header['ETag'] = "\"#{etag}\""
6
+ response.header['Expires'] = (MooTime.now + options[:expires_in]).httpdate if options[:expires_in]
7
+ response.header['Cache-Control'] = 'public'
8
+ if @@etags[request.uri] == etag
9
+ response.status = 304
10
+ response.body = ''
11
+ return true
12
+ else
13
+ @@etags[request.uri] = etag
14
+ return false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ module Params
2
+ def params
3
+ if !self.request.params.keys.join.include?('[')
4
+ @params ||= indifferent_hash.merge(self.request.params)
5
+ else
6
+ @params ||= self.request.params.inject indifferent_hash do |res, (key,val)|
7
+ if key.include?('[')
8
+ head = key.split(/[\]\[]+/)
9
+ last = head.pop
10
+ head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
11
+ else
12
+ res[key] = val
13
+ end
14
+ res
15
+ end
16
+ end
17
+ if request.path_components.size() > 2
18
+ @params.merge!(:id => request.path_components[3])
19
+ end
20
+ @params
21
+ end
22
+
23
+ def indifferent_hash
24
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ module Redirect
2
+ def redirect(destination, options={})
3
+ destination = destination.to_s if destination.is_a? Symbol
4
+ unless destination[0,1] == "/" or destination =~ /^http:\/\// or destination =~ /^file:\/\//
5
+ destination = "/#{self.controller}/#{destination}"
6
+ end
7
+ puts "redirecting to #{destination}"
8
+
9
+ @rendered = true
10
+ response.status = 302
11
+ response.header['Location'] = destination
12
+ return ""
13
+ end
14
+ end
@@ -0,0 +1,80 @@
1
+ module Template
2
+
3
+ @@template_cache = {}
4
+
5
+ def erb(template, options={}, locals={})
6
+ require 'erb' unless defined? ::ERB
7
+ render :erb, template, options, locals
8
+ end
9
+
10
+ def haml(template, options={}, locals={})
11
+ require 'haml' unless defined? ::Haml
12
+ render :haml, template, options, locals
13
+ end
14
+
15
+ private
16
+ def render(engine, template, options={}, locals={})
17
+ # extract generic options
18
+ layout = options.delete(:layout)
19
+ layout = :layout if layout.nil? || layout == true
20
+ views = options.delete(:views) || self.request.application_context.view_path
21
+ locals = options.delete(:locals) || locals || {}
22
+
23
+ # render template
24
+ data, options[:filename], options[:line] = lookup_template(engine, template, views)
25
+ output = __send__("render_#{engine}", data, options, locals)
26
+
27
+ # render layout
28
+ if layout
29
+ data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
30
+ if data
31
+ output = __send__("render_#{engine}", data, options, locals) { output }
32
+ end
33
+ end
34
+
35
+ output
36
+ end
37
+
38
+ def lookup_template(engine, template, views_dir)
39
+ case template
40
+ when Symbol
41
+ # if cached = @@template_cache[template]
42
+ # cached
43
+ # else
44
+ path = ::File.join(views_dir, "#{template}.#{engine}")
45
+ [ ::File.read(path), path, 1 ]
46
+ # @@template_cache[template] = res
47
+ # res
48
+ #end
49
+ when Proc
50
+ [template.call, template, 1]
51
+ when String
52
+ [template, template, 1]
53
+ else
54
+ raise ArgumentError
55
+ end
56
+ end
57
+
58
+ def lookup_layout(engine, template, views_dir)
59
+ lookup_template(engine, template, views_dir)
60
+ rescue Errno::ENOENT
61
+ nil
62
+ end
63
+
64
+ def render_erb(data, options, locals, &block)
65
+ original_out_buf = @_out_buf
66
+ data = data.call if data.kind_of? Proc
67
+
68
+ instance = ::ERB.new(data, nil, nil, '@_out_buf')
69
+ locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
70
+
71
+ src = "#{locals_assigns.join("\n")}\n#{instance.src}"
72
+ eval src, binding, '(__ERB__)', locals_assigns.length + 1
73
+ @_out_buf, result = original_out_buf, @_out_buf
74
+ result
75
+ end
76
+
77
+ def render_haml(data, options, locals, &block)
78
+ ::Haml::Engine.new(data, options).render(self, locals, &block)
79
+ end
80
+ end
@@ -0,0 +1,43 @@
1
+ # Env handles all the configuration loading, database initialization and class (re)loading
2
+ # would be nice to allow the user to specify their own init though if they want
3
+ # Env.new can be called anywhere in any application and therefore acts as a global config
4
+ module Rambo
5
+ class Env
6
+ def self.config
7
+ @@config ||= YAML.load_file("config.yml") rescue nil
8
+ @@config ||= {}
9
+ end
10
+
11
+ def initialize
12
+ begin
13
+ # TODO: config reload
14
+
15
+ if dbconf = Env.config['mongodb']
16
+ require 'mongomapper'
17
+ @@database ||= MongoMapper.database = dbconf['database']
18
+ end
19
+
20
+ if dbconf = Env.config['datamapper']
21
+ require 'dm-core'
22
+ require 'dm-validations'
23
+ require 'dm-timestamps'
24
+ @@connection ||= DataMapper.setup(
25
+ :default,
26
+ :adapter => :mysql,
27
+ :host => dbconf['host'],
28
+ :database => dbconf['database'],
29
+ :username => dbconf['username'],
30
+ :password => dbconf['password']
31
+ )
32
+ @@dblogger ||= DataObjects::Mysql.logger = DataObjects::Logger.new(STDOUT, dbconf['logging']) if dbconf['logging']
33
+ end
34
+ rescue Exception => e
35
+ puts "Exception initializing environment: #{e.message}"
36
+ puts e.backtrace.join("\n")
37
+ raise e
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ require 'rambo/middleware/lock'
2
+ require 'rambo/middleware/proxy'
3
+ require 'rambo/middleware/upload'
@@ -0,0 +1,18 @@
1
+ module Rack
2
+ class Lock
3
+ FLAG = 'rack.multithread'.freeze unless defined? FLAG
4
+
5
+ def initialize(app, lock = Mutex.new)
6
+ @app, @lock = app, lock
7
+ end
8
+
9
+ def call(env)
10
+ old, env[FLAG] = env[FLAG], false
11
+ @lock.synchronize { @app.call(env) }
12
+ ensure
13
+ env[FLAG] = old
14
+ end
15
+
16
+ end
17
+ end
18
+
@@ -0,0 +1,48 @@
1
+ require 'net/http'
2
+ #require 'right_http_connection'
3
+
4
+ module Rack
5
+ class Proxy
6
+ def initialize(app, options={})
7
+ @app = app
8
+ @options = options
9
+ @@conn = Rightscale::HttpConnection.new
10
+ end
11
+
12
+ def call(env)
13
+
14
+ request = Request.new(env)
15
+
16
+ # tey out persistent connections using right http conn (need to make params aware?)
17
+
18
+ begin
19
+
20
+ backend = rand(@options[:backend].size+1)
21
+
22
+ if backend == @options[:backend].size
23
+ @app.call(env)
24
+ else
25
+ host = URI.parse("http://#{env['HTTP_HOST']}").host
26
+ port = @options[:backend][backend]
27
+
28
+ req = Net::HTTP::Get.new(env['REQUEST_URI'])
29
+ res = Net::HTTP.start(host, port) {|http|
30
+ http.request(req, request.params)
31
+ }
32
+
33
+ # need to be able to pass params into request below
34
+ #res = @@conn.req(:server => host, :port => port, :protocol => 'http', :request => request)
35
+
36
+ body = res.body || ''
37
+
38
+ [res.code.to_i, {'ETag' => res['ETag'], 'Cache-Control' => res['Cache-Control'], 'Content-Type' => res['Content-Type'], 'Location' => res['Location']}, body]
39
+ end
40
+ rescue Exception => e
41
+ puts e.message
42
+ @app.call(env)
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+
@@ -0,0 +1,16 @@
1
+ module Rack
2
+ class Upload
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ request = Rack::Request.new(env)
9
+
10
+ #puts "UPLOAD SAYS HELLO"
11
+
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,61 @@
1
+ require 'rack'
2
+
3
+ module Rambo
4
+ class Request < Rack::Request
5
+ def user_agent
6
+ @env['HTTP_USER_AGENT']
7
+ end
8
+
9
+ # Returns an array of acceptable media types for the response
10
+ def accept
11
+ @env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
12
+ end
13
+
14
+ def path
15
+ @env["REQUEST_PATH"]
16
+ end
17
+
18
+ def uri
19
+ @env["REQUEST_URI"]
20
+ end
21
+
22
+ def path_components
23
+ @path_components ||= path.split('/')
24
+ end
25
+
26
+ def default_controller
27
+ if rambo_conf = Rambo::Env.config['rambo']
28
+ rambo_conf['default_controller'] || 'home'
29
+ else
30
+ 'home'
31
+ end
32
+ end
33
+
34
+ def application_context
35
+ @application_context
36
+ end
37
+
38
+ def application_context=(ctx)
39
+ @application_context = ctx
40
+ end
41
+
42
+ def controller
43
+ path_components[1] || default_controller
44
+ end
45
+
46
+ def action
47
+ path_components[2] || 'index'
48
+ end
49
+
50
+ def controller_class
51
+ self.controller.downcase.gsub(/^[a-z]|\s+[a-z]/) { |a| a.upcase } + 'Controller'
52
+ end
53
+
54
+ # Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
55
+ def params
56
+ self.GET.update(self.POST)
57
+ rescue EOFError => boom
58
+ self.GET
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ module Rambo
2
+ class Response < Rack::Response
3
+ def initialize
4
+ @status, @body = 200, []
5
+ @header = Rack::Utils::HeaderHash.new({'Content-Type' => 'text/html'})
6
+ end
7
+
8
+ # def write(str)
9
+ # @body << str.to_s
10
+ # str
11
+ # end
12
+ #
13
+ # def finish
14
+ # @body = block if block_given?
15
+ # if [204, 304].include?(status.to_i)
16
+ # header.delete "Content-Type"
17
+ # [status.to_i, header.to_hash, []]
18
+ # else
19
+ # body = @body || []
20
+ # body = [body] if body.respond_to? :to_str
21
+ # if body.respond_to?(:to_ary)
22
+ # header["Content-Length"] = body.to_ary.
23
+ # inject(0) { |len, part| len + part.bytesize }.to_s
24
+ # end
25
+ # [status.to_i, header.to_hash, body]
26
+ # end
27
+ # end
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rambo/env'
4
+ require 'rambo/controller'
5
+ require 'rambo/middleware'
6
+ require 'rambo/time'
7
+ require 'rambo/request'
8
+ require 'rambo/application_request'
9
+ require 'rambo/response'
10
+ require 'rambo/application_context'
11
+
12
+ # Server simply routes the request to the correct controller/action and returns the result
13
+ module Rambo
14
+ class Server
15
+
16
+ def initialize(options = {})
17
+ @options = options
18
+ env = Rambo::Env.new
19
+ @contexts = {
20
+ 'default' => Rambo::ApplicationContext.new(),
21
+ #'blog' => Rambo::ApplicationContext.new('blog')
22
+ }
23
+ end
24
+
25
+ def call(env)
26
+ begin
27
+
28
+ @contexts.each { |key, context| context.reload } if Rambo::Env.config
29
+
30
+ request = Request.new(env)
31
+ response = Response.new
32
+
33
+ if @contexts.keys.include? request.controller.downcase
34
+ current_context = @contexts[request.controller.downcase]
35
+ request = Rambo::ApplicationRequest.new(env)
36
+ end
37
+ current_context ||= @contexts['default']
38
+ request.application_context = current_context
39
+
40
+ #puts "rambo: looking for #{request.controller_class}"
41
+
42
+ begin
43
+ controller = Object.module_eval("::#{request.controller_class}", __FILE__, __LINE__).new
44
+ rescue Exception => e
45
+ return [404, response.header, "<h2>Routing error: controller <span style='color:grey'>#{request.controller}</span> not found</h2>"]
46
+ end
47
+ controller.request = request
48
+ controller.response = response
49
+
50
+ controller.init if controller.respond_to? :init
51
+
52
+ unless controller.respond_to? request.action
53
+ return [404, response.header, "<h2>Routing error: action <span style='color:grey'>#{request.action}</span> not found in <span style='color:grey'>#{request.controller}</span></h2>"]
54
+ end
55
+
56
+ result = controller.send(request.action) unless controller.already_rendered?
57
+
58
+ response.body = result if result
59
+
60
+ [response.status, response.header, response.body]
61
+ rescue Exception => e
62
+ puts e.message
63
+ return [500, {}, "<pre><b>#{e.message.gsub("<","&lt;")}</b>\n#{e.backtrace.join("\n")}</pre>"]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,11 @@
1
+ class MooTime < Time
2
+ RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
3
+ RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
4
+ def httpdate
5
+ t = dup.utc
6
+ sprintf('%s, %02d %s %d %02d:%02d:%02d GMT',
7
+ RFC2822_DAY_NAME[t.wday],
8
+ t.day, RFC2822_MONTH_NAME[t.mon-1], t.year,
9
+ t.hour, t.min, t.sec)
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rambo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - Richard Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-30 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: rambo is an experimental ruby web framework
17
+ email: moomerman@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - lib/rambo.rb
27
+ - lib/rambo/application_context.rb
28
+ - lib/rambo/application_request.rb
29
+ - lib/rambo/controller/cache.rb
30
+ - lib/rambo/controller/params.rb
31
+ - lib/rambo/controller/redirect.rb
32
+ - lib/rambo/controller/template.rb
33
+ - lib/rambo/controller.rb
34
+ - lib/rambo/env.rb
35
+ - lib/rambo/middleware/lock.rb
36
+ - lib/rambo/middleware/proxy.rb
37
+ - lib/rambo/middleware/upload.rb
38
+ - lib/rambo/middleware.rb
39
+ - lib/rambo/request.rb
40
+ - lib/rambo/response.rb
41
+ - lib/rambo/server.rb
42
+ - lib/rambo/time.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/moomerman/rambo
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --inline-source
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project: rambo
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: rambo is an experimental ruby web framework based on rack
72
+ test_files: []
73
+