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.
- data/History.rdoc +1 -1
- data/IDEAS.rdoc +62 -0
- data/Manifest.txt +38 -7
- data/README.rdoc +124 -5
- data/Rakefile +22 -6
- data/bin/leash +102 -157
- data/contrib/hoetemplate/.autotest.erb +23 -0
- data/contrib/hoetemplate/History.rdoc.erb +4 -0
- data/contrib/hoetemplate/Manifest.txt.erb +8 -0
- data/contrib/hoetemplate/README.rdoc.erb +17 -0
- data/contrib/hoetemplate/Rakefile.erb +24 -0
- data/contrib/hoetemplate/data/file_name/apps/file_name_app +36 -0
- data/contrib/hoetemplate/data/file_name/templates/layout.tmpl.erb +13 -0
- data/contrib/hoetemplate/data/file_name/templates/top.tmpl.erb +8 -0
- data/contrib/hoetemplate/lib/file_name.rb.erb +18 -0
- data/contrib/hoetemplate/spec/file_name_spec.rb.erb +21 -0
- data/data/strelka/apps/hello-world +30 -0
- data/lib/strelka/app/defaultrouter.rb +49 -30
- data/lib/strelka/app/errors.rb +121 -0
- data/lib/strelka/app/exclusiverouter.rb +40 -0
- data/lib/strelka/app/filters.rb +18 -7
- data/lib/strelka/app/negotiation.rb +122 -0
- data/lib/strelka/app/parameters.rb +171 -14
- data/lib/strelka/app/paramvalidator.rb +751 -0
- data/lib/strelka/app/plugins.rb +66 -46
- data/lib/strelka/app/restresources.rb +499 -0
- data/lib/strelka/app/router.rb +73 -0
- data/lib/strelka/app/routing.rb +140 -18
- data/lib/strelka/app/templating.rb +12 -3
- data/lib/strelka/app.rb +174 -24
- data/lib/strelka/constants.rb +0 -20
- data/lib/strelka/exceptions.rb +29 -0
- data/lib/strelka/httprequest/acceptparams.rb +377 -0
- data/lib/strelka/httprequest/negotiation.rb +257 -0
- data/lib/strelka/httprequest.rb +155 -7
- data/lib/strelka/httpresponse/negotiation.rb +579 -0
- data/lib/strelka/httpresponse.rb +140 -0
- data/lib/strelka/logging.rb +4 -1
- data/lib/strelka/mixins.rb +53 -0
- data/lib/strelka.rb +22 -1
- data/spec/data/error.tmpl +1 -0
- data/spec/lib/constants.rb +0 -1
- data/spec/lib/helpers.rb +21 -0
- data/spec/strelka/app/defaultrouter_spec.rb +41 -35
- data/spec/strelka/app/errors_spec.rb +212 -0
- data/spec/strelka/app/exclusiverouter_spec.rb +220 -0
- data/spec/strelka/app/filters_spec.rb +196 -0
- data/spec/strelka/app/negotiation_spec.rb +73 -0
- data/spec/strelka/app/parameters_spec.rb +149 -0
- data/spec/strelka/app/paramvalidator_spec.rb +1059 -0
- data/spec/strelka/app/plugins_spec.rb +26 -19
- data/spec/strelka/app/restresources_spec.rb +393 -0
- data/spec/strelka/app/router_spec.rb +63 -0
- data/spec/strelka/app/routing_spec.rb +183 -9
- data/spec/strelka/app/templating_spec.rb +1 -2
- data/spec/strelka/app_spec.rb +265 -32
- data/spec/strelka/exceptions_spec.rb +53 -0
- data/spec/strelka/httprequest/acceptparams_spec.rb +282 -0
- data/spec/strelka/httprequest/negotiation_spec.rb +246 -0
- data/spec/strelka/httprequest_spec.rb +204 -14
- data/spec/strelka/httpresponse/negotiation_spec.rb +464 -0
- data/spec/strelka/httpresponse_spec.rb +114 -0
- data/spec/strelka/mixins_spec.rb +99 -0
- data.tar.gz.sig +1 -0
- metadata +175 -79
- metadata.gz.sig +2 -0
- data/IDEAS.textile +0 -174
- data/data/strelka/apps/strelka-admin +0 -65
- data/data/strelka/apps/strelka-setup +0 -26
- data/data/strelka/bootstrap-config.rb +0 -34
- data/data/strelka/templates/admin/console.tmpl +0 -21
- data/data/strelka/templates/layout.tmpl +0 -30
- 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
|
data/lib/strelka/app/routing.rb
CHANGED
@@ -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
|
-
#
|
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
|
95
|
+
# The name of the routing strategy class to use
|
26
96
|
attr_accessor :routerclass
|
27
|
-
@routerclass =
|
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}
|
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
|
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
|
-
#
|
86
|
-
#
|
87
|
-
self.routes
|
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
|
-
|
215
|
+
|
97
216
|
if component.start_with?( ':' )
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
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
|
132
|
-
|
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, :
|
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::
|
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
|
-
|
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::
|
22
|
-
@
|
23
|
-
return @
|
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 =>
|
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(
|
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
|
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.
|
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.
|
125
|
-
self.log.debug "Setting
|
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
|
|
data/lib/strelka/constants.rb
CHANGED
@@ -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
|
+
|