roda 3.68.0 → 3.70.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +10 -0
- data/doc/release_notes/3.69.0.txt +33 -0
- data/doc/release_notes/3.70.0.txt +19 -0
- data/lib/roda/plugins/assets.rb +3 -3
- data/lib/roda/plugins/caching.rb +6 -6
- data/lib/roda/plugins/chunked.rb +1 -1
- data/lib/roda/plugins/class_matchers.rb +1 -1
- data/lib/roda/plugins/common_logger.rb +2 -2
- data/lib/roda/plugins/content_security_policy.rb +2 -2
- data/lib/roda/plugins/drop_body.rb +2 -2
- data/lib/roda/plugins/exception_page.rb +5 -5
- data/lib/roda/plugins/header_matchers.rb +1 -1
- data/lib/roda/plugins/heartbeat.rb +1 -1
- data/lib/roda/plugins/json.rb +1 -1
- data/lib/roda/plugins/mailer.rb +2 -2
- data/lib/roda/plugins/not_allowed.rb +1 -1
- data/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/lib/roda/plugins/public.rb +2 -2
- data/lib/roda/plugins/route_csrf.rb +4 -1
- data/lib/roda/plugins/sinatra_helpers.rb +7 -7
- data/lib/roda/plugins/symbol_matchers.rb +36 -5
- data/lib/roda/plugins/type_routing.rb +2 -2
- data/lib/roda/response.rb +45 -23
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d8ea04ca767f8be110f6ba14d1fd877a389a2e733e2925020f2ac589f6a9571
|
4
|
+
data.tar.gz: c9a2d26f41724c05ea1de5e8fb0058093e5fad34fb0c6479dd74badcfea67016
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 288a793976f389dfa2fe8fd55fb649590748cbe7ebb9da48f351117bb93da33ceee5848077f53c73430fd943b6be5b69a6d189e8b7424fe9ec60892dbaacff56
|
7
|
+
data.tar.gz: 3cad6d6823a2fbcd5534521b6e5fbcc7b85c04d1a80e9ca758cac6ab9b4b967a5f019b653de7d4de3c1d32069832f362fd2564ec219252b0f2dd8ccf1c0785c1
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 3.70.0 (2023-07-12)
|
2
|
+
|
3
|
+
* Add plain_hash_response_headers plugin, using a plain hash for response headers on Rack 3 for much better performance (jeremyevans)
|
4
|
+
|
5
|
+
* Use lower case response header keys by default on Rack 3, instead of relying on Rack::Headers conversion (jeremyevans)
|
6
|
+
|
7
|
+
= 3.69.0 (2023-06-13)
|
8
|
+
|
9
|
+
* Allow symbol_matcher in symbol_matchers plugin to take a block to allow type conversion (jeremyevans)
|
10
|
+
|
1
11
|
= 3.68.0 (2023-05-11)
|
2
12
|
|
3
13
|
* Make Roda.run in multi_run plugin accept blocks to allow autoloading the apps to dispatch to (jeremyevans)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
= New Feature
|
2
|
+
|
3
|
+
* The symbol_matcher method in the symbol_matchers plugin now
|
4
|
+
supports a block to allow for type conversion of matched
|
5
|
+
segments:
|
6
|
+
|
7
|
+
symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
8
|
+
[Date.new(y.to_i, m.to_i, d.to_i)]
|
9
|
+
end
|
10
|
+
|
11
|
+
route do |r|
|
12
|
+
r.on :date do |date|
|
13
|
+
# date is an instance of Date
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
As shown above, the block should return an array of objects to yield
|
18
|
+
to the match block.
|
19
|
+
|
20
|
+
If you have a segment match the passed regexp, but decide during block
|
21
|
+
processing that you do not want to treat it as a match, you can have the
|
22
|
+
block return nil or false. This is useful if you want to make sure you
|
23
|
+
are using valid data:
|
24
|
+
|
25
|
+
symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
26
|
+
y = y.to_i
|
27
|
+
m = m.to_i
|
28
|
+
d = d.to_i
|
29
|
+
[Date.new(y, m, d)] if Date.valid_date?(y, m, d)
|
30
|
+
end
|
31
|
+
|
32
|
+
When providing a block when using the symbol_matchers method, that
|
33
|
+
symbol may not work with the params_capturing plugin.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A plain_hash_response_headers plugin has been added. On Rack 3,
|
4
|
+
this changes Roda to use a plain hash for response headers (as it
|
5
|
+
does on Rack 2), instead of using Rack::Headers (the default on
|
6
|
+
Rack 3). For a minimal app, using this plugin can almost double
|
7
|
+
the performance on Rack 3. Before using this plugin, you should
|
8
|
+
make sure that all response headers set explictly in your
|
9
|
+
application are already lower-case.
|
10
|
+
|
11
|
+
= Improvements
|
12
|
+
|
13
|
+
* Roda now natively uses lower-case for all response headers set
|
14
|
+
implicitly when using Rack 3. Previously, Roda used mixed-case
|
15
|
+
response headers and had Rack::Headers handle the conversion to
|
16
|
+
lower-case (Rack 3 requires lower-case response headers). Note
|
17
|
+
that Rack::Headers is still used for response headers by default
|
18
|
+
on Rack 3, as applications may not have converted to using
|
19
|
+
lower-case response headers.
|
data/lib/roda/plugins/assets.rb
CHANGED
@@ -430,8 +430,8 @@ class Roda
|
|
430
430
|
opts[:css_headers] = headers.merge(opts[:css_headers])
|
431
431
|
opts[:js_headers] = headers.merge(opts[:js_headers])
|
432
432
|
end
|
433
|
-
opts[:css_headers][
|
434
|
-
opts[:js_headers][
|
433
|
+
opts[:css_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "text/css; charset=UTF-8".freeze
|
434
|
+
opts[:js_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "application/javascript; charset=UTF-8".freeze
|
435
435
|
|
436
436
|
[:css_headers, :js_headers, :css_opts, :js_opts, :dependencies, :expanded_dependencies].each do |s|
|
437
437
|
opts[s].freeze
|
@@ -754,7 +754,7 @@ class Roda
|
|
754
754
|
file = "#{o[:"compiled_#{type}_path"]}#{file}"
|
755
755
|
|
756
756
|
if o[:gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
|
757
|
-
@_response[
|
757
|
+
@_response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
|
758
758
|
file += '.gz'
|
759
759
|
end
|
760
760
|
|
data/lib/roda/plugins/caching.rb
CHANGED
@@ -86,7 +86,7 @@ class Roda
|
|
86
86
|
return unless time
|
87
87
|
res = response
|
88
88
|
e = env
|
89
|
-
res[
|
89
|
+
res[RodaResponseHeaders::LAST_MODIFIED] = time.httpdate
|
90
90
|
return if e['HTTP_IF_NONE_MATCH']
|
91
91
|
status = res.status
|
92
92
|
|
@@ -122,7 +122,7 @@ class Roda
|
|
122
122
|
|
123
123
|
res = response
|
124
124
|
e = env
|
125
|
-
res[
|
125
|
+
res[RodaResponseHeaders::ETAG] = etag = "#{'W/' if weak}\"#{value}\""
|
126
126
|
status = res.status
|
127
127
|
|
128
128
|
if (!status || (status >= 200 && status < 300) || status == 304)
|
@@ -176,7 +176,7 @@ class Roda
|
|
176
176
|
values << (v == true ? k : "#{k}=#{v}")
|
177
177
|
end
|
178
178
|
|
179
|
-
|
179
|
+
@headers[RodaResponseHeaders::CACHE_CONTROL] = values.join(', ') unless values.empty?
|
180
180
|
end
|
181
181
|
|
182
182
|
# Set Cache-Control header with the max_age given. max_age should
|
@@ -185,7 +185,7 @@ class Roda
|
|
185
185
|
# HTTP 1.0 clients (Cache-Control is an HTTP 1.1 header).
|
186
186
|
def expires(max_age, opts=OPTS)
|
187
187
|
cache_control(Hash[opts].merge!(:max_age=>max_age))
|
188
|
-
|
188
|
+
@headers[RodaResponseHeaders::EXPIRES] = (Time.now + max_age).httpdate
|
189
189
|
end
|
190
190
|
|
191
191
|
# Remove Content-Type and Content-Length for 304 responses.
|
@@ -193,8 +193,8 @@ class Roda
|
|
193
193
|
a = super
|
194
194
|
if a[0] == 304
|
195
195
|
h = a[1]
|
196
|
-
h.delete(
|
197
|
-
h.delete(
|
196
|
+
h.delete(RodaResponseHeaders::CONTENT_TYPE)
|
197
|
+
h.delete(RodaResponseHeaders::CONTENT_LENGTH)
|
198
198
|
end
|
199
199
|
a
|
200
200
|
end
|
data/lib/roda/plugins/chunked.rb
CHANGED
@@ -257,7 +257,7 @@ class Roda
|
|
257
257
|
headers.merge!(chunk_headers)
|
258
258
|
end
|
259
259
|
if self.opts[:force_chunked_encoding]
|
260
|
-
|
260
|
+
res[RodaResponseHeaders::TRANSFER_ENCODING] = 'chunked'
|
261
261
|
body = Body.new(self)
|
262
262
|
else
|
263
263
|
body = StreamBody.new(self)
|
@@ -33,7 +33,7 @@ class Roda
|
|
33
33
|
# block return nil or false. This is useful if you want to make sure you
|
34
34
|
# are using valid data:
|
35
35
|
#
|
36
|
-
# class_matcher(Date, /(\
|
36
|
+
# class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
37
37
|
# y = y.to_i
|
38
38
|
# m = m.to_i
|
39
39
|
# d = d.to_i
|
@@ -46,7 +46,7 @@ class Roda
|
|
46
46
|
|
47
47
|
# Log request/response information in common log format to logger.
|
48
48
|
def _roda_after_90__common_logger(result)
|
49
|
-
return unless result && result[0] && result[1]
|
49
|
+
return unless result && (status = result[0]) && (headers = result[1])
|
50
50
|
|
51
51
|
elapsed_time = if timer = @_request_timer
|
52
52
|
'%0.4f' % (CommonLogger.start_timer - timer)
|
@@ -56,7 +56,7 @@ class Roda
|
|
56
56
|
|
57
57
|
env = @_request.env
|
58
58
|
|
59
|
-
line = "#{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}\" #{
|
59
|
+
line = "#{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}\" #{status} #{((length = headers[RodaResponseHeaders::CONTENT_LENGTH]) && (length unless length == '0')) || '-'} #{elapsed_time}\n"
|
60
60
|
if MUTATE_LINE
|
61
61
|
line.gsub!(/[^[:print:]\n]/){|c| sprintf("\\x%x", c.ord)}
|
62
62
|
# :nocov:
|
@@ -200,7 +200,7 @@ class Roda
|
|
200
200
|
|
201
201
|
# The header name to use, depends on whether report only mode has been enabled.
|
202
202
|
def header_key
|
203
|
-
@report_only ?
|
203
|
+
@report_only ? RodaResponseHeaders::CONTENT_SECURITY_POLICY_REPORT_ONLY : RodaResponseHeaders::CONTENT_SECURITY_POLICY
|
204
204
|
end
|
205
205
|
|
206
206
|
# The header value to use.
|
@@ -309,7 +309,7 @@ class Roda
|
|
309
309
|
# Set the appropriate content security policy header.
|
310
310
|
def set_default_headers
|
311
311
|
super
|
312
|
-
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(
|
312
|
+
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
|
313
313
|
end
|
314
314
|
end
|
315
315
|
end
|
@@ -27,8 +27,8 @@ class Roda
|
|
27
27
|
when DROP_BODY_RANGE, 204, 304
|
28
28
|
r[2] = EMPTY_ARRAY
|
29
29
|
h = r[1]
|
30
|
-
h.delete(
|
31
|
-
h.delete(
|
30
|
+
h.delete(RodaResponseHeaders::CONTENT_LENGTH)
|
31
|
+
h.delete(RodaResponseHeaders::CONTENT_TYPE)
|
32
32
|
when 205
|
33
33
|
r[2] = EMPTY_ARRAY
|
34
34
|
empty_205_headers(r[1])
|
@@ -198,7 +198,7 @@ END
|
|
198
198
|
def exception_page(exception, opts=OPTS)
|
199
199
|
message = exception_page_exception_message(exception)
|
200
200
|
if opts[:json]
|
201
|
-
@_response[
|
201
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "application/json"
|
202
202
|
{
|
203
203
|
"exception"=>{
|
204
204
|
"class"=>exception.class.to_s,
|
@@ -207,7 +207,7 @@ END
|
|
207
207
|
}
|
208
208
|
}
|
209
209
|
elsif env['HTTP_ACCEPT'] =~ /text\/html/
|
210
|
-
@_response[
|
210
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "text/html"
|
211
211
|
|
212
212
|
context = opts[:context] || 7
|
213
213
|
css_file = opts[:css_file]
|
@@ -394,7 +394,7 @@ END1
|
|
394
394
|
</html>
|
395
395
|
END
|
396
396
|
else
|
397
|
-
@_response[
|
397
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] = "text/plain"
|
398
398
|
"#{exception.class}: #{message}\n#{exception.backtrace.map{|l| "\t#{l}"}.join("\n")}"
|
399
399
|
end
|
400
400
|
end
|
@@ -429,11 +429,11 @@ END
|
|
429
429
|
# Serve exception page assets
|
430
430
|
def exception_page_assets
|
431
431
|
get 'exception_page.css' do
|
432
|
-
response[
|
432
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = "text/css"
|
433
433
|
scope.exception_page_css
|
434
434
|
end
|
435
435
|
get 'exception_page.js' do
|
436
|
-
response[
|
436
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = "application/javascript"
|
437
437
|
scope.exception_page_js
|
438
438
|
end
|
439
439
|
end
|
@@ -45,7 +45,7 @@ class Roda
|
|
45
45
|
# Match if the given mimetype is one of the accepted mimetypes.
|
46
46
|
def match_accept(mimetype)
|
47
47
|
if @env["HTTP_ACCEPT"].to_s.split(',').any?{|s| s.strip == mimetype}
|
48
|
-
response[
|
48
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = mimetype
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -27,7 +27,7 @@ class Roda
|
|
27
27
|
if env['PATH_INFO'] == opts[:heartbeat_path]
|
28
28
|
response = @_response
|
29
29
|
response.status = 200
|
30
|
-
response[
|
30
|
+
response[RodaResponseHeaders::CONTENT_TYPE] = 'text/plain'
|
31
31
|
response.write 'OK'
|
32
32
|
throw :halt, response.finish
|
33
33
|
end
|
data/lib/roda/plugins/json.rb
CHANGED
@@ -86,7 +86,7 @@ class Roda
|
|
86
86
|
# Handle a result for one of the registered JSON result classes
|
87
87
|
# by converting the result to JSON.
|
88
88
|
def handle_json_block_result(result)
|
89
|
-
@_response[
|
89
|
+
@_response[RodaResponseHeaders::CONTENT_TYPE] ||= opts[:json_result_content_type]
|
90
90
|
@_request.send(:convert_to_json, result)
|
91
91
|
end
|
92
92
|
end
|
data/lib/roda/plugins/mailer.rb
CHANGED
@@ -186,7 +186,7 @@ class Roda
|
|
186
186
|
# that the routing tree did not handle the request.
|
187
187
|
def finish
|
188
188
|
if m = mail
|
189
|
-
header_content_type = @headers.delete(
|
189
|
+
header_content_type = @headers.delete(RodaResponseHeaders::CONTENT_TYPE)
|
190
190
|
m.headers(@headers)
|
191
191
|
m.body(@body.join) unless @body.empty?
|
192
192
|
mail_attachments.each do |a, block|
|
@@ -241,7 +241,7 @@ class Roda
|
|
241
241
|
if mail = env['roda.mail']
|
242
242
|
res = @_response
|
243
243
|
res.mail = mail
|
244
|
-
res.headers.delete(
|
244
|
+
res.headers.delete(RodaResponseHeaders::CONTENT_TYPE)
|
245
245
|
end
|
246
246
|
end
|
247
247
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The response_headers_plain_hash plugin will change Roda to
|
7
|
+
# use a plain hash for response headers. This is Roda's
|
8
|
+
# default behavior on Rack 2, but on Rack 3+, Roda defaults
|
9
|
+
# to using Rack::Headers for response headers for backwards
|
10
|
+
# compatibility (Rack::Headers automatically lower cases header
|
11
|
+
# keys).
|
12
|
+
#
|
13
|
+
# On Rack 3+, you should use this plugin for better performance
|
14
|
+
# if you are sure all headers in your application and middleware
|
15
|
+
# are already lower case (lower case response header keys are
|
16
|
+
# required by the Rack 3 spec).
|
17
|
+
module PlainHashResponseHeaders
|
18
|
+
if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
|
19
|
+
module ResponseMethods
|
20
|
+
private
|
21
|
+
|
22
|
+
# Use plain hash for headers
|
23
|
+
def _initialize_headers
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
register_plugin(:plain_hash_response_headers, PlainHashResponseHeaders)
|
31
|
+
end
|
32
|
+
end
|
data/lib/roda/plugins/public.rb
CHANGED
@@ -123,8 +123,8 @@ class Roda
|
|
123
123
|
headers.replace(h)
|
124
124
|
|
125
125
|
unless s == 304
|
126
|
-
headers[
|
127
|
-
headers[
|
126
|
+
headers[RodaResponseHeaders::CONTENT_TYPE] = ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
127
|
+
headers[RodaResponseHeaders::CONTENT_ENCODING] = encoding
|
128
128
|
end
|
129
129
|
|
130
130
|
halt [s, headers, b]
|
@@ -193,7 +193,10 @@ class Roda
|
|
193
193
|
raise InvalidToken, msg
|
194
194
|
when :empty_403
|
195
195
|
@_response.status = 403
|
196
|
-
@_response.headers
|
196
|
+
headers = @_response.headers
|
197
|
+
headers.clear
|
198
|
+
headers[RodaResponseHeaders::CONTENT_TYPE] = 'text/html'
|
199
|
+
headers[RodaResponseHeaders::CONTENT_LENGTH] ='0'
|
197
200
|
throw :halt, @_response.finish_with_body([])
|
198
201
|
when :clear_session
|
199
202
|
session.clear
|
@@ -327,7 +327,7 @@ class Roda
|
|
327
327
|
def send_file(path, opts = OPTS)
|
328
328
|
res = response
|
329
329
|
headers = res.headers
|
330
|
-
if opts[:type] || !headers[
|
330
|
+
if opts[:type] || !headers[RodaResponseHeaders::CONTENT_TYPE]
|
331
331
|
res.content_type(opts[:type] || ::File.extname(path), :default => 'application/octet-stream')
|
332
332
|
end
|
333
333
|
|
@@ -352,7 +352,7 @@ class Roda
|
|
352
352
|
end
|
353
353
|
|
354
354
|
res.status = opts[:status] || s
|
355
|
-
headers.delete(
|
355
|
+
headers.delete(RodaResponseHeaders::CONTENT_LENGTH)
|
356
356
|
headers.replace(h.merge!(headers))
|
357
357
|
res.body = b
|
358
358
|
|
@@ -407,7 +407,7 @@ class Roda
|
|
407
407
|
|
408
408
|
# If the body is a DelayedBody, set the appropriate length for it.
|
409
409
|
def finish
|
410
|
-
@length = @body.length if @body.is_a?(DelayedBody) && !@headers[
|
410
|
+
@length = @body.length if @body.is_a?(DelayedBody) && !@headers[RodaResponseHeaders::CONTENT_LENGTH]
|
411
411
|
super
|
412
412
|
end
|
413
413
|
|
@@ -424,7 +424,7 @@ class Roda
|
|
424
424
|
|
425
425
|
# Set the Content-Type of the response body given a media type or file
|
426
426
|
# extension. See plugin documentation for options.
|
427
|
-
def content_type(type = nil || (return @headers[
|
427
|
+
def content_type(type = nil || (return @headers[RodaResponseHeaders::CONTENT_TYPE]), opts = OPTS)
|
428
428
|
unless (mime_type = mime_type(type) || opts[:default])
|
429
429
|
raise RodaError, "Unknown media type: #{type}"
|
430
430
|
end
|
@@ -437,7 +437,7 @@ class Roda
|
|
437
437
|
end
|
438
438
|
end
|
439
439
|
|
440
|
-
@headers[
|
440
|
+
@headers[RodaResponseHeaders::CONTENT_TYPE] = mime_type
|
441
441
|
end
|
442
442
|
|
443
443
|
# Set the Content-Disposition to "attachment" with the specified filename,
|
@@ -463,14 +463,14 @@ class Roda
|
|
463
463
|
encoded_params = "; filename*=#{encoding.to_s}''#{encoded_filename}"
|
464
464
|
end
|
465
465
|
|
466
|
-
unless @headers[
|
466
|
+
unless @headers[RodaResponseHeaders::CONTENT_TYPE]
|
467
467
|
ext = File.extname(filename)
|
468
468
|
unless ext.empty?
|
469
469
|
content_type(ext)
|
470
470
|
end
|
471
471
|
end
|
472
472
|
end
|
473
|
-
@headers[
|
473
|
+
@headers[RodaResponseHeaders::CONTENT_DISPOSITION] = "#{disposition}#{params}#{encoded_params}"
|
474
474
|
end
|
475
475
|
|
476
476
|
# Whether or not the status is set to 1xx. Returns nil if status not yet set.
|
@@ -37,6 +37,35 @@ class Roda
|
|
37
37
|
#
|
38
38
|
# If using this plugin with the params_capturing plugin, this plugin should
|
39
39
|
# be loaded first.
|
40
|
+
#
|
41
|
+
# You can provide a block when calling +symbol_matcher+, and it will be called
|
42
|
+
# for all matches to allow for type conversion. The block must return an
|
43
|
+
# array:
|
44
|
+
#
|
45
|
+
# symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
46
|
+
# [Date.new(y.to_i, m.to_i, d.to_i)]
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# route do |r|
|
50
|
+
# r.on :date do |date|
|
51
|
+
# # date is an instance of Date
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# If you have a segment match the passed regexp, but decide during block
|
56
|
+
# processing that you do not want to treat it as a match, you can have the
|
57
|
+
# block return nil or false. This is useful if you want to make sure you
|
58
|
+
# are using valid data:
|
59
|
+
#
|
60
|
+
# symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
61
|
+
# y = y.to_i
|
62
|
+
# m = m.to_i
|
63
|
+
# d = d.to_i
|
64
|
+
# [Date.new(y, m, d)] if Date.valid_date?(y, m, d)
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# However, if providing a block to the symbol_matchers plugin, the symbol may
|
68
|
+
# not work with the params_capturing plugin.
|
40
69
|
module SymbolMatchers
|
41
70
|
def self.load_dependencies(app)
|
42
71
|
app.plugin :_symbol_regexp_matchers
|
@@ -50,9 +79,10 @@ class Roda
|
|
50
79
|
|
51
80
|
module ClassMethods
|
52
81
|
# Set the regexp to use for the given symbol, instead of the default.
|
53
|
-
def symbol_matcher(s, re)
|
82
|
+
def symbol_matcher(s, re, &block)
|
54
83
|
meth = :"match_symbol_#{s}"
|
55
|
-
|
84
|
+
array = [re, block].freeze
|
85
|
+
self::RodaRequest.send(:define_method, meth){array}
|
56
86
|
self::RodaRequest.send(:private, meth)
|
57
87
|
end
|
58
88
|
end
|
@@ -67,8 +97,8 @@ class Roda
|
|
67
97
|
meth = :"match_symbol_#{s}"
|
68
98
|
if respond_to?(meth, true)
|
69
99
|
# Allow calling private match methods
|
70
|
-
re = send(meth)
|
71
|
-
consume(self.class.cached_matcher(re){re})
|
100
|
+
re, block = send(meth)
|
101
|
+
consume(self.class.cached_matcher(re){re}, &block)
|
72
102
|
else
|
73
103
|
super
|
74
104
|
end
|
@@ -80,7 +110,8 @@ class Roda
|
|
80
110
|
meth = :"match_symbol_#{s}"
|
81
111
|
if respond_to?(meth, true)
|
82
112
|
# Allow calling private match methods
|
83
|
-
send(meth)
|
113
|
+
re, = send(meth)
|
114
|
+
re
|
84
115
|
else
|
85
116
|
super
|
86
117
|
end
|
@@ -152,7 +152,7 @@ class Roda
|
|
152
152
|
# the request afterwards, returning the result of the block.
|
153
153
|
def on_type(type, &block)
|
154
154
|
return unless type == requested_type
|
155
|
-
response[
|
155
|
+
response[RodaResponseHeaders::CONTENT_TYPE] ||= @scope.opts[:type_routing][:types][type]
|
156
156
|
always(&block)
|
157
157
|
end
|
158
158
|
|
@@ -200,7 +200,7 @@ class Roda
|
|
200
200
|
@env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do |part|
|
201
201
|
mime, _= part.split(/\s*;\s*/, 2)
|
202
202
|
if sym = mimes[mime]
|
203
|
-
response[
|
203
|
+
response[RodaResponseHeaders::VARY] = (vary = response[RodaResponseHeaders::VARY]) ? "#{vary}, Accept" : 'Accept'
|
204
204
|
return sym
|
205
205
|
end
|
206
206
|
end
|
data/lib/roda/response.rb
CHANGED
@@ -6,6 +6,25 @@ rescue LoadError
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class Roda
|
9
|
+
# Contains constants for response headers. This approach is used so that all
|
10
|
+
# headers used internally by Roda can be lower case on Rack 3, so that it is
|
11
|
+
# possible to use a plain hash of response headers instead of using Rack::Headers.
|
12
|
+
module RodaResponseHeaders
|
13
|
+
headers = %w'Allow Cache-Control Content-Disposition Content-Encoding Content-Length
|
14
|
+
Content-Security-Policy Content-Security-Policy-Report-Only Content-Type
|
15
|
+
ETag Expires Last-Modified Link Location Set-Cookie Transfer-Encoding Vary'.freeze.each(&:freeze)
|
16
|
+
|
17
|
+
if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
|
18
|
+
headers.each do |mixed_case|
|
19
|
+
const_set(mixed_case.gsub('-', '_').upcase!.to_sym, mixed_case.downcase.freeze)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
headers.each do |mixed_case|
|
23
|
+
const_set(mixed_case.gsub('-', '_').upcase!.to_sym, mixed_case.freeze)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
9
28
|
# Base class used for Roda responses. The instance methods for this
|
10
29
|
# class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
|
11
30
|
# methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
|
@@ -30,7 +49,7 @@ class Roda
|
|
30
49
|
|
31
50
|
# Instance methods for RodaResponse
|
32
51
|
module ResponseMethods
|
33
|
-
DEFAULT_HEADERS = {
|
52
|
+
DEFAULT_HEADERS = {RodaResponseHeaders::CONTENT_TYPE => "text/html".freeze}.freeze
|
34
53
|
|
35
54
|
# The body for the current response.
|
36
55
|
attr_reader :body
|
@@ -42,20 +61,11 @@ class Roda
|
|
42
61
|
# code for non-empty responses and a 404 code for empty responses.
|
43
62
|
attr_accessor :status
|
44
63
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@length = 0
|
51
|
-
end
|
52
|
-
else
|
53
|
-
# Set the default headers when creating a response.
|
54
|
-
def initialize
|
55
|
-
@headers = {}
|
56
|
-
@body = []
|
57
|
-
@length = 0
|
58
|
-
end
|
64
|
+
# Set the default headers when creating a response.
|
65
|
+
def initialize
|
66
|
+
@headers = _initialize_headers
|
67
|
+
@body = []
|
68
|
+
@length = 0
|
59
69
|
end
|
60
70
|
|
61
71
|
# Return the response header with the given key. Example:
|
@@ -108,15 +118,15 @@ class Roda
|
|
108
118
|
if b.empty?
|
109
119
|
s = @status || 404
|
110
120
|
if (s == 304 || s == 204 || (s >= 100 && s <= 199))
|
111
|
-
h.delete(
|
121
|
+
h.delete(RodaResponseHeaders::CONTENT_TYPE)
|
112
122
|
elsif s == 205
|
113
123
|
empty_205_headers(h)
|
114
124
|
else
|
115
|
-
h[
|
125
|
+
h[RodaResponseHeaders::CONTENT_LENGTH] ||= '0'
|
116
126
|
end
|
117
127
|
else
|
118
128
|
s = @status || default_status
|
119
|
-
h[
|
129
|
+
h[RodaResponseHeaders::CONTENT_LENGTH] ||= @length.to_s
|
120
130
|
end
|
121
131
|
|
122
132
|
[s, h, b]
|
@@ -149,7 +159,7 @@ class Roda
|
|
149
159
|
# response.redirect('foo', 301)
|
150
160
|
# response.redirect('bar')
|
151
161
|
def redirect(path, status = 302)
|
152
|
-
@headers[
|
162
|
+
@headers[RodaResponseHeaders::LOCATION] = path
|
153
163
|
@status = status
|
154
164
|
nil
|
155
165
|
end
|
@@ -171,18 +181,30 @@ class Roda
|
|
171
181
|
|
172
182
|
private
|
173
183
|
|
184
|
+
if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
|
185
|
+
# Use Rack::Headers for headers by default on Rack 3
|
186
|
+
def _initialize_headers
|
187
|
+
Rack::Headers.new
|
188
|
+
end
|
189
|
+
else
|
190
|
+
# Use plain hash for headers by default on Rack 1-2
|
191
|
+
def _initialize_headers
|
192
|
+
{}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
174
196
|
if Rack.release < '2.0.2'
|
175
197
|
# Don't use a content length for empty 205 responses on
|
176
198
|
# rack 1, as it violates Rack::Lint in that version.
|
177
199
|
def empty_205_headers(headers)
|
178
|
-
headers.delete(
|
179
|
-
headers.delete(
|
200
|
+
headers.delete(RodaResponseHeaders::CONTENT_TYPE)
|
201
|
+
headers.delete(RodaResponseHeaders::CONTENT_LENGTH)
|
180
202
|
end
|
181
203
|
else
|
182
204
|
# Set the content length for empty 205 responses to 0
|
183
205
|
def empty_205_headers(headers)
|
184
|
-
headers.delete(
|
185
|
-
headers[
|
206
|
+
headers.delete(RodaResponseHeaders::CONTENT_TYPE)
|
207
|
+
headers[RodaResponseHeaders::CONTENT_LENGTH] = '0'
|
186
208
|
end
|
187
209
|
end
|
188
210
|
|
data/lib/roda/version.rb
CHANGED
data/lib/roda.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.70.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -241,7 +241,9 @@ extra_rdoc_files:
|
|
241
241
|
- doc/release_notes/3.66.0.txt
|
242
242
|
- doc/release_notes/3.67.0.txt
|
243
243
|
- doc/release_notes/3.68.0.txt
|
244
|
+
- doc/release_notes/3.69.0.txt
|
244
245
|
- doc/release_notes/3.7.0.txt
|
246
|
+
- doc/release_notes/3.70.0.txt
|
245
247
|
- doc/release_notes/3.8.0.txt
|
246
248
|
- doc/release_notes/3.9.0.txt
|
247
249
|
files:
|
@@ -316,7 +318,9 @@ files:
|
|
316
318
|
- doc/release_notes/3.66.0.txt
|
317
319
|
- doc/release_notes/3.67.0.txt
|
318
320
|
- doc/release_notes/3.68.0.txt
|
321
|
+
- doc/release_notes/3.69.0.txt
|
319
322
|
- doc/release_notes/3.7.0.txt
|
323
|
+
- doc/release_notes/3.70.0.txt
|
320
324
|
- doc/release_notes/3.8.0.txt
|
321
325
|
- doc/release_notes/3.9.0.txt
|
322
326
|
- lib/roda.rb
|
@@ -410,6 +414,7 @@ files:
|
|
410
414
|
- lib/roda/plugins/path_matchers.rb
|
411
415
|
- lib/roda/plugins/path_rewriter.rb
|
412
416
|
- lib/roda/plugins/placeholder_string_matchers.rb
|
417
|
+
- lib/roda/plugins/plain_hash_response_headers.rb
|
413
418
|
- lib/roda/plugins/precompile_templates.rb
|
414
419
|
- lib/roda/plugins/public.rb
|
415
420
|
- lib/roda/plugins/r.rb
|