diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index 1f90bef9..5e4d1455 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -380,12 +380,23 @@ 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 + for h in ["Content-Length", "Content-Type", + "Content-Encoding", "Transfer-Encoding"]: + try: + del self.request.headers[h] + except KeyError: + pass 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 a65c8c68..f0f9be0f 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -54,6 +54,16 @@ class NoContentHandler(RequestHandler): self.set_header("Content-Length", "7") self.set_status(204) +class SeeOther303PostHandler(RequestHandler): + def post(self): + self.set_header("Location", "/303_get") + self.set_status(303) + +class SeeOther303GetHandler(RequestHandler): + def get(self): + self.write("ok") + + class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): def setUp(self): super(SimpleHTTPClientTestCase, self).setUp() @@ -72,6 +82,8 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): url("/content_length", ContentLengthHandler), url("/head", HeadHandler), url("/no_content", NoContentHandler), + url("/303_post", SeeOther303PostHandler), + url("/303_get", SeeOther303GetHandler), ], gzip=True) def test_singleton(self): @@ -150,6 +162,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("/303_post", method="POST", body="") + self.assertEqual(200, response.code) + self.assertTrue(response.request.url.endswith("/303_post")) + self.assertTrue(response.effective_url.endswith("/303_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)