strelka 0.0.1.pre148 → 0.0.1.pre177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/ChangeLog +1294 -0
  2. data/IDEAS.rdoc +6 -0
  3. data/Manifest.txt +20 -0
  4. data/README.rdoc +8 -2
  5. data/Rakefile +9 -7
  6. data/examples/auth-demo.rb +32 -0
  7. data/examples/auth-demo2.rb +37 -0
  8. data/examples/auth-form.tmpl +10 -0
  9. data/examples/auth-success.tmpl +3 -0
  10. data/examples/config.yml +12 -0
  11. data/examples/examples.css +4 -0
  12. data/examples/examples.html +31 -0
  13. data/examples/gen-config.rb +5 -2
  14. data/examples/layout.tmpl +31 -0
  15. data/lib/strelka/app/auth.rb +480 -0
  16. data/lib/strelka/app/sessions.rb +8 -6
  17. data/lib/strelka/app/templating.rb +78 -17
  18. data/lib/strelka/app.rb +13 -5
  19. data/lib/strelka/authprovider/basic.rb +134 -0
  20. data/lib/strelka/authprovider/hostaccess.rb +91 -0
  21. data/lib/strelka/authprovider.rb +122 -0
  22. data/lib/strelka/cookie.rb +1 -1
  23. data/lib/strelka/cookieset.rb +1 -1
  24. data/lib/strelka/httprequest/auth.rb +31 -0
  25. data/lib/strelka/logging.rb +69 -14
  26. data/lib/strelka/mixins.rb +35 -65
  27. data/lib/strelka/session/db.rb +115 -0
  28. data/lib/strelka/session/default.rb +38 -49
  29. data/lib/strelka/session.rb +1 -1
  30. data/lib/strelka.rb +4 -1
  31. data/spec/lib/helpers.rb +8 -3
  32. data/spec/strelka/app/auth_spec.rb +367 -0
  33. data/spec/strelka/authprovider/basic_spec.rb +192 -0
  34. data/spec/strelka/authprovider/hostaccess_spec.rb +70 -0
  35. data/spec/strelka/authprovider_spec.rb +99 -0
  36. data/spec/strelka/cookie_spec.rb +1 -1
  37. data/spec/strelka/httprequest/auth_spec.rb +55 -0
  38. data/spec/strelka/httprequest/session_spec.rb +63 -3
  39. data/spec/strelka/session/db_spec.rb +85 -0
  40. data/spec/strelka/session/default_spec.rb +5 -51
  41. data.tar.gz.sig +0 -0
  42. metadata +88 -57
  43. metadata.gz.sig +0 -0
@@ -0,0 +1,480 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ #encoding: utf-8
4
+
5
+ require 'strelka' unless defined?( Strelka )
6
+ require 'strelka/app' unless defined?( Strelka::App )
7
+
8
+ require 'strelka/httprequest/auth'
9
+ require 'strelka/authprovider'
10
+
11
+
12
+ # Pluggable authentication and authorization for Strelka applications.
13
+ #
14
+ # Enabling the +:auth+ plugin by default causes all requests to your
15
+ # handler to go through an authentication and authorization provider
16
+ # first. This provider checks the request for the necessary credentials,
17
+ # then either forwards it on if sufficient conditions are met, or
18
+ # responds with the appropriate 4xx status response.
19
+ #
20
+ # The conditions are broken down into two stages:
21
+ #
22
+ # * Authentication -- the client is who they say they are
23
+ # * Authorization -- the client is allowed to access the resources in question
24
+ #
25
+ # Auth providers are plugins that are named <tt>Strelka::AuthProvider::<MyType></tt>, and
26
+ # inherit from Strelka::AuthProvider. In order for them to be discoverable, each one
27
+ # should be in a file named <tt>lib/strelka/authprovider/<mytype>.rb</tt>. They
28
+ # can implement one or both of the stages; see the API docs for Strelka::AuthProvider
29
+ # for details on how to write your own plugin.
30
+ #
31
+ #
32
+ # == Applying Authentication
33
+ #
34
+ # The default authentication policy is to require authentification from every
35
+ # request, but sometimes you may wish to narrow the restrictions a bit.
36
+ #
37
+ # === Relaxing \Auth for A Few Methods
38
+ #
39
+ # Sometimes you want to expose just one or two resources to the world, say in the
40
+ # case of a REST API that includes the authentication endpoint. Obviously, clients
41
+ # can't be authenticated until after they send their request that authenticates
42
+ # them, so you can expose just the +/login+ URI by using the 'no_auth_for' directive:
43
+ #
44
+ # class MyService < Strelka::App
45
+ # plugins :auth
46
+ # no_auth_for '/login'
47
+ #
48
+ # # ...
49
+ # end
50
+ #
51
+ # A String or a Regexp argument will be used to match against the request's
52
+ # {#app_path}[rdoc-ref:Strelka::HTTPRequest#app_path] (the path of the request
53
+ # URI with the Mongrel2 route omitted), and any requests which match are sent
54
+ # along as-is.
55
+ #
56
+ # If you require some more-complex criteria for determining if the request should
57
+ # skip the auth plugin, you can provide a block to +no_auth_for+ instead.
58
+ #
59
+ # # Allow requests from 'localhost' without auth, but require it from
60
+ # # everywhere else
61
+ # no_auth_for do |request|
62
+ # return 'internal-user' if request.header.x_forwarded_for == '127.0.0.1'
63
+ # end
64
+ #
65
+ # If the block returns a true-ish value, it will be used in the place of the
66
+ # authenticated username and the request will be handed to your app.
67
+ #
68
+ # Returning a false-ish value will go ahead with the rest of the auth
69
+ # processing.
70
+ #
71
+ # You can also combine String and Regexp arguments with a block to further refine
72
+ # the conditions:
73
+ #
74
+ # # Allow people to visit the seminar registration view without an account
75
+ # # if there are still slots open
76
+ # no_auth_for( '/register' ) do |request|
77
+ # if Seminars.any? {|seminar| !seminar.full? }
78
+ # 'register'
79
+ # else
80
+ #
81
+ # end
82
+ # end
83
+ #
84
+ #
85
+ # === Relaxing \Auth for All But a Few Methods
86
+ #
87
+ # Sometimes, though, you want just the opposite -- a few methods are available
88
+ # only to a select few, but the majority are unrestricted.
89
+ #
90
+ # To do this, use the 'require_auth_for' directive:
91
+ #
92
+ # class MyBlog < Strelka::App
93
+ # plugins :auth
94
+ # require_auth_for '/admin'
95
+ #
96
+ # # ...
97
+ # end
98
+ #
99
+ # Note that this inverts the usual behavior of the +:auth+ plugin: resources
100
+ # will, by default, be unguarded, so be sure you keep this in mind when
101
+ # using +require_auth_for+.
102
+ #
103
+ # Like +no_auth_for+, +require_auth_for+ can also take a block, and
104
+ # a true-ish return value will cause the request to pass through the
105
+ # AuthProvider.
106
+ #
107
+ # You can't use +no_auth_for+ and +require_auth_for+ in the same application; doing
108
+ # so will result in a ScriptError being raised when the application is loaded.
109
+ #
110
+ #
111
+ # == Adding Authorization
112
+ #
113
+ # Sometimes simple authentication isn't sufficient for accessing some
114
+ # resources, especially if you have some kind of permissions system that
115
+ # dictates who can see/use what. That's where the second stage of the
116
+ # authentification process comes into play: Authorization.
117
+ #
118
+ # The AuthProvider you're using may provide some form of general authorization
119
+ # itself (especially a custom one), but typically authorization is particular to an application and
120
+ # even particular actions within the application.
121
+ #
122
+ # To provide the particulars for your app's authorization, you can declare
123
+ # a block with the +authz_callback+ directive.
124
+ #
125
+ # authz_callback do |request, user, *other_auth_info|
126
+ # user.can?( request.app_path )
127
+ # end
128
+ #
129
+ # The block will be called once authentication has succeeded, and any general authorization has been
130
+ # checked. It will be called with at least the credentials object returned from the authentication
131
+ # stage and the request object. Some AuthProviders may opt to return authentication credentials
132
+ # as a User object of some kind (e.g., a database row, LDAP entry, model object, etc.), but the
133
+ # simpler ones just return the login of the authenticated +user+. The AuthProvider may also
134
+ # furnish additional useful arguments such as a database handle, permission objects, etc. to your
135
+ # authorization block. See the documentation for your chosen AuthProvider for details.
136
+ #
137
+ # == Customizing Failure
138
+ #
139
+ # As mentioned before, an authentication or authorization failure results in a
140
+ # 4xx status response. By default Strelka will present this back to the
141
+ # browser as a simple error response, but oftentimes you will want to customize
142
+ # it to look a little nicer, or to behave in a more-intuitive way.
143
+ # The easiest way to do this is to use the {:errors}[rdoc-ref:Strelka::App::Errors]
144
+ # plugin.
145
+ #
146
+ # === Redirecting to a Form
147
+ #
148
+ # If you're using form-based session authentication (as opposed to basic
149
+ # auth, which has its own UI), you can rewrite the response to instruct
150
+ # the browser to go to a static HTML form instead:
151
+ #
152
+ # class FormAuthApp < Strelka::App
153
+ # plugins :errors, :auth, :sessions
154
+ # auth_provider :session
155
+ #
156
+ # on_status HTTP::AUTH_REQUIRED do |res, status|
157
+ # formuri = res.request.uri
158
+ # formuri.path = '/loginform.html'
159
+ #
160
+ # res.reset
161
+ # res.status = HTTP::SEE_OTHER
162
+ # res.content_type = 'text/plain'
163
+ # res.puts "This resource requires authentication."
164
+ # res.header.location = formuri
165
+ #
166
+ # return res
167
+ # end
168
+ # end
169
+ #
170
+ # === Responding With a Form
171
+ #
172
+ # With the addition of the {:templating}[rdoc-ref:Strelka::App::Templating] plugin,
173
+ # you can respond with the form directly instead:
174
+ #
175
+ # class TemplateFormAuthApp < Strelka::App
176
+ # plugins :auth, :errors, :templating
177
+ # auth_provider :session
178
+ #
179
+ # layout 'examples/layout.tmpl'
180
+ # templates \
181
+ # form: 'examples/auth-form.tmpl',
182
+ # success: 'examples/auth-success.tmpl'
183
+ #
184
+ # on_status HTTP::AUTH_REQUIRED, :form
185
+ #
186
+ # ### Handle any (authenticated) HTTP request
187
+ # def handle_request( req )
188
+ # return :success
189
+ # end
190
+ #
191
+ # end
192
+ #
193
+ # == Examples
194
+ #
195
+ # Here are a few more examples using a few different AuthProviders.
196
+ #
197
+ # # Guard every request the app does behind a simple passphrase
198
+ # class MyGuardedApp < Strelka::App
199
+ # plugins :auth
200
+ #
201
+ # auth_provider :passphrase
202
+ # end
203
+ #
204
+ # # Require LDAP authentication for one route
205
+ # class MyGuardedApp < Strelka::App
206
+ # plugins :auth, :routing
207
+ #
208
+ # auth_provider :ldap
209
+ # authz_callback do |user, request, directory|
210
+ # authgroup = directory.ou( :appperms ).cn( :guarded_app )
211
+ # authgroup.members.include?( user.dn )
212
+ # end
213
+ #
214
+ # authenticated %r{^/admin}
215
+ # end
216
+ #
217
+ # # Use a user table in a PostgreSQL database for authentication for
218
+ # # all routes except one
219
+ # class MyGuardedApp < Strelka::App
220
+ # plugins :auth
221
+ #
222
+ # auth_provider :sequel
223
+ # authz_callback do |user, request, db|
224
+ # db[:permissions].filter( :user_id => user[:id] ).
225
+ # filter( :permname => 'guardedapp' )
226
+ # end
227
+ #
228
+ # unauthenticated %r{^/auth}
229
+ #
230
+ # # Only authenticated users can use this
231
+ # post '/servers' do
232
+ # # ...
233
+ # end
234
+ # end
235
+ #
236
+ module Strelka::App::Auth
237
+ extend Strelka::App::Plugin,
238
+ Strelka::MethodUtilities,
239
+ Configurability
240
+ include Strelka::Loggable,
241
+ Strelka::Constants
242
+
243
+ run_before :routing, :restresources
244
+ run_after :templating, :errors, :sessions
245
+
246
+
247
+ # The name of the default plugin to use for authentication
248
+ DEFAULT_AUTH_PROVIDER = :hostaccess
249
+
250
+
251
+ # Class methods to add to app classes that enable Auth
252
+ module ClassMethods
253
+
254
+ @auth_provider = nil
255
+ @authz_callback = nil
256
+ @positive_auth_criteria = {}
257
+ @negative_auth_criteria = {}
258
+
259
+ ##
260
+ # Arrays of criteria for applying and skipping auth for a request.
261
+ attr_reader :positive_auth_criteria, :negative_auth_criteria
262
+
263
+
264
+ ### Get/set the authentication type.
265
+ def auth_provider( type=nil )
266
+ if type
267
+ @auth_provider = Strelka::AuthProvider.get_subclass( type )
268
+ elsif type.nil?
269
+ @auth_provider ||= Strelka::AuthProvider.get_subclass( DEFAULT_AUTH_PROVIDER )
270
+ end
271
+
272
+ return @auth_provider
273
+ end
274
+
275
+
276
+ ### Register a function to call after the user successfully authenticates to check
277
+ ### for authorization or other criteria. The arguments to the function depend on
278
+ ### which authentication plugin is used. Returning +true+ from this function will
279
+ ### cause authorization to succeed, while returning a false value causes it to fail
280
+ ### with a FORBIDDEN response. If no callback is set, and the provider doesn't
281
+ ### provide authorization
282
+ def authz_callback( callable=nil, &block )
283
+ if callable
284
+ @authz_callback = callable
285
+ elsif block
286
+ @authz_callback = block
287
+ end
288
+
289
+ return @authz_callback
290
+ end
291
+
292
+
293
+ ### Returns +true+ if there are any criteria for determining whether or
294
+ ### not a request needs auth.
295
+ def has_auth_criteria?
296
+ return self.has_positive_auth_criteria? || self.has_negative_auth_criteria?
297
+ end
298
+
299
+
300
+ ### Returns +true+ if the app has been set up so that only some methods
301
+ ### require auth.
302
+ def has_positive_auth_criteria?
303
+ return !self.positive_auth_criteria.empty?
304
+ end
305
+
306
+
307
+ ### Returns +true+ if the app has been set up so that all methods but
308
+ ### ones that match declared criteria require auth.
309
+ def has_negative_auth_criteria?
310
+ return !self.negative_auth_criteria.empty?
311
+ end
312
+
313
+
314
+ ### Constrain auth to apply only to requests which match the given +criteria+,
315
+ ### and/or the given +block+. The +criteria+ are either Strings or Regexps
316
+ ### which are tested against
317
+ ### {the request's #app_path}[rdoc-ref:Strelka::HTTPRequest#app_path]. The block
318
+ ### should return a true-ish value if the request should undergo authentication
319
+ ### and authorization.
320
+ ### *NOTE:* using this declaration inverts the default security policy of
321
+ ### restricting access to all requests.
322
+ def require_auth_for( *criteria, &block )
323
+ if self.has_negative_auth_criteria?
324
+ raise ScriptError,
325
+ "defining both positive and negative auth criteria is unsupported."
326
+ end
327
+
328
+ criteria << '' if criteria.empty?
329
+ block ||= Proc.new { true }
330
+
331
+ criteria.each do |pattern|
332
+ self.positive_auth_criteria[ pattern ] = block
333
+ end
334
+ end
335
+
336
+
337
+ ### Contrain auth to apply to all requests *except* those that match the
338
+ ### given +criteria+.
339
+ def no_auth_for( *criteria, &block )
340
+ if self.has_positive_auth_criteria?
341
+ raise ScriptError,
342
+ "defining both positive and negative auth criteria is unsupported."
343
+ end
344
+
345
+ criteria << '' if criteria.empty?
346
+ block ||= Proc.new { true }
347
+
348
+ criteria.each do |pattern|
349
+ self.negative_auth_criteria[ pattern ] = block
350
+ end
351
+ end
352
+
353
+
354
+ end # module ClassMethods
355
+
356
+
357
+ ### Extension callback -- extend the HTTPRequest class with Auth
358
+ ### support when this plugin is loaded.
359
+ def self::included( object )
360
+ Strelka.log.debug "Extending Request with Auth mixin"
361
+ Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::Auth }
362
+ super
363
+ end
364
+
365
+
366
+ ### Add an AuthProvider instance to the app.
367
+ def initialize( * )
368
+ super
369
+ @auth_provider = self.class.auth_provider.new( self )
370
+ end
371
+
372
+
373
+ ######
374
+ public
375
+ ######
376
+
377
+ # The instance of (a subclass of) Strelka::AuthProvider that provides authentication
378
+ # logic for the app.
379
+ attr_reader :auth_provider
380
+
381
+
382
+ ### Check authentication and authorization for requests that need it before
383
+ ### sending them on.
384
+ def handle_request( request, &block )
385
+ self.log.debug "AuthProvider: %p" % [ self.auth_provider ]
386
+
387
+ self.authenticate_and_authorize( request ) if self.request_should_auth?( request )
388
+
389
+ super
390
+ end
391
+
392
+
393
+ #########
394
+ protected
395
+ #########
396
+
397
+ ### Returns +true+ if the given +request+ requires authentication.
398
+ def request_should_auth?( request )
399
+ self.log.debug "Checking to see if Auth(entication/orization) should be applied for %s" %
400
+ [ request.app_path ]
401
+
402
+ # If there are positive criteria, return true if the request matches any of them,
403
+ # or false if they don't
404
+ if self.class.has_positive_auth_criteria?
405
+ criteria = self.class.positive_auth_criteria
406
+ self.log.debug " checking %d positive auth criteria" % [ criteria.length ]
407
+ return criteria.any? do |pattern, block|
408
+ self.log.debug " %p -> %p" % [ pattern, block ]
409
+ self.request_matches_criteria( request, pattern, &block )
410
+ end
411
+
412
+ # If there are negative criteria, return false if the request matches any of them,
413
+ # or true if they don't
414
+ elsif self.class.has_negative_auth_criteria?
415
+ criteria = self.class.negative_auth_criteria
416
+ self.log.debug " checking %d negative auth criteria" % [ criteria.length ]
417
+ return !criteria.any? do |pattern, block|
418
+ self.log.debug " %p -> %p" % [ pattern, block ]
419
+ self.request_matches_criteria( request, pattern, &block )
420
+ end
421
+
422
+ else
423
+ self.log.debug " no auth criteria; default to requiring auth"
424
+ return true
425
+ end
426
+ end
427
+
428
+
429
+ ### Returns +true+ if there are positive auth criteria and the +request+ matches
430
+ ### at least one of them.
431
+ def request_matches_criteria( request, pattern )
432
+ case pattern
433
+ when Regexp
434
+ self.log.debug " matching app_path with regexp: %p" % [ pattern ]
435
+ matchdata = pattern.match( request.app_path ) or return false
436
+ self.log.debug " calling the block"
437
+ return yield( request, matchdata )
438
+
439
+ when String
440
+ self.log.debug " matching app_path prefix: %p" % [ pattern ]
441
+ request.app_path.start_with?( pattern ) or return false
442
+ self.log.debug " calling the block"
443
+ return yield( request )
444
+
445
+ else
446
+ raise ScriptError, "don't know how to match a request with a %p" % [ pattern.class ]
447
+ end
448
+ end
449
+
450
+
451
+ ### Process authentication and authorization for the specified +request+.
452
+ def authenticate_and_authorize( request )
453
+ credentials = self.provide_authentication( request )
454
+ request.authenticated_user = credentials
455
+ self.provide_authorization( credentials, request )
456
+ end
457
+
458
+
459
+ ### If the AuthProvider does authentication, try to extract authenticated credentials
460
+ ### from the +request+ and return them, throwing a :finish with
461
+ ### a properly-constructed 401 (Auth required) response if that fails.
462
+ def provide_authentication( request )
463
+ provider = self.auth_provider
464
+ self.log.info "Authenticating request using provider: %p" % [ provider ]
465
+ return provider.authenticate( request )
466
+ end
467
+
468
+
469
+ ### Process authorization for the given +credentials+ and +request+.
470
+ def provide_authorization( credentials, request )
471
+ provider = self.auth_provider
472
+ callback = self.class.authz_callback
473
+
474
+ self.log.info "Authorizing using credentials: %p, callback: %p" % [ credentials, callback ]
475
+ provider.authorize( credentials, request, &callback )
476
+ end
477
+
478
+ end # module Strelka::App::Auth
479
+
480
+
@@ -85,9 +85,7 @@ module Strelka::App::Sessions
85
85
  Strelka::Constants
86
86
 
87
87
  # Default options to pass to the session object
88
- DEFAULT_OPTIONS = {
89
- :cookie_name => 'strelka-session',
90
- }
88
+ DEFAULT_OPTIONS = {}
91
89
 
92
90
  # Configurability API -- specify which section of the config this class gets
93
91
  config_key :sessions
@@ -101,7 +99,7 @@ module Strelka::App::Sessions
101
99
  singleton_attr_writer :session_class
102
100
 
103
101
 
104
- # Class methods and instance variables to add to classes with routing.
102
+ # Class methods and instance variables to add to classes with sessions.
105
103
  module ClassMethods # :nodoc:
106
104
 
107
105
  # The namespace of the session that will be exposed to instances of this
@@ -135,7 +133,11 @@ module Strelka::App::Sessions
135
133
  if config
136
134
  self.session_class = Strelka::Session.get_subclass( config[:session_class] ) if
137
135
  config.key?( :session_class )
138
- options.merge!( config[:options] ) if config[:options]
136
+ if config[:options]
137
+ options.merge!( config[:options] ) do |key, oldval, newval|
138
+ oldval.merge( newval )
139
+ end
140
+ end
139
141
  else
140
142
  self.session_class = Strelka::Session.get_subclass( :default )
141
143
  end
@@ -150,6 +152,7 @@ module Strelka::App::Sessions
150
152
  def self::included( object )
151
153
  Strelka.log.debug "Extending Request with Session mixin"
152
154
  Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::Session }
155
+ Strelka.log.debug "Extending Response with Session mixin"
153
156
  Strelka::HTTPResponse.class_eval { include Strelka::HTTPResponse::Session }
154
157
  super
155
158
  end
@@ -169,7 +172,6 @@ module Strelka::App::Sessions
169
172
  return super
170
173
  end
171
174
 
172
-
173
175
  end # module Strelka::App::Sessions
174
176
 
175
177
 
@@ -10,7 +10,82 @@ require 'strelka/app' unless defined?( Strelka::App )
10
10
  require 'strelka/app/plugins'
11
11
 
12
12
 
13
- # Templating plugin for Strelka::Apps.
13
+ # A templated content-generation plugin for Strelka::Apps. It uses the
14
+ # Inversion[http://deveiate.org/projects/Inversion] templating system.
15
+ #
16
+ # It adds:
17
+ #
18
+ # * a preloaded/cached template table
19
+ # * a mechanism for fetching templates from the table
20
+ # * a global layout template which is automatically wrapped around responses
21
+ #
22
+ # == Usage
23
+ #
24
+ # To use it, just load the <tt>:templating</tt> plugin in your app:
25
+ #
26
+ # plugins :templating
27
+ #
28
+ # and declare one or more templates that your application will use:
29
+ #
30
+ # templates :console => 'views/console.tmpl',
31
+ # :proctable => 'partials/proctable.tmpl'
32
+ #
33
+ # Then, inside your app, you can fetch a copy of one or more of the templates and
34
+ # return it as the reponse:
35
+ #
36
+ # def handle_request( req )
37
+ # super do
38
+ # res = request.response
39
+ #
40
+ # proctable = template :proctable
41
+ # proctable.processes = ProcessList.fetch
42
+ #
43
+ # tmpl = template :console
44
+ # tmpl.message = "Everything's up."
45
+ # tmpl.proctable = proctable
46
+ # res.body = tmpl
47
+ #
48
+ # return res
49
+ # end
50
+ # end
51
+ #
52
+ # You can also just return the template if you don't need to do anything else to the
53
+ # response.
54
+ #
55
+ # When returning a template, either in the body of the response or directly, it will
56
+ # automatically set a few attributes for commonly-used objects:
57
+ #
58
+ # request :: The current Strelka::HTTPRequest
59
+ # strelka_version :: Strelka.version_string( true )
60
+ # mongrel2_version :: Mongrel2.version_string( true )
61
+ # route :: If the :routing plugin is loaded, this will be set to the
62
+ # 'routing_info' of the chosen route. See
63
+ # Strelka::Router#add_route for details.
64
+ #
65
+ # If your app will *only* be loading and returning a template without doing anything
66
+ # with it, you can return just its name:
67
+ #
68
+ # def handle_request( req )
69
+ # super { :console }
70
+ # end
71
+ #
72
+ # It will be loaded, set as the response body, and the above common objects added to it.
73
+ #
74
+ # === Layouts
75
+ #
76
+ # Very often, you'll want your app to share a common page layout. To accomplish this, you
77
+ # can declare a special layout template:
78
+ #
79
+ # layout 'layout.tmpl'
80
+ #
81
+ # Any template that you return will be set as the 'body' attribute of this layout
82
+ # template, and the layout rendered as the body of the response.
83
+ #
84
+ # Note that if you want any of the "common objects" from above with a layout template,
85
+ # you must use the <tt><?import ?></tt> directive to import them:
86
+ #
87
+ # <?import request, strelka_version, route ?>
88
+ #
14
89
  module Strelka::App::Templating
15
90
  include Strelka::Constants
16
91
  extend Strelka::App::Plugin
@@ -20,7 +95,7 @@ module Strelka::App::Templating
20
95
 
21
96
 
22
97
  # Class methods to add to classes with templating.
23
- module ClassMethods # :nodoc:
98
+ module ClassMethods
24
99
 
25
100
  # The map of template names to template file paths.
26
101
  @template_map = {}
@@ -97,21 +172,7 @@ module Strelka::App::Templating
97
172
 
98
173
 
99
174
  ### Intercept responses on the way back out and turn them into a Mongrel2::HTTPResponse
100
- ### with a String for its entity body. It will take action if the response is one of:
101
- ###
102
- ### 1. A Mongrel2::Response with an Inversion::Template as its body.
103
- ### 2. An Inversion::Template by itself.
104
- ### 3. A Symbol that matches one of the keys of the registered templates.
105
- ###
106
- ### In all three of these cases, the return value will be a Mongrel2::Response with a
107
- ### body set to the rendered value of the template in question, and with its status
108
- ### set to '200 OK' unless it is already set to something else.
109
- ###
110
- ### If there is a registered layout template, and any of the three cases is true, the
111
- ### layout template is loaded, its #body attributes set to the content template,
112
- ### and its rendered output set as the body of the response instead.
113
- ###
114
- ### Every other response is returned without modification.
175
+ ### with a String for its entity body.
115
176
  def handle_request( request, &block )
116
177
  response = super
117
178
 
data/lib/strelka/app.rb CHANGED
@@ -239,7 +239,6 @@ class Strelka::App < Mongrel2::Handler
239
239
  ### for the plugin system. Without being overridden or extended by plugins, this
240
240
  ### method just returns the default Mongrel2::HTTPRequest#response.
241
241
  def handle_request( request, &block )
242
- self.log.debug "Strelka::App#handle_request"
243
242
  if block
244
243
  return super( request, &block )
245
244
  else
@@ -302,15 +301,17 @@ class Strelka::App < Mongrel2::Handler
302
301
  ### Abort the current execution and return a response with the specified
303
302
  ### http_status code immediately. The specified +message+ will be logged,
304
303
  ### and will be included in any message that is returned as part of the
305
- ### response. The +otherstuff+ hash can be used to pass headers, etc.
306
- def finish_with( http_status, message, otherstuff={} )
307
- status_info = otherstuff.merge( :status => http_status, :message => message )
304
+ ### response. The +headers+ hash will be used to set response headers.
305
+ def finish_with( http_status, message, headers={} )
306
+ status_info = { :status => http_status, :message => message, :headers => headers }
308
307
  throw :finish, status_info
309
308
  end
310
309
 
311
310
 
312
311
  ### Create a response to specified +request+ based on the specified +status_code+
313
312
  ### and +message+.
313
+ ### :TODO: Document and test the :content_type status_info field.
314
+ ### :TODO: Implement a way to set headers from the status_info.
314
315
  def prepare_status_response( request, status_info )
315
316
  status_code, message = status_info.values_at( :status, :message )
316
317
  self.log.info "Non-OK response: %d (%s)" % [ status_code, message ]
@@ -322,10 +323,17 @@ class Strelka::App < Mongrel2::Handler
322
323
  # Some status codes allow explanatory text to be returned; some forbid it. Append the
323
324
  # message for those that allow one.
324
325
  unless request.verb == :HEAD || HTTP::BODILESS_HTTP_RESPONSE_CODES.include?( status_code )
325
- response.content_type = status_info[ :content_type ] || 'text/plain'
326
+ response.content_type = 'text/plain'
326
327
  response.puts( message )
327
328
  end
328
329
 
330
+ # Now assign any headers to the response that are part of the status
331
+ if status_info.key?( :headers )
332
+ status_info[:headers].each do |hdr, value|
333
+ response.headers[ hdr ] = value
334
+ end
335
+ end
336
+
329
337
  return response
330
338
  end
331
339