strelka 0.0.1.pre148 → 0.0.1.pre177

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