roda 3.69.0 → 3.70.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 +6 -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/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/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 +5 -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,9 @@
|
|
|
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
|
+
|
|
1
7
|
= 3.69.0 (2023-06-13)
|
|
2
8
|
|
|
3
9
|
* Allow symbol_matcher in symbol_matchers plugin to take a block to allow type conversion (jeremyevans)
|
|
@@ -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)
|
|
@@ -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.
|
|
@@ -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
|
|
@@ -243,6 +243,7 @@ extra_rdoc_files:
|
|
|
243
243
|
- doc/release_notes/3.68.0.txt
|
|
244
244
|
- doc/release_notes/3.69.0.txt
|
|
245
245
|
- doc/release_notes/3.7.0.txt
|
|
246
|
+
- doc/release_notes/3.70.0.txt
|
|
246
247
|
- doc/release_notes/3.8.0.txt
|
|
247
248
|
- doc/release_notes/3.9.0.txt
|
|
248
249
|
files:
|
|
@@ -319,6 +320,7 @@ files:
|
|
|
319
320
|
- doc/release_notes/3.68.0.txt
|
|
320
321
|
- doc/release_notes/3.69.0.txt
|
|
321
322
|
- doc/release_notes/3.7.0.txt
|
|
323
|
+
- doc/release_notes/3.70.0.txt
|
|
322
324
|
- doc/release_notes/3.8.0.txt
|
|
323
325
|
- doc/release_notes/3.9.0.txt
|
|
324
326
|
- lib/roda.rb
|
|
@@ -412,6 +414,7 @@ files:
|
|
|
412
414
|
- lib/roda/plugins/path_matchers.rb
|
|
413
415
|
- lib/roda/plugins/path_rewriter.rb
|
|
414
416
|
- lib/roda/plugins/placeholder_string_matchers.rb
|
|
417
|
+
- lib/roda/plugins/plain_hash_response_headers.rb
|
|
415
418
|
- lib/roda/plugins/precompile_templates.rb
|
|
416
419
|
- lib/roda/plugins/public.rb
|
|
417
420
|
- lib/roda/plugins/r.rb
|