scorched 0.11.1 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{Milestones.md → CHANGES.md} +7 -26
- data/Gemfile +1 -1
- data/README.md +2 -7
- data/TODO.md +17 -0
- data/docs/02_fundamentals/03_routing.md +1 -1
- data/lib/scorched/controller.rb +82 -17
- data/lib/scorched/error.rb +1 -2
- data/lib/scorched/response.rb +15 -9
- data/lib/scorched/version.rb +1 -1
- data/scorched.gemspec +6 -5
- data/spec/controller_spec.rb +12 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83445d0e97b4717ff0d830c7e72f09bf0c8f8fa1
|
4
|
+
data.tar.gz: eb90cc0893a4b53d180d7dca083f04340026b964
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d90ea66b9a3a280fa2746d0a9cf12a0697c6169e035fdcba9753612bf3297bd01c87b4ca7fa286740c24441e6ec539105cb4621c99a4b28facacdfe2deedeade
|
7
|
+
data.tar.gz: db597065e5a9d1dd93be4a738f998bea0cc85be0b057cd8b107eaad98f5932cc4ebf51aefc03a7b11c5ee412672f2c695e687e755cab5ed69c5f1c93f200d522
|
@@ -1,10 +1,12 @@
|
|
1
|
-
Milestones
|
2
|
-
==========
|
3
|
-
|
4
1
|
Changelog
|
5
|
-
|
2
|
+
=========
|
3
|
+
|
4
|
+
### v0.12
|
5
|
+
* Halt can now take an optional response body (typically a string).
|
6
|
+
* Controller now returns a valid rack array, rather than a Scorched::Response.
|
7
|
+
|
6
8
|
### v0.11.1
|
7
|
-
* Fixed issue where
|
9
|
+
* Fixed an issue where subsequent nested render calls would render the default layout, which they shouldn't (issue #9).
|
8
10
|
* Bumped Tilt dependancy to v1.4 and removed work-around for Tilt encoding issue.
|
9
11
|
|
10
12
|
### v0.11
|
@@ -93,24 +95,3 @@ Changelog
|
|
93
95
|
* Mechanism for handling exceptions in routes and before/after filters.
|
94
96
|
* Added static resource serving. E.g. public folder.
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
-
To Do
|
99
|
-
-----
|
100
|
-
Some of these remaining features may be reconsidered and either left out, or put into some kind of contrib library.
|
101
|
-
|
102
|
-
* If one or more matches are found, but their conditions don't pass, a 403 should be returned instead of a 404.
|
103
|
-
* Make specs for Collection and Options classes more thorough, e.g. test all non-reading modifiers such as clear, delete, etc.
|
104
|
-
|
105
|
-
|
106
|
-
Unlikely
|
107
|
-
--------
|
108
|
-
These features are unlikely to be implemented unless someone provides a good reason.
|
109
|
-
|
110
|
-
* Mutex locking option - I'm of the opinion that the web server should be configured for the concurrency model of the application, rather than the framework.
|
111
|
-
* Using Rack::Protection by default - The problem here is that a good portion of Rack::Protection involves sessions, and given that Scorched doesn't itself load any session middleware, these components of Rack::Protection would have to be excluded. I wouldn't want to invoke a false sense of security
|
112
|
-
* Filter priorities - They're technically possible, but I believe it would introduce the potential for _filter hell_; badly written filters and mass confusion. Filter order has to be logical and predictable. Adding prioritisation would undermine that, and make for lazy use of filters. By not having prioritisation, there's incentive to design filters to be order-agnostic.
|
113
|
-
* Verbose logging - I originally intended to add some form of debug-style logging to show the complete flow of a request as it traverses through filters and controllers, etc. For a couple of reasons, I've decided to leave this out of Scorched. For those unfamiliar with the order in which filters and routes are invoked, it's better to learn through first-hand experience writing little test applications, rather than depending on debug logging.
|
114
|
-
|
115
|
-
|
116
|
-
More things will be added as they're thought of and considered.
|
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
gemspec
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
Scorched is a generic, unopinionated, DRY, light-weight web framework for Ruby. It provides a generic yet powerful set of constructs for processing HTTP requests, with which websites and applications of almost any scale can be built.
|
5
5
|
|
6
|
-
If you've used a light-weight DSL-based Ruby web framework before, such as Sinatra,
|
6
|
+
If you've used a light-weight DSL-based Ruby web framework before, such as Sinatra, Scorched should look quite familiar. Scorched is a true evolutionary enhancement of Sinatra, with more power, focus, and less clutter.
|
7
7
|
|
8
8
|
Getting Started
|
9
9
|
---------------
|
@@ -35,7 +35,7 @@ $ rackup hello_world.ru
|
|
35
35
|
|
36
36
|
#### A Note on Requirements
|
37
37
|
|
38
|
-
Scorched requires Ruby 2.0
|
38
|
+
Scorched requires Ruby 2.0 or above. It's important to ensure your version of Ruby 2.0 includes [changeset 39919](http://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39919) in order to avoid suffering from [random segmentation faults](http://bugs.ruby-lang.org/issues/8100). Ruby 2.0.0-p195 and newer include the fix, otherwise you can manually patch it during installation.
|
39
39
|
|
40
40
|
The Errors of Our Past
|
41
41
|
----------------------
|
@@ -107,11 +107,6 @@ This API shouldn't look too foreign to anyone familiar with frameworks like Sina
|
|
107
107
|
* Conditions - Conditions are merely procs defined on the controller which are inherited (and can be overriden) by child controllers. When a request comes in, mappings that match the requested URL, first have their conditions evaluated in the context of the controller instance, before control is handed off to the target associated with that mapping. It's a very simple implementation that comes with a lot of flexibility.
|
108
108
|
|
109
109
|
|
110
|
-
Development Progress
|
111
|
-
--------------------
|
112
|
-
Please refer to [Milestones.md](Milestones.md) for a breakdown of development progress.
|
113
|
-
|
114
|
-
|
115
110
|
Links
|
116
111
|
-----
|
117
112
|
* [Website](http://scorchedrb.com)
|
data/TODO.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
To Do
|
2
|
+
=====
|
3
|
+
* If one or more matches are found, but their conditions don't pass, a 403 should be returned instead of a 404.
|
4
|
+
* Make specs for Collection and Options classes more thorough, e.g. test all non-reading modifiers such as clear, delete, etc.
|
5
|
+
|
6
|
+
|
7
|
+
Unlikely
|
8
|
+
========
|
9
|
+
These features are unlikely to be implemented unless someone provides a good reason.
|
10
|
+
|
11
|
+
* Mutex locking option - I'm of the opinion that the web server should be configured for the concurrency model of the application, rather than the framework.
|
12
|
+
* Using Rack::Protection by default - The problem here is that a good portion of Rack::Protection involves sessions, and given that Scorched doesn't itself load any session middleware, these components of Rack::Protection would have to be excluded. I wouldn't want to invoke a false sense of security
|
13
|
+
* Filter priorities - They're technically possible, but I believe it would introduce the potential for _filter hell_; badly written filters and mass confusion. Filter order has to be logical and predictable. Adding prioritisation would undermine that, and make for lazy use of filters. By not having prioritisation, there's incentive to design filters to be order-agnostic.
|
14
|
+
* Verbose logging - I originally intended to add some form of debug-style logging to show the complete flow of a request as it traverses through filters and controllers, etc. For a couple of reasons, I've decided to leave this out of Scorched. For those unfamiliar with the order in which filters and routes are invoked, it's better to learn through first-hand experience writing little test applications, rather than depending on debug logging.
|
15
|
+
|
16
|
+
|
17
|
+
More things will be added as they're thought of and considered.
|
@@ -85,7 +85,7 @@ As of v0.11, Scorched also supports inverted/negated conditions by adding a trai
|
|
85
85
|
Like configuration options, conditions are implemented using the `Scorched::Options` class, so they're inherited and can be overridden by child classes. You may easily add your own conditions as the example below demonstrates.
|
86
86
|
|
87
87
|
```ruby
|
88
|
-
|
88
|
+
conditions[:has_permission] = proc { |v|
|
89
89
|
user.has_permission == v
|
90
90
|
}
|
91
91
|
|
data/lib/scorched/controller.rb
CHANGED
@@ -100,8 +100,7 @@ module Scorched
|
|
100
100
|
def call(env)
|
101
101
|
loaded = env['scorched.middleware'] ||= Set.new
|
102
102
|
app = lambda do |env|
|
103
|
-
|
104
|
-
instance.action
|
103
|
+
self.new(env).action
|
105
104
|
end
|
106
105
|
|
107
106
|
builder = Rack::Builder.new
|
@@ -151,6 +150,18 @@ module Scorched
|
|
151
150
|
end
|
152
151
|
|
153
152
|
# Generates and returns a new route proc from the given block, and optionally maps said proc using the given args.
|
153
|
+
# Helper methods are provided for each HTTP method which automatically define the appropriate _:method_
|
154
|
+
# condition.
|
155
|
+
#
|
156
|
+
# :call-seq:
|
157
|
+
# route(pattern = nil, priority = nil, **conds, &block)
|
158
|
+
# get(pattern = nil, priority = nil, **conds, &block)
|
159
|
+
# post(pattern = nil, priority = nil, **conds, &block)
|
160
|
+
# put(pattern = nil, priority = nil, **conds, &block)
|
161
|
+
# delete(pattern = nil, priority = nil, **conds, &block)
|
162
|
+
# head(pattern = nil, priority = nil, **conds, &block)
|
163
|
+
# options(pattern = nil, priority = nil, **conds, &block)
|
164
|
+
# patch(pattern = nil, priority = nil, **conds, &block)
|
154
165
|
def route(pattern = nil, priority = nil, **conds, &block)
|
155
166
|
target = lambda do |env|
|
156
167
|
env['scorched.response'].body = instance_exec(*env['scorched.request'].captures, &block)
|
@@ -159,7 +170,7 @@ module Scorched
|
|
159
170
|
self << {pattern: compile(pattern, true), priority: priority, conditions: conds, target: target} if pattern
|
160
171
|
target
|
161
172
|
end
|
162
|
-
|
173
|
+
|
163
174
|
['get', 'post', 'put', 'delete', 'head', 'options', 'patch'].each do |method|
|
164
175
|
methods = (method == 'get') ? ['GET', 'HEAD'] : [method.upcase]
|
165
176
|
define_method(method) do |*args, **conds, &block|
|
@@ -168,11 +179,18 @@ module Scorched
|
|
168
179
|
end
|
169
180
|
end
|
170
181
|
|
182
|
+
# Defines a filter of +type+. Helper methods are provided as syntactic sugar for each filter type.
|
183
|
+
# +args+ is used internally by Scorched for passing additional arguments to the filter, such as the exception in
|
184
|
+
# the case of error blocks.
|
185
|
+
# :call-seq:
|
186
|
+
# filter(type, *args, **conds, &block)
|
187
|
+
# before(*args, **conds, &block)
|
188
|
+
# after(*args, **conds, &block)
|
189
|
+
# error(*args, **conds, &block)
|
171
190
|
def filter(type, *args, **conds, &block)
|
172
191
|
filters[type.to_sym] << {args: args, conditions: conds, proc: block}
|
173
192
|
end
|
174
193
|
|
175
|
-
# A bit of syntactic sugar for #filter.
|
176
194
|
['before', 'after', 'error'].each do |type|
|
177
195
|
define_method(type) do |*args, &block|
|
178
196
|
filter(type, *args, &block)
|
@@ -218,6 +236,8 @@ module Scorched
|
|
218
236
|
end
|
219
237
|
env['scorched.request'] ||= Request.new(env)
|
220
238
|
env['scorched.response'] ||= Response.new
|
239
|
+
@_log_prefix = ' ' * (4 * request.breadcrumb.length)
|
240
|
+
log :debug, "#{log_object(self)} instantiated"
|
221
241
|
end
|
222
242
|
|
223
243
|
def action
|
@@ -236,6 +256,7 @@ module Scorched
|
|
236
256
|
redirect(request.path.chomp('/'))
|
237
257
|
end
|
238
258
|
|
259
|
+
log :debug, :matches
|
239
260
|
if matches.all? { |m| m.failed_condition }
|
240
261
|
pass if config[:auto_pass]
|
241
262
|
response.status = matches.empty? ? 404 : 403
|
@@ -246,21 +267,23 @@ module Scorched
|
|
246
267
|
@_matched = true == matches.each { |match|
|
247
268
|
next if match.failed_condition
|
248
269
|
request.breadcrumb << match
|
270
|
+
target = match.mapping[:target]
|
249
271
|
break if catch(:pass) {
|
250
|
-
|
272
|
+
log :debug, "Invoking target: #{log_object(target)}"
|
251
273
|
response.merge! (Proc === target) ? instance_exec(env, &target) : target.call(env)
|
252
274
|
}
|
253
275
|
request.breadcrumb.pop
|
276
|
+
log :debug, "#{log_object(target)} passed the request"
|
254
277
|
}
|
255
278
|
rescue => inner_error
|
256
279
|
rescue_block.call(inner_error)
|
257
280
|
end
|
258
281
|
run_filters(:after)
|
259
|
-
end
|
282
|
+
end || log(:debug, "Request halted")
|
260
283
|
rescue => outer_error
|
261
284
|
outer_error == inner_error ? raise : rescue_block.call(outer_error)
|
262
285
|
end
|
263
|
-
response
|
286
|
+
response.finish
|
264
287
|
end
|
265
288
|
|
266
289
|
# Finds mappings that match the unmatched portion of the request path, returning an array of `Match` objects, or an
|
@@ -308,8 +331,16 @@ module Scorched
|
|
308
331
|
halt(status)
|
309
332
|
end
|
310
333
|
|
311
|
-
|
312
|
-
|
334
|
+
# call-seq:
|
335
|
+
# halt(status=nil, body=nil)
|
336
|
+
# halt(body)
|
337
|
+
def halt(status=nil, body=nil)
|
338
|
+
unless status.nil? || Integer === status
|
339
|
+
body = status
|
340
|
+
status = nil
|
341
|
+
end
|
342
|
+
response.status = status if status
|
343
|
+
response.body = body if body
|
313
344
|
throw :halt
|
314
345
|
end
|
315
346
|
|
@@ -489,18 +520,52 @@ module Scorched
|
|
489
520
|
|
490
521
|
def run_filters(type)
|
491
522
|
tracker = env['scorched.executed_filters'] ||= {before: Set.new, after: Set.new}
|
492
|
-
filters[type].reject{ |f| tracker[type].include? f
|
493
|
-
|
494
|
-
|
495
|
-
|
523
|
+
eligable = filters[type].reject{ |f| tracker[type].include? f || check_for_failed_condition(f[:conditions])}
|
524
|
+
log :debug, "Running #{eligable.length} eligable #{type} filters:"
|
525
|
+
eligable.each do |f|
|
526
|
+
log :debug, " #{f[:conditions]} => #{log_object f[:proc]}"
|
527
|
+
tracker[type] << f
|
528
|
+
instance_exec(&f[:proc])
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def log(type, message = nil, *args)
|
533
|
+
if config[:logger]
|
534
|
+
logger = config[:logger]
|
535
|
+
type = Logger.const_get(type.to_s.upcase)
|
536
|
+
logger.progname ||= 'Scorched'
|
537
|
+
|
538
|
+
case message
|
539
|
+
when :matches
|
540
|
+
message = []
|
541
|
+
eligable = matches.reject { |m| m.failed_condition }
|
542
|
+
message << "#{matches.length} mappings matched #{request.unmatched_path.inspect}:"
|
543
|
+
matches.each do |m|
|
544
|
+
message << " #{m.mapping[:pattern].inspect} => #{log_object m.mapping[:target]}"
|
545
|
+
end
|
546
|
+
message << "#{eligable.length} of which are eligable:"
|
547
|
+
eligable.each do |m|
|
548
|
+
message << " #{m.mapping[:pattern].inspect} => #{log_object m.mapping[:target]}"
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
[*message].each do |v|
|
553
|
+
v.insert(0, @_log_prefix)
|
554
|
+
logger.add(type, v)
|
496
555
|
end
|
497
556
|
end
|
557
|
+
true # Makes it safe to use in conditions
|
498
558
|
end
|
499
559
|
|
500
|
-
def
|
501
|
-
|
502
|
-
|
503
|
-
|
560
|
+
def log_object(obj)
|
561
|
+
case obj
|
562
|
+
when Proc
|
563
|
+
"<Proc @#{obj.source_location.join(':')}>"
|
564
|
+
when Array
|
565
|
+
obj.map { |v| log_object(v) }.inspect
|
566
|
+
else
|
567
|
+
obj.class.name || Controller === obj.class ? 'Anonymous controller' : 'Anonymous class'
|
568
|
+
end
|
504
569
|
end
|
505
570
|
end
|
506
571
|
end
|
data/lib/scorched/error.rb
CHANGED
data/lib/scorched/response.rb
CHANGED
@@ -5,26 +5,32 @@ module Scorched
|
|
5
5
|
def merge!(response)
|
6
6
|
return self if response == self
|
7
7
|
if Rack::Response === response
|
8
|
-
response.
|
9
|
-
self.status = response.status
|
10
|
-
self.header.merge!(response.header)
|
11
|
-
self.body = []
|
12
|
-
response.each { |v| self.body << v }
|
13
|
-
else
|
14
|
-
self.status, @header, self.body = response
|
8
|
+
response = [response.status, response.header, response]
|
15
9
|
end
|
10
|
+
self.status, self.body = response[0], response[2]
|
11
|
+
self.header.merge!(response[1])
|
12
|
+
self
|
16
13
|
end
|
17
14
|
|
18
15
|
# Automatically wraps the assigned value in an array if it doesn't respond to ``each``.
|
19
16
|
# Also filters out non-true values and empty strings.
|
20
17
|
def body=(value)
|
21
|
-
value = []
|
18
|
+
value = [] if !value || value == ''
|
22
19
|
super(value.respond_to?(:each) ? value : [value.to_s])
|
23
20
|
end
|
24
21
|
|
22
|
+
# Override finish to avoid using BodyProxy
|
25
23
|
def finish(*args, &block)
|
26
24
|
self['Content-Type'] ||= 'text/html;charset=utf-8'
|
27
|
-
|
25
|
+
@block = block if block
|
26
|
+
if [204, 205, 304].include?(status.to_i)
|
27
|
+
header.delete "Content-Type"
|
28
|
+
header.delete "Content-Length"
|
29
|
+
close
|
30
|
+
[status.to_i, header, []]
|
31
|
+
else
|
32
|
+
[status.to_i, header, body]
|
33
|
+
end
|
28
34
|
end
|
29
35
|
|
30
36
|
alias :to_a :finish
|
data/lib/scorched/version.rb
CHANGED
data/scorched.gemspec
CHANGED
@@ -2,11 +2,12 @@ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
|
2
2
|
require 'scorched/version'
|
3
3
|
|
4
4
|
Gem::Specification.new 'scorched', Scorched::VERSION do |s|
|
5
|
-
s.summary =
|
6
|
-
s.description =
|
7
|
-
s.authors = [
|
8
|
-
s.email =
|
9
|
-
s.homepage =
|
5
|
+
s.summary = 'Light-weight, DRY as a desert, web framework for Ruby'
|
6
|
+
s.description = 'A light-weight Sinatra-inspired web framework for web sites and applications of any size.'
|
7
|
+
s.authors = ['Tom Wardrop']
|
8
|
+
s.email = 'tom@tomwardrop.com'
|
9
|
+
s.homepage = 'http://scorchedrb.com'
|
10
|
+
s.license = 'MIT'
|
10
11
|
s.files = Dir.glob(`git ls-files`.split("\n") - %w[.gitignore])
|
11
12
|
s.test_files = Dir.glob('spec/**/*_spec.rb')
|
12
13
|
s.rdoc_options = %w[--line-numbers --inline-source --title Scorched --encoding=UTF-8]
|
data/spec/controller_spec.rb
CHANGED
@@ -537,6 +537,17 @@ module Scorched
|
|
537
537
|
rt.get('/').status.should == 401
|
538
538
|
end
|
539
539
|
|
540
|
+
it "takes an optional response body" do
|
541
|
+
app.get('/') { halt 'cool' }
|
542
|
+
rt.get('/').body.should == 'cool'
|
543
|
+
end
|
544
|
+
|
545
|
+
it "can take a status and a response body" do
|
546
|
+
app.get('/') { halt 401, 'cool' }
|
547
|
+
rt.get('/').status.should == 401
|
548
|
+
rt.get('/').body.should == 'cool'
|
549
|
+
end
|
550
|
+
|
540
551
|
it "skips processing filters" do
|
541
552
|
app.after { response.status = 403 }
|
542
553
|
app.get('/') { halt }
|
@@ -546,7 +557,7 @@ module Scorched
|
|
546
557
|
it "short circuits filters if halted within filter" do
|
547
558
|
app.before { halt }
|
548
559
|
app.after { response.status = 403 }
|
549
|
-
rt.get('/').status.
|
560
|
+
rt.get('/').status.should_not == 403
|
550
561
|
end
|
551
562
|
end
|
552
563
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scorched
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.12'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Wardrop
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -80,17 +80,18 @@ dependencies:
|
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '2.9'
|
83
|
-
description: A
|
83
|
+
description: A light-weight Sinatra-inspired web framework for web sites and applications
|
84
84
|
of any size.
|
85
85
|
email: tom@tomwardrop.com
|
86
86
|
executables: []
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- CHANGES.md
|
90
91
|
- Gemfile
|
91
92
|
- LICENSE
|
92
|
-
- Milestones.md
|
93
93
|
- README.md
|
94
|
+
- TODO.md
|
94
95
|
- docs/01_preface.md
|
95
96
|
- docs/02_fundamentals/01_the_controller.md
|
96
97
|
- docs/02_fundamentals/02_configuration.md
|
@@ -131,7 +132,8 @@ files:
|
|
131
132
|
- spec/views/other.str
|
132
133
|
- spec/views/partial.erb
|
133
134
|
homepage: http://scorchedrb.com
|
134
|
-
licenses:
|
135
|
+
licenses:
|
136
|
+
- MIT
|
135
137
|
metadata: {}
|
136
138
|
post_install_message:
|
137
139
|
rdoc_options:
|