merb 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +105 -95
- data/Rakefile +4 -5
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/conf/router.rb +5 -2
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -1
- data/lib/merb.rb +10 -3
- data/lib/merb/core_ext.rb +3 -1
- data/lib/merb/core_ext/merb_class.rb +85 -51
- data/lib/merb/core_ext/merb_inflections.rb +112 -0
- data/lib/merb/core_ext/merb_inflector.rb +275 -0
- data/lib/merb/core_ext/merb_object.rb +0 -55
- data/lib/merb/core_ext/merb_string.rb +1 -13
- data/lib/merb/merb_controller.rb +26 -20
- data/lib/merb/merb_dispatcher.rb +10 -3
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +1 -1
- data/lib/merb/merb_mailer.rb +3 -2
- data/lib/merb/merb_request.rb +56 -0
- data/lib/merb/merb_router.rb +24 -38
- data/lib/merb/mixins/controller_mixin.rb +34 -5
- data/lib/merb/mixins/render_mixin.rb +7 -7
- data/lib/merb/mixins/responder_mixin.rb +17 -10
- data/lib/merb/mixins/view_context_mixin.rb +13 -2
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +4 -4
- data/lib/merb/template/erubis.rb +1 -1
- data/lib/merb/template/haml.rb +20 -14
- data/lib/tasks/merb.rake +11 -2
- metadata +4 -2
data/lib/merb/merb_controller.rb
CHANGED
@@ -12,10 +12,18 @@ module Merb
|
|
12
12
|
# to your controller via params. It also parses the ?query=string and
|
13
13
|
# puts that into params as well.
|
14
14
|
class Controller
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
|
16
|
+
class_inheritable_accessor :_layout,
|
17
|
+
:_session_id_key,
|
18
|
+
:_template_extensions,
|
19
|
+
:_template_root,
|
20
|
+
:_layout_root
|
21
|
+
self._layout = :application
|
22
|
+
self._session_id_key = :_session_id
|
23
|
+
self._template_extensions = { }
|
24
|
+
self._template_root = File.expand_path(MERB_ROOT / "dist/app/views")
|
25
|
+
self._layout_root = File.expand_path(MERB_ROOT / "dist/app/views/layout")
|
26
|
+
|
19
27
|
include Merb::ControllerMixin
|
20
28
|
include Merb::RenderMixin
|
21
29
|
include Merb::ResponderMixin
|
@@ -23,22 +31,22 @@ module Merb
|
|
23
31
|
attr_accessor :status, :body, :request
|
24
32
|
|
25
33
|
MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
|
26
|
-
|
34
|
+
|
27
35
|
# parses the http request into params, headers and cookies
|
28
36
|
# that you can use in your controller classes. Also handles
|
29
37
|
# file uploads by writing a tempfile and passing a reference
|
30
38
|
# in params.
|
31
39
|
def initialize(request, env, args, response)
|
32
40
|
@env = MerbHash[env.to_hash]
|
33
|
-
@status, @method, @response, @headers = 200, (env[
|
34
|
-
|
35
|
-
cookies = query_parse(@env[
|
36
|
-
querystring = query_parse(@env[
|
41
|
+
@status, @method, @response, @headers = 200, (env[Mongrel::Const::REQUEST_METHOD]||Mongrel::Const::GET).downcase.to_sym, response,
|
42
|
+
Mongrel::Const::CONTENT_TYPE_TEXT_HTML_HASH
|
43
|
+
cookies = query_parse(@env[Mongrel::Const::HTTP_COOKIE], ';,')
|
44
|
+
querystring = query_parse(@env[Mongrel::Const::QUERY_STRING])
|
37
45
|
|
38
|
-
if MULTIPART_REGEXP =~ @env[
|
46
|
+
if MULTIPART_REGEXP =~ @env[Mongrel::Const::UPCASE_CONTENT_TYPE] && @method == :post
|
39
47
|
querystring.update(parse_multipart(request, $1))
|
40
48
|
elsif @method == :post
|
41
|
-
if [
|
49
|
+
if [Mongrel::Const::APPLICATION_JSON, Mongrel::Const::TEXT_JSON].include?(@env[Mongrel::Const::UPCASE_CONTENT_TYPE])
|
42
50
|
MERB_LOGGER.info("JSON Request")
|
43
51
|
json = JSON.parse(request.read || "") || {}
|
44
52
|
json = MerbHash.new(json) if json.is_a? Hash
|
@@ -49,7 +57,7 @@ module Merb
|
|
49
57
|
end
|
50
58
|
|
51
59
|
@cookies, @params = cookies, querystring.update(args)
|
52
|
-
@cookies[
|
60
|
+
@cookies[_session_id_key] = @params[_session_id_key] if @params.key?(_session_id_key)
|
53
61
|
|
54
62
|
allow = [:post, :put, :delete]
|
55
63
|
allow << :get if MERB_ENV == 'development'
|
@@ -81,7 +89,7 @@ module Merb
|
|
81
89
|
end
|
82
90
|
call_filters(after_filters)
|
83
91
|
finalize_session if respond_to?:finalize_session
|
84
|
-
MERB_LOGGER.info("Time spent in #{action} action: #{Time.now - start} seconds")
|
92
|
+
MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{Time.now - start} seconds")
|
85
93
|
end
|
86
94
|
|
87
95
|
# override this method on your controller classes to specialize
|
@@ -126,15 +134,13 @@ module Merb
|
|
126
134
|
@response
|
127
135
|
end
|
128
136
|
|
129
|
-
|
130
|
-
trait :template_root => File.expand_path(MERB_ROOT / "dist/app/views")
|
131
|
-
trait :layout_root => File.expand_path(MERB_ROOT / "dist/app/views/layout")
|
137
|
+
|
132
138
|
# lookup the trait[:template_extensions] for the extname of the filename
|
133
139
|
# you pass. Answers with the engine that matches the extension, Template::Erubis
|
134
140
|
# is used if none matches.
|
135
141
|
def engine_for(file)
|
136
142
|
extension = File.extname(file)[1..-1]
|
137
|
-
|
143
|
+
_template_extensions[extension]
|
138
144
|
rescue
|
139
145
|
::Merb::Template::Erubis
|
140
146
|
end
|
@@ -143,7 +149,7 @@ module Merb
|
|
143
149
|
# a list of extensions that will be looked up on #render of an action.
|
144
150
|
def self.register_engine(engine, *extensions)
|
145
151
|
[extensions].flatten.uniq.each do |ext|
|
146
|
-
|
152
|
+
_template_extensions[ext] = engine
|
147
153
|
end
|
148
154
|
end
|
149
155
|
|
@@ -154,8 +160,8 @@ module Merb
|
|
154
160
|
# from its superclasses. Since @@class variables are almost
|
155
161
|
# global vars within an inheritance tree, we use
|
156
162
|
# @class_instance_variables instead
|
157
|
-
|
158
|
-
|
163
|
+
class_inheritable_accessor :before_filters
|
164
|
+
class_inheritable_accessor :after_filters
|
159
165
|
|
160
166
|
# calls a filter chain according to rules.
|
161
167
|
def call_filters(filter_set)
|
data/lib/merb/merb_dispatcher.rb
CHANGED
@@ -5,6 +5,10 @@ module Merb
|
|
5
5
|
|
6
6
|
attr_accessor :path_prefix
|
7
7
|
|
8
|
+
def use_mutex=(val)
|
9
|
+
@@use_mutex = val
|
10
|
+
end
|
11
|
+
|
8
12
|
@@mutex = Mutex.new
|
9
13
|
@@use_mutex = ::Merb::Server.use_mutex
|
10
14
|
# This is where we grab the incoming request PATH_INFO
|
@@ -45,7 +49,7 @@ module Merb
|
|
45
49
|
def route_path(path)
|
46
50
|
path = path.sub(/\/+/, '/').sub(/\?.*$/, '')
|
47
51
|
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
48
|
-
Merb::RouteMatcher.
|
52
|
+
Merb::RouteMatcher.route_request(path)
|
49
53
|
end
|
50
54
|
|
51
55
|
# take a controller class name string and reload or require
|
@@ -57,14 +61,17 @@ module Merb
|
|
57
61
|
raise "Bad controller! #{controller_name.snake_case}"
|
58
62
|
end unless $TESTING
|
59
63
|
begin
|
60
|
-
|
64
|
+
unless MERB_ENV == 'production'
|
65
|
+
Object.send(:remove_const, controller_name.camel_case.intern) rescue nil
|
66
|
+
load(controller_name.snake_case + '.rb')
|
67
|
+
end
|
61
68
|
return Object.const_get( controller_name.camel_case ).new(req, env, params, res)
|
62
69
|
rescue RuntimeError
|
63
70
|
warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
|
64
71
|
raise $!
|
65
72
|
end
|
66
73
|
end
|
67
|
-
|
74
|
+
|
68
75
|
end # end class << self
|
69
76
|
|
70
77
|
end
|
data/lib/merb/merb_exceptions.rb
CHANGED
@@ -43,7 +43,12 @@ module Merb
|
|
43
43
|
|
44
44
|
backtrace.map! do |line|
|
45
45
|
file, lineno, meth = line.scan(/(.*?):(\d+)(?::in `(.*?)')?/).first
|
46
|
-
|
46
|
+
|
47
|
+
# When a backtrace entry doesn't have a filepath as the first field
|
48
|
+
# (e.g., "(haml):12" or "(eval):41:in `_haml_render'"), it'll cause
|
49
|
+
# a second exception to be raised, masking the first. This traps it.
|
50
|
+
lines = (__caller_lines__(file, lineno, 5) rescue [])
|
51
|
+
|
47
52
|
[ lines, lines.object_id.abs, file, lineno, meth ]
|
48
53
|
end
|
49
54
|
|
data/lib/merb/merb_handler.rb
CHANGED
@@ -62,7 +62,7 @@ class MerbHandler < Mongrel::HttpHandler
|
|
62
62
|
@files.process(request,response)
|
63
63
|
else
|
64
64
|
begin
|
65
|
-
#
|
65
|
+
# Let Merb:Dispatcher find the route and call the filter chain and action
|
66
66
|
controller = nil
|
67
67
|
controller, action = Merb::Dispatcher.handle(request, response)
|
68
68
|
|
data/lib/merb/merb_mailer.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'mailfactory'
|
5
|
+
require 'net/smtp'
|
5
6
|
rescue LoadError
|
6
7
|
puts "You need to install the mailfactory gem to use Merb::Mailer"
|
7
8
|
MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
|
@@ -10,7 +11,7 @@ end
|
|
10
11
|
module Merb
|
11
12
|
class Mailer
|
12
13
|
|
13
|
-
|
14
|
+
class_inheritable_accessor :config
|
14
15
|
|
15
16
|
def sendmail
|
16
17
|
sendmail = IO.popen("sendmail #{@mail.to}", 'w+')
|
data/lib/merb/merb_request.rb
CHANGED
@@ -50,6 +50,62 @@ module Merb
|
|
50
50
|
@env['REQUEST_URI']
|
51
51
|
end
|
52
52
|
|
53
|
+
def user_agent
|
54
|
+
@env['HTTP_USER_AGENT']
|
55
|
+
end
|
56
|
+
|
57
|
+
def server_name
|
58
|
+
@env['SERVER_NAME']
|
59
|
+
end
|
60
|
+
|
61
|
+
def accept_encoding
|
62
|
+
@env['HTTP_ACCEPT_ENCODING']
|
63
|
+
end
|
64
|
+
|
65
|
+
def script_name
|
66
|
+
@env['SCRIPT_NAME']
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_control
|
70
|
+
@env['HTTP_CACHE_CONTROL']
|
71
|
+
end
|
72
|
+
|
73
|
+
def accept_language
|
74
|
+
@env['HTTP_ACCEPT_LANGUAGE']
|
75
|
+
end
|
76
|
+
|
77
|
+
def host
|
78
|
+
@env['HTTP_HOST']
|
79
|
+
end
|
80
|
+
|
81
|
+
def server_software
|
82
|
+
@env['SERVER_SOFTWARE']
|
83
|
+
end
|
84
|
+
|
85
|
+
def keep_alive
|
86
|
+
@env['HTTP_KEEP_ALIVE']
|
87
|
+
end
|
88
|
+
|
89
|
+
def accept_charset
|
90
|
+
@env['HTTP_ACCEPT_CHARSET']
|
91
|
+
end
|
92
|
+
|
93
|
+
def version
|
94
|
+
@env['HTTP_VERSION']
|
95
|
+
end
|
96
|
+
|
97
|
+
def gateway
|
98
|
+
@env['GATEWAY_INTERFACE']
|
99
|
+
end
|
100
|
+
|
101
|
+
def accept
|
102
|
+
@env['HTTP_ACCEPT']
|
103
|
+
end
|
104
|
+
|
105
|
+
def connection
|
106
|
+
@env['HTTP_CONNECTION']
|
107
|
+
end
|
108
|
+
|
53
109
|
def query_string
|
54
110
|
@env['QUERY_STRING']
|
55
111
|
end
|
data/lib/merb/merb_router.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
module Merb
|
2
|
-
|
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
|
2
|
+
|
7
3
|
# Merb::RouteMatcher is the request routing mapper for the merb framework.
|
8
4
|
# You can define placeholder parts of the url with the :symbol notation.
|
9
5
|
# so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
|
@@ -28,17 +24,7 @@ module Merb
|
|
28
24
|
yield self
|
29
25
|
compile_router
|
30
26
|
end
|
31
|
-
|
32
|
-
# init @sections for route segment recognition
|
33
|
-
def initialize
|
34
|
-
@sections = Hash.new
|
35
|
-
end
|
36
|
-
|
37
|
-
# all defined routes in their raw form.
|
38
|
-
def routes
|
39
|
-
@@routes
|
40
|
-
end
|
41
|
-
|
27
|
+
|
42
28
|
# the final compiled lambda that gets used
|
43
29
|
# as the body of the route_request method.
|
44
30
|
def self.compiled_statement
|
@@ -63,28 +49,20 @@ module Merb
|
|
63
49
|
# against each of the compiled routes in turn.
|
64
50
|
# first route that matches wins.
|
65
51
|
def self.compile_router
|
66
|
-
router_lambda = @@routes.inject("lambda{|path| \n case path\n") { |m,r|
|
52
|
+
router_lambda = @@routes.inject("lambda{|path| \n sections={}\n case path\n") { |m,r|
|
67
53
|
m << compile(r)
|
68
54
|
} <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}"
|
69
55
|
@@compiled_statement = router_lambda
|
70
|
-
|
56
|
+
meta_def(:route_request, &eval(router_lambda))
|
71
57
|
end
|
72
58
|
|
73
59
|
# compile each individual route into a when /.../
|
74
60
|
# component of the case statement. Takes /:sections
|
75
61
|
# of the route def that start with : and turns them
|
76
62
|
# into placeholders for whatever urls match against
|
77
|
-
# the route in question.
|
78
|
-
# /:controller/:action/:id route.
|
63
|
+
# the route in question.
|
79
64
|
def self.compile(route)
|
80
65
|
raise ArgumentError unless String === route[0]
|
81
|
-
if route[0] == '/:controller/:action/:id'
|
82
|
-
return ' when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
|
83
|
-
@sections[:controller] = $1
|
84
|
-
@sections[:action] = $2 || \'index\'
|
85
|
-
@sections[:id] = $3 if $3
|
86
|
-
return @sections'<<"\n"
|
87
|
-
end
|
88
66
|
code, count = '', 0
|
89
67
|
while route[0] =~ @@section_regexp
|
90
68
|
route[0] = route[0].dup
|
@@ -96,13 +74,13 @@ module Merb
|
|
96
74
|
else
|
97
75
|
route[0].sub!(@@section_regexp, "([^\/,?]+)")
|
98
76
|
end
|
99
|
-
code << "
|
77
|
+
code << " sections[:#{name}] = $#{count}\n"
|
100
78
|
end
|
101
79
|
@@compiled_regexen << Regexp.new(route[0])
|
102
80
|
index = @@compiled_regexen.size - 1
|
103
81
|
condition = " when @@compiled_regexen[#{index}] "
|
104
82
|
statement = "#{condition}\n#{code}"
|
105
|
-
statement << " return #{route[1].inspect}.update(
|
83
|
+
statement << " return #{route[1].inspect}.update(sections)\n"
|
106
84
|
statement
|
107
85
|
end
|
108
86
|
|
@@ -145,7 +123,7 @@ module Merb
|
|
145
123
|
if block_given?
|
146
124
|
procs = []
|
147
125
|
yield Resource.new(res, procs, opts)
|
148
|
-
procs.reverse.each
|
126
|
+
procs.reverse.each &:call
|
149
127
|
else
|
150
128
|
generate_resources_routes(res,opts)
|
151
129
|
end
|
@@ -157,7 +135,7 @@ module Merb
|
|
157
135
|
if block_given?
|
158
136
|
procs = []
|
159
137
|
yield Resource.new(res, procs, opts)
|
160
|
-
procs.reverse.each
|
138
|
+
procs.reverse.each &:call
|
161
139
|
else
|
162
140
|
generate_singleton_routes(res,opts)
|
163
141
|
end
|
@@ -165,19 +143,19 @@ module Merb
|
|
165
143
|
|
166
144
|
def self.generate_resources_routes(res,opt)
|
167
145
|
with_options :controller => res.to_s, :rest => true do |r|
|
168
|
-
r.add "#{opt[:prefix]}/#{res}/:id[
|
169
|
-
r.add "#{opt[:prefix]}/#{res}/new[
|
146
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;/]+edit", :allowed => {:get => 'edit'}
|
147
|
+
r.add "#{opt[:prefix]}/#{res}/new[;/]+:action", :allowed => {:get => 'new', :post => 'new', :put => 'new', :delete => 'new'}
|
170
148
|
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
171
149
|
if mem = opt[:member]
|
172
150
|
mem.keys.sort_by{|x| "#{x}"}.each {|action|
|
173
|
-
allowed = mem[action].
|
174
|
-
r.add "#{opt[:prefix]}/#{res}/:id[
|
151
|
+
allowed = mem[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
|
152
|
+
r.add "#{opt[:prefix]}/#{res}/:id[;/]+#{action}", :allowed => allowed
|
175
153
|
}
|
176
154
|
end
|
177
155
|
if coll = opt[:collection]
|
178
156
|
coll.keys.sort_by{|x| "#{x}"}.each {|action|
|
179
|
-
allowed = coll[action].
|
180
|
-
r.add "#{opt[:prefix]}/#{res}[
|
157
|
+
allowed = coll[action].injecting({}) {|h, verb| h[verb] = "#{action}"}
|
158
|
+
r.add "#{opt[:prefix]}/#{res}[;/]+#{action}", :allowed => allowed
|
181
159
|
}
|
182
160
|
end
|
183
161
|
r.add "#{opt[:prefix]}/#{res}/:id\\.:format", :allowed => {:get => 'show', :put => 'update', :delete => 'destroy'}
|
@@ -189,13 +167,21 @@ module Merb
|
|
189
167
|
|
190
168
|
def self.generate_singleton_routes(res,opt)
|
191
169
|
with_options :controller => res.to_s, :rest => true do |r|
|
192
|
-
r.add "#{opt[:prefix]}/#{res}[
|
170
|
+
r.add "#{opt[:prefix]}/#{res}[;/]+edit", :allowed => {:get => 'edit'}
|
193
171
|
r.add "#{opt[:prefix]}/#{res}\\.:format", :allowed => {:get => 'show'}
|
194
172
|
r.add "#{opt[:prefix]}/#{res}/new" , :allowed => {:get => 'new'}
|
195
173
|
r.add "#{opt[:prefix]}/#{res}/?", :allowed => {:get => 'show', :post => 'create', :put => 'update', :delete => 'destroy'}
|
196
174
|
end
|
197
175
|
end
|
198
176
|
|
177
|
+
def self.default_routes
|
178
|
+
add "/:controller/:action/:id\\.:format"
|
179
|
+
add "/:controller/:action/:id"
|
180
|
+
add "/:controller/:action\\.:format"
|
181
|
+
add "/:controller/:action"
|
182
|
+
add "/:controller\\.:format", :action => 'index'
|
183
|
+
add "/:controller", :action => 'index'
|
184
|
+
end
|
199
185
|
end
|
200
186
|
|
201
187
|
end
|
@@ -11,12 +11,13 @@ module Merb
|
|
11
11
|
buf = ""
|
12
12
|
content_length = @env['CONTENT_LENGTH'].to_i
|
13
13
|
input = request
|
14
|
+
input.binmode if defined? input.binmode
|
14
15
|
boundary_size = boundary.size + EOL.size
|
15
16
|
bufsize = 16384
|
16
17
|
content_length -= boundary_size
|
17
18
|
status = input.read(boundary_size)
|
18
19
|
raise EOFError, "bad content body" unless status == boundary + EOL
|
19
|
-
rx = /(?:#{EOL})?#{Regexp.quote(boundary)}(#{EOL}|--)/
|
20
|
+
rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
|
20
21
|
|
21
22
|
loop {
|
22
23
|
head = nil
|
@@ -32,7 +33,7 @@ module Merb
|
|
32
33
|
content_type = head[CONTENT_TYPE_REGEX, 1]
|
33
34
|
name = head[NAME_REGEX, 1]
|
34
35
|
|
35
|
-
if filename
|
36
|
+
if filename && !filename.empty?
|
36
37
|
body = Tempfile.new(:Merb)
|
37
38
|
body.binmode if defined? body.binmode
|
38
39
|
end
|
@@ -58,9 +59,14 @@ module Merb
|
|
58
59
|
content_length = -1 if $1 == "--"
|
59
60
|
end
|
60
61
|
|
61
|
-
if filename
|
62
|
+
if filename && !filename.empty?
|
62
63
|
body.rewind
|
63
|
-
data = {
|
64
|
+
data = {
|
65
|
+
:filename => File.basename(filename),
|
66
|
+
:content_type => content_type,
|
67
|
+
:tempfile => body,
|
68
|
+
:size => File.size(body)
|
69
|
+
}
|
64
70
|
else
|
65
71
|
data = body
|
66
72
|
end
|
@@ -90,7 +96,7 @@ module Merb
|
|
90
96
|
# request into the params hash. So for example:
|
91
97
|
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
92
98
|
# parses into:
|
93
|
-
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever}}
|
99
|
+
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
|
94
100
|
def query_parse(qs, d = '&;')
|
95
101
|
m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
|
96
102
|
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
@@ -161,6 +167,29 @@ module Merb
|
|
161
167
|
return
|
162
168
|
end
|
163
169
|
|
170
|
+
# stream_file( { :filename => file_name,
|
171
|
+
# :type => content_type,
|
172
|
+
# :content_length => content_length }) do
|
173
|
+
# @response.send_status(opts[:content_length])
|
174
|
+
# @response.send_header
|
175
|
+
# AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
|
176
|
+
# @response.write chunk
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
def stream_file(opts={}, &stream)
|
180
|
+
opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
|
181
|
+
disposition = opts[:disposition].dup || 'attachment'
|
182
|
+
disposition << %(; filename="#{opts[:filename]}")
|
183
|
+
@response.headers.update(
|
184
|
+
'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
|
185
|
+
'Content-Disposition' => disposition,
|
186
|
+
'Content-Transfer-Encoding' => 'binary',
|
187
|
+
'CONTENT-LENGTH' => opts[:content_length]
|
188
|
+
)
|
189
|
+
stream
|
190
|
+
end
|
191
|
+
|
192
|
+
|
164
193
|
# This uses nginx X-Accel-Redirect header to send
|
165
194
|
# a file directly from nginx. See the nginx wiki:
|
166
195
|
# http://wiki.codemongers.com/NginxXSendfile
|