Repository: tornadoweb/tornado

Issue:
RedirectHandler re substitution doesn't work
### Code

``` python
import tornado.web
from tornado.web import url
import tornado.ioloop

app = tornado.web.Application([
    url(r"/pictures/(.*)", tornado.web.RedirectHandler,
        dict(url=r"/photos/\1")),
])

app.listen(8765)
tornado.ioloop.IOLoop.current().start()
```
### visit `localhost:8765/picture/1` will raise TypeError: get() takes 1 positional argument but 2 were given

```
ERROR:tornado.application:Uncaught exception GET /pictures/1 (::1)
HTTPServerRequest(protocol='http', host='localhost:8765', method='GET', uri='/pictures/1', version='HTTP/1.1', remote_ip='::1', headers={'Host': 'localhost:8765', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Dnt': '1', 'Upgrade-Insecure-Requests': '1', 'Connection': 'keep-alive', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36', 'Cookie': 'Pycharm-73b7632f=f7202b97-d898-4046-8aae-ae6536d1d931; _xsrf=2|ca7ba18c|041b228c03a99e0221d548c50c478a96|1476528491', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Accept-Encoding': 'gzip, deflate, sdch, br'})
Traceback (most recent call last):
  File "C:\Users\Jeff\AppData\Local\Programs\Python\Python35-32\lib\site-packages\tornado\web.py", line 1467, in _execute
    result = method(*self.path_args, **self.path_kwargs)
TypeError: get() takes 1 positional argument but 2 were given
ERROR:tornado.access:500 GET /pictures/1 (::1) 1532.03ms
```



Discussion:
Tornado doesn't support pattern-matching redirects like that. Is that Apache mod_redirect syntax? You can implement something like this yourself using `str.format` syntax like `/photos/{0}` instead of regex substitution syntax like `/photos/\1`:

```
class PatternRedirectHandler(tornado.web.RequestHandler):
    def initialize(self, url, permanent=True):
        self._url = url
        self._permanent = permanent

    def get(self, *args):
        url = self._url.format(*args)
        self.redirect(url, permanent=self._permanent)


app = tornado.web.Application([
    url(r"/pictures/(.*)", PatternRedirectHandler,
        dict(url=r"/photos/{0}")),
])

```

thanks, but this code is on [http://www.tornadoweb.org/en/stable/guide/structure.html#redirection](http://www.tornadoweb.org/en/stable/guide/structure.html#redirection).

Apologies, I didn't know about that code in the docs. @bdarnell did RedirectHandler ever really work that way?

```
app = tornado.web.Application([
    url(r"/pictures/(.*)", tornado.web.RedirectHandler,
        dict(url=r"/")),
])
```

This code will get the same exception in Python 3.5.2 and Python 2.7.12. Tornado 4.4.2.

Hmm, I wonder what I was thinking when I wrote that in the docs. This has never worked (the code in question has been untouched since the first git commit in 2009). We should just remove it from the docs for now, although it's not a bad idea to support this (and the `.format()` syntax is a better fit than the `re.sub` syntax, as you point out). 

no, the problem is not the regex substitution syntax.

```
app = tornado.web.Application([
    url(r"/pictures/(.*)", RedirectHandler,
        dict(url=r"/photos/{0}")),
])
```

His code will raise the same exception.

Not if the `get` method is changed from `def get(self)` to `def get(self, *args)`. Then I'm pretty sure it will work (or at least raise a different exception). 

I can implement this if neither of you has started on it yet.


Resolving patch:
diff --git a/tornado/web.py b/tornado/web.py
--- a/tornado/web.py
+++ b/tornado/web.py
@@ -2191,13 +2191,29 @@ class RedirectHandler(RequestHandler):
         application = web.Application([
             (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
         ])
+
+    `RedirectHandler` supports regular expression substitutions. E.g., to
+    swap the first and second parts of a path while preserving the remainder::
+
+        application = web.Application([
+            (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
+        ])
+
+    The final URL is formatted with `str.format` and the substrings that match
+    the capturing groups. In the above example, a request to "/a/b/c" would be
+    formatted like::
+
+        str.format("/{1}/{0}/{2}", "a", "b", "c")  # -> "/b/a/c"
+
+    Use Python's :ref:`format string syntax <formatstrings>` to customize how
+    values are substituted.
     """
     def initialize(self, url, permanent=True):
         self._url = url
         self._permanent = permanent
 
-    def get(self):
-        self.redirect(self._url, permanent=self._permanent)
+    def get(self, *args):
+        self.redirect(self._url.format(*args), permanent=self._permanent)
 
 
 class StaticFileHandler(RequestHandler):


Test patch:
diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py
--- a/tornado/test/web_test.py
+++ b/tornado/test/web_test.py
@@ -2866,3 +2866,20 @@ def test_non_reversible(self):
     def test_reverse_arguments(self):
         self.assertEqual('/api/v1/foo/bar',
                          url(r'^/api/v1/foo/(\w+)$', None).reverse('bar'))
+
+
+class RedirectHandlerTest(WebTestCase):
+    def get_handlers(self):
+        return [
+            ('/src', WebRedirectHandler, {'url': '/dst'}),
+            (r'/(.*?)/(.*?)/(.*)', WebRedirectHandler, {'url': '/{1}/{0}/{2}'})]
+
+    def test_basic_redirect(self):
+        response = self.fetch('/src', follow_redirects=False)
+        self.assertEqual(response.code, 301)
+        self.assertEqual(response.headers['Location'], '/dst')
+
+    def test_redirect_pattern(self):
+        response = self.fetch('/a/b/c', follow_redirects=False)
+        self.assertEqual(response.code, 301)
+        self.assertEqual(response.headers['Location'], '/b/a/c')