roda 3.83.0 → 3.85.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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/lib/roda/plugins/Integer_matcher_max.rb +9 -8
  3. data/lib/roda/plugins/_optimized_matching.rb +2 -2
  4. data/lib/roda/plugins/_symbol_class_matchers.rb +107 -0
  5. data/lib/roda/plugins/capture_erb.rb +6 -5
  6. data/lib/roda/plugins/class_matchers.rb +91 -14
  7. data/lib/roda/plugins/hsts.rb +35 -0
  8. data/lib/roda/plugins/placeholder_string_matchers.rb +4 -0
  9. data/lib/roda/plugins/public.rb +1 -1
  10. data/lib/roda/plugins/render.rb +1 -1
  11. data/lib/roda/plugins/symbol_matchers.rb +70 -15
  12. data/lib/roda/request.rb +16 -13
  13. data/lib/roda/response.rb +1 -1
  14. data/lib/roda/version.rb +1 -1
  15. data/lib/roda.rb +7 -0
  16. metadata +5 -179
  17. data/CHANGELOG +0 -691
  18. data/README.rdoc +0 -1136
  19. data/doc/conventions.rdoc +0 -177
  20. data/doc/release_notes/3.0.0.txt +0 -84
  21. data/doc/release_notes/3.1.0.txt +0 -24
  22. data/doc/release_notes/3.10.0.txt +0 -132
  23. data/doc/release_notes/3.11.0.txt +0 -54
  24. data/doc/release_notes/3.12.0.txt +0 -19
  25. data/doc/release_notes/3.13.0.txt +0 -38
  26. data/doc/release_notes/3.14.0.txt +0 -36
  27. data/doc/release_notes/3.14.1.txt +0 -43
  28. data/doc/release_notes/3.15.0.txt +0 -21
  29. data/doc/release_notes/3.16.0.txt +0 -52
  30. data/doc/release_notes/3.17.0.txt +0 -62
  31. data/doc/release_notes/3.18.0.txt +0 -170
  32. data/doc/release_notes/3.19.0.txt +0 -229
  33. data/doc/release_notes/3.2.0.txt +0 -22
  34. data/doc/release_notes/3.20.0.txt +0 -7
  35. data/doc/release_notes/3.21.0.txt +0 -5
  36. data/doc/release_notes/3.22.0.txt +0 -24
  37. data/doc/release_notes/3.23.0.txt +0 -28
  38. data/doc/release_notes/3.24.0.txt +0 -14
  39. data/doc/release_notes/3.25.0.txt +0 -12
  40. data/doc/release_notes/3.26.0.txt +0 -15
  41. data/doc/release_notes/3.27.0.txt +0 -15
  42. data/doc/release_notes/3.28.0.txt +0 -13
  43. data/doc/release_notes/3.29.0.txt +0 -15
  44. data/doc/release_notes/3.3.0.txt +0 -291
  45. data/doc/release_notes/3.30.0.txt +0 -14
  46. data/doc/release_notes/3.31.0.txt +0 -11
  47. data/doc/release_notes/3.32.0.txt +0 -42
  48. data/doc/release_notes/3.33.0.txt +0 -8
  49. data/doc/release_notes/3.34.0.txt +0 -17
  50. data/doc/release_notes/3.35.0.txt +0 -12
  51. data/doc/release_notes/3.36.0.txt +0 -17
  52. data/doc/release_notes/3.37.0.txt +0 -42
  53. data/doc/release_notes/3.38.0.txt +0 -5
  54. data/doc/release_notes/3.39.0.txt +0 -16
  55. data/doc/release_notes/3.4.0.txt +0 -24
  56. data/doc/release_notes/3.40.0.txt +0 -24
  57. data/doc/release_notes/3.41.0.txt +0 -9
  58. data/doc/release_notes/3.42.0.txt +0 -21
  59. data/doc/release_notes/3.43.0.txt +0 -34
  60. data/doc/release_notes/3.44.0.txt +0 -23
  61. data/doc/release_notes/3.45.0.txt +0 -22
  62. data/doc/release_notes/3.46.0.txt +0 -19
  63. data/doc/release_notes/3.47.0.txt +0 -13
  64. data/doc/release_notes/3.48.0.txt +0 -10
  65. data/doc/release_notes/3.49.0.txt +0 -18
  66. data/doc/release_notes/3.5.0.txt +0 -31
  67. data/doc/release_notes/3.50.0.txt +0 -21
  68. data/doc/release_notes/3.51.0.txt +0 -20
  69. data/doc/release_notes/3.52.0.txt +0 -20
  70. data/doc/release_notes/3.53.0.txt +0 -14
  71. data/doc/release_notes/3.54.0.txt +0 -48
  72. data/doc/release_notes/3.55.0.txt +0 -12
  73. data/doc/release_notes/3.56.0.txt +0 -33
  74. data/doc/release_notes/3.57.0.txt +0 -34
  75. data/doc/release_notes/3.58.0.txt +0 -16
  76. data/doc/release_notes/3.59.0.txt +0 -17
  77. data/doc/release_notes/3.6.0.txt +0 -21
  78. data/doc/release_notes/3.60.0.txt +0 -56
  79. data/doc/release_notes/3.61.0.txt +0 -24
  80. data/doc/release_notes/3.62.0.txt +0 -41
  81. data/doc/release_notes/3.63.0.txt +0 -36
  82. data/doc/release_notes/3.64.0.txt +0 -26
  83. data/doc/release_notes/3.65.0.txt +0 -12
  84. data/doc/release_notes/3.66.0.txt +0 -23
  85. data/doc/release_notes/3.67.0.txt +0 -25
  86. data/doc/release_notes/3.68.0.txt +0 -21
  87. data/doc/release_notes/3.69.0.txt +0 -33
  88. data/doc/release_notes/3.7.0.txt +0 -123
  89. data/doc/release_notes/3.70.0.txt +0 -19
  90. data/doc/release_notes/3.71.0.txt +0 -33
  91. data/doc/release_notes/3.72.0.txt +0 -48
  92. data/doc/release_notes/3.73.0.txt +0 -33
  93. data/doc/release_notes/3.74.0.txt +0 -28
  94. data/doc/release_notes/3.75.0.txt +0 -19
  95. data/doc/release_notes/3.76.0.txt +0 -18
  96. data/doc/release_notes/3.77.0.txt +0 -8
  97. data/doc/release_notes/3.78.0.txt +0 -99
  98. data/doc/release_notes/3.79.0.txt +0 -148
  99. data/doc/release_notes/3.8.0.txt +0 -27
  100. data/doc/release_notes/3.80.0.txt +0 -31
  101. data/doc/release_notes/3.81.0.txt +0 -24
  102. data/doc/release_notes/3.82.0.txt +0 -43
  103. data/doc/release_notes/3.83.0.txt +0 -6
  104. data/doc/release_notes/3.9.0.txt +0 -67
data/README.rdoc DELETED
@@ -1,1136 +0,0 @@
1
- rdoc-image:https://roda.jeremyevans.net/images/roda-logo.svg
2
-
3
- A routing tree web toolkit, designed for building fast and maintainable web applications in Ruby.
4
-
5
- == Table of contents
6
-
7
- - {Installation}[#label-Installation]
8
- - {Resources}[#label-Resources]
9
- - {Goals}[#label-Goals]
10
- - {Usage}[#label-Usage]
11
- - {Running the application}[#label-Running+the+Application]
12
- - {The routing tree}[#label-The+Routing+Tree]
13
- - {Matchers}[#label-Matchers]
14
- - {Optional segments}[#label-Optional+segments]
15
- - {Match/Route Block Return Values}[#label-Match-2FRoute+Block+Return+Values]
16
- - {Status codes}[#label-Status+Codes]
17
- - {Verb methods}[#label-Verb+Methods]
18
- - {Root method}[#label-Root+Method]
19
- - {Request and Response}[#label-Request+and+Response]
20
- - {Pollution}[#label-Pollution]
21
- - {Composition}[#label-Composition]
22
- - {Testing}[#label-Testing]
23
- - {Settings}[#label-Settings]
24
- - {Rendering}[#label-Rendering]
25
- - {Security}[#label-Security]
26
- - {Code Reloading}[#label-Code+Reloading]
27
- - {Plugins}[#label-Plugins]
28
- - {No introspection}[#label-No+Introspection]
29
- - {Inspiration}[#label-Inspiration]
30
- - {Ruby Support Policy}[#label-Ruby+Support+Policy]
31
-
32
- == Installation
33
-
34
- $ gem install roda
35
-
36
- == Resources
37
-
38
- Website :: http://roda.jeremyevans.net
39
- Source :: http://github.com/jeremyevans/roda
40
- Bugs :: http://github.com/jeremyevans/roda/issues
41
- Discussion Forum (GitHub Discussions) :: https://github.com/jeremyevans/roda/discussions
42
- Alternate Discussion Forum (Google Group) :: http://groups.google.com/group/ruby-roda
43
-
44
- == Goals
45
-
46
- * Simplicity
47
- * Reliability
48
- * Extensibility
49
- * Performance
50
-
51
- === Simplicity
52
-
53
- Roda is designed to be simple, both internally and externally.
54
- It uses a routing tree to enable you to write simpler and DRYer
55
- code.
56
-
57
- === Reliability
58
-
59
- Roda supports and encourages immutability. Roda apps are designed
60
- to be frozen in production, which eliminates possible thread safety issues.
61
- Additionally, Roda limits the instance variables, constants, and
62
- methods that it uses, so that they do not conflict with the ones
63
- you use for your application.
64
-
65
- === Extensibility
66
-
67
- Roda is built completely out of plugins, which makes it very
68
- extensible. You can override any part of Roda and call super
69
- to get the default behavior.
70
-
71
- === Performance
72
-
73
- Roda has low per-request overhead, and the use of a routing tree
74
- and intelligent caching of internal datastructures makes it
75
- significantly faster than other popular ruby web frameworks.
76
-
77
- == Usage
78
-
79
- Here's a simple application, showing how the routing tree works:
80
-
81
- # cat config.ru
82
- require "roda"
83
-
84
- class App < Roda
85
- route do |r|
86
- # GET / request
87
- r.root do
88
- r.redirect "/hello"
89
- end
90
-
91
- # /hello branch
92
- r.on "hello" do
93
- # Set variable for all routes in /hello branch
94
- @greeting = 'Hello'
95
-
96
- # GET /hello/world request
97
- r.get "world" do
98
- "#{@greeting} world!"
99
- end
100
-
101
- # /hello request
102
- r.is do
103
- # GET /hello request
104
- r.get do
105
- "#{@greeting}!"
106
- end
107
-
108
- # POST /hello request
109
- r.post do
110
- puts "Someone said #{@greeting}!"
111
- r.redirect
112
- end
113
- end
114
- end
115
- end
116
- end
117
-
118
- run App.freeze.app
119
-
120
- Here's a breakdown of what is going on in the block above:
121
-
122
- The +route+ block is called whenever a new request comes in.
123
- It is yielded an instance of a subclass of <tt>Rack::Request</tt>
124
- with some additional methods for matching routes.
125
- By convention, this argument should be named +r+.
126
-
127
- The primary way routes are matched in Roda is by calling
128
- +r.on+, +r.is+, +r.root+, +r.get+, or +r.post+.
129
- Each of these "routing methods" takes a "match block".
130
-
131
- Each routing method takes each of the arguments (called matchers)
132
- that are given and tries to match it to the current request.
133
- If the method is able to match all of the arguments, it yields to the match block;
134
- otherwise, the block is skipped and execution continues.
135
-
136
- - +r.on+ matches if all of the arguments match.
137
- - +r.is+ matches if all of the arguments match and there are no
138
- further entries in the path after matching.
139
- - +r.get+ matches any +GET+ request when called without arguments.
140
- - +r.get+ (when called with any arguments) matches only if the
141
- current request is a +GET+ request and there are no further entries
142
- in the path after matching.
143
- - +r.root+ only matches a +GET+ request where the current path is +/+.
144
-
145
- If a routing method matches and control is yielded to the match block,
146
- whenever the match block returns, Roda will return the Rack response array
147
- (containing status, headers, and body) to the caller.
148
-
149
- If the match block returns a string
150
- and the response body hasn't already been written to,
151
- the block return value will be interpreted as the body for the response.
152
- If none of the routing methods match and the route block returns a string,
153
- it will be interpreted as the body for the response.
154
-
155
- +r.redirect+ immediately returns the response,
156
- allowing for code such as <tt>r.redirect(path) if some_condition</tt>.
157
- If +r.redirect+ is called without arguments
158
- and the current request method is not +GET+, it redirects to the current path.
159
-
160
- The +.freeze.app+ at the end is optional. Freezing the app makes modifying
161
- app-level settings raise an error, alerting you to possible thread-safety issues
162
- in your application. It is recommended to freeze the app in production and
163
- during testing. The +.app+ is an optimization, which saves a few method calls
164
- for every request.
165
-
166
- == Running the Application
167
-
168
- Running a Roda application is similar to running any other rack-based application
169
- that uses a +config.ru+ file. You can start a basic server using +rackup+, +puma+,
170
- +unicorn+, +passenger+, or any other webserver that can handle +config.ru+ files:
171
-
172
- $ rackup
173
-
174
- == The Routing Tree
175
-
176
- Roda is called a routing tree web toolkit because the way most sites are structured,
177
- routing takes the form of a tree (based on the URL structure of the site).
178
- In general:
179
-
180
- - +r.on+ is used to split the tree into different branches.
181
- - +r.is+ finalizes the routing path.
182
- - +r.get+ and +r.post+ handle specific request methods.
183
-
184
- So, a simple routing tree might look something like this:
185
-
186
- r.on "a" do # /a branch
187
- r.on "b" do # /a/b branch
188
- r.is "c" do # /a/b/c request
189
- r.get do end # GET /a/b/c request
190
- r.post do end # POST /a/b/c request
191
- end
192
- r.get "d" do end # GET /a/b/d request
193
- r.post "e" do end # POST /a/b/e request
194
- end
195
- end
196
-
197
- It's also possible to handle the same requests,
198
- but structure the routing tree by first branching on the request method:
199
-
200
- r.get do # GET
201
- r.on "a" do # GET /a branch
202
- r.on "b" do # GET /a/b branch
203
- r.is "c" do end # GET /a/b/c request
204
- r.is "d" do end # GET /a/b/d request
205
- end
206
- end
207
- end
208
-
209
- r.post do # POST
210
- r.on "a" do # POST /a branch
211
- r.on "b" do # POST /a/b branch
212
- r.is "c" do end # POST /a/b/c request
213
- r.is "e" do end # POST /a/b/e request
214
- end
215
- end
216
- end
217
-
218
- This allows you to easily separate your +GET+ request handling
219
- from your +POST+ request handling.
220
- If you only have a small number of +POST+ request URLs
221
- and a large number of +GET+ request URLs, this may make things easier.
222
-
223
- However, routing first by the path and last by the request method
224
- is likely to lead to simpler and DRYer code.
225
- This is because you can act on the request at any point during the routing.
226
- For example, if all requests in the +/a+ branch need access permission +A+
227
- and all requests in the +/a/b+ branch need access permission +B+,
228
- you can easily handle this in the routing tree:
229
-
230
- r.on "a" do # /a branch
231
- check_perm(:A)
232
- r.on "b" do # /a/b branch
233
- check_perm(:B)
234
- r.is "c" do # /a/b/c request
235
- r.get do end # GET /a/b/c request
236
- r.post do end # POST /a/b/c request
237
- end
238
- r.get "d" do end # GET /a/b/d request
239
- r.post "e" do end # POST /a/b/e request
240
- end
241
- end
242
-
243
- Being able to operate on the request at any point during the routing
244
- is one of the major advantages of Roda.
245
-
246
- == Matchers
247
-
248
- Other than +r.root+, the routing methods all take arguments called matchers.
249
- If all of the matchers match, the routing method yields to the match block.
250
- Here's an example showcasing how different matchers work:
251
-
252
- class App < Roda
253
- route do |r|
254
- # GET /
255
- r.root do
256
- "Home"
257
- end
258
-
259
- # GET /about
260
- r.get "about" do
261
- "About"
262
- end
263
-
264
- # GET /post/2011/02/16/hello
265
- r.get "post", Integer, Integer, Integer, String do |year, month, day, slug|
266
- "#{year}-#{month}-#{day} #{slug}" #=> "2011-02-16 hello"
267
- end
268
-
269
- # GET /username/foobar branch
270
- r.on "username", String, method: :get do |username|
271
- user = User.find_by_username(username)
272
-
273
- # GET /username/foobar/posts
274
- r.is "posts" do
275
- # You can access user here, because the blocks are closures.
276
- "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
277
- end
278
-
279
- # GET /username/foobar/following
280
- r.is "following" do
281
- user.following.size.to_s #=> "1301"
282
- end
283
- end
284
-
285
- # /search?q=barbaz
286
- r.get "search" do
287
- "Searched for #{r.params['q']}" #=> "Searched for barbaz"
288
- end
289
-
290
- r.is "login" do
291
- # GET /login
292
- r.get do
293
- "Login"
294
- end
295
-
296
- # POST /login?user=foo&password=baz
297
- r.post do
298
- "#{r.params['user']}:#{r.params['password']}" #=> "foo:baz"
299
- end
300
- end
301
- end
302
- end
303
-
304
- Here's a description of the matchers.
305
- Note that "segment", as used here, means one part of the path preceded by a +/+.
306
- So, a path such as +/foo/bar//baz+ has four segments: +/foo+, +/bar+, +/+, and +/baz+.
307
- The +/+ here is considered the empty segment.
308
-
309
- === String
310
-
311
- If a string does not contain a slash, it matches a single segment
312
- containing the text of the string, preceded by a slash.
313
-
314
- "" # matches "/"
315
- "foo" # matches "/foo"
316
- "foo" # does not match "/food"
317
-
318
- If a string contains any slashes, it matches one additional segment for each slash:
319
-
320
- "foo/bar" # matches "/foo/bar"
321
- "foo/bar" # does not match "/foo/bard"
322
-
323
- === Regexp
324
-
325
- Regexps match one or more segments by looking for the pattern,
326
- preceded by a slash, and followed by a slash or the end of the path:
327
-
328
- /foo\w+/ # matches "/foobar"
329
- /foo\w+/ # does not match "/foo/bar"
330
- /foo/i # matches "/foo", "/Foo/"
331
- /foo/i # does not match "/food"
332
-
333
- If any patterns are captured by the Regexp, they are yielded:
334
-
335
- /foo\w+/ # matches "/foobar", yields nothing
336
- /foo(\w+)/ # matches "/foobar", yields "bar"
337
-
338
- === Class
339
-
340
- There are two classes that are supported as matchers, String
341
- and Integer.
342
-
343
- String :: matches any non-empty segment, yielding the segment except for
344
- the preceding slash
345
- Integer :: matches any segment of 0-9, returns matched values as integers
346
-
347
- Using String and Integer is the recommended way to handle
348
- arbitrary segments
349
-
350
- String # matches "/foo", yields "foo"
351
- String # matches "/1", yields "1"
352
- String # does not match "/"
353
-
354
- Integer # does not match "/foo"
355
- Integer # matches "/1", yields 1
356
- Integer # does not match "/"
357
-
358
- === Symbol
359
-
360
- Symbols match any nonempty segment,
361
- yielding the segment except for the preceding slash:
362
-
363
- :id # matches "/foo" yields "foo"
364
- :id # does not match "/"
365
-
366
- Symbol matchers operate the same as the class String matcher,
367
- and is the historical way to do arbitrary segment matching.
368
- It is recommended to use the class String matcher in new code
369
- as it is a bit more intuitive.
370
-
371
- === Proc
372
-
373
- Procs match unless they return false or nil:
374
-
375
- proc{true} # matches anything
376
- proc{false} # does not match anything
377
-
378
- Procs don't capture anything by default,
379
- but they can do so if you add the captured text to +r.captures+.
380
-
381
- === Arrays
382
-
383
- Arrays match when any of their elements match.
384
- If multiple matchers are given to +r.on+, they all must match (an AND condition).
385
- If an array of matchers is given, only one needs to match (an OR condition).
386
- Evaluation stops at the first matcher that matches.
387
-
388
- Additionally, if the matched object is a String, the string is yielded.
389
- This makes it easy to handle multiple strings without a Regexp:
390
-
391
- ['page1', 'page2'] # matches "/page1", "/page2"
392
- [] # does not match anything
393
-
394
- === Hash
395
-
396
- Hashes allow easily calling specialized match methods on the request.
397
- The default registered matchers included with Roda are documented below.
398
- Some plugins add additional hash matchers, and the hash_matcher plugin
399
- allows for easily defining your own:
400
-
401
- class App < Roda
402
- plugin :hash_matcher
403
-
404
- hash_matcher(:foo) do |v|
405
- # ...
406
- end
407
-
408
- route do |r|
409
- r.on foo: 'bar' do
410
- # ...
411
- end
412
- end
413
- end
414
-
415
- ==== :all
416
-
417
- The +:all+ matcher matches if all of the entries in the given array match, so
418
-
419
- r.on all: [String, String] do
420
- # ...
421
- end
422
-
423
- is the same as:
424
-
425
- r.on String, String do
426
- # ...
427
- end
428
-
429
- The reason it also exists as a separate hash matcher
430
- is so you can use it inside an array matcher, so:
431
-
432
- r.on ['foo', {all: ['foos', Integer]}] do
433
- end
434
-
435
- would match +/foo+ and +/foos/10+, but not +/foos+.
436
-
437
- ==== :method
438
-
439
- The +:method+ matcher matches the method of the request.
440
- You can provide an array to specify multiple request methods and match on any of them:
441
-
442
- {method: :post} # matches POST
443
- {method: ['post', 'patch']} # matches POST and PATCH
444
-
445
- === true
446
-
447
- If +true+ is given directly as a matcher, it always matches.
448
-
449
- === false, nil
450
-
451
- If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.
452
-
453
- === Everything else
454
-
455
- Everything else raises an error, unless support is specifically added for it
456
- (some plugins add support for additional matcher types).
457
-
458
- == Optional segments
459
-
460
- There are multiple ways you can handle optional segments in Roda. For example,
461
- let's say you want to accept both +/items/123+ and +/items/123/456+, with 123 being
462
- the item's id, and 456 being some optional data.
463
-
464
- The simplest way to handle this is by treating this as two separate routes with a
465
- shared branch:
466
-
467
- r.on "items", Integer do |item_id|
468
- # Shared code for branch here
469
-
470
- # /items/123/456
471
- r.is Integer do |optional_data|
472
- end
473
-
474
- # /items/123
475
- r.is do
476
- end
477
- end
478
-
479
- This works well for many cases, but there are also cases where you really want to
480
- treat it as one route with an optional segment. One simple way to do that is to
481
- use a parameter instead of an optional segment (e.g. <tt>/items/123?opt=456</tt>).
482
-
483
- r.is "items", Integer do |item_id|
484
- optional_data = r.params['opt'].to_s
485
- end
486
-
487
- However, if you really do want to use a optional segment, there are a couple different
488
- ways to use matchers to do so. One is using an array matcher where the last element
489
- is true:
490
-
491
- r.is "items", Integer, [String, true] do |item_id, optional_data|
492
- end
493
-
494
- Note that this technically yields only one argument instead of two arguments if the
495
- optional segment isn't provided.
496
-
497
- An alternative way to implement this is via a regexp:
498
-
499
- r.is "items", /(\d+)(?:\/(\d+))?/ do |item_id, optional_data|
500
- end
501
-
502
- == Match/Route Block Return Values
503
-
504
- If the response body has already been written to by calling +response.write+
505
- directly, then any return value of a match block or route block is ignored.
506
-
507
- If the response body has not already been written to, then the match block
508
- or route block return value is inspected:
509
-
510
- String :: used as the response body
511
- nil, false :: ignored
512
- everything else :: raises an error
513
-
514
- Plugins can add support for additional match block and route block return
515
- values. One example of this is the json plugin, which allows returning
516
- arrays and hashes in match and route blocks and converts those directly
517
- to JSON and uses the JSON as the response body.
518
-
519
- == Status Codes
520
-
521
- When it comes time to finalize a response,
522
- if a status code has not been set manually and anything has been written to the response,
523
- the response will use a 200 status code.
524
- Otherwise, it will use a 404 status code.
525
- This enables the principle of least surprise to work:
526
- if you don't handle an action, a 404 response is assumed.
527
-
528
- You can always set the status code manually,
529
- via the +status+ attribute for the response.
530
-
531
- route do |r|
532
- r.get "hello" do
533
- response.status = 200
534
- end
535
- end
536
-
537
- When redirecting, the response will use a 302 status code by default.
538
- You can change this by passing a second argument to +r.redirect+:
539
-
540
- route do |r|
541
- r.get "hello" do
542
- r.redirect "/other", 301 # use 301 Moved Permanently
543
- end
544
- end
545
-
546
- == Verb Methods
547
-
548
- As displayed above, Roda has +r.get+ and +r.post+ methods
549
- for matching based on the HTTP request method. If you want
550
- to match on other HTTP request methods, use the all_verbs
551
- plugin.
552
-
553
- When called without any arguments, these match as long
554
- as the request has the appropriate method, so:
555
-
556
- r.get do end
557
-
558
- matches any +GET+ request, and
559
-
560
- r.post do end
561
-
562
- matches any +POST+ request
563
-
564
- If any arguments are given to the method, these match only
565
- if the request method matches, all arguments match, and
566
- the path has been fully matched by the arguments, so:
567
-
568
- r.post "" do end
569
-
570
- matches only +POST+ requests where the current path is +/+.
571
-
572
- r.get "a/b" do end
573
-
574
- matches only +GET+ requests where the current path is +/a/b+.
575
-
576
- The reason for this difference in behavior is that
577
- if you are not providing any arguments, you probably don't want
578
- to also test for an exact match with the current path.
579
- If that is something you do want, you can provide +true+ as an argument:
580
-
581
- r.on "foo" do
582
- r.get true do # Matches GET /foo, not GET /foo/.*
583
- end
584
- end
585
-
586
- If you want to match the request method
587
- and do only a partial match on the request path,
588
- you need to use +r.on+ with the <tt>:method</tt> hash matcher:
589
-
590
- r.on "foo", method: :get do # Matches GET /foo(/.*)?
591
- end
592
-
593
- == Root Method
594
-
595
- As displayed above, you can also use +r.root+ as a match method.
596
- This method matches +GET+ requests where the current path is +/+.
597
- +r.root+ is similar to <tt>r.get ""</tt>,
598
- except that it does not consume the +/+ from the path.
599
-
600
- Unlike the other matching methods, +r.root+ takes no arguments.
601
-
602
- Note that +r.root+ does not match if the path is empty;
603
- you should use <tt>r.get true</tt> for that.
604
- If you want to match either the empty path or +/+,
605
- you can use <tt>r.get ["", true]</tt>, or use the slash_path_empty
606
- plugin.
607
-
608
- Note that +r.root+ only matches +GET+ requests.
609
- So, to handle <tt>POST /</tt> requests, use <tt>r.post ''</tt>.
610
-
611
- == Request and Response
612
-
613
- While the request object is yielded to the +route+ block,
614
- it is also available via the +request+ method.
615
- Likewise, the response object is available via the +response+ method.
616
-
617
- The request object is an instance of a subclass of <tt>Rack::Request</tt>,
618
- with some additional methods.
619
-
620
- If you want to extend the request and response objects with additional modules,
621
- you can use the module_include plugin.
622
-
623
- == Pollution
624
-
625
- Roda tries very hard to avoid polluting the scope of the +route+ block.
626
- This should make it unlikely that Roda will cause namespace issues
627
- with your application code. Some of the things Roda does:
628
-
629
- - The only instance variables defined by default in the scope of the +route+ block
630
- are <tt>@_request</tt> and <tt>@_response</tt>. All instance variables in the
631
- scope of the +route+ block used by plugins that ship with Roda are prefixed
632
- with an underscore.
633
- - The main methods defined, beyond the default methods for +Object+, are
634
- +env+, +opts+, +request+, +response+, and +session+. +call+ and +_call+ are also
635
- defined, but are deprecated. All other methods defined are prefixed with +_roda_+
636
- - Constants inside the Roda namespace are all prefixed with +Roda+
637
- (e.g., <tt>Roda::RodaRequest</tt>).
638
-
639
- == Composition
640
-
641
- You can mount any Rack app (including another Roda app), with its own middlewares,
642
- inside a Roda app, using +r.run+:
643
-
644
- class API < Roda
645
- route do |r|
646
- r.is do
647
- # ...
648
- end
649
- end
650
- end
651
-
652
- class App < Roda
653
- route do |r|
654
- r.on "api" do
655
- r.run API
656
- end
657
- end
658
- end
659
-
660
- run App.app
661
-
662
- This will take any path starting with +/api+ and send it to +API+.
663
- In this example, +API+ is a Roda app, but it could easily be
664
- a Sinatra, Rails, or other Rack app.
665
-
666
- When you use +r.run+, Roda calls the given Rack app (+API+ in this case);
667
- whatever the Rack app returns will be returned
668
- as the response for the current application.
669
-
670
- If you have a lot of rack applications that you want to dispatch to, and
671
- which one to dispatch to is based on the request path prefix, look into the
672
- +multi_run+ plugin.
673
-
674
- === hash_branches plugin
675
-
676
- If you are just looking to split up the main route block up by branches,
677
- you should use the +hash_branches+ plugin,
678
- which keeps the current scope of the +route+ block:
679
-
680
- class App < Roda
681
- plugin :hash_branches
682
-
683
- hash_branch "api" do |r|
684
- r.is do
685
- # ...
686
- end
687
- end
688
-
689
- route do |r|
690
- r.hash_branches
691
- end
692
- end
693
-
694
- run App.app
695
-
696
- This allows you to set instance variables in the main +route+ block
697
- and still have access to them inside the +api+ +route+ block.
698
-
699
- == Testing
700
-
701
- It is very easy to test Roda with {Rack::Test}[https://github.com/rack-test/rack-test]
702
- or {Capybara}[https://github.com/teamcapybara/capybara].
703
- Roda's own tests use {minitest/spec}[https://github.com/seattlerb/minitest].
704
- The default Rake task will run the specs for Roda.
705
-
706
- == Settings
707
-
708
- Each Roda app can store settings in the +opts+ hash.
709
- The settings are inherited by subclasses.
710
-
711
- Roda.opts[:layout] = "guest"
712
-
713
- class Users < Roda; end
714
- class Admin < Roda
715
- opts[:layout] = "admin"
716
- end
717
-
718
- Users.opts[:layout] # => 'guest'
719
- Admin.opts[:layout] # => 'admin'
720
-
721
- Feel free to store whatever you find convenient.
722
- Note that when subclassing, Roda only does a shallow clone of the settings.
723
-
724
- If you store nested structures and plan to mutate them in subclasses,
725
- it is your responsibility to dup the nested structures inside +Roda.inherited+
726
- (making sure to call +super+). This should be is done so that modifications
727
- to the parent class made after subclassing do _not_ affect the subclass, and
728
- vice-versa.
729
-
730
- The plugins that ship with Roda freeze their settings and only allow modification
731
- to their settings by reloading the plugin, and external plugins are encouraged
732
- to follow this approach.
733
-
734
- The following options are respected by the default library or multiple plugins:
735
-
736
- :add_script_name :: Prepend the SCRIPT_NAME for the request to paths. This is
737
- useful if you mount the app as a path under another app.
738
- :check_arity :: Whether arity for blocks passed to Roda should be checked
739
- to determine if they can be used directly to define methods
740
- or need to be wrapped. By default, for backwards compatibility,
741
- this is true, so Roda will check blocks and handle cases where
742
- the arity of the block does not match the expected arity. This
743
- can be set to +:warn+ to issue warnings whenever Roda detects an
744
- arity mismatch. If set to +false+, Roda does not check the arity
745
- of blocks, which can result in failures at runtime if the arity
746
- of the block does not match what Roda expects. Note that Roda
747
- does not check the arity for lambda blocks, as those are strict
748
- by default.
749
- :check_dynamic_arity :: Similar to :check_arity, but used for checking blocks
750
- where the number of arguments Roda will call the blocks
751
- with is not possible to determine when defining the
752
- method. By default, Roda checks arity for such methods,
753
- but doing so actually slows the method down even if the
754
- number of arguments matches the expected number of arguments.
755
- :freeze_middleware :: Whether to freeze all middleware when building the rack app.
756
- :json_parser :: A callable for parsing JSON (+JSON.parse+ in general used by
757
- default).
758
- :json_serializer :: A callable for serializing JSON (+to_json+ in general used
759
- by default).
760
- :root :: Set the root path for the app. This defaults to the current working
761
- directory of the process.
762
- :sessions_convert_symbols :: This should be set to +true+ if the sessions in use
763
- do not support roundtripping of symbols (for
764
- example, when sessions are serialized via JSON).
765
-
766
- There may be other options supported by individual plugins, if so it will be
767
- mentioned in the documentation for the plugin.
768
-
769
- == Rendering
770
-
771
- Roda ships with a +render+ plugin that provides helpers for rendering templates.
772
- It uses {Tilt}[https://github.com/rtomayko/tilt],
773
- a gem that interfaces with many template engines.
774
- The +erb+ engine is used by default.
775
-
776
- Note that in order to use this plugin you need to have Tilt installed,
777
- along with the templating engines you want to use.
778
-
779
- This plugin adds the +render+ and +view+ methods, for rendering templates.
780
- By default, +view+ will render the template inside the default layout template;
781
- +render+ will just render the template.
782
-
783
- class App < Roda
784
- plugin :render
785
-
786
- route do |r|
787
- @var = '1'
788
-
789
- r.get "render" do
790
- # Renders the views/home.erb template, which will have access to
791
- # the instance variable @var, as well as local variable content.
792
- render("home", locals: {content: "hello, world"})
793
- end
794
-
795
- r.get "view" do
796
- @var2 = '1'
797
-
798
- # Renders the views/home.erb template, which will have access to the
799
- # instance variables @var and @var2, and takes the output of that and
800
- # renders it inside views/layout.erb (which should yield where the
801
- # content should be inserted).
802
- view("home")
803
- end
804
- end
805
- end
806
-
807
- You can override the default rendering options by passing a hash to the plugin:
808
-
809
- class App < Roda
810
- plugin :render,
811
- escape: true, # Automatically escape output in erb templates using Erubi's escaping support
812
- views: 'admin_views', # Default views directory
813
- layout_opts: {template: 'admin_layout', engine: 'html.erb'}, # Default layout options
814
- template_opts: {default_encoding: 'UTF-8'} # Default template options
815
- end
816
-
817
- == Security
818
-
819
- Web application security is a very large topic,
820
- but here are some things you can do with Roda
821
- to prevent some common web application vulnerabilities.
822
-
823
- === Session Security
824
-
825
- By default, Roda doesn't turn on sessions, and if you don't need sessions, you can
826
- skip this section. If you do need sessions, Roda offers two recommended ways to
827
- implement cookie-based sessions.
828
-
829
- If you do not need any session support in middleware, and only need session support
830
- in the Roda application, then use the sessions plugin:
831
-
832
- require 'roda'
833
- class App < Roda
834
- plugin :sessions, secret: ENV['SESSION_SECRET']
835
- end
836
-
837
- The +:secret+ option should be a randomly generated string of at least 64 bytes.
838
-
839
- If you have middleware that need access to sessions, then use the +RodaSessionMiddleware+
840
- that ships with Roda:
841
-
842
- require 'roda'
843
- require 'roda/session_middleware'
844
- class App < Roda
845
- use RodaSessionMiddleware, secret: ENV['SESSION_SECRET']
846
- end
847
-
848
- If you need non-cookie based sessions (such as sessions stored in a database), you
849
- should use an appropriate external middleware.
850
-
851
- It is possible to use other session cookie middleware such as
852
- <tt>Rack::Session::Cookie</tt>, but other middleware may not have the same security
853
- features that Roda's session support does. For example, the session cookies used by
854
- the <tt>Rack::Session::Cookie</tt> middleware provided by Rack before Rack 3 are not
855
- encrypted, just signed to prevent tampering.
856
-
857
- For any cookie-based sessions, make sure that the necessary secrets (+:secret+ option)
858
- are not disclosed to an attacker. Knowledge of the
859
- secret(s) can allow an attacker to inject arbitrary session values. In the case of
860
- <tt>Rack::Session::Cookie</tt>, that can also lead remote code execution.
861
-
862
- === Cross Site Request Forgery (CSRF)
863
-
864
- CSRF can be prevented by using the +route_csrf+ plugin that ships with Roda.
865
- The +route_csrf+ plugin uses modern security practices to create CSRF tokens,
866
- requires request-specific tokens by default, and offers control to the user
867
- over where in the routing tree that CSRF tokens are checked. For example, if
868
- you are using the +public+ plugin to serve static files and the +assets+
869
- plugin to serve assets, you wouldn't need to check for CSRF tokens for either
870
- of those, so you could put the CSRF check after those in the routing tree,
871
- but before handling other requests:
872
-
873
- route do |r|
874
- r.public
875
- r.assets
876
-
877
- check_csrf! # Must call this to check for valid CSRF tokens
878
-
879
- # ...
880
- end
881
-
882
-
883
- === Cross Site Scripting (XSS)
884
-
885
- The easiest way to prevent XSS with Roda is to use a template library
886
- that automatically escapes output by default.
887
- The +:escape+ option to the +render+ plugin sets the ERB template processor
888
- to escape by default, so that in your templates:
889
-
890
- <%= '<>' %> # outputs &lt;&gt;
891
- <%== '<>' %> # outputs <>
892
-
893
- When using the +:escape+ option, you will need to ensure that your layouts
894
- are not escaping the output of the content template:
895
-
896
- <%== yield %> # not <%= yield %>
897
-
898
- This support requires {Erubi}[https://github.com/jeremyevans/erubi].
899
-
900
- === Unexpected Parameter Types
901
-
902
- Rack converts submitted parameters into a hash of strings, arrays, and
903
- nested hashes. Since the user controls the submission of parameters, you
904
- should treat any submission of parameters with caution, and should be
905
- explicitly checking and/or converting types before using any submitted
906
- parameters. One way to do this is explicitly after accessing them:
907
-
908
- # Convert foo_id parameter to an integer
909
- request.params['foo_id'].to_i
910
-
911
- However, it is easy to forget to convert the type, and if the user
912
- submits +foo_id+ as a hash or array, a NoMethodError will be raised.
913
- Worse is if you do:
914
-
915
- some_method(request.params['bar'])
916
-
917
- Where +some_method+ supports both a string argument and a hash
918
- argument, and you expect the parameter will be submitted as a
919
- string, and +some_method+'s handling of a hash argument performs
920
- an unauthorized action.
921
-
922
- Roda ships with a +typecast_params+ plugin that can easily handle
923
- the typecasting of submitted parameters, and it is recommended
924
- that all Roda applications that deal with parameters use it or
925
- another tool to explicitly convert submitted parameters to the
926
- expected types.
927
-
928
- === Content Security Policy
929
-
930
- The Content-Security-Policy HTTP header can be used to instruct
931
- the browser on what types of content to allow and where content
932
- can be loaded from. Roda ships with a +content_security_policy+
933
- plugin that allows for the easy configuration of the content
934
- security policy. Here's an example of a fairly restrictive
935
- content security policy configuration:
936
-
937
- class App < Roda
938
- plugin :content_security_policy do |csp|
939
- csp.default_src :none # deny everything by default
940
- csp.style_src :self
941
- csp.script_src :self
942
- csp.connect_src :self
943
- csp.img_src :self
944
- csp.font_src :self
945
- csp.form_action :self
946
- csp.base_uri :none
947
- csp.frame_ancestors :none
948
- csp.block_all_mixed_content
949
- csp.report_uri 'CSP_REPORT_URI'
950
- end
951
- end
952
-
953
- === Other Security Related HTTP Headers
954
-
955
- You may want to look into setting the following HTTP headers, which
956
- can be done at the web server level, but can also be done at the
957
- application level using using the +default_headers+ plugin:
958
-
959
- Strict-Transport-Security :: Enforces SSL/TLS Connections to the application.
960
- X-Content-Type-Options :: Forces some browsers to respect a declared Content-Type header.
961
- X-Frame-Options :: Provides click-jacking protection by not allowing usage inside a frame.
962
- Only include this if you want to support and protect old browsers that
963
- do not support Content-Security-Policy.
964
-
965
- Example:
966
-
967
- class App < Roda
968
- plugin :default_headers,
969
- 'Content-Type'=>'text/html',
970
- 'Strict-Transport-Security'=>'max-age=63072000; includeSubDomains',
971
- 'X-Content-Type-Options'=>'nosniff',
972
- 'X-Frame-Options'=>'deny'
973
- end
974
-
975
- === Rendering Templates Derived From User Input
976
-
977
- Roda's rendering plugin by default checks that rendered templates are inside the views
978
- directory. This is because rendering templates outside the views directory is not
979
- commonly needed, and it prevents a common attack (which is especially severe if there is any
980
- location on the file system that users can write files to).
981
-
982
- You can specify which directories are allowed using the +:allowed_paths+ render plugin
983
- option. If you really want to turn path checking off, you can do so via the
984
- <tt>check_paths: false</tt> render plugin option.
985
-
986
- == Code Reloading
987
-
988
- Roda does not ship with integrated support for code reloading, but there are rack-based
989
- reloaders that will work with Roda apps.
990
-
991
- {Zeitwerk}[https://github.com/fxn/zeitwerk] (which Rails now uses for reloading) can be used
992
- with Roda. It requires minimal setup and handles most cases. It overrides +require+ when
993
- activated. If it can meet the needs of your application, it's probably the best approach.
994
-
995
- {rack-unreloader}[https://github.com/jeremyevans/rack-unreloader] uses a fast
996
- approach to reloading while still being fairly safe, as it only reloads files that have
997
- been modified, and unloads constants defined in the files before reloading them. It can handle
998
- advanced cases that Zeitwerk does not support, such as classes defined in multiple files
999
- (common when using separate route files for different routing branches in the same application).
1000
- However, rack-unreloader does not modify core classes and using it requires modifying your
1001
- application code to use rack-unreloader specific APIs, which may not be simple.
1002
-
1003
- {AutoReloader}[https://github.com/rosenfeld/auto_reloader] provides transparent reloading for
1004
- all files reached from one of the +reloadable_paths+ option entries, by detecting new top-level
1005
- constants and removing them when any of the reloadable loaded files changes. It overrides
1006
- +require+ and +require_relative+ when activated (usually in the development environment). No
1007
- configurations other than +reloadable_paths+ are required.
1008
-
1009
- {rerun}[https://github.com/alexch/rerun] uses a fork/exec approach for loading new
1010
- versions of your app. It work without any changes to application
1011
- code, but may be slower as they have to reload the entire application on every change.
1012
- However, for small apps that load quickly, it may be a good approach.
1013
-
1014
- There is no one reloading solution that is the best for all applications and development
1015
- approaches. Consider your needs and the tradeoffs of each of the reloading approaches,
1016
- and pick the one you think will work best. If you are unsure where to start,
1017
- it may be best to start with Zeitwerk, and only consider other options if it does not
1018
- work well for you.
1019
-
1020
- == Plugins
1021
-
1022
- By design, Roda has a very small core, providing only the essentials.
1023
- All nonessential features are added via plugins.
1024
-
1025
- Roda's plugins can override any Roda method and call +super+
1026
- to get the default behavior, which makes Roda very extensible.
1027
-
1028
- {Roda ships with a large number of plugins}[http://roda.jeremyevans.net/documentation.html#included-plugins],
1029
- and {some other libraries ship with support for Roda}[http://roda.jeremyevans.net/documentation.html#external].
1030
-
1031
- === How to create plugins
1032
-
1033
- Authoring your own plugins is pretty straightforward.
1034
- Plugins are just modules, which may contain any of the following modules:
1035
-
1036
- InstanceMethods :: module included in the Roda class
1037
- ClassMethods :: module that extends the Roda class
1038
- RequestMethods :: module included in the class of the request
1039
- RequestClassMethods :: module extending the class of the request
1040
- ResponseMethods :: module included in the class of the response
1041
- ResponseClassMethods :: module extending the class of the response
1042
-
1043
- If the plugin responds to +load_dependencies+, it will be called first,
1044
- and should be used if the plugin depends on another plugin.
1045
-
1046
- If the plugin responds to +configure+, it will be called last,
1047
- and should be used to configure the plugin.
1048
-
1049
- Both +load_dependencies+ and +configure+ are called
1050
- with the additional arguments and block that was given to the plugin call.
1051
-
1052
- So, a simple plugin to add an instance method would be:
1053
-
1054
- module MarkdownHelper
1055
- module InstanceMethods
1056
- def markdown(str)
1057
- BlueCloth.new(str).to_html
1058
- end
1059
- end
1060
- end
1061
-
1062
- Roda.plugin MarkdownHelper
1063
-
1064
- === Registering plugins
1065
-
1066
- If you want to ship a Roda plugin in a gem,
1067
- but still have Roda load it automatically via <tt>Roda.plugin :plugin_name</tt>,
1068
- you should place it where it can be required via +roda/plugins/plugin_name+
1069
- and then have the file register it as a plugin via
1070
- <tt>Roda::RodaPlugins.register_plugin</tt>.
1071
- It's recommended, but not required, that you store your plugin module
1072
- in the <tt>Roda::RodaPlugins</tt> namespace:
1073
-
1074
- class Roda
1075
- module RodaPlugins
1076
- module Markdown
1077
- module InstanceMethods
1078
- def markdown(str)
1079
- BlueCloth.new(str).to_html
1080
- end
1081
- end
1082
- end
1083
-
1084
- register_plugin :markdown, Markdown
1085
- end
1086
- end
1087
-
1088
- To avoid namespace pollution,
1089
- you should avoid creating your module directly in the +Roda+ namespace.
1090
- Additionally, any instance variables created inside +InstanceMethods+
1091
- should be prefixed with an underscore (e.g., <tt>@_variable</tt>)
1092
- to avoid polluting the scope. Finally, do not add any constants inside
1093
- the InstanceMethods module, add constants to the plugin module itself
1094
- (+Markdown+ in the above example).
1095
-
1096
- If you are planning on shipping your plugin in an external gem, it is recommended that you follow
1097
- {standard gem naming conventions for extensions}[http://guides.rubygems.org/name-your-gem/].
1098
- So if your plugin module is named +FooBar+, your gem name should be <tt>roda-foo_bar</tt>.
1099
-
1100
- == No Introspection
1101
-
1102
- Because a routing tree does not store the routes in a data structure, but
1103
- directly executes the routing tree block, you cannot introspect the routes
1104
- when using a routing tree.
1105
-
1106
- If you would like to introspect your routes when using Roda, there is an
1107
- external plugin named {roda-route_list}[https://github.com/jeremyevans/roda-route_list],
1108
- which allows you to add appropriate comments to your routing files, and
1109
- has a parser that will parse those comments into routing metadata that
1110
- you can then introspect.
1111
-
1112
- == Inspiration
1113
-
1114
- Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is].
1115
- It started out as a fork of Cuba, from which it borrows the idea of using a routing tree
1116
- (which Cuba in turn took from {Rum}[https://github.com/chneukirchen/rum]).
1117
- From Sinatra, it takes the ideas that route blocks should return the request bodies
1118
- and that routes should be canonical.
1119
- Roda's plugin system is based on the plugin system used by
1120
- {Sequel}[http://sequel.jeremyevans.net].
1121
-
1122
- == Ruby Support Policy
1123
-
1124
- Roda fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
1125
- support unsupported versions of Ruby or JRuby, but such support may be dropped in any
1126
- minor version if keeping it becomes a support issue. The minimum Ruby version
1127
- required to run the current version of Roda is 1.9.2, and the minimum JRuby version is
1128
- 9.0.0.0.
1129
-
1130
- == License
1131
-
1132
- MIT
1133
-
1134
- == Maintainer
1135
-
1136
- Jeremy Evans <code@jeremyevans.net>