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

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 (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
  #########