-
Notifications
You must be signed in to change notification settings - Fork 217
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #316 from ajvondrak/prevent-params-from-swallowing…
…-errors Prevent Goliath::Rack::Params from swallowing downstream errors
- Loading branch information
Showing
3 changed files
with
243 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
# This integration spec isn't *quite* about exception handling. It's more of a | ||
# regression test, since Goliath::Rack::Params used to swallow any exception | ||
# generated downstream from it in the middleware chain (even though | ||
# Goliath::API#call handles exceptions already). | ||
|
||
require 'spec_helper' | ||
|
||
class ExceptionHandlingMiddleware | ||
include Goliath::Rack::AsyncMiddleware | ||
include Goliath::Constants | ||
|
||
def call(env) | ||
# This kind of relies on downstream middleware raising their own exceptions | ||
# if something goes wrong. Alternatively, they could rescue the exception | ||
# and set env[RACK_EXCEPTION]. See #post_process. | ||
super | ||
rescue Exception => e | ||
handle_exception(e) | ||
end | ||
|
||
def post_process(env, status, headers, body) | ||
# If an exception was raised by Goliath::API#call, it will store the | ||
# exception in env[RACK_EXCEPTION] and automatically generate an error | ||
# response. We circumvent the usual error response by returning our own. | ||
return handle_exception(env[RACK_EXCEPTION]) if env[RACK_EXCEPTION] | ||
[status, headers, body] | ||
end | ||
|
||
private | ||
|
||
def handle_exception(e) | ||
# You could imagine this middleware having some sort of side effect, like | ||
# sending your team an email alert detailing the exception. For the | ||
# purposes of the spec, we'll just return some text. | ||
[200, {}, "Exception raised: #{e.message}"] | ||
end | ||
end | ||
|
||
class ExceptionRaisingMiddleware | ||
def initialize(app) | ||
@app = app | ||
end | ||
|
||
def call(env) | ||
raise env.params['raise'] if env.params['raise'] | ||
@app.call(env) | ||
end | ||
end | ||
|
||
class ExceptionHandlingAPI < Goliath::API | ||
# The exception handling middleware should come first in the chain so that | ||
# any exception in the rest of the chain gets rescued. | ||
use ExceptionHandlingMiddleware | ||
|
||
# Require param parsing before the exception raising middleware, because the | ||
# latter depends on params being parsed. Herein lies the regression: | ||
# Goliath::Rack::Params used to swallow any exceptions raised downstream from | ||
# it (here, ExceptionRaisingMiddleware), so any upstream middleware (here, | ||
# ExceptionHandlingMiddleware) would be none the wiser. Really, the only | ||
# thing Goliath::Rack::Params should be worried about is whether it can parse | ||
# the params. | ||
use Goliath::Rack::Params | ||
|
||
# Allow us to raise an exception on demand with the param raise=<message>. | ||
# You could imagine something less dumb for a real-life application that | ||
# incidentally raises an exception, if you'd like. | ||
use ExceptionRaisingMiddleware | ||
|
||
def response(env) | ||
# Goliath::API#call ensures that any exceptions raised here get rescued and | ||
# stored in env[RACK_EXCEPTION]. | ||
fail env.params['fail'] if env.params['fail'] | ||
[200, {}, 'No exceptions raised'] | ||
end | ||
end | ||
|
||
describe ExceptionHandlingAPI do | ||
let(:err) { Proc.new { fail 'API request failed' } } | ||
|
||
context 'when no exceptions get raised' do | ||
let(:query) { '' } | ||
|
||
it 'returns a normal response' do | ||
with_api(ExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 200 | ||
c.response.should == 'No exceptions raised' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when a middleware raises an exception' do | ||
let(:query) { 'raise=zoinks' } | ||
|
||
it 'handles the exception using ExceptionHandlingMiddleware' do | ||
with_api(ExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 200 | ||
c.response.should == 'Exception raised: zoinks' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when the API raises an exception' do | ||
let(:query) { 'fail=jinkies' } | ||
|
||
it 'handles the exception using ExceptionHandlingMiddleware' do | ||
with_api(ExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 200 | ||
c.response.should == 'Exception raised: jinkies' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when param parsing raises an exception' do | ||
let(:query) { 'ambiguous[]=&ambiguous[4]=' } | ||
|
||
it 'returns a validation error generated by Goliath::Rack::Params' do | ||
with_api(ExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 400 | ||
c.response.should == "[:error, \"Invalid parameters: Rack::Utils::ParameterTypeError\"]" | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
# This API has the same setup as ExceptionHandlingAPI, except for the crucial | ||
# difference that it does *not* use ExceptionHandlingMiddleware. This is to | ||
# make sure that exceptions are still rescued somewhere in an API call, even | ||
# though they aren't getting swallowed by Goliath::Rack::Params anymore. | ||
|
||
class PassiveExceptionHandlingAPI < Goliath::API | ||
use Goliath::Rack::Params | ||
use ExceptionRaisingMiddleware | ||
|
||
def response(env) | ||
fail env.params['fail'] if env.params['fail'] | ||
[200, {}, 'No exceptions raised'] | ||
end | ||
end | ||
|
||
describe PassiveExceptionHandlingAPI do | ||
let(:err) { Proc.new { fail 'API request failed' } } | ||
|
||
context 'when no exceptions get raised' do | ||
let(:query) { '' } | ||
|
||
it 'returns a normal response' do | ||
with_api(PassiveExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 200 | ||
c.response.should == 'No exceptions raised' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when a middleware raises an exception' do | ||
let(:query) { 'raise=ruh-roh' } | ||
|
||
it 'returns the server error generated by Goliath::Request#process' do | ||
with_api(PassiveExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 500 | ||
c.response.should == 'ruh-roh' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when the API raises an exception' do | ||
let(:query) { 'fail=puppy-power' } | ||
|
||
it 'returns the validation error generated by Goliath::API#call' do | ||
with_api(PassiveExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 500 | ||
c.response.should == '[:error, "puppy-power"]' | ||
end | ||
end | ||
end | ||
end | ||
|
||
context 'when param parsing raises an exception' do | ||
let(:query) { 'ambiguous[]=&ambiguous[4]=' } | ||
|
||
it 'returns a validation error generated by Goliath::Rack::Params' do | ||
with_api(PassiveExceptionHandlingAPI) do | ||
get_request({ query: query }, err) do |c| | ||
c.response_header.status.should == 400 | ||
c.response.should == '[:error, "Invalid parameters: Rack::Utils::ParameterTypeError"]' | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters