Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable following of redirects by default #454

Merged
merged 11 commits into from
Mar 27, 2020
Merged

Disable following of redirects by default #454

merged 11 commits into from
Mar 27, 2020

Conversation

mattdowdell
Copy link

@mattdowdell mattdowdell commented Mar 19, 2020

Per #449, redirects should not be silently followed when making requests. This disables that behaviour by default in the synchronous client, and also modifies the responses to no longer consider 3XX HTTP status errors. There's perhaps some nuance to whether all of them are applicable in API responses, but RFC 7231 does not appear to consider these to be errors anyway.

I've left in the exception classes for redirects and make_http_exception is untouched, largely because I wasn't sure if that was considered part of the public API or not. I've also added an integration test and bench tested using my code that originally found the issue and confirmed that it works as expected for my use case.

Fixes #449.

@coveralls
Copy link

coveralls commented Mar 19, 2020

Coverage Status

Coverage increased (+0.05%) to 96.344% when pulling 0c0d475 on mattdowdell:nofollow-redirects into 2e4c1f9 on Yelp:master.

@mattdowdell
Copy link
Author

@macisamuele Are you able to review?

Also, do you understand the failure in CI for mypy? I don't understand why attributes have gone missing.

Copy link
Collaborator

@macisamuele macisamuele left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattdowdell thanks a lot for contributing to the project.

I would suggest to add an additional test to ensure that redirects are followed as well as ensuring that requests and fido clients will behave consistently.

@@ -199,7 +199,7 @@ def response(

swagger_result = self._get_swagger_result(incoming_response)

if self.operation is None and incoming_response.status_code >= 300:
if self.operation is None and incoming_response.status_code >= 400:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not change this behavior (at least on this PR) as users might rely on this.

You're right to mention that RFC7231 does not consider them errors.
As this is independent from the specific issue you're trying to solve (following redirects) I would suggest to create an issue for this and eventually publish a new PR.
NOTE: changing this (so 3xx do not raise exception) requires a major version bump

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Instead I check the response stored in the exception for the relevant information.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raised #455 to track not raising exceptions when redirects are returned.

@@ -473,6 +492,16 @@ def test_msgpack_support(self, swagger_http_server):
assert response.headers['Content-Type'] == APP_MSGPACK
assert unpackb(response.raw_bytes, encoding='utf-8') == API_RESPONSE

def test_redirects_are_not_followed(self, swagger_http_server):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a test that ensures that redirects are followed as well.

Copy link
Author

@mattdowdell mattdowdell Mar 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, but only for the RequestsClient as it doesn't look like the FidoClient supports the ability to follow redirects.

Copy link
Collaborator

@macisamuele macisamuele Mar 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should be doing a small effort here to ensure that the two clients behave the same here.
I'll try to look for pointers to help you out here

Fido does not really support following redirects. I would say that at this point is ok to allow following redirects as best-effort. Eventually we might just want to remove the redirect support completely.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the conclusion I came to. The logigcal place to add support for following redirects in Fido, would be in prepare_request_for_twisted. The retuened value from that in turn goes into fido.fetch which attempts to look like the interface for requests, but isn't fully implemented (as demonstrated by the lack of allow_redirects parameter. For the record, I have no clue how the call to fido.fetch works in fido_client.py as it seems to be missing an import.

In summary, the effort required to make fido work with redirects is much more significant that getting requests to disable redirects by default. Given the bug I initially found where following redirects causes bravado to complain about unexpected HTTP statuses, I'd say that support for following redirects at all should probably be removed in the future or it should handle redirects at a higher level, e.g. response returns redirect, so now you should expect the response(s) from the target of the redirect. As not all redirects have a single target according to the spec, that gets even more complicated to handle at a library level, so I'd argue that's an implementation decision for the user of bravado to deal with.

bravado/testing/integration_test.py Outdated Show resolved Hide resolved
@mattdowdell
Copy link
Author

@macisamuele Is there anything else you'd like me to address before this gets merged in?

@@ -286,7 +286,6 @@ def __init__(
ssl_cert=None, # type: typing.Any
future_adapter_class=RequestsFutureAdapter, # type: typing.Type[RequestsFutureAdapter]
response_adapter_class=RequestsResponseAdapter, # type: typing.Type[RequestsResponseAdapter]
allow_redirects=False # type: bool
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is unclear to me why this parameter has been removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're looking at an old changeset. It was added and then later moved to be a parameter in the request method.

Comment on lines 513 to 523
try:
self.http_client.request({
'method': 'GET',
'url': '{server_address}/redirect'.format(server_address=swagger_http_server),
'params': {},
}).result(timeout=1)
except HTTPMovedPermanently as exc:
assert isinstance(exc.response, IncomingResponse) and exc.response.status_code == 301
assert isinstance(exc.response, IncomingResponse) and exc.response.headers['Location'] == '/json'
else:
pytest.fail("Expected exception was not raised")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend using pytest.raises such that we don't need manual handling of different raised exception

with pytest.raises(HTTPMovedPermanently) as excinfo:
    self.http_client.request({
        'method': 'GET',
        'url': '{server_address}/redirect'.format(server_address=swagger_http_server),
        'params': {},
    }).result(timeout=1)
exc = excinfo.value
assert isinstance(exc.response, IncomingResponse) and exc.response.status_code == 301
assert isinstance(exc.response, IncomingResponse) and exc.response.headers['Location'] == '/json'

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't realise that was support by pytest.raises.

Changed to that method instead and removed the coverage ignore.

Copy link
Collaborator

@macisamuele macisamuele left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes looks good to me.

Pinging @sjaensch to double check the PR and if all looks good to merge this.

Copy link
Contributor

@sjaensch sjaensch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! I'm requesting one minor change, as we do use (and run) those integration tests for bravado-asyncio too.

# the FidoClient doesn't have a way to turn off redirects being followed
# so limit this test to the RequestsClient instead
if not isinstance(self.http_client, RequestsClient):
pytest.skip('Following redirects is only supported in the RequestsClient')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a blacklist instead of a whitelist? bravado-asyncio does have a way to turn off following redirects, and I'd like to be able to run the test there too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, although it's somewhat clunky due to how coverage and test dependencies are loaded. If there's better suggestions on how to implement blacklisting without having to load the thing being blacklisted, I'm happy to change it.

@sjaensch sjaensch merged commit 41caefc into Yelp:master Mar 27, 2020
@sjaensch
Copy link
Contributor

Thanks for your efforts @mattdowdell!

@sjaensch
Copy link
Contributor

This has has been released (with a slightly different name for the option, see #457) in bravado 10.6.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bravado should not silently follow redirects
4 participants