strelka 0.0.1pre4 → 0.0.1.pre129

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 (73) hide show
  1. data/History.rdoc +1 -1
  2. data/IDEAS.rdoc +62 -0
  3. data/Manifest.txt +38 -7
  4. data/README.rdoc +124 -5
  5. data/Rakefile +22 -6
  6. data/bin/leash +102 -157
  7. data/contrib/hoetemplate/.autotest.erb +23 -0
  8. data/contrib/hoetemplate/History.rdoc.erb +4 -0
  9. data/contrib/hoetemplate/Manifest.txt.erb +8 -0
  10. data/contrib/hoetemplate/README.rdoc.erb +17 -0
  11. data/contrib/hoetemplate/Rakefile.erb +24 -0
  12. data/contrib/hoetemplate/data/file_name/apps/file_name_app +36 -0
  13. data/contrib/hoetemplate/data/file_name/templates/layout.tmpl.erb +13 -0
  14. data/contrib/hoetemplate/data/file_name/templates/top.tmpl.erb +8 -0
  15. data/contrib/hoetemplate/lib/file_name.rb.erb +18 -0
  16. data/contrib/hoetemplate/spec/file_name_spec.rb.erb +21 -0
  17. data/data/strelka/apps/hello-world +30 -0
  18. data/lib/strelka/app/defaultrouter.rb +49 -30
  19. data/lib/strelka/app/errors.rb +121 -0
  20. data/lib/strelka/app/exclusiverouter.rb +40 -0
  21. data/lib/strelka/app/filters.rb +18 -7
  22. data/lib/strelka/app/negotiation.rb +122 -0
  23. data/lib/strelka/app/parameters.rb +171 -14
  24. data/lib/strelka/app/paramvalidator.rb +751 -0
  25. data/lib/strelka/app/plugins.rb +66 -46
  26. data/lib/strelka/app/restresources.rb +499 -0
  27. data/lib/strelka/app/router.rb +73 -0
  28. data/lib/strelka/app/routing.rb +140 -18
  29. data/lib/strelka/app/templating.rb +12 -3
  30. data/lib/strelka/app.rb +174 -24
  31. data/lib/strelka/constants.rb +0 -20
  32. data/lib/strelka/exceptions.rb +29 -0
  33. data/lib/strelka/httprequest/acceptparams.rb +377 -0
  34. data/lib/strelka/httprequest/negotiation.rb +257 -0
  35. data/lib/strelka/httprequest.rb +155 -7
  36. data/lib/strelka/httpresponse/negotiation.rb +579 -0
  37. data/lib/strelka/httpresponse.rb +140 -0
  38. data/lib/strelka/logging.rb +4 -1
  39. data/lib/strelka/mixins.rb +53 -0
  40. data/lib/strelka.rb +22 -1
  41. data/spec/data/error.tmpl +1 -0
  42. data/spec/lib/constants.rb +0 -1
  43. data/spec/lib/helpers.rb +21 -0
  44. data/spec/strelka/app/defaultrouter_spec.rb +41 -35
  45. data/spec/strelka/app/errors_spec.rb +212 -0
  46. data/spec/strelka/app/exclusiverouter_spec.rb +220 -0
  47. data/spec/strelka/app/filters_spec.rb +196 -0
  48. data/spec/strelka/app/negotiation_spec.rb +73 -0
  49. data/spec/strelka/app/parameters_spec.rb +149 -0
  50. data/spec/strelka/app/paramvalidator_spec.rb +1059 -0
  51. data/spec/strelka/app/plugins_spec.rb +26 -19
  52. data/spec/strelka/app/restresources_spec.rb +393 -0
  53. data/spec/strelka/app/router_spec.rb +63 -0
  54. data/spec/strelka/app/routing_spec.rb +183 -9
  55. data/spec/strelka/app/templating_spec.rb +1 -2
  56. data/spec/strelka/app_spec.rb +265 -32
  57. data/spec/strelka/exceptions_spec.rb +53 -0
  58. data/spec/strelka/httprequest/acceptparams_spec.rb +282 -0
  59. data/spec/strelka/httprequest/negotiation_spec.rb +246 -0
  60. data/spec/strelka/httprequest_spec.rb +204 -14
  61. data/spec/strelka/httpresponse/negotiation_spec.rb +464 -0
  62. data/spec/strelka/httpresponse_spec.rb +114 -0
  63. data/spec/strelka/mixins_spec.rb +99 -0
  64. data.tar.gz.sig +1 -0
  65. metadata +175 -79
  66. metadata.gz.sig +2 -0
  67. data/IDEAS.textile +0 -174
  68. data/data/strelka/apps/strelka-admin +0 -65
  69. data/data/strelka/apps/strelka-setup +0 -26
  70. data/data/strelka/bootstrap-config.rb +0 -34
  71. data/data/strelka/templates/admin/console.tmpl +0 -21
  72. data/data/strelka/templates/layout.tmpl +0 -30
  73. data/lib/strelka/process.rb +0 -19
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pluginfactory'
4
+
5
+ require 'strelka' unless defined?( Strelka )
6
+ require 'strelka/app' unless defined?( Strelka::App )
7
+ require 'strelka/mixins'
8
+
9
+ # Abstract base class for pluggable routing strategies for the Routing
10
+ # plugin.
11
+ #
12
+ # This class can't be instantiated itself, but it does act as a factory for
13
+ # loading and instantiating its subclasses:
14
+ #
15
+ # # Create an instance of the default router strategy with the given
16
+ # # routes and options.
17
+ # Strelka::App::Router.create( 'default', routes, options )
18
+ #
19
+ # To define your own strategy, you'll need to inherit this class, name it
20
+ # <tt>Strelka::App::{Something}Router</tt>, save it in a file named
21
+ # <tt>strelka/app/{something}router.rb</tt>, and be sure to override the
22
+ # #add_route and #route_request methods.
23
+ class Strelka::App::Router
24
+ include PluginFactory,
25
+ Strelka::Loggable,
26
+ Strelka::AbstractClass
27
+
28
+ ### PluginFactory API -- return the Array of directories to search for plugins.
29
+ def self::derivative_dirs
30
+ return ['strelka/app']
31
+ end
32
+
33
+
34
+ ### Create a new router that will route requests according to the specified
35
+ ### +routes+.
36
+ ###
37
+ ### If the optional +options+ hash is specified, it is passed to the router
38
+ ### strategy.
39
+ def initialize( routes=[], options={} )
40
+ routes.each do |tuple|
41
+ self.log.debug " adding route: %p" % [ tuple ]
42
+ self.add_route( *tuple )
43
+ end
44
+ end
45
+
46
+
47
+ ######
48
+ public
49
+ ######
50
+
51
+ ##
52
+ # :call-seq:
53
+ # add_route( http_verb, path_array, routing_info )
54
+ #
55
+ # Add a route for the specified +http_verb+, +path_array+, and +routing_info+. The
56
+ # +http_verb+ will be one of the methods from
57
+ # {RFC 2616}[http://tools.ietf.org/html/rfc2616#section-9] as a Symbol (e.g.,
58
+ # +:GET+, +:DELETE+). The +path_array+ will be the route path split up by
59
+ # path separator. The +routing_info+ is a Hash that contains the action
60
+ # that will be run when the route matches, routing options, and any other
61
+ # routing information associated with the route.
62
+ pure_virtual :add_route
63
+
64
+
65
+ ##
66
+ # :call-seq:
67
+ # route_request( request )
68
+ #
69
+ # Determine the most-specific route for the specified +request+ and return
70
+ # the routing info Hash.
71
+ pure_virtual :route_request
72
+
73
+ end # class Strelka::App::Router
@@ -2,11 +2,81 @@
2
2
 
3
3
  require 'strelka' unless defined?( Strelka )
4
4
  require 'strelka/app' unless defined?( Strelka::App )
5
- require 'strelka/app/defaultrouter' unless defined?( Strelka::App::DefaultRouter )
6
5
 
6
+ require 'strelka/app/router'
7
+ require 'strelka/exceptions'
7
8
  require 'strelka/app/plugins'
8
9
 
9
- # Default routing logic for Strelka::Apps
10
+ # Sinatra-ish routing logic for Strelka::Apps
11
+ #
12
+ # This plugin adds the ability to declare hooks for requests based on their
13
+ # attributes. The default router (Strelka::App::DefaultRouter) uses only the
14
+ # HTTP verb and the path, but you can also define your own router class if
15
+ # you want to include other attributes.
16
+ #
17
+ # You declare a hook using the HTTP verb, followed by the path, followed by
18
+ # a Hash of options and a block that will be called when a matching request
19
+ # is handled.
20
+ #
21
+ # When two or more hooks match the same request, the most-specific match
22
+ # wins. The mongrel2 route part of the path is stripped before comparing.
23
+ #
24
+ # The hooks are given a Strelka::Request object, and are expected to
25
+ # return either a Strelka::Response or something that can be made
26
+ # into one.
27
+ #
28
+ # == Examples
29
+ #
30
+ # class HelloWorld < Strelka::App
31
+ #
32
+ # plugins :routing
33
+ #
34
+ # # match any GET request
35
+ # get do |req|
36
+ # return req.response << 'Hello, World!'
37
+ # end
38
+ #
39
+ # # match any GET request whose path starts with '/goodbye'
40
+ # get '/goodbye' do |req|
41
+ # return req.response << "Goodbye, cruel World!"
42
+ # end
43
+ #
44
+ #
45
+ # end # class HelloWorld
46
+ #
47
+ # == Routing Strategies
48
+ #
49
+ # The algorithm used to map requests to routes are defined by an object
50
+ # that implements the Strategy pattern. These routing strategies are pluggable,
51
+ # so if Mongrel2's the "longest-match wins" routing isn't to your taste,
52
+ # you can specify a different one using the +router+ declaration. Strelka
53
+ # comes with one alternative "exclusive" router that implements a more
54
+ # restrictive mapping:
55
+ #
56
+ # class ExclusiveHelloWorld < Strelka::App
57
+ #
58
+ # plugins :routing
59
+ # router :exclusive
60
+ #
61
+ # # match a GET request for the exact route only
62
+ # get do |req|
63
+ # return req.response << 'Hello, World!'
64
+ # end
65
+ #
66
+ # # only match a GET request for '/goodbye'
67
+ # get '/goodbye' do |req|
68
+ # return req.response << "Goodbye, cruel World!"
69
+ # end
70
+ #
71
+ # # Every other request responds with a 404
72
+ #
73
+ # end # class ExclusiveHelloWorld
74
+ #
75
+ # == Custom Routers
76
+ #
77
+ # See the Strelka::App::Router for information on how to define your own
78
+ # routing strategies.
79
+ #
10
80
  module Strelka::App::Routing
11
81
  extend Strelka::App::Plugin
12
82
  include Strelka::Loggable,
@@ -16,20 +86,35 @@ module Strelka::App::Routing
16
86
 
17
87
 
18
88
  # Class methods to add to classes with routing.
19
- module ClassMethods
89
+ module ClassMethods # :nodoc:
20
90
 
21
91
  # The list of routes to pass to the Router when the application is created
22
92
  attr_reader :routes
23
93
  @routes = []
24
94
 
25
- # The class of object to instantiate for routing
95
+ # The name of the routing strategy class to use
26
96
  attr_accessor :routerclass
27
- @routerclass = Strelka::App::DefaultRouter
97
+ @routerclass = :default
28
98
 
29
99
 
30
100
  ### Return a Hash of the methods defined by routes.
31
101
  def route_methods
32
- return self.instance_methods.grep( /^#{HTTP::RFC2616_VERB_REGEX}\b/ )
102
+ return self.instance_methods.grep( /^#{HTTP::RFC2616_VERB_REGEX}(_|$)/ )
103
+ end
104
+
105
+
106
+ ### Returns +true+ if the app has a route for the specified +verb+ and +path+.
107
+ def has_route?( http_verb, path )
108
+ path_pattern = self.split_route_pattern( path )
109
+ self.routes.find {|tuple| tuple[0] == http_verb && tuple[1] == path_pattern }
110
+ end
111
+
112
+
113
+ # OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT
114
+
115
+ ### Define a route for the OPTIONS verb and the given +pattern+.
116
+ def options( pattern='', options={}, &block )
117
+ self.add_route( :OPTIONS, pattern, options, &block )
33
118
  end
34
119
 
35
120
 
@@ -45,10 +130,34 @@ module Strelka::App::Routing
45
130
  end
46
131
 
47
132
 
133
+ ### Define a route for the PUT verb and the given +pattern+.
134
+ def put( pattern='', options={}, &block )
135
+ self.add_route( :PUT, pattern, options, &block )
136
+ end
137
+
138
+
139
+ ### Define a route for the DELETE verb and the given +pattern+.
140
+ def delete( pattern='', options={}, &block )
141
+ self.add_route( :DELETE, pattern, options, &block )
142
+ end
143
+
144
+
145
+ ### Define a route for the TRACE verb and the given +pattern+.
146
+ def trace( pattern='', options={}, &block )
147
+ self.add_route( :TRACE, pattern, options, &block )
148
+ end
149
+
150
+
151
+ ### Define a route for the CONNECT verb.
152
+ def connect( options={}, &block )
153
+ self.add_route( :CONNECT, '', options, &block )
154
+ end
155
+
156
+
48
157
  ### Get/set the router class to use for mapping requests to handlers to +newclass.
49
158
  def router( newclass=nil )
50
159
  if newclass
51
- Strelka.log.info "%p will use the %p router" % [ self, newclass ]
160
+ Strelka.log.info "%p router class set to: %p" % [ self, newclass ]
52
161
  self.routerclass = newclass
53
162
  end
54
163
 
@@ -82,9 +191,19 @@ module Strelka::App::Routing
82
191
  Strelka.log.debug " adding route method %p for %p route: %p" % [ methodname, verb, block ]
83
192
  define_method( methodname, &block )
84
193
 
85
- # Now add all the parts to the routes array for the router created by
86
- # instances
87
- self.routes << [ verb, patternparts, self.instance_method(methodname), options ]
194
+ # Remove any existing route for the same verb, patternparts, and options
195
+ # (support for overriding inherited routes)
196
+ self.routes.delete_if do |r|
197
+ r[0] == verb && r[1] == patternparts && r[2][:options] == options
198
+ end
199
+
200
+ # Now add all the parts to the routes array for the router created by
201
+ # instances
202
+ self.routes << [
203
+ verb,
204
+ patternparts,
205
+ {:action => self.instance_method(methodname), :options => options}
206
+ ]
88
207
  end
89
208
 
90
209
 
@@ -93,12 +212,12 @@ module Strelka::App::Routing
93
212
  pattern.slice!( 0, 1 ) if pattern.start_with?( '/' )
94
213
 
95
214
  return pattern.split( '/' ).collect do |component|
96
- # Map patterns to their parameter constraint regex
215
+
97
216
  if component.start_with?( ':' )
98
- Strelka.log.debug " searching for a param for %p" % [ component ]
99
- param = self.parameters[ component[1..-1].to_sym ] or
100
- raise ScriptError, "no parameter %p defined" % [ component ]
101
- param[ :constraint ]
217
+ raise ScriptError,
218
+ "parameter-based routing not supported without a 'parameters' plugin" unless
219
+ self.respond_to?( :extract_route_from_constraint )
220
+ self.extract_route_from_constraint( component )
102
221
  else
103
222
  component
104
223
  end
@@ -118,7 +237,7 @@ module Strelka::App::Routing
118
237
  ### Create a new router object for each class with Routing.
119
238
  def initialize( * )
120
239
  super
121
- @router ||= self.class.routerclass.new( self.class.routes )
240
+ @router ||= Strelka::App::Router.create( self.class.routerclass, self.class.routes )
122
241
  end
123
242
 
124
243
 
@@ -128,8 +247,11 @@ module Strelka::App::Routing
128
247
 
129
248
  ### Dispatch the request using the Router.
130
249
  def handle_request( request, &block )
131
- if handler = self.router.route_request( request )
132
- return handler.bind( self ).call( request )
250
+ if route = self.router.route_request( request )
251
+ # Track which route was chosen for later plugins
252
+ request.notes[:routing][:route] = route
253
+ # Bind the action of the route and call it
254
+ return route[:action].bind( self ).call( request, &block )
133
255
  else
134
256
  finish_with HTTP::NOT_FOUND, "The requested resource was not found on this server."
135
257
  end
@@ -13,11 +13,12 @@ module Strelka::App::Templating
13
13
  include Strelka::Constants
14
14
  extend Strelka::App::Plugin
15
15
 
16
- run_before :routing, :filters
16
+ run_before :routing, :negotiation
17
+ run_after :filters
17
18
 
18
19
 
19
20
  # Class methods to add to classes with templating.
20
- module ClassMethods
21
+ module ClassMethods # :nodoc:
21
22
 
22
23
  # The map of template names to template file paths.
23
24
  @template_map = {}
@@ -100,7 +101,7 @@ module Strelka::App::Templating
100
101
  ### 2. An Inversion::Template by itself.
101
102
  ### 3. A Symbol that matches one of the keys of the registered templates.
102
103
  ###
103
- ### In all three of these cases, the return value will be a Mongrel2::Request with a
104
+ ### In all three of these cases, the return value will be a Mongrel2::Response with a
104
105
  ### body set to the rendered value of the template in question, and with its status
105
106
  ### set to '200 OK' unless it is already set to something else.
106
107
  ###
@@ -111,6 +112,7 @@ module Strelka::App::Templating
111
112
  ### Every other response is returned without modification.
112
113
  def handle_request( request, &block )
113
114
  response = super
115
+
114
116
  self.log.debug "Templating: examining %p response." % [ response.class ]
115
117
  template = nil
116
118
 
@@ -145,6 +147,13 @@ module Strelka::App::Templating
145
147
  template = l_template
146
148
  end
147
149
 
150
+ # Set some default stuff on the top-level template
151
+ template.request = request
152
+ template.strelka_version = Strelka.version_string( true )
153
+ template.mongrel2_version = Mongrel2.version_string( true )
154
+ template.route = request.notes[:routing][:route]
155
+
156
+ # Now render the response body
148
157
  self.log.debug " rendering the template into the response body"
149
158
  response.body = template.render
150
159
  response.status ||= HTTP::OK
data/lib/strelka/app.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'rubygems' # For the Rubygems API
4
+
3
5
  require 'mongrel2/handler'
4
6
  require 'strelka' unless defined?( Strelka )
5
7
 
@@ -14,13 +16,147 @@ class Strelka::App < Mongrel2::Handler
14
16
  include Strelka::App::Plugins
15
17
 
16
18
 
17
- @default_content_type = nil
19
+ # Glob for matching Strelka apps relative to a gem's data directory
20
+ APP_GLOB_PATTERN = '{apps,handlers}/**/*'
21
+
22
+
23
+ # Class instance variables
24
+ @default_type = nil
25
+ @loading_file = nil
26
+ @subclasses = Hash.new {|h,k| h[k] = [] }
27
+
28
+
29
+ # The Hash of Strelka::App subclasses, keyed by the Pathname of the file they were
30
+ # loaded from, or +nil+ if they weren't loaded via ::load.
31
+ class << self; attr_reader :subclasses; end
32
+
33
+
34
+ ### Inheritance callback -- add subclasses to @subclasses so .load can figure out which
35
+ ### classes correspond to which files.
36
+ def self::inherited( subclass )
37
+ super
38
+ @subclasses[ @loading_file ] << subclass if self == Strelka::App
39
+ end
40
+
41
+
42
+ ### Overridden from Mongrel2::Handler -- use the value returned from .default_appid if
43
+ ### one is not specified, and automatically install the config DB if it hasn't been
44
+ ### already.
45
+ def self::run( appid=nil )
46
+ appid ||= self.default_appid
47
+
48
+ Strelka.logger.level = Logger::DEBUG if $VERBOSE
49
+
50
+ super( appid )
51
+
52
+ end
53
+
54
+
55
+ ### Calculate a default application ID for the class based on either its ID
56
+ ### constant or its name and return it.
57
+ def self::default_appid
58
+ Strelka.log.info "Looking up appid for %p" % [ self.class ]
59
+ appid = nil
60
+
61
+ if self.const_defined?( :ID )
62
+ appid = self.const_get( :ID )
63
+ Strelka.log.info " app has an ID: %p" % [ appid ]
64
+ else
65
+ appid = ( self.name || "anonymous#{self.object_id}" ).downcase
66
+ appid.gsub!( /[^[:alnum:]]+/, '-' )
67
+ Strelka.log.info " deriving one from the class name: %p" % [ appid ]
68
+ end
69
+
70
+ return appid
71
+ end
72
+
73
+
74
+ ### Return a Hash of Strelka app files as Pathname objects from installed gems,
75
+ ### keyed by gemspec name .
76
+ def self::discover_paths
77
+ appfiles = {}
78
+
79
+ # Find all the gems that depend on Strelka
80
+ gems = Gem::Specification.find_all do |gemspec|
81
+ gemspec.dependencies.find {|dep| dep.name == 'strelka'}
82
+ end
83
+
84
+ Strelka.log.debug "Found %d gems with a Strelka dependency" % [ gems.length ]
85
+
86
+ # Find all the files under those gems' data directories that match the application
87
+ # pattern
88
+ gems.sort.reverse.each do |gemspec|
89
+ # Only look at the latest version of the gem
90
+ next if appfiles.key?( gemspec.name )
91
+ appfiles[ gemspec.name ] = []
92
+
93
+ Strelka.log.debug " checking %s for apps in its datadir" % [ gemspec.name ]
94
+ pattern = File.join( gemspec.full_gem_path, "data", gemspec.name, APP_GLOB_PATTERN )
95
+ Strelka.log.debug " glob pattern is: %p" % [ pattern ]
96
+ gemapps = Pathname.glob( pattern )
97
+ Strelka.log.debug " found %d app files" % [ gemapps.length ]
98
+ appfiles[ gemspec.name ] += gemapps
99
+ end
100
+
101
+ return appfiles
102
+ end
103
+
104
+
105
+ ### Return an Array of Strelka::App classes loaded from the installed Strelka gems.
106
+ def self::discover
107
+ discovered_apps = []
108
+ app_paths = self.discover_paths
109
+
110
+ Strelka.log.debug "Loading apps from %d discovered paths" % [ app_paths.length ]
111
+ app_paths.each do |gemname, paths|
112
+ Strelka.log.debug " loading gem %s" % [ gemname ]
113
+ gem( gemname )
114
+
115
+ Strelka.log.debug " loading apps from %s: %d handlers" % [ gemname, paths.length ]
116
+ paths.each do |path|
117
+ classes = begin
118
+ Strelka::App.load( path )
119
+ rescue StandardError, ScriptError => err
120
+ Strelka.log.error "%p while loading Strelka apps from %s: %s" %
121
+ [ err.class, path, err.message ]
122
+ Strelka.log.debug "Backtrace: %s" % [ err.backtrace.join("\n\t") ]
123
+ []
124
+ end
125
+ Strelka.log.debug " loaded app classes: %p" % [ classes ]
126
+
127
+ discovered_apps += classes
128
+ end
129
+ end
130
+
131
+ return discovered_apps
132
+ end
133
+
134
+
135
+ ### Load the specified +file+, and return any Strelka::App subclasses that are loaded
136
+ ### as a result.
137
+ def self::load( file )
138
+ Strelka.log.debug "Loading application/s from %p" % [ file ]
139
+ @loading_file = Pathname( file ).expand_path
140
+ self.subclasses.delete( @loading_file )
141
+ Kernel.load( @loading_file.to_s )
142
+ new_subclasses = self.subclasses[ @loading_file ]
143
+ Strelka.log.debug " loaded %d new app class/es" % [ new_subclasses.size ]
144
+
145
+ return new_subclasses
146
+ ensure
147
+ @loading_file = nil
148
+ end
149
+
150
+
151
+ #
152
+ # :section: Application declarative methods
153
+ #
18
154
 
19
155
  ### Get/set the Content-type of requests that don't set one. Leaving this unset will
20
156
  ### leave the Content-type unset.
21
- def self::default_content_type( newtype=nil )
22
- @default_content_type = newtype if newtype
23
- return @default_content_type
157
+ def self::default_type( newtype=nil )
158
+ @default_type = newtype if newtype
159
+ return @default_type
24
160
  end
25
161
 
26
162
 
@@ -32,17 +168,28 @@ class Strelka::App < Mongrel2::Handler
32
168
  public
33
169
  ######
34
170
 
171
+ ### Run the app -- overriden to set the process name to something interesting.
172
+ def run
173
+ procname = "%p %s" % [ self.class, self.conn ]
174
+ $0 = procname
175
+
176
+ super
177
+ end
178
+
179
+
35
180
  ### The main Mongrel2 entrypoint -- accept Strelka::Requests and return
36
181
  ### Strelka::Responses.
37
182
  def handle( request )
38
183
  response = nil
39
184
 
40
- # Run fixup hooks on the request
41
- request = self.fixup_request( request )
42
-
43
185
  # Dispatch the request after allowing plugins to to their thing
44
186
  status_info = catch( :finish ) do
187
+
188
+ # Run fixup hooks on the request
189
+ request = self.fixup_request( request )
45
190
  response = self.handle_request( request )
191
+ response = self.fixup_response( response )
192
+
46
193
  nil # rvalue for the catch
47
194
  end
48
195
 
@@ -50,23 +197,15 @@ class Strelka::App < Mongrel2::Handler
50
197
  if status_info
51
198
  self.log.debug "Preparing a status response: %p" % [ status_info ]
52
199
  return self.prepare_status_response( request, status_info )
53
-
54
- # Normal response
55
- else
56
- self.log.debug "Preparing a regular response: %p" % [ response ]
57
- response ||= request.response
58
- return self.fixup_response( request, response )
59
200
  end
60
201
 
202
+ return response
61
203
  rescue => err
62
- # Propagate Spec failures
63
- raise if err.class.name =~ /^RSpec::/
64
-
65
204
  msg = "%s: %s %s" % [ err.class.name, err.message, err.backtrace.first ]
66
205
  self.log.error( msg )
67
206
  err.backtrace[ 1..-1 ].each {|frame| self.log.debug(' ' + frame) }
68
207
 
69
- status_info = { :status => HTTP::SERVER_ERROR, :message => msg }
208
+ status_info = { :status => HTTP::SERVER_ERROR, :message => 'internal server error' }
70
209
  return self.prepare_status_response( request, status_info )
71
210
  end
72
211
 
@@ -105,24 +244,35 @@ class Strelka::App < Mongrel2::Handler
105
244
  ### Mongrel and return it. This is an alternate extension-point for plugins that
106
245
  ### wish to modify or replace the response after the whole request cycle is
107
246
  ### completed.
108
- def fixup_response( request, response )
247
+ def fixup_response( response )
109
248
  self.log.debug "Fixing up response: %p" % [ response ]
110
249
  self.fixup_response_content_type( response )
111
- self.fixup_head_response( response ) if request.verb == :HEAD
250
+ self.fixup_head_response( response ) if
251
+ response.request && response.request.verb == :HEAD
112
252
  self.log.debug " after fixup: %p" % [ response ]
113
253
 
114
- super
254
+ return super
115
255
  end
116
256
 
117
257
 
118
258
  ### If the +response+ doesn't yet have a Content-type header, and the app has
119
- ### defined a default (via App.default_content_type), set it to the default.
259
+ ### defined a default (via App.default_type), set it to the default.
120
260
  def fixup_response_content_type( response )
261
+
262
+ # Make the error for returning something other than a Response object a little
263
+ # nicer.
264
+ unless response.respond_to?( :content_type )
265
+ self.log.error "expected response (%p, a %p) to respond to #content_type" %
266
+ [ response, response.class ]
267
+ finish_with( HTTP::SERVER_ERROR, "malformed response" )
268
+ end
269
+
121
270
  restype = response.content_type
122
271
 
123
272
  if !restype
124
- if (( default = self.class.default_content_type ))
125
- self.log.debug "Setting default content type"
273
+ if (( default = self.class.default_type ))
274
+ self.log.debug "Setting content type of the response to the default: %p" %
275
+ [ default ]
126
276
  response.content_type = default
127
277
  else
128
278
  self.log.debug "No default content type"
@@ -164,7 +314,7 @@ class Strelka::App < Mongrel2::Handler
164
314
  # Some status codes allow explanatory text to be returned; some forbid it. Append the
165
315
  # message for those that allow one.
166
316
  unless request.verb == :HEAD || HTTP::BODILESS_HTTP_RESPONSE_CODES.include?( status_code )
167
- response.content_type = 'text/plain'
317
+ response.content_type = status_info[ :content_type ] || 'text/plain'
168
318
  response.puts( message )
169
319
  end
170
320
 
@@ -11,14 +11,6 @@ module Strelka::Constants
11
11
  # Import Mongrel2's constants, too
12
12
  include Mongrel2::Constants
13
13
 
14
- # Override the path to the default Sqlite configuration database
15
- # remove_const( :DEFAULT_CONFIG_URI )
16
- DEFAULT_CONFIG_URI = 'strelka.sqlite'
17
-
18
- # The admin server port
19
- DEFAULT_ADMIN_PORT = 7337
20
-
21
-
22
14
  # Extend Mongrel2's HTTP constants collection
23
15
  module HTTP
24
16
  include Mongrel2::Constants::HTTP
@@ -35,18 +27,6 @@ module Strelka::Constants
35
27
  # The list of HTTP verbs considered "idempotent"
36
28
  IDEMPOTENT_RFC2616_VERBS = %w[OPTIONS GET HEAD PUT DELETE TRACE]
37
29
 
38
- # A registry of HTTP status codes that don't allow an entity body
39
- # in the response.
40
- BODILESS_HTTP_RESPONSE_CODES = [
41
- CONTINUE,
42
- SWITCHING_PROTOCOLS,
43
- PROCESSING,
44
- NO_CONTENT,
45
- RESET_CONTENT,
46
- NOT_MODIFIED,
47
- USE_PROXY,
48
- ]
49
-
50
30
  end # module HTTP
51
31
 
52
32
  end # module Strelka::Constants
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/ruby
2
+ #encoding: utf-8
3
+
4
+ # Exception types used by Strelka classes.
5
+
6
+ #--
7
+ module Strelka
8
+
9
+ # A base exception class.
10
+ class Error < ::RuntimeError; end
11
+
12
+ # An exception that's raised when there's a problem with a Request.
13
+ class RequestError < Error
14
+ ### Create a new RequestError for the specified +request+ object.
15
+ def initialize( request, message, *args )
16
+ @request = request
17
+ super( message, *args )
18
+ end
19
+
20
+ # The request that caused the exception
21
+ attr_reader :request
22
+
23
+ end # class RequestError
24
+
25
+ # An exception raised when there is a problem with an application plugin.
26
+ class PluginError < Error; end
27
+
28
+ end # module Strelka
29
+