strelka 0.0.1.pre.295 → 0.0.1.pre.301
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.
- data.tar.gz.sig +0 -0
- data/ChangeLog +31 -2
- data/Deploying.rdoc +83 -0
- data/Manifest.txt +4 -0
- data/Plugins.rdoc +201 -0
- data/README.rdoc +108 -74
- data/Tutorial.rdoc +759 -0
- data/examples/strelka.conf.example +51 -0
- data/lib/strelka/cookie.rb +10 -7
- data/lib/strelka/paramvalidator.rb +6 -0
- data/spec/strelka/paramvalidator_spec.rb +25 -0
- metadata +19 -8
- metadata.gz.sig +1 -3
data/Tutorial.rdoc
ADDED
@@ -0,0 +1,759 @@
|
|
1
|
+
= Strelka Tutorial
|
2
|
+
|
3
|
+
== Configuration
|
4
|
+
|
5
|
+
Strelka uses a library called
|
6
|
+
Configurability[http://bitbucket.org/ged/configurability]
|
7
|
+
for configuration. It lets you put settings for everything into one
|
8
|
+
file, and then distribute the config sections where they belong when
|
9
|
+
the file is loaded.
|
10
|
+
|
11
|
+
It's used by several Strelka plugins, by the Inversion templating system,
|
12
|
+
and by the Mongrel2 library. The gem comes with an example config with
|
13
|
+
comments; see <tt>{gemdir}/examples/strelka.conf.example</tt>.
|
14
|
+
|
15
|
+
You can also dump out a config file populated with defaults to get you
|
16
|
+
started with the 'strelka' command:
|
17
|
+
|
18
|
+
$ strelka config > config.yml
|
19
|
+
|
20
|
+
If you start your apps via the command line tool, the config is loaded
|
21
|
+
for you (via the -c option), but you can also do it yourself if you
|
22
|
+
prefer launching your apps manually:
|
23
|
+
|
24
|
+
Strelka.load_config( "myconfig.yml" )
|
25
|
+
|
26
|
+
It's then available from the <tt>Strelka.config</tt> reader, allowing you
|
27
|
+
to reload it if it's changed (or whatever):
|
28
|
+
|
29
|
+
Strelka.config.reload if Strelka.config.changed?
|
30
|
+
|
31
|
+
See the {Configurability docs}[http://deveiate.org/code/configurabilty] for
|
32
|
+
more details.
|
33
|
+
|
34
|
+
|
35
|
+
== Creating Applications with Plugins
|
36
|
+
|
37
|
+
As mentioned in the {README page}[rdoc-ref:README.rdoc], our default
|
38
|
+
application doesn't afford us many conveniences over using a raw
|
39
|
+
<tt>Mongrel2::Handler</tt>. Strelka breaks most of its niceties into a plugin
|
40
|
+
system, so you can add only what you require, keeping your apps nice and
|
41
|
+
streamlined.
|
42
|
+
|
43
|
+
The plugins system is contained in the Strelka::PluginLoader mixin, which
|
44
|
+
is included in Strelka::App already. This adds the <tt>plugins</tt>
|
45
|
+
declarative, which you use from your app to load the plugins you want to use.
|
46
|
+
|
47
|
+
If you're interested in hooking into the HTTP conversation yourself, or just
|
48
|
+
want to factor your common application code up to a reusable plugin, you can
|
49
|
+
{Write Your Own Strelka Plugin}[rdoc-ref:Plugins.rdoc], too.
|
50
|
+
|
51
|
+
|
52
|
+
== Routing
|
53
|
+
|
54
|
+
Rather than checking the <tt>request.path</tt> and supplying elaborate
|
55
|
+
conditional dispatching yourself, you can use the +routing+ plugin. This
|
56
|
+
provides block style route declarations to your application, that execute their
|
57
|
+
blocks when a matching HTTP method and path URI is requested. This is similar
|
58
|
+
to {Sinatra's}[http://www.sinatrarb.com/] routing -- if you've used that
|
59
|
+
before, this should be familiar territory. It matches routes a bit differently,
|
60
|
+
though. Instead of top down, first match wins, Strelka's routing is more
|
61
|
+
similar to the routing algorithm that Mongrel2 uses. The longest, most-specific
|
62
|
+
route wins, regardless of where it was defined.
|
63
|
+
|
64
|
+
class HelloWorldApp < Strelka::App
|
65
|
+
|
66
|
+
plugins :routing
|
67
|
+
|
68
|
+
# match any GET request
|
69
|
+
get do |request|
|
70
|
+
request.response.content_type = 'text/plain'
|
71
|
+
request.response << 'Hello, World!'
|
72
|
+
return request.response
|
73
|
+
end
|
74
|
+
|
75
|
+
# match a GET request starting with '/goodbye'
|
76
|
+
get '/goodbye' do |request|
|
77
|
+
request.response.content_type = 'text/plain'
|
78
|
+
request.response << "Goodbye, cruel World!"
|
79
|
+
return request.response
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
The example app above responds only to 'GET' requests. Anything under the
|
84
|
+
<tt>/goodbye</tt> URI responds with a departure message, while any other
|
85
|
+
request (anywhere in the URI space!) responds with a friendly greeting. You can
|
86
|
+
think of this almost as wildcard routing, effectively dividing up the URI space
|
87
|
+
into individual handler blocks. A lack of a URI argument is the same thing as
|
88
|
+
declaring one with <tt>/</tt>.
|
89
|
+
|
90
|
+
This introduces some important concepts, as well. All blocks are passed the
|
91
|
+
Strelka::HTTPRequest object and should return a Strelka::HTTPResponse object.
|
92
|
+
Both the request and response are wrappers around _streams_ (input and output,
|
93
|
+
respectively), similar to STDIN and STDOUT in a command-line utility. These
|
94
|
+
streams contain the *body* of the request and response, and can be any
|
95
|
+
<tt>IO</tt>-like object.
|
96
|
+
|
97
|
+
|
98
|
+
== More Explicit Routing
|
99
|
+
|
100
|
+
Sometimes you might have an explicit URI mapping already in mind, or just don't
|
101
|
+
want the same HTTP resource potentially accessible under different URLs. The
|
102
|
+
+routing+ plugin has an option to alter its default matching behavior, and make
|
103
|
+
it only match routes that are requested specifically:
|
104
|
+
|
105
|
+
class HelloWorldApp < Strelka::App
|
106
|
+
|
107
|
+
plugins :routing
|
108
|
+
|
109
|
+
# Require exact matching for route paths
|
110
|
+
router :exclusive
|
111
|
+
|
112
|
+
# only match a GET request to '/'
|
113
|
+
get do |request|
|
114
|
+
request.response.content_type = 'text/plain'
|
115
|
+
request.response << 'Hello, World!'
|
116
|
+
return request.response
|
117
|
+
end
|
118
|
+
|
119
|
+
# only match a GET request for '/goodbye'
|
120
|
+
get '/goodbye' do |request|
|
121
|
+
request.response.content_type = 'text/plain'
|
122
|
+
request.response << "Goodbye, cruel World!"
|
123
|
+
return request.response
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
This application now serves requests to only <tt>/</tt> and
|
128
|
+
<tt>/goodbye</tt>. Anything else is met with a <tt>404 NOT FOUND</tt>
|
129
|
+
response.
|
130
|
+
|
131
|
+
|
132
|
+
== Setting Content-Types
|
133
|
+
|
134
|
+
You can, of course, explicitly set the content type for each route as
|
135
|
+
we've been doing above. If you'd like to have a fallback if one isn't
|
136
|
+
set, Strelka provides an easy way to do so:
|
137
|
+
|
138
|
+
class HelloWorldApp < Strelka::App
|
139
|
+
|
140
|
+
plugins :routing
|
141
|
+
|
142
|
+
# Unless explicitly stated, use a text/plain content-type.
|
143
|
+
default_type 'text/plain'
|
144
|
+
|
145
|
+
get do |request|
|
146
|
+
request.response << 'Hello, World!'
|
147
|
+
return request.response
|
148
|
+
end
|
149
|
+
|
150
|
+
get '/goodbye' do |request|
|
151
|
+
request.response << "Goodbye, cruel World!"
|
152
|
+
return request.response
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Now all content sent will have a <tt>text/plain</tt> content-type, unless
|
157
|
+
specifically set to something else in the response.
|
158
|
+
|
159
|
+
|
160
|
+
== Dealing with URI and Query Parameters
|
161
|
+
|
162
|
+
If you just want to retrieve passed query parameters directly, they are
|
163
|
+
accessible as a Hash via the <tt>Request</tt> object.
|
164
|
+
|
165
|
+
class HelloWorldApp < Strelka::App
|
166
|
+
|
167
|
+
plugins :routing
|
168
|
+
|
169
|
+
default_type 'text/plain'
|
170
|
+
|
171
|
+
get '/' do |request|
|
172
|
+
name = request.params['name'] || 'Handsome'
|
173
|
+
request.response << "Hello, #{name}!"
|
174
|
+
return request.response
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
Try passing <tt>/?name=there</tt> to the handler. By default, no
|
179
|
+
validity-checking is done; it'll just return the query arguments as
|
180
|
+
Strelka saw them.
|
181
|
+
|
182
|
+
Strelka offers a +parameters+ plugin that provides a framework for
|
183
|
+
describing parameters globally. It manages validation and untainting,
|
184
|
+
and you can override the global descriptions on a per-route basis.
|
185
|
+
|
186
|
+
class HelloWorldApp < Strelka::App
|
187
|
+
|
188
|
+
plugins :routing, :parameters
|
189
|
+
|
190
|
+
default_type 'text/plain'
|
191
|
+
|
192
|
+
param :name, /[[:alpha:]]+/
|
193
|
+
|
194
|
+
get '/' do |request|
|
195
|
+
name = request.params['name'] || 'Handsome'
|
196
|
+
request.response << "Hello, #{name}!"
|
197
|
+
return request.response
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
Loading the plugin gives you the +param+ method, which you use to
|
202
|
+
declare all of the global parameters your application might use. It
|
203
|
+
already has a bunch of built in matchers for common things like email
|
204
|
+
and hostnames. You can continue using the <tt>params</tt> attribute as a
|
205
|
+
Hash, but it now is much smarter than it was before:
|
206
|
+
|
207
|
+
class HelloWorldApp < Strelka::App
|
208
|
+
|
209
|
+
plugins :routing, :parameters
|
210
|
+
|
211
|
+
default_type 'text/plain'
|
212
|
+
|
213
|
+
param :name, :alpha, :required
|
214
|
+
param :email, 'An RFC822 email address'
|
215
|
+
|
216
|
+
get '/' do |request|
|
217
|
+
response = request.response
|
218
|
+
|
219
|
+
response.puts( request.params.inspect )
|
220
|
+
|
221
|
+
if request.params.okay?
|
222
|
+
response << "Hello, %s!\n" % [ request.params['name'] ]
|
223
|
+
else
|
224
|
+
response << "Aaahhhh noooaaahhhh!!\n"
|
225
|
+
request.params.error_messages.each do |err|
|
226
|
+
response.body << " - %s\n" % [ err ]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
return response
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
Passing the URL <tt>/?name=Bob</tt> should output the following:
|
235
|
+
|
236
|
+
1 parameters (1 valid, 0 invalid, 0 missing)
|
237
|
+
Hello, Bob!
|
238
|
+
|
239
|
+
While passing <tt>/?email=what</tt> should display:
|
240
|
+
|
241
|
+
1 parameters (0 valid, 1 invalid, 1 missing)
|
242
|
+
Aaahhhh noooaaahhhh!!
|
243
|
+
- Missing value for 'Name'
|
244
|
+
- Invalid value for 'An RFC822 email address'
|
245
|
+
|
246
|
+
Neat.
|
247
|
+
|
248
|
+
All this time, we've only been dealing with query parameters. Using the
|
249
|
+
+parameters+ plugin also allows params to be part of the route path
|
250
|
+
itself. If you have both query AND route parameters in a request, the
|
251
|
+
route values win.
|
252
|
+
|
253
|
+
class HelloWorldApp < Strelka::App
|
254
|
+
|
255
|
+
plugins :routing, :parameters
|
256
|
+
|
257
|
+
default_type 'text/plain'
|
258
|
+
|
259
|
+
param :name, :alpha
|
260
|
+
param :email, 'An RFC822 email address'
|
261
|
+
|
262
|
+
get '/' do |request|
|
263
|
+
request.params.override( :email, /\w+@\w+/ )
|
264
|
+
name = request.params['name'] || 'Handsome'
|
265
|
+
request.response << "Hello, %s!" % [ name ]
|
266
|
+
return request.response
|
267
|
+
end
|
268
|
+
|
269
|
+
get '/:name' do |request|
|
270
|
+
response = request.response
|
271
|
+
|
272
|
+
if request.params.okay?
|
273
|
+
response.puts "Name: %s" % [ request.params['name'] ]
|
274
|
+
else
|
275
|
+
response.status = HTTP::BAD_REQUEST
|
276
|
+
response.puts( *request.params.error_messages )
|
277
|
+
end
|
278
|
+
|
279
|
+
return response
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
The above example shows how to selectively override the <tt>email</tt>
|
284
|
+
parameter for the <tt>/</tt> route, and how to incorporate a parameter
|
285
|
+
into a route. There are many, many more options for the param object.
|
286
|
+
Please see the {Strelka API documentation}[rdoc-
|
287
|
+
ref:Strelka::ParamValidator] for more information.
|
288
|
+
|
289
|
+
To document:
|
290
|
+
|
291
|
+
* Named match groups
|
292
|
+
* Validating uploaded files (mediatype, size, etc.)
|
293
|
+
* Validating non-form entity bodies (YAML, JSON, etc.)
|
294
|
+
|
295
|
+
|
296
|
+
== Accessing Headers
|
297
|
+
|
298
|
+
The request and response objects both have a headers object that
|
299
|
+
provides methods for getting and setting header values. You can use it
|
300
|
+
like a Hash:
|
301
|
+
|
302
|
+
remote_host = request.header['X-Forwarded-For']
|
303
|
+
response.header['Content-Type'] = 'image/jpeg'
|
304
|
+
|
305
|
+
or with a Symbol key (hyphens become underscores):
|
306
|
+
|
307
|
+
remote_host = request.header[:x_forwarded_for]
|
308
|
+
response.header[:content_type] = 'application/pdf'
|
309
|
+
|
310
|
+
You can also access it using struct-like methods, with the same pattern
|
311
|
+
as with Symbol keys:
|
312
|
+
|
313
|
+
remote_host = request.header.x_forwarded_for
|
314
|
+
response.header.content_type = 'text/html'
|
315
|
+
|
316
|
+
Keep in mind that some headers can appear multiple times, so what you
|
317
|
+
get back could be an Array, too.
|
318
|
+
|
319
|
+
|
320
|
+
== Setting Response Status
|
321
|
+
|
322
|
+
Responses start out with a <tt>204 No Content</tt> status, and will
|
323
|
+
automatically switch to <tt>200 OK</tt> if you add a body to it.
|
324
|
+
|
325
|
+
You can, of course, set the status yourself:
|
326
|
+
|
327
|
+
response.status = HTTP::NOT_FOUND
|
328
|
+
|
329
|
+
If you set it directly, however, the response will still go back through
|
330
|
+
all of your loaded plugins, which is probably not what you want. In that
|
331
|
+
case you can finish a request early using the {finish_with helper}[rdoc-
|
332
|
+
ref:Strelka::App#finish_with]:
|
333
|
+
|
334
|
+
finish_with HTTP::NOT_FOUND, "That user doesn't exist."
|
335
|
+
|
336
|
+
Using <tt>finish_with</tt> stops additional processing immediately and returns
|
337
|
+
a response with the specified status and message. You can also include
|
338
|
+
additional headers:
|
339
|
+
|
340
|
+
new_location = "http://example.com/somewhere/else"
|
341
|
+
finish_with HTTP::REDIRECT,
|
342
|
+
"That resource has moved here: #{new_location}.",
|
343
|
+
headers: { location: new_location }
|
344
|
+
|
345
|
+
|
346
|
+
== Using Templates
|
347
|
+
|
348
|
+
Most web frameworks come with some kind of templating built in, but
|
349
|
+
Strelka doesn't have any preconceived assumptions about what you might
|
350
|
+
want to use for your applications. As long as your templates implement
|
351
|
+
<tt>#to_s</tt>, you can set them as the response body and your app will
|
352
|
+
work fine:
|
353
|
+
|
354
|
+
require 'erubis'
|
355
|
+
|
356
|
+
class HelloWorldApp < Strelka::App
|
357
|
+
|
358
|
+
plugins :routing
|
359
|
+
|
360
|
+
default_type 'text/html'
|
361
|
+
|
362
|
+
get '/' do |request|
|
363
|
+
response = request.response
|
364
|
+
template = Erubis::Eruby.load_file( 'template1.rhtml' )
|
365
|
+
response.body = template.evaluate( :greeting => "Why hello!" )
|
366
|
+
return response
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
Strelka comes with a <tt>:templating</tt> plugin that provides your application
|
372
|
+
with the ability to use the
|
373
|
+
{Inversion templating system}[http://deveiate.org/code/Inversion-manual/] to
|
374
|
+
build a response with minimal fuss.
|
375
|
+
|
376
|
+
class HelloWorldApp < Strelka::App
|
377
|
+
|
378
|
+
plugins :routing, :templating
|
379
|
+
|
380
|
+
templates :index => 'index.tmpl'
|
381
|
+
|
382
|
+
default_type 'text/html'
|
383
|
+
|
384
|
+
get '/' do |request|
|
385
|
+
tmpl = template( :index )
|
386
|
+
tmpl.greeting = "Why hello!"
|
387
|
+
return tmpl
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
Using Inversion, you can optionally wrap content in a global look and
|
392
|
+
feel via a layout template. This is accomplished by simply declaring a
|
393
|
+
layout template, which should contain a <tt>body</tt> attribute. That
|
394
|
+
attribute expands to the current response template.
|
395
|
+
|
396
|
+
class HelloWorldApp < Strelka::App
|
397
|
+
|
398
|
+
plugins :routing, :templating
|
399
|
+
|
400
|
+
layout 'layout.tmpl'
|
401
|
+
templates :index => 'index.tmpl'
|
402
|
+
|
403
|
+
default_type 'text/html'
|
404
|
+
|
405
|
+
get '/' do |request|
|
406
|
+
tmpl = template( :index )
|
407
|
+
tmpl.greeting = "Why hello!"
|
408
|
+
return tmpl
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
In the above example, the Inversion index template would look as such:
|
413
|
+
|
414
|
+
<!-- index.tmpl -->
|
415
|
+
<?attr greeting ?>
|
416
|
+
|
417
|
+
and then the layout might look like:
|
418
|
+
|
419
|
+
<!-- layout.tmpl -->
|
420
|
+
<html>
|
421
|
+
<body>
|
422
|
+
<?attr body ?>
|
423
|
+
</body>
|
424
|
+
</html>
|
425
|
+
|
426
|
+
If you need to set some stuff on the response object, you can also set
|
427
|
+
the template as the response body with the same effect:
|
428
|
+
|
429
|
+
class HelloWorldApp < Strelka::App
|
430
|
+
|
431
|
+
plugins :routing, :templating
|
432
|
+
|
433
|
+
layout 'layout.tmpl'
|
434
|
+
templates :index => 'index.tmpl'
|
435
|
+
|
436
|
+
default_type 'text/html'
|
437
|
+
|
438
|
+
get '/' do |request|
|
439
|
+
tmpl = template( :index )
|
440
|
+
tmpl.greeting = "Why hello!"
|
441
|
+
|
442
|
+
response = request.response
|
443
|
+
response.status = HTTP::CREATED
|
444
|
+
response.body = tmpl
|
445
|
+
|
446
|
+
return response
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
On the other hand, if your application doesn't need to set attributes on
|
451
|
+
the template *or* the response, you can automate the template loading
|
452
|
+
and response by returning the name of the template (as a Symbol)
|
453
|
+
instead:
|
454
|
+
|
455
|
+
class HelloWorldApp < Strelka::App
|
456
|
+
|
457
|
+
plugins :routing, :templating
|
458
|
+
|
459
|
+
layout 'layout.tmpl'
|
460
|
+
templates :index => 'index.tmpl'
|
461
|
+
|
462
|
+
default_type 'text/html'
|
463
|
+
|
464
|
+
get '/' do |request|
|
465
|
+
return :index
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
Sometimes you want to have a layout, but there's one or two responses
|
470
|
+
that you don't want to be wrapped. You can accomplish this just by
|
471
|
+
rendering the template immediately so the response body is just a
|
472
|
+
String:
|
473
|
+
|
474
|
+
class HelloWorldApp < Strelka::App
|
475
|
+
|
476
|
+
plugins :routing, :templating
|
477
|
+
|
478
|
+
layout 'layout.tmpl'
|
479
|
+
templates :robots => 'robots.txt.tmpl'
|
480
|
+
|
481
|
+
default_type 'text/html'
|
482
|
+
|
483
|
+
get '/robots.txt' do |request|
|
484
|
+
response = request.response
|
485
|
+
response.content_type = 'text/plain'
|
486
|
+
response.body = template( :robots ).render
|
487
|
+
return response
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
Response templates also have some attributes automatically set on them
|
492
|
+
by the <tt>:templating</tt> plugin:
|
493
|
+
|
494
|
+
[request] The current Strelka::HTTPRequest object.
|
495
|
+
[app] The application object.
|
496
|
+
[strelka_version] The current Strelka version string.
|
497
|
+
[mongrel2_version] The current Ruby-Mongrel2 version string.
|
498
|
+
[route] If you're using the <tt>:routing</tt> plugin, this will be
|
499
|
+
set to the routing information for the matched route.
|
500
|
+
|
501
|
+
Note that if you're using a layout template, you'll need to use the
|
502
|
+
<tt>import</tt> tag to use them in the body template:
|
503
|
+
|
504
|
+
<?import request, app, route ?>
|
505
|
+
|
506
|
+
|
507
|
+
== Filtering Every Request and/or Response
|
508
|
+
|
509
|
+
Sometimes there are actions you want to take before requests are
|
510
|
+
handled, or after the response is built. To do that, you can use the
|
511
|
+
{:filters}[rdoc-ref:Strelka::App::Filters] plugin.
|
512
|
+
|
513
|
+
You enable that the same way as with other plugins:
|
514
|
+
|
515
|
+
plugin :filters
|
516
|
+
|
517
|
+
That gives you the <tt>filter</tt> directive. You can declare
|
518
|
+
<tt>:request</tt> filters, <tt>:response</tt> filters, or filters that
|
519
|
+
apply to <tt>:both</tt> requests and responses.
|
520
|
+
|
521
|
+
For example:
|
522
|
+
|
523
|
+
### Make various configuration settings available to templates.
|
524
|
+
###
|
525
|
+
filter :request do |request|
|
526
|
+
# Provide the ability to hook/display other stuff if running in
|
527
|
+
# development.
|
528
|
+
request.notes[ :devmode ] = MyLibrary.dev?
|
529
|
+
|
530
|
+
# The default contact email address.
|
531
|
+
request.notes[ :contact ] = MyLibrary.contact_email
|
532
|
+
|
533
|
+
# Yo, what time is it?
|
534
|
+
request.notes[ :now ] = Time.now
|
535
|
+
end
|
536
|
+
|
537
|
+
### Modify outgoing headers on all responses to include library
|
538
|
+
### version info.
|
539
|
+
###
|
540
|
+
filter :response do |response|
|
541
|
+
response.headers.x_libraryversion = MyLibrary.version_string
|
542
|
+
response.headers.x_elapsed = Time.now - response.request.notes[ :now ]
|
543
|
+
end
|
544
|
+
|
545
|
+
See the docs for Strelka::App::Filters for more details.
|
546
|
+
|
547
|
+
|
548
|
+
== Altering Error Display
|
549
|
+
|
550
|
+
Strelka provides basic error-handling, turning any exception that is
|
551
|
+
raised from your application into a 500 response, etc., but you'll
|
552
|
+
probably want to provide something prettier and/or override some
|
553
|
+
response types.
|
554
|
+
|
555
|
+
The {:errors}[rdoc-ref:Strelka::App::Errors] plugin can help you with
|
556
|
+
this; enable it the usual way:
|
557
|
+
|
558
|
+
plugin :errors
|
559
|
+
|
560
|
+
It provides your application with an {on_status}[rdoc-
|
561
|
+
ref:Strelka::App::Errors.on_status] declarative that you can use to
|
562
|
+
provide handlers for particular status codes, or ranges of status codes:
|
563
|
+
|
564
|
+
# Handle only status 400 errors
|
565
|
+
on_status HTTP::BAD_REQUEST do |response, status_info|
|
566
|
+
# Do something on 400 errors
|
567
|
+
end
|
568
|
+
|
569
|
+
# Handle any other error in the 4xx range
|
570
|
+
on_status 400..499 do |response, status_info|
|
571
|
+
# Do something on 4xx errors
|
572
|
+
end
|
573
|
+
|
574
|
+
See the {API docs}[rdoc-ref:Strelka::App::Errors] for more details.
|
575
|
+
|
576
|
+
|
577
|
+
== Sessions
|
578
|
+
|
579
|
+
When you need some place to store a bit of state between requests,
|
580
|
+
there's the {:sessions plugin}[rdoc-ref:Strelka::Apps::Sessions]. Since
|
581
|
+
sessions have all kinds of strategies for server-side and client-side
|
582
|
+
storage, the Strelka sessions plugin is really a mini-framework for
|
583
|
+
storing serialized data. Strelka comes with a few basic examples to get
|
584
|
+
you started, but in all likelihood, you'll want to create your own
|
585
|
+
session type, or use a third-party one that fits into your environment.
|
586
|
+
|
587
|
+
The session plugin to use is set via the config file:
|
588
|
+
|
589
|
+
# Use built in (in-process) session storage.
|
590
|
+
sessions:
|
591
|
+
type: default
|
592
|
+
|
593
|
+
And then the plugin itself is configured via its own section:
|
594
|
+
|
595
|
+
# Configuration for the built in session provider.
|
596
|
+
defaultsession:
|
597
|
+
cookie_name: "session-demo"
|
598
|
+
cookie_options:
|
599
|
+
expires: +5m
|
600
|
+
|
601
|
+
The 'default' session type uses an in-memory Hash to store session data,
|
602
|
+
and cookies to track the session ID.
|
603
|
+
|
604
|
+
Strelka also comes with a simple database storage session plugin, that
|
605
|
+
supports any database backend that Sequel[http://sequel.rubyforge.org/]
|
606
|
+
does. Its configuration looks similar. Here's an example that writes
|
607
|
+
session information to a SQLite[http://www.sqlite.org/] database:
|
608
|
+
|
609
|
+
# Use the database session storage.
|
610
|
+
sessions:
|
611
|
+
type: db
|
612
|
+
|
613
|
+
# Configuration for the database session provider.
|
614
|
+
dbsession:
|
615
|
+
connect: sqlite://sessions.db
|
616
|
+
cookie_name: "session-demo"
|
617
|
+
cookie_options:
|
618
|
+
expires: +5m
|
619
|
+
|
620
|
+
See the API docs for Strelka::App::Sessions for more.
|
621
|
+
|
622
|
+
|
623
|
+
== Authentication and Authorization
|
624
|
+
|
625
|
+
When you want to guard all or part of your application behind an
|
626
|
+
authentication layer, the {:auth plugin}[rdoc-ref:Strelka::App::Auth]
|
627
|
+
can help.
|
628
|
+
|
629
|
+
As with the :sessions plugin, Strelka only comes with a basic plugin
|
630
|
+
just to get you started, as you're likely to want to use one that is
|
631
|
+
particular to your environment.
|
632
|
+
|
633
|
+
It, too, is configured via a section of the config file:
|
634
|
+
|
635
|
+
# Use the Basic authentication provider for
|
636
|
+
# any routes that require user validation.
|
637
|
+
auth:
|
638
|
+
provider: basic
|
639
|
+
|
640
|
+
and then the plugin has its own section:
|
641
|
+
|
642
|
+
# Configuration for the Basic auth provider.
|
643
|
+
basicauth:
|
644
|
+
realm: Examples
|
645
|
+
users:
|
646
|
+
ged: "dn44fsIrKF6KmBw0im4GIJktSpM="
|
647
|
+
jblack: "1pAnQNSVtpL1z88QwXV4sG8NMP8="
|
648
|
+
kmurgen: "MZj9+VhZ8C9+aJhmwp+kWBL76Vs="
|
649
|
+
|
650
|
+
|
651
|
+
Protecting all routes in a handler is the default behavior when the
|
652
|
+
plugin is loaded:
|
653
|
+
|
654
|
+
class HelloWorldApp < Strelka::App
|
655
|
+
|
656
|
+
plugins :routing, :auth
|
657
|
+
|
658
|
+
default_type 'text/html'
|
659
|
+
|
660
|
+
get do |request|
|
661
|
+
request.response.content_type = 'text/plain'
|
662
|
+
request.response << "Hello, %s!" % [ request.authenticated_user ]
|
663
|
+
return request.response
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
You can be specific about what routes are protected also:
|
668
|
+
|
669
|
+
class HelloWorldApp < Strelka::App
|
670
|
+
|
671
|
+
plugins :routing, :auth
|
672
|
+
|
673
|
+
# Only require authentication for /private
|
674
|
+
require_auth_for '/private'
|
675
|
+
|
676
|
+
default_type 'text/html'
|
677
|
+
|
678
|
+
get '/public' do |request|
|
679
|
+
request.response.content_type = 'text/plain'
|
680
|
+
request.response << "Hello, Anonymous!"
|
681
|
+
return request.response
|
682
|
+
end
|
683
|
+
|
684
|
+
get '/private' do |request|
|
685
|
+
request.response.content_type = 'text/plain'
|
686
|
+
request.response << "Hello, %s!" % [ request.authenticated_user ]
|
687
|
+
return request.response
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
See the API docs for Strelka::App::Auth for further details on usage,
|
692
|
+
how to integrate it with your application, applying authorization
|
693
|
+
permissions, and how to create your own plugin for your environment.
|
694
|
+
|
695
|
+
|
696
|
+
== HTTP Content Negotiation
|
697
|
+
|
698
|
+
Nowadays, when you write a web application, you're more than likely writing
|
699
|
+
one or more REST (or at least HTTP-based) services as part of it. Even if
|
700
|
+
you're not, you might want to support
|
701
|
+
{HTTP Content Negotation}[http://tools.ietf.org/html/rfc2616#section-12]
|
702
|
+
for your regular content, too.
|
703
|
+
|
704
|
+
The {:negotiation plugin}[rdoc-ref:Strelka::App::Negotiation] allows you
|
705
|
+
to negotiate with the client via <tt>Allow*</tt> headers to determine
|
706
|
+
the best mediatype, character encoding, natural language, and transport
|
707
|
+
encoding.
|
708
|
+
|
709
|
+
Here's an example of how you might support several different formats for
|
710
|
+
an extremely simple 'User' service:
|
711
|
+
|
712
|
+
require 'user'
|
713
|
+
|
714
|
+
class UserService < Strelka::App
|
715
|
+
|
716
|
+
plugins :routing, :negotiation, :parameters
|
717
|
+
|
718
|
+
# Declare the ID parameter
|
719
|
+
param :id, :integer, "User ID"
|
720
|
+
|
721
|
+
# GET /users -- collection method
|
722
|
+
get do |request|
|
723
|
+
collection = User.all
|
724
|
+
response = request.response
|
725
|
+
response.for( :json, :yaml ) { collection }
|
726
|
+
response.for( :text ) do
|
727
|
+
str = "Users:"
|
728
|
+
collection.each do |user|
|
729
|
+
str << user.to_s << "\n"
|
730
|
+
end
|
731
|
+
str
|
732
|
+
end
|
733
|
+
|
734
|
+
return response
|
735
|
+
end
|
736
|
+
|
737
|
+
# GET /users/{id} -- fetch a single record
|
738
|
+
get '/:id' do |request|
|
739
|
+
id = request.params[:id]
|
740
|
+
user = User[ id ] or finish_with( HTTP::NOT_FOUND, "no such user #{id}" )
|
741
|
+
|
742
|
+
response = request.response
|
743
|
+
response.for( :json, :yaml ) { user }
|
744
|
+
response.for( :text ) { user.to_s }
|
745
|
+
|
746
|
+
return response
|
747
|
+
end
|
748
|
+
|
749
|
+
# ...and so on...
|
750
|
+
|
751
|
+
end # class UserService
|
752
|
+
|
753
|
+
The above example only demonstrates content negotiation, but there's a bunch of
|
754
|
+
additional stuff you can do with this plugin. See the API docs for
|
755
|
+
Strelka::App::Negotiation for all the goods.
|
756
|
+
|
757
|
+
|
758
|
+
== REST Services
|
759
|
+
|