roda 3.52.0 → 3.55.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.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -588
- data/doc/release_notes/3.53.0.txt +14 -0
- data/doc/release_notes/3.54.0.txt +48 -0
- data/doc/release_notes/3.55.0.txt +12 -0
- data/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/lib/roda/plugins/assets.rb +6 -3
- data/lib/roda/plugins/chunked.rb +48 -22
- data/lib/roda/plugins/drop_body.rb +5 -4
- data/lib/roda/plugins/heartbeat.rb +6 -1
- data/lib/roda/plugins/indifferent_params.rb +17 -5
- data/lib/roda/plugins/json_parser.rb +18 -3
- data/lib/roda/plugins/middleware.rb +17 -2
- data/lib/roda/plugins/multi_public.rb +5 -1
- data/lib/roda/plugins/public.rb +14 -7
- data/lib/roda/plugins/render.rb +5 -3
- data/lib/roda/plugins/route_csrf.rb +3 -1
- data/lib/roda/plugins/run_handler.rb +7 -1
- data/lib/roda/plugins/sinatra_helpers.rb +5 -1
- data/lib/roda/response.rb +34 -7
- data/lib/roda/version.rb +1 -1
- metadata +11 -4
@@ -0,0 +1,48 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* You can now override the type attribute for script tags produced
|
4
|
+
by the assets plugin, by providing a :type attribute when calling
|
5
|
+
the assets method.
|
6
|
+
|
7
|
+
= Other Improvements
|
8
|
+
|
9
|
+
* Reloading the render plugin after the additional_view_directories
|
10
|
+
plugin no longer removes the additional view directories from
|
11
|
+
the allowed paths for templates.
|
12
|
+
|
13
|
+
* When using Rack 3, Roda will now use an instance of Rack::Headers
|
14
|
+
instead of a plain hash for the headers, allowing for compliance
|
15
|
+
with the Rack 3 SPEC (which will require lowercase header keys).
|
16
|
+
|
17
|
+
* The public, multi_public, and sinatra_helpers plugin now use
|
18
|
+
Rack::Files instead of Rack::File if available, as Rack::File will
|
19
|
+
be deprecated in Rack 3.0.
|
20
|
+
|
21
|
+
* The json_parser plugin no longer rewinds the request body before
|
22
|
+
and after reading it when used with Rack 3.0, as Rack 3.0 has
|
23
|
+
dropped the requirement for rewindable input.
|
24
|
+
|
25
|
+
* The run_handler plugin now closes bodies for upstream 404 responses
|
26
|
+
when using the not_found: :pass option.
|
27
|
+
|
28
|
+
* The chunked plugin no longer uses Transfer-Encoding: chunked by
|
29
|
+
default. Requiring the use of Transfer-Encoding: chunked made the
|
30
|
+
plugin only work on HTTP 1.1, and not older or newer versions. The
|
31
|
+
plugin still allows for streaming template bodies as they are being
|
32
|
+
rendered. To get the previous behavior of forcing the use of
|
33
|
+
Transfer-Encoding: chunked, you can use the :force_chunked_encoding
|
34
|
+
plugin option
|
35
|
+
|
36
|
+
* Roda now supports testing with Rack::Lint. This found multiple
|
37
|
+
violations of the Rack SPEC which are fixed in this version, and
|
38
|
+
should ensure that Roda stays in compliance with the Rack SPEC going
|
39
|
+
forward.
|
40
|
+
|
41
|
+
= Backwards Compatibility
|
42
|
+
|
43
|
+
* Roda will no longer set the Content-Length header for 205 responses
|
44
|
+
when using Rack <2.0.2, as doing so violates the Rack SPEC for those
|
45
|
+
Rack versions.
|
46
|
+
|
47
|
+
* The drop_body plugin now drops response bodies for all 1xx responses,
|
48
|
+
not just for 100 and 101 responses, in compliance with the Rack SPEC.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A :forward_response_headers option has been added to the middleware
|
4
|
+
plugin, which uses the response headers added by the middleware
|
5
|
+
as default response headers even if the middleware does not handle
|
6
|
+
the response. Response headers set by the underlying application
|
7
|
+
take precedence over response headers set by the middleware.
|
8
|
+
|
9
|
+
* The render plugin view method now accepts a block and will pass the
|
10
|
+
block to the underlying render method call. This is useful for
|
11
|
+
rendering a template that yields inside of an existing layout.
|
12
|
+
Previously, you had to nest render calls to do that.
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The additional_view_directories plugin allows for specifying additional view
|
7
|
+
# directories to look in for templates. When rendering a template, it will
|
8
|
+
# first try the :views directory specified in the render plugin. If the template
|
9
|
+
# file to be rendered does not exist in that directory, it will try each additional
|
10
|
+
# view directory specified in this plugin, in order, using the path to the first
|
11
|
+
# template file that exists in the file system. If no such path is found, it
|
12
|
+
# uses the default path specified by the render plugin.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# plugin :render, :views=>'dir'
|
17
|
+
# plugin :additional_view_directories, ['dir1', 'dir2', 'dir3']
|
18
|
+
#
|
19
|
+
# route do |r|
|
20
|
+
# # Will check the following in order, using path for first
|
21
|
+
# # template file that exists:
|
22
|
+
# # * dir/t.erb
|
23
|
+
# # * dir1/t.erb
|
24
|
+
# # * dir2/t.erb
|
25
|
+
# # * dir3/t.erb
|
26
|
+
# render :t
|
27
|
+
# end
|
28
|
+
module AdditionalViewDirectories
|
29
|
+
# Depend on the render plugin, since this plugin only makes
|
30
|
+
# sense when the render plugin is used.
|
31
|
+
def self.load_dependencies(app, view_dirs)
|
32
|
+
app.plugin :render
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the additional view directories to look in. Each additional view directory
|
36
|
+
# is also added as an allowed path.
|
37
|
+
def self.configure(app, view_dirs)
|
38
|
+
view_dirs = app.opts[:additional_view_directories] = view_dirs.map{|f| app.expand_path(f, nil)}.freeze
|
39
|
+
app.plugin :render, :allowed_paths=>(app.opts[:render][:allowed_paths] + view_dirs).uniq.freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
private
|
44
|
+
|
45
|
+
# If the template path does not exist, try looking for the template
|
46
|
+
# in each of the additional view directories, in order, returning
|
47
|
+
# the first path that exists. If no additional directory includes
|
48
|
+
# the template, return the original path.
|
49
|
+
def template_path(opts)
|
50
|
+
orig_path = super
|
51
|
+
|
52
|
+
unless File.file?(orig_path)
|
53
|
+
self.opts[:additional_view_directories].each do |view_dir|
|
54
|
+
path = super(opts.merge(:views=>view_dir))
|
55
|
+
return path if File.file?(path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
orig_path
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
register_plugin(:additional_view_directories, AdditionalViewDirectories)
|
65
|
+
end
|
66
|
+
end
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -708,6 +708,9 @@ class Roda
|
|
708
708
|
# To return the tags for a specific asset group, use an array for
|
709
709
|
# the type, such as [:css, :frontend].
|
710
710
|
#
|
711
|
+
# You can specify custom attributes for the tag by passing a hash
|
712
|
+
# as the attrs argument.
|
713
|
+
#
|
711
714
|
# When the assets are not compiled, this will result in a separate
|
712
715
|
# tag for each asset file. When the assets are compiled, this will
|
713
716
|
# result in a single tag to the compiled asset file.
|
@@ -720,13 +723,13 @@ class Roda
|
|
720
723
|
attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
|
721
724
|
end
|
722
725
|
|
723
|
-
|
726
|
+
attributes = attrs.map{|k,v| "#{k}=\"#{h(v)}\""}.join(' ')
|
724
727
|
|
725
728
|
if ltype == :js
|
726
|
-
tag_start = "<script type
|
729
|
+
tag_start = "<script#{' type="text/javascript"' unless attrs[:type]} #{attributes} src=\""
|
727
730
|
tag_end = "\"></script>"
|
728
731
|
else
|
729
|
-
tag_start = "<link rel=\"stylesheet\" #{
|
732
|
+
tag_start = "<link rel=\"stylesheet\" #{attributes} href=\""
|
730
733
|
tag_end = "\" />"
|
731
734
|
end
|
732
735
|
|
data/lib/roda/plugins/chunked.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
#
|
4
4
|
class Roda
|
5
5
|
module RodaPlugins
|
6
|
-
# The chunked plugin allows you to stream
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
6
|
+
# The chunked plugin allows you to stream rendered views to clients.
|
7
|
+
# This can significantly improve performance of page rendering on the
|
8
|
+
# client, as it flushes the headers and top part of the layout template
|
9
|
+
# (generally containing references to the stylesheet and javascript assets)
|
10
|
+
# before rendering the content template.
|
11
11
|
#
|
12
12
|
# This allows the client to fetch the assets while the template is still
|
13
13
|
# being rendered. Additionally, this plugin makes it easy to defer
|
@@ -17,11 +17,11 @@ class Roda
|
|
17
17
|
# order to render the content template, such as retrieving values from a
|
18
18
|
# database.
|
19
19
|
#
|
20
|
-
# There are a couple disadvantages of streaming
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
20
|
+
# There are a couple disadvantages of streaming. First is that the layout
|
21
|
+
# must be rendered before the content, so any state changes made in your
|
22
|
+
# content template will not affect the layout template. Second, error
|
23
|
+
# handling is reduced, since if an error occurs while rendering a template,
|
24
|
+
# a successful response code has already been sent.
|
25
25
|
#
|
26
26
|
# To use chunked encoding for a response, just call the chunked method
|
27
27
|
# instead of view:
|
@@ -56,7 +56,6 @@ class Roda
|
|
56
56
|
# end
|
57
57
|
# end
|
58
58
|
#
|
59
|
-
#
|
60
59
|
# If you want to chunk all responses, pass the :chunk_by_default option
|
61
60
|
# when loading the plugin:
|
62
61
|
#
|
@@ -121,12 +120,7 @@ class Roda
|
|
121
120
|
# flush to output the message to the client inside handle_chunk_error.
|
122
121
|
#
|
123
122
|
# In order for chunking to work, you must make sure that no proxies between
|
124
|
-
# the application and the client buffer responses.
|
125
|
-
# plugin only works for HTTP/1.1 requests since Transfer-Encoding: chunked
|
126
|
-
# is not supported in HTTP/1.0. If an HTTP/1.0 request is submitted, this
|
127
|
-
# plugin will automatically fallback to the normal template rendering.
|
128
|
-
# Note that some proxies including nginx default to HTTP/1.0 even if the
|
129
|
-
# client supports HTTP/1.1. For nginx, set the proxy_http_version to 1.1.
|
123
|
+
# the application and the client buffer responses.
|
130
124
|
#
|
131
125
|
# If you are using nginx and have it set to buffer proxy responses by
|
132
126
|
# default, you can turn this off on a per response basis using the
|
@@ -135,6 +129,15 @@ class Roda
|
|
135
129
|
#
|
136
130
|
# plugin :chunked, headers: {'X-Accel-Buffering'=>'no'}
|
137
131
|
#
|
132
|
+
# By default, this plugin does not use Transfer-Encoding: chunked, it only
|
133
|
+
# returns a body that will stream the response in chunks. If you would like
|
134
|
+
# to force the use of Transfer-Encoding: chunked, you can use the
|
135
|
+
# :force_chunked_encoding plugin option. If using the
|
136
|
+
# :force_chunked_encoding plugin option, chunking will only be used for
|
137
|
+
# HTTP/1.1 requests since Transfer-Encoding: chunked is only supported
|
138
|
+
# in HTTP/1.1 (non-HTTP/1.1 requests will have behavior similar to
|
139
|
+
# calling no_chunk!).
|
140
|
+
#
|
138
141
|
# The chunked plugin requires the render plugin, and only works for
|
139
142
|
# template engines that store their template output variable in
|
140
143
|
# @_out_buf. Also, it only works if the content template is directly
|
@@ -155,12 +158,14 @@ class Roda
|
|
155
158
|
# :headers :: Set default additional headers to use when calling view
|
156
159
|
def self.configure(app, opts=OPTS)
|
157
160
|
app.opts[:chunk_by_default] = opts[:chunk_by_default]
|
161
|
+
app.opts[:force_chunked_encoding] = opts[:force_chunked_encoding]
|
158
162
|
if opts[:headers]
|
159
163
|
app.opts[:chunk_headers] = (app.opts[:chunk_headers] || {}).merge(opts[:headers]).freeze
|
160
164
|
end
|
161
165
|
end
|
162
166
|
|
163
|
-
# Rack response body instance for chunked responses
|
167
|
+
# Rack response body instance for chunked responses using
|
168
|
+
# Transfer-Encoding: chunked.
|
164
169
|
class Body
|
165
170
|
# Save the scope of the current request handling.
|
166
171
|
def initialize(scope)
|
@@ -184,6 +189,22 @@ class Roda
|
|
184
189
|
end
|
185
190
|
end
|
186
191
|
|
192
|
+
# Rack response body instance for chunked responses not
|
193
|
+
# using Transfer-Encoding: chunked.
|
194
|
+
class StreamBody
|
195
|
+
# Save the scope of the current request handling.
|
196
|
+
def initialize(scope)
|
197
|
+
@scope = scope
|
198
|
+
end
|
199
|
+
|
200
|
+
# Yield each non-empty chunk as the body.
|
201
|
+
def each(&block)
|
202
|
+
@scope.each_chunk do |chunk|
|
203
|
+
yield chunk if chunk && !chunk.empty?
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
187
208
|
module InstanceMethods
|
188
209
|
# Disable chunking for the current request. Mostly useful when
|
189
210
|
# chunking is turned on by default.
|
@@ -194,7 +215,7 @@ class Roda
|
|
194
215
|
# If chunking by default, call chunked if it hasn't yet been
|
195
216
|
# called and chunking is not specifically disabled.
|
196
217
|
def view(*a)
|
197
|
-
if opts[:chunk_by_default] && !defined?(@_chunked)
|
218
|
+
if opts[:chunk_by_default] && !defined?(@_chunked) && !defined?(yield)
|
198
219
|
chunked(*a)
|
199
220
|
else
|
200
221
|
super
|
@@ -205,7 +226,7 @@ class Roda
|
|
205
226
|
# an overview. If a block is given, it is passed to #delay.
|
206
227
|
def chunked(template, opts=OPTS, &block)
|
207
228
|
unless defined?(@_chunked)
|
208
|
-
@_chunked = env['HTTP_VERSION'] == "HTTP/1.1"
|
229
|
+
@_chunked = !self.opts[:force_chunked_encoding] || env['HTTP_VERSION'] == "HTTP/1.1"
|
209
230
|
end
|
210
231
|
|
211
232
|
if block
|
@@ -235,9 +256,14 @@ class Roda
|
|
235
256
|
if chunk_headers = self.opts[:chunk_headers]
|
236
257
|
headers.merge!(chunk_headers)
|
237
258
|
end
|
238
|
-
|
259
|
+
if self.opts[:force_chunked_encoding]
|
260
|
+
headers['Transfer-Encoding'] = 'chunked'
|
261
|
+
body = Body.new(self)
|
262
|
+
else
|
263
|
+
body = StreamBody.new(self)
|
264
|
+
end
|
239
265
|
|
240
|
-
throw :halt, res.finish_with_body(
|
266
|
+
throw :halt, res.finish_with_body(body)
|
241
267
|
end
|
242
268
|
|
243
269
|
# Delay the execution of the block until right before the
|
@@ -15,22 +15,23 @@ class Roda
|
|
15
15
|
DROP_BODY_STATUSES = [100, 101, 102, 204, 205, 304].freeze
|
16
16
|
RodaPlugins.deprecate_constant(self, :DROP_BODY_STATUSES)
|
17
17
|
|
18
|
+
DROP_BODY_RANGE = 100..199
|
19
|
+
private_constant :DROP_BODY_RANGE
|
20
|
+
|
18
21
|
# If the response status indicates a body should not be
|
19
22
|
# returned, use an empty body and remove the Content-Length
|
20
23
|
# and Content-Type headers.
|
21
24
|
def finish
|
22
25
|
r = super
|
23
26
|
case r[0]
|
24
|
-
when
|
27
|
+
when DROP_BODY_RANGE, 204, 304
|
25
28
|
r[2] = EMPTY_ARRAY
|
26
29
|
h = r[1]
|
27
30
|
h.delete("Content-Length")
|
28
31
|
h.delete("Content-Type")
|
29
32
|
when 205
|
30
33
|
r[2] = EMPTY_ARRAY
|
31
|
-
|
32
|
-
h["Content-Length"] = '0'
|
33
|
-
h.delete("Content-Type")
|
34
|
+
empty_205_headers(r[1])
|
34
35
|
end
|
35
36
|
r
|
36
37
|
end
|
@@ -14,6 +14,11 @@ class Roda
|
|
14
14
|
#
|
15
15
|
# plugin :heartbeat, path: '/status'
|
16
16
|
module Heartbeat
|
17
|
+
# :nocov:
|
18
|
+
HEADER_CLASS = (defined?(Rack::Headers) && Rack::Headers.is_a?(Class)) ? Rack::Headers : Hash
|
19
|
+
# :nocov:
|
20
|
+
private_constant :HEADER_CLASS
|
21
|
+
|
17
22
|
HEARTBEAT_RESPONSE = [200, {'Content-Type'=>'text/plain'}.freeze, ['OK'.freeze].freeze].freeze
|
18
23
|
|
19
24
|
# Set the heartbeat path to the given path.
|
@@ -28,7 +33,7 @@ class Roda
|
|
28
33
|
def _roda_before_20__heartbeat
|
29
34
|
if env['PATH_INFO'] == opts[:heartbeat_path]
|
30
35
|
response = HEARTBEAT_RESPONSE.dup
|
31
|
-
response[1] =
|
36
|
+
response[1] = HEADER_CLASS[response[1]]
|
32
37
|
throw :halt, response
|
33
38
|
end
|
34
39
|
end
|
@@ -52,17 +52,29 @@ class Roda
|
|
52
52
|
end
|
53
53
|
|
54
54
|
class Params < Rack::QueryParser::Params
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
# :nocov:
|
56
|
+
if Rack.release >= '2.3'
|
57
|
+
def initialize
|
58
|
+
@size = 0
|
59
|
+
@params = Hash.new(&INDIFFERENT_PROC)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
# :nocov:
|
63
|
+
def initialize(limit = Rack::Utils.key_space_limit)
|
64
|
+
@limit = limit
|
65
|
+
@size = 0
|
66
|
+
@params = Hash.new(&INDIFFERENT_PROC)
|
67
|
+
end
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
62
71
|
end
|
63
72
|
|
64
73
|
module RequestMethods
|
65
|
-
|
74
|
+
# :nocov:
|
75
|
+
query_parser = Rack.release >= '2.3' ? QueryParser.new(QueryParser::Params, 32) : QueryParser.new(QueryParser::Params, 65536, 32)
|
76
|
+
# :nocov:
|
77
|
+
QUERY_PARSER = Rack::Utils.default_query_parser = query_parser
|
66
78
|
|
67
79
|
private
|
68
80
|
|
@@ -52,9 +52,7 @@ class Roda
|
|
52
52
|
if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
|
53
53
|
post_params
|
54
54
|
elsif (input = env["rack.input"]) && content_type =~ /json/
|
55
|
-
input
|
56
|
-
str = input.read
|
57
|
-
input.rewind
|
55
|
+
str = _read_json_input(input)
|
58
56
|
return super if str.empty?
|
59
57
|
begin
|
60
58
|
json_params = parse_json(str)
|
@@ -81,6 +79,23 @@ class Roda
|
|
81
79
|
args << self if roda_class.opts[:json_parser_include_request]
|
82
80
|
roda_class.opts[:json_parser_parser].call(*args)
|
83
81
|
end
|
82
|
+
|
83
|
+
|
84
|
+
# Rack 3 dropped requirement that input be rewindable
|
85
|
+
if Rack.release >= '2.3'
|
86
|
+
# :nocov:
|
87
|
+
def _read_json_input(input)
|
88
|
+
input.read
|
89
|
+
end
|
90
|
+
# :nocov:
|
91
|
+
else
|
92
|
+
def _read_json_input(input)
|
93
|
+
input.rewind
|
94
|
+
str = input.read
|
95
|
+
input.rewind
|
96
|
+
str
|
97
|
+
end
|
98
|
+
end
|
84
99
|
end
|
85
100
|
end
|
86
101
|
|
@@ -73,11 +73,16 @@ class Roda
|
|
73
73
|
# and rack response for all requests passing through the middleware,
|
74
74
|
# after either the middleware or next app handles the request
|
75
75
|
# and returns a response.
|
76
|
+
# :forward_response_headers :: Whether changes to the response headers made inside
|
77
|
+
# the middleware's route block should be applied to the
|
78
|
+
# final response when the request is forwarded to the app.
|
79
|
+
# Defaults to false.
|
76
80
|
def self.configure(app, opts={}, &block)
|
77
81
|
app.opts[:middleware_env_var] = opts[:env_var] if opts.has_key?(:env_var)
|
78
82
|
app.opts[:middleware_env_var] ||= 'roda.forward_next'
|
79
83
|
app.opts[:middleware_configure] = block if block
|
80
84
|
app.opts[:middleware_handle_result] = opts[:handle_result]
|
85
|
+
app.opts[:middleware_forward_response_headers] = opts[:forward_response_headers]
|
81
86
|
end
|
82
87
|
|
83
88
|
# Forwarder instances are what is actually used as middleware.
|
@@ -108,6 +113,10 @@ class Roda
|
|
108
113
|
|
109
114
|
if call_next
|
110
115
|
res = @app.call(env)
|
116
|
+
|
117
|
+
if modified_headers = env.delete('roda.response_headers')
|
118
|
+
res[1] = modified_headers.merge(res[1])
|
119
|
+
end
|
111
120
|
end
|
112
121
|
|
113
122
|
if handle_result = @mid.opts[:middleware_handle_result]
|
@@ -135,7 +144,10 @@ class Roda
|
|
135
144
|
def call(&block)
|
136
145
|
super do |r|
|
137
146
|
res = instance_exec(r, &block) # call Fallback
|
138
|
-
|
147
|
+
if r.forward_next
|
148
|
+
r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
|
149
|
+
throw :next, true
|
150
|
+
end
|
139
151
|
res
|
140
152
|
end
|
141
153
|
end
|
@@ -144,7 +156,10 @@ class Roda
|
|
144
156
|
# that the next middleware is called.
|
145
157
|
def _roda_run_main_route(r)
|
146
158
|
res = super
|
147
|
-
|
159
|
+
if r.forward_next
|
160
|
+
r.env['roda.response_headers'] = response.headers if opts[:middleware_forward_response_headers]
|
161
|
+
throw :next, true
|
162
|
+
end
|
148
163
|
res
|
149
164
|
end
|
150
165
|
end
|
@@ -58,6 +58,10 @@ class Roda
|
|
58
58
|
# end
|
59
59
|
# end
|
60
60
|
module MultiPublic
|
61
|
+
# :nocov:
|
62
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
63
|
+
# :nocov:
|
64
|
+
|
61
65
|
def self.load_dependencies(app, _, opts=OPTS)
|
62
66
|
app.plugin(:public, opts)
|
63
67
|
end
|
@@ -67,7 +71,7 @@ class Roda
|
|
67
71
|
roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
|
68
72
|
directories.each do |key, path|
|
69
73
|
path, headers, mime = path
|
70
|
-
roots[key] =
|
74
|
+
roots[key] = RACK_FILES.new(app.expand_path(path), headers||{}, mime||'text/plain')
|
71
75
|
end
|
72
76
|
roots.freeze
|
73
77
|
end
|
data/lib/roda/plugins/public.rb
CHANGED
@@ -37,6 +37,9 @@ class Roda
|
|
37
37
|
module Public
|
38
38
|
SPLIT = Regexp.union(*[File::SEPARATOR, File::ALT_SEPARATOR].compact)
|
39
39
|
PARSER = URI::DEFAULT_PARSER
|
40
|
+
# :nocov:
|
41
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
42
|
+
# :nocov:
|
40
43
|
|
41
44
|
# Use options given to setup a Rack::File instance for serving files. Options:
|
42
45
|
# :default_mime :: The default mime type to use if the mime type is not recognized.
|
@@ -52,7 +55,7 @@ class Roda
|
|
52
55
|
elsif !app.opts[:public_root]
|
53
56
|
app.opts[:public_root] = app.expand_path("public")
|
54
57
|
end
|
55
|
-
app.opts[:public_server] =
|
58
|
+
app.opts[:public_server] = RACK_FILES.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
56
59
|
app.opts[:public_gzip] = opts[:gzip]
|
57
60
|
app.opts[:public_brotli] = opts[:brotli]
|
58
61
|
end
|
@@ -99,7 +102,10 @@ class Roda
|
|
99
102
|
public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
|
100
103
|
|
101
104
|
if public_file_readable?(path)
|
102
|
-
|
105
|
+
s, h, b = public_serve(server, path)
|
106
|
+
headers = response.headers
|
107
|
+
headers.replace(h)
|
108
|
+
halt [s, headers, b]
|
103
109
|
end
|
104
110
|
end
|
105
111
|
|
@@ -108,21 +114,22 @@ class Roda
|
|
108
114
|
compressed_path = path + suffix
|
109
115
|
|
110
116
|
if public_file_readable?(compressed_path)
|
111
|
-
|
112
|
-
headers =
|
117
|
+
s, h, b = public_serve(server, compressed_path)
|
118
|
+
headers = response.headers
|
119
|
+
headers.replace(h)
|
113
120
|
|
114
|
-
unless
|
121
|
+
unless s == 304
|
115
122
|
headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
116
123
|
headers['Content-Encoding'] = encoding
|
117
124
|
end
|
118
125
|
|
119
|
-
halt
|
126
|
+
halt [s, headers, b]
|
120
127
|
end
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
124
131
|
if ::Rack.release > '2'
|
125
|
-
# Serve the given path using the given Rack::
|
132
|
+
# Serve the given path using the given Rack::Files server.
|
126
133
|
def public_serve(server, path)
|
127
134
|
server.serving(self, path)
|
128
135
|
end
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -495,8 +495,10 @@ class Roda
|
|
495
495
|
|
496
496
|
# Render the given template. If there is a default layout
|
497
497
|
# for the class, take the result of the template rendering
|
498
|
-
# and render it inside the layout.
|
499
|
-
|
498
|
+
# and render it inside the layout. Blocks passed to view
|
499
|
+
# are passed to render when rendering the template.
|
500
|
+
# See Render for details.
|
501
|
+
def view(template, opts = (content = _optimized_view_content(template) unless defined?(yield); OPTS), &block)
|
500
502
|
if content
|
501
503
|
# First, check if the optimized layout method has already been created,
|
502
504
|
# and use it if so. This way avoids the extra conditional and local variable
|
@@ -516,7 +518,7 @@ class Roda
|
|
516
518
|
end
|
517
519
|
else
|
518
520
|
opts = parse_template_opts(template, opts)
|
519
|
-
content = opts[:content] || render_template(opts)
|
521
|
+
content = opts[:content] || render_template(opts, &block)
|
520
522
|
end
|
521
523
|
|
522
524
|
if layout_opts = view_layout_opts(opts)
|
@@ -191,7 +191,9 @@ class Roda
|
|
191
191
|
when :raise
|
192
192
|
raise InvalidToken, msg
|
193
193
|
when :empty_403
|
194
|
-
|
194
|
+
@_response.status = 403
|
195
|
+
@_response.headers.replace('Content-Type'=>'text/html', 'Content-Length'=>'0')
|
196
|
+
throw :halt, @_response.finish_with_body([])
|
195
197
|
when :clear_session
|
196
198
|
session.clear
|
197
199
|
when :csrf_failure_method
|
@@ -35,7 +35,13 @@ class Roda
|
|
35
35
|
def run(app, opts=OPTS)
|
36
36
|
res = catch(:halt){super(app)}
|
37
37
|
yield res if defined?(yield)
|
38
|
-
|
38
|
+
if opts[:not_found] == :pass && res[0] == 404
|
39
|
+
body = res[2]
|
40
|
+
body.close if body.respond_to?(:close)
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
throw(:halt, res)
|
44
|
+
end
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -215,6 +215,10 @@ class Roda
|
|
215
215
|
ISO88591_ENCODING = Encoding.find('ISO-8859-1')
|
216
216
|
BINARY_ENCODING = Encoding.find('BINARY')
|
217
217
|
|
218
|
+
# :nocov:
|
219
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
220
|
+
# :nocov:
|
221
|
+
|
218
222
|
# Depend on the status_303 plugin.
|
219
223
|
def self.load_dependencies(app, _opts = nil)
|
220
224
|
app.plugin :status_303
|
@@ -333,7 +337,7 @@ class Roda
|
|
333
337
|
last_modified(lm)
|
334
338
|
end
|
335
339
|
|
336
|
-
file =
|
340
|
+
file = RACK_FILES.new nil
|
337
341
|
s, h, b = if Rack.release > '2'
|
338
342
|
file.serving(self, path)
|
339
343
|
else
|