strelka 0.0.1.pre.187 → 0.0.1.pre.193

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data.tar.gz.sig +0 -0
  2. data/ChangeLog +94 -26
  3. data/Manifest.txt +4 -2
  4. data/examples/apps/ws-echo +17 -0
  5. data/lib/strelka/app.rb +26 -24
  6. data/lib/strelka/app/auth.rb +2 -1
  7. data/lib/strelka/app/errors.rb +1 -1
  8. data/lib/strelka/app/filters.rb +1 -1
  9. data/lib/strelka/app/negotiation.rb +1 -1
  10. data/lib/strelka/app/parameters.rb +2 -2
  11. data/lib/strelka/app/restresources.rb +1 -1
  12. data/lib/strelka/app/routing.rb +3 -2
  13. data/lib/strelka/app/sessions.rb +3 -3
  14. data/lib/strelka/app/templating.rb +3 -3
  15. data/lib/strelka/authprovider.rb +2 -10
  16. data/lib/strelka/behavior/plugin.rb +3 -3
  17. data/lib/strelka/httprequest.rb +5 -2
  18. data/lib/strelka/httprequest/session.rb +3 -2
  19. data/lib/strelka/httpresponse/session.rb +8 -9
  20. data/lib/strelka/mixins.rb +15 -0
  21. data/lib/strelka/plugins.rb +257 -0
  22. data/lib/strelka/router/default.rb +27 -2
  23. data/lib/strelka/session.rb +20 -2
  24. data/lib/strelka/session/db.rb +20 -10
  25. data/lib/strelka/session/default.rb +41 -18
  26. data/spec/lib/helpers.rb +1 -1
  27. data/spec/strelka/app/auth_spec.rb +1 -1
  28. data/spec/strelka/app/errors_spec.rb +1 -1
  29. data/spec/strelka/app/filters_spec.rb +1 -1
  30. data/spec/strelka/app/negotiation_spec.rb +1 -1
  31. data/spec/strelka/app/parameters_spec.rb +1 -1
  32. data/spec/strelka/app/restresources_spec.rb +1 -1
  33. data/spec/strelka/app/routing_spec.rb +4 -1
  34. data/spec/strelka/app/sessions_spec.rb +63 -17
  35. data/spec/strelka/app/templating_spec.rb +1 -1
  36. data/spec/strelka/app_spec.rb +13 -5
  37. data/spec/strelka/httprequest/session_spec.rb +44 -23
  38. data/spec/strelka/httprequest_spec.rb +21 -0
  39. data/spec/strelka/httpresponse/session_spec.rb +143 -0
  40. data/spec/strelka/{app/plugins_spec.rb → plugins_spec.rb} +64 -53
  41. data/spec/strelka/router/default_spec.rb +15 -0
  42. data/spec/strelka/router/exclusive_spec.rb +14 -0
  43. data/spec/strelka/session/db_spec.rb +11 -0
  44. data/spec/strelka/session/default_spec.rb +10 -2
  45. metadata +119 -37
  46. metadata.gz.sig +0 -0
  47. data/lib/strelka/app/plugins.rb +0 -284
@@ -7,7 +7,7 @@ require 'inversion'
7
7
  require 'strelka' unless defined?( Strelka )
8
8
  require 'strelka/app' unless defined?( Strelka::App )
9
9
 
10
- require 'strelka/app/plugins'
10
+ require 'strelka/plugins'
11
11
 
12
12
 
13
13
  # A templated content-generation plugin for Strelka::Apps. It uses the
@@ -88,7 +88,7 @@ require 'strelka/app/plugins'
88
88
  #
89
89
  module Strelka::App::Templating
90
90
  include Strelka::Constants
91
- extend Strelka::App::Plugin
91
+ extend Strelka::Plugin
92
92
 
93
93
  run_before :routing, :negotiation, :errors
94
94
  run_after :filters
@@ -109,7 +109,7 @@ module Strelka::App::Templating
109
109
  def inherited( subclass )
110
110
  super
111
111
  subclass.instance_variable_set( :@template_map, @template_map.dup )
112
- subclass.instance_variable_set( :@layout_template, @layout_template.dup )
112
+ subclass.instance_variable_set( :@layout_template, @layout_template.dup ) if @layout_template
113
113
  end
114
114
 
115
115
 
@@ -44,7 +44,8 @@ class Strelka::AuthProvider
44
44
  include PluginFactory,
45
45
  Strelka::Loggable,
46
46
  Strelka::Constants,
47
- Strelka::AbstractClass
47
+ Strelka::AbstractClass,
48
+ Strelka::ResponseHelpers
48
49
 
49
50
 
50
51
  ### PluginFactory API -- return the Array of directories to search for concrete
@@ -109,14 +110,5 @@ class Strelka::AuthProvider
109
110
  end
110
111
 
111
112
 
112
- ### Abort the current execution and return a response with the specified
113
- ### http_status code immediately. The specified +message+ will be logged,
114
- ### and will be included in any message that is returned as part of the
115
- ### response. The +headers+ hash will be used to set response headers.
116
- def finish_with( http_status, message, headers={} )
117
- status_info = { :status => http_status, :message => message, :headers => headers }
118
- throw :finish, status_info
119
- end
120
-
121
113
  end # class Strelka::AuthProvider
122
114
 
@@ -6,7 +6,7 @@ require 'rspec'
6
6
 
7
7
  require 'strelka'
8
8
  require 'strelka/app'
9
- require 'strelka/app/plugins'
9
+ require 'strelka/plugins'
10
10
 
11
11
 
12
12
  # This is a shared behavior for specs which different Strelka::App
@@ -29,8 +29,8 @@ shared_examples_for "A Strelka::App Plugin" do
29
29
  end
30
30
 
31
31
 
32
- it "extends Strelka::App::Plugin" do
33
- plugin.should be_a( Strelka::App::Plugin )
32
+ it "extends Strelka::Plugin" do
33
+ plugin.should be_a( Strelka::Plugin )
34
34
  end
35
35
 
36
36
  end
@@ -14,7 +14,8 @@ require 'strelka/cookieset'
14
14
  # An HTTP request class.
15
15
  class Strelka::HTTPRequest < Mongrel2::HTTPRequest
16
16
  include Strelka::Loggable,
17
- Strelka::Constants
17
+ Strelka::Constants,
18
+ Strelka::ResponseHelpers
18
19
 
19
20
  # Set Mongrel2 to use Strelka's request class for HTTP requests
20
21
  register_request_type( self, *HTTP::RFC2616_VERBS )
@@ -163,8 +164,10 @@ class Strelka::HTTPRequest < Mongrel2::HTTPRequest
163
164
  ### Return a Hash of request form data.
164
165
  def parse_form_data
165
166
  case self.headers.content_type
167
+ when nil
168
+ finish_with( HTTP::BAD_REQUEST, "Malformed request (no content type?)" )
166
169
  when 'application/x-www-form-urlencoded'
167
- return merge_query_args( URI.decode_www_form(self.body) )
170
+ return merge_query_args( URI.decode_www_form(self.body) )
168
171
  when 'application/json', 'text/javascript'
169
172
  return Yajl.load( self.body )
170
173
  when 'text/x-yaml', 'application/x-yaml'
@@ -43,10 +43,11 @@ module Strelka::HTTPRequest::Session
43
43
  end
44
44
 
45
45
 
46
- ### Returns +true+ if the request already has an associated session object.
46
+ ### Returns +true+ if the request has an associated session object.
47
47
  def session?
48
- return !@session.nil?
48
+ return @session || Strelka::App::Sessions.session_class.has_session_for?( self )
49
49
  end
50
+ alias_method :has_session?, :session?
50
51
 
51
52
 
52
53
  ### Return the session associated with the request, creating it if necessary.
@@ -57,13 +57,10 @@ module Strelka::HTTPResponse::Session
57
57
  # new blank session.
58
58
  if self.request.session?
59
59
  self.log.debug "Getting the request's session."
60
- @session = request.session
60
+ self.session = request.session
61
61
  else
62
62
  self.log.debug "No session loaded in the request; creating it in the response."
63
- sessionclass = Strelka::App::Sessions.session_class
64
- @session = sessionclass.load_or_create( self.request )
65
- @session.namespace = self.session_namespace
66
- request.session = @session
63
+ self.session = Strelka::App::Sessions.session_class.new
67
64
  end
68
65
  end
69
66
 
@@ -73,9 +70,11 @@ module Strelka::HTTPResponse::Session
73
70
 
74
71
  ### Set the request's session object.
75
72
  def session=( new_session )
73
+ self.log.debug "Setting session to %p in namespace %p" % [ new_session, self.session_namespace ]
76
74
  new_session.namespace = self.session_namespace
77
75
  @session = new_session
78
- request.session = new_session
76
+ self.log.debug " session is: %p" % [ @session ]
77
+ # request.session = new_session # should it set the session in the request too?
79
78
  end
80
79
 
81
80
 
@@ -83,15 +82,15 @@ module Strelka::HTTPResponse::Session
83
82
  def session?
84
83
  return @session || self.request.session?
85
84
  end
85
+ alias_method :has_session?, :session?
86
86
 
87
87
 
88
88
  ### Tell the associated session to save itself and set up the session ID in the
89
89
  ### response, if one exists.
90
90
  def save_session
91
91
  if self.session?
92
- session = self.session
93
- self.log.debug "Saving session: %p" % [ @session ]
94
- session.save( self )
92
+ self.log.debug "Saving session: %p" % [ self.session ]
93
+ self.session.save( self )
95
94
  else
96
95
  self.log.debug "No session to save."
97
96
  end
@@ -333,6 +333,21 @@ module Strelka
333
333
 
334
334
  end # module MethodUtilities
335
335
 
336
+
337
+ # A collection of functions for generating responses.
338
+ module ResponseHelpers
339
+
340
+ ### Abort the current execution and return a response with the specified
341
+ ### http_status code immediately. The specified +message+ will be logged,
342
+ ### and will be included in any message that is returned as part of the
343
+ ### response. The +headers+ hash will be used to set response headers.
344
+ def finish_with( http_status, message, headers={} )
345
+ status_info = { :status => http_status, :message => message, :headers => headers }
346
+ throw :finish, status_info
347
+ end
348
+
349
+ end # module ResponseHelpers
350
+
336
351
  end # module Strelka
337
352
 
338
353
  # vim: set nosta noet ts=4 sw=4:
@@ -0,0 +1,257 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'set'
6
+ require 'tsort'
7
+
8
+ require 'strelka' unless defined?( Strelka )
9
+ require 'strelka/mixins'
10
+
11
+ module Strelka
12
+ extend Strelka::MethodUtilities
13
+
14
+ # A topologically-sorted hash for plugin management
15
+ class PluginRegistry < Hash
16
+ include TSort
17
+ alias_method :tsort_each_node, :each_key
18
+ def tsort_each_child( node, &block )
19
+ mod = fetch( node ) { [] }
20
+ if mod.respond_to?( :successors )
21
+ mod.successors.each( &block )
22
+ else
23
+ mod.each( &block )
24
+ end
25
+ end
26
+ end
27
+
28
+
29
+ # Plugin Module extension -- adds registration, load-order support, etc.
30
+ module Plugin
31
+
32
+ ### Extension hook -- Extend the given object with methods for setting it
33
+ ### up as a plugin for its containing namespace.
34
+ def self::extended( object )
35
+ super
36
+
37
+ # Find the plugin's namespace container, which will be the
38
+ # pluggable class/module
39
+ pluggable_name = object.name.split( '::' )[ 0..-2 ]
40
+ pluggable = pluggable_name.inject( Object ) do |mod, name|
41
+ mod.const_get( name )
42
+ end
43
+
44
+ Strelka.log.debug "Extending %p as a Strelka::Plugin for %p" % [ object, pluggable ]
45
+ object.successors = Set.new
46
+ object.pluggable = pluggable
47
+
48
+ # Register any pending dependencies for the newly-loaded plugin
49
+ name = object.plugin_name
50
+ if (( deps = pluggable.loaded_plugins[name] ))
51
+ Strelka.log.debug " installing deferred deps for %p" % [ name ]
52
+ object.run_after( *deps )
53
+ end
54
+
55
+ Strelka.log.debug " adding %p (%p) to the plugin registry for %p" %
56
+ [ name, object, pluggable ]
57
+ pluggable.loaded_plugins[ name ] = object
58
+ end
59
+
60
+
61
+ #############################################################
62
+ ### A P P E N D E D M E T H O D S
63
+ #############################################################
64
+
65
+ # An Array that tracks which plugins should be installed after itself.
66
+ attr_accessor :successors
67
+
68
+ # The Class/Module that this plugin belongs to
69
+ attr_accessor :pluggable
70
+
71
+
72
+ ### Return the name of the receiving plugin
73
+ def plugin_name
74
+ name = self.name || "anonymous#{self.object_id}"
75
+ name.sub!( /.*::/, '' )
76
+ return name.downcase.to_sym
77
+ end
78
+
79
+
80
+ ### Register the receiver as needing to be run before +other_plugins+ for requests, and
81
+ ### *after* them for responses.
82
+ def run_before( *other_plugins )
83
+ name = self.plugin_name
84
+ other_plugins.each do |other_name|
85
+ self.pluggable.loaded_plugins[ other_name ] ||= []
86
+ mod = self.pluggable.loaded_plugins[ other_name ]
87
+
88
+ if mod.respond_to?( :run_after )
89
+ mod.run_after( name )
90
+ else
91
+ Strelka.log.debug "%p plugin not yet loaded; setting up pending deps" % [ other_name ]
92
+ mod << name
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ ### Register the receiver as needing to be run after +other_plugins+ for requests, and
99
+ ### *before* them for responses.
100
+ def run_after( *other_plugins )
101
+ Strelka.log.debug " %p will run after %p" % [ self, other_plugins ]
102
+ self.successors.merge( other_plugins )
103
+ end
104
+
105
+ end # module Plugin
106
+
107
+
108
+ # Module API for the plugin system. This mixin adds the ability to load
109
+ # and install plugins into the extended object.
110
+ module PluginLoader
111
+
112
+ ### Extension callback -- initialize some data structures in the extended
113
+ ### object.
114
+ def self::extended( mod )
115
+ super
116
+ mod.loaded_plugins = Strelka::PluginRegistry.new
117
+ end
118
+
119
+
120
+ ##
121
+ # The Hash of loaded plugin modules, keyed by their downcased and symbolified
122
+ # name (e.g., Strelka::App::Templating => :templating)
123
+ attr_accessor :loaded_plugins
124
+
125
+ ##
126
+ # If plugins have already been installed, this will be the call frame
127
+ # they were first installed from. This is used to warn about installing
128
+ # plugins twice.
129
+ attr_accessor :plugins_installed_from
130
+
131
+
132
+ ### Returns +true+ if the plugins for the extended app class have already
133
+ ### been installed.
134
+ def plugins_installed?
135
+ return !self.plugins_installed_from.nil?
136
+ end
137
+
138
+
139
+ ### Extension callback -- add instance variables to extending objects.
140
+ def inherited( subclass )
141
+ super
142
+ @plugins ||= []
143
+ subclass.loaded_plugins = self.loaded_plugins
144
+ subclass.plugins_installed_from = nil
145
+ subclass.instance_variable_set( :@plugins, @plugins.dup )
146
+ end
147
+
148
+
149
+ ### Load the plugins with the given +names+ and install them.
150
+ def plugins( *names )
151
+ Strelka.log.info "Adding plugins: %s" % [ names.flatten.map(&:to_s).join(', ') ]
152
+
153
+ # Load the associated Plugin Module objects
154
+ names.flatten.each {|name| self.load_plugin(name) }
155
+
156
+ # Add the name/s to the list of mixins to apply on startup
157
+ @plugins |= names
158
+
159
+ # Install the declarative half of the plugin immediately
160
+ names.each do |name|
161
+ plugin = nil
162
+
163
+ if name.is_a?( Module )
164
+ plugin = name
165
+ else
166
+ plugin = self.loaded_plugins[ name ]
167
+ end
168
+
169
+ Strelka.log.debug " registering %p" % [ name ]
170
+ self.register_plugin( plugin )
171
+ end
172
+ end
173
+ alias_method :plugin, :plugins
174
+
175
+
176
+ ### Load the plugin with the given +name+
177
+ def load_plugin( name )
178
+
179
+ # Just return Modules as-is
180
+ return name if name.is_a?( Strelka::Plugin )
181
+ mod = self.loaded_plugins[ name.to_sym ]
182
+
183
+ unless mod.is_a?( Module )
184
+ prefix = self.name.gsub( /::/, File::PATH_SEPARATOR )
185
+ Strelka.log.debug "Loading plugin from #{prefix}/#{name}"
186
+ require "#{prefix}/#{name}"
187
+ mod = self.loaded_plugins[ name.to_sym ] or
188
+ raise "#{name} plugin didn't load correctly."
189
+ end
190
+
191
+ return mod
192
+ end
193
+
194
+
195
+ ### Register the plugin +mod+ in the receiving class. This adds any
196
+ ### declaratives and class-level data necessary for configuring the
197
+ ### plugin.
198
+ def register_plugin( mod )
199
+ if mod.const_defined?( :ClassMethods )
200
+ cm_mod = mod.const_get(:ClassMethods)
201
+ Strelka.log.debug " adding class methods from %p" % [ cm_mod ]
202
+
203
+ extend( cm_mod )
204
+ cm_mod.instance_variables.each do |ivar|
205
+ Strelka.log.debug " copying class instance variable %s" % [ ivar ]
206
+ ival = cm_mod.instance_variable_get( ivar )
207
+
208
+ # Don't duplicate modules/classes or immediates
209
+ instance_variable_set( ivar, Strelka::DataUtilities.deep_copy(ival) )
210
+ end
211
+ end
212
+ end
213
+
214
+
215
+ ### Install the mixin part of plugins immediately before the first instance
216
+ ### is created.
217
+ def new( * )
218
+ self.install_plugins unless self.plugins_installed?
219
+ super
220
+ end
221
+
222
+
223
+ ### Install the mixin part of the plugin, in the order determined by
224
+ ### the plugin registry based on the run_before and run_after specifications
225
+ ### of the plugins themselves.
226
+ def install_plugins
227
+ if self.plugins_installed?
228
+ Strelka.log.warn "Plugins were already installed for %p from %p" %
229
+ [ self, self.plugins_installed_from ]
230
+ Strelka.log.info "I'll attempt to install any new ones, but plugin ordering"
231
+ Strelka.log.info "and other functionality might exhibit strange behavior."
232
+ else
233
+ Strelka.log.info "Installing plugins for %p." % [ self ]
234
+ end
235
+
236
+ sorted_plugins = self.loaded_plugins.tsort.reverse
237
+
238
+ sorted_plugins.each do |name|
239
+ mod = self.loaded_plugins[ name ]
240
+
241
+ unless @plugins.include?( name ) || @plugins.include?( mod )
242
+ Strelka.log.debug " skipping %s" % [ name ]
243
+ next
244
+ end
245
+
246
+ Strelka.log.info " including %p." % [ mod ]
247
+ include( mod )
248
+ end
249
+
250
+ self.plugins_installed_from = caller( 1 ).first
251
+ end
252
+
253
+ end # module PluginLoader
254
+
255
+ end # class Strelka
256
+
257
+
@@ -8,7 +8,9 @@ require 'strelka/router'
8
8
 
9
9
  # Simple (dumb?) request router for Strelka::App-based applications.
10
10
  class Strelka::Router::Default < Strelka::Router
11
- include Strelka::Loggable
11
+ include Strelka::Loggable,
12
+ Strelka::Constants,
13
+ Strelka::ResponseHelpers
12
14
 
13
15
  ### Create a new router that will route requests according to the specified
14
16
  ### +routes+. Each route is a tuple of the form:
@@ -20,6 +22,8 @@ class Strelka::Router::Default < Strelka::Router
20
22
  ### ]
21
23
  def initialize( routes=[], options={} )
22
24
  @routes = Hash.new {|hash, verb| hash[verb] = {} }
25
+ # :FIXME: Either call #add_route for each route in 'routes', or eliminate
26
+ # the parameter
23
27
 
24
28
  super
25
29
  end
@@ -52,7 +56,8 @@ class Strelka::Router::Default < Strelka::Router
52
56
  path.slice!( 0, 1 ) if path.start_with?( '/' ) # Strip the leading '/'
53
57
 
54
58
  self.log.debug "Looking for a route for: %p %p" % [ verb, path ]
55
- verbroutes = @routes[ verb ] or return nil
59
+ verbroutes = @routes[ verb ]
60
+ return self.allowed_response( path ) if verbroutes.empty?
56
61
  match = self.find_longest_match( verbroutes.keys, path ) or return nil
57
62
  self.log.debug " longest match result: %p" % [ match ]
58
63
 
@@ -76,6 +81,26 @@ class Strelka::Router::Default < Strelka::Router
76
81
  end
77
82
 
78
83
 
84
+ ### If the specified +path+ matches a valid route, then respond with a
85
+ ### 405 (Method Not Allowed) with the allowed HTTP verbs. Otherwise, return
86
+ ### +nil+.
87
+ def allowed_response( path )
88
+ allowed_verbs = []
89
+
90
+ routes = @routes.each do |verb, verbroutes|
91
+ allowed_verbs << verb if self.find_longest_match( verbroutes.keys, path )
92
+ end
93
+ allowed_verbs << :HEAD if allowed_verbs.include?( :GET )
94
+
95
+ self.log.debug "Allowed methods for path %s: %p" % [ path, allowed_verbs ]
96
+ return nil if allowed_verbs.empty?
97
+
98
+ allowed_hdr = allowed_verbs.map {|verb| verb.to_s.upcase }.join( ', ' )
99
+ finish_with( HTTP::METHOD_NOT_ALLOWED, 'Method not allowed.', allow: allowed_hdr )
100
+ end
101
+
102
+
103
+
79
104
  #########
80
105
  protected
81
106
  #########