Skip to content

Commit

Permalink
feat: implement exponential backoff
Browse files Browse the repository at this point in the history
When 429 (Too Many Requests) or 503 (Service unavailable) status codes
are received, the fuzzer will try to resend the request after number of
seconds specified in `Retry-After` header. If the header is not present
it will use exponential backoff algorithm with a start value of  1
second.
  • Loading branch information
matusf committed Oct 10, 2023
1 parent c487471 commit d2ffa87
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 14 deletions.
61 changes: 48 additions & 13 deletions src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::{
path::{Path, PathBuf},
process::ExitCode,
rc::Rc,
time::Instant,
thread,
time::{Duration, Instant},
};

use anyhow::{Context, Error, Result};
Expand All @@ -27,6 +28,8 @@ use crate::{
stats::Stats,
};

const BACKOFF_STATUS_CODES: [u16; 2] = [429, 503];

#[derive(Debug, Deserialize, Serialize)]
pub struct FuzzResult<'a> {
pub payload: Payload,
Expand Down Expand Up @@ -126,17 +129,11 @@ impl Fuzzer {
&any_with::<Payload>(Rc::new(ArbitraryParameters::new(operation))),
|payload| {
let now = Instant::now();
let response = Fuzzer::send_request(
&self.url,
path_with_params.to_owned(),
method,
&payload,
&self.extra_headers,
&self.agent,
)
.map_err(|e| {
TestCaseError::Fail(format!("unable to send request: {e}").into())
})?;
let response = self
.send_request_with_backoff(path_with_params, method, &payload)
.map_err(|e| {
TestCaseError::Fail(format!("unable to send request: {e}").into())
})?;

let is_expected_response = self.is_expected_response(&response, &responses);
stats.borrow_mut().times.push(now.elapsed().as_micros());
Expand Down Expand Up @@ -170,14 +167,52 @@ impl Fuzzer {
}
}

fn send_request_with_backoff(
&self,
path_with_params: &str,
method: &str,
payload: &Payload,
) -> Result<ureq::Response> {
let mut backoff = 0;
loop {
let response = self.send_request_(path_with_params, method, payload)?;
if !BACKOFF_STATUS_CODES.contains(&response.status()) {
return Ok(response);
}
let time = response
.header("Retry-After")
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(1 << backoff);
thread::sleep(Duration::from_millis(time * 1000));
backoff += 1;
}
}

fn send_request_(
&self,
path_with_params: &str,
method: &str,
payload: &Payload,
) -> Result<ureq::Response> {
Fuzzer::send_request(
&self.url,
path_with_params,
method,
&payload,
&self.extra_headers,
&self.agent,
)
}

pub fn send_request(
url: &Url,
mut path_with_params: String,
path_with_params: &str,
method: &str,
payload: &Payload,
extra_headers: &HashMap<String, String>,
agent: &Agent,
) -> Result<ureq::Response> {
let mut path_with_params = path_with_params.to_owned();
for (name, value) in payload.path_params().iter() {
path_with_params = path_with_params.replace(&format!("{{{name}}}"), value);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ fn main() -> Result<ExitCode> {

let response = Fuzzer::send_request(
&args.url.into(),
result.path.to_owned(),
result.path,
result.method,
&result.payload,
&args.header.into_iter().map(Into::into).collect(),
Expand Down

0 comments on commit d2ffa87

Please sign in to comment.