Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Middleware enhancements: automatic and explicit forward to downstream [

  • Loading branch information...
commit cf32f2e6802410fc53e063ef701002c5012fc9ff 1 parent 6fd8aaa
@rtomayko rtomayko authored
Showing with 118 additions and 29 deletions.
  1. +6 −0 CHANGES
  2. +17 −1 lib/sinatra/base.rb
  3. +95 −28 test/base_test.rb
View
6 CHANGES
@@ -5,6 +5,12 @@
passed as arguments to the block. [#140]
* The "helpers" method now takes a variable number of modules
along with the normal block syntax. [#133]
+ * New request-level #forward method for middleware components: passes
+ the env to the downstream app and merges the response status, headers,
+ and body into the current context.
+ * Requests are now automatically forwarded to the downstream app when
+ running as middleware and no matching route is found or all routes
+ pass.
* New simple API for extensions/plugins to add DSL-level and
request-level methods. Use Sinatra.register(mixin) to extend
the DSL with all public methods defined in the mixin module;
View
18 lib/sinatra/base.rb
@@ -366,6 +366,16 @@ def pass
throw :pass
end
+ # Forward the request to the downstream app -- middleware only.
+ def forward
+ fail "downstream app not set" unless @app.respond_to? :call
+ status, headers, body = @app.call(@request.env)
+ @response.status = status
+ @response.body = body
+ headers.each { |k, v| @response[k] = v }
+ nil
+ end
+
private
# Run before filters and then locate and run a matching route.
def route!
@@ -409,7 +419,13 @@ def route!
end
end
- raise NotFound
+ # No matching route found or all routes passed -- forward downstream
+ # when running as middleware; 404 when running as normal app.
+ if @app
+ forward
+ else
+ raise NotFound
+ end
end
def nested_params(params)
View
123 test/base_test.rb
@@ -1,45 +1,112 @@
require File.dirname(__FILE__) + '/helper'
-describe 'Sinatra::Base' do
- it 'includes Rack::Utils' do
- assert Sinatra::Base.included_modules.include?(Rack::Utils)
+describe 'Sinatra::Base subclasses' do
+
+ class TestApp < Sinatra::Base
+ get '/' do
+ 'Hello World'
+ end
+ end
+
+ it 'include Rack::Utils' do
+ assert TestApp.included_modules.include?(Rack::Utils)
end
- it 'can be used as a Rack application' do
- mock_app {
- get '/' do
- 'Hello World'
- end
- }
- assert @app.respond_to?(:call)
+ it 'processes requests with #call' do
+ assert TestApp.respond_to?(:call)
- request = Rack::MockRequest.new(@app)
+ request = Rack::MockRequest.new(TestApp)
response = request.get('/')
assert response.ok?
assert_equal 'Hello World', response.body
end
- it 'can be used as Rack middleware' do
- app = lambda { |env| [200, {}, ['Goodbye World']] }
- mock_middleware =
- mock_app {
- get '/' do
- 'Hello World'
- end
- get '/goodbye' do
- @app.call(request.env)
- end
- }
- middleware = mock_middleware.new(app)
+ class TestApp < Sinatra::Base
+ get '/state' do
+ body = "Foo: #{@foo}"
+ @foo = 'discard'
+ body
+ end
+ end
+
+ it 'does not maintain state between requests' do
+ request = Rack::MockRequest.new(TestApp)
+ 2.times do
+ response = request.get('/state')
+ assert response.ok?
+ assert_equal 'Foo: ', response.body
+ end
+ end
+end
+
+describe "Sinatra::Base as Rack middleware" do
+
+ app = lambda { |env|
+ [210, {'X-Downstream' => 'true'}, ['Hello from downstream']] }
+
+ class TestMiddleware < Sinatra::Base
+ end
+
+ it 'creates a middleware that responds to #call with .new' do
+ middleware = TestMiddleware.new(app)
+ assert middleware.respond_to?(:call)
+ end
+
+ it 'exposes the downstream app' do
+ middleware = TestMiddleware.new(app)
assert_same app, middleware.app
+ end
- request = Rack::MockRequest.new(middleware)
+ class TestMiddleware < Sinatra::Base
+ get '/' do
+ 'Hello from middleware'
+ end
+ end
+
+ middleware = TestMiddleware.new(app)
+ request = Rack::MockRequest.new(middleware)
+
+ it 'intercepts requests' do
response = request.get('/')
assert response.ok?
- assert_equal 'Hello World', response.body
+ assert_equal 'Hello from middleware', response.body
+ end
- response = request.get('/goodbye')
- assert response.ok?
- assert_equal 'Goodbye World', response.body
+ it 'automatically forwards requests downstream when no matching route found' do
+ response = request.get('/missing')
+ assert_equal 210, response.status
+ assert_equal 'Hello from downstream', response.body
+ end
+
+ class TestMiddleware < Sinatra::Base
+ get '/low-level-forward' do
+ app.call(env)
+ end
+ end
+
+ it 'can call the downstream app directly and return result' do
+ response = request.get('/low-level-forward')
+ assert_equal 210, response.status
+ assert_equal 'true', response['X-Downstream']
+ assert_equal 'Hello from downstream', response.body
+ end
+
+ class TestMiddleware < Sinatra::Base
+ get '/explicit-forward' do
+ response['X-Middleware'] = 'true'
+ res = forward
+ assert_nil res
+ assert_equal 210, response.status
+ assert_equal 'true', response['X-Downstream']
+ assert_equal ['Hello from downstream'], response.body
+ 'Hello after explicit forward'
+ end
+ end
+
+ it 'forwards the request downstream and integrates the response into the current context' do
+ response = request.get('/explicit-forward')
+ assert_equal 210, response.status
+ assert_equal 'true', response['X-Downstream']
+ assert_equal 'Hello after explicit forward', response.body
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.