roda 3.18.0 → 3.19.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 +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
|