summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2020-02-04 10:15:20 -0800
committerJeremy Evans <code@jeremyevans.net>2020-02-04 10:36:40 -0800
commit274336e96bc6034ee1e56bf9c9fd7f18874483e9 (patch)
tree0c572107dd7b1ea328d2310c230a91d231c05012
parentdbd33f7e8c2149332636e32301c295197f046490 (diff)
downloadrack-274336e96bc6034ee1e56bf9c9fd7f18874483e9.tar.gz
Fix Cascade to use the last response if all responses should be cascaded
This was broken in the last commit for Cascade. The behavior wasn't specified, but it previously returned the response from the final app, and that is a better idea than the default 404 response. Also add documentation for Cascade.
-rw-r--r--lib/rack/cascade.rb30
-rw-r--r--test/spec_cascade.rb6
2 files changed, 29 insertions, 7 deletions
diff --git a/lib/rack/cascade.rb b/lib/rack/cascade.rb
index 8a3cf66b..d71274c2 100644
--- a/lib/rack/cascade.rb
+++ b/lib/rack/cascade.rb
@@ -2,24 +2,37 @@
module Rack
# Rack::Cascade tries a request on several apps, and returns the
- # first response that is not 404 or 405 (or in a list of configurable
- # status codes).
+ # first response that is not 404 or 405 (or in a list of configured
+ # status codes). If all applications tried return one of the configured
+ # status codes, return the last response.
class Cascade
# deprecated, no longer used
NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
+ # An array of applications to try in order.
attr_reader :apps
- def initialize(apps, catch = [404, 405])
+ # Set the apps to send requests to, and what statuses result in
+ # cascading. Arguments:
+ #
+ # apps: An enumerable of rack applications.
+ # cascade_for: The statuses to use cascading for. If a response is received
+ # from an app, the next app is tried.
+ def initialize(apps, cascade_for = [404, 405])
@apps = []
apps.each { |app| add app }
- @catch = {}
- [*catch].each { |status| @catch[status] = true }
+ @cascade_for = {}
+ [*cascade_for].each { |status| @cascade_for[status] = true }
end
+ # Call each app in order. If the responses uses a status that requires
+ # cascading, try the next app. If all responses require cascading,
+ # return the response from the last app.
def call(env)
+ return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
+ result = nil
last_body = nil
@apps.each do |app|
@@ -32,17 +45,20 @@ module Rack
last_body.close if last_body.respond_to? :close
result = app.call(env)
+ return result unless @cascade_for.include?(result[0].to_i)
last_body = result[2]
- return result unless @catch.include?(result[0].to_i)
end
- [404, { CONTENT_TYPE => "text/plain" }, []]
+ result
end
+ # Append an app to the list of apps to cascade. This app will
+ # be tried last.
def add(app)
@apps << app
end
+ # Whether the given app is one of the apps to cascade to.
def include?(app)
@apps.include?(app)
end
diff --git a/test/spec_cascade.rb b/test/spec_cascade.rb
index 883b47c5..8f1fd131 100644
--- a/test/spec_cascade.rb
+++ b/test/spec_cascade.rb
@@ -61,6 +61,12 @@ describe Rack::Cascade do
body.must_be_empty
end
+ it "returns final response if all responses are cascaded" do
+ app = Rack::Cascade.new([])
+ app << lambda { |env| [405, {}, []] }
+ app.call({})[0].must_equal 405
+ end
+
it "append new app" do
cascade = Rack::Cascade.new([], [404, 403])
Rack::MockRequest.new(cascade).get('/').must_be :not_found?