roda 3.83.0 → 3.85.0

Sign up to get free protection for your applications and to get access to all the features.
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>