roda 3.53.0 → 3.56.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 +42 -588
- data/doc/release_notes/3.54.0.txt +48 -0
- data/doc/release_notes/3.55.0.txt +12 -0
- data/doc/release_notes/3.56.0.txt +33 -0
- data/lib/roda/plugins/additional_view_directories.rb +1 -3
- data/lib/roda/plugins/assets.rb +6 -3
- data/lib/roda/plugins/chunked.rb +48 -22
- data/lib/roda/plugins/common_logger.rb +1 -1
- data/lib/roda/plugins/cookies.rb +2 -0
- data/lib/roda/plugins/drop_body.rb +5 -4
- data/lib/roda/plugins/heartbeat.rb +6 -1
- data/lib/roda/plugins/json_parser.rb +24 -5
- data/lib/roda/plugins/middleware.rb +17 -2
- data/lib/roda/plugins/multi_public.rb +13 -1
- data/lib/roda/plugins/not_allowed.rb +13 -0
- data/lib/roda/plugins/public.rb +22 -7
- data/lib/roda/plugins/render.rb +5 -3
- data/lib/roda/plugins/route_csrf.rb +4 -1
- data/lib/roda/plugins/run_append_slash.rb +1 -1
- data/lib/roda/plugins/run_handler.rb +7 -1
- data/lib/roda/plugins/run_require_slash.rb +46 -0
- data/lib/roda/plugins/sessions.rb +1 -0
- data/lib/roda/plugins/sinatra_helpers.rb +15 -1
- data/lib/roda/plugins/static.rb +2 -0
- data/lib/roda/plugins/status_303.rb +6 -3
- data/lib/roda/plugins/status_handler.rb +35 -9
- data/lib/roda/plugins/symbol_status.rb +2 -0
- data/lib/roda/plugins/unescape_path.rb +2 -0
- data/lib/roda/request.rb +35 -1
- data/lib/roda/response.rb +39 -7
- data/lib/roda/version.rb +1 -1
- metadata +28 -7
|
@@ -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,33 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* RodaRequest#http_version has been added for determining the HTTP
|
|
4
|
+
version the request was submitted with. This will be a string
|
|
5
|
+
such as "HTTP/1.0", "HTTP/1.1", "HTTP/2", etc. This will use the
|
|
6
|
+
SERVER_PROTOCOL and HTTP_VERSION entries from the environment to
|
|
7
|
+
determine which HTTP version is in use.
|
|
8
|
+
|
|
9
|
+
* The status_handler method in the status_handler plugin now supports
|
|
10
|
+
a :keep_headers option. The value for this option should be an
|
|
11
|
+
array of header names to keep. All other headers are removed. The
|
|
12
|
+
default behavior without the option is still to remove all headers.
|
|
13
|
+
|
|
14
|
+
* A run_require_slash plugin has been added, which will skip
|
|
15
|
+
dispatching to another rack application if the remaining path is not
|
|
16
|
+
empty and does not start with a slash.
|
|
17
|
+
|
|
18
|
+
= Other Improvements
|
|
19
|
+
|
|
20
|
+
* The status_303 plugin will use 303 as the default redirect status
|
|
21
|
+
for non-GET requests for HTTP/2 and higher HTTP versions. Previously,
|
|
22
|
+
it only used 303 for HTTP/1.1.
|
|
23
|
+
|
|
24
|
+
* The not_allowed plugin now overrides the r.root method to return
|
|
25
|
+
405 responses to non-GET requests to the root.
|
|
26
|
+
|
|
27
|
+
* The not_allowed plugin no longer sets the body when returning 405
|
|
28
|
+
responses using methods such as r.get and r.post. Previously, the
|
|
29
|
+
body was unintentionally set to the same value as the Allow header.
|
|
30
|
+
|
|
31
|
+
* When using the Rack master branch (what will become Rack 3), Roda
|
|
32
|
+
only requires the parts of rack that it uses, instead of requiring
|
|
33
|
+
rack and relying on autoload to load the parts of rack in use.
|
|
@@ -36,9 +36,7 @@ class Roda
|
|
|
36
36
|
# is also added as an allowed path.
|
|
37
37
|
def self.configure(app, view_dirs)
|
|
38
38
|
view_dirs = app.opts[:additional_view_directories] = view_dirs.map{|f| app.expand_path(f, nil)}.freeze
|
|
39
|
-
|
|
40
|
-
app.opts[:render] = opts.merge(:allowed_paths=>(opts[:allowed_paths] + view_dirs).uniq.freeze)
|
|
41
|
-
opts.freeze
|
|
39
|
+
app.plugin :render, :allowed_paths=>(app.opts[:render][:allowed_paths] + view_dirs).uniq.freeze
|
|
42
40
|
end
|
|
43
41
|
|
|
44
42
|
module InstanceMethods
|
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 =
|
|
229
|
+
@_chunked = !self.opts[:force_chunked_encoding] || @_request.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
|
|
@@ -53,7 +53,7 @@ class Roda
|
|
|
53
53
|
|
|
54
54
|
env = @_request.env
|
|
55
55
|
|
|
56
|
-
opts[:common_logger_meth].call("#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{
|
|
56
|
+
opts[:common_logger_meth].call("#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{result[0]} #{((length = result[1]['Content-Length']) && (length unless length == '0')) || '-'} #{elapsed_time}\n")
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Create timer instance used for timing
|
data/lib/roda/plugins/cookies.rb
CHANGED
|
@@ -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
|
|
@@ -4,12 +4,16 @@ require 'json'
|
|
|
4
4
|
|
|
5
5
|
class Roda
|
|
6
6
|
module RodaPlugins
|
|
7
|
-
# The json_parser plugin parses request bodies in
|
|
7
|
+
# The json_parser plugin parses request bodies in JSON format
|
|
8
8
|
# if the request's content type specifies json. This is mostly
|
|
9
9
|
# designed for use with JSON API sites.
|
|
10
10
|
#
|
|
11
11
|
# This only parses the request body as JSON if the Content-Type
|
|
12
12
|
# header for the request includes "json".
|
|
13
|
+
#
|
|
14
|
+
# The parsed JSON body will be available in +r.POST+, just as a
|
|
15
|
+
# parsed HTML form body would be. It will also be available in
|
|
16
|
+
# +r.params+ (which merges +r.GET+ with +r.POST+).
|
|
13
17
|
module JsonParser
|
|
14
18
|
DEFAULT_ERROR_HANDLER = proc{|r| r.halt [400, {}, []]}
|
|
15
19
|
|
|
@@ -25,7 +29,7 @@ class Roda
|
|
|
25
29
|
# object as the second argument, so the parser needs
|
|
26
30
|
# to respond to +call(str, request)+.
|
|
27
31
|
# :wrap :: Whether to wrap uploaded JSON data in a hash with a "_json"
|
|
28
|
-
# key. Without this, calls to r.params will fail if a non-Hash
|
|
32
|
+
# key. Without this, calls to +r.params+ will fail if a non-Hash
|
|
29
33
|
# (such as an array) is uploaded in JSON format. A value of
|
|
30
34
|
# :always will wrap all values, and a value of :unless_hash will
|
|
31
35
|
# only wrap values that are not already hashes.
|
|
@@ -52,9 +56,7 @@ class Roda
|
|
|
52
56
|
if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
|
|
53
57
|
post_params
|
|
54
58
|
elsif (input = env["rack.input"]) && content_type =~ /json/
|
|
55
|
-
input
|
|
56
|
-
str = input.read
|
|
57
|
-
input.rewind
|
|
59
|
+
str = _read_json_input(input)
|
|
58
60
|
return super if str.empty?
|
|
59
61
|
begin
|
|
60
62
|
json_params = parse_json(str)
|
|
@@ -81,6 +83,23 @@ class Roda
|
|
|
81
83
|
args << self if roda_class.opts[:json_parser_include_request]
|
|
82
84
|
roda_class.opts[:json_parser_parser].call(*args)
|
|
83
85
|
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Rack 3 dropped requirement that input be rewindable
|
|
89
|
+
if Rack.release >= '2.3'
|
|
90
|
+
# :nocov:
|
|
91
|
+
def _read_json_input(input)
|
|
92
|
+
input.read
|
|
93
|
+
end
|
|
94
|
+
# :nocov:
|
|
95
|
+
else
|
|
96
|
+
def _read_json_input(input)
|
|
97
|
+
input.rewind
|
|
98
|
+
str = input.read
|
|
99
|
+
input.rewind
|
|
100
|
+
str
|
|
101
|
+
end
|
|
102
|
+
end
|
|
84
103
|
end
|
|
85
104
|
end
|
|
86
105
|
|
|
@@ -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
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
+
begin
|
|
4
|
+
require 'rack/files'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# :nocov:
|
|
7
|
+
require 'rack/file'
|
|
8
|
+
# :nocov:
|
|
9
|
+
end
|
|
10
|
+
|
|
3
11
|
#
|
|
4
12
|
class Roda
|
|
5
13
|
module RodaPlugins
|
|
@@ -58,6 +66,10 @@ class Roda
|
|
|
58
66
|
# end
|
|
59
67
|
# end
|
|
60
68
|
module MultiPublic
|
|
69
|
+
# :nocov:
|
|
70
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
|
71
|
+
# :nocov:
|
|
72
|
+
|
|
61
73
|
def self.load_dependencies(app, _, opts=OPTS)
|
|
62
74
|
app.plugin(:public, opts)
|
|
63
75
|
end
|
|
@@ -67,7 +79,7 @@ class Roda
|
|
|
67
79
|
roots = app.opts[:multi_public_servers] = (app.opts[:multi_public_servers] || {}).dup
|
|
68
80
|
directories.each do |key, path|
|
|
69
81
|
path, headers, mime = path
|
|
70
|
-
roots[key] =
|
|
82
|
+
roots[key] = RACK_FILES.new(app.expand_path(path), headers||{}, mime||'text/plain')
|
|
71
83
|
end
|
|
72
84
|
roots.freeze
|
|
73
85
|
end
|
|
@@ -17,6 +17,9 @@ class Roda
|
|
|
17
17
|
# will return a 200 response for <tt>GET /</tt> and a 405
|
|
18
18
|
# response for <tt>POST /</tt>.
|
|
19
19
|
#
|
|
20
|
+
# This plugin changes the +r.root+ method to return a 405 status
|
|
21
|
+
# for non-GET requests to +/+.
|
|
22
|
+
#
|
|
20
23
|
# This plugin also changes the +r.is+ method so that if you use
|
|
21
24
|
# a verb method inside +r.is+, it returns a 405 status if none
|
|
22
25
|
# of the verb methods match. So this code:
|
|
@@ -100,6 +103,15 @@ class Roda
|
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
|
|
106
|
+
# Treat +r.root+ similar to <tt>r.get ''</tt>, using a 405
|
|
107
|
+
# response for non-GET requests.
|
|
108
|
+
def root
|
|
109
|
+
super
|
|
110
|
+
if @remaining_path == "/" && !is_get?
|
|
111
|
+
always{method_not_allowed("GET")}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
103
115
|
# Setup methods for all verbs. If inside an is block and not given
|
|
104
116
|
# arguments, record the verb used. If given an argument, add an is
|
|
105
117
|
# check with the arguments.
|
|
@@ -129,6 +141,7 @@ class Roda
|
|
|
129
141
|
res = response
|
|
130
142
|
res.status = 405
|
|
131
143
|
res['Allow'] = verbs
|
|
144
|
+
nil
|
|
132
145
|
end
|
|
133
146
|
end
|
|
134
147
|
end
|
data/lib/roda/plugins/public.rb
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
require 'uri'
|
|
4
4
|
|
|
5
|
+
begin
|
|
6
|
+
require 'rack/files'
|
|
7
|
+
rescue LoadError
|
|
8
|
+
# :nocov:
|
|
9
|
+
require 'rack/file'
|
|
10
|
+
# :nocov:
|
|
11
|
+
end
|
|
12
|
+
|
|
5
13
|
#
|
|
6
14
|
class Roda
|
|
7
15
|
module RodaPlugins
|
|
@@ -37,6 +45,9 @@ class Roda
|
|
|
37
45
|
module Public
|
|
38
46
|
SPLIT = Regexp.union(*[File::SEPARATOR, File::ALT_SEPARATOR].compact)
|
|
39
47
|
PARSER = URI::DEFAULT_PARSER
|
|
48
|
+
# :nocov:
|
|
49
|
+
RACK_FILES = defined?(Rack::Files) ? Rack::Files : Rack::File
|
|
50
|
+
# :nocov:
|
|
40
51
|
|
|
41
52
|
# Use options given to setup a Rack::File instance for serving files. Options:
|
|
42
53
|
# :default_mime :: The default mime type to use if the mime type is not recognized.
|
|
@@ -52,7 +63,7 @@ class Roda
|
|
|
52
63
|
elsif !app.opts[:public_root]
|
|
53
64
|
app.opts[:public_root] = app.expand_path("public")
|
|
54
65
|
end
|
|
55
|
-
app.opts[:public_server] =
|
|
66
|
+
app.opts[:public_server] = RACK_FILES.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
|
|
56
67
|
app.opts[:public_gzip] = opts[:gzip]
|
|
57
68
|
app.opts[:public_brotli] = opts[:brotli]
|
|
58
69
|
end
|
|
@@ -99,7 +110,10 @@ class Roda
|
|
|
99
110
|
public_serve_compressed(server, path, '.gz', 'gzip') if roda_opts[:public_gzip]
|
|
100
111
|
|
|
101
112
|
if public_file_readable?(path)
|
|
102
|
-
|
|
113
|
+
s, h, b = public_serve(server, path)
|
|
114
|
+
headers = response.headers
|
|
115
|
+
headers.replace(h)
|
|
116
|
+
halt [s, headers, b]
|
|
103
117
|
end
|
|
104
118
|
end
|
|
105
119
|
|
|
@@ -108,21 +122,22 @@ class Roda
|
|
|
108
122
|
compressed_path = path + suffix
|
|
109
123
|
|
|
110
124
|
if public_file_readable?(compressed_path)
|
|
111
|
-
|
|
112
|
-
headers =
|
|
125
|
+
s, h, b = public_serve(server, compressed_path)
|
|
126
|
+
headers = response.headers
|
|
127
|
+
headers.replace(h)
|
|
113
128
|
|
|
114
|
-
unless
|
|
129
|
+
unless s == 304
|
|
115
130
|
headers['Content-Type'] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
|
116
131
|
headers['Content-Encoding'] = encoding
|
|
117
132
|
end
|
|
118
133
|
|
|
119
|
-
halt
|
|
134
|
+
halt [s, headers, b]
|
|
120
135
|
end
|
|
121
136
|
end
|
|
122
137
|
end
|
|
123
138
|
|
|
124
139
|
if ::Rack.release > '2'
|
|
125
|
-
# Serve the given path using the given Rack::
|
|
140
|
+
# Serve the given path using the given Rack::Files server.
|
|
126
141
|
def public_serve(server, path)
|
|
127
142
|
server.serving(self, path)
|
|
128
143
|
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)
|
|
@@ -4,6 +4,7 @@ require 'base64'
|
|
|
4
4
|
require 'openssl'
|
|
5
5
|
require 'securerandom'
|
|
6
6
|
require 'uri'
|
|
7
|
+
require 'rack/utils'
|
|
7
8
|
|
|
8
9
|
class Roda
|
|
9
10
|
module RodaPlugins
|
|
@@ -191,7 +192,9 @@ class Roda
|
|
|
191
192
|
when :raise
|
|
192
193
|
raise InvalidToken, msg
|
|
193
194
|
when :empty_403
|
|
194
|
-
|
|
195
|
+
@_response.status = 403
|
|
196
|
+
@_response.headers.replace('Content-Type'=>'text/html', 'Content-Length'=>'0')
|
|
197
|
+
throw :halt, @_response.finish_with_body([])
|
|
195
198
|
when :clear_session
|
|
196
199
|
session.clear
|
|
197
200
|
when :csrf_failure_method
|
|
@@ -34,7 +34,7 @@ class Roda
|
|
|
34
34
|
# path internally, or a redirect is issued when configured with
|
|
35
35
|
# <tt>use_redirects: true</tt>.
|
|
36
36
|
def run(*)
|
|
37
|
-
if remaining_path.empty?
|
|
37
|
+
if @remaining_path.empty?
|
|
38
38
|
if scope.opts[:run_append_slash_redirect]
|
|
39
39
|
redirect("#{path}/")
|
|
40
40
|
else
|