Retry bad nonce errors after generating a new nonce

Retry `urn:ietf:params:acme:error:badNonce` errors as they are defined
as retryable[^1]. Requests failing with a bad nonce error will be
retried three time retried three times before the failure is returned
to the caller.

This implenments the `BytesBody` trait for `bytes::Bytes` as we need
to consume the response body to be able check if the failure was due
to a bad nonce which required parsing the body. The body is only
consumed if the response status is bad request, in all other cases the
body is still lazily consumable.

[^1]: https://datatracker.ietf.org/doc/html/rfc8555/#section-6.5
This commit is contained in:
Nick Spain 2024-09-16 09:03:17 +10:00 committed by Dirkjan Ochtman
parent 643c5859a6
commit e83836b719
1 changed files with 44 additions and 0 deletions

View File

@ -490,6 +490,43 @@ impl Client {
}
async fn post(
&self,
payload: Option<&impl Serialize>,
mut nonce: Option<String>,
signer: &impl Signer,
url: &str,
) -> Result<BytesResponse, Error> {
let mut retries = 3;
loop {
let mut response = self
.post_attempt(payload, nonce.clone(), signer, url)
.await?;
if response.parts.status != StatusCode::BAD_REQUEST {
return Ok(response);
}
let body = response.body.into_bytes().await.map_err(Error::Other)?;
let problem = serde_json::from_slice::<Problem>(&body)?;
if let Some("urn:ietf:params:acme:error:badNonce") = problem.r#type.as_deref() {
retries -= 1;
if retries != 0 {
// Retrieve the new nonce. If it isn't there (it
// should be, the spec requires it) then we will
// manually refresh a new one in `post_attempt`
// due to `nonce` being `None` but getting it from
// the response saves us making that request.
nonce = nonce_from_response(&response);
continue;
}
}
return Ok(BytesResponse {
parts: response.parts,
body: Box::new(body),
});
}
}
async fn post_attempt(
&self,
payload: Option<&impl Serialize>,
nonce: Option<String>,
@ -789,6 +826,13 @@ where
}
}
#[async_trait]
impl BytesBody for Bytes {
async fn into_bytes(&mut self) -> Result<Bytes, Box<dyn StdError + Send + Sync + 'static>> {
Ok(self.to_owned())
}
}
/// Object safe body trait
#[async_trait]
pub trait BytesBody: Send {