merb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +71 -14
- data/Rakefile +20 -31
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
- data/lib/merb.rb +3 -2
- data/lib/merb/caching.rb +5 -0
- data/lib/merb/caching/action_cache.rb +56 -0
- data/lib/merb/caching/fragment_cache.rb +38 -0
- data/lib/merb/caching/store/file_cache.rb +82 -0
- data/lib/merb/caching/store/memory_cache.rb +67 -0
- data/lib/merb/core_ext.rb +1 -1
- data/lib/merb/core_ext/merb_hash.rb +35 -0
- data/lib/merb/core_ext/merb_object.rb +88 -2
- data/lib/merb/merb_controller.rb +71 -69
- data/lib/merb/merb_dispatcher.rb +72 -0
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +19 -47
- data/lib/merb/merb_mailer.rb +1 -1
- data/lib/merb/merb_request.rb +11 -3
- data/lib/merb/merb_router.rb +113 -8
- data/lib/merb/merb_server.rb +71 -12
- data/lib/merb/merb_upload_handler.rb +8 -6
- data/lib/merb/merb_upload_progress.rb +1 -1
- data/lib/merb/merb_view_context.rb +13 -3
- data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
- data/lib/merb/mixins/controller_mixin.rb +149 -17
- data/lib/merb/mixins/form_control_mixin.rb +1 -0
- data/lib/merb/mixins/render_mixin.rb +148 -151
- data/lib/merb/mixins/responder_mixin.rb +133 -18
- data/lib/merb/mixins/view_context_mixin.rb +24 -0
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +6 -5
- data/lib/merb/template.rb +10 -0
- data/lib/merb/template/erubis.rb +52 -0
- data/lib/merb/template/haml.rb +77 -0
- data/lib/merb/template/markaby.rb +48 -0
- data/lib/merb/template/xml_builder.rb +34 -0
- metadata +19 -17
- data/lib/merb/mixins/merb_status_codes.rb +0 -59
data/lib/merb/merb_mailer.rb
CHANGED
@@ -4,7 +4,7 @@ begin
|
|
4
4
|
require 'mailfactory'
|
5
5
|
rescue LoadError
|
6
6
|
puts "You need to install the mailfactory gem to use Merb::Mailer"
|
7
|
-
MERB_LOGGER.
|
7
|
+
MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
|
8
8
|
end
|
9
9
|
|
10
10
|
module Merb
|
data/lib/merb/merb_request.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Merb
|
2
2
|
|
3
3
|
class Request
|
4
|
-
|
4
|
+
attr_accessor :env
|
5
5
|
def initialize(env, method)
|
6
6
|
@env = env
|
7
7
|
@method = method
|
@@ -40,11 +40,20 @@ module Merb
|
|
40
40
|
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
41
41
|
end
|
42
42
|
|
43
|
+
# returns the request HTTP_REFERER.
|
44
|
+
def referer
|
45
|
+
@env['HTTP_REFERER']
|
46
|
+
end
|
47
|
+
|
43
48
|
# returns he request uri.
|
44
49
|
def uri
|
45
50
|
@env['REQUEST_URI']
|
46
51
|
end
|
47
52
|
|
53
|
+
def query_string
|
54
|
+
@env['QUERY_STRING']
|
55
|
+
end
|
56
|
+
|
48
57
|
# returns the uri without the query string.
|
49
58
|
def path
|
50
59
|
uri ? uri.split('?').first : ''
|
@@ -78,7 +87,7 @@ module Merb
|
|
78
87
|
|
79
88
|
# returns the REQUEST_METHOD
|
80
89
|
def method
|
81
|
-
@method ||= @env['REQUEST_METHOD']
|
90
|
+
@method ||= @env['REQUEST_METHOD'].downcase.intern
|
82
91
|
end
|
83
92
|
|
84
93
|
# create predicate methods for querying the REQUEST_METHOD
|
@@ -92,4 +101,3 @@ module Merb
|
|
92
101
|
|
93
102
|
end
|
94
103
|
|
95
|
-
|
data/lib/merb/merb_router.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module Merb
|
2
|
-
|
2
|
+
begin
|
3
|
+
require 'active_support'
|
4
|
+
rescue
|
5
|
+
MERB_LOGGER.warn "You must have ActiveSupport installed to use merb restful routing\nNormal routing works fine without"
|
6
|
+
end
|
3
7
|
# Merb::RouteMatcher is the request routing mapper for the merb framework.
|
4
8
|
# You can define placeholder parts of the url with the :symbol notation.
|
5
9
|
# so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
|
@@ -20,6 +24,7 @@ module Merb
|
|
20
24
|
def self.prepare
|
21
25
|
@@routes = Array.new
|
22
26
|
@@compiled_statement = String.new
|
27
|
+
@@compiled_regexen = Array.new
|
23
28
|
yield self
|
24
29
|
compile_router
|
25
30
|
end
|
@@ -36,6 +41,14 @@ module Merb
|
|
36
41
|
|
37
42
|
# the final compiled lambda that gets used
|
38
43
|
# as the body of the route_request method.
|
44
|
+
def self.compiled_statement
|
45
|
+
@@compiled_statement
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.compiled_regexen
|
49
|
+
@@compiled_regexen
|
50
|
+
end
|
51
|
+
|
39
52
|
def compiled_statement
|
40
53
|
@@compiled_statement
|
41
54
|
end
|
@@ -79,18 +92,110 @@ module Merb
|
|
79
92
|
(name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false)
|
80
93
|
count += 1
|
81
94
|
if flag
|
82
|
-
route[0].sub!(@@section_regexp, "([
|
95
|
+
route[0].sub!(@@section_regexp, "([^,?]+)")
|
83
96
|
else
|
84
|
-
route[0].sub!(@@section_regexp, "([
|
97
|
+
route[0].sub!(@@section_regexp, "([^\/,?]+)")
|
85
98
|
end
|
86
99
|
code << " @sections[:#{name}] = $#{count}\n"
|
87
|
-
end
|
88
|
-
|
100
|
+
end
|
101
|
+
@@compiled_regexen << Regexp.new(route[0])
|
102
|
+
index = @@compiled_regexen.size - 1
|
103
|
+
condition = " when @@compiled_regexen[#{index}] "
|
89
104
|
statement = "#{condition}\n#{code}"
|
90
|
-
statement << " return #{route[1].inspect}.
|
105
|
+
statement << " return #{route[1].inspect}.update(@sections)\n"
|
91
106
|
statement
|
92
107
|
end
|
93
|
-
|
108
|
+
|
109
|
+
class Resource
|
110
|
+
# TODO : come up with naming convention and generate helper
|
111
|
+
# methods for route generation. Route generation
|
112
|
+
# framework needed but needs to be simple and light
|
113
|
+
|
114
|
+
def initialize(resource, procs=[], opts={})
|
115
|
+
@resource, @procs, @opts = resource, procs, opts
|
116
|
+
@procs << proc { ::Merb::RouteMatcher.generate_resources_routes(@resource, @opts) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def resources(res, opts={})
|
120
|
+
(opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id"
|
121
|
+
|
122
|
+
opts[:prefix] = @opts[:prefix] + opts[:prefix]
|
123
|
+
if block_given?
|
124
|
+
yield self.class.new(res, @procs, opts)
|
125
|
+
else
|
126
|
+
@procs << proc { ::Merb::RouteMatcher.generate_resources_routes(res, opts) }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def resource(res, opts={})
|
131
|
+
(opts[:prefix]||='') << "/#{@resource}/:#{@resource.to_s.singularize}_id"
|
132
|
+
|
133
|
+
opts[:prefix] = @opts[:prefix] + opts[:prefix]
|
134
|
+
if block_given?
|
135
|
+
yield self.class.new(res, @procs, opts)
|
136
|
+
else
|
137
|
+
@procs << proc { ::Merb::RouteMatcher.generate_singleton_routes(res, opts) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# add a resource to be compiled for rest style dispatch
|
143
|
+
def self.resources(res, opts={})
|
144
|
+
opts[:prefix] ||= ""
|
145
|
+
if block_given?
|
146
|
+
procs = []
|
147
|
+
yield Resource.new(res, procs, opts)
|
148
|
+
procs.reverse.each {|p| p.call}
|
149
|
+
else
|
150
|
+
generate_resources_routes(res,opts)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# add a resource to be compiled for rest style dispatch
|
155
|
+
def self.resource(res, opts={})
|
156
|
+
opts[:prefix] ||= ""
|
157
|
+
if block_given?
|
158
|
+
procs = []
|
159
|
+
yield Resource.new(res, procs, opts)
|
160
|
+
procs.reverse.each {|p| p.call}
|
161
|
+
else
|
162
|
+
generate_singleton_routes(res,opts)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.generate_resources_routes(res,opt)
|
167
|
+
with_options :controller => res.to_s, :rest => true do |r|
|
168
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;]edit", :allowed => {:get => 'edit'}
|
169
|
+
r.add "#{opt[:prefix]}/#{res}/new[;]:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
|
170
|
+
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
171
|
+
if mem = opt[:member]
|
172
|
+
mem.keys.sort_by{|x| "#{x}"}.each {|action|
|
173
|
+
allowed = mem[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
|
174
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;]#{action}", :allowed => allowed
|
175
|
+
}
|
176
|
+
end
|
177
|
+
if coll = opt[:collection]
|
178
|
+
coll.keys.sort_by{|x| "#{x}"}.each {|action|
|
179
|
+
allowed = coll[action].inject({}) {|h, verb| h[verb] = "#{action}" ; h}
|
180
|
+
r.add "#{opt[:prefix]}/#{res}[;]#{action}", :allowed => allowed
|
181
|
+
}
|
182
|
+
end
|
183
|
+
r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
|
184
|
+
r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'index', :post => 'create'}
|
185
|
+
r.add "#{opt[:prefix]}/#{res}/:id", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
|
186
|
+
r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'index', :post => 'create'}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.generate_singleton_routes(res,opt)
|
191
|
+
with_options :controller => res.to_s, :rest => true do |r|
|
192
|
+
r.add "#{opt[:prefix]}/#{res}[;]edit", :allowed => {:get => 'edit'}
|
193
|
+
r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
|
194
|
+
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
195
|
+
r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
94
199
|
end
|
95
200
|
|
96
|
-
end
|
201
|
+
end
|
data/lib/merb/merb_server.rb
CHANGED
@@ -14,8 +14,8 @@ module Merb
|
|
14
14
|
:port => "4000",
|
15
15
|
:allow_reloading => true,
|
16
16
|
:merb_root => Dir.pwd,
|
17
|
-
:
|
18
|
-
:
|
17
|
+
:cache_templates => false,
|
18
|
+
:use_mutex => true
|
19
19
|
}
|
20
20
|
begin
|
21
21
|
options = defaults.merge(YAML.load(Erubis::Eruby.new(IO.read("#{defaults[:merb_root]}/dist/conf/merb.yml")).result))
|
@@ -35,12 +35,20 @@ module Merb
|
|
35
35
|
options = Merb::Config.setup
|
36
36
|
|
37
37
|
opts = OptionParser.new do |opts|
|
38
|
-
opts.banner = "Usage: merb [
|
38
|
+
opts.banner = "Usage: merb [fdcepghmisluMG] [argument]"
|
39
39
|
opts.define_head "Merb Mongrel+ Erb. Lightweight replacement for ActionPack"
|
40
40
|
opts.separator '*'*80
|
41
41
|
opts.separator 'If no flags are given, Merb starts in the foreground on port 4000'
|
42
42
|
opts.separator '*'*80
|
43
|
-
|
43
|
+
|
44
|
+
opts.on("-u", "--user USER", "This flag is for having merb run as a user other than the one currently logged in. Note: if you set this you must also provide a --group option for it to take effect.") do |config|
|
45
|
+
options[:user] = config
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-G", "--group GROUP", "This flag is for having merb run as a group other than the one currently logged in. Note: if you set this you must also provide a --user option for it to take effect.") do |config|
|
49
|
+
options[:group] = config
|
50
|
+
end
|
51
|
+
|
44
52
|
opts.on("-f", "--config-file FILENAME", "This flag is for adding extra config files for things like the upload progress module") do |config|
|
45
53
|
options[:config] = config
|
46
54
|
end
|
@@ -69,7 +77,9 @@ module Merb
|
|
69
77
|
options[:console] = true
|
70
78
|
end
|
71
79
|
|
72
|
-
opts.on("-s", "--drb
|
80
|
+
opts.on("-s", "--start-drb PORTNUM", "This is the port number to run the drb daemon on for sessions and uplod progress monitoring.") do |drb_port|
|
81
|
+
options[:start_drb] = true
|
82
|
+
options[:only_drb] = true
|
73
83
|
options[:drb_server_port] = drb_port
|
74
84
|
end
|
75
85
|
|
@@ -85,6 +95,22 @@ module Merb
|
|
85
95
|
options[:generate] = path || Dir.pwd
|
86
96
|
end
|
87
97
|
|
98
|
+
opts.on("-k", "--kill PORT or all", "Kill one merb proceses by port number. use merb -k all to kill all merbs") do |ports|
|
99
|
+
options[:kill] = ports
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on("-M", "--merb-config FILENAME", "This flag is for explicitly declaring the merb app's config file") do |config|
|
103
|
+
options[:merb_config] = config
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on("-X", "--mutex on/off", "This flag is for turnhing on and off the mutex lock.") do |mutex|
|
107
|
+
if mutex == 'off'
|
108
|
+
options[:use_mutex] = false
|
109
|
+
else
|
110
|
+
options[:use_mutex] = true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
88
114
|
opts.on("-?", "--help", "Show this help message") do
|
89
115
|
puts opts
|
90
116
|
exit
|
@@ -94,12 +120,17 @@ module Merb
|
|
94
120
|
|
95
121
|
opts.parse!(@@merb_raw_opts)
|
96
122
|
|
123
|
+
# Added by: Rogelio J. Samour 2007-01-23
|
124
|
+
# We need to reload the options that exist in the App's merb.yml
|
125
|
+
# This is needed when one calls merb NOT from the merb_app ROOT
|
126
|
+
# like so: merb --merb-config /path/to/dist/conf/merb.yml -m /path/to/merb/app
|
127
|
+
# or if we add :merb_root: /path/to/merb/app in the merb.yml we can now only call it
|
128
|
+
# like so: merb --merb-config /path/to/dist/conf/merb.yml
|
129
|
+
if options[:merb_config]
|
130
|
+
options = options.merge(YAML.load(Erubis::Eruby.new(IO.read("#{options[:merb_config]}")).result))
|
131
|
+
end
|
97
132
|
|
98
133
|
@@merb_opts = options
|
99
|
-
unless options[:generate] || options[:console]
|
100
|
-
puts %{Merb started with these options:}
|
101
|
-
puts @@merb_opts.to_yaml; puts
|
102
|
-
end
|
103
134
|
end
|
104
135
|
|
105
136
|
def initialize_merb
|
@@ -112,6 +143,22 @@ module Merb
|
|
112
143
|
@@merb_raw_opts = ARGV
|
113
144
|
merb_config
|
114
145
|
|
146
|
+
if k = @@merb_opts[:kill]
|
147
|
+
begin
|
148
|
+
Dir[@@merb_opts[:merb_root] + "/log/merb.#{k == 'all' ? '*' : k }.pid"].each do |f|
|
149
|
+
puts f
|
150
|
+
pid = IO.read(f).chomp.to_i
|
151
|
+
Process.kill(9, pid)
|
152
|
+
FileUtils.rm f
|
153
|
+
puts "killed PID: #{pid}"
|
154
|
+
end
|
155
|
+
rescue
|
156
|
+
puts "Failed to kill! #{k}"
|
157
|
+
ensure
|
158
|
+
exit
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
115
162
|
case @@merb_opts[:environment].to_s
|
116
163
|
when 'development'
|
117
164
|
@@merb_opts[:allow_reloading] ||= true
|
@@ -157,9 +204,10 @@ module Merb
|
|
157
204
|
exit!
|
158
205
|
end
|
159
206
|
|
160
|
-
if @@merb_opts[:
|
207
|
+
if @@merb_opts[:start_drb]
|
161
208
|
puts "Starting merb drb server on port: #{@@merb_opts[:drb_server_port]}"
|
162
209
|
start(@@merb_opts[:drb_server_port], :drbserver_start)
|
210
|
+
exit if @@merb_opts[:only_drb]
|
163
211
|
end
|
164
212
|
|
165
213
|
if @@merb_opts[:cluster]
|
@@ -211,15 +259,26 @@ module Merb
|
|
211
259
|
def drbserver_start(port)
|
212
260
|
puts "Starting merb drb server on port: #{port}"
|
213
261
|
require 'merb/merb_drb_server'
|
262
|
+
drb_init = File.join(@@merb_opts[:merb_root], "/dist/conf/merb_drb_init")
|
263
|
+
require drb_init if File.exist?(drb_init)
|
214
264
|
DRb.start_service("druby://#{@@merb_opts[:host]}:#{port}", Merb::DrbServiceProvider)
|
215
265
|
DRb.thread.join
|
216
266
|
end
|
217
267
|
|
218
268
|
def mongrel_start(port)
|
219
269
|
@@merb_opts[:port] = port
|
270
|
+
unless @@merb_opts[:generate] || @@merb_opts[:console] || @@merb_opts[:only_drb] || @@merb_opts[:kill]
|
271
|
+
puts %{Merb started with these options:}
|
272
|
+
puts @@merb_opts.to_yaml; puts
|
273
|
+
end
|
220
274
|
initialize_merb
|
221
275
|
|
222
|
-
|
276
|
+
mconf_hash = {:host => (@@merb_opts[:host]||"0.0.0.0"), :port => (port ||4000)}
|
277
|
+
if @@merb_opts[:user] and @@merb_opts[:group]
|
278
|
+
mconf_hash[:user] = @@merb_opts[:user]
|
279
|
+
mconf_hash[:group] = @@merb_opts[:group]
|
280
|
+
end
|
281
|
+
mconfig = Mongrel::Configurator.new(mconf_hash) do
|
223
282
|
yconfig = YAML.load(Erubis::Eruby.new(IO.read(File.expand_path(@@merb_opts[:config]))).result) if @@merb_opts[:config]
|
224
283
|
listener do
|
225
284
|
uri( "/", :handler => MerbUploadHandler.new(yconfig), :in_front => true) if @@merb_opts[:config]
|
@@ -241,4 +300,4 @@ module Merb
|
|
241
300
|
|
242
301
|
end # Server
|
243
302
|
|
244
|
-
end # Merb
|
303
|
+
end # Merb
|
@@ -52,18 +52,14 @@ class MerbUploadHandler < Mongrel::HttpHandler
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def request_aborted(params)
|
55
|
-
return unless
|
56
|
-
params[Mongrel::Const::REQUEST_METHOD] == Mongrel::Const::POST &&
|
57
|
-
upload_id = Mongrel::HttpRequest.query_parse(params[Mongrel::Const::QUERY_STRING])[Mongrel::Const::UPLOAD_ID]
|
55
|
+
return unless upload_id = valid_upload?(params)
|
58
56
|
Mongrel::Uploads.finish(upload_id)
|
59
57
|
puts "request aborted!"
|
60
58
|
end
|
61
59
|
|
62
60
|
private
|
63
61
|
def upload_notify(action, params, *args)
|
64
|
-
return unless
|
65
|
-
params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
|
66
|
-
upload_id = Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
|
62
|
+
return unless upload_id = valid_upload?(params)
|
67
63
|
if action == :mark
|
68
64
|
last_checked_time = Mongrel::Uploads.last_checked(upload_id)
|
69
65
|
return unless last_checked_time && Time.now - last_checked_time > @frequency
|
@@ -71,5 +67,11 @@ class MerbUploadHandler < Mongrel::HttpHandler
|
|
71
67
|
Mongrel::Uploads.send(action, upload_id, *args)
|
72
68
|
Mongrel::Uploads.update_checked_time(upload_id) unless action == :finish
|
73
69
|
end
|
70
|
+
|
71
|
+
def valid_upload?(params)
|
72
|
+
@path_info.any? { |p| params[Mongrel::Const::PATH_INFO].include?(p) } &&
|
73
|
+
params[Mongrel::Const::REQUEST_METHOD] == Mongrel::Const::POST &&
|
74
|
+
Mongrel::HttpRequest.query_parse(params[Mongrel::Const::QUERY_STRING])[Mongrel::Const::UPLOAD_ID]
|
75
|
+
end
|
74
76
|
end
|
75
77
|
|
@@ -14,7 +14,6 @@ module Merb
|
|
14
14
|
# and then use as the context object passed to Erubis
|
15
15
|
# when evaluating the templates.
|
16
16
|
class ViewContext
|
17
|
-
include Merb::ErubisCaptureMixin
|
18
17
|
include Merb::ViewContextMixin
|
19
18
|
include Merb::FormControls
|
20
19
|
include Merb::GlobalHelper
|
@@ -31,10 +30,21 @@ module Merb
|
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
33
|
+
# hack so markaby doesn't dup us and lose ivars.
|
34
|
+
def dup
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
34
38
|
# accessor for the view. refers to the current @controller object
|
35
39
|
def controller
|
36
40
|
@controller
|
37
|
-
end
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :old_respond_to?, :respond_to?
|
44
|
+
|
45
|
+
def respond_to?(sym, include_private=false)
|
46
|
+
old_respond_to?(sym, include_private) || @controller.respond_to?(sym, include_private)
|
47
|
+
end
|
38
48
|
|
39
49
|
# catch any method calls that the controller responds to
|
40
50
|
# and delegate them back to the controller.
|
@@ -48,4 +58,4 @@ module Merb
|
|
48
58
|
|
49
59
|
end
|
50
60
|
|
51
|
-
end
|
61
|
+
end
|
@@ -1,6 +1,140 @@
|
|
1
1
|
module Merb
|
2
2
|
module ControllerMixin
|
3
|
-
|
3
|
+
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
4
|
+
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
5
|
+
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
6
|
+
CRLF = "\r\n".freeze
|
7
|
+
EOL = CRLF
|
8
|
+
def parse_multipart(request,boundary)
|
9
|
+
boundary = "--#{boundary}"
|
10
|
+
paramhsh = MerbHash.new
|
11
|
+
buf = ""
|
12
|
+
content_length = @env['CONTENT_LENGTH'].to_i
|
13
|
+
input = request
|
14
|
+
boundary_size = boundary.size + EOL.size
|
15
|
+
bufsize = 16384
|
16
|
+
content_length -= boundary_size
|
17
|
+
status = input.read(boundary_size)
|
18
|
+
raise EOFError, "bad content body" unless status == boundary + EOL
|
19
|
+
rx = /(?:#{EOL})?#{Regexp.quote(boundary)}(#{EOL}|--)/
|
20
|
+
|
21
|
+
loop {
|
22
|
+
head = nil
|
23
|
+
body = ''
|
24
|
+
filename = content_type = name = nil
|
25
|
+
|
26
|
+
until head && buf =~ rx
|
27
|
+
if !head && i = buf.index("\r\n\r\n")
|
28
|
+
head = buf.slice!(0, i+2) # First \r\n
|
29
|
+
buf.slice!(0, 2) # Second \r\n
|
30
|
+
|
31
|
+
filename = head[FILENAME_REGEX, 1]
|
32
|
+
content_type = head[CONTENT_TYPE_REGEX, 1]
|
33
|
+
name = head[NAME_REGEX, 1]
|
34
|
+
|
35
|
+
if filename
|
36
|
+
body = Tempfile.new(:Merb)
|
37
|
+
body.binmode if defined? body.binmode
|
38
|
+
end
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
# Save the read body part.
|
43
|
+
if head && (boundary_size+4 < buf.size)
|
44
|
+
body << buf.slice!(0, buf.size - (boundary_size+4))
|
45
|
+
end
|
46
|
+
|
47
|
+
c = input.read(bufsize < content_length ? bufsize : content_length)
|
48
|
+
raise EOFError, "bad content body" if c.nil? || c.empty?
|
49
|
+
buf << c
|
50
|
+
content_length -= c.size
|
51
|
+
end
|
52
|
+
|
53
|
+
# Save the rest.
|
54
|
+
if i = buf.index(rx)
|
55
|
+
body << buf.slice!(0, i)
|
56
|
+
buf.slice!(0, boundary_size+2)
|
57
|
+
|
58
|
+
content_length = -1 if $1 == "--"
|
59
|
+
end
|
60
|
+
|
61
|
+
if filename
|
62
|
+
body.rewind
|
63
|
+
data = {:filename => filename, :type => content_type, :tempfile => body}
|
64
|
+
else
|
65
|
+
data = body
|
66
|
+
end
|
67
|
+
paramhsh = normalize_params(paramhsh,name,data)
|
68
|
+
break if buf.empty? || content_length == -1
|
69
|
+
}
|
70
|
+
paramhsh
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_params(parms, key, val)
|
74
|
+
case key
|
75
|
+
when /(.+)\[(.+)\]\[\]$/
|
76
|
+
parms[$1] ||= MerbHash.new
|
77
|
+
parms[$1] = normalize_params(parms[$1], "#{$2}[]", val)
|
78
|
+
when /(.+)\[(.+)\]$/
|
79
|
+
parms[$1] ||= MerbHash.new
|
80
|
+
parms[$1] = normalize_params(parms[$1], $2, val)
|
81
|
+
when /(.+)\[\]$/
|
82
|
+
(parms[$1] ||= []) << val
|
83
|
+
else
|
84
|
+
parms[key] = val if val
|
85
|
+
end
|
86
|
+
parms
|
87
|
+
end
|
88
|
+
|
89
|
+
# parses a query string or the payload of a POST
|
90
|
+
# request into the params hash. So for example:
|
91
|
+
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
92
|
+
# parses into:
|
93
|
+
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
|
94
|
+
def query_parse(qs, d = '&;')
|
95
|
+
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
96
|
+
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
97
|
+
k, v=unescape(p).split('=',2)
|
98
|
+
h.u(k.split(/[\]\[]+/).reverse.
|
99
|
+
inject(v) { |x,i| MerbHash[i,x] },&m)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# render using chunked encoding
|
104
|
+
# def stream
|
105
|
+
# prefix = '<p>'
|
106
|
+
# suffix = "</p>\r\n"
|
107
|
+
# render_chunked do
|
108
|
+
# IO.popen("cat /tmp/test.log") do |io|
|
109
|
+
# done = false
|
110
|
+
# until done == true do
|
111
|
+
# sleep 0.3
|
112
|
+
# line = io.gets.chomp
|
113
|
+
# if line == 'EOF'
|
114
|
+
# done = true
|
115
|
+
# else
|
116
|
+
# send_chunk(prefix + line + suffix)
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
def render_chunked(&blk)
|
123
|
+
headers['Transfer-Encoding'] = 'chunked'
|
124
|
+
Proc.new {
|
125
|
+
response.send_status_no_connection_close(0)
|
126
|
+
response.send_header
|
127
|
+
blk.call
|
128
|
+
response.write("0\r\n\r\n")
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
# for use within a render_chunked response
|
133
|
+
def send_chunk(data)
|
134
|
+
response.write('%x' % data.size + "\r\n")
|
135
|
+
response.write(data + "\r\n")
|
136
|
+
end
|
137
|
+
|
4
138
|
# redirect to another url It can be like /foo/bar
|
5
139
|
# for redirecting within your same app. Or it can
|
6
140
|
# be a fully qualified url to another site.
|
@@ -31,7 +165,7 @@ module Merb
|
|
31
165
|
# a file directly from nginx. See the nginx wiki:
|
32
166
|
# http://wiki.codemongers.com/NginxXSendfile
|
33
167
|
def nginx_send_file(file)
|
34
|
-
headers['X-Accel-Redirect'] = file
|
168
|
+
headers['X-Accel-Redirect'] = File.expand_path(file)
|
35
169
|
return
|
36
170
|
end
|
37
171
|
|
@@ -47,26 +181,24 @@ module Merb
|
|
47
181
|
set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
|
48
182
|
end
|
49
183
|
|
50
|
-
# parses a query string or the payload of a POST
|
51
|
-
# request into the params hash. So for example:
|
52
|
-
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
53
|
-
# parses into:
|
54
|
-
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
|
55
|
-
def query_parse(qs, d = '&;')
|
56
|
-
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
57
|
-
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
58
|
-
k, v=unescape(p).split('=',2)
|
59
|
-
h.u(k.split(/[\]\[]+/).reverse.
|
60
|
-
inject(v) { |x,i| MerbHash[i,x] },&m)
|
61
|
-
}
|
62
|
-
end
|
63
|
-
|
64
184
|
# creates a random token like:
|
65
185
|
# "b9a82e011694cc13a4249731b9e83cea"
|
66
186
|
def make_token
|
67
187
|
require 'digest/md5'
|
68
188
|
Digest::MD5.hexdigest("#{inspect}#{Time.now}#{rand}")
|
69
189
|
end
|
190
|
+
|
191
|
+
def rand_uuid
|
192
|
+
"%04x%04x-%04x-%04x-%04x-%06x%06x" % [
|
193
|
+
rand(0x0010000),
|
194
|
+
rand(0x0010000),
|
195
|
+
rand(0x0010000),
|
196
|
+
rand(0x0010000),
|
197
|
+
rand(0x0010000),
|
198
|
+
rand(0x1000000),
|
199
|
+
rand(0x1000000),
|
200
|
+
]
|
201
|
+
end
|
70
202
|
|
71
203
|
def escape_xml(obj)
|
72
204
|
obj.to_s.gsub(/[&<>"']/) { |s| Merb::Const::ESCAPE_TABLE[s] }
|
@@ -85,4 +217,4 @@ module Merb
|
|
85
217
|
end
|
86
218
|
|
87
219
|
end
|
88
|
-
end
|
220
|
+
end
|