roda 3.109.0 → 3.110.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/lib/roda/cache.rb +0 -2
- data/lib/roda/opts.rb +1 -1
- data/lib/roda/plugins/_symbol_class_matchers.rb +2 -4
- data/lib/roda/plugins/additional_view_directories.rb +2 -2
- data/lib/roda/plugins/assets.rb +173 -142
- data/lib/roda/plugins/assets_preloading.rb +26 -10
- data/lib/roda/plugins/backtracking_array.rb +2 -2
- data/lib/roda/plugins/branch_locals.rb +4 -4
- data/lib/roda/plugins/break.rb +2 -2
- data/lib/roda/plugins/caching.rb +27 -17
- data/lib/roda/plugins/capture_erb.rb +15 -12
- data/lib/roda/plugins/chunked.rb +32 -30
- data/lib/roda/plugins/class_level_routing.rb +5 -2
- data/lib/roda/plugins/class_matchers.rb +11 -6
- data/lib/roda/plugins/common_logger.rb +21 -3
- data/lib/roda/plugins/conditional_sessions.rb +3 -3
- data/lib/roda/plugins/content_for.rb +7 -6
- data/lib/roda/plugins/content_security_policy.rb +11 -12
- data/lib/roda/plugins/cookie_flags.rb +12 -6
- data/lib/roda/plugins/cookies.rb +7 -5
- data/lib/roda/plugins/csrf.rb +2 -1
- data/lib/roda/plugins/custom_block_results.rb +6 -7
- data/lib/roda/plugins/default_headers.rb +2 -2
- data/lib/roda/plugins/delay_build.rb +1 -1
- data/lib/roda/plugins/delegate.rb +7 -6
- data/lib/roda/plugins/direct_call.rb +5 -1
- data/lib/roda/plugins/disallow_file_uploads.rb +2 -3
- data/lib/roda/plugins/drop_body.rb +3 -6
- data/lib/roda/plugins/each_part.rb +4 -6
- data/lib/roda/plugins/early_hints.rb +4 -4
- data/lib/roda/plugins/empty_root.rb +5 -5
- data/lib/roda/plugins/environments.rb +8 -8
- data/lib/roda/plugins/erb_h.rb +4 -4
- data/lib/roda/plugins/error_email.rb +16 -17
- data/lib/roda/plugins/error_handler.rb +24 -32
- data/lib/roda/plugins/error_mail.rb +26 -15
- data/lib/roda/plugins/exception_page.rb +41 -35
- data/lib/roda/plugins/filter_common_logger.rb +6 -4
- data/lib/roda/plugins/flash.rb +16 -13
- data/lib/roda/plugins/freeze_request_response.rb +24 -0
- data/lib/roda/plugins/hash_routes.rb +1 -1
- data/lib/roda/plugins/permissions_policy.rb +14 -16
- data/lib/roda/plugins/public.rb +11 -0
- data/lib/roda/plugins/recheck_precompiled_assets.rb +30 -8
- data/lib/roda/plugins/render.rb +3 -0
- data/lib/roda/plugins/render_locals.rb +1 -1
- data/lib/roda/plugins/sessions.rb +158 -137
- data/lib/roda/plugins/symbol_matchers.rb +8 -5
- data/lib/roda/plugins/unescape_path.rb +22 -4
- data/lib/roda/plugins.rb +24 -0
- data/lib/roda/request.rb +8 -7
- data/lib/roda/response.rb +9 -7
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +12 -9
- metadata +2 -1
|
@@ -11,11 +11,11 @@ class Roda
|
|
|
11
11
|
# For a list of compatible browsers, see
|
|
12
12
|
# http://caniuse.com/#search=link-rel-preload
|
|
13
13
|
#
|
|
14
|
-
# The plugin provides two functions
|
|
15
|
-
# preload_assets_link_tags
|
|
16
|
-
# identical, it is up to you which
|
|
14
|
+
# The plugin provides two functions: +preload_assets_link_header+ and
|
|
15
|
+
# +preload_assets_link_tags+. The resulting preloading should be
|
|
16
|
+
# identical, it is up to you which approach you prefer.
|
|
17
17
|
#
|
|
18
|
-
# preload_assets_link_header returns a string suitable for populating
|
|
18
|
+
# +preload_assets_link_header+ returns a string suitable for populating
|
|
19
19
|
# the response Link header:
|
|
20
20
|
#
|
|
21
21
|
# response.headers['Link'] = preload_assets_link_header(:css)
|
|
@@ -43,7 +43,6 @@ class Roda
|
|
|
43
43
|
#
|
|
44
44
|
# # generate Link header for css assets and js asset groups app and bar
|
|
45
45
|
# preload_assets_link_header(:css, [:js, :app])
|
|
46
|
-
#
|
|
47
46
|
module AssetsPreloading
|
|
48
47
|
TYPE_AS = {
|
|
49
48
|
:css => 'style'.freeze,
|
|
@@ -59,27 +58,44 @@ class Roda
|
|
|
59
58
|
# Return a string of <link> tags for the given asset
|
|
60
59
|
# types/groups.
|
|
61
60
|
def preload_assets_link_tags(*args)
|
|
62
|
-
|
|
61
|
+
_each_preload_asset(args, "\n") do |path, as|
|
|
62
|
+
"<link href=\"#{h(path)}\" rel=\"preload\" as=\"#{as}\">"
|
|
63
|
+
end
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
# Return a string suitable for a Link header for the
|
|
66
67
|
# given asset types/groups.
|
|
67
68
|
def preload_assets_link_header(*args)
|
|
68
|
-
|
|
69
|
+
_each_preload_asset(args, ",") do |path, as|
|
|
70
|
+
"<#{path}>;rel=preload;as=#{as}"
|
|
71
|
+
end
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
private
|
|
72
75
|
|
|
73
76
|
# Return an array of paths/as pairs for the given asset
|
|
74
77
|
# types and/or groups.
|
|
75
|
-
def
|
|
76
|
-
|
|
78
|
+
def _each_preload_asset(assets, sep)
|
|
79
|
+
output = String.new
|
|
80
|
+
append_sep = false
|
|
81
|
+
|
|
82
|
+
assets.each do |type|
|
|
83
|
+
if append_sep
|
|
84
|
+
output << sep
|
|
85
|
+
else
|
|
86
|
+
append_sep = sep
|
|
87
|
+
end
|
|
88
|
+
|
|
77
89
|
paths = assets_paths(type)
|
|
78
90
|
type = type[0] if type.is_a?(Array)
|
|
79
91
|
as = TYPE_AS[type]
|
|
80
92
|
|
|
81
|
-
paths.
|
|
93
|
+
paths.each do |path|
|
|
94
|
+
output << yield(path, as)
|
|
95
|
+
end
|
|
82
96
|
end
|
|
97
|
+
|
|
98
|
+
output
|
|
83
99
|
end
|
|
84
100
|
end
|
|
85
101
|
end
|
|
@@ -16,8 +16,8 @@ class Roda
|
|
|
16
16
|
# This is because the <tt>'a'</tt> entry in the array matches, which
|
|
17
17
|
# makes the array match. However, the next matcher is the
|
|
18
18
|
# terminal matcher (since +r.is+ was used), and since the
|
|
19
|
-
# path is not
|
|
20
|
-
# matching <tt>'a'</tt
|
|
19
|
+
# remaining path is not empty as it still contains +/b+ after
|
|
20
|
+
# matching <tt>'a'</tt>, matching fails.
|
|
21
21
|
#
|
|
22
22
|
# With the backtracking_array plugin, when the terminal matcher
|
|
23
23
|
# fails, matching will go on to the next entry in the array,
|
|
@@ -17,9 +17,9 @@ class Roda
|
|
|
17
17
|
# end
|
|
18
18
|
# end
|
|
19
19
|
#
|
|
20
|
-
# The locals you specify in the set_layout_locals and set_view_locals methods
|
|
20
|
+
# The locals you specify in the +set_layout_locals+ and +set_view_locals+ methods
|
|
21
21
|
# have higher precedence than the render_locals plugin options, but lower precedence
|
|
22
|
-
# than options you directly pass to the view
|
|
22
|
+
# than options you directly pass to the +view+/+render+ methods.
|
|
23
23
|
module BranchLocals
|
|
24
24
|
SCOPE_INSTANCE_VARIABLES = [:@_layout_locals, :@_view_locals].freeze
|
|
25
25
|
|
|
@@ -54,7 +54,7 @@ class Roda
|
|
|
54
54
|
def render_locals
|
|
55
55
|
locals = super
|
|
56
56
|
if @_view_locals
|
|
57
|
-
locals =
|
|
57
|
+
locals = locals.merge(@_view_locals)
|
|
58
58
|
end
|
|
59
59
|
locals
|
|
60
60
|
end
|
|
@@ -63,7 +63,7 @@ class Roda
|
|
|
63
63
|
def layout_locals
|
|
64
64
|
locals = super
|
|
65
65
|
if @_layout_locals
|
|
66
|
-
locals =
|
|
66
|
+
locals = locals.merge(@_layout_locals)
|
|
67
67
|
end
|
|
68
68
|
locals
|
|
69
69
|
end
|
data/lib/roda/plugins/break.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
class Roda
|
|
5
5
|
module RodaPlugins
|
|
6
|
-
# The break plugin supports calling break inside a match block, to
|
|
6
|
+
# The break plugin supports calling +break+ inside a match block, to
|
|
7
7
|
# return from the block and continue in the routing tree, restoring
|
|
8
8
|
# the remaining path so that future matchers operating on the path
|
|
9
9
|
# operate as expected.
|
|
@@ -28,7 +28,7 @@ class Roda
|
|
|
28
28
|
module RequestMethods
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
# Handle
|
|
31
|
+
# Handle break inside match blocks, restoring remaining path.
|
|
32
32
|
def always
|
|
33
33
|
rp = @remaining_path
|
|
34
34
|
super
|
data/lib/roda/plugins/caching.rb
CHANGED
|
@@ -100,33 +100,31 @@ class Roda
|
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
# Set the response entity tag using the ETag header.
|
|
103
|
+
# Set the response entity tag using the +ETag+ header.
|
|
104
104
|
#
|
|
105
105
|
# The +value+ argument is an identifier that uniquely
|
|
106
106
|
# identifies the current version of the resource.
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
107
|
+
# Keyword Arguments:
|
|
108
|
+
# +weak+ :: Use a weak cache validator (a strong cache validator is the default)
|
|
109
|
+
# +new_resource+ :: Whether this etag should match an etag of * (true for POST, false otherwise)
|
|
110
110
|
#
|
|
111
|
-
# When the current request includes an If-None-Match header with a
|
|
111
|
+
# When the current request includes an <tt>If-None-Match</tt> header with a
|
|
112
112
|
# matching etag, immediately returns a response with a 304 or 412 status,
|
|
113
113
|
# depending on the request method.
|
|
114
114
|
#
|
|
115
|
-
# When the current request includes an If-Match header with an etag that
|
|
116
|
-
# doesn't match, or where the
|
|
115
|
+
# When the current request includes an <tt>If-Match</tt> header with an etag that
|
|
116
|
+
# doesn't match, or where the +weak+ argument was used, immediately returns a
|
|
117
117
|
# response with a 412 status.
|
|
118
|
-
def etag(value, opts=OPTS)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
new_resource = opts.fetch(:new_resource){post?}
|
|
122
|
-
|
|
118
|
+
def etag(value, opts=(no_positional_opts = true; OPTS), weak: opts[:weak], new_resource: opts.fetch(:new_resource){post?}, **kw)
|
|
119
|
+
RodaPlugins.check_keywords(__callee__, no_positional_opts, opts, kw)
|
|
120
|
+
|
|
123
121
|
res = response
|
|
124
122
|
e = env
|
|
125
123
|
res[RodaResponseHeaders::ETAG] = etag = "#{'W/' if weak}\"#{value}\""
|
|
126
124
|
status = res.status
|
|
127
125
|
|
|
128
126
|
if (!status || (status >= 200 && status < 300) || status == 304)
|
|
129
|
-
if etag_matches?(e['HTTP_IF_NONE_MATCH'], etag, new_resource)
|
|
127
|
+
if etag_matches?(e['HTTP_IF_NONE_MATCH'], etag, new_resource, weak: true)
|
|
130
128
|
res.status = (request_method =~ /\AGET|HEAD|OPTIONS|TRACE\z/i ? 304 : 412)
|
|
131
129
|
halt
|
|
132
130
|
end
|
|
@@ -143,10 +141,21 @@ class Roda
|
|
|
143
141
|
private
|
|
144
142
|
|
|
145
143
|
# Helper method checking if a ETag value list includes the current ETag.
|
|
146
|
-
|
|
144
|
+
# If +weak+ is true, uses the weak comparison that <tt>If-None-Match</tt> requires
|
|
145
|
+
# (RFC 9110 section 13.1.2), which ignores a W/ prefix on both sides.
|
|
146
|
+
def etag_matches?(list, etag, new_resource, weak: false)
|
|
147
147
|
return unless list
|
|
148
148
|
return !new_resource if list == '*'
|
|
149
|
-
list.to_s.split(/\s*,\s*/)
|
|
149
|
+
list = list.to_s.split(/\s*,\s*/)
|
|
150
|
+
if weak
|
|
151
|
+
etag = etag.sub(/\AW\//, '')
|
|
152
|
+
list.any? do |tag|
|
|
153
|
+
tag.sub!(/\AW\//, '')
|
|
154
|
+
tag == etag
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
list.include?(etag)
|
|
158
|
+
end
|
|
150
159
|
end
|
|
151
160
|
|
|
152
161
|
# Helper method parsing a time value from an HTTP header, returning the
|
|
@@ -154,11 +163,12 @@ class Roda
|
|
|
154
163
|
def time_from_header(t)
|
|
155
164
|
Time.httpdate(t).to_i if t
|
|
156
165
|
rescue ArgumentError
|
|
166
|
+
nil
|
|
157
167
|
end
|
|
158
168
|
end
|
|
159
169
|
|
|
160
170
|
module ResponseMethods
|
|
161
|
-
# Specify response freshness policy for using the Cache-Control header.
|
|
171
|
+
# Specify response freshness policy for using the <tt>Cache-Control</tt> header.
|
|
162
172
|
# Options can can any non-value directives (:public, :private, :no_cache,
|
|
163
173
|
# :no_store, :must_revalidate, :proxy_revalidate), with true as the value.
|
|
164
174
|
# Options can also contain value directives (:max_age, :s_maxage).
|
|
@@ -184,7 +194,7 @@ class Roda
|
|
|
184
194
|
# cached for. Also sets the Expires header, useful if you have
|
|
185
195
|
# HTTP 1.0 clients (Cache-Control is an HTTP 1.1 header).
|
|
186
196
|
def expires(max_age, opts=OPTS)
|
|
187
|
-
cache_control(
|
|
197
|
+
cache_control(opts.merge(:max_age=>max_age))
|
|
188
198
|
@headers[RodaResponseHeaders::EXPIRES] = (Time.now + max_age).httpdate
|
|
189
199
|
end
|
|
190
200
|
|
|
@@ -34,7 +34,7 @@ class Roda
|
|
|
34
34
|
#
|
|
35
35
|
# In this case, the block may return nil, instead of the content of
|
|
36
36
|
# the template. To handle this case, you can provide the
|
|
37
|
-
# <tt>returns: :buffer</tt>
|
|
37
|
+
# <tt>returns: :buffer</tt> argument when calling the method (to handle
|
|
38
38
|
# that specific call, or when loading the plugin (to default to that
|
|
39
39
|
# behavior). Note that if the output buffer object responds to
|
|
40
40
|
# +capture+ and is not an instance of String, the <tt>returns: :buffer</tt>
|
|
@@ -46,9 +46,10 @@ class Roda
|
|
|
46
46
|
|
|
47
47
|
# Support <tt>returns: :buffer</tt> to default to returning buffer
|
|
48
48
|
# object.
|
|
49
|
-
def self.configure(app, opts=OPTS)
|
|
49
|
+
def self.configure(app, opts=(no_positional_opts = true; OPTS), returns: (opts.has_key?(:returns) ? opts[:returns] : (returns_not_set = true; nil)), **kw)
|
|
50
|
+
RodaPlugins.check_keywords("the capture_erb plugin", no_positional_opts, opts, kw)
|
|
50
51
|
# RODA4: make returns: :buffer the default behavior
|
|
51
|
-
app.opts[:capture_erb_returns] =
|
|
52
|
+
app.opts[:capture_erb_returns] = returns unless returns_not_set
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
module ClassMethods
|
|
@@ -61,21 +62,23 @@ class Roda
|
|
|
61
62
|
# Return the value of the block, converted to a string.
|
|
62
63
|
# Restore the previous ERB output buffer before returning.
|
|
63
64
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
def capture_erb(opts=OPTS, &block)
|
|
65
|
+
# Keyword arguments:
|
|
66
|
+
# +returns+ :: If set to :buffer, returns the value of the
|
|
67
|
+
# template output variable, instead of the return
|
|
68
|
+
# value of the block converted to a string. This
|
|
69
|
+
# is the default behavior if the template output
|
|
70
|
+
# variable supports the +capture+ method and is not
|
|
71
|
+
# a String instance.
|
|
72
|
+
def capture_erb(opts=(no_positional_opts = true; OPTS), returns: (opts.has_key?(:returns) ? opts[:returns] : (returns_not_set = true; nil)), **kw, &block)
|
|
73
|
+
RodaPlugins.check_keywords(__callee__, no_positional_opts, opts, kw)
|
|
74
|
+
|
|
72
75
|
outvar = render_opts[:template_opts][:outvar]
|
|
73
76
|
buf_was = instance_variable_get(outvar)
|
|
74
77
|
|
|
75
78
|
if buf_was.respond_to?(:capture) && !buf_was.instance_of?(String)
|
|
76
79
|
buf_was.capture(&block)
|
|
77
80
|
else
|
|
78
|
-
returns =
|
|
81
|
+
returns = self.class.capture_erb_returns if returns_not_set
|
|
79
82
|
|
|
80
83
|
begin
|
|
81
84
|
instance_variable_set(outvar, String.new)
|
data/lib/roda/plugins/chunked.rb
CHANGED
|
@@ -23,7 +23,7 @@ class Roda
|
|
|
23
23
|
# handling is reduced, since if an error occurs while rendering a template,
|
|
24
24
|
# a successful response code has already been sent.
|
|
25
25
|
#
|
|
26
|
-
# To use chunked encoding for a response,
|
|
26
|
+
# To use chunked encoding for a response, call the +chunked+ method
|
|
27
27
|
# instead of view:
|
|
28
28
|
#
|
|
29
29
|
# r.root do
|
|
@@ -32,7 +32,7 @@ class Roda
|
|
|
32
32
|
#
|
|
33
33
|
# If you want to execute code after flushing the top part of the layout
|
|
34
34
|
# template, but before rendering the content template, pass a block to
|
|
35
|
-
# chunked
|
|
35
|
+
# +chunked+:
|
|
36
36
|
#
|
|
37
37
|
# r.root do
|
|
38
38
|
# chunked(:index) do
|
|
@@ -40,7 +40,7 @@ class Roda
|
|
|
40
40
|
# end
|
|
41
41
|
# end
|
|
42
42
|
#
|
|
43
|
-
# You can also call delay manually with a block, and the execution of the
|
|
43
|
+
# You can also call +delay+ manually with a block, and the execution of the
|
|
44
44
|
# block will be delayed until rendering the content template. This is
|
|
45
45
|
# useful if you want to delay execution for all routes under a branch:
|
|
46
46
|
#
|
|
@@ -56,12 +56,12 @@ class Roda
|
|
|
56
56
|
# end
|
|
57
57
|
# end
|
|
58
58
|
#
|
|
59
|
-
# If you want to chunk all responses, pass the
|
|
59
|
+
# If you want to chunk all responses, pass the +chunk_by_default+ argument
|
|
60
60
|
# when loading the plugin:
|
|
61
61
|
#
|
|
62
62
|
# plugin :chunked, chunk_by_default: true
|
|
63
63
|
#
|
|
64
|
-
# then you can just use the normal view method:
|
|
64
|
+
# then you can just use the normal +view+ method:
|
|
65
65
|
#
|
|
66
66
|
# r.root do
|
|
67
67
|
# view(:index)
|
|
@@ -73,17 +73,17 @@ class Roda
|
|
|
73
73
|
# enable chunking by default, you need to make sure that none of your
|
|
74
74
|
# content templates make state changes that affect the layout template.
|
|
75
75
|
# Additionally, make sure nowhere in your app are you doing any processing
|
|
76
|
-
# after the call to view
|
|
76
|
+
# after the call to +view+.
|
|
77
77
|
#
|
|
78
|
-
# If you use
|
|
79
|
-
# call no_chunk
|
|
78
|
+
# If you use +chunk_by_default+, but want to turn off chunking for a view,
|
|
79
|
+
# call +no_chunk!+:
|
|
80
80
|
#
|
|
81
81
|
# r.root do
|
|
82
82
|
# no_chunk!
|
|
83
83
|
# view(:index)
|
|
84
84
|
# end
|
|
85
85
|
#
|
|
86
|
-
# Inside your layout or content templates, you can call the flush method
|
|
86
|
+
# Inside your layout or content templates, you can call the +flush+ method
|
|
87
87
|
# to flush the current result of the template to the user, useful for
|
|
88
88
|
# streaming large datasets.
|
|
89
89
|
#
|
|
@@ -103,7 +103,7 @@ class Roda
|
|
|
103
103
|
# <% flush %><%= render(:subtemplate) %>
|
|
104
104
|
#
|
|
105
105
|
# If you want to use chunked encoding when rendering a template, but don't
|
|
106
|
-
# want to use a layout, pass the <tt>layout: false</tt> option to chunked
|
|
106
|
+
# want to use a layout, pass the <tt>layout: false</tt> option to +chunked+.
|
|
107
107
|
#
|
|
108
108
|
# r.root do
|
|
109
109
|
# chunked(:index, layout: false)
|
|
@@ -116,8 +116,8 @@ class Roda
|
|
|
116
116
|
# env['rack.logger'].error(e)
|
|
117
117
|
# end
|
|
118
118
|
#
|
|
119
|
-
# It is possible to set
|
|
120
|
-
# flush to output the message to the client inside handle_chunk_error.
|
|
119
|
+
# It is possible to set +@_out_buf+ to an error notification and call
|
|
120
|
+
# +flush+ to output the message to the client inside handle_chunk_error.
|
|
121
121
|
#
|
|
122
122
|
# In order for chunking to work, you must make sure that no proxies between
|
|
123
123
|
# the application and the client buffer responses.
|
|
@@ -125,22 +125,22 @@ class Roda
|
|
|
125
125
|
# If you are using nginx and have it set to buffer proxy responses by
|
|
126
126
|
# default, you can turn this off on a per response basis using the
|
|
127
127
|
# X-Accel-Buffering header. To set this header or similar headers for
|
|
128
|
-
# all chunked responses, pass a
|
|
128
|
+
# all chunked responses, pass a +headers+ argument when loading the plugin:
|
|
129
129
|
#
|
|
130
130
|
# plugin :chunked, headers: {'X-Accel-Buffering'=>'no'}
|
|
131
131
|
#
|
|
132
|
-
# By default, this plugin does not use Transfer-Encoding: chunked
|
|
132
|
+
# By default, this plugin does not use <tt>Transfer-Encoding: chunked</tt>, it only
|
|
133
133
|
# returns a body that will stream the response in chunks. If you would like
|
|
134
|
-
# to force the use of Transfer-Encoding: chunked
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# HTTP/1.1 requests since Transfer-Encoding: chunked is only supported
|
|
134
|
+
# to force the use of <tt>Transfer-Encoding: chunked</tt>, you can use the
|
|
135
|
+
# +force_chunked_encoding+ plugin argument. If using the
|
|
136
|
+
# +force_chunked_encoding+ argument, chunking will only be used for
|
|
137
|
+
# HTTP/1.1 requests since <tt>Transfer-Encoding: chunked</tt> is only supported
|
|
138
138
|
# in HTTP/1.1 (non-HTTP/1.1 requests will have behavior similar to
|
|
139
|
-
# calling no_chunk
|
|
139
|
+
# calling +no_chunk!+).
|
|
140
140
|
#
|
|
141
141
|
# The chunked plugin requires the render plugin, and only works for
|
|
142
142
|
# template engines that store their template output variable in
|
|
143
|
-
#
|
|
143
|
+
# +@_out_buf+. Also, it only works if the content template is directly
|
|
144
144
|
# injected into the layout template without modification.
|
|
145
145
|
#
|
|
146
146
|
# If using the chunked plugin with the flash plugin, make sure you
|
|
@@ -155,14 +155,16 @@ class Roda
|
|
|
155
155
|
app.plugin :render
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
-
# Set plugin specific
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
# Set plugin specific arguments. Keyword arguments:
|
|
159
|
+
# +chunk_by_default+ :: chunk all calls to view by default
|
|
160
|
+
# +force_chunked_encoding+ :: force the use of Transfer-Encoding: chunked for HTTP 1.1 requests
|
|
161
|
+
# +headers+ :: Set default additional headers to use when calling view
|
|
162
|
+
def self.configure(app, opts=(no_positional_opts = true; OPTS), chunk_by_default: opts[:chunk_by_default], force_chunked_encoding: opts[:force_chunked_encoding], headers: opts[:headers], **kw)
|
|
163
|
+
RodaPlugins.check_keywords("the chunked plugin", no_positional_opts, opts, kw)
|
|
164
|
+
app.opts[:chunk_by_default] = chunk_by_default
|
|
165
|
+
app.opts[:force_chunked_encoding] = force_chunked_encoding
|
|
166
|
+
if headers
|
|
167
|
+
app.opts[:chunk_headers] = (app.chunk_headers || {}).merge(headers).freeze
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
|
|
@@ -251,7 +253,7 @@ class Roda
|
|
|
251
253
|
if opts.empty?
|
|
252
254
|
opts = template
|
|
253
255
|
else
|
|
254
|
-
opts =
|
|
256
|
+
opts = opts.merge(template)
|
|
255
257
|
end
|
|
256
258
|
end
|
|
257
259
|
|
|
@@ -295,7 +297,7 @@ class Roda
|
|
|
295
297
|
@_out_buf = String.new
|
|
296
298
|
end
|
|
297
299
|
|
|
298
|
-
if layout_opts
|
|
300
|
+
if layout_opts = view_layout_opts(opts)
|
|
299
301
|
@_out_buf = render_template(layout_opts) do
|
|
300
302
|
flush
|
|
301
303
|
run_delayed_blocks
|
|
@@ -45,13 +45,16 @@ class Roda
|
|
|
45
45
|
#
|
|
46
46
|
# When using the class_level_routing plugin with nested routes, you may also want to use the
|
|
47
47
|
# delegate plugin to delegate certain instance methods to the request object, so you don't have
|
|
48
|
-
# to continually use +request.+ in your routing blocks
|
|
48
|
+
# to continually use +request.+ in your routing blocks, or use the r plugin so you can use +r+
|
|
49
|
+
# instead of +request+.
|
|
49
50
|
#
|
|
50
51
|
# Note that class level routing is implemented via a simple array of routes, so routing performance
|
|
51
52
|
# will degrade linearly as the number of routes increases. For best performance, you should use
|
|
52
53
|
# the normal +route+ class method to define your routing tree. This plugin does make it simpler to
|
|
53
54
|
# add additional routes after the routing tree has already been defined, though.
|
|
54
55
|
module ClassLevelRouting
|
|
56
|
+
SCOPE_INSTANCE_VARIABLES = [:@_original_remaining_path].freeze
|
|
57
|
+
|
|
55
58
|
# Initialize the class_routes array when the plugin is loaded. Also, if the application doesn't
|
|
56
59
|
# currently have a routing block, setup an empty routing block so that things will still work if
|
|
57
60
|
# a routing block isn't added.
|
|
@@ -63,7 +66,7 @@ class Roda
|
|
|
63
66
|
RodaPlugins.opt_attr_reader(self, :class_level_routes)
|
|
64
67
|
|
|
65
68
|
# Define routing methods that will store class level routes.
|
|
66
|
-
[
|
|
69
|
+
%i[root on is get post delete head options link patch put trace unlink query].each do |request_meth|
|
|
67
70
|
define_method(request_meth) do |*args, &block|
|
|
68
71
|
meth = define_roda_method("class_level_routing_#{request_meth}", :any, &block)
|
|
69
72
|
class_level_routes << [request_meth, args, meth].freeze
|
|
@@ -67,8 +67,8 @@ class Roda
|
|
|
67
67
|
# Blocks passed to the class_matchers plugin are evaluated in route
|
|
68
68
|
# block context.
|
|
69
69
|
#
|
|
70
|
-
# When passing a regexp as a matcher
|
|
71
|
-
# <tt>segment: true</tt>
|
|
70
|
+
# When passing a regexp as a matcher to class_matcher, you can provide a
|
|
71
|
+
# <tt>segment: true</tt> argument to speed up the matching on Ruby 2.4+:
|
|
72
72
|
#
|
|
73
73
|
# symbol_matcher(:employee_id, /E-(\d{6})/, segment: true) do |employee_id|
|
|
74
74
|
# employee_id.to_i
|
|
@@ -103,10 +103,13 @@ class Roda
|
|
|
103
103
|
# If providing a registered class or symbol, the block will be called with the
|
|
104
104
|
# captures returned by the block for the registered class or symbol, or the regexp
|
|
105
105
|
# captures if no block was registered with the class or symbol. In either case,
|
|
106
|
-
# if a block is given, it should return an
|
|
107
|
-
# the match block.
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
# if a block is given, it should return an object to yield to the match block,
|
|
107
|
+
# or an array to yield multiple arguments to the match block. If the block returns
|
|
108
|
+
# nil or false, it will be treated as not matching.
|
|
109
|
+
def class_matcher(klass, matcher, opts=(no_positional_opts = true; OPTS), segment: opts[:segment], **kw, &block)
|
|
110
|
+
RodaPlugins.check_keywords(__callee__, no_positional_opts, opts, kw)
|
|
111
|
+
|
|
112
|
+
_symbol_class_matcher(Class, klass, matcher, block, segment: segment) do |meth, (_, regexp, convert_meth, consume_meth)|
|
|
110
113
|
if regexp
|
|
111
114
|
if consume_meth == :_consume_single_segment
|
|
112
115
|
define_method(meth){_consume_single_segment(regexp, convert_meth)}
|
|
@@ -117,6 +120,8 @@ class Roda
|
|
|
117
120
|
define_method(meth){_consume_segment(convert_meth)}
|
|
118
121
|
end
|
|
119
122
|
end
|
|
123
|
+
|
|
124
|
+
nil
|
|
120
125
|
end
|
|
121
126
|
|
|
122
127
|
# Freeze the class_matchers hash when freezing the app.
|
|
@@ -19,20 +19,31 @@ class Roda
|
|
|
19
19
|
# plugin :common_logger, $stdout
|
|
20
20
|
# plugin :common_logger, Logger.new('filename')
|
|
21
21
|
# plugin :common_logger, Logger.new('filename'), method: :debug
|
|
22
|
+
# plugin :common_logger, use_request_ip: true
|
|
23
|
+
#
|
|
24
|
+
# Keyword Arguments:
|
|
25
|
+
# +method+ :: Specifies the method to call on the logger object when logging.
|
|
26
|
+
# +use_request_ip+ :: Use +request.ip+ to get the IP address, instead of the default
|
|
27
|
+
# of checking +HTTP_X_FORWARDED_FOR+ and then +REMOTE_ADDR+. This
|
|
28
|
+
# will become the default behavior starting in Roda 4.
|
|
22
29
|
module CommonLogger
|
|
23
30
|
SCOPE_INSTANCE_VARIABLES = [:@_request_timer].freeze
|
|
24
31
|
|
|
25
32
|
MUTATE_LINE = RUBY_VERSION < '2.3' || RUBY_VERSION >= '3'
|
|
26
33
|
private_constant :MUTATE_LINE
|
|
27
34
|
|
|
28
|
-
def self.configure(app, logger=nil, opts=OPTS)
|
|
35
|
+
def self.configure(app, logger=nil, opts=(no_positional_opts = true; OPTS), method: opts[:method], use_request_ip: false, **kw)
|
|
36
|
+
RodaPlugins.check_keywords("the common_logger plugin", no_positional_opts, opts, kw)
|
|
37
|
+
|
|
29
38
|
common_logger = app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr # RODA4: drop setting of option, no longer used
|
|
30
|
-
app.opts[:common_logger_meth] = common_logger.method(
|
|
39
|
+
app.opts[:common_logger_meth] = common_logger.method(method || (common_logger.respond_to?(:write) ? :write : :<<))
|
|
40
|
+
app.opts[:common_logger_use_request_ip] = use_request_ip # RODA4: Deefault to true
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
if RUBY_VERSION >= '2.1'
|
|
34
44
|
# A timer object for calculating elapsed time.
|
|
35
45
|
def self.start_timer
|
|
46
|
+
# RODA4.1: Remove
|
|
36
47
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
37
48
|
end
|
|
38
49
|
else
|
|
@@ -45,6 +56,7 @@ class Roda
|
|
|
45
56
|
|
|
46
57
|
module ClassMethods
|
|
47
58
|
RodaPlugins.opt_attr_reader(self, :common_logger_meth)
|
|
59
|
+
RodaPlugins.opt_attr_reader(self, :common_logger_use_request_ip, name: :common_logger_use_request_ip?)
|
|
48
60
|
end
|
|
49
61
|
|
|
50
62
|
module InstanceMethods
|
|
@@ -62,7 +74,13 @@ class Roda
|
|
|
62
74
|
|
|
63
75
|
env = @_request.env
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
ip = if self.class.common_logger_use_request_ip?
|
|
78
|
+
@_request.ip
|
|
79
|
+
else
|
|
80
|
+
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
line = "#{ip || "-"} - #{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} "
|
|
66
84
|
if MUTATE_LINE
|
|
67
85
|
line.gsub!(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
|
|
68
86
|
# simplecov:disable
|
|
@@ -8,15 +8,15 @@ class Roda
|
|
|
8
8
|
# use in applications that want to use sessions for some requests,
|
|
9
9
|
# and want to be sure that sessions are not used for other requests.
|
|
10
10
|
# For example, if you want to make sure that sessions are not used for
|
|
11
|
-
# requests with paths starting with
|
|
11
|
+
# requests with paths starting with +/static+, you could do:
|
|
12
12
|
#
|
|
13
13
|
# plugin :conditional_sessions, secret: ENV["SECRET"] do
|
|
14
14
|
# !path_info.start_with?('/static')
|
|
15
15
|
# end
|
|
16
16
|
#
|
|
17
|
-
#
|
|
17
|
+
# Then the request +session+, +session_created_at+, and +session_updated_at+ methods
|
|
18
18
|
# raise a RodaError exception when sessions are not allowed. The request
|
|
19
|
-
# persist_session and route scope clear_session methods do nothing when
|
|
19
|
+
# +persist_session+ and route scope +clear_session+ methods do nothing when
|
|
20
20
|
# sessions are not allowed.
|
|
21
21
|
module ConditionalSessions
|
|
22
22
|
# Pass all options to the sessions block, and use the block to define
|
|
@@ -11,7 +11,7 @@ class Roda
|
|
|
11
11
|
# of the normal content pane.
|
|
12
12
|
#
|
|
13
13
|
# In the template in which you want to store content, call
|
|
14
|
-
# content_for with a block:
|
|
14
|
+
# +content_for+ with a block:
|
|
15
15
|
#
|
|
16
16
|
# <% content_for :foo do %>
|
|
17
17
|
# Some content here.
|
|
@@ -27,11 +27,11 @@ class Roda
|
|
|
27
27
|
# <% content_for :foo, "Some content" %>
|
|
28
28
|
#
|
|
29
29
|
# In the template in which you want to retrieve content,
|
|
30
|
-
# call content_for without the block or argument:
|
|
30
|
+
# call +content_for+ without the block or argument:
|
|
31
31
|
#
|
|
32
32
|
# <%= content_for :foo %>
|
|
33
33
|
#
|
|
34
|
-
# Note that when storing content by calling content_for
|
|
34
|
+
# Note that when storing content by calling +content_for+
|
|
35
35
|
# with a block and embedding template code, the return
|
|
36
36
|
# value of the block is used as the content (after being
|
|
37
37
|
# converted to a string). This can cause issues in some
|
|
@@ -51,7 +51,7 @@ class Roda
|
|
|
51
51
|
# If content_for is used multiple times with the same key,
|
|
52
52
|
# by default, the last call will append previous calls.
|
|
53
53
|
# If you want to overwrite the previous content, pass the
|
|
54
|
-
# <tt>append: false</tt>
|
|
54
|
+
# <tt>append: false</tt> argument when loading the plugin:
|
|
55
55
|
#
|
|
56
56
|
# plugin :content_for, append: false
|
|
57
57
|
module ContentFor
|
|
@@ -65,8 +65,9 @@ class Roda
|
|
|
65
65
|
|
|
66
66
|
# Configure whether to append or overwrite if content_for
|
|
67
67
|
# is called multiple times with the same key.
|
|
68
|
-
def self.configure(app, opts = OPTS)
|
|
69
|
-
|
|
68
|
+
def self.configure(app, opts=(no_positional_opts = true; OPTS), append: opts.fetch(:append, true), **kw)
|
|
69
|
+
RodaPlugins.check_keywords("the content_for plugin", no_positional_opts, opts, kw)
|
|
70
|
+
app.opts[:append_content_for] = append
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
module ClassMethods
|