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
@@ -61,6 +61,14 @@ class Strelka::Session
61
61
  end
62
62
 
63
63
 
64
+ ### Fetch the session ID from the given +request, returning +nil+ if it doesn't
65
+ ### have any session attributes. You should override this and provide the
66
+ ### code which fetches the ID from the +request+.
67
+ def self::get_existing_session_id( request )
68
+ return nil
69
+ end
70
+
71
+
64
72
  ### Fetch the session ID from the given +request+, or create a new one if the
65
73
  ### request is +nil+ or doesn't have the necessary attributes. You should
66
74
  ### override this, as the default implementation just returns +nil+.
@@ -106,13 +114,23 @@ class Strelka::Session
106
114
  end
107
115
 
108
116
 
117
+ ### Return +true+ if the given +request+ has a valid session token, and that
118
+ ### token corresponds to an existing session ID. You should override this if
119
+ ### there's a cheaper way to check for an existing session than just calling
120
+ ### ::load_session_data.
121
+ def self::has_session_for?( request )
122
+ id = self.get_existing_session_id( request ) or return false
123
+ return true if self.load( id )
124
+ end
125
+
126
+
109
127
  #################################################################
110
128
  ### I N S T A N C E M E T H O D S
111
129
  #################################################################
112
130
 
113
131
  ### Set up a new instance with the given +session_id+ and +initial_values+.
114
- def initialize( session_id, initial_values={} ) # :notnew:
115
- @session_id = session_id
132
+ def initialize( session_id=nil, initial_values={} ) # :notnew:
133
+ @session_id = session_id || self.class.get_session_id
116
134
  end
117
135
 
118
136
 
@@ -15,7 +15,8 @@ require 'strelka/session/default'
15
15
  #
16
16
  class Strelka::Session::Db < Strelka::Session::Default
17
17
  include Strelka::Loggable
18
- extend Forwardable
18
+ extend Forwardable,
19
+ Strelka::MethodUtilities
19
20
 
20
21
  # Class-instance variables
21
22
  @table_name = :sessions
@@ -25,16 +26,17 @@ class Strelka::Session::Db < Strelka::Session::Default
25
26
  :name => 'strelka-session'
26
27
  }
27
28
 
28
- class << self
29
- # The Sequel dataset connection
30
- attr_reader :db
29
+ ##
30
+ # The Sequel dataset connection
31
+ singleton_attr_reader :db
31
32
 
32
- # The Sequel dataset for the sessions table
33
- attr_reader :dataset
33
+ ##
34
+ # The Sequel dataset for the sessions table
35
+ singleton_attr_reader :dataset
34
36
 
35
- # The configured session cookie parameters
36
- attr_accessor :cookie_options
37
- end
37
+ ##
38
+ # The configured session cookie parameters
39
+ singleton_attr_accessor :cookie_options
38
40
 
39
41
 
40
42
  ########################################################################
@@ -94,7 +96,7 @@ class Strelka::Session::Db < Strelka::Session::Default
94
96
 
95
97
 
96
98
  ### Save the given +data+ associated with the +session_id+ to the DB.
97
- def self::save_session_data( session_id, data )
99
+ def self::save_session_data( session_id, data={} )
98
100
  self.db.transaction do
99
101
  self.delete_session_data( session_id.to_s )
100
102
  self.dataset.insert(
@@ -111,5 +113,13 @@ class Strelka::Session::Db < Strelka::Session::Default
111
113
  self.dataset.filter( :session_id => session_id ).delete
112
114
  end
113
115
 
116
+
117
+ ### Return +true+ if the given +request+ has a session token which corresponds
118
+ ### to an existing session key.
119
+ def self::has_session_for?( request )
120
+ id = self.get_existing_session_id( request ) or return false
121
+ return !self.dataset.filter( :session_id => id ).empty?
122
+ end
123
+
114
124
  end # class Strelka::Session::Db
115
125
 
@@ -18,31 +18,38 @@ require 'strelka/mixins'
18
18
  # #[], #[]=, #delete, #key?
19
19
  class Strelka::Session::Default < Strelka::Session
20
20
  extend Forwardable,
21
+ Strelka::MethodUtilities,
21
22
  Strelka::Delegation
22
23
  include Strelka::Loggable,
23
24
  Strelka::DataUtilities
24
25
 
26
+ # Default configuration
27
+ DEFAULT_COOKIE_OPTIONS = {
28
+ :name => 'strelka-session'
29
+ }.freeze
30
+
31
+
25
32
  # Class-instance variables
33
+ @cookie_options = DEFAULT_COOKIE_OPTIONS.dup
26
34
  @sessions = {}
27
- @cookie_options = {
28
- :name => 'strelka-session'
29
- }
30
35
 
31
- class << self
32
- # Persist sessions in memory
33
- attr_reader :sessions
36
+ ##
37
+ # In-memory session store
38
+ singleton_attr_reader :sessions
34
39
 
35
- # The configured session cookie parameters
36
- attr_accessor :cookie_options
37
- end
40
+ ##
41
+ # The configured session cookie parameters
42
+ singleton_attr_accessor :cookie_options
38
43
 
39
44
 
40
45
  ### Configure the session class with the given +options+, which should be a
41
46
  ### Hash or an object that has a Hash-like interface. Sets cookie options
42
47
  ### for the session if the +:cookie+ key is set.
43
- def self::configure( options )
48
+ def self::configure( options=nil )
44
49
  if options
45
50
  self.cookie_options.merge!( options[:cookie] ) if options[:cookie]
51
+ else
52
+ self.cookie_options = DEFAULT_COOKIE_OPTIONS.dup
46
53
  end
47
54
  end
48
55
 
@@ -66,17 +73,35 @@ class Strelka::Session::Default < Strelka::Session
66
73
  end
67
74
 
68
75
 
76
+ ### Try to fetch a session ID from the specified +request+, returning +nil+
77
+ ### if there isn't one.
78
+ def self::get_existing_session_id( request )
79
+ cookie = request.cookies[ self.cookie_options[:name] ] or return nil
80
+
81
+ if cookie.value =~ /^([[:xdigit:]]+)$/i
82
+ return $1.untaint
83
+ else
84
+ Strelka.log.warn "Request with a malformed session cookie: %p" % [ request ]
85
+ return nil
86
+ end
87
+ end
88
+
89
+
69
90
  ### Fetch the session ID from the given +request+, or create a new one if the
70
91
  ### request doesn't have the necessary attributes.
71
92
  def self::get_session_id( request=nil )
72
- id = nil
93
+ id = self.get_existing_session_id( request ) if request
94
+ return id || SecureRandom.hex
95
+ end
73
96
 
74
- # Fetch and untaint the existing ID if it exists and looks valid
75
- if request && (cookie = request.cookies[ self.cookie_options[:name] ])
76
- id = $1.untaint if cookie.value =~ /^([[:xdigit:]]+)$/i
77
- end
78
97
 
79
- return id || SecureRandom.hex
98
+ ### Return +true+ if the given +request+ has a session token which corresponds
99
+ ### to an existing session key.
100
+ def self::has_session_for?( request )
101
+ Strelka.log.debug "Checking request (%s/%d) for session." % [ request.sender_id, request.conn_id ]
102
+ id = self.get_existing_session_id( request ) or return false
103
+ Strelka.log.debug " got a session ID: %p" % [ id ]
104
+ return @sessions.key?( id )
80
105
  end
81
106
 
82
107
 
@@ -86,8 +111,6 @@ class Strelka::Session::Default < Strelka::Session
86
111
 
87
112
  ### Create a new session store using the given +hash+ for initial values.
88
113
  def initialize( session_id=nil, initial_values={} )
89
- session_id ||= self.class.get_session_id
90
-
91
114
  @hash = Hash.new {|h,k| h[k] = {} }
92
115
  @hash.merge!( initial_values )
93
116
 
data/spec/lib/helpers.rb CHANGED
@@ -38,7 +38,7 @@ require 'mongrel2/testing'
38
38
  require 'strelka'
39
39
 
40
40
  require 'spec/lib/constants'
41
- # require 'spec/lib/matchers'
41
+
42
42
 
43
43
  ### RSpec helper functions.
44
44
  module Strelka::SpecHelpers
@@ -12,7 +12,7 @@ require 'rspec/mocks'
12
12
  require 'spec/lib/helpers'
13
13
 
14
14
  require 'strelka'
15
- require 'strelka/app/plugins'
15
+ require 'strelka/plugins'
16
16
  require 'strelka/app/auth'
17
17
  require 'strelka/authprovider'
18
18
 
@@ -14,7 +14,7 @@ require 'inversion'
14
14
  require 'spec/lib/helpers'
15
15
 
16
16
  require 'strelka'
17
- require 'strelka/app/plugins'
17
+ require 'strelka/plugins'
18
18
  require 'strelka/app/errors'
19
19
 
20
20
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/filters'
18
18
 
19
19
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/negotiation'
18
18
  require 'strelka/behavior/plugin'
19
19
 
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/parameters'
18
18
  require 'strelka/behavior/plugin'
19
19
 
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/restresources'
18
18
 
19
19
  require 'strelka/behavior/plugin'
@@ -13,7 +13,7 @@ require 'rspec'
13
13
  require 'spec/lib/helpers'
14
14
 
15
15
  require 'strelka'
16
- require 'strelka/app/plugins'
16
+ require 'strelka/plugins'
17
17
  require 'strelka/app/routing'
18
18
 
19
19
  require 'strelka/behavior/plugin'
@@ -203,6 +203,7 @@ describe Strelka::App::Routing do
203
203
 
204
204
  it "has its routes inherited by subclasses" do
205
205
  @app.class_eval do
206
+ router :deep
206
207
  get( '/info' ) {}
207
208
  get( '/about' ) {}
208
209
  get( '/origami' ) {}
@@ -220,6 +221,8 @@ describe Strelka::App::Routing do
220
221
  subclass.routes.should include(
221
222
  [ :GET, ['origami'], {action: subclass.instance_method(:GET_origami), options: {}} ]
222
223
  )
224
+
225
+ subclass.routerclass.should == @app.routerclass
223
226
  end
224
227
 
225
228
  describe "that also uses the :parameters plugin" do
@@ -11,7 +11,7 @@ require 'rspec'
11
11
  require 'spec/lib/helpers'
12
12
 
13
13
  require 'strelka'
14
- require 'strelka/app/plugins'
14
+ require 'strelka/plugins'
15
15
  require 'strelka/app/sessions'
16
16
 
17
17
  require 'strelka/behavior/plugin'
@@ -36,23 +36,50 @@ describe Strelka::App::Sessions do
36
36
  it_should_behave_like( "A Strelka::App Plugin" )
37
37
 
38
38
 
39
- it "has an associated session class" do
40
- Strelka::App::Sessions.session_class.should be_a( Class )
41
- Strelka::App::Sessions.session_class.should < Strelka::Session
42
- end
39
+ describe "session-class loading" do
40
+ before( :all ) do
41
+ # First, hook the anonymous class up to the 'testing' name using the PluginFactory API
42
+ @test_session_class = Class.new( Strelka::Session ) do
43
+ class << self; attr_accessor :options; end
44
+ def self::configure( options )
45
+ @options = options
46
+ end
47
+ end
48
+ Strelka::Session.derivatives[ 'testing' ] = @test_session_class
49
+ end
43
50
 
44
- it "is can be configured to use a different session class" do
45
- # First, hook the anonymous class up to the 'testing' name using the PluginFactory API
46
- test_session_class = Class.new( Strelka::Session )
47
- Strelka::Session.derivatives[ 'testing' ] = test_session_class
51
+ after( :each ) do
52
+ Strelka::App::Sessions.instance_variable_set( :@session_class, nil )
53
+ end
48
54
 
49
- # Now configuring it to use the 'testing' session type should set it to use the
50
- # anonymous class
51
- Strelka::App::Sessions.configure( :session_class => 'testing' )
55
+ it "has a default associated session class" do
56
+ Strelka::App::Sessions.session_class.should be_a( Class )
57
+ Strelka::App::Sessions.session_class.should < Strelka::Session
58
+ end
52
59
 
53
- Strelka::App::Sessions.session_class.should == test_session_class
54
- end
60
+ it "is can be configured to use a different session class" do
61
+ Strelka::App::Sessions.configure( :session_class => 'testing' )
62
+ Strelka::App::Sessions.session_class.should == @test_session_class
63
+ end
55
64
 
65
+ it "configures the configured session class with default options" do
66
+ Strelka::App::Sessions.configure( :session_class => 'testing' )
67
+ Strelka::App::Sessions.session_class.options.should == Strelka::App::Sessions::DEFAULT_OPTIONS
68
+ end
69
+
70
+ it "merges any config options for the configured session class" do
71
+ options = { 'cookie_name' => 'patience' }
72
+ Strelka::App::Sessions.configure( :session_class => 'testing', :options => options )
73
+ Strelka::App::Sessions.session_class.options.
74
+ should == Strelka::App::Sessions::DEFAULT_OPTIONS.merge( options )
75
+ end
76
+
77
+ it "uses the default session class if the config doesn't have a session section" do
78
+ Strelka::App::Sessions.configure
79
+ Strelka::App::Sessions.session_class.should be( Strelka::Session::Default )
80
+ end
81
+
82
+ end
56
83
 
57
84
  describe "an including App" do
58
85
 
@@ -72,9 +99,11 @@ describe Strelka::App::Sessions do
72
99
  super
73
100
  end
74
101
 
75
- def handle( req )
76
- req.session[ :test ] = 'session data'
77
- return req.response
102
+ def handle_request( req )
103
+ super do
104
+ req.session[ :test ] = 'session data'
105
+ req.response
106
+ end
78
107
  end
79
108
  end
80
109
  end
@@ -107,6 +136,23 @@ describe Strelka::App::Sessions do
107
136
  @app.session_namespace.should == :findizzle
108
137
  end
109
138
 
139
+ it "extends the request and response classes" do
140
+ @app.install_plugins
141
+ Strelka::HTTPRequest.should < Strelka::HTTPRequest::Session
142
+ Strelka::HTTPResponse.should < Strelka::HTTPResponse::Session
143
+ end
144
+
145
+ it "sets the session namespace on requests" do
146
+ req = @request_factory.get( '/foom' )
147
+ res = @app.new.handle( req )
148
+ req.session_namespace.should == @app.default_appid
149
+ end
150
+
151
+ it "saves the session automatically" do
152
+ req = @request_factory.get( '/foom' )
153
+ res = @app.new.handle( req )
154
+ res.cookies.should include( Strelka::Session::Default.cookie_options[:name] )
155
+ end
110
156
 
111
157
  end
112
158
 
@@ -14,7 +14,7 @@ require 'inversion'
14
14
  require 'spec/lib/helpers'
15
15
 
16
16
  require 'strelka'
17
- require 'strelka/app/plugins'
17
+ require 'strelka/plugins'
18
18
  require 'strelka/app/templating'
19
19
 
20
20
  require 'strelka/behavior/plugin'
@@ -26,6 +26,7 @@ describe Strelka::App do
26
26
 
27
27
  before( :all ) do
28
28
  setup_logging( :fatal )
29
+ @initial_registry = Strelka::App.loaded_plugins.dup
29
30
  @request_factory = Mongrel2::RequestFactory.new( route: '/mail' )
30
31
  Mongrel2::Config.db = Mongrel2::Config.in_memory_db
31
32
  Mongrel2::Config.init_database
@@ -39,6 +40,7 @@ describe Strelka::App do
39
40
  end
40
41
 
41
42
  before( :each ) do
43
+ Strelka::App.loaded_plugins.clear
42
44
  @app = Class.new( Strelka::App ) do
43
45
  def initialize( appid=TEST_APPID, sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
44
46
  super
@@ -55,6 +57,7 @@ describe Strelka::App do
55
57
  end
56
58
 
57
59
  after( :all ) do
60
+ Strelka::App.loaded_plugins = @initial_registry
58
61
  reset_logging()
59
62
  end
60
63
 
@@ -174,7 +177,8 @@ describe Strelka::App do
174
177
 
175
178
  # make a plugin that always 304s and install it
176
179
  not_modified_plugin = Module.new do
177
- extend Strelka::App::Plugin
180
+ def self::name; "Strelka::App::NotModified"; end
181
+ extend Strelka::Plugin
178
182
  def handle_request( r )
179
183
  finish_with( HTTP::NOT_MODIFIED, "Unchanged." )
180
184
  fail "Shouldn't be reached."
@@ -193,7 +197,8 @@ describe Strelka::App do
193
197
  it "creates a simple response body for status responses that can have them" do
194
198
  # make an auth plugin that always denies requests
195
199
  forbidden_plugin = Module.new do
196
- extend Strelka::App::Plugin
200
+ def self::name; "Strelka::App::Forbidden"; end
201
+ extend Strelka::Plugin
197
202
  def handle_request( r )
198
203
  finish_with( HTTP::FORBIDDEN, "You aren't allowed to look at that." )
199
204
  fail "Shouldn't be reached."
@@ -213,7 +218,8 @@ describe Strelka::App do
213
218
  it "uses the specified content type for error responses" do
214
219
  # make an auth plugin that always denies requests
215
220
  forbidden_plugin = Module.new do
216
- extend Strelka::App::Plugin
221
+ def self::name; "Strelka::App::Forbidden"; end
222
+ extend Strelka::Plugin
217
223
  def handle_request( r )
218
224
  finish_with( HTTP::FORBIDDEN, "You aren't allowed to look at that.",
219
225
  :content_type => 'text/html' )
@@ -350,7 +356,8 @@ describe Strelka::App do
350
356
  it "provides a plugin hook for plugins to manipulate the request before handling it" do
351
357
  # make a fixup plugin that adds a custom x- header to the request
352
358
  header_fixup_plugin = Module.new do
353
- extend Strelka::App::Plugin
359
+ def self::name; "Strelka::App::HeaderFixup"; end
360
+ extend Strelka::Plugin
354
361
  def fixup_request( r )
355
362
  r.headers[:x_funted_by] = 'Cragnux/1.1.3'
356
363
  super
@@ -375,7 +382,8 @@ describe Strelka::App do
375
382
  it "provides a plugin hook for plugins to manipulate the response before it's returned to Mongrel2" do
376
383
  # make a fixup plugin that adds a custom x- header to the response
377
384
  header_fixup_plugin = Module.new do
378
- extend Strelka::App::Plugin
385
+ def self::name; "Strelka::App::HeaderFixup"; end
386
+ extend Strelka::Plugin
379
387
  def fixup_response( res )
380
388
  res.headers.x_funted_by = 'Cragnux/1.1.3'
381
389
  super