roda 3.18.0 → 3.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/README.rdoc +7 -9
- data/doc/conventions.rdoc +10 -10
- data/doc/release_notes/3.19.0.txt +229 -0
- data/lib/roda.rb +88 -45
- data/lib/roda/plugins/assets.rb +11 -4
- data/lib/roda/plugins/delay_build.rb +3 -30
- data/lib/roda/plugins/empty_root.rb +1 -1
- data/lib/roda/plugins/hash_routes.rb +455 -0
- data/lib/roda/plugins/match_hook.rb +69 -0
- data/lib/roda/plugins/multi_route.rb +4 -0
- data/lib/roda/plugins/multi_view.rb +4 -0
- data/lib/roda/plugins/optimized_string_matchers.rb +1 -1
- data/lib/roda/plugins/sessions.rb +63 -16
- data/lib/roda/plugins/static_routing.rb +7 -40
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +3 -0
- data/spec/freeze_spec.rb +10 -1
- data/spec/integration_spec.rb +1 -1
- data/spec/plugin/assets_spec.rb +16 -0
- data/spec/plugin/delay_build_spec.rb +2 -3
- data/spec/plugin/hash_routes_spec.rb +535 -0
- data/spec/plugin/match_hook_spec.rb +79 -0
- data/spec/plugin/middleware_spec.rb +1 -0
- data/spec/plugin/sessions_spec.rb +363 -320
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 930b16a28e9acd91a802b03248b36f940ee50f22b387e2e6839b34513b181c19
|
4
|
+
data.tar.gz: ff504ecc1d793e7af9180742204e219e26838f2187c0538241e2c240d57d4ad9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 472f855ac1ea091a973d5396dd5950147cdee2f72fa4692233e0acca033ed989ad3d7c7aa338c053058b070efaa151de4b40ad475a9a14e70a32a21e81fe0f3c
|
7
|
+
data.tar.gz: bc8c7417ec1922531a2a535b57ca3528fe0de9731df0aaed520d9e3df22263fa96f23eadf2313284a8f03af3e77addafaf45c1fc0061e3795e01b3dd18024922
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
= 3.19.0 (2019-04-12)
|
2
|
+
|
3
|
+
* Allow assets plugin :timestamp_paths option to be a string to specify a custom separator (jeremyevans)
|
4
|
+
|
5
|
+
* Fix handling for blocks with arity > 1 where expected arity is 1 (jeremyevans)
|
6
|
+
|
7
|
+
* Improve performance for handling blocks with arity 0 where expected arity is 1 by avoiding instance_exec (jeremyevans)
|
8
|
+
|
9
|
+
* Improve terminal maching by around 4x (jeremyevans)
|
10
|
+
|
11
|
+
* Improve symbol matching by 10-20% (jeremyevans)
|
12
|
+
|
13
|
+
* Improve string matching by 10-20% (jeremyevans)
|
14
|
+
|
15
|
+
* Automatically load the direct_call plugin when freezing if no middleware is used for better performance (jeremyevans)
|
16
|
+
|
17
|
+
* Delay building rack app until Roda.app is called (jeremyevans)
|
18
|
+
|
19
|
+
* Add hash_routes plugin for O(1) route dispatching at any level in the routing tree (jeremyevans)
|
20
|
+
|
21
|
+
* Add support for per-cookie cipher secrets in the sessions plugin, and enable them by default (jeremyevans)
|
22
|
+
|
23
|
+
* Add match_hook plugin for calling hooks when there is a successful match block (adam12) (#164)
|
24
|
+
|
1
25
|
= 3.18.0 (2019-03-15)
|
2
26
|
|
3
27
|
* Add direct_call plugin for making Roda.call skip middleware, allowing more optimization when dispatching routes (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -633,25 +633,23 @@ If you have a lot of rack applications that you want to dispatch to, and
|
|
633
633
|
which one to dispatch to is based on the request path prefix, look into the
|
634
634
|
+multi_run+ plugin.
|
635
635
|
|
636
|
-
===
|
636
|
+
=== hash_routes plugin
|
637
637
|
|
638
638
|
If you are just looking to split up the main route block up by branches,
|
639
|
-
you should use the +
|
639
|
+
you should use the +hash_routes+ plugin,
|
640
640
|
which keeps the current scope of the +route+ block:
|
641
641
|
|
642
642
|
class App < Roda
|
643
|
-
plugin :
|
643
|
+
plugin :hash_routes
|
644
644
|
|
645
|
-
|
645
|
+
hash_branch "api" do |r|
|
646
646
|
r.is do
|
647
647
|
# ...
|
648
648
|
end
|
649
649
|
end
|
650
650
|
|
651
651
|
route do |r|
|
652
|
-
r.
|
653
|
-
r.route "api"
|
654
|
-
end
|
652
|
+
r.hash_routes
|
655
653
|
end
|
656
654
|
end
|
657
655
|
|
@@ -662,8 +660,8 @@ and still have access to them inside the +api+ +route+ block.
|
|
662
660
|
|
663
661
|
== Testing
|
664
662
|
|
665
|
-
It is very easy to test Roda with {Rack::Test}[https://github.com/
|
666
|
-
or {Capybara}[https://github.com/
|
663
|
+
It is very easy to test Roda with {Rack::Test}[https://github.com/rack-test/rack-test]
|
664
|
+
or {Capybara}[https://github.com/teamcapybara/capybara].
|
667
665
|
Roda's own tests use {minitest/spec}[https://github.com/seattlerb/minitest].
|
668
666
|
The default Rake task will run the specs for Roda.
|
669
667
|
|
data/doc/conventions.rdoc
CHANGED
@@ -78,14 +78,14 @@ Large applications generally need more structure:
|
|
78
78
|
|
79
79
|
For larger apps, the +Rakefile+, +assets/+, +migrate+, +models.rb+, +models/+, +public/+, remain the same.
|
80
80
|
|
81
|
-
+app_name.rb+ should use the +
|
82
|
-
The routes used by the +
|
81
|
+
+app_name.rb+ should use the +hash_routes+ and +view_options+ plugin, or the +multi_run+ plugin.
|
82
|
+
The routes used by the +hash_routes+ or +multi_run+ should be stored in routing files in the +routes/+
|
83
83
|
directory, with one file per prefix.
|
84
84
|
|
85
85
|
For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
|
86
86
|
and one file per prefix in +spec/web/+.
|
87
87
|
|
88
|
-
You should have a separate view subdirectory per prefix. If you are using +
|
88
|
+
You should have a separate view subdirectory per prefix. If you are using +hash_routes+ and +view_options+ plugins,
|
89
89
|
use +set_view_subdir+ in your routing files to specify the subdirectory to use, so it doesn't need to be
|
90
90
|
specified on every call to view.
|
91
91
|
|
@@ -95,7 +95,7 @@ and views. In a small application, these methods should just be specified in +a
|
|
95
95
|
=== Really Large Applications
|
96
96
|
|
97
97
|
For very large applications, it's expected that there will be deviations from these conventions. However,
|
98
|
-
it is recommended to use the +
|
98
|
+
it is recommended to use the +hash_routes+ or +multi_run+ plugins to organize your application, and have
|
99
99
|
subdirectories in the +routes/+ directory, and nested subdirectories in the +views/+ directory.
|
100
100
|
|
101
101
|
== Roda Application File Layout
|
@@ -112,7 +112,7 @@ For a small application, the convention in Roda is to layout your Roda applicati
|
|
112
112
|
|
113
113
|
use SomeMiddleware
|
114
114
|
|
115
|
-
plugin :render
|
115
|
+
plugin :render, escape: true
|
116
116
|
plugin :assets
|
117
117
|
|
118
118
|
route do |r|
|
@@ -145,21 +145,21 @@ For larger applications, there are some slight changes to the Roda application f
|
|
145
145
|
|
146
146
|
use SomeMiddleware
|
147
147
|
|
148
|
-
plugin :render
|
148
|
+
plugin :render, escape: true
|
149
149
|
plugin :assets
|
150
150
|
plugin :view_options
|
151
|
-
plugin :
|
151
|
+
plugin :hash_routes
|
152
152
|
Dir['./routes/*.rb'].each{|f| require f}
|
153
153
|
|
154
154
|
route do |r|
|
155
|
-
r.
|
155
|
+
r.hash_routes
|
156
156
|
end
|
157
157
|
|
158
158
|
Dir['./helpers/*.rb'].each{|f| require f}
|
159
159
|
end
|
160
160
|
|
161
|
-
After loading the +view_options+ and +
|
161
|
+
After loading the +view_options+ and +hash_routes+ plugin, you require all of your
|
162
162
|
routing files. Inside your route block, instead of defining your routes, you just call
|
163
|
-
+r.
|
163
|
+
+r.hash_routes+, which will dispatch to all of your routing files. After your route
|
164
164
|
block, you require all of your helper files containing the instance methods for your
|
165
165
|
route block or views, instead of defining the methods directly.
|
@@ -0,0 +1,229 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A hash_routes plugin has been added for O(1) route dispatching at
|
4
|
+
any level of the routing tree. By default, Roda uses a linear
|
5
|
+
search of possible branches at each level of the routing tree,
|
6
|
+
which results in roughly O(log(n)) routing behavior in most
|
7
|
+
applications (where n is the total number of routes in the
|
8
|
+
application).
|
9
|
+
|
10
|
+
Assume you have the following routing tree:
|
11
|
+
|
12
|
+
route do |r|
|
13
|
+
r.on "a" do
|
14
|
+
# ...
|
15
|
+
end
|
16
|
+
|
17
|
+
r.on "b" do
|
18
|
+
# ...
|
19
|
+
end
|
20
|
+
|
21
|
+
r.is "c" do
|
22
|
+
# ...
|
23
|
+
end
|
24
|
+
|
25
|
+
# ...
|
26
|
+
end
|
27
|
+
|
28
|
+
With this routing tree, a request for /c will first check /a and
|
29
|
+
/b. This is not normally a performance issue, but if you have a
|
30
|
+
large number of routes at a particular level, it can be.
|
31
|
+
|
32
|
+
The hash_routes plugin allows you to convert this routing tree to:
|
33
|
+
|
34
|
+
plugin :hash_routes
|
35
|
+
|
36
|
+
hash_routes do
|
37
|
+
on "a" do |r|
|
38
|
+
# ...
|
39
|
+
end
|
40
|
+
|
41
|
+
on "b" do |r|
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
|
45
|
+
is "c" do |r|
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
|
49
|
+
# ...
|
50
|
+
end
|
51
|
+
|
52
|
+
route do |r|
|
53
|
+
r.hash_routes
|
54
|
+
end
|
55
|
+
|
56
|
+
This routing tree looks similar to Roda's standard routing tree, and
|
57
|
+
will have the same behavior as the previous example, but dispatching
|
58
|
+
to the routes inside the hash_routes block by the r.hash_routes
|
59
|
+
method will be an O(1) operation, instead of a linear search. This
|
60
|
+
can significantly improve performance in cases where you have a large
|
61
|
+
number of branches at any point in the routing tree.
|
62
|
+
|
63
|
+
In order to support O(1) route dispatching at any level of the
|
64
|
+
tree, the hash_routes plugin supports namespaces. You can use this
|
65
|
+
namespace support to keep the primary advantage of Roda when using
|
66
|
+
the hash_routes plugin, which is the ability to operate on a request
|
67
|
+
at any point during routing. Assume you have this routing tree:
|
68
|
+
|
69
|
+
hash_routes :root do
|
70
|
+
on "foo" do |r|
|
71
|
+
r.on Integer do |foo_id|
|
72
|
+
next unless @foo = Foo[foo_id]
|
73
|
+
r.hash_routes(:foo)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
on "bar" do |r|
|
78
|
+
r.on Integer do |bar_id|
|
79
|
+
next unless @bar = Bar[bar_id]
|
80
|
+
r.hash_routes(:bar)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# ...
|
85
|
+
end
|
86
|
+
|
87
|
+
hash_routes :foo do
|
88
|
+
get "show" do
|
89
|
+
@page_title = @foo.name
|
90
|
+
view('foo/show')
|
91
|
+
end
|
92
|
+
|
93
|
+
# ...
|
94
|
+
end
|
95
|
+
|
96
|
+
hash_routes :bar do
|
97
|
+
post "edit" do
|
98
|
+
@bar.update(:name=>request.params['name'])
|
99
|
+
r.redirect "/"
|
100
|
+
end
|
101
|
+
|
102
|
+
# ...
|
103
|
+
end
|
104
|
+
|
105
|
+
route do |r|
|
106
|
+
r.hash_routes(:root)
|
107
|
+
end
|
108
|
+
|
109
|
+
With this routing tree, a GET /foo/123/show request will first get
|
110
|
+
dispatched to the on "foo" block in the :root namespace. That will
|
111
|
+
extract the 123 segment from the path, and use it to find the Foo
|
112
|
+
object with id 123 and set that to the instance variable @foo.
|
113
|
+
If there is no matching foo, the rest of the block will be skipped,
|
114
|
+
which will result in a 404 response. If there is a matching foo,
|
115
|
+
after setting the instance variable, it will dispatch to routes in
|
116
|
+
the :foo namespace, one of which is show, which will be able to use
|
117
|
+
the @foo variable, both in the route and in the view.
|
118
|
+
|
119
|
+
Similarly, a POST /bar/321/edit request would dispatch to the on
|
120
|
+
"bar" block in the :root namespace, will look up the matching bar,
|
121
|
+
then will dispatch to the edit route in the :bar namespace.
|
122
|
+
|
123
|
+
The hash_routes plugin can be used as a faster version of the
|
124
|
+
multi_route plugin's r.multi_route method. It can also be used as
|
125
|
+
a faster replacement for the multi_view plugin.
|
126
|
+
|
127
|
+
Please see the hash_routes plugin documentation for additional
|
128
|
+
methods and configuration styles supported by the plugin.
|
129
|
+
|
130
|
+
* A match_hook plugin has been added, which is called for each
|
131
|
+
successful match, before yielding to the match block. For example,
|
132
|
+
with the following routing tree:
|
133
|
+
|
134
|
+
plugin :match_hook
|
135
|
+
|
136
|
+
match_hook do
|
137
|
+
puts "#{r.matched_path}|#{r.remaining_path}"
|
138
|
+
end
|
139
|
+
|
140
|
+
route do |r|
|
141
|
+
r.on "a" do
|
142
|
+
r.is "b" do
|
143
|
+
r.get do
|
144
|
+
end
|
145
|
+
|
146
|
+
r.post do
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
A GET request for /a/b would call the match hook three times, and
|
153
|
+
output the following:
|
154
|
+
|
155
|
+
/a|/b # When the r.on block matches
|
156
|
+
/a/b| # When the r.is block matches
|
157
|
+
/a/b| # When the r.get block matches
|
158
|
+
|
159
|
+
A GET request for /a/c would call the match hook once, and output
|
160
|
+
the following:
|
161
|
+
|
162
|
+
/a|/b # When the r.on block matches
|
163
|
+
|
164
|
+
This plugin can be used to make debugging easier, as well as for
|
165
|
+
metrics.
|
166
|
+
|
167
|
+
= Other Improvements
|
168
|
+
|
169
|
+
* Per-cookie cipher secrets are now supported and used automatically
|
170
|
+
by default in the sessions plugin. This can prevent issues where
|
171
|
+
the cipher secret can be leaked if the random initialization vector
|
172
|
+
turns out not to be so random and ends up being reused. This
|
173
|
+
makes the session cookies slightly larger and about 10-20% slower.
|
174
|
+
|
175
|
+
Note that because of the way the sessions plugin is designed,
|
176
|
+
even if the cipher secret was leaked and you are not using
|
177
|
+
per-cookie cipher secrets, it would not allow an attacker to
|
178
|
+
forge a session, it would only allow them to read the contents of
|
179
|
+
an existing session.
|
180
|
+
|
181
|
+
If you are currently using the sessions plugin, and performing
|
182
|
+
rolling restarts, you should temporarily disable per-cookie session
|
183
|
+
secrets until all processes have been restarted and are able to
|
184
|
+
support per-cookie session secrets. You can do so by setting the
|
185
|
+
:per_cookie_cipher_secret sessions plugin option to false
|
186
|
+
temporarily until all processes have restarted and are running Roda
|
187
|
+
3.19.0+.
|
188
|
+
|
189
|
+
* When passing route blocks to Roda that have 0 arity instead of the
|
190
|
+
expected arity of 1, emulate an arity of 1 using an approach that is
|
191
|
+
about 2.75-8x faster. This emulation is still about 20% slower than
|
192
|
+
using the expected arity.
|
193
|
+
|
194
|
+
* Fix emulation of route blocks that have >1 arity but where the
|
195
|
+
expected arity is 1. Such blocks were not handled correctly in
|
196
|
+
Roda 3.18.0.
|
197
|
+
|
198
|
+
* String matching performance has been improved by 10-20%.
|
199
|
+
|
200
|
+
* Symbol and String class matching performance has improved by 10-20%.
|
201
|
+
|
202
|
+
* Terminal matching performance has improved by about 4x.
|
203
|
+
|
204
|
+
* Roda will now automatically load the direct_call plugin when
|
205
|
+
freezing the application if there is no middleware used and the
|
206
|
+
application has not been subclassed, for improved performance.
|
207
|
+
|
208
|
+
* Roda no longer builds the rack application until the app class
|
209
|
+
method is called. This can fix O(n^2) issues when building
|
210
|
+
applications with a lot of middleware. One consequence of
|
211
|
+
this is that a Roda.route block is no longer required. If
|
212
|
+
Roda.route is not called, then the default routing tree will
|
213
|
+
return a 404 response for all requests.
|
214
|
+
|
215
|
+
The delay_build plugin used to support delaying building the rack
|
216
|
+
application until a build! method is called. Now that Roda delays
|
217
|
+
building the rack application until the app method is called, there
|
218
|
+
is no reason to use this plugin, and it is now a no-op.
|
219
|
+
|
220
|
+
* The assets plugin :timestamp_paths option now supports a string
|
221
|
+
value to use a custom separator. A slash separator is still used
|
222
|
+
by default.
|
223
|
+
|
224
|
+
= Backwards Compatibility
|
225
|
+
|
226
|
+
* The static_routing plugin internals have changed, as the
|
227
|
+
static_routing is now implemented via the hash_routes plugin. If
|
228
|
+
you were depending on the internals, you will need to update your
|
229
|
+
code.
|
data/lib/roda.rb
CHANGED
@@ -118,7 +118,9 @@ class Roda
|
|
118
118
|
# Class methods for the Roda class.
|
119
119
|
module ClassMethods
|
120
120
|
# The rack application that this class uses.
|
121
|
-
|
121
|
+
def app
|
122
|
+
@app || build_rack_app
|
123
|
+
end
|
122
124
|
|
123
125
|
# Whether middleware from the current class should be inherited by subclasses.
|
124
126
|
# True by default, should be set to false when using a design where the parent
|
@@ -142,7 +144,7 @@ class Roda
|
|
142
144
|
# Clear the middleware stack
|
143
145
|
def clear_middleware!
|
144
146
|
@middleware.clear
|
145
|
-
|
147
|
+
@app = nil
|
146
148
|
end
|
147
149
|
|
148
150
|
# Define an instance method using the block with the provided name and
|
@@ -172,6 +174,7 @@ class Roda
|
|
172
174
|
if meth.is_a?(String)
|
173
175
|
meth = roda_method_name(meth)
|
174
176
|
end
|
177
|
+
call_meth = meth
|
175
178
|
|
176
179
|
if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
|
177
180
|
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
|
@@ -194,8 +197,15 @@ class Roda
|
|
194
197
|
if check_arity == :warn
|
195
198
|
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
|
196
199
|
end
|
200
|
+
temp_method = roda_method_name("temp")
|
201
|
+
class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
|
202
|
+
alias_method meth, temp_method
|
203
|
+
undef_method temp_method
|
204
|
+
private meth
|
205
|
+
meth = :"#{meth}_arity"
|
206
|
+
elsif required_args > 1
|
197
207
|
b = block
|
198
|
-
block = lambda{|
|
208
|
+
block = lambda{|r| instance_exec(r, &b)} # Fallback
|
199
209
|
end
|
200
210
|
when :any
|
201
211
|
if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
|
@@ -239,10 +249,9 @@ class Roda
|
|
239
249
|
send(meth, *a)
|
240
250
|
end
|
241
251
|
private arity_meth
|
242
|
-
arity_meth
|
243
|
-
else
|
244
|
-
meth
|
245
252
|
end
|
253
|
+
|
254
|
+
call_meth
|
246
255
|
end
|
247
256
|
|
248
257
|
# Expand the given path, using the root argument as the base directory.
|
@@ -258,8 +267,7 @@ class Roda
|
|
258
267
|
# Note that freezing the class prevents you from subclassing it, mostly because
|
259
268
|
# it would cause some plugins to break.
|
260
269
|
def freeze
|
261
|
-
|
262
|
-
@middleware.freeze
|
270
|
+
return self if frozen?
|
263
271
|
|
264
272
|
unless opts[:subclassed]
|
265
273
|
# If the _roda_run_main_route instance method has not been overridden,
|
@@ -276,8 +284,16 @@ class Roda
|
|
276
284
|
end
|
277
285
|
end
|
278
286
|
end
|
287
|
+
|
288
|
+
if @middleware.empty? && use_new_dispatch_api?
|
289
|
+
plugin :direct_call
|
290
|
+
end
|
279
291
|
end
|
280
292
|
|
293
|
+
build_rack_app
|
294
|
+
@opts.freeze
|
295
|
+
@middleware.freeze
|
296
|
+
|
281
297
|
super
|
282
298
|
end
|
283
299
|
|
@@ -340,7 +356,7 @@ class Roda
|
|
340
356
|
self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods)
|
341
357
|
self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods)
|
342
358
|
plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
|
343
|
-
nil
|
359
|
+
@app = nil
|
344
360
|
end
|
345
361
|
|
346
362
|
# Setup routing tree for the current Roda application, and build the
|
@@ -366,7 +382,7 @@ class Roda
|
|
366
382
|
@route_block = block = convert_route_block(block)
|
367
383
|
@rack_app_route_block = block = rack_app_route_block(block)
|
368
384
|
public define_roda_method(:_roda_main_route, 1, &block)
|
369
|
-
|
385
|
+
@app = nil
|
370
386
|
end
|
371
387
|
|
372
388
|
# Add a middleware to use for the rack application. Must be
|
@@ -375,7 +391,7 @@ class Roda
|
|
375
391
|
# Roda.use Rack::ShowExceptions
|
376
392
|
def use(*args, &block)
|
377
393
|
@middleware << [args, block].freeze
|
378
|
-
|
394
|
+
@app = nil
|
379
395
|
end
|
380
396
|
|
381
397
|
private
|
@@ -426,29 +442,15 @@ class Roda
|
|
426
442
|
|
427
443
|
# Build the rack app to use
|
428
444
|
def build_rack_app
|
429
|
-
|
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)
|
445
|
+
app = base_rack_app_callable(use_new_dispatch_api?)
|
444
446
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
end
|
450
|
-
@app = app
|
447
|
+
@middleware.reverse_each do |args, bl|
|
448
|
+
mid, *args = args
|
449
|
+
app = mid.new(app, *args, &bl)
|
450
|
+
app.freeze if opts[:freeze_middleware]
|
451
451
|
end
|
452
|
+
|
453
|
+
@app = app
|
452
454
|
end
|
453
455
|
|
454
456
|
# Modify the route block to use for any route block provided as input,
|
@@ -499,6 +501,24 @@ WARNING
|
|
499
501
|
block
|
500
502
|
end
|
501
503
|
|
504
|
+
# Whether the new dispatch API should be used.
|
505
|
+
def use_new_dispatch_api?
|
506
|
+
# RODA4: remove this method
|
507
|
+
ancestors.each do |mod|
|
508
|
+
break if mod == InstanceMethods
|
509
|
+
meths = mod.instance_methods(false)
|
510
|
+
if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
|
511
|
+
RodaPlugins.warn <<WARNING
|
512
|
+
Falling back to using #call for dispatching for #{self}, due to #call override in #{mod}.
|
513
|
+
#{mod} should be fixed to adjust to Roda's new dispatch API, and override _roda_handle_main_route or _roda_run_main_route
|
514
|
+
WARNING
|
515
|
+
return false
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
true
|
520
|
+
end
|
521
|
+
|
502
522
|
method_num = 0
|
503
523
|
method_num_mutex = Mutex.new
|
504
524
|
# Return a unique method name symbol for the given suffix.
|
@@ -1017,13 +1037,34 @@ WARNING
|
|
1017
1037
|
# request path is a slash (indicating a new segment).
|
1018
1038
|
def _match_string(str)
|
1019
1039
|
rp = @remaining_path
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1040
|
+
length = str.length
|
1041
|
+
|
1042
|
+
match = case rp.rindex(str, length)
|
1043
|
+
when nil
|
1044
|
+
# segment does not match, most common case
|
1045
|
+
return
|
1046
|
+
when 1
|
1047
|
+
# segment matches, check first character is /
|
1048
|
+
rp.getbyte(0) == 47
|
1049
|
+
else # must be 0
|
1050
|
+
# segment matches at first character, only a match if
|
1051
|
+
# empty string given and first character is /
|
1052
|
+
length == 0 && rp.getbyte(0) == 47
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
if match
|
1056
|
+
length += 1
|
1057
|
+
case rp.getbyte(length)
|
1058
|
+
when 47
|
1059
|
+
# next character is /, update remaining path to rest of string
|
1060
|
+
@remaining_path = rp[length, 100000000]
|
1025
1061
|
when nil
|
1062
|
+
# end of string, so remaining path is empty
|
1026
1063
|
@remaining_path = ""
|
1064
|
+
# else
|
1065
|
+
# Any other value means this was partial segment match,
|
1066
|
+
# so we return nil in that case without updating the
|
1067
|
+
# remaining_path. No need for explicit else clause.
|
1027
1068
|
end
|
1028
1069
|
end
|
1029
1070
|
end
|
@@ -1031,7 +1072,7 @@ WARNING
|
|
1031
1072
|
# Match the given symbol if any segment matches.
|
1032
1073
|
def _match_symbol(sym=nil)
|
1033
1074
|
rp = @remaining_path
|
1034
|
-
if rp
|
1075
|
+
if rp.getbyte(0) == 47
|
1035
1076
|
if last = rp.index('/', 1)
|
1036
1077
|
if last > 1
|
1037
1078
|
@captures << rp[1, last-1]
|
@@ -1117,7 +1158,7 @@ WARNING
|
|
1117
1158
|
|
1118
1159
|
# Whether the current path is considered empty.
|
1119
1160
|
def empty_path?
|
1120
|
-
remaining_path
|
1161
|
+
remaining_path.empty?
|
1121
1162
|
end
|
1122
1163
|
|
1123
1164
|
# If all of the arguments match, yields to the match block and
|
@@ -1148,18 +1189,20 @@ WARNING
|
|
1148
1189
|
_match_class(matcher)
|
1149
1190
|
when TERM
|
1150
1191
|
empty_path?
|
1151
|
-
when Symbol
|
1152
|
-
_match_symbol(matcher)
|
1153
1192
|
when Regexp
|
1154
1193
|
_match_regexp(matcher)
|
1155
|
-
when
|
1156
|
-
|
1194
|
+
when true
|
1195
|
+
matcher
|
1157
1196
|
when Array
|
1158
1197
|
_match_array(matcher)
|
1198
|
+
when Hash
|
1199
|
+
_match_hash(matcher)
|
1200
|
+
when Symbol
|
1201
|
+
_match_symbol(matcher)
|
1202
|
+
when false, nil
|
1203
|
+
matcher
|
1159
1204
|
when Proc
|
1160
1205
|
matcher.call
|
1161
|
-
when true, false, nil
|
1162
|
-
matcher
|
1163
1206
|
else
|
1164
1207
|
unsupported_matcher(matcher)
|
1165
1208
|
end
|