strelka 0.0.1pre4 → 0.0.1.pre129

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