roda 3.17.0 → 3.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +22 -4
- data/doc/release_notes/3.18.0.txt +170 -0
- data/lib/roda.rb +249 -26
- data/lib/roda/plugins/_after_hook.rb +4 -26
- data/lib/roda/plugins/_before_hook.rb +30 -2
- data/lib/roda/plugins/branch_locals.rb +2 -2
- data/lib/roda/plugins/class_level_routing.rb +9 -7
- data/lib/roda/plugins/default_headers.rb +15 -1
- data/lib/roda/plugins/default_status.rb +9 -10
- data/lib/roda/plugins/direct_call.rb +38 -0
- data/lib/roda/plugins/error_email.rb +1 -1
- data/lib/roda/plugins/error_handler.rb +37 -11
- data/lib/roda/plugins/hooks.rb +28 -30
- data/lib/roda/plugins/mail_processor.rb +16 -11
- data/lib/roda/plugins/mailer.rb +1 -1
- data/lib/roda/plugins/middleware.rb +13 -3
- data/lib/roda/plugins/multi_route.rb +3 -3
- data/lib/roda/plugins/named_templates.rb +4 -4
- data/lib/roda/plugins/path.rb +13 -8
- data/lib/roda/plugins/render.rb +2 -2
- data/lib/roda/plugins/route_block_args.rb +4 -3
- data/lib/roda/plugins/route_csrf.rb +9 -4
- data/lib/roda/plugins/sessions.rb +2 -1
- data/lib/roda/plugins/shared_vars.rb +1 -1
- data/lib/roda/plugins/static_routing.rb +7 -17
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/view_options.rb +2 -2
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +257 -0
- data/spec/plugin/class_level_routing_spec.rb +0 -27
- data/spec/plugin/default_headers_spec.rb +7 -0
- data/spec/plugin/default_status_spec.rb +31 -1
- data/spec/plugin/direct_call_spec.rb +28 -0
- data/spec/plugin/error_handler_spec.rb +27 -0
- data/spec/plugin/hooks_spec.rb +21 -0
- data/spec/plugin/middleware_spec.rb +108 -36
- data/spec/plugin/multi_route_spec.rb +12 -0
- data/spec/plugin/route_csrf_spec.rb +27 -0
- data/spec/plugin/sessions_spec.rb +26 -1
- data/spec/plugin/static_routing_spec.rb +25 -3
- data/spec/plugin/status_handler_spec.rb +17 -0
- data/spec/route_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -2
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f39ffb4b00598e7e113f06c2f6e43e968f96b2a447059de8b351c7f5cc35675a
|
4
|
+
data.tar.gz: eacc16913dcfd72ad822fd8b6f25e132be80123c25a6a0c29869b39a13f04b50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8aec053c2bda39f201cb03043cfe382d5982e5e16abca381ba76220603a25d680425a8aaf4b405f4134caef4336e3586e83d2ae0f43e8c7a40279650927237d3
|
7
|
+
data.tar.gz: 104867afa6a526d13acd76eab634eaf9688f44131606e1c7c67f73f1464c662d16d76938217c0b878deb0d7e6217ae5586fc2764be13cdd64298b5256bd4b3b8
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,51 @@
|
|
1
|
+
= 3.18.0 (2019-03-15)
|
2
|
+
|
3
|
+
* Add direct_call plugin for making Roda.call skip middleware, allowing more optimization when dispatching routes (jeremyevans)
|
4
|
+
|
5
|
+
* Improve performance of default_headers plugin by directly defining set_default_headers (jeremyevans)
|
6
|
+
|
7
|
+
* Improve performance when freezing app if certain methods have not been overridden (jeremyevans)
|
8
|
+
|
9
|
+
* Support :check_arity and :check_dynamic_arity app options for whether/how to check arity for blocks used to define methods (jeremyevans)
|
10
|
+
|
11
|
+
* Improve performance of the status_handler plugin by using methods instead of instance_exec (jeremyevans)
|
12
|
+
|
13
|
+
* Remove r.static_route method from the static_routing plugin (jeremyevans)
|
14
|
+
|
15
|
+
* Improve performance of the static_routing plugin by using methods instead of instance_exec (jeremyevans)
|
16
|
+
|
17
|
+
* Add support for the route_block_args plugin to the route_csrf plugin (jeremyevans)
|
18
|
+
|
19
|
+
* Improve performance of the route_csrf plugin by using a method instead of instance_exec (jeremyevans)
|
20
|
+
|
21
|
+
* Improve performance of the route_block_args plugin by using a method instead of instance_exec (jeremyevans)
|
22
|
+
|
23
|
+
* Improve performance of the path plugin by using methods instead of instance_exec (jeremyevans)
|
24
|
+
|
25
|
+
* Improve performance of the named_templates plugin by using methods instead of instance_exec (jeremyevans)
|
26
|
+
|
27
|
+
* Improve performance of the multi_route plugin by using methods instead of instance_exec (jeremyevans)
|
28
|
+
|
29
|
+
* Improve performance of the hooks plugin by using methods instead of instance_exec (jeremyevans)
|
30
|
+
|
31
|
+
* Improve performance of the mail_processor plugin by using methods instead of instance_exec (jeremyevans)
|
32
|
+
|
33
|
+
* Improve performance of the default_status plugin by directly defining the default_status method (jeremyevans)
|
34
|
+
|
35
|
+
* Improve performance of class_level_routing plugin using methods instead of instance_exec (jeremyevans)
|
36
|
+
|
37
|
+
* Do not have route_block_args plugin affect class_level_routes plugin (jeremyevans)
|
38
|
+
|
39
|
+
* Integrate internal after hook with error_handler plugin (jeremyevans)
|
40
|
+
|
41
|
+
* Improve performance of internal before and after hooks (jeremyevans)
|
42
|
+
|
43
|
+
* Improve performance by using method instead of instance_exec for main route block (jeremyevans)
|
44
|
+
|
45
|
+
* Add Roda.define_roda_method for defining instance methods instead of using instance_exec (jeremyevans)
|
46
|
+
|
47
|
+
* Include cookie_options when clearing the cookie (#162, #163) (eiko, jeremyevans)
|
48
|
+
|
1
49
|
= 3.17.0 (2019-02-15)
|
2
50
|
|
3
51
|
* Improve performance in the common case for RodaResponse#finish (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -103,7 +103,7 @@ The primary way routes are matched in Roda is by calling
|
|
103
103
|
Each of these "routing methods" takes a "match block".
|
104
104
|
|
105
105
|
Each routing method takes each of the arguments (called matchers)
|
106
|
-
that
|
106
|
+
that are given and tries to match it to the current request.
|
107
107
|
If the method is able to match all of the arguments, it yields to the match block;
|
108
108
|
otherwise, the block is skipped and execution continues.
|
109
109
|
|
@@ -592,8 +592,9 @@ with your application code. Some of the things Roda does:
|
|
592
592
|
are <tt>@_request</tt> and <tt>@_response</tt>. All instance variables in the
|
593
593
|
scope of the +route+ block used by plugins that ship with Roda are prefixed
|
594
594
|
with an underscore.
|
595
|
-
- The
|
596
|
-
+
|
595
|
+
- The main methods defined, beyond the default methods for +Object+, are
|
596
|
+
+env+, +opts+, +request+, +response+, and +session+. +call+ and +_call+ are also
|
597
|
+
defined, but are deprecated. All other methods defined are prefixed with +_roda_+
|
597
598
|
- Constants inside the Roda namespace are all prefixed with +Roda+
|
598
599
|
(e.g., <tt>Roda::RodaRequest</tt>).
|
599
600
|
|
@@ -698,6 +699,23 @@ The following options are respected by the default library or multiple plugins:
|
|
698
699
|
|
699
700
|
:add_script_name :: Prepend the SCRIPT_NAME for the request to paths. This is
|
700
701
|
useful if you mount the app as a path under another app.
|
702
|
+
:check_arity :: Whether arity for blocks passed to Roda should be checked
|
703
|
+
to determine if they can be used directly to define methods
|
704
|
+
or need to be wrapped. By default, for backwards compatibility,
|
705
|
+
this is true, so Roda will check blocks and handle cases where
|
706
|
+
the arity of the block does not match the expected arity. This
|
707
|
+
can be set to +:warn+ to issue warnings whenever Roda detects an
|
708
|
+
arity mismatch. If set to +false+, Roda does not check the arity
|
709
|
+
of blocks, which can result in failures at runtime if the arity
|
710
|
+
of the block does not match what Roda expects. Note that Roda
|
711
|
+
does not check the arity for lambda blocks, as those are strict
|
712
|
+
by default.
|
713
|
+
:check_dynamic_arity :: Similar to :check_arity, but used for checking blocks
|
714
|
+
where the number of arguments Roda will call the blocks
|
715
|
+
with is not possible to determine when defining the
|
716
|
+
method. By default, Roda checks arity for such methods,
|
717
|
+
but doing so actually slows the method down even if the
|
718
|
+
number of arguments matches the expected number of arguments.
|
701
719
|
:freeze_middleware :: Whether to freeze all middleware when building the rack app.
|
702
720
|
:json_parser :: A callable for parsing JSON (+JSON.parse+ in general used by
|
703
721
|
default).
|
@@ -831,7 +849,7 @@ but before handling other requests:
|
|
831
849
|
|
832
850
|
The easiest way to prevent XSS with Roda is to use a template library
|
833
851
|
that automatically escapes output by default.
|
834
|
-
The +:escape+ option to the +render+ plugin sets the
|
852
|
+
The +:escape+ option to the +render+ plugin sets the ERB template processor
|
835
853
|
to escape by default, so that in your templates:
|
836
854
|
|
837
855
|
<%= '<>' %> # outputs <>
|
@@ -0,0 +1,170 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A direct_call plugin has been added. This plugin makes Roda.call
|
4
|
+
call the app directly, skipping any middleware. This plugin
|
5
|
+
can be used for performance reasons, as the class itself can be
|
6
|
+
used as the base rack app, instead of using a lambda as the base
|
7
|
+
rack app. Roda.app.call will still call all middleware when
|
8
|
+
using this plugin.
|
9
|
+
|
10
|
+
= Other Improvements
|
11
|
+
|
12
|
+
* Blocks that are given during application configuration, and
|
13
|
+
previously executed with instance_exec, instead now define methods,
|
14
|
+
and Roda now calls these methods. This is a much faster approach.
|
15
|
+
This new approach, combined with the direct_call plugin and the
|
16
|
+
Roda.freeze optimizations, can be over 80% faster for trivial
|
17
|
+
applications, with measureable improvements in most applications.
|
18
|
+
|
19
|
+
As methods are strict in regards to arity and instance_exec is
|
20
|
+
not, Roda now checks all such blocks for arity mismatches, and
|
21
|
+
attempts to compensate for arity mismatches. In case of an arity
|
22
|
+
mismatch, Roda will define a method that will call instance_exec,
|
23
|
+
in which case there will not be a performance improvement.
|
24
|
+
|
25
|
+
For some methods, Roda may not know the expected arity until
|
26
|
+
runtime. In that case, Roda will check the arity at runtime and
|
27
|
+
try to call the method with the arity that it supports if there is
|
28
|
+
an arity mismatch.
|
29
|
+
|
30
|
+
You can control the checking of arity via two options:
|
31
|
+
|
32
|
+
:check_arity :: Set to false to turn off all arity checking. Set to
|
33
|
+
:warn to issue a warning when defining the method if
|
34
|
+
there is an arity mismatch (for methods where the
|
35
|
+
expected arity is known in advance).
|
36
|
+
:check_dynamic_arity :: Set to false to turn off arity checking for
|
37
|
+
methods defined where the arity is not known
|
38
|
+
at compile time. Set to :warn to issue a
|
39
|
+
warning at runtime every time the method is
|
40
|
+
called and there is an arity mismatch (for
|
41
|
+
methods where the expected arity is not
|
42
|
+
known in advance). Note that checking the
|
43
|
+
arity at runtime has a performance cost,
|
44
|
+
so for maximum performance this should be
|
45
|
+
set to false.
|
46
|
+
|
47
|
+
Note that this arity checking is only done to keep backwards
|
48
|
+
compatibility. Since lambdas already used strict arity, no arity
|
49
|
+
checking is done if the block is a lambda and not a regular proc.
|
50
|
+
|
51
|
+
Roda has a new dispatch API that works with these defined methods.
|
52
|
+
The new dispatch API uses the following methods:
|
53
|
+
|
54
|
+
* _roda_handle_main_route: Entry point for normal request dispatch.
|
55
|
+
* _roda_handle_route: Yields to the routing block, catching any
|
56
|
+
halts inside the block, treating the block as a routing block.
|
57
|
+
* _roda_main_route: Roda.route defines this method using the
|
58
|
+
block provided, it accepts the request as an argument.
|
59
|
+
* _roda_run_main_route: Calls _roda_main_route with the request,
|
60
|
+
allowing for plugins to execute code around the main routing,
|
61
|
+
while still being able to throw :halt to return a response.
|
62
|
+
|
63
|
+
All instance methods defined by Roda use the _roda_ prefix.
|
64
|
+
|
65
|
+
* When deleting the session cookie in the sessions plugin, the
|
66
|
+
Set-Cookie response header now uses the same path and domain
|
67
|
+
that was originally used to set the cookie. This can fix cases
|
68
|
+
where the cookie was not being cleared as expected.
|
69
|
+
|
70
|
+
* Freezing a Roda app now can add performance improvements in
|
71
|
+
addition to reliability improvements. When freezing the class,
|
72
|
+
if certain methods in the class have not been overridden, Roda
|
73
|
+
now defines aliases or more optimized methods to improve
|
74
|
+
performance.
|
75
|
+
|
76
|
+
* Roda now warns if the Roda#call method is overridden in a module,
|
77
|
+
without the module also overridding _roda_handle_main_route or
|
78
|
+
_roda_run_main_route. This indicates that the module needs
|
79
|
+
to be updated to use Roda's new dispatch API. Roda will continue
|
80
|
+
to work in this case, but it will be slower than the Roda's now
|
81
|
+
default behavior, as it will force usage of the old dispatch API.
|
82
|
+
This check will be removed in Roda 4, which will remove support
|
83
|
+
for Roda#call (and Roda#_call).
|
84
|
+
|
85
|
+
* When there is only a single internal before or after hook defined,
|
86
|
+
the hook is now faster by using a method alias.
|
87
|
+
|
88
|
+
* The route_csrf plugin block or :csrf_failure option proc now
|
89
|
+
integrates with the route_block_args plugin.
|
90
|
+
|
91
|
+
* The default_status plugin is now faster by defining the
|
92
|
+
default_status method directly.
|
93
|
+
|
94
|
+
* The default_headers plugin is now faster by defining an optimized
|
95
|
+
set_default_headers method directly.
|
96
|
+
|
97
|
+
* The hooks plugin is now faster by defining methods for each
|
98
|
+
hook block, with a main hook method that dispatches to each
|
99
|
+
of the hook block methods. If only a single hook block is
|
100
|
+
used, the main hook method is an alias to the hook block
|
101
|
+
method to avoid an extra method call.
|
102
|
+
|
103
|
+
* The following plugins now use define_method instead of
|
104
|
+
instance_exec for better performance:
|
105
|
+
|
106
|
+
* defaults_setter
|
107
|
+
* mail_processor
|
108
|
+
* multi_route
|
109
|
+
* named_templates
|
110
|
+
* path
|
111
|
+
* route_block_args
|
112
|
+
* route_csrf
|
113
|
+
* static_routing
|
114
|
+
* status_handler
|
115
|
+
|
116
|
+
* The internal after hook implementation has now been merged into
|
117
|
+
the error_handler plugin. This is faster in cases where the
|
118
|
+
error_handler plugin is used, and slower in cases where the
|
119
|
+
internal after hook plugin was used without the error_handler
|
120
|
+
plugin.
|
121
|
+
|
122
|
+
* The route_block_args plugin now handles cases where
|
123
|
+
Roda.convert_route_block has already been overridden.
|
124
|
+
|
125
|
+
* Performance of routing methods that can yield captures has been
|
126
|
+
improved.
|
127
|
+
|
128
|
+
* Hash#merge is now used in preference to Hash[].merge! in cases
|
129
|
+
where the receiver of Hash#merge would not be provided by the
|
130
|
+
user. This is because Hash#merge is faster than Hash[].merge!
|
131
|
+
in recent ruby versions. If the receiver of #merge is provided
|
132
|
+
by the user, then Hash[].merge! is still used to ensure that the
|
133
|
+
resulting value is plain hash.
|
134
|
+
|
135
|
+
* The static_routing plugin no longer removes existing static
|
136
|
+
routes if loaded more than once.
|
137
|
+
|
138
|
+
* Roda now warns when calling Roda.route without a block.
|
139
|
+
|
140
|
+
= Backwards Compatibility
|
141
|
+
|
142
|
+
* The route_block_args plugin no longer affects the
|
143
|
+
class_level_routing plugin. Support for this was added in Roda
|
144
|
+
3.17.0 when the route_block_args plugin was added, but this was a
|
145
|
+
mistake as class_level_routing blocks should be called with the
|
146
|
+
captures for their matchers, not with the route block args.
|
147
|
+
|
148
|
+
* Some of the internal state was changed in the following plugins:
|
149
|
+
|
150
|
+
* class_level_routing
|
151
|
+
* mail_processor
|
152
|
+
* multi_route
|
153
|
+
* named_templates
|
154
|
+
* static_routing
|
155
|
+
* status_handler
|
156
|
+
|
157
|
+
This only affects you if you were accessing the internal state
|
158
|
+
via the opts hash.
|
159
|
+
|
160
|
+
* The static_routing plugin no longer defines the r.static_route
|
161
|
+
method.
|
162
|
+
|
163
|
+
* The mailer plugin was switched to use the new dispatch API, and
|
164
|
+
will no longer handle cases where the old dispatch API (Roda#call)
|
165
|
+
was overrridden.
|
166
|
+
|
167
|
+
* The static_route method in the static_routing plugin must
|
168
|
+
now be called with a block. Previously, that would not
|
169
|
+
cause a failure until runtime, where it would fail when
|
170
|
+
you tried to execute the route.
|
data/lib/roda.rb
CHANGED
@@ -145,6 +145,106 @@ class Roda
|
|
145
145
|
build_rack_app
|
146
146
|
end
|
147
147
|
|
148
|
+
# Define an instance method using the block with the provided name and
|
149
|
+
# expected arity. If the name is given as a Symbol, it is used directly.
|
150
|
+
# If the name is given as a String, a unique name will be generated using
|
151
|
+
# that string. The expected arity should be either 0 (no arguments),
|
152
|
+
# 1 (single argument), or :any (any number of arguments).
|
153
|
+
#
|
154
|
+
# If the :check_arity app option is not set to false, Roda will check that
|
155
|
+
# the arity of the block matches the expected arity, and compensate for
|
156
|
+
# cases where it does not. If it is set to :warn, Roda will warn in the
|
157
|
+
# cases where the arity does not match what is expected.
|
158
|
+
#
|
159
|
+
# If the expected arity is :any, Roda must perform a dynamic arity check
|
160
|
+
# when the method is called, which can hurt performance even in the case
|
161
|
+
# where the arity matches. The :check_dynamic_arity app option can be
|
162
|
+
# set to false to turn off the dynamic arity checks. The
|
163
|
+
# :check_dynamic_arity app option can be to :warn to warn if Roda needs
|
164
|
+
# to adjust arity dynamically.
|
165
|
+
#
|
166
|
+
# Roda only checks arity for regular blocks, not lambda blocks, as the
|
167
|
+
# fixes Roda uses for regular blocks would not work for lambda blocks.
|
168
|
+
#
|
169
|
+
# Roda does not support blocks with required keyword arguments if the
|
170
|
+
# expected arity is 0 or 1.
|
171
|
+
def define_roda_method(meth, expected_arity, &block)
|
172
|
+
if meth.is_a?(String)
|
173
|
+
meth = roda_method_name(meth)
|
174
|
+
end
|
175
|
+
|
176
|
+
if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
|
177
|
+
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
|
178
|
+
|
179
|
+
if keyword == :required && (expected_arity == 0 || expected_arity == 1)
|
180
|
+
raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}"
|
181
|
+
end
|
182
|
+
|
183
|
+
case expected_arity
|
184
|
+
when 0
|
185
|
+
unless required_args == 0
|
186
|
+
if check_arity == :warn
|
187
|
+
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}"
|
188
|
+
end
|
189
|
+
b = block
|
190
|
+
block = lambda{instance_exec(&b)} # Fallback
|
191
|
+
end
|
192
|
+
when 1
|
193
|
+
if required_args == 0 && optional_args == 0 && !rest
|
194
|
+
if check_arity == :warn
|
195
|
+
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
|
196
|
+
end
|
197
|
+
b = block
|
198
|
+
block = lambda{|_| instance_exec(&b)} # Fallback
|
199
|
+
end
|
200
|
+
when :any
|
201
|
+
if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
|
202
|
+
if keyword
|
203
|
+
# Complexity of handling keyword arguments using define_method is too high,
|
204
|
+
# Fallback to instance_exec in this case.
|
205
|
+
b = block
|
206
|
+
block = lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
|
207
|
+
else
|
208
|
+
arity_meth = meth
|
209
|
+
meth = :"#{meth}_arity"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
else
|
213
|
+
raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
define_method(meth, &block)
|
218
|
+
private meth
|
219
|
+
|
220
|
+
if arity_meth
|
221
|
+
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
|
222
|
+
max_args = required_args + optional_args
|
223
|
+
define_method(arity_meth) do |*a|
|
224
|
+
arity = a.length
|
225
|
+
if arity > required_args
|
226
|
+
if arity > max_args && !rest
|
227
|
+
if check_dynamic_arity == :warn
|
228
|
+
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. At most #{max_args} arguments accepted, but #{arity} arguments given for #{block.inspect}"
|
229
|
+
end
|
230
|
+
a = a.slice(0, max_args)
|
231
|
+
end
|
232
|
+
elsif arity < required_args
|
233
|
+
if check_dynamic_arity == :warn
|
234
|
+
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}"
|
235
|
+
end
|
236
|
+
a.concat([nil] * (required_args - arity))
|
237
|
+
end
|
238
|
+
|
239
|
+
send(meth, *a)
|
240
|
+
end
|
241
|
+
private arity_meth
|
242
|
+
arity_meth
|
243
|
+
else
|
244
|
+
meth
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
148
248
|
# Expand the given path, using the root argument as the base directory.
|
149
249
|
def expand_path(path, root=opts[:root])
|
150
250
|
::File.expand_path(path, root)
|
@@ -160,6 +260,24 @@ class Roda
|
|
160
260
|
def freeze
|
161
261
|
@opts.freeze
|
162
262
|
@middleware.freeze
|
263
|
+
|
264
|
+
unless opts[:subclassed]
|
265
|
+
# If the _roda_run_main_route instance method has not been overridden,
|
266
|
+
# make it an alias to _roda_main_route for performance
|
267
|
+
if instance_method(:_roda_run_main_route).owner == InstanceMethods
|
268
|
+
class_eval("alias _roda_run_main_route _roda_main_route")
|
269
|
+
end
|
270
|
+
self::RodaResponse.class_eval do
|
271
|
+
if instance_method(:set_default_headers).owner == ResponseMethods &&
|
272
|
+
instance_method(:default_headers).owner == ResponseMethods
|
273
|
+
|
274
|
+
def set_default_headers
|
275
|
+
@headers['Content-Type'] ||= 'text/html'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
163
281
|
super
|
164
282
|
end
|
165
283
|
|
@@ -176,10 +294,16 @@ class Roda
|
|
176
294
|
# and setup the request and response subclasses.
|
177
295
|
def inherited(subclass)
|
178
296
|
raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
|
297
|
+
|
298
|
+
# Mark current class as having been subclassed, as some optimizations
|
299
|
+
# depend on the class not being subclassed
|
300
|
+
opts[:subclassed] = true
|
301
|
+
|
179
302
|
super
|
180
303
|
subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
|
181
304
|
subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
|
182
305
|
subclass.instance_variable_set(:@opts, opts.dup)
|
306
|
+
subclass.opts.delete(:subclassed)
|
183
307
|
subclass.opts.to_a.each do |k,v|
|
184
308
|
if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
|
185
309
|
subclass.opts[k] = v.dup
|
@@ -233,9 +357,15 @@ class Roda
|
|
233
357
|
# This should only be called once per class, and if called multiple
|
234
358
|
# times will overwrite the previous routing.
|
235
359
|
def route(&block)
|
360
|
+
unless block
|
361
|
+
RodaPlugins.warn "no block passed to Roda.route"
|
362
|
+
return
|
363
|
+
end
|
364
|
+
|
236
365
|
@raw_route_block = block
|
237
366
|
@route_block = block = convert_route_block(block)
|
238
|
-
@rack_app_route_block = rack_app_route_block(block)
|
367
|
+
@rack_app_route_block = block = rack_app_route_block(block)
|
368
|
+
public define_roda_method(:_roda_main_route, 1, &block)
|
239
369
|
build_rack_app
|
240
370
|
end
|
241
371
|
|
@@ -250,10 +380,68 @@ class Roda
|
|
250
380
|
|
251
381
|
private
|
252
382
|
|
383
|
+
# Return the number of required argument, optional arguments,
|
384
|
+
# whether the callable accepts any additional arguments,
|
385
|
+
# and whether the callable accepts keyword arguments (true, false
|
386
|
+
# or :required).
|
387
|
+
def _define_roda_method_arg_numbers(callable)
|
388
|
+
optional_args = 0
|
389
|
+
rest = false
|
390
|
+
keyword = false
|
391
|
+
callable.parameters.map(&:first).each do |arg_type, _|
|
392
|
+
case arg_type
|
393
|
+
when :opt
|
394
|
+
optional_args += 1
|
395
|
+
when :rest
|
396
|
+
rest = true
|
397
|
+
when :keyreq
|
398
|
+
keyword = :required
|
399
|
+
when :key, :keyrest
|
400
|
+
keyword ||= true
|
401
|
+
end
|
402
|
+
end
|
403
|
+
arity = callable.arity
|
404
|
+
if arity < 0
|
405
|
+
arity = arity.abs - 1
|
406
|
+
end
|
407
|
+
required_args = arity
|
408
|
+
arity -= 1 if keyword == :required
|
409
|
+
|
410
|
+
if callable.is_a?(Proc) && !callable.lambda?
|
411
|
+
optional_args -= arity
|
412
|
+
end
|
413
|
+
|
414
|
+
[required_args, optional_args, rest, keyword]
|
415
|
+
end
|
416
|
+
|
417
|
+
# The base rack app to use, before middleware is added.
|
418
|
+
def base_rack_app_callable(new_api=true)
|
419
|
+
if new_api
|
420
|
+
lambda{|env| new(env)._roda_handle_main_route}
|
421
|
+
else
|
422
|
+
block = @rack_app_route_block
|
423
|
+
lambda{|env| new(env).call(&block)}
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
253
427
|
# Build the rack app to use
|
254
428
|
def build_rack_app
|
255
|
-
if
|
256
|
-
|
429
|
+
if @rack_app_route_block
|
430
|
+
# RODA4: Assume optimize is true
|
431
|
+
optimize = ancestors.each do |mod|
|
432
|
+
break true if mod == InstanceMethods
|
433
|
+
meths = mod.instance_methods(false)
|
434
|
+
if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
|
435
|
+
RodaPlugins.warn <<WARNING
|
436
|
+
Falling back to using #call for dispatching for #{self}, due to #call override in #{mod}.
|
437
|
+
#{mod} should be fixed to adjust to Roda's new dispatch API, and override _roda_handle_main_route or _roda_run_main_route
|
438
|
+
WARNING
|
439
|
+
break false
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
app = base_rack_app_callable(optimize)
|
444
|
+
|
257
445
|
@middleware.reverse_each do |args, bl|
|
258
446
|
mid, *args = args
|
259
447
|
app = mid.new(app, *args, &bl)
|
@@ -274,13 +462,15 @@ class Roda
|
|
274
462
|
# in order, if any _roda_before_* methods are defined. Also, rebuild
|
275
463
|
# the route block if a _roda_before method is defined.
|
276
464
|
def def_roda_before
|
277
|
-
meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort
|
465
|
+
meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort
|
278
466
|
unless meths.empty?
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
467
|
+
plugin :_before_hook unless private_method_defined?(:_roda_before)
|
468
|
+
if meths.length == 1
|
469
|
+
class_eval("alias _roda_before #{meths.first}", __FILE__, __LINE__)
|
470
|
+
else
|
471
|
+
class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
|
283
472
|
end
|
473
|
+
private :_roda_before
|
284
474
|
end
|
285
475
|
end
|
286
476
|
|
@@ -288,10 +478,14 @@ class Roda
|
|
288
478
|
# in order, if any _roda_after_* methods are defined. Also, use
|
289
479
|
# the internal after hook plugin if the _roda_after method is defined.
|
290
480
|
def def_roda_after
|
291
|
-
meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort
|
481
|
+
meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort
|
292
482
|
unless meths.empty?
|
293
|
-
plugin :
|
294
|
-
|
483
|
+
plugin :error_handler unless private_method_defined?(:_roda_after)
|
484
|
+
if meths.length == 1
|
485
|
+
class_eval("alias _roda_after #{meths.first}", __FILE__, __LINE__)
|
486
|
+
else
|
487
|
+
class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
|
488
|
+
end
|
295
489
|
private :_roda_after
|
296
490
|
end
|
297
491
|
end
|
@@ -302,14 +496,14 @@ class Roda
|
|
302
496
|
# if any before hooks are defined.
|
303
497
|
# Can be modified by plugins.
|
304
498
|
def rack_app_route_block(block)
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
499
|
+
block
|
500
|
+
end
|
501
|
+
|
502
|
+
method_num = 0
|
503
|
+
method_num_mutex = Mutex.new
|
504
|
+
# Return a unique method name symbol for the given suffix.
|
505
|
+
define_method(:roda_method_name) do |suffix|
|
506
|
+
:"_roda_#{suffix}_#{method_num_mutex.synchronize{method_num += 1}}"
|
313
507
|
end
|
314
508
|
end
|
315
509
|
|
@@ -328,20 +522,49 @@ class Roda
|
|
328
522
|
@_response = klass::RodaResponse.new
|
329
523
|
end
|
330
524
|
|
331
|
-
#
|
332
|
-
#
|
333
|
-
|
334
|
-
|
525
|
+
# Handle dispatching to the main route, catching :halt and handling
|
526
|
+
# the result of the block.
|
527
|
+
def _roda_handle_main_route
|
528
|
+
catch(:halt) do
|
529
|
+
r = @_request
|
530
|
+
r.block_result(_roda_run_main_route(r))
|
531
|
+
@_response.finish
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# Treat the given block as a routing block, catching :halt if
|
536
|
+
# thrown by the block.
|
537
|
+
def _roda_handle_route
|
538
|
+
catch(:halt) do
|
539
|
+
@_request.block_result(yield)
|
540
|
+
@_response.finish
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# Default implementation of the main route, usually overridden
|
545
|
+
# by Roda.route.
|
546
|
+
def _roda_main_route(_)
|
547
|
+
end
|
548
|
+
|
549
|
+
# Run the main route block with the request. Designed for
|
550
|
+
# extension by plugins
|
551
|
+
def _roda_run_main_route(r)
|
552
|
+
_roda_main_route(r)
|
553
|
+
end
|
554
|
+
|
555
|
+
# Deprecated method for the previous main route dispatch API.
|
335
556
|
def call(&block)
|
557
|
+
# RODA4: Remove
|
336
558
|
catch(:halt) do
|
337
559
|
r = @_request
|
338
|
-
r.block_result(instance_exec(r, &block))
|
560
|
+
r.block_result(instance_exec(r, &block)) # Fallback
|
339
561
|
@_response.finish
|
340
562
|
end
|
341
563
|
end
|
342
564
|
|
343
|
-
#
|
565
|
+
# Deprecated private alias for internal use
|
344
566
|
alias _call call
|
567
|
+
# RODA4: Remove
|
345
568
|
private :_call
|
346
569
|
|
347
570
|
# The environment hash for the current request. Example:
|
@@ -904,7 +1127,7 @@ class Roda
|
|
904
1127
|
path = @remaining_path
|
905
1128
|
# For every block, we make sure to reset captures so that
|
906
1129
|
# nesting matchers won't mess with each other's captures.
|
907
|
-
@captures.clear
|
1130
|
+
captures = @captures.clear
|
908
1131
|
|
909
1132
|
if match_all(args)
|
910
1133
|
block_result(yield(*captures))
|