Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Reading Urls from file #639

Merged
merged 4 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui anima
Usage: oha [OPTIONS] <URL>

Arguments:
<URL> Target URL.
<URL> Target URL or file with multiple URLs.

Options:
-n <N_REQUESTS>
Expand All @@ -122,6 +122,8 @@ Options:
Note: If qps is specified, burst will be ignored
--rand-regex-url
Generate URL by rand_regex crate but dot is disabled for each query e.g. http://127.0.0.1/[a-z][a-z][0-9]. Currently dynamic scheme, host and port with keep-alive do not work well. See https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html for details of syntax.
--urls-from-file
Read the URLs to query from a file
--max-repeat <MAX_REPEAT>
A parameter for the '--rand-regex-url'. The max_repeat parameter gives the maximum extra repeat counts the x*, x+ and x{n,} operators will become. [default: 4]
--dump-urls <DUMP_URLS>
Expand Down Expand Up @@ -284,6 +286,22 @@ Optionally you can set `--max-repeat` option to limit max repeat count for each

Currently dynamic scheme, host and port with keep-alive are not works well.

## URLs from file feature

You can use `--urls-from-file` to read the target URLs from a file. Each line of this file needs to contain one valid URL as in the example below.

```
http://domain.tld/foo/bar
http://domain.tld/assets/vendors-node_modules_highlight_js_lib_index_js-node_modules_tanstack_react-query_build_modern-3fdf40-591fb51c8a6e.js
http://domain.tld/images/test.png
http://domain.tld/foo/bar?q=test
http://domain.tld/foo
```

Such a file can for example be created from an access log to generate a more realistic load distribution over the different pages of a server.

When this type of URL specification is used, every request goes to a random URL given in the file.

# Contribution

Feel free to help us!
Expand Down
12 changes: 11 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
#[command(version, about, long_about = None)]
#[command(arg_required_else_help(true))]
struct Opts {
#[arg(help = "Target URL.")]
#[arg(help = "Target URL or file with multiple URLs.")]
url: String,
#[arg(
help = "Number of requests to run.",
Expand Down Expand Up @@ -91,6 +91,14 @@ Note: If qps is specified, burst will be ignored",
long
)]
rand_regex_url: bool,

#[arg(
help = "Read the URLs to query from a file",
default_value = "false",
long
)]
urls_from_file: bool,

#[arg(
help = "A parameter for the '--rand-regex-url'. The max_repeat parameter gives the maximum extra repeat counts the x*, x+ and x{n,} operators will become.",
default_value = "4",
Expand Down Expand Up @@ -318,6 +326,8 @@ async fn main() -> anyhow::Result<()> {
})
.collect();
UrlGenerator::new_dynamic(Regex::compile(&dot_disabled, opts.max_repeat)?)
} else if opts.urls_from_file {
UrlGenerator::new_multi_static(&opts.url)?
} else {
UrlGenerator::new_static(Url::parse(&opts.url)?)
};
Expand Down
28 changes: 28 additions & 0 deletions src/url_generator.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use std::{borrow::Cow, string::FromUtf8Error};

use rand::prelude::*;
use rand::seq::SliceRandom;
use rand_regex::Regex;
use thiserror::Error;
use url::{ParseError, Url};

#[derive(Clone, Debug)]
pub enum UrlGenerator {
Static(Url),
MultiStatic(Vec<Url>),
Dynamic(Regex),
}

Expand All @@ -17,20 +22,43 @@ pub enum UrlGeneratorError {
ParseError(ParseError, String),
#[error(transparent)]
FromUtf8Error(#[from] FromUtf8Error),
#[error("No valid URLs found")]
NoURLsError(),
}

impl UrlGenerator {
pub fn new_static(url: Url) -> Self {
Self::Static(url)
}

pub fn new_multi_static(filename: &str) -> io::Result<Self> {
let path = Path::new(filename);
let file = File::open(path)?;
let reader = io::BufReader::new(file);

let urls: Vec<Url> = reader
.lines()
.flatten()
.map(|url_str| Url::parse(&url_str).unwrap())
.collect();

Ok(Self::MultiStatic(urls))
}

pub fn new_dynamic(regex: Regex) -> Self {
Self::Dynamic(regex)
}

pub fn generate<R: Rng>(&self, rng: &mut R) -> Result<Cow<Url>, UrlGeneratorError> {
match self {
Self::Static(url) => Ok(Cow::Borrowed(url)),
Self::MultiStatic(urls) => {
if let Some(random_url) = urls.choose(&mut rand::thread_rng()) {
Ok(Cow::Borrowed(random_url))
} else {
Err(UrlGeneratorError::NoURLsError())
}
}
Self::Dynamic(regex) => {
let generated = Distribution::<Result<String, FromUtf8Error>>::sample(regex, rng)?;
Ok(Cow::Owned(Url::parse(generated.as_str()).map_err(|e| {
Expand Down