How to avoid "ClientRedirectError: loop detected" error during testing Flask application (and how is it triggered)?
How to avoid "ClientRedirectError: loop detected" error during testing Flask application (and how is it triggered)?
环境:
- Python 3.6.1
- 烧瓶 0.12.2
- Werkzeug 0.14.1
在为我的 Flask 应用程序编写测试时,我发现了以下特性:如果在 Flask 应用程序的测试中,您将被重定向到同一个 url 两次 'in a row' ClientRedirectError: loop detected
将被抛出,即使它在第二次重定向后停止重定向(即实际上没有发生循环)。
考虑以下简化示例:
app.py
from flask import (
Flask,
redirect,
url_for,
session
)
app = Flask(__name__)
app.secret_key = 'improper secret key'
@app.route('/')
def index():
if not session.get('test', False):
session['test'] = True
return redirect(url_for('index'))
else:
return "Hello"
@app.route('/redirection/')
def redirection():
# do something — login user, for example
return redirect(url_for('index'))
test_redirect.py
from unittest import TestCase
from app import app
class RedirectTestCase(TestCase):
def setUp(self):
self.test_client = app.test_client()
def testLoop(self):
response = self.test_client.get('/redirection/', follow_redirects=True)
self.assertTrue('Hello'.encode('ascii') in response.data)
现在,如果我要 运行 测试 — 它会抛出 ClientRedirectError: loop detected
(尽管从代码中可以看出第二次重定向只会发生一次)。
如果我只是 运行 应用程序并转到 /redirection/
— 它会毫无问题地将我带到索引(即 /
),并且不会发生循环。
我在 index()
中需要 if not session.get('test', False):
的原因是因为在我的应用程序中我用它来设置 session
中的一些东西,以防用户访问 [=16= 】 第一次。正如代码中的评论所建议的那样,在我的 'real' 应用程序中 redirection()
是一个让用户登录的功能。
我的问题是:
- 在类似情况下是否有 'right' 方法来克服抛出
ClientRedirectError: loop detected
的问题(即是否有可能使测试 运行 并通过)?[=67=]
- 是否有更好/'more correct' 的方法来为 'first-time' 用户设置
session
中的内容?
- 是否可以将提到的行为视为 werkzeug 中的错误(即实际循环并未发生,但仍然抛出
ClientRedirectError: loop detected
)?
解决方法,我想出了(它没有回答我的问题,仍然):
def testLoop(self):
self.test_client.get('/redirection/') # removed follow_redirects=True
response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly
self.assertTrue('Hello'.encode('ascii') in response.data)
这可能看起来多余,但这只是一个简化的示例(如果将 self.test_client.get('/redirection/')
替换为 self.test_client.post('/login/', data=dict(username='user', password='pass'))
.
之类的内容,可能会更有意义
看来,我现在已经准备好了(发布后差不多有 1.5 个酵母)到 answer my own questions:
有可能通过测试运行。
鉴于上面的代码,问题末尾提供的解决方法可以,但可以缩短为:
def testLoop(self):
response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly
self.assertTrue('Hello'.encode('ascii') in response.data)
在这种特殊情况下,不需要 self.test_client.get('/redirection/')
行。
但是,如果对 /redirection/
中的 session['test']
进行了一些更改,例如:
@app.route('/redirection/')
def redirection():
session['test'] = True
return redirect(url_for('index'))
那么,self.test_client.get('/redirection/')
是必要的,所以测试会变成:
from unittest import TestCase
from app import app
class RedirectTestCase(TestCase):
def setUp(self):
self.test_client = app.test_client()
def testLoop(self):
self.test_client.get('/redirection/')
response = self.test_client.get('/', follow_redirects=True)
self.assertTrue('Hello'.encode('ascii') in response.data)
这个所谓的“解决方法”只是阻止测试连续两次重定向到 /
(这正是导致 ClientRedirectError: loop detected
— 请参阅下面第 3 点的答案)。
是否有更好的方法——将取决于应用程序实施的具体细节。在问题的示例中, index
内部的重定向实际上是不必要的,可以删除:
@app.route('/')
def index():
session['test'] = session.get('test', True)
return "Hello"
顺便说一句,这样的改变也会使初始测试(来自问题)通过。
据我所知,这种行为是 "by design",即:当重定向到相同的 url 时(至少)连续两次。下面是一些例子。
这个,测试不会遇到ClientRedirectError
(因为重定向只发生一次):
isolated_app.py
from flask import (
Flask,
redirect,
url_for,
session
)
app = Flask(__name__)
app.secret_key = 'improper secret key'
@app.route('/')
def index():
session['test'] = session.get('test', 0)
if session['test'] < 1:
session['test'] += 1
return redirect(url_for('index'))
else:
return "Hello"
test.py
from unittest import TestCase
from isolated_app import app
class RedirectTestCase(TestCase):
def setUp(self):
self.test_client = app.test_client()
def testLoop(self):
response = self.test_client.get('/', follow_redirects=True)
self.assertTrue('Hello'.encode('ascii') in response.data)
虽然,如果应用程序的代码将更改为:
from flask import (
Flask,
redirect,
url_for,
session
)
app = Flask(__name__)
app.secret_key = 'improper secret key'
@app.route('/')
def index():
session['test'] = session.get('test', 0)
if session['test'] < 1:
session['test'] += 1
return redirect(url_for('index'))
elif session['test'] < 2:
session['test'] += 1
return redirect(url_for('index'))
else:
return "Hello"
然后测试将失败,抛出 werkzeug.test.ClientRedirectError: loop detected
.
还值得一提的是,重定向循环错误的触发对于 Flask 测试和实际浏览是不同的。如果我们将 运行 最后一个应用程序,并在浏览器中转到 /
- 它会很容易 return Hello
给我们。允许的重定向数量由每个浏览器的开发人员定义(例如,在 FireFox 中,它由 about:config
的 network.http.redirection-limit
首选项定义,20 是默认值)。这是一个例子:
from flask import (
Flask,
redirect,
url_for,
session
)
app = Flask(__name__)
app.secret_key = 'improper secret key'
@app.route('/')
def index():
session['test'] = session.get('test', 0)
while True:
session['test'] = session.get('test', 0)
session['test'] += 1
print(session['test'])
return redirect(url_for('index'))
这将打印 1 2
,然后失败 werkzeug.test.ClientRedirectError: loop detected
,如果我们尝试 运行 测试它。另一方面,如果我们将 运行 这个应用程序并转到 FireFox 中的 /
— 浏览器将向我们显示 The page isn’t redirecting properly
消息,应用程序将在控制台中打印 1 2 3 4 5 6 ... 21
。
环境:
- Python 3.6.1
- 烧瓶 0.12.2
- Werkzeug 0.14.1
在为我的 Flask 应用程序编写测试时,我发现了以下特性:如果在 Flask 应用程序的测试中,您将被重定向到同一个 url 两次 'in a row' ClientRedirectError: loop detected
将被抛出,即使它在第二次重定向后停止重定向(即实际上没有发生循环)。
考虑以下简化示例:
app.py
from flask import (
Flask,
redirect,
url_for,
session
)
app = Flask(__name__)
app.secret_key = 'improper secret key'
@app.route('/')
def index():
if not session.get('test', False):
session['test'] = True
return redirect(url_for('index'))
else:
return "Hello"
@app.route('/redirection/')
def redirection():
# do something — login user, for example
return redirect(url_for('index'))
test_redirect.py
from unittest import TestCase
from app import app
class RedirectTestCase(TestCase):
def setUp(self):
self.test_client = app.test_client()
def testLoop(self):
response = self.test_client.get('/redirection/', follow_redirects=True)
self.assertTrue('Hello'.encode('ascii') in response.data)
现在,如果我要 运行 测试 — 它会抛出 ClientRedirectError: loop detected
(尽管从代码中可以看出第二次重定向只会发生一次)。
如果我只是 运行 应用程序并转到 /redirection/
— 它会毫无问题地将我带到索引(即 /
),并且不会发生循环。
我在 index()
中需要 if not session.get('test', False):
的原因是因为在我的应用程序中我用它来设置 session
中的一些东西,以防用户访问 [=16= 】 第一次。正如代码中的评论所建议的那样,在我的 'real' 应用程序中 redirection()
是一个让用户登录的功能。
我的问题是:
- 在类似情况下是否有 'right' 方法来克服抛出
ClientRedirectError: loop detected
的问题(即是否有可能使测试 运行 并通过)?[=67=] - 是否有更好/'more correct' 的方法来为 'first-time' 用户设置
session
中的内容? - 是否可以将提到的行为视为 werkzeug 中的错误(即实际循环并未发生,但仍然抛出
ClientRedirectError: loop detected
)?
解决方法,我想出了(它没有回答我的问题,仍然):
def testLoop(self):
self.test_client.get('/redirection/') # removed follow_redirects=True
response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly
self.assertTrue('Hello'.encode('ascii') in response.data)
这可能看起来多余,但这只是一个简化的示例(如果将 self.test_client.get('/redirection/')
替换为 self.test_client.post('/login/', data=dict(username='user', password='pass'))
.
看来,我现在已经准备好了(发布后差不多有 1.5 个酵母)到 answer my own questions:
有可能通过测试运行。
鉴于上面的代码,问题末尾提供的解决方法可以,但可以缩短为:def testLoop(self): response = self.test_client.get('/', follow_redirects=True) # navigating to '/', directly self.assertTrue('Hello'.encode('ascii') in response.data)
在这种特殊情况下,不需要
self.test_client.get('/redirection/')
行。 但是,如果对/redirection/
中的session['test']
进行了一些更改,例如:@app.route('/redirection/') def redirection(): session['test'] = True return redirect(url_for('index'))
那么,
self.test_client.get('/redirection/')
是必要的,所以测试会变成:from unittest import TestCase from app import app class RedirectTestCase(TestCase): def setUp(self): self.test_client = app.test_client() def testLoop(self): self.test_client.get('/redirection/') response = self.test_client.get('/', follow_redirects=True) self.assertTrue('Hello'.encode('ascii') in response.data)
这个所谓的“解决方法”只是阻止测试连续两次重定向到
/
(这正是导致ClientRedirectError: loop detected
— 请参阅下面第 3 点的答案)。是否有更好的方法——将取决于应用程序实施的具体细节。在问题的示例中,
index
内部的重定向实际上是不必要的,可以删除:@app.route('/') def index(): session['test'] = session.get('test', True) return "Hello"
顺便说一句,这样的改变也会使初始测试(来自问题)通过。
据我所知,这种行为是 "by design",即:当重定向到相同的 url 时(至少)连续两次。下面是一些例子。
这个,测试不会遇到
ClientRedirectError
(因为重定向只发生一次):isolated_app.py
from flask import ( Flask, redirect, url_for, session ) app = Flask(__name__) app.secret_key = 'improper secret key' @app.route('/') def index(): session['test'] = session.get('test', 0) if session['test'] < 1: session['test'] += 1 return redirect(url_for('index')) else: return "Hello"
test.py
from unittest import TestCase from isolated_app import app class RedirectTestCase(TestCase): def setUp(self): self.test_client = app.test_client() def testLoop(self): response = self.test_client.get('/', follow_redirects=True) self.assertTrue('Hello'.encode('ascii') in response.data)
虽然,如果应用程序的代码将更改为:
from flask import ( Flask, redirect, url_for, session ) app = Flask(__name__) app.secret_key = 'improper secret key' @app.route('/') def index(): session['test'] = session.get('test', 0) if session['test'] < 1: session['test'] += 1 return redirect(url_for('index')) elif session['test'] < 2: session['test'] += 1 return redirect(url_for('index')) else: return "Hello"
然后测试将失败,抛出
werkzeug.test.ClientRedirectError: loop detected
.还值得一提的是,重定向循环错误的触发对于 Flask 测试和实际浏览是不同的。如果我们将 运行 最后一个应用程序,并在浏览器中转到
/
- 它会很容易 returnHello
给我们。允许的重定向数量由每个浏览器的开发人员定义(例如,在 FireFox 中,它由about:config
的network.http.redirection-limit
首选项定义,20 是默认值)。这是一个例子:from flask import ( Flask, redirect, url_for, session ) app = Flask(__name__) app.secret_key = 'improper secret key' @app.route('/') def index(): session['test'] = session.get('test', 0) while True: session['test'] = session.get('test', 0) session['test'] += 1 print(session['test']) return redirect(url_for('index'))
这将打印
1 2
,然后失败werkzeug.test.ClientRedirectError: loop detected
,如果我们尝试 运行 测试它。另一方面,如果我们将 运行 这个应用程序并转到 FireFox 中的/
— 浏览器将向我们显示The page isn’t redirecting properly
消息,应用程序将在控制台中打印1 2 3 4 5 6 ... 21
。