From 1667e0b7f800f4fe41ad49c17f7151c6a6058c92 Mon Sep 17 00:00:00 2001 From: Aaron Morton Date: Wed, 28 Sep 2011 23:26:58 +1300 Subject: [PATCH] handle HTTP 303 redirects correctly see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 In response to a 303 the client SHOULD make a GET to the Location, was using the original method. --- tornado/simple_httpclient.py | 7 ++++++- tornado/test/simple_httpclient_test.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index a98eb5448..79a5960c3 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -350,12 +350,17 @@ class _HTTPConnection(object): self.request) if (self.request.follow_redirects and self.request.max_redirects > 0 and - self.code in (301, 302)): + self.code in (301, 302, 303)): new_request = copy.copy(self.request) new_request.url = urlparse.urljoin(self.request.url, self.headers["Location"]) new_request.max_redirects -= 1 del new_request.headers["Host"] + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 + # client SHOULD make a GET request + if self.code == 303: + new_request.method = "GET" + new_request.body = None new_request.original_request = original_request final_callback = self.final_callback self.final_callback = None diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index f6d7235ae..55d5cf144 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -44,6 +44,16 @@ class ContentLengthHandler(RequestHandler): self.set_header("Content-Length", self.get_argument("value")) self.write("ok") +class PRGPostHandler(RequestHandler): + def post(self): + self.set_header("Location", "/prg_get") + self.set_status(303) + +class PRGGetHandler(RequestHandler): + def get(self): + self.write("ok") + + class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): def get_app(self): # callable objects to finish pending /trigger requests @@ -56,6 +66,8 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): url("/hang", HangHandler), url("/hello", HelloWorldHandler), url("/content_length", ContentLengthHandler), + url("/prg_post", PRGPostHandler), + url("/prg_get", PRGGetHandler), ], gzip=True) def test_singleton(self): @@ -134,6 +146,14 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): self.assertTrue(response.effective_url.endswith("/countdown/2")) self.assertTrue(response.headers["Location"].endswith("/countdown/1")) + def test_303_redirect(self): + response = self.fetch("/prg_post", method="POST", body="") + self.assertEqual(200, response.code) + self.assertTrue(response.request.url.endswith("/prg_post")) + self.assertTrue(response.effective_url.endswith("/prg_get")) + #request is the original request, is a POST still + self.assertEqual("POST", response.request.method) + def test_request_timeout(self): response = self.fetch('/hang', request_timeout=0.1) self.assertEqual(response.code, 599) -- 2.47.2