Repository: tornadoweb/tornado
Pull request #2109 (2017-07-16 14:10:51)

feat: RedirectHandler to pass GET query


Patch:
diff --git a/tornado/httputil.py b/tornado/httputil.py
index 5b87ce61..a98273ae 100755
--- a/tornado/httputil.py
+++ b/tornado/httputil.py
@@ -942,6 +942,14 @@ def split_host_and_port(netloc):
     return (host, port)
 
 
+def qs_to_qsl(qs):
+    """Generator rewinding a result of ``parse_qs`` back to name-value pairs. 
+    """
+    for k, vs in qs.items():
+        for v in vs:
+            yield (k, v)
+
+
 _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
 _QuotePatt = re.compile(r"[\\].")
 _nulljoin = ''.join
diff --git a/tornado/web.py b/tornado/web.py
index defd2161..0cc34b8e 100644
--- a/tornado/web.py
+++ b/tornado/web.py
@@ -2279,7 +2279,11 @@ class RedirectHandler(RequestHandler):
         self._permanent = permanent
 
     def get(self, *args):
-        self.redirect(self._url.format(*args), permanent=self._permanent)
+        to_url = self._url.format(*args)
+        if self.request.query_arguments:
+            to_url = httputil.url_concat(
+                to_url, list(httputil.qs_to_qsl(self.request.query_arguments)))
+        self.redirect(to_url, permanent=self._permanent)
 
 
 class StaticFileHandler(RequestHandler):


Test patch:
diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
index d1278567..7ac79925 100644
--- a/tornado/test/httputil_test.py
+++ b/tornado/test/httputil_test.py
@@ -3,7 +3,7 @@
 
 
 from __future__ import absolute_import, division, print_function
-from tornado.httputil import url_concat, parse_multipart_form_data, HTTPHeaders, format_timestamp, HTTPServerRequest, parse_request_start_line, parse_cookie
+from tornado.httputil import url_concat, parse_multipart_form_data, HTTPHeaders, format_timestamp, HTTPServerRequest, parse_request_start_line, parse_cookie, qs_to_qsl, PY3
 from tornado.escape import utf8, native_str
 from tornado.log import gen_log
 from tornado.testing import ExpectLog
@@ -15,6 +15,11 @@ import logging
 import pickle
 import time
 
+if PY3:
+    import urllib.parse as urllib_parse
+else:
+    import urlparse as urllib_parse
+
 
 class TestUrlConcat(unittest.TestCase):
     def test_url_concat_no_query_params(self):
@@ -102,6 +107,17 @@ class TestUrlConcat(unittest.TestCase):
         self.assertEqual(url, "https://localhost/path?y=y")
 
 
+class QsParseTest(unittest.TestCase):
+
+    def test_parsing(self):
+        qsstring = "a=1&b=2&a=3"
+        qs = urllib_parse.parse_qs(qsstring)
+        qsl = list(qs_to_qsl(qs))
+        self.assertIn(('a', '1'), qsl)
+        self.assertIn(('a', '3'), qsl)
+        self.assertIn(('b', '2'), qsl)
+
+
 class MultipartFormDataTest(unittest.TestCase):
     def test_file_upload(self):
         data = b"""\
diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py
index ffccc02d..7c33fd90 100644
--- a/tornado/test/web_test.py
+++ b/tornado/test/web_test.py
@@ -2880,6 +2880,7 @@ class RedirectHandlerTest(WebTestCase):
     def get_handlers(self):
         return [
             ('/src', WebRedirectHandler, {'url': '/dst'}),
+            ('/src2', WebRedirectHandler, {'url': '/dst2?foo=bar'}),
             (r'/(.*?)/(.*?)/(.*)', WebRedirectHandler, {'url': '/{1}/{0}/{2}'})]
 
     def test_basic_redirect(self):
@@ -2887,6 +2888,16 @@ class RedirectHandlerTest(WebTestCase):
         self.assertEqual(response.code, 301)
         self.assertEqual(response.headers['Location'], '/dst')
 
+    def test_redirect_with_argument(self):
+        response = self.fetch('/src?foo=bar', follow_redirects=False)
+        self.assertEqual(response.code, 301)
+        self.assertEqual(response.headers['Location'], '/dst?foo=bar')
+
+    def test_redirect_with_appending_argument(self):
+        response = self.fetch('/src2?foo2=bar2', follow_redirects=False)
+        self.assertEqual(response.code, 301)
+        self.assertEqual(response.headers['Location'], '/dst2?foo=bar&foo2=bar2')
+
     def test_redirect_pattern(self):
         response = self.fetch('/a/b/c', follow_redirects=False)
         self.assertEqual(response.code, 301)