moomerman-rambo 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +12 -0
- data/lib/rambo/base_controller.rb +71 -0
- data/lib/rambo/lock.rb +18 -0
- data/lib/rambo/proxy.rb +48 -0
- data/lib/rambo/request.rb +39 -0
- data/lib/rambo/response.rb +29 -0
- data/lib/rambo/server.rb +80 -0
- data/lib/rambo/templating.rb +68 -0
- data/lib/rambo/time.rb +11 -0
- data/lib/rambo/upload.rb +16 -0
- data/lib/rambo.rb +4 -0
- metadata +84 -0
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
To install the gem:
|
2
|
+
|
3
|
+
gem sources -a http://gems.github.com
|
4
|
+
sudo gem install moomerman-rambo
|
5
|
+
|
6
|
+
To run the example:
|
7
|
+
|
8
|
+
clone the repo (the examples are not part of the gem)
|
9
|
+
cd rambo/example/blog/
|
10
|
+
thin start -R server.ru -p 4000 --threaded
|
11
|
+
|
12
|
+
head over to http://localhost:4000/
|
@@ -0,0 +1,71 @@
|
|
1
|
+
load 'rambo/templating.rb'
|
2
|
+
|
3
|
+
module Rambo
|
4
|
+
class BaseController
|
5
|
+
include Templating
|
6
|
+
|
7
|
+
attr_accessor :params, :request, :response
|
8
|
+
|
9
|
+
def params
|
10
|
+
if !self.request.params.keys.join.include?('[')
|
11
|
+
@params ||= indifferent_hash.merge(self.request.params)
|
12
|
+
else
|
13
|
+
@params ||= self.request.params.inject indifferent_hash do |res, (key,val)|
|
14
|
+
if key.include?('[')
|
15
|
+
head = key.split(/[\]\[]+/)
|
16
|
+
last = head.pop
|
17
|
+
head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val
|
18
|
+
else
|
19
|
+
res[key] = val
|
20
|
+
end
|
21
|
+
res
|
22
|
+
end
|
23
|
+
end
|
24
|
+
if request.path_components.size() > 2
|
25
|
+
@params.merge!(:id => request.path_components[3])
|
26
|
+
end
|
27
|
+
@params
|
28
|
+
end
|
29
|
+
|
30
|
+
def fresh?(model, options={})
|
31
|
+
@@etags ||= {}
|
32
|
+
etag = Digest::SHA1.hexdigest(model.inspect)
|
33
|
+
response.header['ETag'] = "\"#{etag}\""
|
34
|
+
response.header['Expires'] = (MooTime.now + options[:expires_in]).httpdate if options[:expires_in]
|
35
|
+
response.header['Cache-Control'] = 'public'
|
36
|
+
if @@etags[request.uri] == etag
|
37
|
+
response.status = 304
|
38
|
+
response.body = ''
|
39
|
+
return true
|
40
|
+
else
|
41
|
+
@@etags[request.uri] = etag
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def controller
|
47
|
+
self.request.controller
|
48
|
+
end
|
49
|
+
|
50
|
+
def action
|
51
|
+
self.request.action
|
52
|
+
end
|
53
|
+
|
54
|
+
def indifferent_hash
|
55
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
56
|
+
end
|
57
|
+
|
58
|
+
def redirect(destination, options={})
|
59
|
+
destination = destination.to_s if destination.is_a? Symbol
|
60
|
+
unless destination[0,1] == "/"
|
61
|
+
destination = "/#{self.controller}/#{destination}"
|
62
|
+
end
|
63
|
+
puts "redirecting to #{destination}"
|
64
|
+
|
65
|
+
response.status = 302
|
66
|
+
response.header['Location'] = destination
|
67
|
+
return ""
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/rambo/lock.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rack
|
2
|
+
class Lock
|
3
|
+
FLAG = 'rack.multithread'.freeze
|
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
|
+
|
data/lib/rambo/proxy.rb
ADDED
@@ -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,39 @@
|
|
1
|
+
module Rambo
|
2
|
+
class Request < Rack::Request
|
3
|
+
def user_agent
|
4
|
+
@env['HTTP_USER_AGENT']
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns an array of acceptable media types for the response
|
8
|
+
def accept
|
9
|
+
@env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip }
|
10
|
+
end
|
11
|
+
|
12
|
+
def path
|
13
|
+
env["REQUEST_PATH"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def uri
|
17
|
+
env["REQUEST_URI"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def path_components
|
21
|
+
@path_components ||= path.split('/')
|
22
|
+
end
|
23
|
+
|
24
|
+
def controller
|
25
|
+
path_components[1] || 'home'
|
26
|
+
end
|
27
|
+
|
28
|
+
def action
|
29
|
+
path_components[2] || 'index'
|
30
|
+
end
|
31
|
+
|
32
|
+
# Override Rack 0.9.x's #params implementation (see #72 in lighthouse)
|
33
|
+
def params
|
34
|
+
self.GET.update(self.POST)
|
35
|
+
rescue EOFError => boom
|
36
|
+
self.GET
|
37
|
+
end
|
38
|
+
end
|
39
|
+
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
|
data/lib/rambo/server.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thin'
|
5
|
+
|
6
|
+
require 'rambo/lock'
|
7
|
+
require 'rambo/proxy'
|
8
|
+
require 'rambo/templating'
|
9
|
+
require 'rambo/base_controller'
|
10
|
+
require 'rambo/time'
|
11
|
+
require 'rambo/upload'
|
12
|
+
require 'rambo/request'
|
13
|
+
require 'rambo/response'
|
14
|
+
|
15
|
+
module Rambo
|
16
|
+
class Server
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
@options = options
|
20
|
+
# load from config - only do this IF database required
|
21
|
+
require 'dm-core'
|
22
|
+
require 'dm-validations'
|
23
|
+
require 'dm-timestamps'
|
24
|
+
#DataMapper.setup(:default, 'mysql://localhost/moo_development')
|
25
|
+
DataMapper.setup(:default, :adapter => :mysql, :host => 'localhost', :database => 'moo_development', :username => 'root', :password => '')
|
26
|
+
#DataObjects::Mysql.logger = DataObjects::Logger.new(STDOUT, :debug)
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
begin
|
31
|
+
Dir["controller/*.rb"].each { |x| funkyload x }
|
32
|
+
Dir["model/*.rb"].each { |x| funkyload x }
|
33
|
+
Dir["lib/*.rb"].each { |x| funkyload x }
|
34
|
+
|
35
|
+
request = Request.new(env)
|
36
|
+
response = Response.new
|
37
|
+
|
38
|
+
ctl_string = (request.controller.downcase.gsub(/^[a-z]|\s+[a-z]/) { |a| a.upcase } + 'Controller')
|
39
|
+
begin
|
40
|
+
obj = Object.module_eval("::#{ctl_string}", __FILE__, __LINE__).new
|
41
|
+
rescue Exception => e
|
42
|
+
return [404, response.header, "<h1>Controller #{ctl_string} Not Found</h1>"]
|
43
|
+
end
|
44
|
+
obj.request = request
|
45
|
+
obj.response = response
|
46
|
+
|
47
|
+
#begin
|
48
|
+
result = obj.send(request.action)
|
49
|
+
#rescue Exception => e
|
50
|
+
#return [404, response.header, "<h1>Action #{request.action} Not Found</h1>"]
|
51
|
+
#end
|
52
|
+
response.body = result if result
|
53
|
+
|
54
|
+
[response.status, response.header, response.body]
|
55
|
+
rescue Exception => e
|
56
|
+
puts e.message
|
57
|
+
return [500, response.header, "<pre><b>#{e.message.gsub("<","<")}</b>\n#{e.backtrace.join("\n")}</pre>"]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
# turn this into a thread that checks every x seconds
|
63
|
+
# (any chance of a callback?) so it is outside of the
|
64
|
+
# request/response cycle
|
65
|
+
def funkyload(file)
|
66
|
+
@@loadcache ||= {}
|
67
|
+
if cache = @@loadcache[file]
|
68
|
+
if (mtime = File.mtime(file)) > cache
|
69
|
+
puts "reloading: #{file}"
|
70
|
+
load file
|
71
|
+
@@loadcache[file] = mtime
|
72
|
+
end
|
73
|
+
else
|
74
|
+
puts "loading: #{file}"
|
75
|
+
load file
|
76
|
+
@@loadcache[file] = File.mtime(file)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Templating
|
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
|
+
private
|
11
|
+
def render(engine, template, options={}, locals={})
|
12
|
+
# extract generic options
|
13
|
+
layout = options.delete(:layout)
|
14
|
+
layout = :layout if layout.nil? || layout == true
|
15
|
+
views = options.delete(:views) || "./view"
|
16
|
+
locals = options.delete(:locals) || locals || {}
|
17
|
+
|
18
|
+
# render template
|
19
|
+
data = lookup_template(engine, template, views)
|
20
|
+
output = __send__("render_#{engine}", template, data, options, locals)
|
21
|
+
|
22
|
+
# render layout
|
23
|
+
if layout && data = lookup_layout(engine, layout, views)
|
24
|
+
__send__("render_#{engine}", layout, data, options, {}) { output }
|
25
|
+
else
|
26
|
+
output
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def lookup_template(engine, template, views_dir)
|
31
|
+
case template
|
32
|
+
when Symbol
|
33
|
+
# if cached = @@template_cache[template]
|
34
|
+
# cached
|
35
|
+
# else
|
36
|
+
path = ::File.join(views_dir, "#{template}.#{engine}")
|
37
|
+
res = ::File.read(path)
|
38
|
+
# @@template_cache[template] = res
|
39
|
+
# res
|
40
|
+
#end
|
41
|
+
when Proc
|
42
|
+
template.call
|
43
|
+
when String
|
44
|
+
template
|
45
|
+
else
|
46
|
+
raise ArgumentError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def lookup_layout(engine, template, views_dir)
|
51
|
+
lookup_template(engine, template, views_dir)
|
52
|
+
rescue Errno::ENOENT
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def render_erb(template, data, options, locals, &block)
|
57
|
+
original_out_buf = @_out_buf
|
58
|
+
data = data.call if data.kind_of? Proc
|
59
|
+
|
60
|
+
instance = ::ERB.new(data, nil, nil, '@_out_buf')
|
61
|
+
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
|
62
|
+
|
63
|
+
src = "#{locals_assigns.join("\n")}\n#{instance.src}"
|
64
|
+
eval src, binding, '(__ERB__)', locals_assigns.length + 1
|
65
|
+
@_out_buf, result = original_out_buf, @_out_buf
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
data/lib/rambo/time.rb
ADDED
@@ -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
|
data/lib/rambo/upload.rb
ADDED
data/lib/rambo.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moomerman-rambo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.3"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Taylor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-17 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: oauth
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.2
|
34
|
+
version:
|
35
|
+
description: rambo is an experimental ruby web framework
|
36
|
+
email: moomerman@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- README
|
45
|
+
- lib/rambo.rb
|
46
|
+
- lib/rambo
|
47
|
+
- lib/rambo/base_controller.rb
|
48
|
+
- lib/rambo/lock.rb
|
49
|
+
- lib/rambo/server.rb
|
50
|
+
- lib/rambo/proxy.rb
|
51
|
+
- lib/rambo/request.rb
|
52
|
+
- lib/rambo/response.rb
|
53
|
+
- lib/rambo/templating.rb
|
54
|
+
- lib/rambo/time.rb
|
55
|
+
- lib/rambo/upload.rb
|
56
|
+
has_rdoc: false
|
57
|
+
homepage: http://github.com/moomerman/rambo
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --inline-source
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project: rambo
|
79
|
+
rubygems_version: 1.2.0
|
80
|
+
signing_key:
|
81
|
+
specification_version: 2
|
82
|
+
summary: rambo is an experimental ruby web framework
|
83
|
+
test_files: []
|
84
|
+
|