From e153b94190359f9d7dbdd0753d182bf672baab9d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 21 May 2011 12:44:58 -0400 Subject: [PATCH] Improved redirection behavior for 301/303 --- requests/api.py | 5 ++++- requests/models.py | 22 ++++++++++++++++++---- test_requests.py | 32 +++++++++++++++++++------------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/requests/api.py b/requests/api.py index 53577b4e..61da42ce 100644 --- a/requests/api.py +++ b/requests/api.py @@ -31,13 +31,16 @@ def request(method, url, **kwargs): :param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload. :param auth: (optional) AuthObject to enable Basic HTTP Auth. :param timeout: (optional) Float describing the timeout of the request. + :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. """ data = kwargs.pop('data', dict()) or kwargs.pop('params', dict()) r = Request(method=method, url=url, data=data, headers=kwargs.pop('headers', {}), cookiejar=kwargs.pop('cookies', None), files=kwargs.pop('files', None), auth=kwargs.pop('auth', auth_manager.get_auth(url)), - timeout=kwargs.pop('timeout', requests.timeout)) + timeout=kwargs.pop('timeout', requests.timeout), + allow_redirects=kwargs.pop('allow_redirects', requests.timeout) + ) r.send() return r.response diff --git a/requests/models.py b/requests/models.py index 2f5215fe..3a00d8c9 100644 --- a/requests/models.py +++ b/requests/models.py @@ -31,7 +31,8 @@ class Request(object): _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE') def __init__(self, url=None, headers=dict(), files=None, method=None, - data=dict(), auth=None, cookiejar=None, timeout=None, redirect=True): + data=dict(), auth=None, cookiejar=None, timeout=None, + redirect=True, allow_redirects=False): socket.setdefaulttimeout(timeout) @@ -48,7 +49,9 @@ class Request(object): #: True if :class:`Request ` is part of a redirect chain (disables history #: and HTTPError storage). self.redirect = redirect - + #: Set to True if full redirects are allowed (e.g. re-POST-ing of data at new ``Location``) + self.allow_redirects = allow_redirects + if hasattr(data, 'items'): for (k, v) in data.items(): self.data.update({ @@ -163,14 +166,25 @@ class Request(object): if self.redirect: - while 'location' in r.headers: + while ( + ('location' in r.headers) and + ((self.method in ('GET', 'HEAD')) or + (r.status_code is 303) or + (self.allow_redirects)) + ): history.append(r) url = r.headers['location'] + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 + if r.status_code is 303: + method = 'GET' + else: + method = self.method + request = Request( - url, self.headers, self.files, self.method, + url, self.headers, self.files, method, self.data, self.auth, self.cookiejar, redirect=False ) request.send() diff --git a/test_requests.py b/test_requests.py index f58fee50..aa1b40cb 100755 --- a/test_requests.py +++ b/test_requests.py @@ -71,35 +71,41 @@ class RequestsTestSuite(unittest.TestCase): def test_POSTBIN_GET_POST_FILES(self): bin = requests.post('http://www.postbin.org/') - self.assertEqual(bin.status_code, 200) + self.assertEqual(bin.status_code, 302) - post = requests.post(bin.url, data={'some': 'data'}) + post_url = bin.headers['location'] + post = requests.post(post_url, data={'some': 'data'}) self.assertEqual(post.status_code, 201) - post2 = requests.post(bin.url, files={'some': open('test_requests.py')}) + post2 = requests.post(post_url, files={'some': open('test_requests.py')}) self.assertEqual(post2.status_code, 201) - post3 = requests.post(bin.url, data='[{"some": "json"}]') + post3 = requests.post(post_url, data='[{"some": "json"}]') self.assertEqual(post.status_code, 201) + def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self): bin = requests.post('http://www.postbin.org/') + self.assertEqual(bin.status_code, 302) - self.assertEqual(bin.status_code, 200) + post_url = bin.headers['location'] - post2 = requests.post(bin.url, files={'some': open('test_requests.py')}, data={'some': 'data'}) + post2 = requests.post(post_url, files={'some': open('test_requests.py')}, data={'some': 'data'}) self.assertEqual(post2.status_code, 201) def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self): bin = requests.post('http://www.postbin.org/') - self.assertEqual(bin.status_code, 200) + self.assertEqual(bin.status_code, 302) - post2 = requests.post(bin.url, files={'some': open('test_requests.py')}, - headers={'User-Agent': 'requests-tests'}) + post_url = bin.headers['location'] + + post2 = requests.post(post_url, files={'some': open('test_requests.py')}, + headers = {'User-Agent': 'requests-tests'}) self.assertEqual(post2.status_code, 201) + def test_nonzero_evaluation(self): r = requests.get('http://google.com/some-404-url') self.assertEqual(bool(r), False) @@ -155,17 +161,17 @@ class RequestsTestSuite(unittest.TestCase): r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth) self.assertEquals(r.status_code, 401) - + def test_settings(self): with requests.settings(timeout=0.0001): self.assertRaises(requests.Timeout, requests.get, 'http://google.com') - + with requests.settings(timeout=10): requests.get('http://google.com') - + def test_nonurlencoded_post_data(self): requests.post('http://google.com', data='foo') - + if __name__ == '__main__':