Skip to content

Commit

Permalink
feat: implement rate limiting
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.

If it won't succeed after `max_backoff` tries, it will abort fuzzing
current endpoint.

Closes: #23
  • Loading branch information
matusf committed Oct 11, 2023
1 parent c487471 commit 983570f
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 15 deletions.
66 changes: 52 additions & 14 deletions src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ use std::{
path::{Path, PathBuf},
process::ExitCode,
rc::Rc,
time::Instant,
thread,
time::{Duration, Instant},
};

use anyhow::{Context, Error, Result};
use anyhow::{anyhow, Context, Error, Result};
use indexmap::IndexMap;
use openapi_utils::ReferenceOrExt;
use openapiv3::{OpenAPI, ReferenceOr, Response, StatusCode};
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,55 @@ impl Fuzzer {
}
}

fn send_request_with_backoff(
&self,
path_with_params: &str,
method: &str,
payload: &Payload,
) -> Result<ureq::Response> {
let max_backoff = 10;

for backoff in 0..max_backoff {
let response = self.send_request_(path_with_params, method, payload)?;
if !BACKOFF_STATUS_CODES.contains(&response.status()) {
return Ok(response);
}

let wait_seconds = response
.header("Retry-After")
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(1 << backoff);
thread::sleep(Duration::from_millis(wait_seconds * 1000));
}

Err(anyhow!("max backoff threshold reached"))
}

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 983570f

Please sign in to comment.