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.
- 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
|
+
|