strelka 0.0.1.pre.295 → 0.0.1.pre.301
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|