diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index 81a0fd2476..6af9dd15c3 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -608,6 +608,8 @@ def _should_follow_redirect(self) -> bool: return ( self.code in (301, 302, 303, 307, 308) and self.request.max_redirects > 0 + and self.headers is not None + and self.headers.get("Location") is not None ) return False diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index ca7fb5a9d9..29319c743a 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -62,6 +62,13 @@ def prepare(self): ) +class RedirectWithoutLocationHandler(RequestHandler): + def prepare(self): + # For testing error handling of a redirect with no location header. + self.set_status(301) + self.finish() + + class ChunkHandler(RequestHandler): @gen.coroutine def get(self): @@ -143,6 +150,7 @@ def get_app(self): url("/post", PostHandler), url("/put", PutHandler), url("/redirect", RedirectHandler), + url("/redirect_without_location", RedirectWithoutLocationHandler), url("/chunk", ChunkHandler), url("/auth", AuthHandler), url("/countdown/([0-9]+)", CountdownHandler, name="countdown"), @@ -291,6 +299,14 @@ def test_follow_redirect(self): self.assertTrue(response.effective_url.endswith("/countdown/0")) self.assertEqual(b"Zero", response.body) + def test_redirect_without_location(self): + response = self.fetch("/redirect_without_location", follow_redirects=True) + # If there is no location header, the redirect response should + # just be returned as-is. (This should arguably raise an + # error, but libcurl doesn't treat this as an error, so we + # don't either). + self.assertEqual(301, response.code) + def test_credentials_in_url(self): url = self.get_url("/auth").replace("http://", "http://me:secret@") response = self.fetch(url)