Skip to content

Commit

Permalink
Merge pull request #4113 from certbot/auto-retry
Browse files Browse the repository at this point in the history
Automatically retry after getting badNonce error
  • Loading branch information
bmw committed Jan 26, 2017
2 parents 578815a + c650c9a commit 2f50dfd
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 4 deletions.
21 changes: 19 additions & 2 deletions acme/acme/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,8 +674,25 @@ def _get_nonce(self, url):
self._add_nonce(self.head(url))
return self._nonces.pop()

def post(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
"""POST object wrapped in `.JWS` and check response."""
def post(self, *args, **kwargs):
"""POST object wrapped in `.JWS` and check response.
If the server responded with a badNonce error, the request will
be retried once.
"""
should_retry = True
while True:
try:
return self._post_once(*args, **kwargs)
except messages.Error as error:
if should_retry and error.code == 'badNonce':
logger.debug('Retrying request after error:\n%s', error)
should_retry = False
else:
raise

def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
data = self._wrap_in_jws(obj, self._get_nonce(url))
kwargs.setdefault('headers', {'Content-Type': content_type})
response = self._send_request('POST', url, data=data, **kwargs)
Expand Down
25 changes: 23 additions & 2 deletions acme/acme/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,9 @@ def setUp(self):
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type

self.all_nonces = [jose.b64encode(b'Nonce'), jose.b64encode(b'Nonce2')]
self.all_nonces = [
jose.b64encode(b'Nonce'),
jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')]
self.available_nonces = self.all_nonces[:]

def send_request(*args, **kwargs):
Expand Down Expand Up @@ -690,7 +692,7 @@ def test_post(self):
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()))

assert not self.available_nonces
self.available_nonces = []
self.assertRaises(errors.MissingNonce, self.net.post,
'uri', self.obj, content_type=self.content_type)
self.net._wrap_in_jws.assert_called_with(
Expand All @@ -706,6 +708,25 @@ def test_post_wrong_post_response_nonce(self):
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
self.obj, content_type=self.content_type)

def test_post_failed_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = messages.Error.with_code('badNonce')

# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
self.obj, content_type=self.content_type)

def test_post_successful_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('badNonce'),
self.checked_response]

# pylint: disable=protected-access
self.net._check_response = check_response
self.assertEqual(self.checked_response, self.net.post(
'uri', self.obj, content_type=self.content_type))

def test_head_get_post_error_passthrough(self):
self.send_request.side_effect = requests.exceptions.RequestException
for method in self.net.head, self.net.get:
Expand Down

0 comments on commit 2f50dfd

Please sign in to comment.